Bulk update of AutoPilot group tags via Powershell

Copper Contributor

As the title says, I want to use a command to customize the group tags of many AutoPilot devices. However, I don't want to use a file as a basis first, as described in many tutorials, but access directly the AutoPilot devices in Intune.


In other words, change all devices with a group tag named "A" to a new tag named "B".

Do you guys have a solution on how I can do this? I've only read something about the Graph API so far?


Thanks a lot!

3 Replies

I found this method stopped working for me around the end of September 2023. Before that it worked fine for me to use a script to automatically add Group Tags to Autopilot devices in Intune based on their on-prem AD OU placement.

I am happy to share the full script I was using that has stopped working if anyone is interested. I have reached out to the author of the original script to see if he has any ideas. I think personally that some Beta feature of the previous MSGraph scripting has been changed and no longer works as it used to.
I have also attempted to use the PowerShell module https://www.powershellgallery.com/packages/WindowsAutoPilotIntune/5.6
but that also has no success at least how I have tried when using MSGraph direct login (without registered app ID and secret etc.).
If anyone has any help to offer I would be most grateful as I do this type of thing many times whilst working as a contract senior IT technician for many different companies on the same cloud enabled Hybrid-Join journey.

# GroupTagBulkListWinAutopilotDevices-v4.ps1

# Run this script with local admin rights, preferably on a Windows server.

$ErrorActionPreference = "SilentlyContinue"

## Trust scripts downloaded from the PSGallery
Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted
## Update Nuget Package and PowerShellGet Module
Install-PackageProvider NuGet -Scope CurrentUser -Force

