##################################################################################################################### # # 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 }