Problem:
Ein Terminal Server (RDS Session Host) zeigt nach einiger Zeit verschiedene Effekte, wie z.B. „schwarzer Bildschirm nach der Anmeldung“, „nicht mehr funktionierendes Startmenü“, allgemeine Performance- oder Anmeldeprobleme.
Dies betrifft übrigens nicht zwingend nur RDS Sitzungshosts, sondern prinzipiell alle Windows Server 2016 Maschinen, auf Terminalservern (insbesondere mit User Profile Disks, UPDs) ist das ganze aufgrund potenziell größerer Anmelde-/Benutzeranzahl nur deutlich wahrscheinlicher anzutreffen.
Lösung:
Schuld könnte ein Bug sein, welcher dafür sorgt, dass bei der Benutzeranmeldung erstellte Firewall-Regeln beim löschen des Benutzerprofils (hier kommen die UPDs ins Spiel, unter Einsatz dieser wird das Profil nämlich bei der Abmeldung automatisch wieder vom Server gelöscht) nicht mehr entfernt werden.
Das verursacht einen Registry-Bloat in
HKLM:\System\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules
und/oder
HKLM:\System\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\RestrictedServices\Configurable\System
Sind diese Keys bereits „überfüllt“, kann es gut sein, dass sich die Werte nicht mehr per GUI (regedit) entfernen lassen – der Key lädt schlichtweg Ewigkeiten und stürzt bei zu wenig RAM ab.
Abhilfe schafft unser kleines PowerShell-Script, das die Einträge nacheinander löscht:
$profiles = get-wmiobject -class win32_userprofile
Clear-Host
Write-Host "`n`n`n`n`n`n`n`n"
Write-Host "Getting Firewall Rules..."
$Rules1 = Get-NetFirewallRule -All |
Where-Object {$profiles.sid -notcontains $_.owner -and $_.owner }
$Rules1Count = $Rules1.count
Write-Host "" $Rules1Count "Rules`n"
Write-Host "Getting Firewall Rules from ConfigurableServiceStore..."
$Rules2 = Get-NetFirewallRule -All -PolicyStore ConfigurableServiceStore |
Where-Object { $profiles.sid -notcontains $_.owner -and $_.owner }
$Rules2Count = $Rules2.count
Write-Host "" $Rules2Count "Rules`n"
$Total = $Rules1.count + $Rules2.count
Write-Host "Deleting" $Total "Firewall Rules:" -ForegroundColor Green
$Result = Measure-Command {
$start = (Get-Date)
$i = 0.0
foreach($rule1 in $Rules1){
# action
Remove-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules" -Name $rule1.name
# progress
$i = $i + 1.0
$prct = $i / $total * 100.0
$elapsed = (Get-Date) - $start
$totaltime = ($elapsed.TotalSeconds) / ($prct / 100.0)
$remain = $totaltime - $elapsed.TotalSeconds
$eta = (Get-Date).AddSeconds($remain)
# display
$prctnice = [math]::round($prct,2)
$elapsednice = $([string]::Format("{0:d2}:{1:d2}:{2:d2}", $elapsed.hours, $elapsed.minutes, $elapsed.seconds))
$speed = $i/$elapsed.totalminutes
$speednice = [math]::round($speed,2)
Write-Progress -Activity "Deleting Rules ETA $eta elapsed $elapsednice loops/min $speednice" -Status "$prctnice" -PercentComplete $prct -SecondsRemaining $remain
}
foreach($rule2 in $Rules2) {
# action
Remove-ItemProperty -Path "HKLM:\System\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\RestrictedServices\Configurable\System" -Name $rule2.name
# progress
$i = $i + 1.0
$prct = $i / $total * 100.0
$elapsed = (Get-Date) - $start
$totaltime = ($elapsed.TotalSeconds) / ($prct / 100.0)
$remain = $totaltime - $elapsed.TotalSeconds
$eta = (Get-Date).AddSeconds($remain)
# display
$prctnice = [math]::round($prct,2)
$elapsednice = $([string]::Format("{0:d2}:{1:d2}:{2:d2}", $elapsed.hours, $elapsed.minutes, $elapsed.seconds))
$speed = $i/$elapsed.totalminutes
$speednice = [math]::round($speed,2)
Write-Progress -Activity "Deleting Rules from ConfugurableServiceStore ETA $eta elapsed $elapsednice loops/min $speednice" -Status "$prctnice" -PercentComplete $prct -secondsremaining $remain
}
}
$end = Get-Date
Write-Host end $end
Write-Host eta $eta
Write-Host $result.minutes min $result.seconds sec
Achtung: bei einem bereits länger laufenden Terminalserver, kann das Script schonmal so ein paar Stunden brauchen um alle Regeln zu entfernen. In einem Fall hier knapp 4 Stunden für etwa 95000 Regeln. Je nach CPU kann das natürlich etwas schneller gehen oder länger dauern – es wird wirklich fertig.
Aus jenen Performancegründen löscht das Script die Registry-Werte direkt, anstatt das eigentlich korrekte Remove-NetFirewallRule
Cmdlet zu verwenden.