This script has been deprecated in favor of a more universal one here.
function invokeWindowsUpdate{
[CmdletBinding()]
param (
[parameter(Mandatory=$true,Position=0)][string]$computer,
[parameter(Mandatory=$false,Position=1)][System.Management.Automation.PSCredential]$adminCredential,
[parameter(Mandatory=$false,Position=2)][bool]$microsoftUpdates=$false,
[parameter(Mandatory=$false,Position=3)][bool]$bypassWsus=$false,
[parameter(Mandatory=$false,Position=4)][int]$winRmPort=5985,
[parameter(Mandatory=$false,Position=5)][string]$logFile='C:\PSWindowsUpdate.log'
)
$ErrorActionPreference='stop'
<#
.SYNOPSIS
This script will automatically install all avaialable windows updates on a device and will automatically reboot if needed.
After reboots, windows updates will continue to run until all updates are installed.
# Features:
# - Check WSUS settings (bypass if required by the boolean value)
# - Prepare the targets by installing prerequisites prior to proceeding further to preemptively resolve dependency errors
# - Include additional dedendencies such as TLS1.2, Nuget & PSGALLERY
# - Check if server needs a reboot before issuing the reboot command, instead of just inadvertently trigger reboots
# - Fixed the blank lines in output log causing bug in status query
# - More thorough cleanup routine
# Future developments:
# - Detect and handle proxies
#>
function installPsWindowsUpdate{
$ErrorActionPreference='stop'
$psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -ErrorAction SilentlyContinue;
if (!($psWindowsUpdateAvailable)){
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false | Out-Null;
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null;
Install-Module PSWindowsUpdate -Confirm:$false -Force | Out-Null;
Import-Module PSWindowsUpdate -force | Out-Null;
return $true;
}
catch{
"Prerequisites not met on $ENV:COMPUTERNAME.";
return $false;
}
}else{
return $true
}
}
function checkPendingReboot([string]$computer=$ENV:computername,$session){
function checkRegistry{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-Item "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting" -EA Ignore) { return $true }
if (Get-Item 'HKLM:\SOFTWARE\Microsoft\ServerManager\CurrentRebootAttemps' -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name 'PendingFileRenameOperations' -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name 'PendingFileRenameOperations2' -EA Ignore) { return $true }
if (Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce' -Name 'DVDRebootSignal' -EA Ignore) { return $true }
if (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon' -Name 'JoinDomain' -EA Ignore) { return $true }
if (Get-ItemProperty 'HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon' -Name 'AvoidSpnSet' -EA Ignore) { return $true }
try{ # This peruses CCM utility, if exists
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($null -ne $status) -and $status.RebootPending){
$result.SCCMRebootPending = $true
}
}catch{
return $false
}
}
if ($ENV:computername -eq $computer){
$result=checkRegistry;
}else{
$result=Invoke-Command -session $session -ScriptBlock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -ArgumentList ${function:checkRegistry}
}
return $result;
}
function clearRebootFlags($computer,$session,$verbose=$false){
if (checkPendingReboot $computer $session){
$isLocalHost=$env:computername -eq $computer
if($isLocalHost){
write-warning "$computer is LOCALHOST. Please reboot manually to clear reboot flags"
return $false
}else{
write-host "`nRestarting remote computer $computer to clear pending reboot flags..."
if($adminCredential){
Restart-Computer -Wait -ComputerName $computer -Credential $adminCredential -Force
}else{
Restart-Computer -Wait -ComputerName $computer -Force
}
write-host "$computer has been successfully restarted!" -ForegroundColor Yellow
return $true
}
}else{
if($verbose){write-host "There are no pending reboot flags to clear." -ForegroundColor Green}
return $true
}
}
function checkWsus{
# Check if this machine has WSUS settings configured
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
$wuIsOn=(Get-ItemProperty -path $wuPath -name $wuKey -ErrorAction SilentlyContinue).$wuKey;
#if($wuIsOn){$GLOBAL:wsus=$True}else{$GLOBAL:wsus=$False}
return $wuIsOn
}
function turnoffWsus{
# Turn WSUS settings OFF temporarily...
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
Set-Itemproperty -path $wuPath -Name $wuKey -value 0
restart-service wuauserv;
}
function turnonWsus{
# Turning WSUS settings back to ON status
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
Set-Itemproperty -path $wuPath -Name $wuKey -value 1
restart-service wuauserv;
}
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{
if($adminCredential){
try{
$session=New-PSSession -ComputerName $computer -Credential $adminCredential
}catch{
$session=New-PSSession -ComputerName $computer -Credential $adminCredential -SessionOption $(new-pssessionoption -IncludePortInSPN)
}
}else{
try{
$session = New-PSSession -ComputerName $computer
}catch{
$session=New-PSSession -ComputerName $computer -SessionOption $(new-pssessionoption -IncludePortInSPN)
}
}
write-host "Connecting to remote computer $computer..."
sleep -seconds 1
if ($session){
write-host "Connected."
return $session
}
} until ($session.state -match "Opened")
}
# Advisories
write-warning "$computer will go through Windows Update and will REBOOT AUTOMATICALLY (if necessary). Press Ctrl+C at anytime to cancel."
$session=connectWinRm $computer $adminCredential
# Install prerequisites
$psWindowsUpdateAvailable=invoke-command -session $session -scriptblock {
param($installPsWindowsUpdate);
[ScriptBlock]::Create($installPsWindowsUpdate).Invoke();
} -Args ${function:installPsWindowsUpdate}
if(!$psWindowsUpdateAvailable){
write-warning "$computername`t: PSWindowsUpdate installation failed."
return $false
}
if($bypassWsus){
$wsusIsOn=Invoke-Command -session $session -scriptblock{
param($checkWsus);
[scriptblock]::create($checkWsus).invoke()
} -Args ${function:checkWsus}
if($wsusIsOn){
Invoke-Command -session $session -scriptblock{
param($turnoffWsus);
[scriptblock]::create($turnoffWsus).invoke()
} -args ${function:turnoffWsus}
}
}
Do{
#retrieves a list of available updates
write-host "Checking for new updates on $computer..."
$updates=invoke-command -session $session -scriptblock {
param($microsoftUpdates)
if($(Get-ExecutionPolicy) -ne 'RemoteSigned'){
Set-ExecutionPolicy RemoteSigned -force
}
$null=Import-Module PSWindowsUpdate
$availableUpdates=if($microsoftUpdates){
Get-wulist -MicrosoftUpdate -verbose
}else{
Get-Wulist -verbose
}
write-host $($availableUpdates|out-string).trim()
return $availableUpdates
} -Args $microsoftUpdates
$updatesCount=$updates.KB.count # $updates.count returns $null when count equals 1
# If there are available updates, proceed with installing the updates and then reboot the remote machine if required
if ($updatesCount){
#Invoke-WUJob will insert a scheduled task on the remote target as a mean to bypass 2nd hop issues
invoke-command -Session $session {
param($microsoftUpdates)
$logFile='C:\PSWindowsUpdate.log'
if(test-path $logFile){remove-item $logFile -force}
if($microsoftUpdates){
# 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
}
$invokeScript={
import-module PSWindowsUpdate;
Get-WindowsUpdate -AcceptAll -MicrosoftUpdate -Install | Out-File 'C:\PSWindowsUpdate.log'
}
}else{
$invokeScript={
import-module PSWindowsUpdate;
Get-WindowsUpdate -AcceptAll -Install | Out-File 'C:\PSWindowsUpdate.log'
}
}
Invoke-WUjob -ComputerName $env:computername -Script $invokeScript -Confirm:$false -RunNow
write-host "Windows Updates have been triggerred. Now checking for its log file to generate...`r`n"
Do{
$logFileGenerated=if(test-path $logFile){get-content $logFile}else{$false}
if(!$logFileGenerated){
Start-Sleep -Seconds 1
write-host '.' -NoNewline
}
}until($logFileGenerated)
} -Args $microsoftUpdates
#Show update status until the amount of installed updates equals the same as the count of updates being reported
$dotLimit=10
$dotCount=0
$minute=0
$lastActivity=$null;
$installedCount=0;
Write-Host "There $(if($updatesCount -eq 1){'is'}else{'are'}) $updatesCount pending update(s)`r`n";
do {
$getWindowsUpdateLog={param($logFile);Get-Content $logFile}
$updatestatus=Invoke-Command -Session $session -ScriptBlock $getWindowsUpdateLog -Args $logFile
$currentActivity=.{
$index=$updatestatus.count-1
do{
$value=$updatestatus[$index--]
}until($value)
return $value
}
$installedCount=([regex]::Matches($updatestatus, "Installed")).count
$failsCount=([regex]::Matches($updatestatus, "Failed")).count
$processedCount=$installedCount+$failsCount
if ($currentActivity -ne $lastActivity){
if($currentActivity -match 'Installed'){
Write-Host "`r`nProcessed $processedCount of $updatesCount updates: $currentActivity" -ForegroundColor Green
}elseif($currentActivity -match 'Failed'){
Write-Host "`r`nFailed item: $currentActivity" -ForegroundColor Yellow
}else{
Write-Host "`r`n$currentActivity"
}
$lastActivity=$currentActivity;
}else{
if ($dotCount++ -le $dotLimit){
Write-Host -NoNewline "."
if($processedCount -eq $updatesCount){
Write-Host "Processing last update: $processedCount of $updatesCount." -ForegroundColor Yellow
}
}else{
$minute++
Write-Host "`r`nMinute count: $minute"
$dotCount=0
}
}
Start-Sleep -Seconds 6
}until ($processedCount -eq $updatesCount)
}else{
write-host "There are no available patches detected on $computer basing on current update criteria."
}
$localhostRebootFlag=!(clearRebootFlags $computer $session)
if($session.State -ne 'Opened'){
write-host "Reconnecting to $computer..." -ForegroundColor Yellow
$session=connectWinRm $computer $adminCredential
}
$updatesCompleted=$installedCount -eq $updatesCount
if($updatesCompleted){
Write-Host "Windows is now up to date on $computer" -ForegroundColor Green
}
}until(($null -eq $updates) -or $updatesCompleted -or $localhostRebootFlag)
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 -Credential $adminCredential -ErrorAction SilentlyContinue){
Write-Host "Deleting log to prevent collisions with subsequent runs."
Write-Host "Removing $logFile."
Remove-item $logFile
}
} -Args $logFile
if($bypassWsus -and $wsusIsOn){
write-host "Reverting WSUS registry edits"
Invoke-Command -session $session -scriptblock{
param($turnonWsus);
[scriptblock]::create($turnonWsus).invoke()
} -args ${function:turnonWsus} ;
}
if($session.State -eq 'Opened'){Remove-PSSession $session}
if($localhostRebootFlag){
write-host "$computer requires a reboot to complete the updates"
return $false
}else{
return $true
}
}
# updateRemoteWindows_v0.0.3.ps1
# Requirement: WinRM and Internet Access must be enabled on target computer(s)
$computername='192.168.100.114'
$adminCredential=get-credential
function updateRemoteWindows{
[CmdletBinding()]
param (
[parameter(Mandatory=$true,Position=0)][string]$computer,
[parameter(Mandatory=$false,Position=1)][System.Management.Automation.PSCredential]$adminCredential,
[parameter(Mandatory=$false,Position=2)][bool]$microsoftUpdates=$false,
[parameter(Mandatory=$false,Position=3)][bool]$bypassWsus=$false,
[parameter(Mandatory=$false,Position=4)][int]$winRmPort=5985,
[parameter(Mandatory=$false,Position=5)][string]$logFile='C:\PSWindowsUpdate.log'
)
$ErrorActionPreference='stop'
<#
.SYNOPSIS
This script will automatically install all avaialable windows updates on a device and will automatically reboot if needed.
After reboots, windows updates will continue to run until all updates are installed.
# Features:
# - Check WSUS settings (bypass if required by the boolean value)
# - Prepare the targets by installing prerequisites prior to proceeding further to preemptively resolve dependency errors
# - Include additional dedendencies such as TLS1.2, Nuget & PSGALLERY
# - Check if server needs a reboot before issuing the reboot command, instead of just inadvertently trigger reboots
# - Fixed the blank lines in output log causing bug in status query
# - More thorough cleanup routine
# Future developments:
# - Detect and handle proxies
#>
function installPsWindowsUpdate{
$ErrorActionPreference='stop'
$psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -ErrorAction SilentlyContinue;
if (!($psWindowsUpdateAvailable)){
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false | Out-Null;
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null;
Install-Module PSWindowsUpdate -Confirm:$false -Force | Out-Null;
Import-Module PSWindowsUpdate -force | Out-Null;
return $true;
}
catch{
"Prerequisites not met on $ENV:COMPUTERNAME.";
return $false;
}
}else{
return $true
}
}
function checkPendingReboot([string]$computer=$ENV:computername,$session){
function checkRegistry{
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending" -EA Ignore) { return $true }
if (Get-ChildItem "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootInProgress" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired" -EA Ignore) { return $true }
if (Get-Item "HKLM:\Software\Microsoft\Windows\CurrentVersion\Component Based Servicing\PackagesPending" -EA Ignore) { return $true }
if (Get-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\PostRebootReporting" -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations -EA Ignore) { return $true }
if (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name PendingFileRenameOperations2 -EA Ignore) { return $true }
try{
$util = [wmiclass]"\\.\root\ccm\clientsdk:CCM_ClientUtilities"
$status = $util.DetermineIfRebootPending()
if(($null -ne $status) -and $status.RebootPending){
$result.SCCMRebootPending = $true
}
}catch{
return $false
}
}
if ($ENV:computername -eq $computer){
$result=checkRegistry;
}else{
$result=Invoke-Command -session $session -ScriptBlock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -ArgumentList ${function:checkRegistry}
}
return $result;
}
function clearRebootFlags($computer,$session,$verbose=$false){
if (checkPendingReboot $computer $session){
write-host "`nRestarting remote computer $computer to clear pending reboot flags..."
if($adminCredential){
Restart-Computer -Wait -ComputerName $computer -Credential $adminCredential -Force
}else{
Restart-Computer -Wait -ComputerName $computer -Force
}
write-host "$computer has been successfully restarted!" -ForegroundColor Yellow
}elseif($verbose){
write-host "Reboot flags are cleared." -ForegroundColor Green
}
}
function checkWsus{
# Check if this machine has WSUS settings configured
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
$wuIsOn=(Get-ItemProperty -path $wuPath -name $wuKey -ErrorAction SilentlyContinue).$wuKey;
#if($wuIsOn){$GLOBAL:wsus=$True}else{$GLOBAL:wsus=$False}
return $wuIsOn
}
function turnoffWsus{
# Turn WSUS settings OFF temporarily...
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
Set-Itemproperty -path $wuPath -Name $wuKey -value 0
restart-service wuauserv;
}
function turnonWsus{
# Turning WSUS settings back to ON status
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
Set-Itemproperty -path $wuPath -Name $wuKey -value 1
restart-service wuauserv;
}
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
}
$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{
if($adminCredential){
$session = New-PSSession -ComputerName $computer -Credential $adminCredential
}else{
$session = New-PSSession -ComputerName $computer
}
write-host "Connecting to remote computer $computer..."
sleep -seconds 1
if ($session){
write-host "Connected."
return $session
}
} until ($session.state -match "Opened")
}
# Ensure that this function does not execute on the localhost
if($env:computername,'localhost'|?{$_ -like "$computer*"}){
write-warning "$computer is detected as the localhost where this program is invoked. This is out of scope of this function."
return $false
}
# Advisories
write-warning "$computer will go through Windows Update and will REBOOT AUTOMATICALLY (if necessary). Press Ctrl+C at anytime to cancel."
$session=connectWinRm $computer $adminCredential
Do{
# Install prerequisites
$psWindowsUpdateAvailable=invoke-command -session $session -scriptblock {
param($installPsWindowsUpdate);
[ScriptBlock]::Create($installPsWindowsUpdate).Invoke();
} -Args ${function:installPsWindowsUpdate}
if(!$psWindowsUpdateAvailable){
write-warning "$computername`t: PSWindowsUpdate installation failed."
return $false
}
#retrieves a list of available updates
write-host "Checking for new updates on $computer..."
$updates=invoke-command -session $session -scriptblock {
param($microsoftUpdates)
if($(Get-ExecutionPolicy) -ne 'RemoteSigned'){
Set-ExecutionPolicy RemoteSigned -force
}
$null=Import-Module PSWindowsUpdate
$availableUpdates=if($microsoftUpdates){
Get-wulist -MicrosoftUpdate -verbose
}else{
Get-Wulist -verbose
}
write-host $($availableUpdates|out-string).trim()
return $availableUpdates
} -Args $microsoftUpdates
$updatesCount=$updates.KB.count # $updates.count returns $null when count equals 1
# If there are available updates, proceed with installing the updates and then reboot the remote machine if required
if ($updatesCount){
if($bypassWsus){
$wsusIsOn=Invoke-Command -session $session -scriptblock{
param($checkWsus);
[scriptblock]::create($checkWsus).invoke()
} -Args ${function:checkWsus}
if($wsusIsOn){
Invoke-Command -session $session -scriptblock{
param($turnoffWsus);
[scriptblock]::create($turnoffWsus).invoke()
} -args ${function:turnoffWsus}
}
}
#Invoke-WUJob will insert a scheduled task on the remote target as a mean to bypass 2nd hop issues
invoke-command -Session $session {
param($microsoftUpdates)
$logFile='C:\PSWindowsUpdate.log'
if(test-path $logFile){remove-item $logFile -force}
if($microsoftUpdates){
# 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
}
$invokeScript={
import-module PSWindowsUpdate;
Get-WindowsUpdate -AcceptAll -MicrosoftUpdate -Install | Out-File 'C:\PSWindowsUpdate.log'
}
}else{
$invokeScript={
import-module PSWindowsUpdate;
Get-WindowsUpdate -AcceptAll -Install | Out-File 'C:\PSWindowsUpdate.log'
}
}
Invoke-WUjob -ComputerName $env:computername -Script $invokeScript -Confirm:$false -RunNow
write-host "Windows Updates have been triggerred. Now checking for its log file to generate...`r`n"
Do{
$logFileGenerated=if(test-path $logFile){get-content $logFile}else{$false}
if(!$logFileGenerated){
Start-Sleep -Seconds 1
write-host '.' -NoNewline
}
}until($logFileGenerated)
} -Args $microsoftUpdates
#Show update status until the amount of installed updates equals the same as the count of updates being reported
$dotLimit=10
$dotCount=0
$minute=0
$lastActivity=$null;
$installedCount=0;
Write-Host "There $(if($updatesCount -eq 1){'is'}else{'are'}) $updatesCount pending update(s)`r`n";
do {
$getWindowsUpdateLog={param($logFile);Get-Content $logFile}
$updatestatus=Invoke-Command -Session $session -ScriptBlock $getWindowsUpdateLog -Args $logFile
$currentActivity=.{
$index=$updatestatus.count-1
do{
$value=$updatestatus[$index--]
}until($value)
return $value
}
$installedCount = ([regex]::Matches($updatestatus, "Installed")).count
if ($currentActivity -ne $lastActivity){
if($currentActivity -match 'Installed'){
Write-Host "`r`nProcessed $installedCount of $updatesCount updates: $currentActivity" -ForegroundColor Green
}elseif($currentActivity -match 'Failed'){
Write-Host "`r`nFailed item: $currentActivity" -ForegroundColor Yellow
}else{
Write-Host "`r`n$currentActivity"
}
$lastActivity=$currentActivity;
}else{
if ($dotCount++ -le $dotLimit){
Write-Host -NoNewline "."
if($installedCount -eq $updatesCount){
Write-Host "Processing last update: $installedCount of $updatesCount." -ForegroundColor Yellow
}
}else{
$minute++
Write-Host "`r`nMinute count: $minute"
$dotCount=0
}
}
Start-Sleep -Seconds 6
}until ($installedCount -eq $updatesCount)
}else{
write-host "There are no available patches detected on $computer basing on current update criteria."
}
clearRebootFlags $computer $session
if($session.State -ne 'Opened'){
write-host "Reconnecting to $computer..." -ForegroundColor Yellow
$session=connectWinRm $computer $adminCredential
}
}until(($null -eq $updates) -OR ($installedCount -eq $updatesCount))
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 -Credential $adminCredential -ErrorAction SilentlyContinue){
Write-Host "Deleting log to prevent collisions with subsequent runs."
Write-Host "Removing $logFile."
Remove-item $logFile
}
} -Args $logFile
if($bypassWsus -and $wsusIsOn){
write-host "Reverting WSUS registry edits"
Invoke-Command -session $session -scriptblock{
param($turnonWsus);
[scriptblock]::create($turnonWsus).invoke()
} -args ${function:turnonWsus} ;
}
Write-Host "Windows is now up to date on $computer" -ForegroundColor Green
if($session.State -eq 'Opened'){Remove-PSSession $session}
return $true
}
updateRemoteWindows -computer $computername -adminCredential $adminCredential -microsoftUpdates $true
# Troubleshooting:
# Error:
#File C:\Program Files\WindowsPowerShell\Modules\PSWindowsUpdate\2.2.0.2\PSWindowsUpdate.psm1 cannot be loaded because running scripts is disabled on this
#system. For more information, see about_Execution_Policies at https:/go.microsoft.com/fwlink/?LinkID=135170.
#At line:237 char:9
#+ $updates = invoke-command -session $session -scriptblock {$nu ...
#+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# + CategoryInfo : SecurityError: (:) [Import-Module], PSSecurityException
# + FullyQualifiedErrorId : UnauthorizedAccess,Microsoft.PowerShell.Commands.ImportModuleCommand
# + PSComputerName : 192.168.1.54
# Resolve:
# Set-ExecutionPolicy RemoteSigned -Force
# updateRemoteWindows_v0.0.2.ps1
$computername='TEST-SERVER01'
function updateRemoteWindows{
[CmdletBinding()]
param (
[parameter(Mandatory=$true,Position=0)][string]$computer,
[parameter(Mandatory=$false,Position=1)][bool]$bypassWsus=$false,
[parameter(Mandatory=$false,Position=2)][System.Management.Automation.PSCredential]$adminCredential,
[parameter(Mandatory=$false,Position=3)][int]$winRmPort=5985
)
$logFile="\\$computer\c$\PSWindowsUpdate.log"
$ErrorActionPreference='stop'
<#
.SYNOPSIS
This script will automatically install all avaialable windows updates on a device and will automatically reboot if needed.
After reboots, windows updates will continue to run until all updates are installed.
# Features:
# - Check WSUS settings (bypass if required by the boolean value)
# - Prepare the targets by installing prerequisites prior to proceeding further to preemptively resolve dependency errors
# - Include additional dedendencies such as TLS1.2, Nuget & PSGALLERY
# - Check if server needs a reboot before issuing the reboot command, instead of just inadvertently trigger reboots
# - Fixed the blank lines in output log causing bug in status query
# - More thorough cleanup routine
# Future developments:
# - Detect and handle proxies
#>
function setRegKey{
[CmdletBinding()]
Param(
[Parameter(Position=0, Mandatory=$True)][string]$path,
[Parameter(Position=1, Mandatory=$True)][string]$name,
[Parameter(Position=2, Mandatory=$True)][string]$value
)
Set-Itemproperty -path $path -Name $name -value $value
}
function installPsWindowsUpdate{
$ErrorActionPreference='stop'
$psWindowsUpdateAvailable=Get-Module -ListAvailable -Name PSWindowsUpdate -ErrorAction SilentlyContinue;
if (!($psWindowsUpdateAvailable)){
try {
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12;
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Confirm:$false | Out-Null;
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted | Out-Null;
Install-Module PSWindowsUpdate -Confirm:$false -Force | Out-Null;
Import-Module PSWindowsUpdate -force | Out-Null;
return $true;
}
catch{
"Prerequisites not met on $ENV:COMPUTER.";
return $false;
}
}else{
return $true
}
}
function checkPendingReboot{
param([string]$computer=$ENV:computername)
function checkRegistry{
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(($status -ne $null) -and $status.RebootPending){
return $true
}
}catch{}
return $false
}
$localhost=$ENV:computername
if ($localHost -eq $computer){
$result=checkRegistry;
}else{
$result=Invoke-Command -ComputerName $computer -Credential $domainAdminCred -ScriptBlock{
param($importedFunc);
[ScriptBlock]::Create($importedFunc).Invoke();
} -ArgumentList ${function:checkRegistry}
}
return $result;
}
# Function requires 2 parameters: -computerName and -processName
function killProcess{
[CmdletBinding()]
param(
[string[]]$computerName=$ENV:COMPUTERNAME,
[parameter(Mandatory=$true)][string]$executableName="powershell.exe"
)
# WMI querying method
$processes = Get-WmiObject -Class Win32_Process -ComputerName $ComputerName -Credential $domainAdminCred -Filter "name='$executableName'"
if ($processes){
foreach ($process in $processes) {
$terminationResult = $process.terminate()
$processid = $process.handle
if($terminationResult.returnvalue -eq 0) {
write-host "The process $executableName `($processid`) terminated successfully"
} else {
write-host "The process $executableName `($processid`) termination has some problems"
}
}
}else{
"$executableName is currently not running on $computerName."
}
}
function cleanup{
#if(check-netconnection -ComputerName $computer -port 445){
# if(Get-Process -ComputerName $computer powershell -ErrorAction SilentlyContinue){
# Write-Host "Terminating any powershell.exe processes."
# killProcess -ComputerName $computer -ExecutableName powershell.exe
# }
# }
if (Test-Path $logFile -ErrorAction SilentlyContinue){
Write-Host "Deleting log to prevent collisions with subsequent runs."
Write-Host "Removing $logFile."
Remove-item $logFile
}
invoke-command -computername $computer -Credential $domainAdminCred -ScriptBlock {
if (Get-ScheduledTask -TaskName "PSWindowsUpdate" -ErrorAction SilentlyContinue){
Write-Host "Removing PSWindowsUpdate scheduled task from $computer..."
Unregister-ScheduledTask -TaskName PSWindowsUpdate -Confirm:$false};
}
}
function checkWsus{
# Check if this machine has WSUS settings configured
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
$wuIsOn=(Get-ItemProperty -path $wuPath -name $wuKey -ErrorAction SilentlyContinue).$wuKey;
if($wuIsOn){$GLOBAL:wsus=$True}else{$GLOBAL:wsus=$False}
}
function turnoffWsus{
# Turn WSUS settings OFF temporarily...
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
setRegKey -path $wuPath -name $wuKey -value 0;
restart-service wuauserv;
}
function turnonWsus{
# Turning WSUS settings back to ON status
$wuPath="Registry::HKLM\Software\Policies\Microsoft\Windows\WindowsUpdate\AU";
$wuKey="UseWUServer";
setRegKey -path $wuPath -name $wuKey -value 1;
restart-service wuauserv;
}
function enableWinRm($remoteComputer,$winRmPort){
function Check-NetConnection($computername, $port=5985) {
$session = New-Object System.Net.Sockets.TcpClient;
try {
$session.Connect($computername, $port);
$true;
} catch {
$false;
} finally {
$session.Close();
}
}
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
psexec.exe \\$remoteComputer -s C:\Windows\system32\winrm.cmd qc -quiet; # Enable WinRM Remotely
return check-netconnection $remoteComputer $winRmPort
}
# Advisories
write-warning "$computer will go through Windows Update and will REBOOT AUTOMATICALLY (if necessary). Press Ctrl+C at anytime to cancel."
# Ensure that this function does not execute on the localhost
if($env:computername,'localhost'|?{$_ -like "$computer*"}){
write-warning "$computer is detected as the localhost where this script is invoked. Please use another method to update, instead."
return $false
}
# Legacy equivalent to Test-Netconnection
function Check-NetConnection($computername, $port) {
$session = New-Object System.Net.Sockets.TcpClient;
try {
$session.Connect($computername, $port);
$true;
} catch {
$false;
} finally {
$session.Close();
}
}
$winRmAvailable=Check-NetConnection $computer $winRmPort
if(!$winRmAvailable){
write-warning "Attempting to enable WinRM on $computer"
$enableWinRmSuccessful=enableWinRm $computer
}
Do{
# Wait for WinRm session prior to proceeding
do{
if($adminCredential){
$session = New-PSSession -ComputerName $computer -Credential $adminCredential
}else{
$session = New-PSSession -ComputerName $computer
}
"Connecting to remote computer $computer..."
sleep -seconds 5
if ($session){"Connected."}
} until ($session.state -match "Opened")
# Install prerequisites
$psWindowsUpdateAvailable=invoke-command -session $session -scriptblock {
param($installPsWindowsUpdate);
return [ScriptBlock]::Create($installPsWindowsUpdate).Invoke();
} -Args ${function:installPsWindowsUpdate}
if(!$psWindowsUpdateAvailable){
write-warning "$computername`t: PSWindowsUpdate installation failed."
return $false
}
#retrieves a list of available updates
"Checking for new updates on $computer..."
$updates = invoke-command -session $session -scriptblock {Get-wulist -verbose}
# Count how many updates are available
$updatesCount = ($updates.kb).count
# If there are available updates proceed with installing the updates and then reboot the remote machine if required
if ($updates -ne $null){
if($bypassWsus){
checkWsus;
if($wsus){turnoffWsus}
}
#Invoke-WUJob will insert a scheduled task on the remote target as a mean to bypass 2nd hop issues
$job=invoke-command -Session $session -AsJob {
$invokeScript={import-module PSWindowsUpdate; Get-WindowsUpdate -AcceptAll -Install | Out-File C:\PSWindowsUpdate.log}
Invoke-WUjob -ComputerName $env:computername -Script $invokeScript -Confirm:$false -RunNow}
#Show update status until the amount of installed updates equals the same as the count of updates being reported
sleep -Seconds 30 # Wait for the log file to generate
$dots=80
$dotsCount=0
$lastActivity="";
$installedCount=0;
Write-Host "There is/are $updatesCount pending update(s)`n";
do {
$updatestatus = Get-Content "\\$computer\c$\PSWindowsUpdate.log"
$currentActivity=$updatestatus | select-object -last 1
if (($currentActivity -ne $lastActivity) -AND ($currentActivity -ne $Null)){
Write-Host "Procesing $($installedCount+1) of $updatesCount updates."
Write-Host "`n$currentActivity";
$lastActivity=$currentActivity;
}else{
if ($dotCount++ -le $dots){
Write-Host -NoNewline ".";
if($installedCount -eq $updatesCount){Write-Host "Processing last update: $installedCount of $updatesCount."}
}else{
Write-Host ".";
$dotCount=0;
}
}
sleep -Seconds 10
$ErrorActionPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Continue'
$installedCount = ([regex]::Matches($updatestatus, "Installed")).count
}until ($installedCount -eq $updatesCount)
#restarts the remote computer and waits till it starts up again
if (checkPendingReboot $computer){
write-host "`nReboots required.`nRestarting remote computer $computer to clear pending reboot flags."
Restart-Computer -Wait -ComputerName $computer -Force;
write-host "$computer has been restarted."
}else{
write-host "No reboots required."
}
}
}until(($updates -eq $null) -OR ($installedCount -eq $updatesCount))
# Revert WSUS registry edits, if any
cleanup;
if($wsus -and $bypassWsus){turnonWsus;}
Write-Host "Windows is now up to date on $computer"
return $true
}
updateRemoteWindows -computer $computername
Categories: