Tuesday, January 28, 2014

Computers not registering with WSUS

I'm not like some of these bloggers who live to post, it takes time and that's something I don't have a lot of.  Still, I really need to make an effort to post fixes when I find them.

For example I just fixed an issue that has been plaguing us for weeks.  We are teetering between using Windows WSUS and a 3rd party product for patch management.  For now we will use WSUS.  I set up a global policy in our domain to assign each system to one of our two WSUS servers according to AD site.  We're not using client side targeting (yet) so the GPO setup is fairly straightforward.

Initially about 2/3 of our systems registered with one or the other of the WSUS servers but the rest would not.  We tried everything but nothing would get these things to register.  The OS type ranged from XP to Server 2012 and everything in between.

Finally One of the guys here hit on a Technet article that cinched it. https://social.technet.microsoft.com/Forums/en-US/f983bb7b-cbb1-4acf-9665-638be4cc6a60/client-computers-not-registering-on-wsus-server?forum=winserverwsus

This article described the same issues another user had.  The person answering it hit the issue on the mark.

Seems there is a unique SID used for registering with WSUS located at
HKEY_LOCAL_MACHINE\SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\WINDOWSUPDATE\SUSCLIENTID
When you use cloned virtual systems this SID doesn't get changed.  When two or more systems have the same SID, the first registers with WSUS.  The second tries to register but since the SID is known WSUS says "you already registered and I told you what to do".  The result is the first system gets patched and all others with duplicate SID don't and they never register themselves with WSUS.

To fix this I first ran a PowerShell script to extract the SIDs.


clear-host
$ErrorActionPreference = "SilentlyContinue"

$TargetKey = "SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\WINDOWSUPDATE"
$TargetValueName = "SUSCLIENTID" 

Function GetString ($ValueName, $ValueData){
  $ReadBack = ""
  $ReadBack = $SubKey.getValue($ValueName)
  Write-Host $ReadBack
}
  $Computers = Get-ADComputer -Filter {OperatingSystem -Like "*"} -Property * | ForEach-Object {$_.Name}

ForEach ($Target in $Computers){
  $Target = $Target.ToUpper()

  If(Test-Connection -ComputerName $Target -count 1 -ea 0) {
    $BaseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $Target)
    $SubKey = $BaseKey.OpenSubKey($TargetKey, $True)
    GetString $TargetValueName $TargetValueData
  }Else{
    Write-host "Failed to connect to $Target..."
  }
}
Write-host "--- Run Completed ---"


The script is quick and dirty and just prints the SIDs to the screen.  I copied them into Excel, sorted and filtered it down to the list of just duplicates.

Once I had the list of duplicate SIDs I needed to detect them, clear them out, and get the affected system to register.  For this I created a variation of the script.  This script also has a debug mode that allows you to target a single system.  That way you can test it without affecting the entire domain.


clear-host
$Debug = $true
$DebugTarget = "testbox"
$ErrorActionPreference = "SilentlyContinue"

$TargetKey = "SOFTWARE\MICROSOFT\WINDOWS\CURRENTVERSION\WINDOWSUPDATE"
$TargetValueName = "SUSCLIENTID" 

Function SetString ($ValueName, $ValueData){
  $ReadBack = ""
  $ReadBack = $SubKey.getValue($ValueName)

  foreach ($GUID in $GUIDArray){

  if ($GUID -eq $ReadBack){
  Write-Host $target
  Write-Host "deleting key"
  $SubKey.DeleteValue("SusClientID", [Microsoft.Win32.RegistryValueKind]::String)

  Write-Host "running fix"
  Invoke-Command -ComputerName $Target -ScriptBlock { Start-Process "\\dc01\netlogon\wsusfix.bat" }

  Write-Host "restarting service"
  Restart-Service -InputObject $(Get-Service -Computer $Target -Name "wuauserv");
  }
}
}

$GUIDArray = @("07b0b81c-80c6-4755-9d60-361b922a029e")
$GUIDArray += "1180369a-0b69-4542-86f3-a2437ef593dd"
$GUIDArray += "2be0adc5-b137-4fd0-a28d-cd470de2fe09"
$GUIDArray += "2f29bacf-5385-445e-ad4f-26e6c6a2b2eb"
$GUIDArray += "3dd11e2a-a8ad-4eb0-bde5-7f3ee07da9c9"
$GUIDArray += "56f220f0-01ea-4df7-af3e-9cba3be8b957"

If ($Debug){
  Write-host "===================== DEBUG MODE ENABLED ============================="
  $Computers = $DebugTarget
}Else{
  $Computers = Get-ADComputer -Filter {OperatingSystem -Like "*"} -Property * | ForEach-Object {$_.Name}
}

ForEach ($Target in $Computers){
  $Target = $Target.ToUpper()
  $IP = [System.Net.Dns]::GetHostAddresses($Target)

  Write-host "----------------------------------------------------------------------"

  If(Test-Connection -ComputerName $Target -count 1 -ea 0) {
    $BaseKey = [Microsoft.Win32.RegistryKey]::OpenRemoteBaseKey("LocalMachine", $Target)
    $SubKey = $BaseKey.OpenSubKey($TargetKey, $True)
    SetString $TargetValueName $TargetValueData
  }Else{
    Write-host "Failed to connect to $Target..."
  }
}
Write-host "--- Run Completed ---"


This script required an external batch file in a common location (the netlogon share on a DC).  I tried all sorts of ways to reliably get the register command to run, this was the most reliable.

The batch file contains one line of text "wuauclt /resetauthorization /detectnow".

When run, the script detects all systems in the domain, and checks the registry key for one of the listed SIDs.  If found it removes the key, generates a new SID and restarts the Windows Update service.  Within about 10 to 15 seconds , if you are watching the registry, you'll see the key regenerate with a new SID. 

I recommend letting things sit over night.  The next day you should see all your missing systems in the WSUS server.