Powershell : désinstallation des hotfix Windows installés à une date spécifiée

J’ai développé pour le compte d’un collègue un script Powershell permettant de lister les hotfix Windows installés à une date spécifiée par l’utilisateur et proposant de les désinstaller, par exemple dans le cadre d’une campagne de patch mal déroulée sur une station ou un serveur.

Le script récupère la liste des patchs – Windows uniquement – installés le jour spécifié, extrait le numéro de KB et passe ensuite en argument de la ligne de commande de désinstallation de paquets Windows Update ce numéro en évitant un redémarrage.

Write-Host "Windows Operating System Hotfix KB remover"
Write-Host "==========================================`r`n"
$instdate = Read-Host "Please input the date KB were installed on."
$instdate = $instdate -as [datetime]
if($instdate -eq $null) { Write-Host "Please input a valid date.`r`nBye." ; exit }
$hotfixlist = Get-Hotfix | Where-Object { $_.InstalledOn -eq $instdate }
if($hotfixlist -eq $null) { Write-Host "No KBs installed for the specified date.`r`nBye." ; exit }
Write-Host "KB list for"$instdate
echo $hotfixlist
$choice = Read-Host "`r`nDo you want to uninstall those KBs ? (y to uninstall - any other input will cancel)"
if($choice -eq "y") {
foreach($kb in $hotfixlist){
$kbid = $kb.HotfixID.Substring(2,$kb.HotfixID.Length-2)
Write-Host "Uninstalling"$kbid
wusa.exe /uninstall /KB:$kbid /norestart
Write-Host "Please press ENTER only once the update wizard is closed because only one instance of it can be started at a time." -ForegroundColor "Yellow"
Read-Host
}
}
Write-Host "Bye."

Une pause marquée par le Read-Host est obligatoire après l’exécution de la commande de désinstallation sinon le script les exécute toutes à la suite, ce qui ne fonctionne pas puisque seule une instance de wusa.exe peut être lancée en même temps.

Restriction du Windows Update Delivery Optimization par groupe ActiveDirectory

Le WUDO ou Windows Update Delivery Optimization est un composant Windows présent sous Windows 10 qui permet d’optimiser la bande passante réseau consommée lors de la distribution de mises à jour système via WSUS par exemple. En imaginant un réseau d’une dizaine de stations, une seule d’entre elles pourrait utiliser le WAN pour récupérer les mises à jour depuis un serveur WSUS situé en central et les autres utilisent le LAN pour les télécharger à partir de celle les ayant obtenues via le WAN afin d’éviter une saturation de ce dernier. Ce qui peut être un gain de bande passante peut avoir des effets de bord notables ; c’est pourquoi il est possible de contrôler finement le comportement du WUDO via GPO. Un moyen de mieux centrer son action est de passer par la création de groupes ActiveDirectory pour regrouper des stations ; c’est ce que je vais présenter dans cet article.

La problématique est la suivante : des stations du site fictif de Paris utilisent le WAN pour télécharger des mises à jour WSUS sur des stations à Londres. Afin d’éviter de saturer ce lien, on souhaite donc limiter le WUDO à récupérer des données depuis des stations d’un même site uniquement.

Voici la composition de cette maquette de test :

  • OU computers, comprenant 3 OU paris, bxl et london, comprenant les stations propres à chaque site.
  • OU wudo-grp, qui contiendra les groupes AD wudo_paris, wudo_bxl et wudo_ldn dans lesquels seront placées les stations de chaque site.

Il est nécessaire de créer une GPO par groupe AD, d’où l’importance de bien définir ses besoins et surtout, l’organisation qui va être adoptée pour bien séparer les flux de communication WUDO. Une séparation trop granulaire demandera beaucoup de travail d’administration et l’inverse n’aura probablement aucun effet de correction des effets de bord ! Cette GPO va prendre en paramètre unique le GUID du groupe, il faut donc le récupérer et surtout, le stocker quelque part pour toujours avoir une association facile entre GUID et GPO associée.

En Powershell, l’instruction suivante nous permettra de récupérer le GUID du groupe.

(Get-AdGroup wudo_paris).ObjectGuid.Guid

Bien entendu, une boucle foreach dans un script récupérant tous les groupes d’une OU permettra de tous les obtenir facilement.

Désormais, il faut s’occuper de la création des GPO. Les réglages qui nous intéressent sont dans Computer Configuration > Policies > Administrative Templates > Windows Components > Delivery Optimization.

La première option est le Download Mode, qu’il faut passer à 2. La description du paramètre explique bien les différentes options possibles. Il est important de noter que ce qui est désigné comme « LAN » ici n’est pas un LAN au sens physique du terme mais un réseau interne ActiveDirectory. Par exemple, si votre domaine s’appelle schmitouille.net, alors le LAN englobera toutes les stations dans schmitouille.net. Si vous avez des sous-domaines, alors le LAN considèrera les stations du sous-domaine, par exemple fr.schmitouille.net.

Ensuite, l’autre paramètre est Group ID. C’est ici que devra être renseigné le GUID du groupe correspondant. Ici, je vais placer le GUID du groupe wudo_bxl car c’est la GPO qui va régir le WUDO des stations du site fictif de Bruxelles.

