This is basically two posts in one, add logging to your scripts in the script node, and Get Service Windows Info, check if you have any deployments being squashed by too restrictive Services Windows, and remove the service windows if you want to. Why add logging… several reasons, to know what the script did, to have a “paper trail”, and helps with troubleshooting. I’ve added logging to my scripts for accountability as well. I have it log to a network share because I’m not local admin on the boxes I’m running the scripts on, so having a central share to collect the scripts is very important to me when I’m troubleshooting issues.
The reason I wrote this script was because we’ve run into machines that would never run the Upgrade because they would return with a status message “deployment will never run, too restrictive of service window”. We do not check the box on our deployments to run outside of a Service Window, as we want to respect what the Business Unit have said are their approved times to service machines. However there are times when the windows are just too small for the time we’ve specified in our TS, or someone created a local service window manually, using a tool like Client Center, to accomplish a one-off task and never cleaned it up. So I created a script that would read the execmgr.log file, search for a restrictive window issue, then delete them (based on parameters). You can search for Local Service Windows or Server Side (Collection) Service Window and delete those, or choose Delete all service windows. Common Sense Warning: Use with Caution, and Test please. This script is deleting Service Windows which restrict installs / reboots from happening all willy nilly. If you start removing those restrictions… well.. you get it.
The Script in action on local machine:
Running Script from ScriptNode. Shows the Parameters I chose, and the Status on Machines
The Script in action, showing log on Server
It also logs locally
If you run the script via ISE, it will Log locally, but not on the server, and it will show all of same info in ISE (Write-Host)
All logging & Write Host is being done via the Function CMTraceLog, borrowed from @EphingPosh HERE
This Script has several parameters, giving you a lot of flexibility, Disable or Enable Logging, Delete different Windows, or Delete only if it found error. In this case, the “Error” means “The program may never run because of Service Window restrictions.” I’ll see if I can post a picture with a demo of a machine that has this “error”.
Based on that, it will report back all of the Service Windows, and delete them based on the parameters being set.
To have it delete any Service Windows you have to change a Parameter from False to True:
- DeleteEverything (Deletes every service window if finds) – Think before you do this, but thought I’d add the option. This could be a resume generating event if you did this to like all workstations, and they had a deployment for updates that were no longer restricted by Service Windows and all started running in the middle of the day are rebooting machines…
- DeleteLocalWindows (Deletes only Local Windows Type 1-5) – Type KEY HERE – This was really the main one that I cared about, because most of the time the Service Window causing the issue was a local Windows created manually.
- DeleteServerWindows (Deletes non-local Service Windows, created by your CM Servers). I’d ASSUME the Windows would come back after machine policy refresh, but have yet to confirm, use with caution, also potential for resume generating event.
- DeleteOnlyIfErrorFound. (must be used in conjunction with the other 3. If you Set this to False, and another Delete Parameter to True, it will Delete the Service Windows even if there was no “Error” found. I added this as I wanted to push it to “ a lot” of computers but only delete the Local Service Windows if the machine was reporting back that the service windows were too restrictive. This was my safeguard to not just delete service windows on every machine I ran the script on.
- ScriptLogging. This is to enable or disable logging for the script. Disables both Server & local side logging.
Script:
<#GARYTOWN.COM Twitter: @gwblok Get Service Windows Info, Delete if you like 2018.09.02 This Script will Return all Service Windows, Allow you to specifiy if you want to delete Local or Remote, as well as let you know if there are any mentions in the execmgr log that service windows are too restrictive to run a deployment. Logs locally (ccm\logs\scriptsnode.log and on Server if you set ScriptLogging to True (Which is the Default) #> [CmdletBinding()] Param ( #Logs to Network (Make sure you update the Server Share info) [Parameter(Mandatory=$false)][ValidateSet("True","False")][string] $ScriptLogging = "True", #Delete SWs that are Local, Type 1-5 (not 6) [Parameter(Mandatory=$false)][ValidateSet("True","False")][string] $DeleteLocalWindows = "False", #Delete SWs that are Collection (Server) Created [Parameter(Mandatory=$false)][ValidateSet("True","False")][string] $DeleteServerWindows = "False", #Deletes them all, big hammer approach, get windows and delete ALL [Parameter(Mandatory=$false)][ValidateSet("True","False")][string] $DeleteEverything = "False", #Deletes the Windows ONLY if an Error was found [Parameter(Mandatory=$false)][ValidateSet("True","False")][string] $DeleteOnlyIfErrorFound = "True" ) #region: CMTraceLog Function formats logging in CMTrace style (If Running as System, write to Network Share) if ([System.Security.Principal.WindowsIdentity]::GetCurrent().Name -eq "NT AUTHORITY\SYSTEM"){$RunningAsSystem = "True"} #If Parameter for Logging Enabled, setup variables for where to log to if ($ScriptLogging -eq "True") { $TargetRootLocal = 'C:\windows\ccm\logs' $LocalLogFile = "$TargetRootLocal\ScriptsNode.log" $TargetRoot = '\\src\src$\Logs' $LogID = "ScriptsNodeLogs\GeneralScripts\$env:ComputerName" $ServerLogFile = "$TargetRoot\$LogID\ScriptsNode-$env:ComputerName.log" } #If Logging enabled & script running in System Context, make sure Log folder on Server is there, or make it. if ($RunningAsSystem -eq "True" -and $ScriptLogging -eq "True") { write-verbose "Create Target $TargetRoot\$LogID" new-item -itemtype Directory -Path $TargetRoot\$LogID -force -erroraction SilentlyContinue | out-null } #region: CMTraceLog Function formats logging in CMTrace style (but to Server) #CMTraceLog Function stolen from @EphingPosh https://www.ephingadmin.com/powershell-cmtrace-log-function/ function CMTraceLog { [CmdletBinding()] Param ( [Parameter(Mandatory=$false)] $Message, [Parameter(Mandatory=$false)] $ErrorMessage, [Parameter(Mandatory=$false)] $Component = $env:computername, [Parameter(Mandatory=$false)] [int]$Type, [Parameter(Mandatory=$true)] $ServerLogFile #[Parameter(Mandatory=$true)] #$LocalLogFile ) <# Type: 1 = Normal, 2 = Warning (yellow), 3 = Error (red) #> $Time = Get-Date -Format "HH:mm:ss.ffffff" $Date = Get-Date -Format "MM-dd-yyyy" if ($ErrorMessage -ne $null) {$Type = 3} if ($Component -eq $null) {$Component = " "} if ($Type -eq $null) {$Type = 1} $LogMessage = "<![LOG[$Message $ErrorMessage" + "]LOG]!><time=`"$Time`" date=`"$Date`" component=`"$Component`" context=`"`" type=`"$Type`" thread=`"`" file=`"`">" Write-Host "$Message $ErrorMessage" if ($RunningAsSystem -eq "True" -and $ScriptLogging -eq "True"){$LogMessage | Out-File -Append -Encoding UTF8 -FilePath $ServerLogFile} if ($ScriptLogging -eq "True"){$LogMessage | Out-File -Append -Encoding UTF8 -FilePath $LocalLogFile} } function FindServiceWindowRestrictions { #Check execmgr.log file for the string about Service Window Restrictions. $errorTable = @( [pscustomobject]@{ Name = 'Service Window restrictions'; File = 'c:\windows\ccm\Logs\execmgr*.log'; Search = 'The program may never run because of Service Window restrictions.' } ) foreach ( $ErrorItem in $ErrorTable ) { if ($ScriptLogging -eq "True"){CMTraceLog -Message "Check for $($ErrorItem.Name)" -Type 1 -ServerLogFile $ServerLogFile} if ( test-path $errorItem.File ) { if ($ScriptLogging -eq "True"){CMTraceLog -Message "Check for $($errorItem.File)" -Type 1 -ServerLogFile $ServerLogFile} type $errorItem.File | Select-String -Pattern $ErrorItem.Search | % { Write-Warning "[$env:ComputerName] Found Error $($ErrorItem.Name) : [$_]" } type $errorItem.File | Select-String -Pattern $ErrorItem.Search | % { if ($ScriptLogging -eq "True"){CMTraceLog -Message "Found Error $($ErrorItem.Name) : [$_]" -Type 2 -ServerLogFile $ServerLogFile} } type $errorItem.File | Select-String -Pattern $ErrorItem.Search | % { $global:ErrorFound="True" } } } } if ($ScriptLogging -eq "True"){CMTraceLog -Message "----------Starting Delete Local Service Windows Script on: $env:computername----------" -Type 1 -ServerLogFile $ServerLogFile} $OutputText = "Script was run in modes: `n Logging: $ScriptLogging `n Reporting Windows: $ReportWindows `n Delete Local Windows: $DeleteLocalWindows `n Delete Collection Windows: $DeleteServerWindows `n Delete All SWs: $DeleteEverything `n Delete Only if Error Fund: $DeleteOnlyIfErrorFound" if ($RunningAsSystem -eq "True" -and $ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} FindServiceWindowRestrictions $LocalServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE PolicySource = "Local" ' foreach ($LocalSW in $LocalServiceWindows) { $LocalServiceWindowID = ("ID: " + ($LocalSW).ServiceWindowID + " Type: " + ($LocalSW).ServiceWindowType) $OutputText = "Machine has Local Service Windows: $LocalServiceWindowID" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } $ServerServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "5") AND PolicySource <> "Local" ' foreach ($ServerSW in $ServerServiceWindows) { $ServerServiceWindowsID = ("ID: " + ($ServerSW).ServiceWindowID + " Type: " + ($ServerSW).ServiceWindowType) $OutputText = "Machine has Server Service Windows: $ServerServiceWindowsID" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } if ($ErrorFound -eq "True") { $OutputText= "Machine has too restrictive of MWs to run Deployments (According to current execmgr logs)" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 2 -ServerLogFile $ServerLogFile} } Else { $OutputText = "Machine has no SWs that are too restrictive (According to current execmgr logs)" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } if ($ErrorFound -eq "True" -and $DeleteOnlyIfErrorFound -eq "True") { $OutputText = "Machine has too restrictive of MWs, Moving ahead with Delete Section of Script" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} if ($DeleteLocalWindows -eq "True") { $LocalServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "2") or (ServiceWindowType = "3") or (ServiceWindowType = "4") or (ServiceWindowType = "5") AND PolicySource = "Local" ' if ($LocalServiceWindows -ne $Null) { foreach ($LocalSW in $LocalServiceWindows) { $LocalServiceWindowID = ("ID: " + ($LocalSW).ServiceWindowID + " Type: " + ($LocalSW).ServiceWindowType) $OutputText = "Machine has Local Service Windows: $LocalServiceWindowID" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } $LocalServiceWindows | Remove-WmiObject #Confirm They are Gone $LocalServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "2") or (ServiceWindowType = "3") or (ServiceWindowType = "4") or (ServiceWindowType = "5") AND PolicySource = "Local" ' if ($LocalServiceWindows -eq $Null) { $OutputText = "Confirmed all Local Services Windows are Gone" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } } } if ($DeleteServerWindows -eq "True") { $ServerServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "5") AND PolicySource <> "Local" ' if ($ServerServiceWindows -ne $Null) { foreach ($ServerSW in $ServerServiceWindows) { $ServerServiceWindowsID = ("ID: " + ($ServerSW).ServiceWindowID + " Type: " + ($ServerSW).ServiceWindowType) $OutputText = "Machine has Server Service Windows: $ServerServiceWindowsID" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } $ServerServiceWindows | Remove-WmiObject #Confirm They are Gone $ServerServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "5") AND PolicySource <> "Local" ' if ($ServerServiceWindows -eq $Null) { $OutputText = "Confirmed all Local Services Windows are Gone" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } } } if ($DeleteEverything -eq "True") { $AllServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow' if ($AllServiceWindows -ne $Null) { foreach ($AllSW in $AllServiceWindows) { $AllServiceWindowsID = ("ID: " + ($AllServiceWindows).ServiceWindowID + " Type: " + ($AllServiceWindows).ServiceWindowType) $OutputText = "Machine has Local & Server Service Windows: $ServerServiceWindowsID" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } $AllServiceWindows | Remove-WmiObject #Confirm They are Gone $AllServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow' if ($AllServiceWindows -eq $Null) { $OutputText = "Confirmed all Local Services Windows are Gone" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } } } } if ($DeleteOnlyIfErrorFound -ne "True") { $OutputText = "User Chose to Delete MWs (if Restriction exist or not) via Params, Moving ahead with Delete Section of Script and will delete any applicable MWs" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} if ($DeleteLocalWindows -eq "True") { $LocalServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "2") or (ServiceWindowType = "3") or (ServiceWindowType = "4") or (ServiceWindowType = "5") AND PolicySource = "Local" ' if ($LocalServiceWindows -ne $Null) { foreach ($LocalSW in $LocalServiceWindows) { $LocalServiceWindowID = ("ID: " + ($LocalSW).ServiceWindowID + " Type: " + ($LocalSW).ServiceWindowType) $OutputText = "Machine has Local Service Windows: $LocalServiceWindowID" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } $LocalServiceWindows | Remove-WmiObject #Confirm They are Gone $LocalServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "2") or (ServiceWindowType = "3") or (ServiceWindowType = "4") or (ServiceWindowType = "5") AND PolicySource = "Local" ' if ($LocalServiceWindows -eq $Null) { $OutputText = "Confirmed all Local Services Windows are Gone" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } } } if ($DeleteServerWindows -eq "True") { $ServerServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "5") AND PolicySource <> "Local" ' if ($ServerServiceWindows -ne $Null) { foreach ($ServerSW in $ServerServiceWindows) { $ServerServiceWindowsID = ("ID: " + ($ServerSW).ServiceWindowID + " Type: " + ($ServerSW).ServiceWindowType) $OutputText = "Machine has Server Service Windows: $ServerServiceWindowsID" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } $ServerServiceWindows | Remove-WmiObject #Confirm They are Gone $ServerServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow WHERE (ServiceWindowType = "1") or (ServiceWindowType = "5") AND PolicySource <> "Local" ' if ($ServerServiceWindows -eq $Null) { $OutputText = "Confirmed all Local Services Windows are Gone" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } } } if ($DeleteEverything -eq "True") { $AllServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow' if ($AllServiceWindows -ne $Null) { foreach ($AllSW in $AllServiceWindows) { $AllServiceWindowsID = ("ID: " + ($AllServiceWindows).ServiceWindowID + " Type: " + ($AllServiceWindows).ServiceWindowType) $OutputText = "Machine has Local & Server Service Windows: $ServerServiceWindowsID" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } $AllServiceWindows | Remove-WmiObject #Confirm They are Gone $AllServiceWindows = Get-WMIObject -Namespace 'ROOT\ccm\Policy\Machine\RequestedConfig' -Query 'SELECT * FROM CCM_ServiceWindow' if ($AllServiceWindows -eq $Null) { $OutputText = "Confirmed all Local Services Windows are Gone" if ($ScriptLogging -eq "True"){CMTraceLog -Message $OutputText -Type 1 -ServerLogFile $ServerLogFile} } } } } if ($ScriptLogging -eq "True"){CMTraceLog -Message "----------Finished Delete Local Service Windows Script on: $env:computername----------" -Type 1 -ServerLogFile $ServerLogFile
In the script, this (image below) is where we determine if you’re running as System (AKA running via ConfigMgr vs a User), then setting the locations for the scripts on a server and locally.
Here is Ryan’s Function, where I added the lines in Yellow. One to just write out to the Terminal if you’re testing in ISE, one for writing to server if running as system, and one to write locally.
Then in the script, you call the function, along with the parameters. What I’ve started to do, create a variable called $OutputText, and have that set to the message, then have the Function use the $OutputText as the “Message” for the log. This probably isn’t needed, in my head I thought it made the organization easier, as I could just paste in the line to to create the log: if ($RunningAsSystem –eq “True” –and $Scriptlogging –eq “true”){CMTraceLog –Message $OutputText…..} Then have a line that sets the message right before it.
Here is the function that searches through the execmgr log and lets you know if it found that it has too restrictive of Service Windows FYI, this code is super handy and you can easily change it to look through other files for other items, like look through panther logs..
As always, if you find any issues, typos, whatever, let me know.
Posted on GARYTOWN.COM
Awesome stuff! Thanks for sharing!