Skip to content

Intune Chronicles: The Automation Secrets of Seamless Group Tags

Unlock seamless Autopilot management with Intune group tag automation. Effortlessly sort, integrate, and assign profiles for efficient device management!

Table Of Contents

The Background

Moving from legacy device management to cloud-based management in Microsoft Intune using Offline Windows Autopilot, you will soon realize your devices are missing the magic Autopilot Group tag.

The Seamless Group Tags

An Autopilot group tag serves as a name or code to identify and categorize Windows Autopilot devices within an organization. Group tags are crucial in organizing and managing devices based on location, purpose, or other relevant characteristics. They streamline the integration of devices, facilitate proper Autopilot profile assignments, and enable effective device management. This approach saves time and resources and ensures a smoother deployment of Autopilot features.

Reimage For Cloud Only Using Offline Windows Autopilot

Organizations moving to cloud-managed devices might need to reimage existing devices for one last time to get them onboarded. They can provide those devices with an offline Windows Autopilot deployment profile to ensure they will contact the Windows Autopilot deployment service without first being registered for the Autopilot service.

The Missing Windows Autopilot Devices

However, it’s essential to note that offline Windows Autopilot does not generate an entry for the device in the Autopilot device section of the Microsoft Intune portal. Intune, a service for remote device management and security, requires you to enable the option to convert all targeted devices to Autopilot in the deployment profile settings to have these devices appear in the Autopilot device section.

The Missing Group Tags

This setting applies to devices meeting the profile criteria, regardless of online or offline status, registering them as Windows Autopilot devices. However, offline devices won’t have a group tag assigned when onboarded to Windows Autopilot this way, as you can quickly get when importing hardware hashes manually for Windows Autopilot.

Group tags become instrumental in assigning Autopilot deployment profiles that define various device settings, including computer name, language, region, and more. Azure dynamic groups automate the assignment of deployment profiles based on group tags. Dynamic groups update their membership automatically according to specified rules. For instance, you can create a rule that adds all devices with a specific group tag to a dynamic group and then assigns the corresponding Autopilot deployment profile. This ensures consistent settings for devices sharing the same group tag.

The Challenge to Target

With this background, let me run you through my Azure Runbook, which finds Autopilot devices missing a valid group tag and assigns the correct one.

The Automation Secrets of Seamless Group Tags

Let us step into the realm of tailored automation solutions! While I provide a crafted solution for my environment, remember to embrace flexibility โ€“ like a bespoke suit, adjustments may be needed to ensure a perfect fit for your unique organizational landscape.

My Demo Environment

I have done some different interpretations of the logic behind this routine. For ease of it, I will start in an environment where devices have uniform computer names based on the country of the registered owner. This is based on a variation of my earlier described solution for Exclusive Computer Renaming with Intune and Azure Runbook. Now, I want these devices to have a Group tag based out of the Country Code.

This gives the following computer name template for this environment:

Country CodeComputer NameGroup tag
SESE-%SERIAL%DeviceSE
NONO-%SERIAL%DeviceNO
DKDK-%SERIAL%DeviceDK
FIFI-%SERIAL%DeviceFI
DEDE-%SERIAL%DeviceDE

The flow in my demo environment will look like the following, where the green box is what is being configured now:

The two Azure Automation jobs could be coordinated into one operation, but this demo environment made it convenient to use two separate actions.

Create an Azure Automation Account

From the Microsoft Azure portal, you can create a new Automation account.

The wizard will lead you through the creation process.

After defining the subscription, resource group, and details for the instance, you should select a system-assigned managed identity.

You should finish up the automation account routine.

Assign Rights to the Managed Identity

Open the Automation Account, navigate to Identity, and copy the Object ID.

With this Object ID, you can use the following script to assign rights to the Managed Identity. The assigned rights are defined in the variable named $app_roles on line number 9.

$TenantID = "YOUR TENANT ID"
$ServicePrincipalId = "YOUR MANAGED IDENTITY OBJECT ID"

# Connect to Microsoft Graph
Connect-MgGraph -TenantId $TenantID -Scopes "Application.Read.All","AppRoleAssignment.ReadWrite.All","RoleManagement.ReadWrite.Directory"

