Last active
October 21, 2025 17:20
-
-
Save idwpan/420d7d5e702240026bd2b65cf33515fe to your computer and use it in GitHub Desktop.
PowerShell script to auto update the qBittorrent listening port when ProtonVPN is connected
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ######################################################################################################################## | |
| # Script to Synchronize qBittorrent Listening Port with ProtonVPN assigned port on Windows. | |
| # | |
| # Description: | |
| # This PowerShell script automates the synchronization of qBittorrent's listening port with the | |
| # port number assigned by ProtonVPN's port forwarding feature. It continuously monitors the | |
| # system to check if ProtonVPN is connected and if qBittorrent is running. When both conditions | |
| # are met, it retrieves the latest port number from ProtonVPN's notifications and updates | |
| # qBittorrent's listening port via its Web API if there's a mismatch. | |
| # | |
| # Usage: | |
| # 1. Ensure qBittorrent's Web UI is enabled and note the username and password. | |
| # 2. Encrypt and store your qBittorrent password: | |
| # - Convert your password to a secure string and export it: | |
| # `$securePassword = ConvertTo-SecureString "YourPassword" -AsPlainText -Force` | |
| # `$securePassword | ConvertFrom-SecureString | Out-File "$env:LOCALAPPDATA\qBittorrentPassword.txt"` | |
| # 3. Adjust the qBittorrentURL and qBittorrentUser variables as needed. | |
| # 4. Run the script, when ProtonVPN is connected the qBittorrent Listening Port should be automatically updated. | |
| # 5. Optional - Configure the script to run automatically via Task Scheduler. | |
| # | |
| # Disclaimer: | |
| # Use this script responsibly and ensure compliance with all applicable laws and terms of service | |
| # of the software and services involved. | |
| ######################################################################################################################## | |
| # Define qBittorrent API credentials and URL | |
| $qBittorrentURL = "http://localhost:8080" | |
| $qBittorrentUser = "admin" | |
| # Function to retrieve the secure password from an encrypted file | |
| function Get-SecurePassword { | |
| # Path to the encrypted password file | |
| $passwordFilePath = "$env:LOCALAPPDATA\qBittorrentPassword.txt" | |
| # Check if the password file exists | |
| if (Test-Path $passwordFilePath) { | |
| # Read the encrypted password and convert it to a SecureString | |
| $encryptedPassword = Get-Content -Path $passwordFilePath | |
| $securePassword = $encryptedPassword | ConvertTo-SecureString | |
| return $securePassword | |
| } | |
| else { | |
| Write-Error "Password file not found at $passwordFilePath" | |
| return $null | |
| } | |
| } | |
| $qBittorrentPassword = Get-SecurePassword | |
| # Install and import the PSSQLite module for SQLite database interaction | |
| if (-not (Get-Module -Name PSSQLite -ListAvailable)) { | |
| Install-Module -Name PSSQLite -Scope CurrentUser -Force | |
| } | |
| $modulePath = "$env:USERPROFILE\Documents\PowerShell\Modules\PSSQLite" | |
| Import-Module -Name $modulePath | |
| # Function to check if qBittorrent process is running | |
| function Get-qBittorrentRunning { | |
| return Get-Process -Name 'qbittorrent' -ErrorAction SilentlyContinue | |
| } | |
| # Function to check if ProtonVPN is connected by checking network adapters | |
| function Get-ProtonVPNConnected { | |
| # Adjust the adapter name pattern if necessary | |
| $adapter = Get-NetAdapter | Where-Object { | |
| ($_.Name -Match 'ProtonVPN') -and ($_.Status -eq 'Up') | |
| } | |
| return $null -ne $adapter | |
| } | |
| # Function to get the latest port number from ProtonVPN notifications | |
| function Get-ProtonVPNPort { | |
| # Define the paths for the notifications database | |
| $databasePath = "$env:LOCALAPPDATA\Microsoft\Windows\Notifications\wpndatabase.db" | |
| $databaseCopy = "$env:TEMP\wpndatabase_copy.db" | |
| # Copy the database to a temporary location to avoid file locks | |
| Copy-Item -Path $databasePath -Destination $databaseCopy -Force #-ErrorAction SilentlyContinue | |
| if (-not (Test-Path $databaseCopy)) { | |
| Write-Error "Failed to copy the notifications database." | |
| return $null | |
| } | |
| # Query the Notification table | |
| $query = "SELECT * FROM Notification;" | |
| try { | |
| $notifications = Invoke-SqliteQuery -DataSource $databaseCopy -Query $query | |
| } | |
| catch { | |
| Write-Error "Failed to query the notifications database." | |
| return $null | |
| } | |
| # Initialize the variable to store the port number | |
| $portNumber = $null | |
| # Loop backwards through the notifications to find the latest port | |
| for ($i = $notifications.Count - 1; $i -ge 0; $i--) { | |
| $notification = $notifications[$i] | |
| # Extract the Payload column | |
| $payloadBytes = $notification.Payload | |
| # Convert bytes to string using UTF8 encoding | |
| $payloadString = [System.Text.Encoding]::UTF8.GetString($payloadBytes) | |
| # Parse the payload as XML | |
| try { | |
| $xml = [xml]$payloadString | |
| # Navigate the XML to find the message content | |
| $messageNodes = $xml.Toast.Visual.Binding.Text | |
| $message = $messageNodes -join " " | |
| # Check if the message starts with "Active port:" | |
| if ($message.StartsWith("Active port:")) { | |
| # Extract the number after "Active port:" | |
| if ($message -match "Active port:\s*(\d+)") { | |
| $portNumber = [int]$matches[1] | |
| # Exit the loop since we've found the desired notification | |
| break | |
| } | |
| } | |
| } | |
| catch { | |
| # Ignore parsing errors for non-XML payloads | |
| continue | |
| } | |
| } | |
| # Check if the port number was found | |
| if ($null -ne $portNumber) { | |
| return $portNumber | |
| } | |
| else { | |
| Write-Host "No matching port notification found." | |
| return $null | |
| } | |
| } | |
| # Function to get qBittorrent session for authentication | |
| function Get-qBittorrentSession { | |
| param ( | |
| [String]$qBittorrentUrl, | |
| [String]$username, | |
| [SecureString]$password | |
| ) | |
| $passwordPlain = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto( | |
| [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($password) | |
| ) | |
| $session = New-Object Microsoft.PowerShell.Commands.WebRequestSession | |
| try { | |
| Invoke-RestMethod -Uri "$qBittorrentUrl/api/v2/auth/login" -Method Post -WebSession $session -Body @{ | |
| username = $username | |
| password = $passwordPlain | |
| } | Out-Null | |
| return $session | |
| } | |
| catch { | |
| Write-Error "Failed to authenticate to qBittorrent. Please check your credentials and URL." | |
| return $null | |
| } | |
| } | |
| # Function to retrieve the current listening port from qBittorrent preferences | |
| function Get-qBittorrentPort { | |
| param ( | |
| [String]$qBittorrentUrl, | |
| [String]$username, | |
| [SecureString]$password | |
| ) | |
| $session = Get-qBittorrentSession -qBittorrentUrl $qBittorrentUrl -username $username -password $password | |
| if (-not $session) { return $null } | |
| try { | |
| $preferences = Invoke-RestMethod -Uri "$qBittorrentUrl/api/v2/app/preferences" -Method Get -WebSession $session | |
| return $preferences.listen_port | |
| } | |
| catch { | |
| Write-Error "An error occurred while retrieving the port." | |
| return $null | |
| } | |
| } | |
| # Function to set the listening port in qBittorrent preferences | |
| function Set-qBittorrentPort { | |
| param ( | |
| [String]$qBittorrentUrl, | |
| [String]$username, | |
| [SecureString]$password, | |
| [int]$port | |
| ) | |
| $session = Get-qBittorrentSession -qBittorrentUrl $qBittorrentUrl -username $username -password $password | |
| if (-not $session) { return } | |
| try { | |
| Invoke-RestMethod -Uri "$qBittorrentUrl/api/v2/app/setPreferences" -Method Post -WebSession $session -Body @{ | |
| json = "{`"listen_port`": $port}" | |
| } | Out-Null | |
| } | |
| catch { | |
| Write-Error "An error occurred while setting the port." | |
| } | |
| } | |
| # Main routine to synchronize qBittorrent port with ProtonVPN port | |
| # Check if ProtonVPN is connected | |
| if (-not (Get-ProtonVPNConnected)) { | |
| Write-Host "ProtonVPN not connected." | |
| return | |
| } | |
| # Check if qBittorrent is running | |
| if (-not (Get-qBittorrentRunning)) { | |
| Write-Host "qBittorrent not running." | |
| return | |
| } | |
| # Get the latest port from ProtonVPN | |
| $protonPort = Get-ProtonVPNPort | |
| if ($null -eq $protonPort) { | |
| Write-Host "Could not determine ProtonVPN port." | |
| return | |
| } | |
| # Get the current port from qBittorrent | |
| $qbitPort = Get-qBittorrentPort -qBittorrentUrl $qBittorrentURL -username $qBittorrentUser -password $qBittorrentPassword | |
| if ($null -eq $qbitPort) { | |
| Write-Host "Could not determine current qBittorrent port." | |
| return | |
| } | |
| # Update the qBittorrent port if it differs | |
| if ($protonPort -ne $qbitPort) { | |
| Set-qBittorrentPort -qBittorrentUrl $qBittorrentURL -username $qBittorrentUser -password $qBittorrentPassword -port $protonPort | |
| Write-Host "Configured qBittorrent port to: $protonPort" | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Hi!
I've been tinkering around with this, resolving for example my custom documents location so the script can find the PSSQLite module, question,
does this work for the destop version of qbittorrent as well?
I currently have both Proton VPN and qbittorent as desktop versions, thank you in advance