7 steps to keep your Windows Servers Patched with AWS Systems Manager

Discover in this blog post how to keep your Windows Servers Patched with AWS Systems Manager. Learn about best practices and deployment using PowerShell. Patching is one of the biggest pains for system administrators and one of the most important. Patching is like backups: nothing happens when you do them, but you’ll be glad to have them when you need them the most! There’s nothing worse than telling your CEO a worm broke out on your network because your team wasn’t keeping the servers patched.

How do organizations keep Windows servers patched anyway?

Most of them are probably familiar with Windows Server Update Services (WSUS), which is a free product provided by Microsoft. However, WSUS requires a fair amount of configuration: You need to setup a new server (if not sharing an existing one), install the feature, define and download patches etc. On top of that, you need to set all the clients to point to the WSUS server, either through a GPO (Group Policy Object) or directly via the registry. WSUS is yet another on-prem service to manage, but what if you could have WSUS functionality without all the fuss? It is possible with AWS Systems Manager (SSM) service, so keep reading to find out how to do it!

1. Install the AWS Systems Manager (SSM) agent

First off, there’s a bit of setup you’re going to have to do to get SSM up and running. Once you’ve got SSM set up and the SSM agent() installed on all of the Windows servers you’d like to patch, you’ve made some great progress, but you’ve got a little ways to go yet.

2. Define Maintenance Windows

SSM only installs patches during maintenance windows, and you must define one before starting on this journey. To do that, you first need to create an IAM role in controlling access to maintenance windows. I’ll start by creating a IAM policy via JSON and saving it to a text file C:\MaintenanceWindowsRole.json.


Next, I’ll create the role. I can do this in different ways (I’m most comfortable with PowerShell so I’ll use that).

PS C:\Users\adam> New-IAMRole -RoleName 'mw-task-role' -AssumeRolePolicyDocument (Get-Content -Raw C:\MaintenanceWindowRole.json)
Path             RoleName                           RoleId                   CreateDate             Description
----             --------                           ------                   ----------             -----------
/                mw-task-role                       AROAIBV5ZRXZCOXC535PQ    4/27/2018 4:27:14 PM

3. Create a Patch Baseline

AWS Systems Manager

Once you’ve got the IAM role created for managing maintenance windows, the next step is to decide on a patch baseline. A patch baseline defines the types of patches you will be installing across your servers. You’ve got a lot of options here and will wildly vary based on your needs. Patch baselines allow you to define different patches that go to different servers based on various criteria.   In our example, I’m creating a baseline only for Windows servers that have critical updates with an auto-approval rule of two days. I could do this via the AWS Systems Manager (SSM) console, but it’s easier to code so I can easily repeat it if needed. The PowerShell code for creating a baseline isn’t very intuitive, but you can simply execute the script to run it again once you’ve got the script created using the code below. The code will create our baseline with the Critical-Updates name.

$autoApproveInDays = 7
$rule = New-Object Amazon.SimpleSystemsManagement.Model.PatchRule
$rule.ApproveAfterDays = $autoApproveInDays
$ruleFilters = New-Object Amazon.SimpleSystemsManagement.Model.PatchFilterGroup
$patchFilter = New-Object Amazon.SimpleSystemsManagement.Model.PatchFilter
$severityFilter = New-Object Amazon.SimpleSystemsManagement.Model.PatchFilter
$severityFilter.Key = 'MSRC_SEVERITY'
$classificationFilter = New-Object Amazon.SimpleSystemsManagement.Model.PatchFilter
$classificationFilter.Key = 'CLASSIFICATION'
$classificationFilter.Values.Add( 'CriticalUpdates' )
$rule.PatchFilterGroup = $ruleFilters
New-SSMPatchBaseline -Name 'Critical-Updates' -Description 'Baseline containing all critical update' -ApprovalRules_PatchRule $rule.

4. Add the Managed Instances to Patch Groups

An optional, yet recommended step is to add your instances to patch groups. This gives you more granular control over which patches are delivered and installed to which servers. For AWS Systems Manager (SSM) to understand there’s a patch group, it must be defined with the tag key: Patch Group (which is case sensitive!). Let’ go ahead and add a tag to our managed instance that we added previously, called SRV1. Let’s say this server is in production, so I’ll assign it to a patch group called Production. To assign the tag, I first need to get the managed instance ID.

