Automazione dell’installazione degli aggiornamenti tramite PowerShell

Vi sono situazioni in cui su alcuni computer occorre intervenire periodicamente in modo manuale ad installare gli aggiornamenti di Windows. Ad esempio nel caso di computer che devono essere sempre in funzione come server, chioschi o computer che eseguono sinottici, etc…

Per sviluppare uno script PowerShell che si occupi di verificare l’esistenza di aggiornamenti da installare, ne esegua se necessario il download e quindi l’installazione è possibile prendere spunto dal post  Hey, Scripting Guy! How Can I Search For, Download, and Install an Update? in cui viene descritto il codice PowerShell per la gestione del processo d’istallazione degli aggiornamenti di Windows.

In sintesi l’approccio di basa sull’utilizzo di WMI per ricercare gli aggiornamenti, ricavare l’ID univoco del singolo aggiornamento, quindi costruire una collection di  aggiornamenti da scaricare e installare (per la spiegazione dettagliata si faccia riferimento al post Hey, Scripting Guy! How Can I Search For, Download, and Install an Update?):

$UpdateCollection = New-Object -ComObject Microsoft.Update.UpdateColl
$Searcher = New-Object -ComObject Microsoft.Update.Searcher
$Session = New-Object -ComObject Microsoft.Update.Session

