Skip to content

Instantly share code, notes, and snippets.

@idwpan
Last active January 23, 2026 23:45
Show Gist options
  • Select an option

  • Save idwpan/420d7d5e702240026bd2b65cf33515fe to your computer and use it in GitHub Desktop.

Select an option

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
########################################################################################################################
# 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"
}
@martsg666
Copy link

Hi, thank you for the script. I've created a fork with logging and a few other things inspired by yours. Instead of looking at the notifications, I look at the ProtonVPN logfile. I had issue with the notifications not getting in the database sometimes. I've also added a wrapper so you don't get the flashing window when scheduling it. Thanks again !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment