In the last post we’ve seen why patching is so important and how it is done in Microsoft Intune with Windows Update for Business. But how do we update all the other products like Microsoft 365, Microsoft Edge, and more? There are multiple ways to achieve that. With more expensive licenses like Microsoft E3 and E5 with Windows 10/11 Enterprise E3 and higher included, you could do that with a few clicks, give Microsoft some support contacts of your organization and Microsoft (Intune) will create the rest for you in the background. Let’s find out what feature I’m talking about and how to make it or parts of it available for everyone. Let’s dive in into my first “advanced” post after the basics, and explore my way of making Windows Autopatch available for everyone.
Important Information for this post
This is just a POC (proof of concept) and I’m not a powershell-god 🙂 I’m sure the following scripts could be made better or more efficient, besides that I was focused on pure function. If you want to implement my solution you should consider to implement some security features and change the settings according to company’s needs. Feel free to give feedback or possible improvements in the comments.
What’s Windows Autopatch?
Windows Autopatch is basically a full outsourcing of update-deployment to Microsoft. It’s “the bigger brother of Microsoft Intune Update Rings”.
To use it, you need these licenses:
- Windows 10/11 Enterprise E3 (or higher), or F3
- Microsoft Entra ID P1 or P2
- Microsoft Intune
After setup, the service includes management of windows update based on Intune Update-Rings, Intune Driver Update and Edge / Microsoft 365 Updates based on configuration profiles.
Feature update policies are included as well. Furthermore there is an automatic onboarding based on a group and dynamic assigning of Update-Rings based on percentage of devices. On top of that there is advanced and intelligent reporting for updates.
Windows Autopatch creates these groups:
Group | Purpose |
Windows Autopatch Device Registration | Group for device registration for Windows Autopatch. Here you would add a device / autopilot device group. |
Windows Autopatch – Devices All | All onboarded devices will be in this group. |
Modern Workplace Devices-Windows Autopatch-Test | Core group for policies, API’s and everything that Windows Autopatch needs to work. |
Modern Workplace Devices-Windows Autopatch-First | Core group for policies, API’s and everything that Windows Autopatch needs to work. |
Modern Workplace Devices-Windows Autopatch-Fast | Core group for policies, API’s and everything that Windows Autopatch needs to work. |
Modern Workplace Devices-Windows Autopatch-Broad | Core group for policies, API’s and everything that Windows Autopatch needs to work. |
Windows Autopatch – Test | Group for Update-Ring Test and software / driver / feature updates. Devices in this group will receive them first. |
Windows Autopatch – Ring1 1% of all devices | Group for Update-Ring. Devices in this group will receive them after the updates ran successfully on the devices in Test group. |
Windows Autopatch – Ring2 9% of all devices | Group for Update-Ring 2. Devices in this group will receive them after the updates ran successfully on the devices in Ring1 group. |
Windows Autopatch – Ring3 90% of all devices | Group for Update-Ring 3. Normal production devices are usually in this group. |
Windows Autopatch – Last | Group for Update-Ring Last. Last devices to receive updates. Normally sensitive production devices. |
Default Config Windows Autopatch (including Update-Rings and device distribution)
Of course it’s possible to add more groups!
The same settings (deferral periods) will get set for driver updates, including an additional policy for automatic approval after 30 days.
Every feature update policy is based on the initial “Windows Autopatch – Global DSS Policy”.
Windows Autopatch creates different update policies for Microsoft 365 Apps and Microsoft Edge (different update channels). One of the biggest advantages beside the intelligent update distribution and testing inside the different update-rings is the advanced report feature.
In the report section of Intune there are two autopatch reports for quality and feature updates.
For example this report after setting up Windows Autopatch and deploying Windows 10 22H2
How it started
A key feature in every Microsoft Intune project is Windows Update as explained in this post.
To create update-rings, driver update policies, and from time to time some feature update policies, is essential. Everything is automatic and everything works fine. Most of the time there are some groups, each of them for one (feature)update-ring and for every ring an exclusion group, in case you want to move a device or a device group to a different update-ring. Maybe you even have the same groups for your update policies for Microsoft 365 and Microsoft Edge. So, for bigger customers with Microsoft 365 E3 and E5 licenses it was an “easy game”, after approval of the customer, I was setting up Windows Autopatch, followed by some adjustments in settings and fully outsourced, fully automatic and intelligent update platform was running. But what about smaller companies without “expensive” licenses, you may ask…? Recently I asked myself the same question. Is there a creative way to build something similar to Windows Autopatch, but for everyone? You want a spoiler? Yes it is, partially.
I began with a brainstorming session and made a simple mindmap with the (essential) features of my adoption of Windows Autopatch. (Sorry for my bad handwriting)
I decided to sort all the features in the following categories: Must-have features and Extra features. More on that in the next chapter.
Features of Autopatch
Must-have Features
These features have to be included.
Dynamic Onboarding
Similar to Windows Autopatch my version should support an easy to use and dynamic onboarding process based on PowerShell. It has to be possible without any remediation scripts or 3rd party tools.
Dynamic and adjustable Group Assignment
After onboarding a device it should already be dynamically assigned to an update-ring and policy but it should also be possible to change that without any “Exclusion-Groups”. The effort of doing that has to be minimized.
Same or better Update Capability as Windows Autopatch
It should be possible to deploy:
- All kind of Windows-Updates (including explicit feature update policies)
- Driver Updates (but sadly not for everyone because of licensing)
- Updates for Microsoft 365 with different update settings
- Updates for Microsoft Edge with different update settings
Additionally other products should be included as well, such as:
- Google Chrome
- Mozilla Firefox
- WinGet Apps
It has to be easy to integrate more apps with ADMX-templates, build in setting-catalog or custom policies.
Reporting
With the build-in Windows Update report in Microsoft Intune it’s already possible to see the update-progress for: feature updates, quality updates and driver updates. Additionally we’ll use the in Intune 2404 release announced Windows update distribution report. Using this we have a powerful report functionality built-in!
Naming
There should be a naming convention for groups, policies and everything else. But it should be easy to read and remember.
Extra Features
These feature(s) would be nice-to-have, but aren’t essential.
PowerShell Console for Management
Windows Autopatch is fully integrated in Microsoft Intune Admin Center. It’s not possible to do the same for my solution, but a management console in PowerShell would be nice.
My Idea and Solution
Idea 1: The Concept with Grouptags
The idea is to define the whole patch-management with one single and easy group-tag assignment within Windows Autopilot. Like this you don’t need any exclusion-groups. After assigning the tag every other assignment works automatically and your devices are getting updated!
Idea 2: The Concept with Entra ID Device Extension Attributes
After posting this blogpost in the amazing Modern Endpoint Management LinkedIn-Community, I got feedback from John. He mentioned the limitations of my approach based on grouptags and suggested Entra ID Device Extension Attributes instead and I fully agree.
Sometimes, especially when you’re designing your own solution, you’re sooo focused on your own approach that you forget about other possible solutions, and that’s where the power of our community comes in. Based on feedback, we make this project even better together <3
So I decided to update my blog with the approach of creating a patch-management with one single and easy Entra ID Device Extension value. Using this approach, you also don’t need any exclusion-groups. After assigning the Entra ID Extension Attribute value, every other assignment works automatically and your devices are getting updated like in the first approach! I decided to use Entra ID Device Extension Attribute 5. I assume that the first 4 extension attributes are already in use in your environment.
Prerequisites
Licensing
You basically need a license with Microsoft Intune included and at least Entra ID P1 for Dynamic Groups and automatic Intune Device enrollment.
Intune
- Microsoft Intune (Initial Setup)
- Windows Autopilot (Setup and Devices)
First Part – The Name(s)
I’m not a fan of fancy names . I call my solution “RTC-Patch”. Simple, right?
Naming convention
The idea is that every group, every policy and every script starts with “RTC-Patch” followed by type and details.
Example
Patch Ring 1 will be called:
RTC-Patch-Ring-1
Patch Policy for Test-Ring will be called:
RTC-Patch-Policy-Test
Second Part – Groups and Policies
The Groups
Dynamic and Static Entra ID Groups for Idea 1 (Grouptags)
To create dynamic groups we need Microsoft Graph API and the following module: Microsoft.Graph.Beta.Groups
Of course it would be possible to create the groups by hand. But let’s be efficient.
To make onboarding efficient we need to create the following dynamic Entra ID groups. Every group has a dynamic membership rule based on grouptags / physical ID’s:
Group Name | Grouptag | Dynamic membership rule |
RTC-Patch-Ring-Test | Patch-Test | (device.devicePhysicalIds -any (_ -eq “[OrderID]:Patch-Test”)) |
RTC-Patch-Ring-1 | Patch-1 | (device.devicePhysicalIds -any (_ -eq “[OrderID]:Patch-1”)) |
RTC-Patch-Ring-2 | Patch-2 | (device.devicePhysicalIds -any (_ -eq “[OrderID]:Patch-2”)) |
RTC-Patch-Ring-3 | Patch-3 | (device.devicePhysicalIds -any (_ -eq “[OrderID]:Patch-3”)) |
RTC-Patch-Ring-Last | Patch-Last | (device.devicePhysicalIds -any (_ -eq “[OrderID]:Patch-Last”)) |
To assign policies we create even more groups. Every previously created dynamic Entra ID group will be a member of one of the following static policy-groups.
Group Name | Description | Membergroup |
RTC-Patch-Policy-Test | This Group will receive beta updates to test. | RTC-Patch-Ring-Test |
RTC-Patch-Policy -Fast | This group will receive regular production updates. | RTC-Patch-Ring-1, RTC-Patch-Ring-2 |
RTC-Patch-Policy -Broad | This group will receive Long-Term Service Releases for the best stability. | RTC-Patch-Ring-3, RTC-Patch-Ring-Last |
To make it easy, I created a PowerShell script, which will create the groups for you, including the assignment.
function Install-ModuleIfNotPresent {
param (
[string]$ModuleName
)
$module = Get-Module -ListAvailable -Name $ModuleName
if ($null -eq $module) {
try {
Install-Module -Name $ModuleName -AllowClobber -Scope CurrentUser -Force -Confirm:$false
Write-Output "$ModuleName module installed successfully."
}
catch {
Write-Error "Failed to install $ModuleName module. Please run PowerShell as Administrator."
exit
}
}
else {
Write-Output "$ModuleName module is already installed."
}
}
function Create-Group {
param (
[hashtable]$Params
)
New-MgBetaGroup -BodyParameter $Params
Write-Output "Group $($Params.displayName) successfully created!"
}
function Add-GroupToGroup {
param (
[string]$dynamicGroupName,
[string]$staticGroupName
)
# Get Object IDs for the Dynamic Group and Static Group
$dynamicGroupId = (Get-MgBetaGroup -Filter "displayName eq '$dynamicGroupName'").Id
$staticGroupId = (Get-MgBetaGroup -Filter "displayName eq '$staticGroupName'").Id
# Prepare the member body
$memberBody = @{
"@odata.id" = "https://graph.microsoft.com/beta/directoryObjects/$dynamicGroupId"
}
# Add the Dynamic Group to the Static Group
New-MgBetaGroupMemberByRef -GroupId $staticGroupId -BodyParameter $memberBody
Write-Output "Added $dynamicGroupName to $staticGroupName"
}
# Main script
$moduleName = "Microsoft.Graph.Beta.Groups"
Install-ModuleIfNotPresent -ModuleName $moduleName
Import-Module $moduleName
Disconnect-MgGraph
Connect-MgGraph -Scopes "Group.ReadWrite.All"
# list of groups to create
$groupsToCreate = @(
@{
description = "Dynamic RTC-Patch Group with all Windows Autopilot Devices"
displayName = "RTC-Patch-AllAutopilotDevices"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-AllAutopilotDevices"
securityEnabled = $true
membershipRule = '(device.devicePhysicalIDs -any (_ -startsWith "[ZTDid]"))'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring Test"
displayName = "RTC-Patch-Ring-Test"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-Test"
securityEnabled = $true
membershipRule = '(device.devicePhysicalIds -any (_ -eq "[OrderID]:Patch-Test"))'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring 1"
displayName = "RTC-Patch-Ring-1"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-1"
securityEnabled = $true
membershipRule = '(device.devicePhysicalIds -any (_ -eq "[OrderID]:Patch-1"))'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring 2"
displayName = "RTC-Patch-Ring-2"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-2"
securityEnabled = $true
membershipRule = '(device.devicePhysicalIds -any (_ -eq "[OrderID]:Patch-2"))'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring 3"
displayName = "RTC-Patch-Ring-3"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-3"
securityEnabled = $true
membershipRule = '(device.devicePhysicalIds -any (_ -eq "[OrderID]:Patch-3"))'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring Last"
displayName = "RTC-Patch-Ring-Last"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-Last"
securityEnabled = $true
membershipRule = '(device.devicePhysicalIds -any (_ -eq "[OrderID]:Patch-Last"))'
membershipRuleProcessingState = "on"
},
@{
description = "Static Group for Edge and Microsoft365 Updates"
displayName = "RTC-Patch-Policy-Test"
groupTypes = @()
mailEnabled = $false
mailNickname = "RTC-PatchPolicy-Test"
securityEnabled = $true
},
@{
description = "Static Group for Edge and Microsoft365 Updates"
displayName = "RTC-Patch-Policy-Fast"
groupTypes = @()
mailEnabled = $false
mailNickname = "RTC-PatchPolicy-Fast"
securityEnabled = $true
},
@{
description = "Static Group for Edge and Microsoft365 Updates"
displayName = "RTC-Patch-Policy-Broad"
groupTypes = @()
mailEnabled = $false
mailNickname = "RTC-PatchPolicy-Broad"
securityEnabled = $true
}
)
foreach ($groupParams in $groupsToCreate) {
Create-Group -Params $groupParams
}
#Group assignment dynamic into static-group
# Group RTC-Patch-Ring-Test into Group RTC-PatchPolicy-Test
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-Test" -staticGroupName "RTC-Patch-Policy-Test"
# Group RTC-Patch-Ring-1 into Group RTC-PatchPolicy-Fast
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-1" -staticGroupName "RTC-Patch-Policy-Fast"
# Group RTC-Patch-Ring-2 into Group RTC-PatchPolicy-Fast
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-2" -staticGroupName "RTC-Patch-Policy-Fast"
# Group RTC-Patch-Ring-3 into Group RTC-PatchPolicy-Broad
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-3" -staticGroupName "RTC-Patch-Policy-Broad"
# Group RTC-Patch-Ring-Last into Group RTC-PatchPolicy-Broad
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-Last" -staticGroupName "RTC-Patch-Policy-Broad"
Dynamic and Static Entra ID Groups for Idea 2 (Entra ID Device Extension Attributes)
To create dynamic groups we need Microsoft Graph API and the following module: Microsoft.Graph.Beta.Groups
Of course it would be possible to create the groups by hand. But let’s be efficient.
To make onboarding efficient we need to create the following dynamic Entra ID groups. Every group has a dynamic membership rule based on device extensionAttribute:
Group Name | Grouptag | Dynamic membership rule |
RTC-Patch-Ring-Test | Patch-Test | (device.extensionAttribute5 -eq “Patch-Test”) |
RTC-Patch-Ring-1 | Patch-1 | (device.extensionAttribute5 -eq “Patch-1”) |
RTC-Patch-Ring-2 | Patch-2 | (device.extensionAttribute5 -eq “Patch-2”) |
RTC-Patch-Ring-3 | Patch-3 | (device.extensionAttribute5 -eq “Patch-3”) |
RTC-Patch-Ring-Last | Patch-Last | (device.extensionAttribute5 -eq “Patch-Last”) |
To assign policies we create even more groups. Every previously created dynamic Entra ID group will be a member of one of the following static policy-groups.
Group Name | Description | Membergroup |
RTC-Patch-Policy-Test | This Group will receive beta updates to test. | RTC-Patch-Ring-Test |
RTC-Patch-Policy -Fast | This group will receive regular production updates. | RTC-Patch-Ring-1, RTC-Patch-Ring-2 |
RTC-Patch-Policy -Broad | This group will receive Long-Term Service Releases for the best stability. | RTC-Patch-Ring-3, RTC-Patch-Ring-Last |
To make it easy, I created a PowerShell script, which will create the groups for you, including the assignment.
function Install-ModuleIfNotPresent {
param (
[string]$ModuleName
)
$module = Get-Module -ListAvailable -Name $ModuleName
if ($null -eq $module) {
try {
Install-Module -Name $ModuleName -AllowClobber -Scope CurrentUser -Force -Confirm:$false
Write-Output "$ModuleName module installed successfully."
}
catch {
Write-Error "Failed to install $ModuleName module. Please run PowerShell as Administrator."
exit
}
}
else {
Write-Output "$ModuleName module is already installed."
}
}
function Create-Group {
param (
[hashtable]$Params
)
New-MgBetaGroup -BodyParameter $Params
Write-Output "Group $($Params.displayName) successfully created!"
}
function Add-GroupToGroup {
param (
[string]$dynamicGroupName,
[string]$staticGroupName
)
# Get Object IDs for the Dynamic Group and Static Group
$dynamicGroupId = (Get-MgBetaGroup -Filter "displayName eq '$dynamicGroupName'").Id
$staticGroupId = (Get-MgBetaGroup -Filter "displayName eq '$staticGroupName'").Id
# Prepare the member body
$memberBody = @{
"@odata.id" = "https://graph.microsoft.com/beta/directoryObjects/$dynamicGroupId"
}
# Add the Dynamic Group to the Static Group
New-MgBetaGroupMemberByRef -GroupId $staticGroupId -BodyParameter $memberBody
Write-Output "Added $dynamicGroupName to $staticGroupName"
}
# Main script
$moduleName = "Microsoft.Graph.Beta.Groups"
Install-ModuleIfNotPresent -ModuleName $moduleName
Import-Module $moduleName
Disconnect-MgGraph
Connect-MgGraph -Scopes "Group.ReadWrite.All"
# list of groups to create
$groupsToCreate = @(
@{
description = "Dynamic RTC-Patch Group with all Windows Autopilot Devices"
displayName = "RTC-Patch-AllAutopilotDevices"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-AllAutopilotDevices"
securityEnabled = $true
membershipRule = '(device.devicePhysicalIDs -any (_ -startsWith "[ZTDid]"))'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring Test"
displayName = "RTC-Patch-Ring-Test"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-Test"
securityEnabled = $true
membershipRule = '(device.extensionAttribute5 -eq "Patch-Test")'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring 1"
displayName = "RTC-Patch-Ring-1"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-1"
securityEnabled = $true
membershipRule = '(device.extensionAttribute5 -eq "Patch-1")'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring 2"
displayName = "RTC-Patch-Ring-2"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-2"
securityEnabled = $true
membershipRule = '(device.extensionAttribute5 -eq "Patch-2")'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring 3"
displayName = "RTC-Patch-Ring-3"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-3"
securityEnabled = $true
membershipRule = '(device.extensionAttribute5 -eq "Patch-3")'
membershipRuleProcessingState = "on"
},
@{
description = "Dynamic RTC-Patch Group with devices assigned to Ring Last"
displayName = "RTC-Patch-Ring-Last"
groupTypes = @("DynamicMembership")
mailEnabled = $false
mailNickname = "RTC-Patch-Ring-Last"
securityEnabled = $true
membershipRule = '(device.extensionAttribute5 -eq "Patch-Last")'
membershipRuleProcessingState = "on"
},
@{
description = "Static Group for Edge and Microsoft365 Updates"
displayName = "RTC-Patch-Policy-Test"
groupTypes = @()
mailEnabled = $false
mailNickname = "RTC-PatchPolicy-Test"
securityEnabled = $true
},
@{
description = "Static Group for Edge and Microsoft365 Updates"
displayName = "RTC-Patch-Policy-Fast"
groupTypes = @()
mailEnabled = $false
mailNickname = "RTC-PatchPolicy-Fast"
securityEnabled = $true
},
@{
description = "Static Group for Edge and Microsoft365 Updates"
displayName = "RTC-Patch-Policy-Broad"
groupTypes = @()
mailEnabled = $false
mailNickname = "RTC-PatchPolicy-Broad"
securityEnabled = $true
}
)
foreach ($groupParams in $groupsToCreate) {
Create-Group -Params $groupParams
}
#Group assignment dynamic into static-group
# Group RTC-Patch-Ring-Test into Group RTC-PatchPolicy-Test
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-Test" -staticGroupName "RTC-Patch-Policy-Test"
# Group RTC-Patch-Ring-1 into Group RTC-PatchPolicy-Fast
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-1" -staticGroupName "RTC-Patch-Policy-Fast"
# Group RTC-Patch-Ring-2 into Group RTC-PatchPolicy-Fast
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-2" -staticGroupName "RTC-Patch-Policy-Fast"
# Group RTC-Patch-Ring-3 into Group RTC-PatchPolicy-Broad
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-3" -staticGroupName "RTC-Patch-Policy-Broad"
# Group RTC-Patch-Ring-Last into Group RTC-PatchPolicy-Broad
Add-GroupToGroup -dynamicGroupName "RTC-Patch-Ring-Last" -staticGroupName "RTC-Patch-Policy-Broad"
Policies
Update-Rings
I decided to stay as close as possible to the default Update-Ring Configuration of Windows Autopatch. So we create the following 5 Update-Rings:
Group Assignment: RTC-Patch-Ring-Test
Group Assignment: RTC-Patch-Ring-1
Group Assignment: RTC-Patch-Ring-2
Group Assignment: RTC-Patch-Ring-3
Group Assignment: RTC-Patch-Ring-4
Information
Please adjust the settings and parameters according to your company’s needs.
Download
You can download all Update-Rings as JSON files from my GitHub and import them for example with Intune Manager from Micke-K and cstaubli.
Driver-Update-Rings
I created Driver-Update-Rings according to Windows-Update-Rings. They are set to automatic update and have the same names, the same deferral periods and the same RTC-Policy group assigned to them.
Important Information
Not every license includes driver update policies / management.
One of the following licenses is necessary :
- Windows 10/11 Enterprise E3 or E5
- Windows 10/11 Education A3 or A5
- Windows Virtual Desktop Access E3 or E5
- Microsoft 365 Business Premium
Of course also every other license (bundle) that includes one of the mentioned licenses.
Download
You can download all Driver-Update-Rings as JSON files from my GitHub and import them for example with Intune Manager from Micke-K and cstaubli.
Web browsers
As described earlier, I want to include update policies for the following browsers:
- Microsoft Edge
- Google Chrome
- Mozilla Firefox
To do that we first need to download and upload the required ADMX-Templates to Microsoft Intune. In this post you see how:
Here you’ll find the ADMX-Templates needed. Microsoft Edge is already built-in.
The goal is to include something like that for every web browser:
Group | Update-Channel |
RTC-Patch-Policy-Test | Beta / Preview |
RTC-Patch-Policy-Fast | Stable |
RTC-Patch-Policy-Broad | Extended Stable |
Configurations
Beta / Preview
Stable
Extended Stable
Beta / Preview
Setting | Value |
Allow installation | Force Installs (Maschine-Wide) |
Target Channel override | beta |
Update policy override | Allways allow updates |
Stable
Setting | Value |
Allow installation | Force Installs (Maschine-Wide) |
Target Channel override | stable |
Update policy override | Allways allow updates |
Extended Stable
Setting | Value |
Allow installation | Force Installs (Maschine-Wide) |
Target Channel override | stable |
Update policy override | Allways allow updates |
Information
Unfortunately there is no LTS Channel setting for Google Chrome in ADMX-Template.
Autoupdate
Unfortunately it’s not possible to set update-channels for normal Mozilla Firefox with ADMX-Templates. It’s only possible to enable autoupdate.
Setting | Value |
Application Autoupdate | Enabled |
You could make a package out of Firefox ESR and assign it to RTC-Patch-Policy-Broad. Please have a look at my blogpost about packaging.
Download
You can download all web browser update policies as JSON files from my GitHub and import them for example with Intune Manager from Micke-K and cstaubli or directly with Microsoft Intune.
Microsoft 365
Also included are Microsoft 365 update Policies.
Group | Update-Channel |
RTC-Patch-Policy-Test | Current Channel (Preview) |
RTC-Patch-Policy-Fast | Current Channel |
RTC-Patch-Policy-Broad | Monthly Enterprise Channel |
Download
You can download all Microsoft 365 update policies as JSON files from my GitHub and import them for example with Intune Manager from Micke-K and cstaubli or directly with Microsoft Intune.
Winget Apps
There are multiple options to update Winget Apps. I decided to include a third party Microsoft Store App together with ADMX-Template configuration for that.
First you need to import the ADMX template, which you can get here.
Then we add the app “Winget-AutoUpdate-aaS” as a “Microsoft Store app (new)”
If you don’t know how to do that, please have a look at this post:
We assign the app to every Update-Ring Group:
Now we create the policy “RTC-Patch-WinGet” and configure the following parameters:
Setting | Value |
Updates at logon | Enabled |
Notification level | None |
Startmenu Shortcut | Disabled |
Desktop Shortcut | Disabled |
Information
By default “Winget-AutoUpdate-aaS” will update every Winget application on the system.
There is also a blacklist of apps that you don’t want to update:
Setting | Value |
Application List | WinGet IDs |
The policy will be assigned to every RTC-Patch-Policy Group. If you want to use the blacklist, feel free to create multiple policies and assign the to the necessary RTC-Patch-Policy Groups.
Third Part – Logic
Onboarding and Changes
Manually (with Grouptags)
If you really want to, it’s possible to manually assign the right group tag to your Windows Autopilot Device. You can assign these group-tags:
- Patch-Test
- Patch-1
- Patch-2
- Patch-3
- Patch-Last
The needed group assignments in the background happen automatically.
Manually (with Entra ID Device Extension Attributes)
If you really want to, it’s possible to manually insert the right value into Entra ID Devices Extension attribute 5. You can set these values:
- Patch-Test
- Patch-1
- Patch-2
- Patch-3
- Patch-Last
The needed group assignments in the background happen automatically.
Automated (with Grouptags)
To onboard devices automatically you can execute the following script. It will show you the available group tags to assign. After choosing the group tag you can select the target device(s) and then the previously chosen group tag will be assigned to these devices.
The following permissions are needed to make these changes:
- DeviceManagementServiceConfig.ReadWrite.All
function Install-ModuleIfNotPresent {
param (
[string]$ModuleName
)
$module = Get-Module -ListAvailable -Name $ModuleName
if ($null -eq $module) {
try {
Install-Module -Name $ModuleName -AllowClobber -Scope CurrentUser -Force -Confirm:$false
Write-Output "$ModuleName module installed successfully."
}
catch {
Write-Error "Failed to install $ModuleName module. Please run PowerShell as Administrator."
exit
}
}
else {
Write-Output "$ModuleName module is already installed."
}
}
function RTC-Patch-ChangeAssignment {
# Get all autopilot devices (even if more than 1000)
$autopilotDevices = Invoke-MSGraphRequest -HttpMethod GET -Url "deviceManagement/windowsAutopilotDeviceIdentities" | Get-MSGraphAllPages
# Ask user to select a group tag to assign
$groupTags = @("Patch-Test", "Patch-1", "Patch-2", "Patch-3", "Patch-Last")
$selectedGroupTag = $groupTags | Out-GridView -OutputMode Single -Title "Select the group tag you want to assign"
# Display gridview to show devices
$selectedAutopilotDevices = $autopilotDevices | Out-GridView -OutputMode Multiple -Title "Select Windows Autopilot entities to update"
$selectedAutopilotDevices | ForEach-Object {
$autopilotDevice = $PSItem
$autopilotDevice.groupTag = $selectedGroupTag
$requestBody =
@"
{
groupTag: `"$($autopilotDevice.groupTag)`",
}
"@
Write-Output "Updating entity: $($autopilotDevice.id) | groupTag: $($autopilotDevice.groupTag) | orderIdentifier: $($autopilotDevice.orderIdentifier)"
Invoke-MSGraphRequest -HttpMethod POST -Content $requestBody -Url "deviceManagement/windowsAutopilotDeviceIdentities/$($autopilotDevice.id)/UpdateDeviceProperties"
}
# Invoke an autopilot service sync
Invoke-MSGraphRequest -HttpMethod POST -Url "deviceManagement/windowsAutopilotSettings/sync"
}
#Main Script
$moduleName = "Microsoft.Graph"
Install-ModuleIfNotPresent -ModuleName $moduleName
Disconnect-MgGraph
Connect-MSGraph
Update-MSGraphEnvironment -SchemaVersion "Beta" -Quiet
Connect-MSGraph -Quiet
RTC-Patch-ChangeAssignment
This is a modified version of a PowerShell script made by Nicola Suter. Thank you so much for the permission to use it in my post 🙂
Automated (with Entra ID Device Extension Attributes)
To onboard devices automatically you can execute the following script. It will show you the available values to assign to Entra ID device extension attribute 5. After choosing the value you can select the target device(s) and then the previously chosen value will be assigned to these device(s).
The following permissions are needed to make these changes:
- Device.ReadWrite.All
function Install-ModuleIfNotPresent {
param (
[string]$ModuleName
)
$module = Get-Module -ListAvailable -Name $ModuleName
if ($null -eq $module) {
try {
Install-Module -Name $ModuleName -AllowClobber -Scope CurrentUser -Force -Confirm:$false
Write-Output "$ModuleName module installed successfully."
}
catch {
Write-Error "Failed to install $ModuleName module. Please run PowerShell as Administrator."
exit
}
}
else {
Write-Output "$ModuleName module is already installed."
}
}
function RTC-Patch-ChangeAssignment {
Connect-MgGraph -Scopes "Device.ReadWrite.All"
# Retrieve all devices
[array]$Devices = Get-MgDevice -All
# Define group tags for selection
$EAvalues = @("Patch-Test", "Patch-1", "Patch-2", "Patch-3", "Patch-Last")
# Ask user to select a group tag to assign
$selectedEAvalue = $EAvalues | Out-GridView -OutputMode Single -Title "Select the value you want to assign to Entra ID extension attribute"
# Allow user to select one or more devices from a list to update
$selectedDevices = $Devices | Out-GridView -OutputMode Multiple -Title "Select devices to update Entra ID extension attribute"
foreach ($Device in $selectedDevices) {
# Prepare the JSON payload for updating the device
$Attributes = @{
"extensionAttributes" = @{
"extensionAttribute5" = $selectedEAvalue
}
} | ConvertTo-Json
# Update the device with the new group tag
Update-MgDevice -DeviceId $Device.Id -BodyParameter $Attributes
Write-Host ("Device {0} has been updated with Entra ID extension attribute {1}" -f $Device.DisplayName, $selectedEAvalue)
}
}
# Main Script
$moduleName = "Microsoft.Graph"
Install-ModuleIfNotPresent -ModuleName $moduleName
Disconnect-MgGraph
RTC-Patch-ChangeAssignment
Fourth Part – Extra
Management Console for Idea 1 (Grouptags)
It’s essential to have some kind of a management tool. In my case it’s a PowerShell script with some pre-built functions.
In detail the following functions are included:
- Get every RTC-Patch device
- Get current Ring assignment
- Change Assignments of RTC-Patch devices
function Install-ModuleIfNotPresent {
param (
[string]$ModuleName
)
$module = Get-Module -ListAvailable -Name $ModuleName
if ($null -eq $module) {
try {
Install-Module -Name $ModuleName -AllowClobber -Scope CurrentUser -Force -Confirm:$false
Write-Output "$ModuleName module installed successfully."
}
catch {
Write-Error "Failed to install $ModuleName module. Please run PowerShell as Administrator."
exit
}
}
else {
Write-Output "$ModuleName module is already installed."
}
}
function RTC-Patch-ShowDevices {
# Get all Autopilot Devices
$autopilotDevices = Invoke-MSGraphRequest -HttpMethod GET -Url "deviceManagement/windowsAutopilotDeviceIdentities" | Get-MSGraphAllPages
# Filter devices where groupTag starts with "Patch"
$filteredAutopilotDevices = $autopilotDevices | Where-Object { $_.groupTag -like "Patch*" }
# Group the filtered devices by groupTag
$groupedAutopilotDevices = $filteredAutopilotDevices | Group-Object -Property groupTag
# Display each group and its devices
foreach ($group in $groupedAutopilotDevices) {
Write-Host "Group Tag: $($group.Name)" -ForegroundColor Green
$group.Group | Out-GridView -Title "Devices with Group Tag: $($group.Name)"
}
}
function RTC-Patch-ChangeAssignment {
# Get all autopilot devices (even if more than 1000)
$autopilotDevices = Invoke-MSGraphRequest -HttpMethod GET -Url "deviceManagement/windowsAutopilotDeviceIdentities" | Get-MSGraphAllPages
# Ask user to select a group tag to assign
$groupTags = @("Patch-Test", "Patch-1", "Patch-2", "Patch-3", "Patch-Last")
$selectedGroupTag = $groupTags | Out-GridView -OutputMode Single -Title "Select the group tag you want to assign"
# Display gridview to show devices
$selectedAutopilotDevices = $autopilotDevices | Out-GridView -OutputMode Multiple -Title "Select Windows Autopilot entities to update"
$selectedAutopilotDevices | ForEach-Object {
$autopilotDevice = $PSItem
$autopilotDevice.groupTag = $selectedGroupTag
$requestBody =
@"
{
groupTag: `"$($autopilotDevice.groupTag)`",
}
"@
Write-Output "Updating entity: $($autopilotDevice.id) | groupTag: $($autopilotDevice.groupTag) | orderIdentifier: $($autopilotDevice.orderIdentifier)"
Invoke-MSGraphRequest -HttpMethod POST -Content $requestBody -Url "deviceManagement/windowsAutopilotDeviceIdentities/$($autopilotDevice.id)/UpdateDeviceProperties"
}
# Invoke an autopilot service sync
Invoke-MSGraphRequest -HttpMethod POST -Url "deviceManagement/windowsAutopilotSettings/sync"
}
# Function to display the menu and handle user input
function RTC-Patch-Display-Menu {
$menu = @(
"Show RTC-Patch devices & Assignments",
"Change Assignments of RTC-Patch devices"
)
# Present the menu options to the user using Out-GridView
$selectedOption = $menu | Out-GridView -Title "RTC-Patch Management Console" -OutputMode Single
# Handle the user's choice
switch ($selectedOption) {
$menu[0] { RTC-Patch-ShowDevices }
$menu[1] { RTC-Patch-ChangeAssignment }
default { Write-Host "No valid option selected." }
}
}
#Main Script
$moduleName = "Microsoft.Graph"
Install-ModuleIfNotPresent -ModuleName $moduleName
Disconnect-MgGraph
Connect-MSGraph
Update-MSGraphEnvironment -SchemaVersion "Beta" -Quiet
Connect-MSGraph -Quiet
# Call the display menu function
RTC-Patch-Display-Menu
Management Console for Idea 2 (Entra ID Device Extension Attributes)
It’s essential to have some kind of a management tool. In my case it’s a PowerShell script with some pre-built functions.
In detail the following functions are included:
- Get every RTC-Patch device
- Get current Ring assignment
- Change Assignments of RTC-Patch devices
function Install-ModuleIfNotPresent {
param (
[string]$ModuleName
)
$module = Get-Module -ListAvailable -Name $ModuleName
if ($null -eq $module) {
try {
Install-Module -Name $ModuleName -AllowClobber -Scope CurrentUser -Force -Confirm:$false
Write-Output "$ModuleName module installed successfully."
}
catch {
Write-Error "Failed to install $ModuleName module. Please run PowerShell as Administrator."
exit
}
}
else {
Write-Output "$ModuleName module is already installed."
}
}
function RTC-Patch-ShowDevices {
$groupNames = @(
"RTC-Patch-Ring-Test",
"RTC-Patch-Ring-1",
"RTC-Patch-Ring-2",
"RTC-Patch-Ring-3",
"RTC-Patch-Ring-Last"
)
Connect-MgGraph -Scopes "Group.Read.All", "Device.Read.All"
foreach ($GroupName in $GroupNames) {
try {
# Get the group by name (assuming group display names are unique)
$group = Get-MgGroup -Filter "displayName eq '$GroupName'"
if ($group) {
# Get members of the group
$memberIds = Get-MgGroupMember -GroupId $group.Id -All | Select-Object Id
$devices = @()
foreach ($memberId in $memberIds) {
# Attempt to retrieve the device by the member ID
$device = Get-MgDevice -DeviceId $memberId.Id -ErrorAction SilentlyContinue
if ($device) {
$devices += $device
}
}
if ($devices.Count -gt 0) {
# Display devices in Out-GridView
$devices | Select-Object DisplayName, Id | Out-GridView -Title "Devices in $GroupName"
}
else {
Write-Host "No devices found in the group $GroupName" -ForegroundColor Yellow
}
}
else {
Write-Host "Group $GroupName not found" -ForegroundColor Red
}
}
catch {
Write-Host "An error occurred: $_" -ForegroundColor Red
}
}
}
function RTC-Patch-ChangeAssignment {
Connect-MgGraph -Scopes "Device.ReadWrite.All"
# Retrieve all devices
[array]$Devices = Get-MgDevice -All
# Define group tags for selection
$EAvalues = @("Patch-Test", "Patch-1", "Patch-2", "Patch-3", "Patch-Last")
# Ask user to select a value to assign
$selectedEAvalue = $EAvalues | Out-GridView -OutputMode Single -Title "Select the value you want to assign to Entra ID extension attribute"
# Allow user to select one or more devices from a list to update
$selectedDevices = $Devices | Out-GridView -OutputMode Multiple -Title "Select devices to update Entra ID extension attribute"
foreach ($Device in $selectedDevices) {
# Prepare the JSON payload for updating the device
$Attributes = @{
"extensionAttributes" = @{
"extensionAttribute5" = $selectedEAvalue
}
} | ConvertTo-Json
# Update the device with the new value
Update-MgDevice -DeviceId $Device.Id -BodyParameter $Attributes
Write-Host ("Device {0} has been updated with Entra ID extension attribute {1}" -f $Device.DisplayName, $selectedEAvalue)
}
}
function RTC-Patch-Display-Menu {
$menu = @(
"Show RTC-Patch devices & Assignments",
"Change Assignments of RTC-Patch devices"
)
$selectedOption = $menu | Out-GridView -Title "RTC-Patch Management Console" -OutputMode Single
# Handle the user's choice
switch ($selectedOption) {
$menu[0] { RTC-Patch-ShowDevices }
$menu[1] { RTC-Patch-ChangeAssignment }
default { Write-Host "No valid option selected." }
}
}
#Main Script
$moduleName = "Microsoft.Graph"
Install-ModuleIfNotPresent -ModuleName $moduleName
# Call the display menu function
RTC-Patch-Display-Menu
Extra-Features to develop
GUI for PowerShell Management Console
It would be very user friendly to have some kind of a real application with graphical user interface. During my research I found the tool “PoshGUI” which is free. With it, it is possible to create a basic user interface like this:
Feature to uninstall specific Windows Updates on specific Rings / Devices
Theoretically it would be already possible to include that feature today. You could use the cmdlet “PSWindowsUpdate” and build a script to remove a specific Windows Update with KB-Number.
But I would love to have a more intuitive solution. Maybe Microsoft will provide us with something like that soon. I would wish that!
That’s it
Now you know, there is a creative way to make Windows Autopatch available for everyone. With only 2 licenses, which almost every tenant already has. It’s no longer needed to create “exclusion-groups” for update-rings, policies, and to manage them. My solution obviously isn’t perfect and can be extended to more products, more apps and more settings within Microsoft Intune.
I can’t wait for your feedback. Let’s make this project even better together!
1 Response
[…] https://racetocloud.com/microsoft-intune/autopatch […]