#Set Service Principal Permissions
$graphStdApp = Get-MgBetaServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'"
$app_roles = "DeviceManagementServiceConfig.ReadWrite.All", "Device.Read.All"
$Permissions = $graphStdApp.AppRoles | Where-Object {$_.value -in $app_roles}
foreach ($Permission in $Permissions) {
    New-MgBetaServicePrincipalAppRoleAssignment -ServicePrincipalId $ServicePrincipalId -PrincipalId $ServicePrincipalId -AppRoleId $Permission.Id -ResourceId $graphStdApp.Id
}

Disconnect-MgGraph
PowerShell

Once this script has been run, you can find the managed identity under Azure Enterprise Applications by searching for the name of the automation account or the Object ID found in the Identity tab for the Automation Account.

Verify that the permissions are in place.

This should give the managed identity the right to add a group tag to the autopilot devices.

The Almighty Runbook

Variables

Now it’s time to configure the environment, and first out, let’s define some variables. Navigate to Variables found under Shared Resources in the Automation Account. Add a new variable holding the Tenant ID.

Making this an encrypted variable ensures only the script can read this upon execution.

Modules

This routine will need the MGGraph module, meaning I must add the modules to the automation account.

Search for the needed modules to be added. First, I added the module for authentication.

Repeat the same routine for the other necessary modules. You can find the necessary modules in Microsoft documentation for each command used.

The installed modules can be listed by searching for them.

Importing the modules can take some time.

Create a Runbook

Now it is time to create a Runbook holding the script for Group tag automation.

Name the Runbook according to your standards.

The runbook will now be created.

The Group Tag Script

I have shared the script through my GitHub repo Community-By-SSkotheimsvik/Microsoft/Intune/Group Tag Fixer/RB-Grouptag Fixer.ps1 at main ยท SimonSkotheimsvik/Community-By-SSkotheimsvik (github.com)

<#
    .SYNOPSIS
    Created on:     01.06.2023
    Modified on:    12.01.2024
    Created by:     Simon Skotheimsvik
    Info:           https://skotheimsvik.no
    Version:        1.1.3
    
    .DESCRIPTION
    Automatic set Group tag on Autopilot devices based on computer name.
    Script designed for running in Azure Automation Account using managed identity.#>

#region Variables
$TenantId = Get-AutomationVariable -Name 'aa-no-grouptag-tenantid'

$csvContent = @"
DevicePrefix;GroupTag
SE-;DeviceSE
NO-;DeviceNO
DK-;DeviceDK
FI-;DeviceFI
DE-;DeviceDE
"@

$GroupTagInfo = $csvContent | ConvertFrom-Csv -delimiter ";"

#endregion Variables

#region Authentication
Connect-MgGraph -Identity #-TenantId $TenantId
write-output "Authentication finished"
#endregion Authentication

