A while ago, to long now, I set out to create a number of scripts to help out with the management of Windows as a Service using Microsoft Intune. Since then, a lot has happened, especially when it comes to the automation capabilities with Intune using the Graph API. Therefore, I’ve now started to re-write the scripts and also adding some additional capabilities to create an, as close to possible, fully automated solution to handle Feature and quality updates with Intune, Azure Automation and Powershell.
Ill continue to add scripts to the toolbox over time, and of course keeping the added once up to date. When I feel that I have enough tools ill combine the implementation of them into an additional post as well, but view these post as a peek into what’s coming. Ill gladly take feature request and welcome feedback.
Before the release of the “complete” solution, ill assume that you have a basic knowledge of Azure Automation, Powershell and Intune – and wont go into detail into how you configure and setup the different solutions – that information will be added later. However, ill of course point out the vital parts, and you can find all the step-by-step guides you need in my previous post: Configure and manage Windows Servicing with Microsoft Intune
So, moving on to the first new tool. Its basically a script to run prior to the release of a new feature upgrade. It will create a number of Software Update rings, as well as corresponding Azure AD Groups, and then assign the corresponding ring to Azure AD Group.
I’m using a number of the publicly available functions from the Intune Product Group based on the Graph API, you can find them on GitHub: Intune Powershell.
I’m also using Nickolaj Andersens Module PSIntuneAuth (read more here) since it enables a smoother experience for Azure Automation.
Pre-reqs:
Azure Automation up and running, with the AzureAD and PSIntuneAuth modules imported, and the following automation variables and credentials set:
WaaSAccount = The credentials you would like to run the script as (will be used twice)
AppClientID = Your app ID for Intune, use either the default one or set up own of your own.
WindowsVersion = Which version you would like to create rings for.
Tenantname = your tenantname in this format “domain.onmicrosoft.com”
The Script
The script will start by settings a number of variables, import some modules and authenticate. I’ve then configured a number of JSON-templates to be used for configuration of the Update Rings which are added to a variable of their own. Lastly, a second variable is created that includes the names for the groups and rings to be used later on.
The script will first check if policies for this Windows Version exist – and if any of them are missing it will be added. It will then do the the same maneuver with the groups, and when both the groups and the configurations have been created it will assign each respective policy/configuration to the corresponding group.
This script will work nicely with the Self-service deferral script in my previous post, but more scripts will be added later on to complete the solution. I also have a number of improvements lined up for this script as well as the old once. And you’ll find them all at GitHub: WaaS Management
Are there anything you are missing or are in need of when it comes to Windows Servicing with Intune or ConfigMgr? Let me know!
#Author: Simon Binder #Blog: bindertech.se #Twitter: @Bindertech #Thanks to: @daltondhcp, @davefalkus, @NickolajA and the Intune product group. #Most functions is copied from the Powershell Intune Samples: https://github.com/microsoftgraph/powershell-intune-samples #Script requires the Azure AD Powershell module or the Azure AD Preview Powershell module to run Function Test-JSON(){ param ( $JSON ) try { $TestJSON = ConvertFrom-Json $JSON -ErrorAction Stop $validJson = $true } catch { $validJson = $false $_.Exception } if (!$validJson){ Write-Host "Provided JSON isn't in valid JSON format" -f Red break } } Function Add-DeviceConfigurationPolicy(){ [cmdletbinding()] param ( $JSON ) $graphApiVersion = "Beta" $DCP_resource = "deviceManagement/deviceConfigurations" try { if($JSON -eq "" -or $JSON -eq $null){ write-host "No JSON specified, please specify valid JSON for the Android Policy..." -f Red } else { Test-JSON -JSON $JSON $uri = "https://graph.microsoft.com/$graphApiVersion/$($DCP_resource)" Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" } } catch { $ex = $_.Exception $errorResponse = $ex.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($errorResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd(); Write-Host "Response content:`n$responseBody" -f Red Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" write-host break } } Function Add-DeviceConfigurationPolicyAssignment(){ [cmdletbinding()] param ( $ConfigurationPolicyId, $TargetGroupId ) $graphApiVersion = "Beta" $Resource = "deviceManagement/deviceConfigurations/$ConfigurationPolicyId/assign" try { if(!$ConfigurationPolicyId){ write-host "No Configuration Policy Id specified, specify a valid Configuration Policy Id" -f Red break } if(!$TargetGroupId){ write-host "No Target Group Id specified, specify a valid Target Group Id" -f Red break } $ConfPolAssign = "$ConfigurationPolicyId" + "_" + "$TargetGroupId" $JSON = @" { "deviceConfigurationGroupAssignments": [ { "@odata.type": "#microsoft.graph.deviceConfigurationGroupAssignment", "id": "$ConfPolAssign", "targetGroupId": "$TargetGroupId" } ] } "@ $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" Invoke-RestMethod -Uri $uri -Headers $authToken -Method Post -Body $JSON -ContentType "application/json" } catch { $ex = $_.Exception $errorResponse = $ex.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($errorResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd(); Write-Host "Response content:`n$responseBody" -f Red Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" write-host break } } Function Get-SoftwareUpdatePolicy(){ [cmdletbinding()] param ( [switch]$Windows10, [switch]$iOS ) $graphApiVersion = "Beta" try { $Count_Params = 0 if($iOS.IsPresent){ $Count_Params++ } if($Windows10.IsPresent){ $Count_Params++ } if($Count_Params -gt 1){ write-host "Multiple parameters set, specify a single parameter -iOS or -Windows10 against the function" -f Red } elseif($Count_Params -eq 0){ Write-Host "Parameter -iOS or -Windows10 required against the function..." -ForegroundColor Red Write-Host break } elseif($Windows10){ $Resource = "deviceManagement/deviceConfigurations?`$filter=isof('microsoft.graph.windowsUpdateForBusinessConfiguration')&`$expand=groupAssignments" $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).value } elseif($iOS){ $Resource = "deviceManagement/deviceConfigurations?`$filter=isof('microsoft.graph.iosUpdateConfiguration')&`$expand=groupAssignments" $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" (Invoke-RestMethod -Uri $uri -Headers $authToken -Method Get).Value } } catch { $ex = $_.Exception $errorResponse = $ex.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($errorResponse) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $responseBody = $reader.ReadToEnd(); Write-Host "Response content:`n$responseBody" -f Red Write-Error "Request to $Uri failed with HTTP Status $($ex.Response.StatusCode) $($ex.Response.StatusDescription)" write-host break } } # Import required modules try { Import-Module -Name AzureAD -ErrorAction Stop Import-Module -Name PSIntuneAuth -ErrorAction Stop } catch { Write-Warning -Message "Failed to import modules" } # Read credentials and variables $AADCredential = Get-AutomationPSCredential -Name "WaaSAccount" $Credential = Get-AutomationPSCredential -Name "WaaSAccount" $AppClientID = Get-AutomationVariable -Name "AppClientID" $WindowsVersion = Get-AutomationVariable -Name "WindowsVersion" $Tenantname = Get-AutomationVariable -Name "Tenantname" # Acquire authentication token try { Write-Output -InputObject "Attempting to retrieve authentication token" $AuthToken = Get-MSIntuneAuthToken -TenantName $Tenantname -ClientID $AppClientID -Credential $Credential if ($AuthToken -ne $null) { Write-Output -InputObject "Successfully retrieved authentication token" } } catch [System.Exception] { Write-Warning -Message "Failed to retrieve authentication token" } Connect-AzureAD -Credential $AADCredential #Enter the Windows 10 version you want to deploy in $WindowsVersion. Change the names (or add additional rings) as you like, but remember to change any other corresponding scripts. $WaaSGroups = "$WindowsVersion-SAC - Technical", "$WindowsVersion-SAC - Compatibility", "$WindowsVersion-SAC - First Ring", "$WindowsVersion-SAC - Second Ring", "$WindowsVersion-Compatibility Issues", "$WindowsVersion-Self-Service Deferred" $NumberofGroups = $WaaSGroups.Count $i = 0 $JSONConfigurations = @" { "displayName":"$WindowsVersion-SAC - Technical", "description":"$WindowsVersion-SAC - Technical", "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", "businessReadyUpdatesOnly":"all", "microsoftUpdateServiceAllowed":true, "driversExcluded":false, "featureUpdatesDeferralPeriodInDays":0, "qualityUpdatesDeferralPeriodInDays":0, "automaticUpdateMode":"autoInstallAtMaintenanceTime", "deliveryOptimizationMode":"httpWithInternetPeering", "installationSchedule":{ "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", "activeHoursStart":"08:00:00.0000000", "activeHoursEnd":"17:00:00.0000000" } } "@, @" { "displayName":"$WindowsVersion-SAC - Compatibility", "description":"$WindowsVersion-SAC - Compatibility", "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", "businessReadyUpdatesOnly":"all", "microsoftUpdateServiceAllowed":true, "driversExcluded":false, "featureUpdatesDeferralPeriodInDays":30, "qualityUpdatesDeferralPeriodInDays":3, "automaticUpdateMode":"autoInstallAtMaintenanceTime", "deliveryOptimizationMode":"httpWithPeeringNat", "installationSchedule":{ "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", "activeHoursStart":"08:00:00.0000000", "activeHoursEnd":"17:00:00.0000000" } } "@, @" { "displayName":"$WindowsVersion-SAC - First Ring", "description":"$WindowsVersion-SAC - First Ring", "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", "businessReadyUpdatesOnly":"all", "microsoftUpdateServiceAllowed":true, "driversExcluded":false, "featureUpdatesDeferralPeriodInDays":90, "qualityUpdatesDeferralPeriodInDays":5, "automaticUpdateMode":"autoInstallAtMaintenanceTime", "deliveryOptimizationMode":"httpWithPeeringNat", "installationSchedule":{ "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", "activeHoursStart":"08:00:00.0000000", "activeHoursEnd":"17:00:00.0000000" } } "@, @" { "displayName":"$WindowsVersion-SAC - Second Ring", "description":"$WindowsVersion-SAC - Second Ring", "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", "businessReadyUpdatesOnly":"all", "microsoftUpdateServiceAllowed":true, "driversExcluded":false, "featureUpdatesDeferralPeriodInDays":120, "qualityUpdatesDeferralPeriodInDays":7, "automaticUpdateMode":"autoInstallAtMaintenanceTime", "deliveryOptimizationMode":"httpWithPeeringNat", "installationSchedule":{ "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", "activeHoursStart":"08:00:00.0000000", "activeHoursEnd":"17:00:00.0000000" } } "@, @" { "displayName":"$WindowsVersion-Compatibility Issues", "description":"$WindowsVersion-Compatibility Issues", "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", "businessReadyUpdatesOnly":"all", "microsoftUpdateServiceAllowed":true, "driversExcluded":false, "featureUpdatesDeferralPeriodInDays":180, "qualityUpdatesDeferralPeriodInDays":30, "automaticUpdateMode":"autoInstallAtMaintenanceTime", "deliveryOptimizationMode":"httpWithPeeringNat", "installationSchedule":{ "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", "activeHoursStart":"08:00:00.0000000", "activeHoursEnd":"17:00:00.0000000" } } "@, @" { "displayName":"$WindowsVersion-Self-Service Deferred", "description":"$WindowsVersion-Self-Service Deferred", "@odata.type":"#microsoft.graph.windowsUpdateForBusinessConfiguration", "businessReadyUpdatesOnly":"all", "microsoftUpdateServiceAllowed":true, "driversExcluded":false, "featureUpdatesDeferralPeriodInDays":140, "qualityUpdatesDeferralPeriodInDays":21, "automaticUpdateMode":"autoInstallAtMaintenanceTime", "deliveryOptimizationMode":"httpOnly", "installationSchedule":{ "@odata.type":"#microsoft.graph.windowsUpdateActiveHoursInstall", "activeHoursStart":"08:00:00.0000000", "activeHoursEnd":"17:00:00.0000000" } } "@ foreach ($JSON in $JSONConfigurations) { $PolicyName = $WaaSGroups | Select-Object -Index $i $Policyexist = Get-SoftwareUpdatePolicy -Windows10 | Where-Object -Property Displayname -EQ $PolicyName if ($Policyexist -eq $null) { Write-Host "$PolicyName do not exist, creating $PolicyName" try { Add-DeviceConfigurationPolicy -JSON $JSON } Catch { Write-Host "Unable to create the policy: $PolicyName, script will terminate" Write-Host "$_.Exception.Message" Break } } else { Write-Host "$PolicyName exist, moving to assignment" } $i++ } foreach ($WaaSGroup in $WaaSGroups){ try { $Groupexist = Get-AzureADGroup -SearchString $WaaSGroup | Select-Object -ExpandProperty ObjectID if ($Groupexist -eq $null) { Write-Host "$WaaSGroup do not exist, creating $WaaSGroup" try { New-AzureADGroup -Description "$WaaSGroup" -DisplayName "$WaaSGroup" -SecurityEnabled $true -MailEnabled $false -MailNickName 'NotSet' Start-Sleep -Seconds 5 $Groupexist = Get-AzureADGroup -SearchString $WaaSGroup | Select-Object -ExpandProperty ObjectID } Catch { "Unable to create $WaaSGroup, script will terminate" Break } } else { Write-Host "$WaaSGroup exist, moving to assignment" } } Finally { $PolicyID = Get-SoftwareUpdatePolicy -Windows10 | Where-Object -Property Displayname -EQ $WaaSGroup | Select-Object -ExpandProperty id Add-DeviceConfigurationPolicyAssignment -ConfigurationPolicyId $PolicyID -TargetGroupId $GroupExist } }<span id="mce_SELREST_start" style="overflow:hidden;line-height:0;"></span>
[…] This script assumes that you in some way have applied Software Update rings to some or all of your users or Windows 10 devices. This can be done partly using my other script that I blogged about last week: First tool in the toolbox – Create Software Update rings and groups in Intune […]
[…] The second part is the first part where things actually happens. The first part of the script focuses on hardware, and will use inventory data from Intune to get all existing hardware models. It will then choose one unique device and add that, its user and all of the users Windows devices to the application compatibility group. If you would like to know more about these groups, please read my blogpost on “The first tool in the toolbox”. […]