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:
Nicola Script:
https://tech.nicolonsky.ch/bulk-update-windows-autopilot/Michael Niehaus Post
Using a well-known Intune app ID for access to Graph:
https://oofhours.com/2024/03/29/using-a-well-known-intune-app-id-for-access-to-graph-not-for-much-longer/- More info on GitHub:
https://github.com/microsoftgraph/powershell-intune-samples/blob/9d0dac47b1058584e1026119d4fd7f635eb446d5/Readme.md?plain=1#L22
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:
- Authentication: Obtains an access token using client credentials.
- Device Retrieval: Fetches all Windows Autopilot devices using the Microsoft Graph API.
- Device Selection: Displays devices in an interactive grid view for selection.
- Group Tag Update: Prompts once for a new Group Tag and updates all selected devices.
- 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:
- Navigate to the Azure Portal and go to Azure Active Directory > App registrations.
- Click New registration.
- Enter a name (e.g., “AutopilotDeviceManager”) and set the account type to Accounts in this organizational directory only.
- Click Register.
2. Grant API Permissions
Assign the necessary permissions to the application:
- In the app’s overview, go to API permissions.
- Click Add a permission > Microsoft Graph > Application permissions.
- Search for and add the following permissions:
DeviceManagementServiceConfig.ReadWrite.All
DeviceManagementManagedDevices.ReadWrite.All
- 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
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