#region Device traversal
foreach ($Group in $GroupTagInfo) {
    # Get variables from CSV for current group
    $DevicePrefix = $Group.DevicePrefix
    $DeviceTargetGroupTag = $Group.GroupTag

    # Search all Autopilot devices starting with Prefix and has Zero Touch Device ID (Autopilot devices)
    $Devices = Get-MgBetaDevice -Filter "startsWith(displayName, '$DevicePrefix')" -ConsistencyLevel eventual -All | Where-Object { $_.PhysicalIds -match '\[ZTDID\]' }

    if ($Devices.count -gt 0) {
        Write-Output "$($Devices.count) devices with prefix $DevicePrefix"

        # Iterate alle devices found where name starting with Prefix
        foreach ($Device in $Devices) {
            # Get variables from the Autopilot device
            $ReturnBodyTemp = New-Object -TypeName PSObject     # New JSON object for logging
            $DeviceDisplayName = $Device.DisplayName
            $AutopilotZTDID = ($Device.PhysicalIds | Select-String -Pattern '\[ZTDID\]:(.*)').Matches.Groups[1].Value
            $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "DisplayName" -Value "$DeviceDisplayName" -Force    # Add value for logging
            $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "Target Group tag" -Value "$DeviceTargetGroupTag" -Force    # Add value for logging
            $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "ZTDID" -Value "$AutopilotZTDID" -Force    # Add value for logging
            try {
                $DeviceGroupTag = ($Device.PhysicalIds | Select-String -Pattern '\[OrderId\]:(.*)').Matches.Groups[1].Value
                $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "OrderId" -Value "$DeviceGroupTag" -Force    # Add value for logging
            }
            catch {
                Write-Error "$($DeviceDisplayName) is missing grouptag."
                $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "OrderId" -Value "-" -Force    # Add value for logging
                $DeviceGroupTag = ""
            }

            # Check if current Group tag is Ok
            if ($DeviceTargetGroupTag -ne $DeviceGroupTag) {
                Write-Warning "$($DeviceDisplayName) has grouptag ""$($DeviceGroupTag)"" not matching target grouptag ""$($DeviceTargetGroupTag)"". Device will be updated."
                $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "Action" -Value "Wrong Group tag" -Force    # Add value for logging
                
                # Update Autopilot device with new Group tag
                $params = @{
                    groupTag = $DeviceTargetGroupTag
                }
                try {
                    Update-MgBetaDeviceManagementWindowsAutopilotDeviceIdentityDeviceProperty -WindowsAutopilotDeviceIdentityId $AutopilotZTDID -BodyParameter $params
                    $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "Status" -Value "Succeed" -Force    # Add value for logging
                    write-warning "Device $($CurrentDeviceName), Group tag set to $($DeviceTargetGroupTag)"              
                }
                catch {
                    $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "Status" -Value "Failed" -Force    # Add value for logging
                    write-warning "Device $($CurrentDeviceName), Failed setting Group tag to $($DeviceTargetGroupTag)"              
                }

            }
            else {
                Write-Output "$($DeviceDisplayName) has grouptag ""$($DeviceGroupTag)"". No change required."
                $ReturnBodyTemp | Add-Member -MemberType NoteProperty -Name "Action" -Value "OK Group tag" -Force    # Add value for logging

            }

            #write output logs in JSON format
            $LogOutputMsg = $ReturnBodyTemp | ConvertTo-Json 
            Write-Output $LogOutputMsg
            
            $Device = $null
            $DeviceDisplayName = $null
            $DeviceGroupTag = $null
            $AutopilotZTDID = $null
        }
    }
    else {
        Write-Output "$($Devices.count) devices with prefix $DevicePrefix"
    }
    $Devices = $null     
}
#endregion Device traversal
PowerShell

The script should be rich in comments to make you catch the flow. This version has embedded CSV content holding information on the device name prefix and corresponding group tag. I have created alternative versions reading this information from CSV stored in an Azure storage blob.

Download the script from GitHub, make your adaptions, and add it to the runbook before publishing.

Now, we will create a schedule telling when this script should run.

This will ensure this script is running each night.

Testing The Automation On A Device

It is time to stage an automation test doing the proof of concept. As the curtain rises, our digital actors, the scripts of code, take center stage, ready to showcase the seamless choreography of efficiency.

Windows Autopilot Deployment Profile

I have one Windows Autopilot deployment profile in this demo environment intended for Norwegian users. This is the “DeviceNO” deployment profile. From this point, profiles for the other nations mentioned in the script will be provided.

I can now export the JSON for this deployment profile for offline Autopilot onboarding. For this, I am using a Graph script shared by Johan Arwidmark: Windows Autopilot for Existing Devices – Downloading the Deployment Profile – Deployment Research.

Running this script gave me the JSON output for an offline Autopilot onboarding. This file will be added as C:\Windows\Provisioning\Autopilot\AutoPilotConfigurationFile.json on existing Windows devices before being reset. This will allow them to do Windows Autopilot without first collecting and adding device hardware hashes to the Windows Autopilot service.

Legacy on-premises management solutions typically distribute this JSON file as a last effort to migrate the devices to a cloud-only approach led by Microsoft Intune.

The Offline Autopilot Experience