PS C:\Users\adam> Get-SSMInstanceInformation
ActivationId                           : 1c6f3fac-0cdd-4d9e-a32e-c7cae4231495
AgentVersion                           : 2.2.493.0
AssociationOverview                    :
AssociationStatus                      :
ComputerName                           : SRV1.WORKGROUP
IamRole                                : SSMServiceRole
InstanceId                             : mi-07fae420a558c8281
IPAddress                              :
IsLatestVersion                        : True
LastAssociationExecutionDate           : 1/1/0001 12:00:00 AM
LastPingDateTime                       : 4/27/2018 5:01:07 PM
LastSuccessfulAssociationExecutionDate : 1/1/0001 12:00:00 AM
Name                                   : SRV1
PingStatus                             : Online
PlatformName                           : Microsoft Windows Server 2016 Datacenter
PlatformType                           : Windows
PlatformVersion                        : 10.0.14393
RegistrationDate                       : 4/27/2018 4:53:27 PM
ResourceType                           : ManagedInstance
PS> $tag = New-Object Amazon.SimpleSystemsManagement.Model.Tag
>> $tag.Key = 'Patch Group'
>> $tag.Value = 'Production'
>> Add-SSMResourceTag -ResourceType 'ManagedInstance' -ResourceId 'mi-07fae420a558c8281' -Tag $tag

5. Create a Maintenance Window

Next step is to create a maintenance window that states the time when patches can be installed on our servers. To do that, we can use the `New-SSMMaintenanceWindow` command. In the example below, I’m creating a maintenance window that occurs every Tuesday at 4 PM for four hours and cuts off any new tasks one hour before the maintenance window expires.

PS> New-SSMMaintenanceWindow -Name 'EveryTuesday' -Duration 4 -Cutoff 1 -Schedule 'cron(0 16 ? * TUE *)'

Register the Patch Group with the Maintenance Window

Now we need to associate the patch group with the maintenance window to specify which maintenance window is used by the instances in the group.

## Find the maintenance window ID
PS> Get-SSMMaintenanceWindowList
Cutoff      : 1
Description :
Duration    : 4
Enabled     : True
Name        : EveryTuesday
WindowId    : mw-01d06df5638742bb4
## Build the target query
PS> $target = @{Key="tag:Patch Group";Values=@("Production")}
## Register the target
PS> Register-SSMTargetWithMaintenanceWindow -WindowId 'mw-01d06df5638742bb4' -Target $target -ResourceType INSTANCE

Register the Install Task with the Maintenance Window

Finally, we need to set up an install task to scan and install all patches coming from the patch baseline we built. This baseline will be attached to the maintenance windows we just created. This section requires a bit of code preparation and execution but feel free to copy/paste!

PS> $maintenanceWindowId = (Get-SSMMaintenanceWindowList | Where-Object {$_.Name -eq 'EveryTuesday'}).WindowId
PS> $windowTargetId = (Get-SSMMaintenanceWindowTarget -WindowId $maintenanceWindowId).WindowTargetId
PS> $windowRoleArn = (Get-IAMRole -RoleName mw-task-role).Arn
PS> $parameters = @{}
PS> $parameterValues = New-Object Amazon.SimpleSystemsManagement.Model.MaintenanceWindowTaskParameterValueExpression
PS> $parameterValues.Values = @("Install")
PS> $parameters.Add("Operation", $parameterValues)
PS> Register-SSMTaskWithMaintenanceWindow -WindowId $maintenanceWindowId -TaskArn 'AWS-ApplyPatchBaseline' -Target @{ Key="WindowTargetIds";Values=$windowTargetId } -TaskType "RUN_COMMAND" -TaskParameter $parameters -ServiceRoleArn $windowRoleArn -MaxConcurrency 1 -MaxError 1


In this article, I covered a simple scenario of setting up the AWS Systems Manager (SSM) Patch Manager service to install patches to a Windows server on a regular basis. We performed all the configurations via PowerShell, but you can also use AWS CLI and the console to perform these tasks. This may seem a daunting process at first, due to all the various objects we had to manipulate, but I encourage you to copy the code described here and create your own scripts from it. You’ll see that once you have the base code working, you’ll be able to configure it much easier later.

About the Author

Guest author: Adam the Automator
Adam Bertram is a 20-year veteran of IT. He’s an automation engineer, blogger, consultant, freelance writer, Pluralsight course author and a content marketing advisor to multiple technology companies. Adam focuses on DevOps, system management, and automation technologies as well as various cloud platforms.
You can find him blogging at adamtheautomator.com or connect with him on LinkedIn or Twitter @adbertram.

Read Also

Share this post →

Share on linkedin
Share on twitter
Share on facebook
Share on email
You might also like: