Functions/Task/WindowsUpdateInstall.ps1
<#
.SYNOPSIS Autonance DSL task to install Windows updates. .DESCRIPTION The WindowsUpdateInstall task is part of the Autonance domain-specific language (DSL). The task will install all pending Windows updates on the target Windows computer by using WinRM and the 'Microsoft.Update.Session' COM object. A user account can be specified with the Credential parameter. .NOTES Author : Claudio Spizzi License : MIT License .LINK https://github.com/claudiospizzi/Autonance #> function WindowsUpdateInstall { [CmdletBinding()] param ( # This task restarts the specified Windows computer. [Parameter(Mandatory = $true, Position = 0)] [System.String] $ComputerName, # Specifies a user account that has permission to perform the task. [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, # If specified, all available updates will be installed without a query. [Parameter(Mandatory = $false)] [switch] $All ) if (!$Script:AutonanceBlock) { throw 'WindowsUpdateInstall task not encapsulated in a Maintenance container' } New-AutonanceTask -Type 'WindowsUpdateInstall' -Name $ComputerName -Credential $Credential -Arguments $PSBoundParameters -ScriptBlock { [CmdletBinding()] param ( # This task restarts the specified Windows computer. [Parameter(Mandatory = $true, Position = 0)] [System.String] $ComputerName, # Specifies a user account that has permission to perform the task. [Parameter(Mandatory = $false)] [System.Management.Automation.PSCredential] [System.Management.Automation.Credential()] $Credential, # If specified, all available updates will be installed without a query. [Parameter(Mandatory = $false)] [switch] $All ) try { $guid = New-Guid | Select-Object -ExpandProperty 'Guid' $script = Get-Content -Path "$Script:ModulePath\Scripts\WindowsUpdate.ps1" $session = New-AutonanceSession -ComputerName $ComputerName -Credential $Credential -SessionType WinRM -ErrorAction Stop ## Part 1: Search for pending updates Write-Autonance -Message 'Search for pending updates ...' $pendingUpdates = Invoke-Command -Session $session -ErrorAction Stop -ScriptBlock { $updateSession = New-Object -ComObject 'Microsoft.Update.Session' $updateSearcher = $updateSession.CreateUpdateSearcher() $searchResult = $updateSearcher.Search("IsInstalled=0 and Type='Software'") foreach ($update in $searchResult.Updates) { [PSCustomObject] @{ KBArticle = 'KB' + $update.KBArticleIDs[0] Identity = $update.Identity.UpdateID Title = $update.Title } } } if ($pendingUpdates.Count -eq 0) { Write-Autonance -Message 'No pending updates found' return } Write-Autonance -Message ("{0} pending update(s) found" -f $pendingUpdates.Count) ## Part 2: Select updates if ($All.IsPresent) { Write-Autonance -Message 'All pending update(s) were preselected to install' $selectedUpdates = $pendingUpdates } else { Write-Autonance -Message 'Query the user for update(s) to install' $readHostMultipleChoiceSelection = @{ Caption = 'Choose Updates' Message = 'Please select the updates to install from the following list.' ChoiceObject = $pendingUpdates ChoiceLabel = $pendingUpdates.Title } $selectedUpdates = @(Read-HostMultipleChoiceSelection @readHostMultipleChoiceSelection) if ($selectedUpdates.Count -eq 0) { Write-Autonance -Message 'No updates selected by the user' return } Write-Autonance -Message ("{0} pending update(s) were selected to install" -f $selectedUpdates.Count) } ## Part 3: Install the updates with a one-time scheduled task Write-Autonance -Message 'Invoke a remote scheduled task to install the update(s)' # Create and start the scheduled task Invoke-Command -Session $session -ErrorAction Stop -ScriptBlock { $using:script | Set-Content -Path "C:\Windows\Temp\WindowsUpdate-$using:guid.ps1" -Encoding UTF8 $updateList = $using:selectedUpdates.Identity -join ',' try { # Use the new scheduled tasks cmdlets $newScheduledTask = @{ Action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument "-NoProfile -ExecutionPolicy Bypass -File `"C:\Windows\Temp\WindowsUpdate-$using:guid.ps1`" -Id `"$using:guid`" -Update `"$updateList`"" -ErrorAction Stop Trigger = New-ScheduledTaskTrigger -Once -At (Get-Date).AddSeconds(-1) -ErrorAction Stop Principal = New-ScheduledTaskPrincipal -UserId 'SYSTEM' -RunLevel Highest -ErrorAction Stop Settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -ErrorAction Stop } New-ScheduledTask @newScheduledTask -ErrorAction Stop | Register-ScheduledTask -TaskName "WindowsUpdate-$using:guid" -ErrorAction Stop | Start-ScheduledTask -ErrorAction Stop } catch { # Craete a temporary batch file "powershell.exe -NoProfile -ExecutionPolicy Bypass -File `"C:\Windows\Temp\WindowsUpdate-$using:guid.ps1`" -Id `"$using:guid`" -Update `"$updateList`"" | Out-File "C:\Windows\Temp\WindowsUpdate-$using:guid.cmd" -Encoding Ascii # The scheduled tasks cmdlets are missing, use schtasks.exe (SCHTASKS.EXE /CREATE /RU "NT Authority\System" /SC ONCE /ST 23:59 /TN "WindowsUpdate-$using:guid" /TR "`"C:\Windows\Temp\WindowsUpdate-$using:guid.cmd`"" /RL HIGHEST /F) | Out-Null (SCHTASKS.EXE /RUN /TN "WindowsUpdate-$using:guid") | Out-Null } } # Wait for every step until it is completed foreach ($step in @('Search', 'Download', 'Install')) { $status = Invoke-Command -Session $session -ErrorAction Stop -ScriptBlock { $step = $using:step $path = "C:\Windows\Temp\WindowsUpdate-$using:guid.xml" do { Start-Sleep -Seconds 1 if (Test-Path -Path $path) { $status = Import-Clixml -Path $path } } while ($null -eq $status -or $status.$step.Status -eq $false) Write-Output $status.$step } Write-Autonance -Message $status.Message if (-not $status.Result) { throw $status.Message } } } catch { throw $_ } finally { # Try to cleanup the scheduled task and the script file if ($null -ne $session) { Invoke-Command -Session $session -ErrorAction SilentlyContinue -ScriptBlock { Unregister-ScheduledTask -TaskName "WindowsUpdate-$using:guid" -Confirm:$false -ErrorAction SilentlyContinue Remove-Item -Path "C:\Windows\Temp\WindowsUpdate-$using:guid.cmd" -Force -ErrorAction SilentlyContinue Remove-Item -Path "C:\Windows\Temp\WindowsUpdate-$using:guid.ps1" -Force -ErrorAction SilentlyContinue Remove-Item -Path "C:\Windows\Temp\WindowsUpdate-$using:guid.xml" -Force -ErrorAction SilentlyContinue } } Remove-AutonanceSession -Session $session # Ensure, that the next task has a short delay Start-Sleep -Seconds 3 } } } |