Update Windows Autopilot Group Tag with PowerShell Grid View and Microsoft Graph API

Share this post:

Introduction

Windows Autopilot simplifies the deployment and management of new Windows devices. However, updating device properties like the Group Tag for multiple devices can be tedious if done manually. Automating this process not only enhances efficiency but also reduces the potential for human error. Shout out to @Nicola, who back in 2019 created a script that did something similar. However, with recent changes to how you can interact with GraphAPI, we need to make some adjustments, which inspired me to create a new script. Read more about the changes that took effect in April 2024 here:

In this post, we’ll walk through a PowerShell script that:

  • Fetches all Windows Autopilot devices.
  • Allows you to select multiple devices via a user-friendly grid view.
  • Prompts for a new Group Tag once and updates all selected devices.
  • Triggers an Autopilot sync after updating the devices.

By the end of this guide, you’ll be able to streamline your Autopilot device management tasks significantly.

Prerequisites

Before you begin, ensure you have the following:

  • Administrator Access: You need admin rights in your Azure AD tenant.

  • Azure AD Application: An app registered in Azure AD with the appropriate permissions.

  • Microsoft Graph API Permissions:

    • DeviceManagementServiceConfig.ReadWrite.All
    • DeviceManagementManagedDevices.ReadWrite.All
  • PowerShell: Version 5.1 or later.

  • Internet Access: Ability to connect to https://graph.microsoft.com.

  • Execution Policy: Set to allow script execution. You can set it to RemoteSigned with: 

				
					Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

				
			

The script performs the following actions:

  1. Authentication: Obtains an access token using client credentials.
  2. Device Retrieval: Fetches all Windows Autopilot devices using the Microsoft Graph API.
  3. Device Selection: Displays devices in an interactive grid view for selection.
  4. Group Tag Update: Prompts once for a new Group Tag and updates all selected devices.
  5. Autopilot Sync: Triggers a sync to apply the updates.
1. Register an Azure AD Application

First, you need to register an application in Azure Active Directory:

  1. Navigate to the Azure Portal and go to Azure Active Directory > App registrations.
  2. Click New registration.
  3. Enter a name (e.g., “AutopilotDeviceManager”) and set the account type to Accounts in this organizational directory only.
  4. Click Register.

2. Grant API Permissions

Assign the necessary permissions to the application:

  1. In the app’s overview, go to API permissions.
  2. Click Add a permission > Microsoft Graph > Application permissions.
  3. Search for and add the following permissions:
    • DeviceManagementServiceConfig.ReadWrite.All
    • DeviceManagementManagedDevices.ReadWrite.All
  4. Click Grant admin consent to grant the permissions.

Best Practices

  • Secure Credential Storage: For production environments, consider using Azure Key Vault or environment variables to store credentials securely.
  • Error Handling: The script includes error handling to provide detailed feedback. Customize it further to meet your needs.
  • Testing: Always test scripts in a non-production environment before deploying.
  • Logging: Implement logging to a file or monitoring system for auditing purposes.

Running th script

Run the script and specify your
  • Tenant ID
  • Client/App ID
  • Client Secret
Set your filter and search for your devices that you want to update the Group Tag for. You can filter on

  • ID
  • Display Name
  • Model
  • Manufacturer
  • Order ID
  • Enrollment State
  • Seriel Number
After selecting your filter you should be able to see your devices. Select the devices you want to update. And Click OK. 
Type your preffered group tags and the script will change or update the existing Tag for you. 

Thats it. Let us know if you have any issues and please provide feedback for improvements by using the comment field below! 
In a hurry? If you are looking for more answers or need help with Identity and security, you are more than welcome to leave a comment below or email me directly by clicking on my profile.

The script

				
					 
<# Update Windows Autopilot Devices via Microsoft Graph API

 Description:
 This PowerShell script automates the process of updating the Group Tag for multiple Windows Autopilot devices using the Microsoft Graph API.
 It allows you to select devices from a grid view, enter a new Group Tag once, and applies it to all selected devices.
 After updating the devices, it triggers an Autopilot sync to apply the changes.

 Prerequisites:
 - An Azure AD application with the following Microsoft Graph API permissions:
   - DeviceManagementServiceConfig.ReadWrite.All
   - DeviceManagementManagedDevices.ReadWrite.All
 - Tenant ID, Client ID, and Client Secret for the Azure AD application.
 - PowerShell 5.1 or higher.
 - Internet connectivity to access https://graph.microsoft.com.
 - Execution policy set to allow script execution (e.g., RemoteSigned).

 Usage Instructions:
 1. Run the script in PowerShell.
 2. When prompted, enter your Tenant ID, Client ID, and Client Secret.
    - The Client Secret is entered securely and will not be displayed on the screen.
 3. A grid view window will appear displaying all Autopilot devices.
    - Select one or more devices you wish to update and click OK.
 4. When prompted, enter the new Group Tag to be applied to the selected devices.
 5. The script will update the devices and trigger an Autopilot sync.

 Notes:
 - The script uses beta endpoints of the Microsoft Graph API.
   - Beta APIs are subject to change and should be used with caution in production environments.
 - Ensure that the Azure AD application has been granted admin consent for the required permissions.
 - Test the script in a non-production environment before running it in production.

 Version: 1.0
 Author: Marius A. Skovli, Spirhed Group
 Date: October 7, 2024
 https://spirhed.com

#>

# Function to securely get credentials
function Get-SecureCredential {
    param (
        [string]$CredentialName
    )
    return Read-Host -Prompt "Enter your $CredentialName" -AsSecureString
}

