After completing our production upgrade of ConfigMgr to 1802 I’ve been eagerly awaiting a quiet moment or two to sit down and write a post about the new run scripts feature which has me absolutely over the moon. This feature could not have been more timely for me as we my organization begins prep-work to close out a project near and dear to my heart consolidation of patching windows to a national standard. It’s timely because we’ve been struggling with a way for people who aren’t adept in PowerShell to leverage some of the tools I’ve been working on and then finally after salivating over it in Pre-Release for months it went to production.
The Script
First, before I go any farther I KNOW the data is available in SQL locally and I know many people have created a console extension to find out maintenance window information but I thought this was a great ‘native’ solution that didn’t care about recurrence or any of the other strange things out there. So lets take a quick look at the WMI Class in the client SDK we will be exploring CCM_ServiceWindow:
From this we know that there are different types of maintenance windows this is something we can choose to factor into our script using parameters to choose the type of window. For this I’m going to pull out and adapt some of the code from the CMOperations Module:
param ( [Parameter(Mandatory = $False)] [string]$SoftwareMW, [Parameter(Mandatory = $False)] [string]$AllProgramsMW, [Parameter(Mandatory = $False)] [String]$ProgramsMW ) $Computername = $ENV:COMPUTERNAME if ($AllProgramsMW -eq 'Yes') #If the All Programs MW is selected find the next available ALL PROGRAMS maintenance window. { try { Write-Verbose -Message "Attempting to connect to $ComputerName and retrieve next available ALL PROGRAMS MAINTENANCE WINDOW" $Window = Get-WmiObject -ComputerName $ComputerName -ErrorACtion Stop -Namespace root\ccm\clientsdk -ClassName CCM_ServiceWindow | Where-Object{ $_.type -eq 1 } | ForEach-Object{ [Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime) } | Sort $_.StartTime | Select-Object -First 1 #Gets the Maintenance Window time from WMI And converts it to a date time object. $Message = "Next available ALL PROGRAMS window for $ComputerName is " + $Window $Message #Returns and displays the window information to the screen. } catch #catches and throws a terminating error if the remote WMI call fails. { throw "An Error has occured retriving window information" } } if ($SoftwareMW -eq "Yes") #if the SoftwareMW is selected finds the next available software maintenance window for the device. { try { Write-Verbose -Message "Attempting to connect to $ComputerName and retrieve next available SOFTWARE UPDATES MAINTENANCE WINDOW" $Window = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\clientsdk -ClassName CCM_ServiceWindow | Where-Object{ $_.type -eq 4 } | ForEach-Object{ [Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime) } | Sort $_.StartTime | Select-Object -First 1 #Gets the next available maintenance window from WMI of type Software Update and converts it to a datetime object $Message = "Next available SOFTWARE UPDATES MAINTENANCE window for $ComputerName is " + $Window $Message #Returns and displays the window information to the screen. } catch #catches and throws a terminating error if the remote WMI call fails. { throw "An Error has occured retriving window information" } } if ($ProgramsMW -eq "Yes") #if the ProgramsMW is selected finds the next available Programs window for the device. { try { Write-Verbose -Message "Attempting to connect to $ComputerName and retrieve next available PROGRAMS MAINTENANCE WINDOW" $window = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\clientsdk -ClassName CCM_ServiceWindow | Where-Object{ $_.type -eq 2 } | ForEach-Object{ [Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime) } | Sort $_.StartTime | Select-Object -First 1 $Message = "Next available PROGRAMS MAINTENANCE window for $ComputerName is " + $Window $Message } catch { throw "An Error has occured retriving window information" } } if ($SoftwareMW -eq "No" -and $AllProgramsMW -eq "No" -and $ProgramsMW -eq "No") { try { Write-Verbose -Message "Attempting to connect to $ComputerName and retrieve next available MAINTENANCE WINDOW OF ANY TYPE" $Window = Get-WmiObject -ComputerName $ComputerName -Namespace root\ccm\clientsdk -ClassName CCM_ServiceWindow | Where-Object{ $_.type -eq 2 -or $_.Type -eq 1 -or $_.Type -eq 4 } | ForEach-Object{ [Management.ManagementDateTimeConverter]::ToDateTime($_.StartTime) } | Sort $_.StartTime | Select-Object -First 1 #Gets the next available Programs Maintenance window from WMI and converts it to a datetime object $Message = "Next available MAINTEANNCE window of any type for $ComputerName is " + $Window $Message #Returns and displays the window information to the screen. } catch #catches and throws a terminating error if the remote WMI call fails. { throw "An Error has occured retriving window information" } }
The above has been saved as a PS1 script and is available on our GitHub. Now what the heck do we do with this script.
Run Scripts
First we are going to take our saved script and we are going to create a new script package in ConfigMgr. In order to do this we need to navigate to ‘Software Library’ right click on ‘Scripts’ and ‘Create Script’
This will launch the wizard for creating a script in the ConfigMgr Run Scripts space. First we need to name our script something – and then we can copy and past the above script and put it in here. before we hit next. For this example I used ‘Get-NextMW’ as the name of the script. You can also use the ‘Import’ function but note that it does interesting things with storing the script in the database when you use the import feature. (We will talk about that in my next post reporting on run scripts with PowerBI)
Now our script does have parameters, three of them to be exact – SoftwareMW, AllProgramsMW and ProgramsMW (ProgramsMW means Task Sequence I just haven’t fixed that yet). Click next.
Now we get to assign some things about our parameters for the purpose of the above script we want to set a default value of ‘No’ for each thing. This will mean the default is to find the next available window of any type. We can also click the edit button so that we can add a description to each parameter so people know what the parameter is for and what value they should put in.
Once we put in our flavor text to say whatever we want to describe the variables as we can go ahead and click next next next to complete the creation of the script. Now if you haven’t used ‘Run Scripts’ yet you’ll need to do a few more things before you can press forward. First you’re going to need to approve the script to be able to run. However, if you are all alone in your configmgr environment you may need to make a hierarchy settings change to allow you to approve your own scripts. You can find he setting in -> Administration -> Sites Configuration -> Right Click ‘Sites’ and select hierarchy settings. At the bottom you’ll see an option to allow you to approve you’re own scripts.
OK, so we’ve got a script in place, we’ve allowed ourselves to approve our own script. Next we go back and we approve our script its a simple ‘Next Next’ process after you click approve with a radio button option at the end and the opportunity to place a comment.
Once the script is approved now we can make some magic happen. So lets go find a server that we know has a maintenance window. In this example I’ve chosen my SCCM Site Server and I want t know when is the next Installation Maintenance window is. So I right click my client and select ‘Run Script’
Select the script I want to run
Select next and then I’m going to change my ‘AllProgramsMW’ parameter to ‘YES’ hit next and wait for the magic to happen.
If I did everything correct, it will come back and tell me that my next window to install software is in fact today!
So what now
Whats great about this is that I’ve got users in my environment who aren’t confident in powershell or maybe they don’t want to use SSRS report. Now I can give them a method where they can run this script against the servers they are responsible for and find out when the maintenance window is for that server! This is really helpful especially since it can run against an entire collection of servers etc. The usability doesn’t just stop there either there are a lot of new ways this toolbox can be used and I’m looking forward to writing about them!
If there is some interest I’ll do something about RBAC for run scripts in the future.
Hi – Excellent script. Going to test in lab tomorrow.
Look forward to RBAC script.
Thanks
Ram
Glad you liked it! I’m going to be working on some improvements to it and will udpate it in the GitHub I need to add some comments and correct a couple ‘misleading’ labels.
Cheers!
Thanks for this script, but am I missing something ?
I tried the script and found a glitch on how wmi stores the date (that was new to me). Our maintenance windows are configured with the time already in our timezone (so no UTC checked) so when I get them from wmi I can see for example :
StartTime : 20180603221500.000000+000
This should be read as 03 June 2018 22:15:00, but instead the script gives me 04 June 2018 00:15:00
The function [Management.ManagementDateTimeConverter]::ToDateTime converts the time thinking it is in UTC format and add hours to compensate the timezone.
So I had to modify your script to remove these unneeded hours and to do that I had to change $window to $window.ToUniversalTime()
Am I the only one with this problem ?
That’s a good catch I didn’t think about checking out differences between UTC in MW or non UTC.
I’ll have to look into it.