Les correctifs hors bande (Out-Of-Band ou OOB), c’est-à-dire ceux qui sont publiés en dehors du cycle régulier du Patch Tuesday de Microsoft, concernent souvent des vulnérabilités critiques ou des problèmes de stabilité qui exigent une action immédiate. Dans les environnements rapides des fournisseurs de services gérés (MSP) et des opérations informatiques, l’automatisation du déploiement de ces correctifs peut réduire considérablement les temps de réponse et améliorer la sécurité. Un script PowerShell robuste qui gère les installations de correctifs locales et distantes fournit une méthode fiable et reproductible pour gérer ces situations.
Cet article présente un script PowerShell conçu pour installer des correctifs hors bande à l’aide d’une URL ou d’un chemin d’accès à un fichier donné. Nous verrons comment il fonctionne, pourquoi il est utile et le comparerons aux autres stratégies de mise à jour.
Contexte
Microsoft publie occasionnellement des mises à jour hors bande (Out-Of-Band ou OOB) afin d’atténuer les menaces de sécurité urgentes ou de résoudre des problèmes de stabilité majeurs. Ces mises à jour sont généralement livrées dans des fichiers .msu
(Microsoft Update Standalone). Les administrateurs doivent agir rapidement, en particulier lorsque ces correctifs comblent des vulnérabilités de type « zero-day » ou des bugs majeurs. Cependant, le téléchargement et l’installation manuels des correctifs sur plusieurs systèmes sont inefficaces et sources d’erreurs.
Ce script PowerShell comble cette lacune. Il permet aux professionnels de l’informatique d’automatiser le processus d’installation en utilisant soit une URL directe vers la mise à jour, soit un chemin d’accès à un fichier local. Il est particulièrement utile dans les environnements gérés où la cohérence administrative, la traçabilité et la rapidité sont essentielles.
Le script
#Requires -Version 5.1 <# .SYNOPSIS Installs an out-of-band patch given a URL or a local path. .DESCRIPTION Installs an out-of-band patch given a URL or a local path. By using this script, you indicate your acceptance of the following legal terms as well as our Terms of Use at https://www.ninjaone.com/terms-of-use. Ownership Rights: NinjaOne owns and will continue to own all right, title, and interest in and to the script (including the copyright). NinjaOne is giving you a limited license to use the script in accordance with these legal terms. Use Limitation: You may only use the script for your legitimate personal or internal business purposes, and you may not share the script with another party. Republication Prohibition: Under no circumstances are you permitted to re-publish the script in any script library or website belonging to or under the control of any other software provider. Warranty Disclaimer: The script is provided “as is” and “as available”, without warranty of any kind. NinjaOne makes no promise or guarantee that the script will be free from defects or that it will meet your specific needs or expectations. Assumption of Risk: Your use of the script is at your own risk. You acknowledge that there are certain inherent risks in using the script, and you understand and assume each of those risks. Waiver and Release: You will not hold NinjaOne responsible for any adverse or unintended consequences resulting from your use of the script, and you waive any legal or equitable rights or remedies you may have against NinjaOne relating to your use of the script. EULA: If you are a NinjaOne customer, your use of the script is subject to the End User License Agreement applicable to you (EULA). .EXAMPLE -MSU 'https://catalog.s.download.windowsupdate.com/d/msdownload/update/software/secu/2024/07/windows10.0-kb5040427-x64_750f2819b527034dcdd10be981fa82d140767f8f.msu' URL 'https://catalog.s.download.windowsupdate.com/d/msdownload/update/software/secu/2024/07/windows10.0-kb5040427-x64_750f2819b527034dcdd10be981fa82d140767f8f.msu' was given. Downloading the file... Waiting for 14 seconds. Download Attempt 1 Installing update at C:\Windows\TEMP\windowsupdate-1135150612.msu. Exit Code: 3010 [Warn] A reboot is required for this update to take effect. PRESET PARAMETER: -MSU "https://www.replace.me" Specify either a link or a file path to the patch you would like to install. PRESET PARAMETER: -ForceReboot Reboot the computer after successfully installing the requested patch. .NOTES Minimum OS Architecture Supported: Windows 10, Windows Server 2016 Release Notes: Initial Release #> [CmdletBinding()] param ( [Parameter()] [String]$MSU, [Parameter()] [Switch]$ForceReboot = [System.Convert]::ToBoolean($env:forceReboot) ) begin { if ($env:urlOrLocalPathToMsu -and $env:urlOrLocalPathToMsu -notlike "null") { $MSU = $env:urlOrLocalPathToMsu } # Check if $MSU is not provided if (!$MSU) { Write-Host -Object "[Error] An MSU was not provided. Please provide either a local file path or a URL to download the MSU." exit 1 } # Remove quotations if given quotations if ($MSU) { if ($MSU.Trim() -match "^'" -or $MSU.Trim() -match "'$" -or $MSU.Trim() -match '^"' -or $MSU.Trim() -match '"$') { $QuotationsFound = $true } $MSU = ($MSU.Trim() -replace "^'" -replace "'$" -replace '^"' -replace '"$').Trim() if ($QuotationsFound) { Write-Host -Object "[Warning] Removing quotations from your path. Your new path is '$MSU'." } } # Check if $MSU is not a local file path if ($MSU -notmatch '^[A-Za-z]:\\') { # Check if $MSU starts with 'http' but not 'http[s]?://' if ($MSU -match '^http' -and $MSU -notmatch '^http[s]?://') { Write-Host -Object "[Error] URL '$MSU' is malformed." exit 1 } # Check if $MSU contains double 'http[s]?://' if ($MSU -match '^http[s]?://http[s]?://') { Write-Host -Object "[Error] URL '$MSU' is malformed." exit 1 } # Add 'https://' to $MSU if it does not start with 'http' if ($MSU -notmatch '^http') { $MSU = "https://$MSU" Write-Host -Object "[Warn] Missing http(s) from URL, changing URL to '$MSU'." } # Check if $MSU has a top-level domain if ($MSU -notmatch '.*\..*') { Write-Host -Object "[Error] No top-level domain found in URL." exit 1 } # Validate $MSU as a URI try { [System.Uri]$MSU | Out-Null } catch { Write-Host -Object "[Error] URL '$MSU' is malformed." Write-Host -Object "[Error] $($_.Exception.Message)" exit 1 } } # Check if $MSU is a local file path if ($MSU -match '^[A-Za-z]:\\') { # Check if $MSU contains invalid characters if ($MSU -match '[<>"/\|?]' -or $MSU -match ':.*:' -or $MSU -match '::') { Write-Host -Object "[Error] The file path '$MSU' contains one of the following invalid characters: < > : `" / \ | ? :" exit 1 } # Check if the file at $MSU exists if (!(Test-Path -Path "$MSU" -ErrorAction SilentlyContinue)) { Write-Host -Object "[Error] File does not exist at path '$MSU'." exit 1 } # Try to get the item at $MSU path try { $MSUFile = Get-Item -Path $MSU -Force -ErrorAction Stop } catch { Write-Host -Object "[Error] Failed to retrieve file at path '$MSU'." Write-Host -Object "[Error] $($_.Exception.Message)" exit 1 } # Check if more than one file is found at $MSU path if ($MSUFile.Count -gt 1) { Write-Host -Object "[Error] Too many files were found at path '$MSU'; please be more specific." } # Check if the $MSU path is a folder if ($MSUFile.PSIsContainer) { Write-Host -Object "[Error] The given path '$MSU' is a folder and not an MSU file." exit 1 } } # Utility function for downloading files. function Invoke-Download { param( [Parameter()] [String]$URL, [Parameter()] [String]$Path, [Parameter()] [int]$Attempts = 3, [Parameter()] [Switch]$SkipSleep ) Write-Host -Object "URL '$URL' was given." Write-Host -Object "Downloading the file..." $SupportedTLSversions = [enum]::GetValues('Net.SecurityProtocolType') if ( ($SupportedTLSversions -contains 'Tls13') -and ($SupportedTLSversions -contains 'Tls12') ) { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol::Tls13 -bor [System.Net.SecurityProtocolType]::Tls12 } elseif ( $SupportedTLSversions -contains 'Tls12' ) { [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 } else { Write-Warning -Message "TLS 1.2 and or TLS 1.3 are not supported on this system. This download may fail!" if ($PSVersionTable.PSVersion.Major -lt 3) { Write-Warning -Message "PowerShell 2 / .NET 2.0 doesn't support TLS 1.2." } } $i = 1 While ($i -le $Attempts) { # Some cloud services have rate-limiting if (-not ($SkipSleep)) { $SleepTime = Get-Random -Minimum 3 -Maximum 15 Write-Host -Object "Waiting for $SleepTime seconds." Start-Sleep -Seconds $SleepTime } if ($i -ne 1) { Write-Host "" } Write-Host -Object "Download Attempt $i" $PreviousProgressPreference = $ProgressPreference $ProgressPreference = 'SilentlyContinue' try { # Invoke-WebRequest is preferred because it supports links that redirect, e.g., https://t.ly if ($PSVersionTable.PSVersion.Major -lt 4) { # Downloads the file $WebClient = New-Object System.Net.WebClient $WebClient.DownloadFile($URL, $Path) } else { # Standard options $WebRequestArgs = @{ Uri = $URL OutFile = $Path MaximumRedirection = 10 UseBasicParsing = $true } # Downloads the file Invoke-WebRequest @WebRequestArgs } $File = Test-Path -Path $Path -ErrorAction SilentlyContinue } catch { Write-Warning -Message "An error has occurred while downloading!" Write-Warning -Message $_.Exception.Message if (Test-Path -Path $Path -ErrorAction SilentlyContinue) { Remove-Item $Path -Force -Confirm:$false -ErrorAction SilentlyContinue } $File = $False } $ProgressPreference = $PreviousProgressPreference if ($File) { $i = $Attempts } else { Write-Warning -Message "File failed to download." Write-Host -Object "" } $i++ } if (-not (Test-Path $Path)) { Write-Host -Object "[Error] Failed to download file." Write-Host -Object "Please verify the URL of '$URL'." exit 1 } else { return $Path } } function Test-IsElevated { $id = [System.Security.Principal.WindowsIdentity]::GetCurrent() $p = New-Object System.Security.Principal.WindowsPrincipal($id) $p.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator) } if (!$ExitCode) { $ExitCode = 0 } } process { # Check if the script is running with elevated (Administrator) privileges if (!(Test-IsElevated)) { Write-Host -Object "[Error] Access Denied. Please run with Administrator privileges." exit 1 } # Check if $MSU is not a local file path and download it if it's a URL if ($MSU -notmatch '^[A-Za-z]:\\') { $MSU = Invoke-Download -URL $MSU -Path "$env:TEMP\windowsupdate-$(Get-Random).msu" $DownloadedUpdate = $True } # Set the log file location $LogLocation = "$env:TEMP\update-log-$(Get-Random).evt" # Inform the user that the update is being installed Write-Host -Object "Installing update at $MSU." # Prepare the arguments for the update process $UpdateArguments = "`"$MSU`"", "/quiet", "/norestart", "/log:`"$LogLocation`"" $UpdateProcess = Start-Process -FilePath "$env:SystemRoot\System32\wusa.exe" -ArgumentList $UpdateArguments -Wait -NoNewWindow -PassThru # Check if the log file exists and try to read it if (Test-Path -Path $LogLocation -ErrorAction SilentlyContinue) { $LogFile = Get-WinEvent -Path $LogLocation -Oldest -ErrorAction SilentlyContinue | Select-Object TimeCreated, Message -ErrorAction SilentlyContinue | ForEach-Object { "$($_.TimeCreated) $($_.Message)" } Remove-Item -Path $LogLocation -Force -ErrorAction SilentlyContinue } # Remove the downloaded update file if it was downloaded if ($DownloadedUpdate -and (Test-Path -Path $MSU -ErrorAction SilentlyContinue)) { Remove-Item -Path $MSU -Force } # Output the exit code of the update process Write-Host -Object "Exit Code: $($UpdateProcess.ExitCode)" # Check if the exit code indicates success $ValidExitCodes = "0", "3010" if ($ValidExitCodes -notcontains $UpdateProcess.ExitCode) { Write-Host -Object "[Error] Exit code does not indicate success!" # Output the update log if available if ($LogFile) { Write-Host -Object "`n### Update Log ###" Write-Host -Object $LogFile } exit 1 } # Inform the user if a reboot is required if (!$ForceReboot -and $UpdateProcess.ExitCode -eq 3010) { Write-Host -Object "[Warn] A reboot is required for this update to take effect." } # Schedule a reboot if requested and the update was successful if ($ForceReboot -and $ExitCode -eq 0) { Write-Host "`nScheduling reboot for $((Get-Date).AddMinutes(1)) as requested." Start-Process shutdown.exe -ArgumentList "/r /t 60" -Wait -NoNewWindow } exit $ExitCode } end { }
Description détaillée
Le script se compose de trois phases principales : la validation et le prétraitement, le téléchargement et l’installation, et la gestion post-installation.
1. Traitement et validation des paramètres
Le script accepte deux paramètres :
-MSU
: L’URL ou le chemin local du fichier .msu.-ForceReboot
: Indicateur qui, s’il est activé, déclenche un redémarrage du système après l’installation.
Il peut également intégrer ces paramètres à partir de variables d’environnement, ce qui offre une grande souplesse d’intégration avec les systèmes de déploiement.
Le script nettoie les entrées pour supprimer les citations inutiles et vérifie la structure de l’URL ou du chemin d’accès au fichier. Il vérifie l’absence de caractères non valides et s’assure que tout fichier local référencé existe et qu’il ne s’agit pas d’un répertoire.
2. Télécharger Logic
Si une URL est fournie, le script utilise une fonction appelée Invoke-Download
pour récupérer le fichier patch, avec une logique de relance intégrée et la prise en charge des protocoles TLS 1.2 et 1.3. Cette fonction randomise un délai avant chaque tentative afin d’éviter que les réseaux de diffusion de contenu ne limitent le débit.
3. Installation et enregistrement
À l’aide de wusa.exe
, le programme d’installation des mises à jour intégré à Windows, le script lance le processus de correction avec les options /quiet
et /norestart
, ainsi qu’un fichier journal personnalisé.
Après l’installation, le script évalue le code de sortie :
- 0 : Succès
- 3010 : Succès, mais redémarrage nécessaire
Si l'option -ForceReboot
est spécifiée et que le correctif a réussi, le système planifie un redémarrage dans 60 secondes à l’aide de shutdown.exe
.
Cas d’utilisation potentiels
Cas de figure : Déploiement de correctifs d’urgence
Une entreprise MSP gérant des centaines de terminaux est informée d’une vulnérabilité de type « zero-day » dans Windows. Microsoft a publié un correctif hors bande. À l’aide de ce script, elle planifie une automatisation NinjaOne pour invoquer le script avec l’URL du correctif, ce qui garantit un déploiement rapide et uniforme sur tous les terminaux concernés.
En quelques heures, le correctif est installé sur l’ensemble de la flotte et les politiques de redémarrage sont appliquées si nécessaire. Aucune intervention manuelle n’est nécessaire en dehors du paramétrage de l’automatisation initiale.
Comparaisons
Par rapport aux installations manuelles basées sur une interface graphique ou à l’utilisation de WSUS pour les correctifs d’urgence, ce script offre les avantages suivants :
Méthode | Avantages | Inconvénients |
Installation manuelle | Simple pour les systèmes individuels | Chronophage, incohérent |
WSUS / SCCM | Contrôle centralisé | Nécessite la mise en place d’une infrastructure |
Script PowerShell (cet article) | Rapide, automatisable, fonctionne à distance | Nécessite des droits d’administrateur et des tests |
Ce script est également plus performant que les installations PowerShell de base qui ne gèrent pas les URL incorrectes, qui n’ont pas de logique de réessai ou qui omettent les contrôles post-installation.
Questions fréquentes
Q : Ce script peut-il installer des mises à jour cumulatives ?
A : Oui, tout fichier .msu
valide (cumulatif ou correctif individuel) peut être installé.
Q : Que se passe-t-il si le système ne fonctionne pas en tant qu’administrateur ?
A : Le script vérifie l’élévation et interrompt l’exécution s’il n’est pas exécuté avec des droits d’administrateur.
Q : Comment le comportement de redémarrage est-il géré ?
A : Par défaut, aucun redémarrage n’est déclenché. Utilisez le commutateur -ForceReboot
pour lancer un redémarrage si le correctif réussit.
Q : Peut-on l’utiliser dans des tâches planifiées ou des outils d’automatisation ?
A : Absolument. Il accepte les variables d’environnement et s’intègre parfaitement aux plateformes RMM telles que NinjaOne.
Implications
Les correctifs hors bande mal appliqués ou retardés rendent les systèmes vulnérables aux exploits. L’automatisation du déploiement des correctifs garantit la conformité avec les politiques de sécurité et les exigences d’audit. Toutefois, l’option de redémarrage forcé doit être utilisée judicieusement, en particulier dans les environnements multi-utilisateurs, afin d’éviter de perturber la productivité.
En intégrant une gestion intelligente des erreurs, une validation des téléchargements et une journalisation, ce script minimise les risques tout en maximisant l’efficacité.
Recommandations
- Testez dans un environnement d’essai avant de le déployer dans les systèmes de production.
- Utilisez des URL de confiance et validez toutes les sources pour éviter d’introduire des contenus malveillants.
- Activez la journalisation verbeuse en production pour maintenir la traçabilité.
- Combinez avec l’automatisation NinjaOne pour un déploiement et une planification de masse.
- Définissez des périodes de maintenance appropriées lors de l’utilisation de
-ForceReboot
.
Conclusion
La gestion des correctifs hors bande est une fonction essentielle dans le secteur actuel de la cybersécurité. Ce script PowerShell offre une méthode propre, efficace et reproductible pour installer les mises à jour à l’aide d’un chemin de fichier ou d’une URL directe. Il comprend des fonctionnalités bien pensées telles que la détection TLS, des tentatives de relance aléatoires et une validation solide, qui contribuent toutes à un processus de mise à jour fiable.
Lorsqu’il est intégré à une plateforme comme NinjaOne, ce script devient encore plus puissant. NinjaOne peut déployer le script sur les terminaux, gérer les variables d’environnement et s’occuper de la logique de redémarrage après l’installation, ce qui permet aux équipes informatiques de répondre rapidement aux menaces émergentes, en toute confiance et sous contrôle.