My demo device has swallowed the pill. The JSON file has been injected, and the device has been wiped. Pressing SHIFT+F10 in OOBE gives a prompt where I can verify the content of this file.

Booting the device will start the Autopilot routine without the device hash being imported and assigned a profile in advance.

After the device is onboarded, we see the deployment profile has branded the device with the device name template, etc. We can see information on the enrollment profile by looking at the Hardware blade of the device in Intune.

The device is, however, not yet in the Autopilot device list found in Intune.

Dynamic Entra ID Device Group

I will create a dynamic device group in Entra iD to get a hold of these offline Autopilot devices. The dynamic rule will check for the enrollment profile name used by the Offline Autopilot devices. I will also add a check for the Group tag to ensure devices coming to that route are likewise treated.

The rule syntax in this example is:
((device.enrollmentProfileName -eq “OfflineAutoPilotProfile-XXXXXXXXXXXXXXXXXXXXXXXXXX”) or (device.devicePhysicalIDs -any _ -eq “[OrderID]:DeviceNO”))

All devices running Offline Autopilot with this deployment profile will now populate this group.

As the screenshot above shows, my demo device is now a member of this group, and my first automation routine has already renamed the device based on the primary user.

Arm The Windows Autopilot Deployment Profile

I will now make two adjustments to the Autopilot deployment profile.

The policy is assigned to the dynamic device group, and I have set the option to convert all targeted devices to Autopilot devices.

After a while, I will see these devices being added to the Autopilot device list:

Be aware. “After a while” is a relative statement. According to Microsoft, it can take as long as 48 hours to populate the list of Windows Autopilot devices.

“Said, woman, take it slow. It’ll work itself out fine. All we need is just a little patience”

The Automation Seamlessly Adding Group Tags

As seen in the list of Autopilot devices above, the device that arrived through the offline Autopilot route is missing the group tag. This might be problematic further down the road when the device might be wiped and reinstalled. The group tag will ensure the correct Autopilot deployment profile is assigned, and this will take precedence over offline JSON files on the device if present.

This is where my automation will truly shine โญ, as it seamlessly incorporates group tags onto devices that either lack them or possess incorrectly configured group tags.

Looking at the Runbook configured earlier, I can see the status of the scheduled runs.

Outputs from the run will be found by looking into one of the jobs. The following warning says one device was found missing a group tag which the script will fix.

Checking the list of Windows Autopilot devices, I now see a Group tag assigned by the runbook.

The device with a name starting with “NO-” now has the Group tag “DeviceNO” assigned. Voila! The enchantment has taken place!๐Ÿ‘

Monitoring The Solution

Monitoring is your backstage pass to tech harmony, catching and fixing issues before they steal the show!

Builtin Logs

As seen above, it is possible to see all logs from each run by navigating the job logs.

This does not scale well over time in large environments.

Log Analytics

The script has been enriched with JSON logging, where the variable “$ReturnBodyTemp” collects logs for each device before it is written to output as JSON. This is visible in the log for each job.

Under the Automation Account’s Monitoring section, you can configure the option to send JobStreams to an Azure Log Analytics workspace.

With these data available in the Log Analytics workspace, we can do advanced hunting in the data using Kusto Query Language (KQL). Here is one KQL example:

AzureDiagnostics
| where ResourceGroup == "RG-GROUPTAG-AUTOMATOR" and Category == "JobStreams"
| where ResultDescription contains "Wrong Group tag"
| sort by TimeGenerated asc
| project TimeGenerated, ResultDescription
Kusto

This query will output information from all runs.

Monitoring these changes using KQL opens a comprehensive and insightful understanding of the system’s dynamics, allowing for proactive problem-solving, performance optimization, and informed decision-making.

External References

Published inAutomationAutopilotAzureEndpointGraphAPIIntuneKustoMEMPowershellScriptWindows

3 Comments

  1. Carlos Giraldo Carlos Giraldo

    Don’t you have to have an APP created so you can include this in the script? also, you might me missing a pair of ” in the first script when stating the permissions

Leave a Reply

Your email address will not be published. Required fields are marked *