First tool in the toolbox – Create Software Update rings and groups in Intune

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;">&#65279;</span>

As a Solution Architect, Simon inspires customers, partners and colleagues to create the best possible workplace for their users. His main focus is the Windows platform – but todays workplace consists of so much more than that. As an MCT he is passionate about teaching and sharing knowledge. He’s a frequent speaker, blogger and podcaster – as well as a penguin fanatic.

Tagged with: , , , , , , , , , , , , , , ,
Posted in Azure, Intune, Microsoft, Windows 10, Windows as a Service
2 comments on “First tool in the toolbox – Create Software Update rings and groups in Intune
  1. […] 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 […]

  2. […] 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”. […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

I’m speaking!
I’m going!
Follow me on Twitter!
%d bloggers like this: