Private/WindowsUpdate/Invoke-WindowsUpdate.ps1

function Invoke-WindowsUpdate {
    #Requires -Version 3.0

    [CmdletBinding()]
    Param (
        [Parameter(Mandatory)]
        [string]$ComputerName,
        [System.TimeSpan]$InstallUpdateThreshold = $ModuleWideInstallUpdateThreshold,
        [int]$Timeout = $ModuleWideInstallUpdateTimeout,
        [string]$TaskName = $ModuleWideInstallUpdateTaskName,
        [string]$TaskDescription = $ModuleWideInstallUpdateTaskDescription,
        [scriptblock]$Filter,
        [string]$DefaultFilterString = $ModuleWideInstallUpdateDefaultFilterString,
        [string]$Criteria = $ModuleWideUpdateSearchCriteria
    )

    $ErrorActionPreference = 'Stop'

    Write-Debug -Message ('ENTER {0}' -f $MyInvocation.MyCommand.Name)

    try {
        Write-Debug -Message ('ENTER TRY {0}' -f $MyInvocation.MyCommand.Name)

        Write-Debug -Message ('$ComputerName = ''{0}''' -f $ComputerName)
        Write-Debug -Message ('$InstallUpdateThreshold: ''{0}''' -f [string]$InstallUpdateThreshold)
        Write-Debug -Message ('$Timeout = {0}' -f $Timeout)
        Write-Debug -Message ('$TaskName = ''{0}''' -f $TaskName)
        Write-Debug -Message ('$TaskDescription = ''{0}''' -f $TaskDescription)
        Write-Debug -Message ('$Filter = ''{0}''' -f $Filter)
        Write-Debug -Message ('$DefaultFilterString = ''{0}''' -f $DefaultFilterString)
        Write-Debug -Message ('$Criteria = ''{0}''' -f $Criteria)

        if (-not $Filter) {
            Write-Debug -Message ('$ComputerMaintenanceConfiguration = Get-ComputerMaintenanceConfiguration -ComputerName {0}' -f $ComputerName)
            $ComputerMaintenanceConfiguration = Get-ComputerMaintenanceConfiguration -ComputerName $ComputerName
            Write-Debug -Message ('$ComputerMaintenanceConfiguration: {0}' -f $ComputerMaintenanceConfiguration)
            Write-Debug -Message '$FilterString = $ComputerMaintenanceConfiguration.UpdateInstallFilter'
            $FilterString = $ComputerMaintenanceConfiguration.UpdateInstallFilter
            Write-Debug -Message ('$FilterString = ''{0}''' -f $FilterString)
            Write-Debug -Message 'if (-not $FilterString)'
            if (-not $FilterString) {
                Write-Debug -Message '$FilterString = $DefaultFilterString'
                $FilterString = $DefaultFilterString
                Write-Debug -Message ('$FilterString = ''{0}''' -f $FilterString)
            }
            Write-Debug -Message ('$Filter = [scriptblock]::Create(''{0}'')' -f $FilterString)
            $Filter = [scriptblock]::Create($FilterString)
            Write-Debug -Message ('$Filter: ''{0}''' -f $Filter)
        }
        
        [ScriptBlock]$UpdateScriptBlock = {
            $Searcher = New-Object -ComObject Microsoft.Update.Searcher
            $SearchResult = $Searcher.Search($Criteria).Updates
            $Session = New-Object -ComObject Microsoft.Update.Session

            $DesiredUpdates = New-Object -ComObject Microsoft.Update.UpdateColl
            foreach ($Item in $SearchResult) {
                $Item = $Item | Where-Object -FilterScript $Filter
                if ($Item) {
                    $null = $Item.AcceptEula()
                    $null = $DesiredUpdates.Add($Item)
                }
            }

            if ($DesiredUpdates.Count -gt 0) {
                $Downloader = $Session.CreateUpdateDownloader()
                $Downloader.Updates = $DesiredUpdates
                $null = $Downloader.Download()
    
                $Installer = New-Object -ComObject Microsoft.Update.Installer
                $Installer.Updates = $DesiredUpdates
                $null = $Installer.Install()
            }
        }

        Write-Debug -Message ('$UpdateScriptBlock: {0}' -f $UpdateScriptBlock)

        Write-Debug -Message '$FilterString = $Filter.ToString()'
        $FilterString = $Filter.ToString()
        Write-Debug -Message ('$FilterString = ''{0}''' -f $FilterString)
        Write-Debug -Message ('$FilterScriptBlockString = ''$Filter = {{{{{{0}}}}}}'' -f ''{0}''' -f $FilterString)
        $FilterScriptBlockString = '$Filter = {{{0}}}' -f $FilterString
        Write-Debug -Message ('$FilterScriptBlockString = ''{0}''' -f $FilterScriptBlockString)
        
        Write-Debug -Message '$UpdateScriptBlockString = $UpdateScriptBlock.ToString()'
        $UpdateScriptBlockString = $UpdateScriptBlock.ToString()
        Write-Debug -Message ('$UpdateScriptBlockString = ''{0}''' -f $UpdateScriptBlockString)

        Write-Debug -Message ('$SearchScriptBlockString = ''$Criteria = ''''{{0}}'''''' -f ''{0}''' -f $Criteria)
        $SearchScriptBlockString = '$Criteria = ''{0}''' -f $Criteria
        Write-Debug -Message ('$SearchScriptBlockString = ''{0}''' -f $SearchScriptBlockString)

        Write-Debug -Message ('$JoinedScriptBlockString = ''{{0}};{{1}};{{2}}'' -f ''{0}'', ''{1}'', ''{2}''' -f $SearchScriptBlockString, $FilterScriptBlockString, $UpdateScriptBlockString)
        $JoinedScriptBlockString = '{0};{1};{2}' -f $SearchScriptBlockString, $FilterScriptBlockString, $UpdateScriptBlockString
        Write-Debug -Message ('$JoinedScriptBlockString = ''{0}''' -f $JoinedScriptBlockString)

        Write-Debug -Message ('$ConvertedScriptBlock = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes(''{0}''))' -f $JoinedScriptBlockString)
        $ConvertedScriptBlock = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($JoinedScriptBlockString))
        Write-Debug -Message ('$ConvertedScriptBlock: ''{0}''' -f $ConvertedScriptBlock)

        Write-Debug -Message '$Scheduler = New-Object -ComObject ''Schedule.Service'''
        $Scheduler = New-Object -ComObject 'Schedule.Service'
        Write-Debug -Message '$Task = $Scheduler.NewTask(0)'
        $Task = $Scheduler.NewTask(0)

        Write-Debug -Message '$RegistrationInfo = $Task.RegistrationInfo'
        $RegistrationInfo = $Task.RegistrationInfo
        Write-Debug -Message ('$RegistrationInfo.Description = ''{0}''' -f $TaskDescription)
        $RegistrationInfo.Description = $TaskDescription
        Write-Debug -Message '$RegistrationInfo.Author = ''SYSTEM'''
        $RegistrationInfo.Author = 'SYSTEM'

        Write-Debug -Message '$Settings = $Task.Settings'
        $Settings = $Task.Settings
        Write-Debug -Message '$Settings.Enabled = $true'
        $Settings.Enabled = $true
        Write-Debug -Message '$Settings.StartWhenAvailable = $true'
        $Settings.StartWhenAvailable = $true
        Write-Debug -Message '$Settings.Hidden = $false'
        $Settings.Hidden = $false

        Write-Debug -Message '$Action = $Task.Actions.Create(0)'
        $Action = $Task.Actions.Create(0)
        Write-Debug -Message '$Action.Path = ''powershell'''
        $Action.Path = 'powershell'
        Write-Debug -Message ('$Action.Arguments = ''-NoLogo -NoProfile -NonInteractive -EncodedCommand {{0}}'' -f {0}' -f $ConvertedScriptBlock)
        $Action.Arguments = '-NoLogo -NoProfile -NonInteractive -EncodedCommand {0}' -f $ConvertedScriptBlock

        Write-Debug -Message '$Task.Principal.RunLevel = 1'
        $Task.Principal.RunLevel = 1

        Write-Debug -Message ('$Task.XmlText: {0}' -f [string]$Task.XmlText)

        Write-Debug -Message ('$Scheduler.Connect({0})' -f $ComputerName)
        $Scheduler.Connect($ComputerName)
        Write-Debug -Message '$RootFolder = $Scheduler.GetFolder(''\'')'
        $RootFolder = $Scheduler.GetFolder('\')

        Write-Debug -Message ('$RunningTask = $Scheduler.GetRunningTasks(0) | Where-Object {{$_.Name -eq {0}}}' -f $TaskName)
        $RunningTask = $Scheduler.GetRunningTasks(0) | Where-Object {$_.Name -eq $TaskName}
        Write-Debug -Message ('$RunningTask: {0}' -f [string]$RunningTask.Name)
        Write-Debug -Message 'if ($RunningTask)'
        if ($RunningTask) {
            $Message = 'Task {0} is already running (PID: {1}) @ host {2}' -f $TaskName, $RunningTask.EnginePID, $ComputerName
            $PSCmdlet.ThrowTerminatingError((New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList ((New-Object -TypeName 'System.InvalidOperationException' -ArgumentList $Message), 'InvalidOperationException', [System.Management.Automation.ErrorCategory]::ResourceExists, $null)))
        }

        Write-Debug -Message ('$null = $RootFolder.RegisterTaskDefinition(''{0}'', $Task, 6, ''SYSTEM'', $null, 1)' -f $TaskName)
        $null = $RootFolder.RegisterTaskDefinition($TaskName, $Task, 6, 'SYSTEM', $null, 1)
        Write-Debug -Message ('$null = $RootFolder.GetTask(''{0}'').Run(0)' -f $TaskName)
        $null = $RootFolder.GetTask($TaskName).Run(0)

        Write-Debug -Message '$InitialDateTime = Get-Date'
        $InitialDateTime = Get-Date
        Write-Debug -Message ('$InitialDateTime: ''{0}''' -f [string]$InitialDateTime)
        do {
            Write-Debug -Message ('Start-Sleep -Seconds {0}' -f $Timeout)
            Start-Sleep -Seconds $Timeout

            Write-Debug -Message ('$RunningTask = $Scheduler.GetRunningTasks(0) | Where-Object {{$_.Name -eq ''{0}''}}' -f $TaskName)
            $RunningTask = $Scheduler.GetRunningTasks(0) | Where-Object {$_.Name -eq $TaskName}
            Write-Debug -Message ('$RunningTask: {0}' -f [string]$RunningTask.Name)
            if ($RunningTask) {
                Write-Debug -Message '$CurrentDateTime = Get-Date'
                $CurrentDateTime = Get-Date
                Write-Debug -Message ('$CurrentDateTime: ''{0}''' -f [string]$CurrentDateTime)
                Write-Debug -Message ('$InitialDateTime: ''{0}''' -f [string]$InitialDateTime)
                Write-Debug -Message ('$InstallUpdateThreshold: ''{0}''' -f [string]$InstallUpdateThreshold)
                Write-Debug -Message '$InstallUpdateDateTimeThreshold = $InitialDateTime + $InstallUpdateThreshold'
                $InstallUpdateDateTimeThreshold = $InitialDateTime + $InstallUpdateThreshold
                Write-Debug -Message ('$InstallUpdateDateTimeThreshold: ''{0}''' -f [string]$InstallUpdateDateTimeThreshold)
                Write-Debug -Message 'if ($CurrentDateTime -gt $InstallUpdateDateTimeThreshold)'
                if ($CurrentDateTime -gt $InstallUpdateDateTimeThreshold) {
                    $Message = 'The task {0} @ host {1} has not finished in the allowed time ({2}).' -f $TaskName, $ComputerName, [string]$InstallUpdateThreshold
                    $PSCmdlet.ThrowTerminatingError((New-Object -TypeName 'System.Management.Automation.ErrorRecord' -ArgumentList ((New-Object -TypeName 'System.TimeoutException' -ArgumentList $Message), 'TimeoutException', [System.Management.Automation.ErrorCategory]::OperationTimeout, $null)))
                }
            }
        }
        while ($RunningTask)

        Write-Debug -Message ('EXIT TRY {0}' -f $MyInvocation.MyCommand.Name)
    }
    catch {
        Write-Debug -Message ('ENTER CATCH {0}' -f $MyInvocation.MyCommand.Name)

        Write-Debug -Message ('{0}: $PSCmdlet.ThrowTerminatingError($_)' -f $MyInvocation.MyCommand.Name)
        $PSCmdlet.ThrowTerminatingError($_)

        Write-Debug -Message ('EXIT CATCH {0}' -f $MyInvocation.MyCommand.Name)
    }

    Write-Debug -Message ('EXIT {0}' -f $MyInvocation.MyCommand.Name)
}