<#

.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.