J’ai eu a creer un script un peu customisé pour gerer l’application de patch windows, pour des machines qui ne peuvent pas etre géré automatiquement. Dans mon contexte cette action devait en plus etre associé a la creation d’un snapshot de machine virtuelle, apres la mise a jour de cette machine.
Le script:
- vérifie si des mises à jour sont réellement en attente ou si un reboot est déjà nécessaire.
- lance le processus d’installation en arrière-plan (via un Job) pour ne pas bloquer votre session.
- Si l’option
-rebootest donnée, il redémarre la machine et attend qu’elle soit vraiment prête. Apres le reboot Il attends a la fois la disponibilité de la connexion winrm et l’occurence de l’event ID 6009 (validant que la machine viens de redemarrer) avant de valider le fait que la machine a bien redémarré.
#####################################################################################################################
#
# check_and_apply_windows_updates.ps1
#
# Ce script:
# - Verifie sur un serveur cible si il y a des mise a jour Windows Update en attente ou un reboot en attente
# - Execute ces mise a jour
# - Gere ou pas le reboot s'il doit avoir lieu (option -reboot) (attente que la machine cible soit joignable)
########################################################################################################################
<# Exemples
Sans reboot :
.\check_and_apply_windows_updates.ps1 -Target MyServer
Avec reboot si requis suite au patching :
.\check_and_apply_windows_updates.ps1 -Target MyServer -reboot
#>
################# Declaration des variables et fonctions ###############################################
Param(
[ValidateNotNullOrEmpty()]$Target,
[switch]$reboot,
$SecTimeOutForReboot=300,
$SecRetryIntervalReboot=10
)
$Log = "$PSScriptRoot\Launch_Maj.log"
$ScriptMaj = "$env:LOCALAPPDATA\check_and_apply_windows_updates.ps1"
$getUpdateParam = @{
NameSpace = 'root/ccm/ClientSDK'
ClassName = 'CCM_SoftwareUpdate'
Filter = 'EvaluationState < 8'
}
$ContScriptMaj = @'
$installUpdateParam = @{
NameSpace = 'root/ccm/ClientSDK'
ClassName = 'CCM_SoftwareUpdatesManager'
MethodName = 'InstallUpdates'
}
$getUpdateParam = @{
NameSpace = 'root/ccm/ClientSDK'
ClassName = 'CCM_SoftwareUpdate'
Filter = 'EvaluationState < 8'
}
[ciminstance[]]$updates = Get-CimInstance @getUpdateParam
if ($updates) {
Invoke-CimMethod @installUpdateParam -Arguments @{ CCMUpdates = $updates }
while(Get-CimInstance @getUpdateParam){
Start-Sleep -Seconds 30
}
}
'@
Function Add-ToLog {
Param ([string]$Text,
[string]$Couleur="Green"
)
#Write-Output "[LOG] $Text"
$Text = "[$([DateTime]::Now)] - $Text"
Add-Content -Path $Log -Value $Text
Write-Host $Text -ForegroundColor $Couleur
}
Function Check-IfPendingUpdates($Target)
{
[ciminstance[]]$global:updates = Invoke-Command -ComputerName $Target -ScriptBlock {$getUpdateParam = $using:getUpdateParam;Get-CimInstance @getUpdateParam}
if ($updates)
{
return $True
}
else
{
return $false
}
}
Function Check-IfRebootPending($Target)
{
$RebootPending = (Invoke-Command -ComputerName $Target -ScriptBlock {Invoke-CimMethod -Namespace root/ccm/ClientSDK -ClassName CCM_ClientUtilities -MethodName DetermineIfRebootPending}).Rebootpending
if ($RebootPending)
{
return $True
}
else
{
return $false
}
}
function Wait-ForWinRMAndEvent {
[CmdletBinding()]
Param(
[Parameter(Mandatory=$true)] [string] $ComputerName,
[Parameter()] [int] $TimeoutMinutes = 15,
[Parameter()] [int] $PollSeconds = 5,
[Parameter()] [datetime] $RebootStartTime = ((Get-Date).ToUniversalTime()),
[Parameter()] [int] $ToleranceSeconds = 60,
[Parameter()] [int] $global:EventToFind = 6009
)
$deadline = (Get-Date).AddMinutes($TimeoutMinutes)
Write-Verbose "Waiting for $ComputerName WinRM and EventID $EventToFind (timeout $TimeoutMinutes min, poll every $PollSeconds s)"
while ((Get-Date) -lt $deadline) {
# 1) Test WinRM reachability
$winrmOk = $false
try {
# Test-WSMan uses WinRM; returns successfully when WSMan endpoint responsive
Test-WSMan -ComputerName $ComputerName -ErrorAction Stop | Out-Null
$winrmOk = $true
} catch {
$winrmOk = $false
}
if ($winrmOk) {
Write-Verbose "WinRM reachable on $ComputerName, checking EventID $EventToFind..."
try {
# Le ScriptBlock utilise $using:EventToFind pour transmettre la variable dans la session distante
$script = {
# Récupère la dernière occurrence de l'EventID $using:EventToFind dans System
$evt = Get-WinEvent -FilterHashtable @{ LogName = 'System'; Id = $using:EventToFind } -MaxEvents 1 -ErrorAction SilentlyContinue
if ($null -eq $evt) { return $null }
# Retourner le TimeCreated en UTC (format ISO)
return $evt.TimeCreated.ToUniversalTime().ToString("o")
}
$lastIso = Invoke-Command -ComputerName $ComputerName -ScriptBlock $script -ErrorAction Stop
# Invoke-Command peut renvoyer un tableau ; prendre la première valeur utile
if ($lastIso -is [array] -and $lastIso.Length -gt 0) { $lastIso = $lastIso[0] }
if ($lastIso) {
# Parse en datetime UTC
try {
$evtDt = [datetime]::Parse($lastIso).ToUniversalTime()
} catch {
Write-Verbose "Impossible de parser l'ISO renvoye: $lastIso"
$evtDt = $null
}
if ($evtDt) {
$threshold = $RebootStartTime.AddSeconds(-1 * [int]$ToleranceSeconds)
if ($evtDt -ge $threshold) {
Write-Verbose "EventID $EventToFind trouve a $evtDt (>= $threshold). Condition satisfaite."
return $true
} else {
Write-Verbose "EventID $EventToFind trouve mais antérieur au démarrage attendu: $evtDt < $threshold"
}
} else {
Write-Verbose "Aucune date d'evenement valide retournee."
}
} else {
Write-Verbose "Aucun EventID $EventToFind trouve encore."
}
} catch {
Write-Verbose "Erreur lors de la requete d'evenements sur $ComputerName : $_"
# On continue (peut-être WinRM venait juste de s'ouvrir et l'auth a échoué)
}
} else {
Write-Verbose "WinRM non joignable sur $ComputerName (probablement toujours en reboot)."
}
Start-Sleep -Seconds $PollSeconds
}
throw "Timeout: $ComputerName n'a pas repondu à WinRM ET/OU n'a pas loggue l'EventID $EventToFind dans les $TimeoutMinutes minutes."
}
################# Debut du script ###############################################
#Test d'acces au serveur cible
Try
{
Test-WSMan -ComputerName $Target -ErrorAction Stop | Out-Null
}
catch
{
Add-Tolog "$Target est injoignable" -Couleur Red
exit 1
}
# Message de connexion
$message = "Connexion au serveur $Target..."
Add-ToLog -text $message -Couleur White
# Si il y a des updates en attente on les applique
If (Check-IfPendingUpdates -Target $Target)
{
$message = "Il y a $($updates.count) mises a jour a faire sur $Target"
Add-ToLog -Text $message -Couleur White
$message = "Installation des mises a jour sur le serveur $Target..."
Add-ToLog -text $message -Couleur White
Set-Content -Value $ContScriptMaj -Path $ScriptMaj -Force
Invoke-Command -ComputerName $Target -FilePath "$ScriptMaj" -AsJob -JobName "$($Target)_job"
# On attend la fin de l'execution des mise a jours
Add-Tolog "En attente de la fin de la mise a jour de $Target, cette etape peut prendre plus d'une heure..." -couleur Yellow
$job = Get-Job -Name "$($Target)_job"
Wait-Job $job | Out-Null
Remove-Job $job | Out-Null
# Si le job est est OK - Fin de l'installation des updates
If ($job[-1].state -like "Completed")
{
$message = "$Target - Fin de l'installation des mises a jour"
Add-ToLog -Text $message -Couleur White
}
# Si le job est est KO - On affiche le probleme et Fin du script
Else
{
$message = "$Target - Erreur pendant l'installation de une ou plusieurs mise a jour - Etat du job: $($job[-1].state) - Verifier l'Etat de la machine"
Add-ToLog $message -Couleur Red
Exit 1
#$MajJobState = "Error"
}
# On verifie si les updates appliques requiert une redemarrage
If (Check-IfRebootPending -Target $Target)
{
$message = "$Target : Il y a des mises a jour installees qui necessitent un redemarrage du serveur !"
#write-host $message -F Yellow
Add-ToLog $message -Couleur Yellow
If ($reboot.IsPresent -eq $true)
{
$message = "Redemarrage du serveur $Target pour integrer les mises a jours...Le script attendra que le serveur $Target soit a nouveau joignable"
Add-ToLog $message -Couleur Yellow
#write-host $message -F Yellow
# LE RESTART-COMPUTER NE FONCTIONNE PAS!!!
# Restart-Computer -ComputerName $Target -Force -Wait -For PowerShell -Delay 5 -Timeout 300 -WhatIf
# Execution du reboot
Invoke-Command -ComputerName $target -ScriptBlock {shutdown.exe /f /r /c \"Reboot suite a installation de Mise a jour"}
# On attend que la machine ne soit plus joignable
Start-Sleep -Seconds 60
# On attend que ça reboot
$endTime = (Get-Date).AddSeconds($SecTimeOutForReboot)
# tant que la date actuelle est inferieure a la date de fin
while ((Get-Date) -lt $endTime)
{
try {
Wait-ForWinRMAndEvent -ComputerName $target -RebootStartTime $((Get-Date).ToUniversalTime()) -TimeoutMinutes 10 -PollSeconds 10 -Verbose
Write-Host "Machine $target de retour et EventID $EventToFind detecte."
# sortie immédiate de la boucle dès que la condition est satisfaite
break
}
catch
{
Write-Warning $_
exit 1
}
}
}
Else
{
$message = "Le redemarrage du serveur $Target n'a pas ete demande, reexecuter le script avec l'option -reboot pour que le serveur ne reste pas dans l'etat REBOOTPENDING"
Add-ToLog $message -Couleur Yellow
#write-host $message -F Yellow
}
}
}
# Si il y a un reboot en attente Meme sans MAJ ET si l'option reboot est precisee on redemarre
ElseIf (Check-IfRebootPending -Target $Target)
{
$message = "$Target : Il y a des mises a jour installees qui necessitent un redemarrage du serveur !"
#write-host $message -F Yellow
Add-ToLog $message -Couleur Yellow
If ($reboot.IsPresent -eq $true)
{
$message = "Redemarrage du serveur $Target...Le script attendra que le serveur $Target soit a nouveau joignable"
Add-ToLog $message -Couleur Yellow
#write-host $message -F Yellow
# LE RESTART-COMPUTER NE FONCTIONNE PAS!!!
# Restart-Computer -ComputerName $Target -Force -Wait -For PowerShell -Delay 5 -Timeout 300 -WhatIf
# Execution du reboot
Invoke-Command -ComputerName $target -ScriptBlock {shutdown.exe /f /r /c \"Reboot suite a installation de Mise a jour"}
# On attend que la machine ne soit plus joignable
Start-Sleep -Seconds 60
# On attend le reboot
$endTime = (Get-Date).AddSeconds($SecTimeOutForReboot)
# tant que la date actuelle est inferieure a la date de fin
while ((Get-Date) -lt $endTime)
{
try {
Wait-ForWinRMAndEvent -ComputerName $target -RebootStartTime $((Get-Date).ToUniversalTime()) -TimeoutMinutes 10 -PollSeconds 10 -Verbose
Write-Host "Machine $target de retour et EventID $EventToFind detecte."
# sortie immédiate de la boucle dès que la condition est satisfaite
break
}
catch
{
Write-Warning $_
exit 1
}
}
Else
{
$message = "Le redemarrage du serveur $Target n'a pas ete demande, reexecuter le script avec l'option -reboot pour que le serveur ne reste pas dans l'etat REBOOTPENDING"
Add-ToLog $message -Couleur Yellow
#write-host $message -F Yellow
}
}
}
# Si il n'y a pas d'update en attente et pas de reboot en attente
Else {
$message = "OK - Pas de mise a jour en attente et pas de reboot en attente"
Add-ToLog -text $message -Couleur White
start-sleep -Seconds 60
Exit 0
}

0 commentaires