# Function to get an access token
function Get-AccessToken {
    param (
        [string]$TenantId,
        [string]$ClientId,
        [string]$ClientSecret
    )

    $authUrl = "https://login.microsoftonline.com/$TenantId/oauth2/v2.0/token"
    $body = @{
        grant_type    = "client_credentials"
        scope         = "https://graph.microsoft.com/.default"
        client_id     = $ClientId
        client_secret = $ClientSecret
    }

    try {
        $response = Invoke-RestMethod -Method Post -Uri $authUrl -ContentType "application/x-www-form-urlencoded" -Body $body
        return $response.access_token
    }
    catch {
        Write-Host "Error obtaining access token: $($_.Exception.Message)" -ForegroundColor Red
        exit 1
    }
}

# Function to fetch all Autopilot devices
function Get-AutopilotDevices {
    param (
        [string]$AccessToken
    )

    $headers = @{
        'Authorization' = "Bearer $AccessToken"
        'Content-Type'  = 'application/json'
    }

    $devices = @()
    $url = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities"
    do {
        try {
            $response = Invoke-RestMethod -Uri $url -Headers $headers -Method Get
            $devices += $response.value
            $url = $response.'@odata.nextLink'
        }
        catch {
            Write-Host "Error fetching devices: $($_.Exception.Message)" -ForegroundColor Red
            exit 1
        }
    } while ($url)

    return $devices
}

# Function to update device properties
function Update-DeviceProperties {
    param (
        [string]$AccessToken,
        [object]$Device,
        [string]$NewGroupTag
    )

    $headers = @{
        'Authorization' = "Bearer $AccessToken"
        'Content-Type'  = 'application/json'
    }

    $requestBody = @{
        groupTag = $NewGroupTag
    } | ConvertTo-Json

    $updateUrl = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities/$($Device.id)/updateDeviceProperties"

    try {
        Invoke-RestMethod -Uri $updateUrl -Headers $headers -Method Post -Body $requestBody -ContentType 'application/json'
        Write-Host "Successfully updated device: $($Device.displayName) ($($Device.serialNumber))" -ForegroundColor Green
        return $true
    }
    catch {
        $errorResponse = $_.Exception.Response.GetResponseStream()
        if ($errorResponse) {
            $reader = New-Object System.IO.StreamReader($errorResponse)
            $responseBody = $reader.ReadToEnd()
            Write-Host "Error updating device $($Device.displayName): $($_.Exception.Message)" -ForegroundColor Red
            Write-Host "Response Body: $responseBody" -ForegroundColor Yellow
        }
        else {
            Write-Host "Error updating device $($Device.displayName): $($_.Exception.Message)" -ForegroundColor Red
        }
        return $false
    }
}

# Function to trigger Autopilot sync
function Trigger-AutopilotSync {
    param (
        [string]$AccessToken
    )

    $headers = @{
        'Authorization' = "Bearer $AccessToken"
        'Content-Type'  = 'application/json'
    }

    $syncUrl = "https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotSettings/sync"

    try {
        Invoke-RestMethod -Uri $syncUrl -Headers $headers -Method Post
        Write-Host "Autopilot sync successfully triggered." -ForegroundColor Green
    }
    catch {
        $errorResponse = $_.Exception.Response.GetResponseStream()
        if ($errorResponse) {
            $reader = New-Object System.IO.StreamReader($errorResponse)
            $responseBody = $reader.ReadToEnd()
            Write-Host "Error during sync: $($_.Exception.Message)" -ForegroundColor Red
            Write-Host "Response Body: $responseBody" -ForegroundColor Yellow
        }
        else {
            Write-Host "Error during sync: $($_.Exception.Message)" -ForegroundColor Red
        }
    }
}

# Main script execution

# Securely obtain credentials
$tenantId = Read-Host -Prompt "Enter your Tenant ID"
$clientId = Read-Host -Prompt "Enter your Client ID"
$clientSecretSecure = Get-SecureCredential -CredentialName "Client Secret"
$clientSecret = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($clientSecretSecure))

# Get access token
$accessToken = Get-AccessToken -TenantId $tenantId -ClientId $clientId -ClientSecret $clientSecret

# Fetch all Autopilot devices
$autopilotDevices = Get-AutopilotDevices -AccessToken $accessToken

# Check if devices were fetched
if (-not $autopilotDevices) {
    Write-Host "No Autopilot devices found." -ForegroundColor Yellow
    exit 0
}

# Prepare devices for display
$devicesToDisplay = $autopilotDevices | Select-Object -Property id, displayName, model, manufacturer, orderIdentifier, groupTag, enrollmentState, serialNumber

# Show the grid view and allow multiple selection
$selectedDevices = $devicesToDisplay | Out-GridView -OutputMode Multiple -Title "Select devices to update"

# Check if any devices were selected
if (-not $selectedDevices) {
    Write-Host "No devices selected. Exiting script." -ForegroundColor Yellow
    exit 0
}

# Prompt for new group tag with validation
do {
    $newGroupTag = Read-Host -Prompt "Enter the new Group Tag for the selected devices"
    if ([string]::IsNullOrWhiteSpace($newGroupTag)) {
        Write-Host "Group Tag cannot be empty. Please enter a valid Group Tag." -ForegroundColor Yellow
    }
} while ([string]::IsNullOrWhiteSpace($newGroupTag))

# Process selected devices
foreach ($device in $selectedDevices) {
    # Update device properties
    $updateSuccess = Update-DeviceProperties -AccessToken $accessToken -Device $device -NewGroupTag $newGroupTag

    # Optionally, you can choose to break the loop if an update fails
    # if (-not $updateSuccess) { break }
}

# Trigger Autopilot sync after all updates
Trigger-AutopilotSync -AccessToken $accessToken 

				
			

Subscribe to our newsletter

Get the inside scoop! Sign up for our newsletter to stay in the know with all the latest news and updates.

Don’t forget to share this post!

Leave a Comment

Scroll to Top