# Install the required PowerShellGet module if not already installed
if (-not (Get-Module -ListAvailable -Name PowerShellGet)) {
    Install-Module PowerShellGet -Scope CurrentUser -Force -AllowClobber

## Remove old modules from existing session
Remove-Module PowerShellGet,PackageManagement -Force -ErrorAction Ignore
## Import updated module
Import-Module PowerShellGet -MinimumVersion 2.0 -Force
Import-PackageProvider PowerShellGet -MinimumVersion 2.0 -Force

## MSAL is used with a registered MsalClientApplication in Azure for MSGraph Authentication and requires a -ClientId and -ClientSecret or Certificate
## See https://github.com/AzureAD/MSAL.PS
# Install the required MSAL.PS module if not already installed (Not supported by Microsoft)
if (-not (Get-Module -ListAvailable -Name "MSAL.PS")) {
    Install-Module -Name "MSAL.PS" -SkipPublisherCheck -Force -AllowClobber

# Install the required AzureAD module if not already installed
if (-not (Get-Module -ListAvailable -Name AzureAD)) {
    Install-Module -Name AzureAD -Force -AllowClobber

# Install the required Graph.Intune module if not already installed
if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Intune)) {
    Install-Module -Name Microsoft.Graph.Intune -Force -AllowClobber

# Check if the OS is a client version
$isClientOS = (Get-CimInstance Win32_OperatingSystem).ProductType -eq 1

# If it is a client OS
if ($isClientOS) {
    # Check if RSAT is installed for Windows 10 or 11 client OS
    $feature = Get-WindowsCapability -Online | Where-Object { $_.Name -like 'Rsat*' }
    if ($feature.Count -gt 0 -and $feature[0].State -ne 'Installed') {
    # Install RSAT tools
    Add-WindowsCapability -Online -Name $feature[0].Name

# If it is not a client OS
if (-not $isClientOS) {
    # Check if RSAT-AD-PowerShell feature is installed (ONLY WORKS ON SERVER OS)
    $featureInstalled = Get-WindowsFeature RSAT-AD-PowerShell -ErrorAction SilentlyContinue
    # If not installed, install the feature (ONLY WORKS ON SERVER OS)
    if ($featureInstalled -eq $null) {
        Install-WindowsFeature RSAT-AD-PowerShell -IncludeAllSubFeature -IncludeManagementTools

# Install the required ActiveDirectory module if not already installed
if (-not (Get-Module -ListAvailable -Name ActiveDirectory)) {
    Install-Module -Name ActiveDirectory -Force -AllowClobber

# Install the required WindowsAutoPilotIntune module if not already installed
if (-not (Get-Module -ListAvailable -Name WindowsAutoPilotIntune)) {
    Install-Module -Name WindowsAutoPilotIntune -Force -AllowClobber

## Import Modules that are not loaded for the current session

# Check if AzureAD module is already imported
if (-not (Get-Module -Name AzureAD -ListAvailable)) {
    Import-Module -Name AzureAD -Force

# Check if ActiveDirectory module is already imported
if (-not (Get-Module -Name ActiveDirectory -ListAvailable)) {
    Import-Module -Name ActiveDirectory -Force

# Check if WindowsAutoPilotIntune module is already imported
if (-not (Get-Module -Name WindowsAutoPilotIntune -ListAvailable)) {
    Import-Module -Name WindowsAutoPilotIntune -Force

# Check if Microsoft.Graph.Intune module is already imported
if (-not (Get-Module -Name Microsoft.Graph.Intune -ListAvailable)) {
    Import-Module -Name Microsoft.Graph.Intune -Force

 Update-MSGraphEnvironment -SchemaVersion "Beta" -Quiet
# Set-MgEnvironment -GraphEndpoint "https://graph.microsoft.com/v1.0" -SchemaVersion "beta"
 Connect-MSGraph -Quiet


# Define parameters for AD retrieval of computer objects from an OU so that they can be tagged the same.
# $OUpath = 'OU=HybridJoin,OU=Laptop,OU=Workstations,OU=Contoso,DC=com'
# $GrpTag = "Ltop"
# $OUpath = 'OU=HybridJoin,OU=Conference,OU=Workstations,OU=Contoso,DC=com'
# $GrpTag = "Conf"
 $OUpath = 'OU=HybridJoin,OU=Desktop,OU=Workstations,OU=Contoso,DC=com'
 $GrpTag = "Dtop"

$ExportPath = 'c:\temp\computers_in_ou.csv'
Get-ADComputer -Filter * -SearchBase $OUpath | Select-object Name | Export-Csv -NoType $ExportPath -Force

# Define the input file path
$InputFilePath = 'c:\temp\computers_in_ou.csv'

# Fetch all devices from Intune
$intuneDevices = Invoke-MSGraphRequest -HttpMethod GET -Url "deviceManagement/managedDevices" | Get-MSGraphAllPages

# Load computer names from the input file
$computerNames = Get-Content -Path $InputFilePath

# Define an array to store results
$results = @()

foreach ($computer in $computerNames) {
    Write-Output "=============================================================================="
    Write-Output "A Computer was found named: $computer"
    $PC = $computer.Trim('"')
    # Find a matching device in Intune based on computer name
    Write-Output "Trying to match " $_.deviceName " and $PC"
    $matchedDevice = $intuneDevices | Where-Object { $_.deviceName -eq $PC }

    if (-not $matchedDevice) {
        Write-Output "Matching Failed"

    if ($matchedDevice) {
        Write-Output "Matching Success"
        $results += [PSCustomObject]@{
            ComputerName  = $PC
            SerialNumber = $matchedDevice.serialNumber
        $computerSerial = $matchedDevice.serialNumber
        Write-Output "SN: $computerSerial"

        # Get all autopilot devices for Invoke-MSGraphRequest method
        $autopilotDevices = Invoke-MSGraphRequest -HttpMethod GET -Url "deviceManagement/windowsAutopilotDeviceIdentities" | Get-MSGraphAllPages
        # Single out the autopilot device we are dealing with that has a name matching the serial number of the Intune/AD device.
        $matchedDevice2 = $autopilotDevices | Where-Object { $_.serialNumber -eq $computerSerial }
            if ($matchedDevice2) {
                        $matchedDevice2.groupTag = $GrpTag

        # Request Body required for Invoke-MSGraphRequest parameter.
        $requestBody = @"
            groupTag: "`"$($matchedDevice2.groupTag)`"",
        # Quoted Group Tag required for Set-AutopilotDevice command parameter.
        $QuotedGrpTag = '"' + $GrpTag + '"'

        Write-Output "Updating entity: $($matchedDevice2.id) | groupTag: $($matchedDevice2.groupTag)"
        # Attempt 2 different methods to set the Group Tag for the Serial Number Autopilot object
        Set-AutopilotDevice -id $computerSerial -groupTag $QuotedGrpTag
        Invoke-MSGraphRequest -HttpMethod POST -Content $requestBody -Url "deviceManagement/windowsAutopilotDeviceIdentities/$($matchedDevice2.id)/UpdateDeviceProperties"


# Invoke an autopilot service sync
Invoke-MSGraphRequest -HttpMethod POST -Url "deviceManagement/windowsAutopilotSettings/sync"

# Remove all values from used variables
$OUpath = $null
$GrpTag = $null
$QuotedGrpTag = $null
$ExportPath = $null
$InputFilePath = $null
$intuneDevices = $null
$computerNames = $null
$results = $null
$computer = $null
$PC = $null
$matchedDevice = $null
$computerSerial = $null
$autopilotDevices = $null
$matchedDevice2 = $null
$requestBody = $null

# Delete the input file
Remove-Item -Path $InputFilePath -Force