<#
.SYNOPSIS
Configure AVD session hosts for RDP Shortpath over PUBLIC networks.
.DESCRIPTION
- Enables 'Use either UDP or TCP' for RDP transport (registry / GPO-equivalent).
- Ensures client-side UDP is not disabled (harmless on server; useful if run on clients).
- Adds explicit outbound firewall allows for STUN/TURN (UDP 3478, TCP 443).
- Optionally restarts TermService (or reboot later).
.PARAMETER RestartNow
Switch to restart the Remote Desktop Services service immediately.
.EXAMPLE
.\Enable-AvdShortpathPublic.ps1 -RestartNow
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[switch]$RestartNow
)
function Ensure-RegistryValue {
param(
[Parameter(Mandatory)]
[string]$Path,
[Parameter(Mandatory)]
[string]$Name,
[Parameter(Mandatory)]
[ValidateSet('DWord','String','QWord','Binary','MultiString','ExpandString')]
[string]$Type,
[Parameter(Mandatory)]
$Value
)
if (-not (Test-Path $Path)) {
New-Item -Path $Path -Force | Out-Null
}
$existing = (Get-ItemProperty -Path $Path -Name $Name -ErrorAction SilentlyContinue).$Name
if ($null -eq $existing -or $existing -ne $Value) {
New-ItemProperty -Path $Path -Name $Name -PropertyType $Type -Value $Value -Force | Out-Null
Write-Host "Set $Path\$Name = $Value ($Type)"
} else {
Write-Host "No change for $Path\$Name (already $Value)."
}
}
Write-Host "=== Configuring RDP Shortpath (PUBLIC networks) on this machine ===`n"
# 1) SERVER SIDE: Allow UDP for RDP transport (GPO: Select RDP transport protocols = 'Use both UDP and TCP')
# Registry: HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\SelectTransport
# Value 1 = Use only TCP; Value 2 = Use either UDP or TCP.
# (If the value is missing, default is UDP enabled; we set it explicitly to 2.)
$serverKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'
Ensure-RegistryValue -Path $serverKey -Name 'SelectTransport' -Type DWord -Value 2
# 2) CLIENT SIDE SETTING (optional but safe): ensure UDP is not disabled on Windows client policy
# HKLM\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client\fClientDisableUDP = 0 (or not present)
$clientKey = 'HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client'
Ensure-RegistryValue -Path $clientKey -Name 'fClientDisableUDP' -Type DWord -Value 0
# 3) FIREWALL: allow outbound STUN/TURN used by Shortpath public networks
# UDP 3478 for STUN/TURN; TCP 443 for TURN fallback/control
# (Outbound is allow-by-default on Windows; rules added for hardened egress scenarios.)
$rules = @(
@{ Name='AVD Shortpath - STUN/TURN UDP 3478 Out'; Dir='Outbound'; Proto='UDP'; RPort='3478' },
@{ Name='AVD Shortpath - TURN TCP 443 Out'; Dir='Outbound'; Proto='TCP'; RPort='443' }
)
foreach ($r in $rules) {
$existing = Get-NetFirewallRule -DisplayName $r.Name -ErrorAction SilentlyContinue
if (-not $existing) {
New-NetFirewallRule -DisplayName $r.Name `
-Direction $r.Dir -Action Allow -Enabled True `
-Protocol $r.Proto -RemotePort $r.RPort | Out-Null
Write-Host "Created firewall rule: $($r.Name)"
} else {
Write-Host "Firewall rule already exists: $($r.Name)"
}
}
# 4) Optional: Restart RDP service now (policy often applies after restart or reboot)
if ($RestartNow) {
try {
Write-Host "Restarting Remote Desktop Services (TermService)..."
Restart-Service -Name TermService -Force -ErrorAction Stop
Write-Host "TermService restarted."
}
catch {
Write-Warning "Couldn't restart TermService: $($_.Exception.Message). A reboot may be required."
}
}
else {
Write-Host "`nNote: For reliability, reboot this session host (or run with -RestartNow) to apply changes."
}
# 5) Output summary
Write-Host "`n=== Current Values ==="
(Get-ItemProperty -Path $serverKey -Name 'SelectTransport' -ErrorAction SilentlyContinue) | Format-List SelectTransport
(Get-ItemProperty -Path $clientKey -Name 'fClientDisableUDP' -ErrorAction SilentlyContinue) | Format-List fClientDisableUDP
Get-NetFirewallRule -DisplayName 'AVD Shortpath - *' | Select-Object DisplayName,Enabled,Direction,Action,Profile | Format-Table -Auto
<#
.SYNOPSIS
Roll back AVD RDP Shortpath (public networks) changes:
- Remove egress firewall rules for STUN/TURN
- Revert RDP/UDP policy registry values to OS defaults
- (Optional) Remove inbound UDP/3390 rule if previously created for managed networks
- (Optional) Restart TermService
.PARAMETER RemoveManagedInbound
Also remove the managed-networks inbound rule 'RemoteDesktop-UserMode-In-RDPShortpath-UDP' (UDP/3390).
.PARAMETER RestartNow
Restart the Remote Desktop Services (TermService) after rollback.
.PARAMETER BackupPath
Folder to save a JSON backup of any values/rules found. Default: "C:\ProgramData\AVD\RollbackBackup"
.EXAMPLE
.\Rollback-AvdShortpath.ps1 -RestartNow
.EXAMPLE
.\Rollback-AvdShortpath.ps1 -RemoveManagedInbound -WhatIf
#>
[CmdletBinding(SupportsShouldProcess=$true)]
param(
[switch]$RemoveManagedInbound,
[switch]$RestartNow,
[string]$BackupPath = 'C:\ProgramData\AVD\RollbackBackup'
)
#region Helpers
function Ensure-Folder {
param([Parameter(Mandatory)] [string]$Path)
if (-not (Test-Path -LiteralPath $Path)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null }
}
function Backup-ObjectAsJson {
param(
[Parameter(Mandatory)] $Object,
[Parameter(Mandatory)] [string]$FileName
)
Ensure-Folder -Path $BackupPath
$full = Join-Path $BackupPath $FileName
$Object | ConvertTo-Json -Depth 6 | Out-File -FilePath $full -Encoding UTF8
Write-Host "Backup saved: $full"
}
#endregion
Write-Host "=== ROLLBACK: AVD RDP Shortpath (Public networks) ===`n"
# 1) Remove created outbound firewall rules for STUN/TURN
$egressRules = @(
'AVD Shortpath - STUN/TURN UDP 3478 Out',
'AVD Shortpath - TURN TCP 443 Out'
)
$existingEgress = foreach ($name in $egressRules) {
Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue
}
if ($existingEgress) {
Backup-ObjectAsJson -Object ($existingEgress | Select-Object * ) -FileName 'FirewallRules_Egress_Backup.json'
foreach ($rule in $existingEgress) {
if ($PSCmdlet.ShouldProcess($rule.DisplayName, 'Remove-NetFirewallRule')) {
$rule | Remove-NetFirewallRule -ErrorAction SilentlyContinue
Write-Host "Removed firewall rule: $($rule.DisplayName)"
}
}
} else {
Write-Host "Egress rules not found (nothing to remove)."
}
# 2) Optional: remove common inbound rule for managed networks (UDP/3390)
if ($RemoveManagedInbound) {
$inboundRuleName = 'RemoteDesktop-UserMode-In-RDPShortpath-UDP'
$inbound = Get-NetFirewallRule -DisplayName $inboundRuleName -ErrorAction SilentlyContinue
if ($inbound) {
Backup-ObjectAsJson -Object ($inbound | Select-Object * ) -FileName 'FirewallRule_Inbound_3390_Backup.json'
if ($PSCmdlet.ShouldProcess($inboundRuleName, 'Remove-NetFirewallRule')) {
$inbound | Remove-NetFirewallRule -ErrorAction SilentlyContinue
Write-Host "Removed inbound managed-networks rule: $inboundRuleName"
}
} else {
Write-Host "Managed-networks inbound rule not found (skipping)."
}
} else {
Write-Host "Skipping inbound (managed-networks) rule removal. Use -RemoveManagedInbound to include."
}
# 3) Revert policy registry values to OS defaults (delete values if present)
$regChanges = @(
@{ Path='HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services'; Name='SelectTransport' }, # RDP transport policy
@{ Path='HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\Terminal Services\Client'; Name='fClientDisableUDP' } # Client UDP disable
)
$regBackup = @()
foreach ($item in $regChanges) {
$val = $null
try { $val = (Get-ItemProperty -Path $item.Path -Name $item.Name -ErrorAction Stop).$($item.Name) } catch {}
if ($null -ne $val) {
$regBackup += [pscustomobject]@{
Path = $item.Path
Name = $item.Name
Value = $val
}
if ($PSCmdlet.ShouldProcess("$($item.Path)\$($item.Name)", 'Remove-ItemProperty')) {
Remove-ItemProperty -Path $item.Path -Name $item.Name -ErrorAction SilentlyContinue
Write-Host "Deleted policy value: $($item.Path)\$($item.Name) (reverts to OS default)"
}
} else {
Write-Host "Policy value not set (already at OS default): $($item.Path)\$($item.Name)"
}
}
if ($regBackup.Count -gt 0) {
Backup-ObjectAsJson -Object $regBackup -FileName 'RegistryPolicy_Backup.json'
}
# 4) Show resulting state
Write-Host "`n=== Post-Rollback State ==="
foreach ($name in $egressRules) {
$r = Get-NetFirewallRule -DisplayName $name -ErrorAction SilentlyContinue
Write-Host (" {0}: {1}" -f $name, ($(if ($r) {'present'} else {'absent'})))
}
if ($RemoveManagedInbound) {
$r = Get-NetFirewallRule -DisplayName 'RemoteDesktop-UserMode-In-RDPShortpath-UDP' -ErrorAction SilentlyContinue
Write-Host (" Inbound UDP/3390 rule: {0}" -f ($(if ($r) {'present'} else {'absent'})))
}
foreach ($item in $regChanges) {
$exists = $false
try { $null = (Get-ItemProperty -Path $item.Path -Name $item.Name -ErrorAction Stop); $exists = $true } catch {}
Write-Host (" {0}\{1}: {2}" -f $item.Path, $item.Name, ($(if ($exists) {'present'} else {'not set (default)'})))
}
# 5) Optionally restart TermService (or reboot later)
if ($RestartNow) {
try {
Write-Host "`nRestarting Remote Desktop Services (TermService)..."
Restart-Service -Name TermService -Force -ErrorAction Stop
Write-Host "TermService restarted."
} catch {
Write-Warning "Couldn't restart TermService: $($_.Exception.Message). A reboot may be required."
}
} else {
Write-Host "`nTip: Reboot this host (or rerun with -RestartNow) to ensure policies fully revert."
}
Write-Host "`nRollback complete."
[Window Title]
Remote Desktop
[Content]
Your connection quality is good and UDP is enabled.
[^] Hide details [Send Diagnostics] [Disconnect] [OK]
[Expanded Information]
Timestamp (UTC): 2025-10-07T09:12:57.505Z
Activity ID: 8da7db13-068b-48b7-b5a9-bca87ee60000
[Client details]
Client version: 1.2.6513.0 (x64)
Local OS: Microsoft Windows 11 Business x64 (10.0, Build 26200)
[Network details]
Transport protocol: UDP
Round-trip time: 19 ms
Available bandwidth: Greater than 806 Mbps
Frame rate: 1 FPS
[Graphics details]
Codecs used: AVC; RemoteFX Text; Cache
Client compatibility:
Media Foundation: Yes
GPU Presentation: Yes
AVC: CPU; GPU
HEVC: GPU
[Remote computer details]
Remote session type: Remote desktop
Gateway name: Not in use
Gateway logon method: Not in use
Remote computer: avdaad-0
Identity verification method: Server Certificate (View certificate)
Press Ctrl+C to copy.
