UnattendedUpdate.ps1
param( [Switch]$Console = $False, #--[ Set to true to enable local console result display. Defaults to false ]-- [Switch]$Debug = $False, #--[ Set to true to only send results to debug email address. Default to false ]-- [Switch]$Manual = $False, #--[ Use to run update off-schedule ]-- [Switch]$Status = $False, #--[ If set to true checks for results after the reboot and emails, then goes idle. ]-- [Switch]$Deploy = $False, #--[ If set to true will copy this script to the other members of the peer group ]-- [Switch]$UpTimeCheck = $False, #--[ If set to true will send an email alert if no restart occurs in preset # of days ]-- [Switch]$NoRestart = $False #--[ Stops restart from occurring. Restart may still occur if update determines it's needed. ]-- ) <#====================================================================================== File Name : UnattendedUpdate.ps1 Original Author : Kenneth C. Mazie (kcmjr AT kcmjr.com) : Description : Will scan the Windows Update site and install all missing updates. : Operation : Requires PowerShell v5. Requires NuGet and PSWindowsUpdate modules. Will auto-install them if needed. : Reboots system following patching to assure new updates are applied. Creates a LOCAL scheduled task : automatically on first run with script version in name. Will delete and recreate the task if : a script version change is detected. The patch schedule is set to randomize the run : within a 90 minute window. Update routine is governed by the week days noted in the config file. : The scheduled task has two triggers, run time, and on restart. An HTML report is sent on every restart. : Antivirus is disabled during the update run. Kaspersky is configure so change that as needed. : Requires a config file in XML format located in the same folder as the script. See example at bottom. : Arguments : Normal operation is with no command line options. : -Console $true (Will enable local console output) : -Debug $true (Not used) : -Manual $true (forces a manual run bypassing the schedule) : -Status $true (forces a status email to be sent) : -Deploy $true (forces the script and config file to be copied to the identical : location on the other peer servers listed in the config file) : -UpTimeCheck $true (If set to true will send an email alert if no restart occurs in preset # of days) : -NoRestart $true (Stops restart from occurring. Restart may still occur if update determines it's needed.) : Warnings : Uses local SYSTEM user context for tasks. Install LOCALLY, not remotely. : Adjust the task schedule(s) to conform to your maintenance window. : Legal : Public Domain. Modify and redistribute freely. No rights reserved. : SCRIPT PROVIDED "AS IS" WITHOUT WARRANTIES OR GUARANTEES OF : ANY KIND. USE AT YOUR OWN RISK. NO TECHNICAL SUPPORT PROVIDED. : Credits : Code snippets and/or ideas came from many sources including but : not limited to the following: : https://www.powershellgallery.com/packages/PSWindowsUpdate/1.5.2.2 : Last Update by : Kenneth C. Mazie (kcmjr AT kcmjr.com) : Version History : v1.0 - 12-29-16 - Original Change History : v2.0 - 01-13-17 - Added forced status option for Sundays. : v2.1 - 01-17-17 - Added regkey delete. : v2.2 - 04-06-17 - Fixed row data to start clean at each loop : v2.3 - 09-28-17 - Turned off extra email after patching. : Moved config file out. Added script replication option. : Added run day from config file option. : v2.4 - 10-19-17 - Added randomizer for reboot. Added check for no data. : v2.5 - 12-01-17 - Fixed runday detection : v2.6 - 02-27-18 - Eliminated second schedule to send status at restart due to bugs in PS : task schedule commandlets not detecting all schedules. : v2.7 - 02-28-18 - Added reboot detection for automatic status report. : v2.8 - 03-01-18 - Added automatic scheduling adjustmant. Added restart bypass. : v2.9 - 03-05-18 - Fixed registry key removal error. : #> $Script:ScriptVer = "2.9" <# =======================================================================================#> <#PSScriptInfo .VERSION 2.90 .GUID 9fd19521-b906-4e4f-8969-d71a8faa6195 .AUTHOR Kenneth C. Mazie (kcmjr AT kcmjr.com) .DESCRIPTION Automatically applies current patches to a single Windows system, then reboots. Emails a status report upon restart. Should be run from a scheduled task. Can deploy itself to "peer" systems. #> #Requires -version 5.0 clear-host if ($Console){$Script:Console = $true} if ($Debug){$Script:Debug = $true} if ($Manual){$Script:Manual = $true} if ($Status){$Script:Status = $true} if ($Deploy){ $Script:Deploy = $true $Script:Console = $true } if ($UpTimeCheck){$Script:UpTimeCheck = $true} if ($NoRestart){$Script:NoRestart = $true} $ErrorActionPreference = "SilentlyContinue" $Now = Get-Date -Format "MM-dd-yyyy_HHmm" $Script:ThisComputer = $Env:Computername $Script:MessageBody = "Starting Local Patch Processing...<br>" $Today = (get-date).dayofweek $Script:Attach = $false $Script:ResultLog = "$PSScriptRoot\Results-$Now.csv" #--[ This next line is used to stop the local antivirus client. Edit the function below to support your Av client ]-- $Script:KillKaspersky = $true #--------------------------------------------------------------------------------------------------------------- $Script:ScriptName = $MyInvocation.MyCommand.Name $Script:ScriptFullPath = $PSScriptRoot+"\"+$MyInvocation.MyCommand.Name $ConfigFile = $Script:ScriptFullPath.Split(".")[0]+".xml" $Script:UserContext = [Security.Principal.WindowsIdentity]::GetCurrent() #==[ Functions ]================================================================ Function LoadConfig { #--[ Read and load configuration file ]------------------------------------- if (!(Test-Path $ConfigFile)){ #--[ Error out if configuration file doesn't exist ]-- $Script:HTMLData = "MISSING CONFIG FILE. Script aborted." if ($Script:Log){Add-content -Path "$PSScriptRoot\debug.txt" -Value "MISSING CONFIG FILE. Script aborted."} write-host "CONFIGURATION FILE $ConfigFile NOT FOUND - EXITING" -ForegroundColor Red break;break;break }else{ [xml]$Script:Configuration = Get-Content $ConfigFile #--[ Read & Load XML ]-- $Script:PeerList = $Script:Configuration.Settings.General.PeerList $Script:RunDays = $Script:Configuration.Settings.General.RunDays $Script:RunTime = $Script:Configuration.Settings.General.RunTime $Script:DebugEmail = $Script:Configuration.Settings.Email.Debug $Script:eMailRecipient = $Script:Configuration.Settings.Email.To $Script:eMailFrom = $ThisComputer+'_'+$Script:Configuration.Settings.Email.From $Script:eMailHTML = $Script:Configuration.Settings.Email.HTML $Script:eMailSubject = $ThisComputer+' '+($Script:Configuration.Settings.Email.Subject) $Script:SmtpServer = $Script:Configuration.Settings.Email.SmtpServer $Script:UserName = $Script:Configuration.Settings.Credentials.Username $Script:EncryptedPW = $Script:Configuration.Settings.Credentials.Password $Script:Base64String = $Script:Configuration.Settings.Credentials.Key $Script:ReportName = $ThisComputer+' '+($Script:Configuration.Settings.General.ReportName) $Script:UpTimeDays = $Script:Configuration.Settings.General.UpTimeDays } $ByteArray = [System.Convert]::FromBase64String($Script:Base64String); $Script:Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Script:UserName, ($Script:EncryptedPW | ConvertTo-SecureString -Key $ByteArray) $Script:Password = $Script:Credential.GetNetworkCredential().Password } function SendEmail { $SMTP = new-object System.Net.Mail.SmtpClient($Script:SmtpServer) $Email = New-Object System.Net.Mail.MailMessage $Email.Body = $Script:MessageBody $Email.IsBodyHtml = $Script:eMailHTML If ($Script::Debug){ $Email.To.Add($Script:DebugEmail) }Else{ $Email.To.Add($Script:eMailRecipient) } if ($Script:Attach){ $Attachment = New-Object System.Net.Mail.Attachment �ArgumentList $Script:ResultLog, 'Application/Octet' $Email.Attachments.Add($Attachment) } $Email.From = $Script:eMailFrom $Email.Subject = $Script:eMailSubject $SMTP.Send($Email) $Email.Dispose() $SMTP.Dispose() } function GetResults { $Script:ResultOut = "" $Script:ResultLine = "" $RowFlag = $false #--[ Add header to html output file ]-------------------- $Script:MessageBody += '<tr><th>KB Number</th><th>Status</th><th>Results</th><th>Date / Time</th><th>Messages</th></tr>' #--[ HTML row Settings ]---------------------------------------------------- $BGColor = "#dfdfdf" #--[ Grey default cell background ]-- $BGColorRed = "#ff0000" #--[ Red background for alerts ]-- $BGColorOra = "#ff9900" #--[ Orange background for alerts ]-- $BGColorYel = "#ffd900" #--[ Yellow background for alerts ]-- $FGColor = "#000000" #--[ Black default cell foreground ]-- #--[ Only keep 10 of the last runtime logs ]------------------------------------ Get-ChildItem -Path $PSScriptRoot | Where-Object {(-not $_.PsIsContainer) -and ($_.Name -like "*results*.csv")} | Sort-Object -Descending -Property LastTimeWrite | Select-Object -Skip 10 | Remove-Item #--[ Scan Eventlogs for Event 19 & 20 ]-- $Script:LogDump = Get-WinEvent -FilterHashtable @{LogName = "System";ID=19,20} foreach ($Script:LogItem in $Script:LogDump ){ if ($Script:LogItem.ProviderName -eq 'Microsoft-Windows-WindowsUpdateClient'){ $RowFlag = $true $RowData = '<tr>' #--[ Start table row ]-- $Script:LogItemStat = $Script:LogItem.message.split(":")[0] if ($Script:LogItem.Message -like "*(KB*"){ $Script:LogItemKB = ($Script:LogItem.message.split("(")[1]).split(")")[0] if (!($Script:LogItemKB -like "KB*")){ $Script:LogItemKB = ($Script:LogItem.message.split("(")[2]).split(")")[0] } }else{ $Script:LogItemKB = $Script:LogItem.message.split(":")[2] #"---------" } if ($Script:Console){write-host $Script:LogItemKB" " -ForegroundColor Red -NoNewline } $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItemKB + '</td>' if ($Script:Console){write-host $Script:LogItem.LevelDisplayName" " -ForegroundColor yellow -NoNewline } if ($Script:LogItem.LevelDisplayName -like "Error"){ $RowData += '<td bgcolor=' + $BGColor + '><font color=#800000>' + $Script:LogItem.LevelDisplayName + '</td>' $RowData += '<td bgcolor=' + $BGColor + '><font color=#800000>' + $Script:LogItemStat + '</td>' }else{ $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItem.LevelDisplayName + '</td>' $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItemStat + '</td>' } if ($Script:Console){write-host $Script:LogItem.TimeCreated" " -ForegroundColor yellow -NoNewline } $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItem.TimeCreated + '</td>' if ($Script:Console){write-host $Script:LogItemStat" " -ForegroundColor Cyan -NoNewline } #$RowData += '<td bgcolor=' + $BGColor + '><font color=#800000>' + $Script:LogItemStat + '</td>' #--[ Note: Added to error detection above ]-- if ($Script:Console){write-host $Script:LogItem.Message" " -ForegroundColor green} $RowData += '<td bgcolor=' + $BGColor + '><font color=' + $FGColor + '>' + $Script:LogItem.message + '</td>' $Script:ResultLine = $Script:LogItemKB+","+$Script:LogItem.LevelDisplayName+","+$Script:LogItem.TimeCreated+","+$Script:LogItemStat+","+$Script:LogItem.Message $Script:ResultOut = $Script:ResultOut+$Script:ResultLine+"`n" $RowData += '</tr>' } $Script:MessageBody += $RowData } If ($RowFlag){ $Script:Attach = $true }Else{ $Script:MessageBody += '<td colspan=5 bgcolor=' + $BGColor + '><font color=' + $FGColor + '><center>No New Data to Report </center></td>' } Add-Content -value $Script:ResultOut -path $Script:ResultLog $Script:MessageBody += '</table><br>' $Script:MessageBody += "<font size=3 face='times new roman'>- Done. Emailing results...<br>" if ($Script:Console){Write-Host "`n- Done. Emailing results...`n" -ForegroundColor yellow } SendEmail } Function ServiceMgr ($Svc, $SvcStatus) { $Script:MessageBody = $Script:MessageBody + "- Processing: $Svc<br>" if ($Script:Console){Write-Host "`n- Processing :$Svc" -ForegroundColor cyan } $Count = 0 #--[ State prior to start/stop process ]-- $Script:SvcState = (Get-Service -Name $Svc).Status $Script:MessageBody = $Script:MessageBody + "-- $Svc Initial Status: $Script:SvcState<br>" if ($Script:Console){Write-Host "-- $Svc Initial Status: $Script:SvcState" -ForegroundColor yellow } $Script:MessageBody = $Script:MessageBody + "--- Pausing while attemtping to set service state to: $SvcStatus...<br>" if ($Script:Console){Write-Host "--- Pausing while attemtping to set service state to: $SvcStatus..." -ForegroundColor yellow } while ((Get-Service -Name $Svc).Status -ne $SvcStatus){ Get-Service -Name $Svc | Set-Service -Status $SvcStatus sleep -Milliseconds 500 $Count ++ if ($Count -ge 20){ if ($Script:Console){Write-Host "-- There was an error setting the "$Svc" service to the "$SvcStatus" state..." -ForegroundColor red } $Script:MessageBody = $Script:MessageBody + '-- There was an error setting the "$Svc" service to the "$SvcStatus" state...<br>' break } } #--[ State after start/stop process ]-- $Script:SvcState = (Get-Service -Name $Svc).Status $Script:MessageBody = $Script:MessageBody + "-- $Svc Final Status: $Script:SvcState<br>" if ($Script:Console){Write-Host "-- $Svc Final Status: $Script:SvcState" -ForegroundColor yellow } Sleep -Seconds 1 if ($Script:SvcState -eq "stopped"){ RegKill } } function RegKill { if ($Script:Console){Write-Host "`n- Removing Windows Update registry key..." -ForegroundColor cyan } try{ Clear-ItemProperty -Name 'WUServer' -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Force Clear-ItemProperty -Name 'WUStatusServer' -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Force Remove-Item -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate' -Force -Recurse }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:MessageBody = $Script:MessageBody + "- Failed to remove Windows Update Key(s). $ErrorMessage<br>" if ($Script:Console){Write-Host "- Failed to remove Windows Update Key(s). $ErrorMessage" -ForegroundColor Red } } if (Test-Path -Path 'HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate'){ if ($Script:Console){Write-Host "-- FAILED to remove Windows Update registry key..." -ForegroundColor red } $Script:MessageBody = $Script:MessageBody + "- Failed to remove Windows Update Key(s).<br>" }else{ if ($Script:Console){Write-Host "-- Verified removal of Windows Update registry key..." -ForegroundColor green } $Script:MessageBody = $Script:MessageBody + "- Verified removal of Windows Update registry key(s).<br>" } } function PatchIt { if ($Script:KillKaspersky){ ServiceMgr "klnagent" "stopped" #--[ Stops Kaspersky agent prior to running update. Comment out if not applicable. ]-- }else{ $SvcState = "stopped" } if ($SvcState -eq "stopped"){ #--[ NuGet is required to pull the module from the MS repository ]-- if (!(Get-PackageProvider NuGet)){ if (!(Get-ChildItem -Path "C:\Program Files\PackageManagement\ProviderAssemblies\nuget" -Filter "Microsoft.PackageManagement.NuGetProvider.dll" -Recurse)){ try{ Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -ErrorAction SilentlyContinue -Confirm:$false -Force:$true $Script:MessageBody = $Script:MessageBody + "- NuGet provider is being installed.<br>" if ($Script:Console){Write-Host "- NuGet provider is being installed." -ForegroundColor yellow } }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:MessageBody = $Script:MessageBody + "- NuGet module install FAILED. $ErrorMessage<br>" if ($Script:Console){Write-Host "- NuGet module install FAILED. $ErrorMessage" -ForegroundColor Red } } } } #--[ Install the update module if it's not already loaded ]-- if (!(Get-Module PSWindowsUpdate)){ try{ $Script:MessageBody = $Script:MessageBody + '- "PSWindowsUpdate" module is being installed.<br>' if ($Script:Console){Write-Host '- "PSWindowsUpdate" module is being installed...' -ForegroundColor yellow } Install-Module PSWindowsUpdate -ErrorAction Stop -Confirm:$false -Force:$true }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:MessageBody = $Script:MessageBody + '- "PSWindowsUpdate" module install FAILED. $ErrorMessage<br>' if ($Script:Console){Write-Host '- "PSWindowsUpdate" module install FAILED. $ErrorMessage' -ForegroundColor Red } } } #--[ Register to use the Microsoft Update Service, as opposed to just the default Windows Update Service. ]-- if (!((Get-WUServiceManager).ServiceID -contains "7971f918-a847-4430-9279-4a52d1efe18d")){ try{ $Script:MessageBody = $Script:MessageBody + "- Service Manager ID is being registered.<br>" if ($Script:Console){Write-Host "- Service Manager ID is being registered." -ForegroundColor yellow } Add-WUServiceManager -ServiceID '7971f918-a847-4430-9279-4a52d1efe18d' -Confirm:$false -ErrorAction Stop }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:MessageBody = $Script:MessageBody + "- Service Manager ID registration FAILED. $ErrorMessage<br>" if ($Script:Console){Write-Host "- Service Manager ID registration FAILED. $ErrorMessage" -ForegroundColor Red } } } #--[ Run the update in unattended mode, check for all updates on the MS update site, accept all EULAs, reboot if needed ]-- $Script:MessageBody = $Script:MessageBody + "- Checking for and installing new updates.<br>" $Script:MessageBody = $Script:MessageBody + "-- A reboot is required to register new patches, as such the system will be rebooted shortly.<br>" $Script:MessageBody = $Script:MessageBody + "-- A summary report will be dispatched shortly after the system comes back online.<br>" if ($Script:Console){ Write-Host "`n- Checking for and installing new updates." -ForegroundColor cyan Write-Host "-- Note that this process produces no console output." -ForegroundColor yellow Write-Host "-- A reboot is required to register new patches, as such the system will be rebooted shortly." -ForegroundColor yellow Write-Host "-- A summary report will be dispatched shortly after the system comes back online." -ForegroundColor yellow } $Script:Attach = $false #SendEmail #--[ Disabled so that the emails only go out are after reboot or on svc error ]-- Get-WUInstall �MicrosoftUpdate �AcceptAll -Confirm:$false �AutoReboot:$true }else{ $Script:MessageBody = $Script:MessageBody + "-- $Svc Failed to stop -- ABORTING --" if ($Script:Console){Write-Host "`n-- $Svc Failed to stop -- ABORTING --`n" -ForegroundColor Red } SendEmail } #--[ Things to do if no reboot aurtomatically occurs after running update. Comment items out if not applicable. ]-- Sleep -Seconds 60 #--[ Wait to see if no reboot has occurred ]-- if ($Script:KillKaspersky){ ServiceMgr "klnagent" "running" #--[ Restart it if we don't reboot ]-- }else{ $SvcState = "running" } #--[ Force a reboot with random delay if none occurred. ]-- $RndArray = @(300,600,900) #--[ 300 seconds = 5 minutes]-- $Rnd = (new-object System.Random) $RndDelay = $Array[ $Rnd.Next( $Array.Count ) ] $RndDelay = 5 if ($Script:Console){Write-Host `n'--- REBOOTING COMPUTER --- ('$RndDelay' second delay)...' -ForegroundColor red } Sleep -Seconds $RndDelay If ($NoRestart){ if ($Script:Console){Write-Host `n'--- REBOOT BYPASS ENABLED --- ' -ForegroundColor yellow } }Else{ Try{ #Restart-Computer -Credential $Script:Credential -Confirm $false -force #-WhatIf #--[ Not working for the local PC ]-- Invoke-Command -ComputerName "localhost" -Credential $Script:Credential -ScriptBlock {shutdown -r -t 3} }Catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName if ($Script:Console){Write-Host '- System Restart has failed to execute... $ErrorMessage' -ForegroundColor Red } } } } Function ScheduledTask ($ActiveTask){ #$ActiveTask = "UnattendedUpdate-2.9" #--------------------------- for testing --------------------------------- $CreateTask = $True $ExistingTasks = (Get-ScheduledTask | Where-Object {$_.TaskName -like ('*'+$ActiveTask.Split("-")[0]+'*')}) ForEach ($FoundTask in $ExistingTasks){ If (($FoundTask.taskname.Split("-")[1]) -match '\d'){ If ($FoundTask.TaskName -eq $ActiveTask ){ if ($Script:Console){Write-Host '- Task "'$FoundTask.TaskName'" already exists... IGNORING' -ForegroundColor Green } $Script:TaskMessage = '- Task "'+$FoundTask.TaskName+'" already exists...<br>' $CreateTask = $False }Else{ if ($Script:Console){Write-Host '- Task "'$FoundTask.TaskName'" is an incorrect version... REMOVING' -ForegroundColor red } $Script:TaskMessage = '- Task "'+$FoundTask.TaskName+'" is an incorrect version... REMOVING<br>' Try{ #$Command = Get-ScheduledTask | Where-Object {$_.TaskName -eq $FoundTask.TaskName} #Unregister-ScheduledTask -taskname $Command -taskpath "\" #-Confirm:$false #--[ not working [-- $Result = Invoke-Expression ("schtasks.exe /delete /s "+$Env:ComputerName+" /tn "+$FoundTask.TaskName+" /F") if ($Script:Console){Write-Host "-- "$Result -ForegroundColor white } $Script:TaskMessage = '-- "'+$Result+'<br>' $CreateTask = $true }Catch{ $_.Exception.Message $_.Exception.ItemName } } }Else{ if ($Script:Console){Write-Host '- Task "'$FoundTask.TaskName'" is unknown... IGNORING' -ForegroundColor yellow } } } If ($CreateTask){ If ($Script:Console){Write-Host '- Creating new scheduled task "'$ActiveTask' "...' -ForegroundColor Cyan } $Script:TaskMessage = '- Creating new scheduled task "'+$ActiveTask+'"...<br>' #--[ Task Parameters ]-------------------- $Principal = New-ScheduledTaskPrincipal -UserID "NT AUTHORITY\SYSTEM" -LogonType S4U -RunLevel Highest $PSArgument = '-WindowStyle Hidden �Noninteractive -noprofile -nologo -executionpolicy Bypass -Command "&{'+$Script:ScriptFullPath+'}"' $Action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $PSArgument $Trigger = @() #--[ Allows creation of multiple triggers ]-- $Trigger += New-ScheduledTaskTrigger -Daily -RandomDelay (New-TimeSpan -Minutes 90) -At $Script:RunTime #--[ Creates patch task with 90 minute random delay ]-- $Trigger += New-ScheduledTaskTrigger -AtStartup $Task = New-ScheduledTask -Action $Action -Trigger $Trigger -Principal $Principal -Settings (New-ScheduledTaskSettingsSet) #--[ Task Parameters ]-------------------- try{ $Result = ($Task | Register-ScheduledTask -TaskName $ActiveTask -Force -ErrorAction Stop ) if ($Script:Console){Write-Host "-- Created"$Result -ForegroundColor green } $Script:TaskMessage = '-- Created"'+$Result+'<br>' }catch{ $ErrorMessage = $_.Exception.Message $FailedItem = $_.Exception.ItemName $Script:TaskMessage = $Script:TaskMessage + '- Scheduled Task "'+$ActiveTask+'" failed to be created... $ErrorMessage<br>' if ($Script:Console){Write-Host '- Scheduled Task "'$ActiveTask'" failed to be created... $ErrorMessage' -ForegroundColor Red } } }Else{ $Script:TaskMessage = $Script:TaskMessage + '- Scheduled Task "'+$ActiveTask+'" detected... No Action... <br>' if ($Script:Console){Write-Host '- Scheduled Task "'$ActiveTask'" detected... No Action Required... ' -ForegroundColor Green } } } Function DetectRestart { $OS = Get-WmiObject win32_operatingsystem -ComputerName $Env:ComputerName -ErrorAction SilentlyContinue $LastBoot = [DateTime]$OS.ConvertToDateTime($OS.LastBootUpTime) $TimeNow = (Get-Date) $UpTime = New-TimeSpan �End $TimeNow �Start $LastBoot If (($UpTime.Days -lt 1) -and ($UpTime.Hours -lt 1) -and ($UpTime.Minutes -le 30)){ #--[ If last restart was less than 30 minutes ago start a status run ]-- $Script:Status = $true If ($Script:Console){ Write-Host "- Detected a recent restart..." -ForegroundColor cyan Write-Host "-- Last boot : "$LastBoot -foregroundcolor Yellow Write-Host "-- Uptime : "$UpTime.Days" Days "$UpTime.Hours" Hours "$UpTime.Minutes" Minutes" -ForegroundColor Yellow Write-Host '-- Setting "status" mode...' -ForegroundColor Yellow } } If ($UpTimeCheck -and ($UpTime.Days -ge $Script:UpTimeDays)){ #--[ A secondary check. If system restart has exceeded 5 days this optional email may be sent to warn admins ]-- If ($Script:Console){Write-Host `n"--- No reboot has occurred in over "$UpTime.Days" ---..."`n -ForegroundColor red} $Script:MessageBody = "<br><br>WARNING: Computer $Script:ThisComputer has exceeded the reboot window of $Script:UpTimeDays days.<br>Please investigate or disable this feature of the AUTOUPDATE script.<br><br>" } } Function DeployScript{ #--[ Copies this script to all other systems noted in the config file. ]-- if ($Script:Console){Write-Host '--[ Deploying Updated Script to Peer Hosts ]------------' -ForegroundColor Yellow} foreach ($Target in $Script:PeerList.Split(",")){ if ($Script:Console){Write-Host `n'--[ Deploying to'($Target.ToUpper())' ]-------------------------' -ForegroundColor Cyan} if ($Target -ne $ThisComputer){ if (Test-Path "\\$Target\c$\scripts\unattendedupdate.ps1"){ if ($Script:Console){Write-Host " -- Existing files found..." -ForegroundColor green try{ Get-ChildItem -Path "\\$Target\c$\scripts\" | where{$_.Name -match "unattendedupdate.*"} | Remove-Item -Force:$true -Confirm:$false }catch{ if ($Script:Console){Write-Host " -- File delete on $Target FAILED..." -ForegroundColor Red} if ($Script:Console){Write-Host " -- Error Message = "$_.Exception.Message} if ($Script:Console){Write-Host " -- Error Item = "$_.Exception.ItemName} break } } if (!(Test-Path "\\$Target\c$\scripts\unattendedupdate.ps1")){ if ($Script:Console){Write-Host " -- Deletion validated. Files no longer detected..." -ForegroundColor green} }else{ if ($Script:Console){Write-Host " -- Deletion FAILED..." -ForegroundColor red} } try{ Copy-Item -Path $Script:ScriptFullPath -Destination "\\$Target\c$\scripts\" -Force -Confirm:$false Copy-Item -Path ($PSScriptRoot+'\'+$Script:ScriptName.split('.')[0]+'.xml') -Destination "\\$Target\c$\scripts\" -Force -Confirm:$false }catch{ if ($Script:Console){Write-Host " -- File copy to $Target FAILED..." -ForegroundColor Red} if ($Script:Console){Write-Host " -- Error Message = "$_.Exception.Message} if ($Script:Console){Write-Host " -- Error Item = "$_.Exception.ItemName} break } if (Test-Path "\\$Target\c$\scripts\unattendedupdate.ps1"){ if ($Script:Console){Write-Host " -- Verified PS1 copy to $Target..." -ForegroundColor Green} } if (Test-Path "\\$Target\c$\scripts\unattendedupdate.xml"){ if ($Script:Console){Write-Host " -- Verified XML copy to $Target..." -ForegroundColor Green} } } }else{ if ($Script:Console){Write-Host ' -- Bypassing local system'($Target.ToUpper()) -ForegroundColor yellow} } } if ($Script:Console){Write-Host `n"--- COMPLETED ---" -ForegroundColor Red } break } #==[ End of Functions / Start of Main Process ]=============================================== if ($Script:Console){Write-Host `n"--[ Beginning Run ]-----------------------------------`n" -ForegroundColor cyan } LoadConfig #--[ Load the external config file ]-- if ($Script:Deploy){DeployScript} #--[ Check for a script deployment command, then exit ]-- ScheduledTask "UnattendedUpdate-$Script:ScriptVer" #--[ Check for existance of scheduled task named for current version, create if missing ]-- DetectRestart #--[ Check for last restart to determin RUN or STATUS mode ]-- #--[ Create header for html output file ]-- $Script:MessageBody = @() $Script:MessageBody += ' <style Type="text/css"> table.myTable { border:5px solid black;border-collapse:collapse; } table.myTable td { border:2px solid black;padding:5px} table.myTable th { border:2px solid black;padding:5px;background: #949494 } table.bottomBorder { border-collapse:collapse; } table.bottomBorder td, table.bottomBorder th { border-bottom:1px dotted black;padding:5px; } tr.noBorder td {border: 0; } </style>' $Script:MessageBody += '<table class="myTable"> <tr class="noBorder"><td colspan=5><center><h1>- ' + $Script:eMailSubject + ' -</h1></td></tr> <tr class="noBorder"><td colspan=5><center>The following report displays all recently installed patches on the system.</center></td></tr> <tr class="noBorder"><td colspan=5></tr> <tr class="noBorder"><td colspan=5>- script executed by: ' + $Script:UserContext.Name + '</tr> <tr class="noBorder"><td colspan=5>- script version : ' + $Script:ScriptVer + '</tr><br> ' $Script:MessageBody += $Script:TaskMessage if ($Script:Status){ if ($Script:Console){Write-Host "- Collecting results..." -ForegroundColor Cyan } $Script:MessageBody += '<tr class="noBorder"><td colspan=5>- Collecting results....</tr><br>' GetResults }elseif (($Script:Manual) -or ($Script:RunDays -Match $Today)){ #--[ Update routine is governed by the week days noted in the config file ]-- if(Test-Path $PSScriptRoot\Results.csv){Remove-Item -path $PSScriptRoot\Results.csv -confirm:$false } if ($Script:Console){Write-Host "- Running update routine..." -ForegroundColor Cyan } $Script:MessageBody = $Script:MessageBody + "- Running update routine....<br>" PatchIt }else{ if ($Script:Console){Write-Host "`n-- Nothing scheduled for today --`n" -ForegroundColor Cyan } } if ($Script:Console){Write-Host "`n--- COMPLETED ---" -ForegroundColor Red } <#================================================================================================== #--[ Sample XML config file. Should use the same name as this script and be in the same folder. ]-- <!-- Settings & configuration file --> <Settings> <General> <ReportName>Patch Processing</ReportName> <PeerList>server1,server2,server3,server4</PeerList> <RunDays>Monday,Wednesday,Friday</RunDays> <RunTime>1am</RunTime> <UpTimeDays>5</UpTimeDays> </General> <Email> <from>UnattendedUpdate@domain.com</from> <To>you@domain.com</To> <Debug>you@domain.com</Debug> <Subject>Automated Patch Processing</Subject> <HTML>$true</HTML> <SmtpServer>10.10.10.1</SmtpServer> </Email> <Credentials> <UserName>domain\serviceaccount</UserName> <Password>76492d1ws656ertg116743a5345MgB8AHIAegB2AHUTGYT6ghjYAZQAxAGIATbuTeJ7I78gBaAHwAYwAzADQANgA0AGEAMAAwADhH087hnA2AGQAZAAQBmAGQAOAA0ADAHwAYwAzADQANgAEANgBiADAANwBkADEANAA4AGQAZgA3ADIAYQAwADYAZAA3AGUAZgBkAGYAZAA=</Password> <Key>kdhCh7HCvLO+E6/AWnHhQAZgB7812q87nsdXN0IObie8mE=</Key> </Credentials> </Settings> #> |