Automate your Monthly Maintenance Windows


Hi All,

A lot of customers I visit create their maintenance windows on a monthly basis to patch servers, which is a boring and mundane task. I've dabbled in creating scripts with the help of some other PFE’s in the past but I recently created a very nice Dynamic PowerShell script that allows the creation of maintenance windows on a monthly basis and is based on the format of your collection. The idea came while I was onsite with one of my customers who formatted his collections similar to the following format.

MW – Patching – Day 1 08:00

This allowed me to take in the name of all collections and split the values to give me the day after patch Tuesday and the time of the day in 24 hour format of the maintenance windows.

Ill run through an example and then show you how it works in the script.

So based on the premise that Day 0 is patch Tuesday

The collection above will create a maintenance window for that collection on Day 1(Wednesday after patch Tuesday) at 8:00am local time

The following script will work via PowerShell or you can incorporate it into Orchestrator which is what I have done with my customers with a slightly more complex script using PowerShell Remoting.

 Here is the entire script.

#This Script will create maintenance windows dynamically for Monthly patches
#Add Variables
$Duration = '3' #Set the Maintenance windows duration e.g 1 for 1 hour
$CollectionName = 'Maintenancewindows*' #Add Collection Name can use * as a wildcard e.g. MW - Patching - Day*
$Exclude = '' #Add Collection Name can use * as a wildcard e.g. *OOB Day 0*
$MWException = 'Ad Hoc*' #name of exclusions for maintenance windows e.g. Ad Hoc*
$SiteCode = 'PRI' # Put your sitecode here
$Trace = "" #leave this as is to clear the Trace variable this is used for logging

#Import the Configuration Manager Module
Import-Module (Join-Path $(Split-Path $env:SMS_ADMIN_UI_PATH) ConfigurationManager.psd1)
CD "$($SiteCode):"

#Run the function to get Patch Tuesday
$Trace += "Getting Patch Tuesday `r `n"
#Get Patch Tuesday in appropriate format
$CurrentMonth = Get-Date -UFormat "%m"
$CurrentYear = Get-Date -UFormat "%Y"
$Day = "Tuesday"

function Get-DayOfMonth {


    Process {

    $daysInMonth = [datetime]::DaysInMonth($year,$month)

    1..$daysInMonth | Foreach-Object {

        $d = Get-Date  -Year $year -Month $month -Day $_

        if ($d.DayOfWeek -eq $day)
            return $d
        } | Select -Index 1

#Add the names of all the selected collectionsinto an array
$Names = (get-cmdevicecollection | Where {$_.Name -like $CollectionName -and $_.Name -notlike $Exclude}).Name

#for each collection get the name and split it into multiple variables
Foreach ($Name in $Names)
    $CollectionID = (get-cmdevicecollection | Where {$_.Name -eq $Name}).CollectionID
    $NameSplit = $Name.split("")
        $Trace += "Splitting details for MW Monthly `r `n"
        $AddDays = $NameSplit[5]
        $AddHour = $NameSplit[6]
        $AddHoursplit = $AddHour.split(":")
        $AddHours = $AddHoursplit[0]

        #get the day for the Monthly MW Cycle
        $Trace += "getting date for MW Monthly `r `n"
        #Get Patch Tuesday
        $SUSecondTuesday = $CurrentMonth | Get-DayOfMonth -year $CurrentYear -day $Day | Get-Date -Format "yyyy/MM/dd" 

        #Add 1 Day to get Wednesdays Date
        $addday = (get-Date $SUSecondTuesday).AddDays($AddDays) | Get-Date -Format "yyyy/MM/dd HH:mm:ss"
        $addday = (get-Date $addday).AddHours($AddHours) | Get-Date -Format "yyyy/MM/dd HH:mm:ss"

        #Delete any Existing Maintenance windows which are older than today exluding any starting with either OOB* or Monthly *       
        $Trace += "getting MW details for MW Monthly `r `n"
        $MWNames = (Get-CMMaintenanceWindow -CollectionId $collectionID | Where {$_.Name -notlike $MWException}).Name
        Foreach ($MWName in $MWNames)
            #Compmare the MW time with today
            $Starttime = (Get-CMMaintenanceWindow -CollectionId $collectionID -MaintenanceWindowName $MWName).Starttime
            $Timespan = NEW-TIMESPAN –Start $StartDate –End $Starttime
            $compare = ($Timespan).Days
            #If the MW is older than today delete it.
            If ($compare -like '-*')
                $RemoveMW = Remove-CMMaintenanceWindow -CollectionId $CollectionID -MaintenanceWindowName $MWName -Force
                $Trace += "Removed the following Maintenance window $($MWName) from $($CollectionID) `r `n"

        #Create a Schedule
        $ScheduleDate = $addday
        $Schedule = New-CMSchedule -Start $ScheduleDate -DurationCount $Duration -DurationInterval Hours -Nonrecurring
        #Apply a Maintenance window
        $Trace += "Creating MW for MW Monthly `r `n"
        $NameMW = "Monthly $addday"
        $NewMW = New-CMMaintenanceWindow -CollectionID $CollectionID -ApplyTo SoftwareUpdatesOnly  -Name $NameMW -Schedule $Schedule
        $Trace += "Added the following Maintenance window to $($Name) $($NewMW) `r `n"


Now lets break it down

The following function which was written by a fellow Senior PFE based out of Sydney helps us find the actual date of Patch Tuesday for this month


We then collect the names of the Collections excluding anything we specified in the $Exclude variable


We then create a ForEach loop to do the following in each collection

Split the name and adjust the Date and time for the schedule


Get existing maintenance windows and delete any older maintenance windows


Create a Schedule and create the monthly maintenance windows


Before we run the script well look at the collections. I have 4 collections only one that has current maintenance windows


When we look at the properties we can see that the collection has two current maintenance windows. An old one from last month that wed like to delete and one that in the future for the upcoming Sunday that somebody has already created on an Ad hoc basis that wed like to keep.


After the script runs we want the Ad Hoc Maintenance windows to still be there and the old Monthly one to be removed. Along with our new Monthly maintenance window so in the script well add ‘Ad hoc* to the $Exclude variable

Lets run the script and see what happens


I have highlighted two areas. Firstly we can see in my trace that we have gone and removed the old Monthly maintenance windows and we then created 4 new monthly maintenance windows.


If we go back into the console we can see that all of the collections have Maintenance windows now

Lets go into each one and make sure we have the correct times and dates

So this months patch Tuesday occurred this week on the 10th of November 2015. So I expect the following

MaintenanceWindows - Patching - Day 5 08:00 to have two maintenance windows. Ad Hoc and a monthly maintenance window on the 15th of November at 8:00am

Looking at the collection maintenance windows properties we have exactly that


MaintenanceWindows - Patching - Day 5 21:00 to have one maintenance window. A monthly maintenance window on the 15th of November at 9:00pm


MaintenanceWindows - Patching - Day 8 09:00 to have one maintenance window. A monthly maintenance window on the 18th of November at 9:00am


Finally MaintenanceWindows - Patching - Day 8 22:00 to have one maintenance window. A monthly maintenance window on the 18th of November at 10:00pm


Ensure that when you play with this script in your Dev environment that you change the variable to match your collection names maintenance windows and site code.

So you can see that with a little bit of PowerShell you can actually save yourself a lot of time and effort. This is just a very small example of how you can automate Configuration Manager so download the latest set of cmdlets and start enjoying the power of automation.


PLEASE NOTE this script is a sample only and should be adjusted to your unique environment and thoroughly tested in a development environment before any use.