Il ne me reste plus qu’à lier cette GPO sur l’OU contenant les stations, et non celle contenant les groupes. Toujours en restant dans le cadre du site de Bruxelles :

… et à répéter l’opération autant de fois que nécessaire. En fonction de l’architecture de l’ActiveDirectory, il peut être nécessaire d’appliquer un Security Filtering sur le groupe d’ordinateurs concerné afin d’être sûr que la GPO ne s’applique que sur les stations désirées.

Afin de faire en sorte que les paramètres soient pris en compte le plus rapidement possible, il est possible de forcer un gpupdate à distance via Powershell. Voici un snippet que j’ai réalisé permettant de passer sur toutes les stations désormais régies par ces politiques de groupe pour mettre à jour ces dernières (à noter qu’en fonction de la disponibilité des stations, des paramètres firewall ou des droits du compte l’exécutant, il peut y avoir des erreurs) :

$grouplist = Get-ADGroup -Filter 'Name -like "wudo_*"'
foreach ($group in $grouplist){
    $complist = Get-AdGroupMember $group
    foreach ($comp in $complist){
        Write-Host "Processing:"$comp.Name
        Invoke-GPUpdate -Computer $comp.Name -RandomDelayInMinutes 0
    }
}

Enfin, pour vérifier que la GPO est bien redescendue, il est possible d’utiliser la fonctionnalité Group Policy Result et d’avoir un rapport indiquant si oui ou non la GPO a bien été appliquée. Si oui, le WUDO de la station concernée ne pourra donc interroger que les membres du groupe AD dont le GUID correspond à la GPO appliquée.

Powershell : reset de Windows Update

Toujours dans la lignée de la campagne de patchs pour Meltdown et Spectre, j’ai développé un script permettant d’effectuer les manipulations nécessaires à remettre le composant Windows Update en bon état de marche. Il arrive que le service Windows Update soit incapable de trouver des patchs à installer ou qu’il bute sur des erreurs lors de l’installation des patchs. Une fois qu’on a écarté les causes système habituelles (pas de contact du serveur WSUS, espace disque insuffisant sur le système, service non démarré…), il peut être nécessaire de supprimer les fichiers de Windows Update pour le forcer à reprendre comme si le serveur était fraîchement installé, sans que cela ne retire les patchs, bien évidemment.

J’ai donc développé un script en Powershell qui va couper les services, renommer – et pourquoi pas – supprimer ces répertoires.

echo "Stopping Cryptographic Service Provider."
net stop cryptsvc
echo "Stopping the Windows Update service."
net stop wuauserv
echo "Checking presence of .old directories."
if ((Test-Path C:\Windows\SoftwareDistribution.old) -eq $true) {
$delSD = Read-Host -Prompt "The script needs to delete C:\Windows\SoftwareDistribution.old which probably is a backup of the SoftwareDistribution folder. Type 'N' if you want to abort."
if ($delSD -eq 'N') {break}
Remove-Item C:\Windows\SoftwareDistribution.old -Force -Recurse
}
if ((Test-Path C:\Windows\System32\catroot2.old) -eq $true) {
$delCR = Read-Host -Prompt "The script needs to delete C:\Windows\System32\catroot2.old which probably is a backup of the catroot2 folder. Type 'N' if you want to abort."
if ($delCR -eq 'N') {break}
Remove-Item C:\Windows\System32\catroot2.old -Force -Recurse
}
echo "Renaming WindowsSoftwareDistribution to SoftwareDistribution.old"
$SD = $true
try {
Rename-Item -Path C:\Windows\SoftwareDistribution -NewName C:\Windows\SoftwareDistribution.old -ErrorAction Stop
}
catch {
echo "Folder doesn't exist, can't rename."
$SD = $false
}
echo "Renaming Windows\System32\catroot2 to catroot2.old"
$CR = $true
try {
Rename-Item -Path C:\Windows\System32\catroot2 -NewName C:\Windows\System32\catroot2.old -ErrorAction Stop
}
catch {
echo "Folder doesn't exist, can't rename."
$CR = $false
if ($SD -eq $false -And $CR -eq $false) {
echo "Both folders are missing. Aborting."
break
}
}
$delYN = Read-Host -Prompt "Do you want to delete the old folders ? The script will only do so if you answer 'Y'"
if ($delYN -eq 'Y')
{
if ($SD -eq $true) {Remove-Item C:\Windows\SoftwareDistribution.old -Force -Recurse}
if ($CR -eq $true) {Remove-Item C:\Windows\System32\catroot2.old -Force -Recurse}
}
echo "Starting the services previously stopped."
net start cryptsvc
net start wuauserv
echo "Done."

Erreur 800722E2 sur Windows Update

Durant ma campagne de patchs pour Meltdown et Spectre, j’ai été confronté à quelques serveurs qui me remontaient une erreur 800722E2 sur Windows Update.

Le service Windows Update était bien démarré, espace disque suffisant sur le système.

Par contre, l’ouverture de l’adresse du serveur WSUS dans un navigateur ne donnait rien sur le serveur, tandis que j’obtenais une erreur 403 sur mon poste local, ce qui signifie qu’à priori, le serveur ne communiquait pas avec le serveur WSUS (port 80).

Une vérification avec PortQuery de l’ouverture des ports réalisée par la suite :

Windows Update a pu ensuite communiquer avec le WSUS et patcher le serveur correctement.