This script is an extract of a longer version with the features of simultaneous executions and dealing with computers that have Internet connectivity – click here

Current version

# updateWindowsInvokeScheduledTasks.ps1
# Version 0.0.2

# Set a list of remote machines
$computernames=@(
    'TESTWINDOWS0001',
    'TESTWINDOWS0002',
    'TESTWINDOWS0003'
)

# Domain Admin credentials
$adminUsername='domain\admin'
$plaintextPassword='SOMEVERYCOMPLEXPASS'
$encryptedPassword=ConvertTo-securestring $plaintextPassword -AsPlainText -Force
$adminCredential=New-Object -TypeName System.Management.Automation.PSCredential -Args $adminUsername,$encryptedPassword
$autoReboot=$false
$directMicrosoftUpdates=$false

function updateWindowsInvokeScheduledTasks{ 
  [CmdletBinding()] 
  param ( 
      [parameter(Mandatory=$true,Position=0)][string[]]$computernames,
      [parameter(Mandatory=$false,Position=1)][System.Management.Automation.PSCredential]$adminCredential,
      [parameter(Mandatory=$false,Position=6)][bool]$autoReboot=$false,
      [parameter(Mandatory=$false,Position=2)][bool]$directMicrosoftUpdates=$false,
      [parameter(Mandatory=$false,Position=4)][int]$winRmPort=5985,
      [parameter(Mandatory=$false,Position=5)][string]$logFileName='PSWindowsUpdate.log'
      )    
  # $ErrorActionPreference='stop'
  $results=[hashtable]@{}
  function installPsWindowsUpdate{
      $ErrorActionPreference='stop'
      $psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -EA SilentlyContinue;
      if (!($psWindowsUpdateAvailable)){
          try {                
              [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
              if(Get-PackageProvider 'nuget' -ea SilentlyContinue -Force){
                  Install-PackageProvider -Name Nuget -RequiredVersion 2.8.5.201 -Force
              }
              if((Get-PSRepository psgallery).InstallationPolicy -ne 'Trusted'){
                  $null=Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
              }                
              $psWindowsUpdate=Get-Module -ListAvailable -Name 'PSWindowsUpdate' -EA Ignore
              if(!$psWindowsUpdate){
                  $null=Install-Module PSWindowsUpdate -Confirm:$false -Force                    
              }
              # Register Microsoft Update Service if it has not been registered
              $null=Import-Module PSWindowsUpdate -force
              $microsoftUpdateId='7971f918-a847-4430-9279-4a52d1efe18d'
              if (!($microsoftUpdateId -in (Get-WUServiceManager).ServiceID)){
                  Add-WUServiceManager -ServiceID $microsoftUpdateId -Confirm:$false
                  }
              return $true;
              }
          catch{
              write-host "Prerequisites not met on $ENV:COMPUTERNAME.";
              return $false;
          }
      }else{
          # Register Microsoft Update Service if it has not been registered
          $microsoftUpdateId='7971f918-a847-4430-9279-4a52d1efe18d'
          if (!($microsoftUpdateId -in (Get-WUServiceManager).ServiceID)){
              Add-WUServiceManager -ServiceID $microsoftUpdateId -Confirm:$false
              }
          return $true
          }
  }
  function connectWinRm($computer,$adminCredential,$winRmPort=5985){
      if(!$computer){
          write-warning "Computer name must be specified to initiate a WinRM connection."
          return $false
      }
      # Legacy equivalent to Test-Netconnection
      function checkNetConnection($computername,$port,$timeout=1000,$verbose=$false) {
          $tcp = New-Object System.Net.Sockets.TcpClient;
          try {
              $connect=$tcp.BeginConnect($computername,$port,$null,$null)
              $wait = $connect.AsyncWaitHandle.WaitOne($timeout,$false)
              if(!$wait){
                  $null=$tcp.EndConnect($connect)
                  $tcp.Close()
                  if($verbose){
                      Write-Host "Connection Timeout" -ForegroundColor Red
                      }
                  Return $false
              }else{
                  $error.Clear()
                  $null=$tcp.EndConnect($connect) # Dispose of the connection to release memory
                  if(!$?){
                      if($verbose){
                          write-host $error[0].Exception.Message -ForegroundColor Red
                          }
                      $tcp.Close()
                      return $false
                      }
                  $tcp.Close()
                  Return $true
              }
          } catch {
              return $false
          }
      }
      function enableWinRmRemotely($remoteComputer,$winRmPort,$adminCredential){
          function Check-NetConnection($computername,$port,$timeout=1000,$verbose=$false) {
                  $tcp = New-Object System.Net.Sockets.TcpClient;
                  try {
                      $connect=$tcp.BeginConnect($computername,$port,$null,$null)
                      $wait = $connect.AsyncWaitHandle.WaitOne($timeout,$false)
                      if(!$wait){
                          $null=$tcp.EndConnect($connect)
                          $tcp.Close()
                          if($verbose){
                              Write-Host "Connection Timeout" -ForegroundColor Red
                              }
                          Return $false
                      }else{
                          $error.Clear()
                          $null=$tcp.EndConnect($connect) # Dispose of the connection to release memory
                          if(!$?){
                              if($verbose){
                                  write-host $error[0].Exception.Message -ForegroundColor Red
                                  }
                              $tcp.Close()
                              return $false
                              }
                          $tcp.Close()
                          Return $true
                      }
                  } catch {
                      return $false
                  }
          }
          if (!(get-command psexec)){
              # Install Chocolatey
              if (!(Get-Command choco.exe -ErrorAction SilentlyContinue)) {
                  [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
                  Set-ExecutionPolicy Bypass -Scope Process -Force; iex ((New-Object System.Net.WebClient).DownloadString('https://chocolatey.org/install.ps1'))
                  }
              choco install sysinternals -y;  
              }
          $success=check-netconnection $remoteComputer $winRmPort
          write-host 'Attempting to use psexec to enable WinRM remotely...'
          if(!$adminCredential){ # Enable WinRM Remotely
              $null=psexec.exe \\$remoteComputer -s C:\Windows\system32\winrm.cmd qc -quiet; 
          }else{
              $username=$adminCredential.Username
              $password=$adminCredential.GetNetworkCredential().Password
              $null=psexec.exe \\$remoteComputer -u $username -p $password -s C:\Windows\system32\winrm.cmd qc -quiet
              }
          return check-netconnection $remoteComputer $winRmPort
      }

      # If machine is not pingable, wait five minutes
      $fiveMinuteTimer=[System.Diagnostics.Stopwatch]::StartNew()
      do{
          $ping = Test-Connection $computer -quiet
          if($ping -eq $false){sleep 1}
          $pastFiveMinutes=$fiveMinuteTimer.Elapsed.TotalMinutes -ge 5
      }until ($ping -eq $true -or $pastFiveMinutes)
      $fiveMinuteTimer.stop()

      $winRmAvailable=checkNetConnection $computer $winRmPort
      if(!$winRmAvailable){
          write-host "Attempting to enable WinRM on $computer" -ForegroundColor Yellow
          $enableWinRmSuccessful=enableWinRmRemotely $computer
          if($enableWinRmSuccessful){
              write-host "WinRM enabled: $enableWinRmSuccessful"
          }else{
              write-warning "WinRM could not be enabled remotely. WinRM connection aborted."
              return $false
              }
      }   
      # Wait for WinRm session prior to proceeding
      if($session.state -eq 'Opened'){remove-pssession $session}
      do{
          $session=if($adminCredential){
              try{
                  New-PSSession -ComputerName $computer -Credential $adminCredential -ea Stop
              }catch{
                  New-PSSession -ComputerName $computer -Credential $adminCredential -SessionOption $(new-pssessionoption -IncludePortInSPN)
              }
          }else{
              try{
                  New-PSSession -ComputerName $computer -ea Stop
              }catch{
                  New-PSSession -ComputerName $computer -SessionOption $(new-pssessionoption -IncludePortInSPN)
              }
          }
          sleep -seconds 1
          if ($session){
              write-host "Connected to $computer."
              return $session
          }
      } until ($session.state -match "Opened")
  }

  function includePsWindowsUpdate($session){
      $psWindowsUpdateIsAvailable=invoke-command -session $session -scriptblock {
          param($installPsWindowsUpdate);
          # Register Microsoft Update Service if it has not been registered
          return [ScriptBlock]::Create($installPsWindowsUpdate).Invoke()
          } -Args ${function:installPsWindowsUpdate}
      if($psWindowsUpdateIsAvailable){
          return $true
      }else{
          return $false
          }
  }

  function cleanupWuJob($session,$logFile="C:\$logFileName"){
      invoke-command -Session $session -ScriptBlock {
          param($logFile)
          if (Get-ScheduledTask -TaskName "PSWindowsUpdate" -ErrorAction SilentlyContinue){
              Write-Host "Removing PSWindowsUpdate scheduled task from $env:computername..."
              Unregister-ScheduledTask -TaskName 'PSWindowsUpdate' -Confirm:$false};
          if (Test-Path $logFile -ErrorAction SilentlyContinue){
              Write-Host "Removing $logFile..."
              Remove-item $logFile -force
              }
      } -Args $logFile
  }

  function updateLocalWindowsUsingComObjects($autoReboot=$false,$logfile="C:\PSWindowsUpdate.log"){
    # in case contents of function is called from scheduled tasks without any default argument values
    if(!(test-path $logfile)){
        new-item $logfile -type file -force
    }
    $status="$(get-date) => $env:computername patching STARTED"
    write-host $status
    Add-Content -Path $logfile -Value $status
    $updateSession=New-Object -Com Microsoft.Update.Session
    $updateSearcher=$updateSession.CreateUpdateSearcher()
    $searchCriteria="IsInstalled=0 AND Type='Software'" # "IsHidden=0 AND IsInstalled=0 AND Type='Software'"
    $availableUpdates=$updateSearcher.Search($searchCriteria)
    $availableUpdatesCount=$availableUpdates.Updates.count
    if($availableUpdatesCount -eq 0){
        $status="$(get-date) => $env:computername has 0 available updates. Patching FINISHED"
        write-host $status
        Add-Content -Path $logfile -Value $status
        return $true
    }else{
        $status="$(get-date) => $availableUpdatesCount available updates detected"
        write-host $status
        Add-Content -Path $logfile -Value $status
        $updatesToDownload=New-Object -Com Microsoft.Update.UpdateColl
        For ($i=0; $i -lt $availableUpdates.Updates.Count; $i++){
            $item = $availableUpdates.Updates.Item($i)
            $Null = $updatesToDownload.Add($item)
        }                        
        $updateSession=New-Object -Com Microsoft.Update.Session
        $downloader=$updateSession.CreateUpdateDownloader()
        $downloader.Updates=$updatesToDownload
        try{
            $null=$downloader.Download()
        }catch{
            write-warning $_
        }
        $downloadedUpdates=New-Object -Com Microsoft.Update.UpdateColl
        For ($i=0; $i -lt $availableUpdates.Updates.Count; $i++){
            $item = $availableUpdates.Updates.Item($i)
            If ($item.IsDownloaded) {
                $null=$downloadedUpdates.Add($item)        
            }
        }
        $downloadedCount=$downloadedUpdates.count                        
        if($downloadedCount -eq 0){
            $status="$(get-date) => Installation cannot proceed 0 successful downloads."
            write-host $status
            Add-Content -Path $logfile -Value $status
            return $false
        }else{
            $status="$(get-date) => $downloadedCount of $availableUpdatesCount updates downloaded successfully"
            write-host $status
            Add-Content -Path $logfile -Value $status
            $installCount=0
            foreach($update in $downloadedUpdates){
                $thisUpdate = New-object -com "Microsoft.Update.UpdateColl"
                $thisCount=$installCount++ +1
                $null=$thisUpdate.Add($update)
                $installer = $updateSession.CreateUpdateInstaller()
                $installer.Updates = $thisUpdate
                $installResult = $installer.Install()
                $translatedCode=switch ($installResult.ResultCode){
                    0  {'not started'}
                    1  {'in progress'}
                    2  {'succeeded'}
                    3  {'succeeded with errors'}
                    4  {'failed'}
                    5  {'aborted'}
                }
                $status="$(get-date) => $thisCount of $downloadedCount $translatedCode`: $($update.Title)"
                write-host $status
                Add-Content -Path $logfile -Value $status                                
            }
        }
    }
    $pendingReboot=.{
        if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
        if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
        if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
        try { 
            $util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
            $status = $util.DetermineIfRebootPending()
            if (($null -ne $status) -and $status.RebootPending) {
                return $true
            }
        }catch{}                            
        return $false
    }
    If($pendingReboot){
        if($autoReboot){
            $status="$(get-date) => $env:computername REBOOTED"
            write-host $status
            Add-Content -Path $logfile -Value $status
            (Get-WMIObject -Class Win32_OperatingSystem).Reboot()
        }else{
            $status="$(get-date) => $env:computername required a REBOOT"
            write-host $status
            Add-Content -Path $logfile -Value $status
        }
    }else{
        $status="$(get-date) => $env:computername patching FINISHED"
        write-host $status
        Add-Content -Path $logfile -Value $status
    }
}
  function invokeScheduledTaskCallPowerShellFunction{
      param(
          [string[]]$computernames,
          [string]$scriptblock,
          [string]$taskName,
          [string]$description,
          [string]$repeatIntervalMinutes,
          [System.Management.Automation.PSCredential]$credential
          )
      $username=$credential.Username;
      $plaintextPassword=$credential.GetNetworkCredential().password;                    
      if ($credential){
          foreach ($computer in $computernames){
              write-host "Adding task on $computer..."
              $thisSession=if($credential){
                      try{
                          New-PSSession -ComputerName $computer -Credential $credential -ea Stop
                      }catch{
                          New-PSSession -ComputerName $computer -Credential $credential -SessionOption $(new-pssessionoption -IncludePortInSPN)
                      }
                  }else{
                      try{
                          New-PSSession -ComputerName $computer -ea Stop
                      }catch{
                          New-PSSession -ComputerName $computer -SessionOption $(new-pssessionoption -IncludePortInSPN)
                      }
                  }
              if ($thisSession.state -match "Opened"){
                  $scheduledTaskAdded=Invoke-Command -session $thisSession -ScriptBlock{
                      param($scriptblock,$taskName,$description,$repeatMinutes,$user,$password)          
                      $windowsVersion=[Environment]::OSVersion.Version
                      $windowsUpdateScript='C:\Scripts\WindowsUpdates.ps1'                               
                      if(!(test-path $windowsUpdateScript)){
                          $null=new-item $windowsUpdateScript -type File -force
                          $null=Unblock-File -Path $windowsUpdateScript
                      }
                      $null=Set-Content -Path $windowsUpdateScript -Value $scriptblock
                      if($windowsVersion -ge [version]'6.2'){
                          $username=if($user -notmatch '\\'){"$env:USERDOMAIN`\$user"}else{$user}
                          #$encryptedPassword=ConvertTo-SecureString $password -AsPlainText -Force
                          #$adminCredential=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username,$encryptedPassword;
                          # Unrestrict this Domain Administrator from security prompts
                          Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted -Force
                          $settingsCommand = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit 0
                          $callPowerShell = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-ExecutionPolicy Bypass $windowsUpdateScript"
                          $runNow=$false
                          $taskTrigger = if($repeatMinutes -gt 0){ 
                                  New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $repeatMinutes)
                              }else{
                                  $runNow=$true;
                                  New-ScheduledTaskTrigger -Once -At (get-date).AddSeconds(-1)
                              }
                          # Unregister the Scheduled task if it already exists
                          Get-ScheduledTask $taskName -ErrorAction SilentlyContinue | Unregister-ScheduledTask -Confirm:$false;                
                          # Create new scheduled task
                          $null=Register-ScheduledTask -Action $callPowerShell -Trigger $taskTrigger `
                              -TaskName $taskName -Description $description `
                              -User $username -Password $password `
                              -Settings $settingsCommand -RunLevel Highest;
                          if($runNow){
                              Start-ScheduledTask -TaskName $taskname
                              $timer =  [Diagnostics.Stopwatch]::StartNew()
                              while (((Get-ScheduledTask -TaskName $taskname).State -ne 'Running') -or $null -eq $(gc C:\PSWindowsUpdate.log -EA Ignore)) {    
                                  Write-Verbose -Message "Waiting on scheduled task..."
                                  Start-Sleep -Seconds 2   
                                  }
                              Write-host "$taskname has taken $([math]::round(($timer.Elapsed.TotalSeconds),2)) seconds to initiate"
                              $timer.Stop()
                              start-sleep -seconds 5                                
                          }
                          return $true
                      }else{
                          write-host "$env:computername is too old. Just turn it off."
                          return $false
                      }
                  } -ArgumentList $scriptblock,$taskName,$description,$repeatIntervalMinutes,$username,$plainTextPassword
                  Remove-PSSession $thisSession
                  return $scheduledTaskAdded
              }
          }        
      }else{
          write-host "Please run this program with a valid Administrator account."
          return $false
      }
  }

$regexIP = [regex] "\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b"
$names=$(foreach($computername in $computernames){
    if($computername -match $regexIp){
        [System.Net.Dns]::GetHostByAddress($computername).hostname.toUpper()
    }else{$computername.toUpper()}
})|select-Object -Unique

foreach($computer in $names){
    write-warning "Now performing Windows updates on $computer...`r`nPress Ctrl+C at anytime to cancel."
    $session=connectWinRm $computer $adminCredential
    $taskName='windowsUpdate'
    $taskDescription='Perform Windows Updates'
    $taskAdded=invokeScheduledTaskCallPowerShellFunction `
        -computernames $computer `
        -scriptblock $function:updateLocalWindowsUsingComObjects `
        -taskName $taskName `
        -description $taskDescription `
        -repeatIntervalMinutes 0 `
        -credential $adminCredential                
    if($taskAdded){
        $updatesLog="\\$computer\C$\$logfileName"
        $localUpdatesLog="C:\$logfileName"  
        $logContent=$previousLine=''
        $updatesCompleted=$false
        write-host "Checking $updatesLog for Windows Update statuses..."
        while(!$updatesCompleted){
            $logContent=try{
                    get-content $updatesLog -ea Stop
                }catch{
                    invoke-command -ComputerName $computer -Credential $adminCredential -ScriptBlock{
                        param($localUpdatesLog)
                        get-content $localUpdatesLog
                    } -Args $localUpdatesLog
                }
            if($logContent){
                $currentLine=($logContent|select -last 1|out-string).trim()
                $updatesCompleted=$currentLine -match 'FINISHED$'
                $rebootRequired=$currentLine -match 'REBOOT$'
                #$rebooted=$currentLine -match 'REBOOTED$'
                if($currentLine -ne $previousLine){
                    write-host "`r`n$currentLine"
                    $previousLine=$currentLine
                }else{
                    write-host '.' -nonewline
                    sleep 10
                }
                if($rebootRequired -and $autoReboot){
                    # remove-pssession $sesion               
                    # $server=gwmi Win32_operatingsystem -computer $computer
                    # $status = $server.reboot()
                    # if ($status.ReturnValue = "0") {
                    #     Write-Host "Reboot successful."
                    # }else{
                    #     Write-Host "Reboot failed."
                    # }
                    # do {
                    #     Start-Sleep -s 2
                    # }while (Test-Connection $computer -count 1 -EA Ignore)                    
                    write-warning "Reboot flag detected and `$autoReboot flag has been set to `$True...`r`nProceeding to restart $computer now."
                    $null=try{                            
                            Restart-Computer -ComputerName $computer -credential $adminCredential -Force -Wait -ea Stop
                            $currentLine="$(get-date) => $computer REBOOTED"                            
                        }catch{
                            invoke-command -ComputerName $computer -Credential $adminCredential -ScriptBlock{
                                write-host "Restarting $env:computername..."
                                Restart-Computer -Force -Wait
                            }
                            $computerIsOff=$computerIsOn=$computerRestarted=$False
                            do{                                
                                if(!$computerIsOff){
                                    write-host "." -NoNewline
                                    sleep 1
                                    $computerIsOff=!(test-connection $computer -Count 1 -Quiet -EA Ignore)
                                }elseif(!$computerIsOn){
                                    write-host "." -NoNewline
                                    sleep 1
                                    $computerIsOn=test-connection $computer -Count 1 -Quiet
                                    if($computerIsOn){                                        
                                        $startUpTime=invoke-command -computername $computer -Credential $adminCredential -scriptblock{(Get-CimInstance -ClassName win32_OperatingSystem).lastbootuptime}
                                        $computerRestarted=$True
                                        $currentLine="$(($startupTime|out-string).trim()) => $computer REBOOTED"
                                    }                                   
                                }
                            }until($computerRestarted)
                        }                      
                    try{
                        Add-content $updatesLog $currentLine -credential $adminCredential
                    }catch{
                        invoke-command -ComputerName $computer -Credential $adminCredential -ScriptBlock{
                            param($localUpdatesLog,$currentLine)
                            Add-content $localUpdatesLog $currentLine
                        } -Args $localUpdatesLog,$currentLine
                    }
                    write-host $currentLine
                    do{
                        $session=if($adminCredential){
                            try{
                                New-PSSession -ComputerName $computer -Credential $adminCredential -ea Stop
                            }catch{
                                New-PSSession -ComputerName $computer -Credential $adminCredential -SessionOption $(new-pssessionoption -IncludePortInSPN)
                            }
                        }else{
                            try{
                                New-PSSession -ComputerName $computer -ea Stop
                            }catch{
                                New-PSSession -ComputerName $computer -SessionOption $(new-pssessionoption -IncludePortInSPN)
                            }
                        }
                        sleep 1
                    }until($session.State -eq 'Opened')
                    $currentLine="$(get-date) => $taskName re-triggered"
                    invoke-command -session $session -scriptblock {
                        param ($localUpdatesLog,$taskName,$currentLine)                                            
                        $null=Add-content $localUpdatesLog $currentLine
                        Start-ScheduledTask -TaskName $taskname
                    } -Args $localUpdatesLog,$taskName,$currentLine
                    Remove-PSSession $session                
                    write-host $currentLine
                }elseif($updatesCompleted){
                    $currentLine="$(get-date) => $computer patching FINISHED"
                    $null=try{
                        Add-content $updatesLog $currentLine -ea Stop
                    }catch{
                        invoke-command -ComputerName $computer -Credential $adminCredential -ScriptBlock{
                            param($localUpdatesLog,$currentLine)
                            Add-content $localUpdatesLog $currentLine
                        } -Args $localUpdatesLog,$currentLine
                    }
                    write-host $currentLine -ForegroundColor Green
                    $results."$computer"=$currentLine
                }elseif($rebootRequired -and !$autoReboot){
                    $currentStatus="$computer reboot required."
                    $results."$computer"=$currentStatus
                    write-warning $currentStatus
                    if($session){Remove-PSSession $session}
                    break
                }
            }else{
                write-host '.' -nonewline
                sleep 10
            }        
        }
    }else{
        $currentStatus="Cannot add scheduled task onto $computer"
        write-warning $currentStatus
        $results."$computer"=$currentStatus
        if($session){Remove-PSSession $session}
        break
    }
    if($session){Remove-PSSession $session}
}      
return $results
}

updateWindowsInvokeScheduledTasks $computernames $adminCredential $autoReboot $directMicrosoftUpdates

Older version

# updateWindowsNoInternet.ps1
# Version 0.01

# Specify computer name(s)
$computer='testWindows'

# Set credentials - to be injected by Jenkins
$adminUsername='domain\admin'
$plaintextPassword='PASSWORD'
$encryptedPassword=ConvertTo-securestring $plaintextPassword -AsPlainText -Force
$adminCredential=New-Object -TypeName System.Management.Automation.PSCredential -Args $adminUsername,$encryptedPassword

$targetHasInternet=invoke-command -ComputerName $computer -Credential $adminCredential -scriptblock{test-connection 8.8.8.8 -Count 1 -Quiet}
if(!$targetHasInternet){
    write-host "$computer`: no internet connectivity detected"
    function updateLocalWindowsUsingComObjects($autoReboot=$false,$logfile='c:\WindowsUpdate.log'){
        # in case contents of function is called from scheduled tasks without any default argument values
        $logFile=if($logFile){$logFile}else{'c:\WindowsUpdate.log'}
        if(!(test-path $logfile)){
            new-item $logfile -type file -force
        }
        $status="$(get-date) => $env:computername patching STARTED"
        write-host $status
        Add-Content -Path $logfile -Value $status
        $updateSession=New-Object -Com Microsoft.Update.Session
        $updateSearcher=$updateSession.CreateUpdateSearcher()
        $searchCriteria="IsInstalled=0 AND Type='Software'" # "IsHidden=0 AND IsInstalled=0 AND Type='Software'"
        $availableUpdates=$updateSearcher.Search($searchCriteria)
        $availableUpdatesCount=$availableUpdates.Updates.count
        if($availableUpdatesCount -eq 0){
            $status="$(get-date) => $env:computername has 0 available updates. Patching FINISHED"
            write-host $status
            Add-Content -Path $logfile -Value $status
            return $true
        }else{
            $status="$(get-date) => $availableUpdatesCount available updates detected"
            write-host $status
            Add-Content -Path $logfile -Value $status
            $updatesToDownload=New-Object -Com Microsoft.Update.UpdateColl
            For ($i=0; $i -lt $availableUpdates.Updates.Count; $i++){
                $item = $availableUpdates.Updates.Item($i)
                $Null = $updatesToDownload.Add($item)
            }                        
            $updateSession=New-Object -Com Microsoft.Update.Session
            $downloader=$updateSession.CreateUpdateDownloader()
            $downloader.Updates=$updatesToDownload
            try{
                $null=$downloader.Download()
            }catch{
                write-warning $_
            }
            $downloadedUpdates=New-Object -Com Microsoft.Update.UpdateColl
            For ($i=0; $i -lt $availableUpdates.Updates.Count; $i++){
                $item = $availableUpdates.Updates.Item($i)
                If ($item.IsDownloaded) {
                    $null=$downloadedUpdates.Add($item)        
                }
            }
            $downloadedCount=$downloadedUpdates.count                        
            if($downloadedCount -eq 0){
                $status="$(get-date) => Installation cannot proceed 0 successful downloads."
                write-host $status
                Add-Content -Path $logfile -Value $status
                return $false
            }else{
                $status="$(get-date) => $downloadedCount of $availableUpdatesCount updates downloaded successfully"
                write-host $status
                Add-Content -Path $logfile -Value $status
                $installCount=0
                foreach($update in $downloadedUpdates){
                    $thisUpdate = New-object -com "Microsoft.Update.UpdateColl"
                    $thisCount=$installCount++ +1
                    $null=$thisUpdate.Add($update)
                    $installer = $updateSession.CreateUpdateInstaller()
                    $installer.Updates = $thisUpdate
                    $installResult = $installer.Install()
                    $translatedCode=switch ($installResult.ResultCode){
                        0  {'not started'}
                        1  {'in progress'}
                        2  {'succeeded'}
                        3  {'succeeded with errors'}
                        4  {'failed'}
                        5  {'aborted'}
                    }
                    $status="$(get-date) => $thisCount of $downloadedCount $translatedCode`: $($update.Title)"
                    write-host $status
                    Add-Content -Path $logfile -Value $status                                
                }
            }
        }
        $pendingReboot=.{
            if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
            if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
            if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
            try { 
                $util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
                $status = $util.DetermineIfRebootPending()
                if (($null -ne $status) -and $status.RebootPending) {
                    return $true
                }
            }catch{}                            
            return $false
        }
        If($pendingReboot){
            if($autoReboot){
                $status="$(get-date) => $env:computername REBOOTED"
                write-host $status
                Add-Content -Path $logfile -Value $status
                (Get-WMIObject -Class Win32_OperatingSystem).Reboot()
            }else{
                $status="$(get-date) => $env:computername required a REBOOT"
                write-host $status
                Add-Content -Path $logfile -Value $status
            }
        }else{
            $status="$(get-date) => $env:computername patching FINISHED"
            write-host $status
            Add-Content -Path $logfile -Value $status
        }
    }
    function invokeScheduledTaskCallPowerShellFunction{
        param(
            [string[]]$computernames,
            [string]$scriptblock,
            [string]$taskName,
            [string]$description,
            [string]$repeatIntervalMinutes,
            [System.Management.Automation.PSCredential]$credential
            )
        $username=$credential.Username;
        $plaintextPassword=$credential.GetNetworkCredential().password;                    
        if ($credential){
            foreach ($computer in $computernames){
                write-host "Adding task on $computer..."
                $thisSession=if($credential){
                        try{
                            New-PSSession -ComputerName $computer -Credential $credential -ea Stop
                        }catch{
                            New-PSSession -ComputerName $computer -Credential $credential -SessionOption $(new-pssessionoption -IncludePortInSPN)
                        }
                    }else{
                        try{
                            New-PSSession -ComputerName $computer -ea Stop
                        }catch{
                            New-PSSession -ComputerName $computer -SessionOption $(new-pssessionoption -IncludePortInSPN)
                        }
                    }
                if ($thisSession.state -match "Opened"){
                    $scheduledTaskAdded=Invoke-Command -session $thisSession -ScriptBlock{
                        param($scriptblock,$taskName,$description,$repeatMinutes,$user,$password)          
                        $windowsVersion=[Environment]::OSVersion.Version
                        $windowsUpdateScript='C:\Scripts\WindowsUpdates.ps1'                               
                        if(!(test-path $windowsUpdateScript)){
                            $null=new-item $windowsUpdateScript -type File -force
                            $null=Unblock-File -Path $windowsUpdateScript
                        }
                        $null=Set-Content -Path $windowsUpdateScript -Value $scriptblock
                        if($windowsVersion -ge [version]'6.2'){
                            $username=if($user -notmatch '\\'){"$env:USERDOMAIN`\$user"}else{$user}
                            #$encryptedPassword=ConvertTo-SecureString $password -AsPlainText -Force
                            #$adminCredential=New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $username,$encryptedPassword;
                            # Unrestrict this Domain Administrator from security prompts
                            Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted -Force
                            $settingsCommand = New-ScheduledTaskSettingsSet -MultipleInstances IgnoreNew -ExecutionTimeLimit 0
                            $callPowerShell = New-ScheduledTaskAction -Execute "Powershell.exe" -Argument "-ExecutionPolicy Bypass $windowsUpdateScript"
                            $runNow=$false
                            $taskTrigger = if($repeatMinutes -gt 0){ 
                                    New-ScheduledTaskTrigger -Once -At (Get-Date) -RepetitionInterval (New-TimeSpan -Minutes $repeatMinutes)
                                }else{
                                    $runNow=$true;
                                    New-ScheduledTaskTrigger -Once -At (get-date).AddSeconds(-1)
                                }
                            # Unregister the Scheduled task if it already exists
                            Get-ScheduledTask $taskName -ErrorAction SilentlyContinue | Unregister-ScheduledTask -Confirm:$false;                
                            # Create new scheduled task
                            $null=Register-ScheduledTask -Action $callPowerShell -Trigger $taskTrigger `
                                -TaskName $taskName -Description $description `
                                -User $username -Password $password `
                                -Settings $settingsCommand -RunLevel Highest;
                            if($runNow){
                                Start-ScheduledTask -TaskName $taskname
                                $timeout = 60 ##  seconds
                                $timer =  [Diagnostics.Stopwatch]::StartNew()
                                while (((Get-ScheduledTask -TaskName $taskname).State -ne 'Running') -and  ($timer.Elapsed.TotalSeconds -lt $timeout)) {    
                                    Write-Verbose -Message "Waiting on scheduled task..."
                                    Start-Sleep -Seconds 2   
                                    }                                            
                                Write-host "$taskname has taken $([math]::round(($timer.Elapsed.TotalSeconds),2)) seconds to initiate"
                                $timer.Stop()
                                start-sleep -seconds 5                                
                            }
                            return $true
                        }else{
                            write-host "$env:computername is too old. Just turn it off."
                            return $false
                        }
                    } -ArgumentList $scriptblock,$taskName,$description,$repeatIntervalMinutes,$username,$plainTextPassword
                    Remove-PSSession $thisSession
                    return $scheduledTaskAdded
                }
            }        
        }else{
            write-host "Please run this program with a valid Administrator account."
            return $false
        }
    }
    $taskName='windowsUpdate'
    $taskDescription='Perform Windows Updates'
    $taskAdded=invokeScheduledTaskCallPowerShellFunction `
        -computernames $computer `
        -scriptblock $function:updateLocalWindowsUsingComObjects `
        -taskName $taskName `
        -description $taskDescription `
        -repeatIntervalMinutes 0 `
        -credential $adminCredential                
    if($taskAdded){
        $updatesLog="\\$computer\c$\WindowsUpdate.log"
        $localUpdatesLog='c:\WindowsUpdate.log'   
        $logContent=$previousLine=''
        $updatesCompleted=$false
        write-host "Checking $updatesLog for Windows Update statuses..."
        while(!$updatesCompleted){
            # Testing variance in results of local vs invoke-command
            # $computer='testWindows'
            # $updatesLog="\\$computer\c$\windows\system32\drivers\etc\hosts"
            # $localUpdatesLog='c:\windows\system32\drivers\etc\hosts'
            # $x=get-content $updatesLog
            # $y=invoke-command -ComputerName $computer -ScriptBlock{
            #     param($localUpdatesLog)
            #     get-content $localUpdatesLog
            # } -Args $localUpdatesLog
            $logContent=try{
                    get-content $updatesLog -credential $adminCredential -ea Stop
                }catch{
                    invoke-command -ComputerName $computer -Credential $adminCredential -ScriptBlock{
                        param($localUpdatesLog)
                        get-content $localUpdatesLog
                    } -Args $localUpdatesLog
                }
            if($logContent){
                $currentLine=($logContent|select -last 1|out-string).trim()
                $updatesCompleted=$currentLine -match 'FINISHED$'
                $rebootRequired=$currentLine -match 'REBOOT$'
                if($currentLine -ne $previousLine){
                    write-host "`r`n$currentLine"
                    $previousLine=$currentLine
                }else{
                    write-host '.' -nonewline
                    sleep 10
                }
                if($rebootRequired){                    
                    $null=try{                            
                            Restart-Computer -Force -ComputerName $computer -credential $adminCredential -Wait -ea Stop
                            $currentLine="$(get-date) => $computer REBOOTED"                            
                        }catch{
                            invoke-command -ComputerName $computer -Credential $adminCredential -ScriptBlock{
                                write-host "Restarting $env:computername..."
                                Restart-Computer -Force
                            }
                            $computerIsOff=$computerIsOn=$computerRestarted=$False
                            do{                                
                                if(!$computerIsOff){
                                    write-host "." -NoNewline
                                    sleep 1
                                    $computerIsOff=!(test-connection $computer -Count 1 -Quiet)
                                }elseif(!$computerIsOn){
                                    write-host "." -NoNewline
                                    sleep 1
                                    $computerIsOn=test-connection $computer -Count 1 -Quiet
                                    if($computerIsOn){                                        
                                        $startUpTime=invoke-command -computername $computer -Credential $adminCredential -scriptblock{(Get-CimInstance -ClassName win32_OperatingSystem).lastbootuptime}
                                        $computerRestarted=$True
                                        $currentLine="$(($startupTime|out-string).trim()) => $computer REBOOTED"
                                    }                                   
                                }
                            }until($computerRestarted)                            
                        }                      
                    try{
                        Add-content $updatesLog $currentLine -credential $adminCredential
                    }catch{
                        invoke-command -ComputerName $computer -Credential $adminCredential -ScriptBlock{
                            param($localUpdatesLog,$currentLine)
                            Add-content $localUpdatesLog $currentLine
                        } -Args $localUpdatesLog,$currentLine
                    }
                    write-host $currentLine
                    do{
                        $newSession=if($adminCredential){
                            try{
                                New-PSSession -ComputerName $computer -Credential $adminCredential -ea Stop
                            }catch{
                                New-PSSession -ComputerName $computer -Credential $adminCredential -SessionOption $(new-pssessionoption -IncludePortInSPN)
                            }
                        }else{
                            try{
                                New-PSSession -ComputerName $computer -ea Stop
                            }catch{
                                New-PSSession -ComputerName $computer -SessionOption $(new-pssessionoption -IncludePortInSPN)
                            }
                        }
                        sleep 1
                    }until($newSession.State -eq 'Opened')
                    $currentLine="$(get-date) => $taskName re-triggered"
                    invoke-command -session $newSession -scriptblock {
                        param ($localUpdatesLog,$taskName,$currentLine)
                        Start-ScheduledTask -TaskName $taskname                    
                        $null=Add-content $localUpdatesLog $currentLine
                    } -Args $localUpdatesLog,$taskName,$currentLine
                    Remove-PSSession $newSession                
                    write-host $currentLine
                }elseif($updatesCompleted){
                    $currentLine="$(get-date) => $computer patching FINISHED"
                    $null=try{
                        Add-content $updatesLog $currentLine -ea Stop
                    }catch{
                        invoke-command -ComputerName $computer -Credential $adminCredential -ScriptBlock{
                            param($localUpdatesLog,$currentLine)
                            Add-content $localUpdatesLog $currentLine
                        } -Args $localUpdatesLog,$currentLine
                    }
                    write-host $currentLine -ForegroundColor Green    
                }
            }else{
                write-host '.' -nonewline
                sleep 10
            }        
        }
    }
}else{
    write-host "$computer currently has Internet access. Use a diffferent function to patch it."
}
# Sample Output
#
# testwindows: no internet connectivity detected
# Adding task on testwindows...
# -Message windowsUpdate has taken 0.5089243 seconds to initiate
# Checking \\testwindows\c$\WindowsUpdate.log for Windows Update statuses...
# 01/18/2021 18:51:36 => TESTWINDOWS patching STARTED
# .01/18/2021 18:51:51 => 1 of 3 succeeded: Security Update for Windows Server 2019 for x64-based Systems (KB4535680)
# ........................................................................................................................
# ............................01/18/2021 19:16:34 => 2 of 3 succeeded: 2021-01 Cumulative Update for Windows Server 2019 f
# or x64-based Systems (KB4598230)
# ......01/18/2021 19:17:38 => 3 of 3 succeeded: 2021-01 Servicing Stack Update for Windows Server 2019 for x64-based Syst
# ems (KB4598480)
# .01/18/2021 19:17:43 => TESTWINDOWS required a REBOOT
# ...................................................
# 01/18/2021 19:29:25 => testwindows REBOOTED
# 01/18/2021 19:34:12 => windowsUpdate re-triggered
# Checking \\testwindows\c$\WindowsUpdate.log for Windows Update statuses...
# 01/18/2021 19:36:09 => TESTWINDOWS has 0 available updates. Patching FINISHED
# 01/18/2021 19:37:52 => testwindows patching FINISHED