$updateID = “f1b1a591-bb75-4b1c-9fbd-03eedb00cc9d”
$Result = $Searcher.Search(“UpdateID=’$updateID'”)
$Updates = $Result.updates
$UpdateCollection.Add($Updates.Item(0)) | out-null

$Downloader = $Session.CreateUpdateDownloader()
$Downloader.Updates = $UpdateCollection
$Downloader.Download()

$Installer = New-Object -ComObject Microsoft.Update.Installer
$Installer.Updates = $UpdateCollection
$Installer.Install()

Partendo da questo codice ho sviluppato uno script che eseguire la ricerca degli aggiornamenti non installati e se necessario li scarica, quindi li installa, riavvia il computer e invia il log dell’attività per mail.

La parte iniziale dello script permette di gestire alcuni flag gestire il funzionamento dello script, il parametro $updatesLimit se diverso da -1 permette di installare solo un numero specificato di aggiornamenti e può essere utile a fini di test così come i flag $enableDownload e $enableInstall che permettono di eseguire solo la verifica degli aggiornamenti da scaricare e da installare:

# *** Impostazioni ***
$rebootAfterUpdate = $TRUE

$updatesLimit = -1
$enableDownload = $TRUE
$enableInstall = $TRUE
$logFilePath = “Path dei file di logs
$logFileNameSuffix = “LogInstallUpdates”
$logFilesRetained = 10

$sendLogByMail = $TRUE
$smtpServer = “Nome o IP Server SMTP
$mailFrom = “Indirizzo mail from
$mailTo = “Indirizzo mail to
# ********************

Per gestire in modo automatico l’istallazione degli aggiornamenti in base alla schedulazione desiderata basterà configurare Windows Update per eseguire la sola verifica degli aggiornamenti e gestire poi download e installazione tramite lo script.

image

E’ possibile schedulare lo script tramite un file cmd contente un comando di questo tipo (che può anche essere utilizzato per l’avvio dello script durante le fasi di test per visualizzare eventuali errori):

%SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe C:\Scripts\InstallUpdates.ps1

In alternativa come suggerito nei commenti da Luigi semplicemente powershell nel campo “Programma o script” dell’operazione pianificata e lo script negli argomenti meglio se preceduto dall’opzione –file, quindi nel nostro caso:

image

A riguardo si veda anche il post Weekend Scripter: Use the Windows Task Scheduler to Run a Windows PowerShell Script.

Per evitare che la finestra del prompt del dos venga visualizzata quando l’operazione schedulata viene eseguita mentre si è connessi in console impostare l’esecuzione del file cmd tramite l’utente System.

Per poter eseguire gli script PowerShell sui sistemi operativi client occorre impostare l’execution policy almeno a  RemoteSigned tramite il cmdlet Set-ExecutionPolicy:

Set-ExecutionPolicy RemoteSigned

Di seguito lo script InstallUpdates.ps1 che va eseguito con privilegi amministrativi e che ho testato su sistemi Windows 8.1:

# *** Impostazioni ***

$rebootAfterUpdate = $TRUE
$updatesLimit = -1
$enableDownload = $TRUE
$enableInstall = $TRUE
$logFilePath = “Path dei file di logs
$logFileNameSuffix = “LogInstallUpdates”
$logFilesRetained = 10
$sendLogByMail = $TRUE
$smtpServer = “Nome o IP Server SMTP
$mailFrom = “Indirizzo mail from
$mailTo = “Indirizzo mail to
# ********************

# *** Creazione Log Path e impostazione Log File Name
$logFileNameBase = $logFilePath + “\” + $logFileNameSuffix
$logFile = $logFileNameBase + “-” + (Get-Date).ToString(“yyyy-MM-dd-HH-mm-ss”) + “.txt”
if (!(Test-Path $logFilePath)){
New-Item $logFilePath -type directory
}

# *** Ricerca Updates da Installare
$updatesSearcher = New-Object -ComObject Microsoft.Update.Searcher
$updatesPending = $updatesSearcher.Search(“Type=’software’ AND IsInstalled=0 AND IsHidden=0”)

$updatesSelected = $updatesPending.Updates
If ($updatesLimit -ne -1){
$updatesSelected = $updatesSelected | Select-Object -First $updatesLimit
}

# *** Analisi Elenco Updates da installare
If ($updatesPending.Updates.Count -eq 0){
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Non sono stati trovati aggiornamenti da installare” | Out-File $logFile -append
}
Else {
# Definizione della collezione degli update da scaricare
$updatesDownloadCollection = New-Object -ComObject Microsoft.Update.UpdateColl

# Definizione della collezione degli update da installare
$updatesInstallCollection = New-Object -ComObject Microsoft.Update.UpdateColl

# Scorrimento degli update da installare
ForEach ($update In $updatesSelected) {
# Estrazione ID update da installare
$updateID=$update.Identity.UpdateID

# Log delle informazoni dell’update
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Analisi aggiornamento da installare” | Out-File $logFile -append
“`t ” + $update.Title | Out-File $logFile -append
“`t ” + “ID: ” + $update.Identity.UpdateID | Out-File $logFile -append
“`t ” + “Mandatory: ” + $update.IsMandatory.ToString() | Out-File $logFile -append
“`t ” + “MaxDownloadSize: ” + $update.MaxDownloadSize + ” bytes” | Out-File $logFile -append
“`t ” + $update.Description | Out-File $logFile -append
If ($update.DownloadPriority -eq 1){
“`t ” + “Priority: Low” | Out-File $logFile -append
}
ElseIf ($update.DownloadPriority -eq 2){
“`t ” + “Priority: Normal” | Out-File $logFile -append
}
ElseIf ($update.DownloadPriority -eq 3){
“`t ” + “Priority: High” | Out-File $logFile -append
}

# Ricerca Item Update
$updateIDSearcher = $updatesSearcher.Search(“UpdateID=’$updateID'”)
$updateIDUpdates = $updateIDSearcher.Updates
$updateIDItem = $updateIDUpdates.Item(0)

# Aggiunta dell’update alla collezione degli update da scaricare
If ($update.IsDownloaded){
“`t ” +  “Aggiornamento scaricato” | Out-File $logFile -append
}
Else {
“`t ” + “Aggiornamento da scaricare” | Out-File $logFile -append
$updatesDownloadCollection.Add($updateIDItem) | out-null
}

# Aggiunta dell’update alla collezione degli update da installare
$updatesInstallCollection.Add($updateIDItem) | out-null
#”———–” | Out-File $logFile -append
}

# Avvio Download Aggiornamenti
If ($updatesDownloadCollection.Count -eq 0){
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Non sono stati trovati aggiornamenti da scaricare” | Out-File $logFile -append
}
ElseIf ($enableDownload) {
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Avvio download ” + $updatesDownloadCollection.Count + ” aggiornamenti” | Out-File $logFile -append
$updateSession = New-Object -ComObject Microsoft.Update.Session
$updatesDownloader = $updateSession.CreateUpdateDownloader()
$updatesDownloader.Updates = $updatesDownloadCollection
$updatesDownloader.Download()
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Download aggiornamenti eseguito” | Out-File $logFile -append
}
Else{
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Necessario dowload di ” + $updatesDownloadCollection.Count + ” aggiornamenti” | Out-File $logFile -append
}

# Installazione Aggiornamenti
If ($updatesInstallCollection.Count -eq 0){
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Non sono stati trovati aggiornamenti da installare” | Out-File $logFile -append
}
ElseIf ($enableInstall) {
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Avvio installazione ” + $updatesInstallCollection.Count + ” aggiornamenti” | Out-File $logFile -append
$updatesInstaller = New-Object -ComObject Microsoft.Update.Installer
$updatesInstaller.Updates = $updatesInstallCollection
$updatesInstaller.Install()
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Installazione aggiornamenti eseguita” | Out-File $logFile -append
}
Else{
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Necessaria installazione di ” + $updatesInstallCollection.Count + ” aggiornamenti” | Out-File $logFile -append
}

# Invio Log File
If ($sendLogByMail){
$mailSubject = “Installazione aggiornamenti computer ” + $env:computername
$mailBody = Get-Content $logFile | Out-String

If ($rebootAfterUpdate){
$mailBody = $mailBody + “`nIl sistema verrà riavviato”
}

Send-MailMessage -To $mailTo -Subject $mailSubject -From $mailFrom -Body $mailBody -SmtpServer $smtpServer -Encoding Default
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Invio log tramite mail eseguito” | Out-File $logFile -append
}

# Eliminazione log file obsoleti
$logFiles = Get-ChildItem $logFilePath –PipelineVariable item | Where {$item.psIsContainer -eq $false -and $item.FullName -like ($logFileNameBase + “*”)} | Sort FullName
If ($logFiles.Count -gt $logFilesRetained){
For ($i = 1; $i -le $logFiles.Count – $logFilesRetained; $i++) {
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Eliminazione log file ” + $logFiles[$i-1].FullName | Out-File $logFile -append
Remove-Item $logFiles[$i-1].FullName | Out-File $logFile -append
}
}

# Esecuzione riavvio
If ($rebootAfterUpdate){
(Get-Date).ToString(“yyyy-MM-dd HH:mm:ss”) + ” Riavvio computer” | Out-File $logFile -append
Restart-Computer
}
}