AutopilotGroupTagger.ps1
<#PSScriptInfo
.VERSION 0.5 .GUID 63c8809e-5c8a-4ddc-82a4-29706992802f .AUTHOR Nick Benton .COMPANYNAME .COPYRIGHT GPL .TAGS Graph Intune Windows Autopilot GroupTags .LICENSEURI https://github.com/ennnbeee/AutopilotGroupTagger/blob/main/LICENSE .PROJECTURI https://github.com/ennnbeee/AutopilotGroupTagger .ICONURI https://raw.githubusercontent.com/ennnbeee/AutopilotGroupTagger/refs/heads/main/img/agt-icon.png .EXTERNALMODULEDEPENDENCIES Microsoft.Graph.Authentication .REQUIREDSCRIPTS .EXTERNALSCRIPTDEPENDENCIES .RELEASENOTES v0.1 - Initial release v0.2 - Included functionality to update group tags based on Purchase order v0.3 - Updated logic around Autopilot device selection v0.4 - Configured to run on PowerShell 5 v0.4.1 - Updated authentication and module detection v0.4.2 - Bug fixes and improvements v0.4.3 - Improvements to user interface and error handling v0.4.4 - Added 'WhatIf' mode, and updated user experience of output of the progress of Group Tag updates v0.4.5 - Function rework to support PowerShell gallery requirements v0.5 - Now supports PowerShell 7 on macOS, removal of Group Tags, and Dynamic Group creation .PRIVATEDATA #> <# .SYNOPSIS Autopilot GroupTagger - Update Autopilot Device Group Tags in bulk. .DESCRIPTION The Autopilot GroupTagger script is designed to allow for bulk updating of Autopilot device group tags in Microsoft Intune. The script will connect to the Microsoft Graph API and retrieve all Autopilot devices, then allow for bulk updating of group tags based on various criteria. .PARAMETER whatIf Switch to enable WhatIf mode to simulate changes. .PARAMETER createGroups Switch to enable the creation of dynamic groups based on Group Tags. .PARAMETER tenantId Provide the Id of the Entra ID tenant to connect to. .PARAMETER appId Provide the Id of the Entra App registration to be used for authentication. .PARAMETER appSecret Provide the App secret to allow for authentication to graph .EXAMPLE Interactive Authentication .\AutopilotGroupTagger.ps1 .EXAMPLE Pass through Authentication .\AutopilotGroupTagger.ps1 -tenantId '437e8ffb-3030-469a-99da-e5b527908099' .EXAMPLE App Authentication .\AutopilotGroupTagger.ps1 -tenantId '437e8ffb-3030-469a-99da-e5b527908099' -appId '799ebcfa-ca81-4e72-baaf-a35126464d67' -appSecret 'g708Q~uof4xo9dU_1EjGQIuUr0UyBHNZmY2mcdy6' .NOTES Version: 0.5 Author: Nick Benton WWW: oddsandendpoints.co.uk Creation Date: 10/02/2025 #> [CmdletBinding(DefaultParameterSetName = 'Default')] param( [Parameter(Mandatory = $false, HelpMessage = 'Switch to enable the creation of dynamic groups based on Group Tags')] [switch]$createGroups, [Parameter(Mandatory = $false, HelpMessage = 'Provide the Id of the Entra ID tenant to connect to')] [ValidateLength(36, 36)] [String]$tenantId, [Parameter(Mandatory = $false, ParameterSetName = 'appAuth', HelpMessage = 'Provide the Id of the Entra App registration to be used for authentication')] [ValidateLength(36, 36)] [String]$appId, [Parameter(Mandatory = $true, ParameterSetName = 'appAuth', HelpMessage = 'Provide the App secret to allow for authentication to graph')] [ValidateNotNullOrEmpty()] [String]$appSecret, [Parameter(Mandatory = $false, HelpMessage = 'WhatIf mode to simulate changes')] [switch]$whatIf ) #region Functions Function Test-JSON { param ( $JSON ) try { $TestJSON = ConvertFrom-Json $JSON -ErrorAction Stop $TestJSON | Out-Null $validJson = $true } catch { $validJson = $false $_.Exception } if (!$validJson) { Write-Host "Provided JSON isn't in valid JSON format" -ForegroundColor Red break } } Function Connect-ToGraph { <# .SYNOPSIS Authenticates to the Graph API via the Microsoft.Graph.Authentication module. .DESCRIPTION The Connect-ToGraph cmdlet is a wrapper cmdlet that helps authenticate to the Intune Graph API using the Microsoft.Graph.Authentication module. It leverages an Azure AD app ID and app secret for authentication or user-based auth. .PARAMETER TenantId Specifies the tenantId from Entra ID to which to authenticate. .PARAMETER AppId Specifies the Azure AD app ID (GUID) for the application that will be used to authenticate. .PARAMETER AppSecret Specifies the Azure AD app secret corresponding to the app ID that will be used to authenticate. .PARAMETER Scopes Specifies the user scopes for interactive authentication. .EXAMPLE Connect-ToGraph -tenantId $tenantId -appId $app -appSecret $secret -#> [cmdletbinding()] param ( [Parameter(Mandatory = $false)] [string]$tenantId, [Parameter(Mandatory = $false)] [string]$appId, [Parameter(Mandatory = $false)] [string]$appSecret, [Parameter(Mandatory = $false)] [string[]]$scopes ) Process { Import-Module Microsoft.Graph.Authentication $version = (Get-Module microsoft.graph.authentication | Select-Object -ExpandProperty Version).major if ($AppId -ne '') { $body = @{ grant_type = 'client_credentials'; client_id = $appId; client_secret = $appSecret; scope = 'https://graph.microsoft.com/.default'; } $response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -Body $body $accessToken = $response.access_token if ($version -eq 2) { Write-Host 'Version 2 module detected' $accessTokenFinal = ConvertTo-SecureString -String $accessToken -AsPlainText -Force } else { Write-Host 'Version 1 Module Detected' Select-MgProfile -Name Beta $accessTokenFinal = $accessToken } $graph = Connect-MgGraph -AccessToken $accessTokenFinal Write-Host "Connected to Intune tenant $TenantId using app-based authentication (Azure AD authentication not supported)" } else { if ($version -eq 2) { Write-Host 'Version 2 module detected' } else { Write-Host 'Version 1 Module Detected' Select-MgProfile -Name Beta } $graph = Connect-MgGraph -Scopes $scopes -TenantId $tenantId Write-Host "Connected to Intune tenant $($graph.TenantId)" } } } Function Get-AutopilotDevice() { <# .SYNOPSIS This function is used to get autopilot devices via the Graph API REST interface .DESCRIPTION The function connects to the Graph API Interface and gets any autopilot devices .EXAMPLE Get-AutopilotDevice Returns any autopilot devices .NOTES NAME: Get-AutopilotDevice #> $graphApiVersion = 'Beta' $Resource = 'deviceManagement/windowsAutopilotDeviceIdentities' try { $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" $graphResults = Invoke-MgGraphRequest -Uri $uri -Method Get $results = @() $results += $graphResults.value $pages = $graphResults.'@odata.nextLink' while ($null -ne $pages) { $additional = Invoke-MgGraphRequest -Uri $pages -Method Get if ($pages) { $pages = $additional.'@odata.nextLink' } $results += $additional.value } $results } catch { Write-Error $_.Exception.Message break } } Function Set-AutopilotDevice() { <# .SYNOPSIS This function is used to set autopilot devices properties via the Graph API REST interface .DESCRIPTION The function connects to the Graph API Interface and sets autopilot device properties .EXAMPLE Set-AutopilotDevice Returns any autopilot devices .NOTES NAME: Set-AutopilotDevice #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'high')] param( [Parameter(Mandatory = $true)] $Id, [Parameter(Mandatory = $true)] $groupTag ) process { $graphApiVersion = 'Beta' $Resource = "deviceManagement/windowsAutopilotDeviceIdentities/$Id/updateDeviceProperties" if ($PSCmdlet.ShouldProcess('Autopilot Device', 'Update')) { try { $Autopilot = New-Object -TypeName psobject $Autopilot | Add-Member -MemberType NoteProperty -Name 'groupTag' -Value $groupTag $JSON = $Autopilot | ConvertTo-Json -Depth 3 # POST to Graph Service $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" Invoke-MgGraphRequest -Uri $uri -Method Post -Body $JSON -ContentType 'application/json' } catch { Write-Error $_.Exception.Message break } } elseif ($WhatIfPreference.IsPresent) { #On a Whatif we return the full splat we would have used to call Write-Output "Autopilot Device $Id would have been updated with Group Tag $groupTag" } else { Write-Output "Autopilot Device $Id was not updated with Group Tag $groupTag" } } } Function Get-EntraIDObject() { [CmdletBinding(DefaultParameterSetName = 'Default')] param ( [parameter(Mandatory = $false)] [switch]$user, [parameter(Mandatory = $false, ParameterSetName = 'devices')] [switch]$device, [parameter(Mandatory = $true, ParameterSetName = 'devices')] [ValidateSet('Windows', 'iOS', 'Android', 'macOS')] [string]$os ) $graphApiVersion = 'beta' if ($user) { $Resource = "users?`$filter=userType eq 'member' and accountEnabled eq true" } elseif ($device) { switch ($os) { 'iOS' { $Resource = "devices?`$filter=operatingSystem eq 'iOS'" } 'Android' { $Resource = "devices?`$filter=operatingSystem eq 'Android'" } 'macOS' { $Resource = "devices?`$filter=operatingSystem eq 'macOS'" } 'Windows' { $Resource = "devices?`$filter=operatingSystem eq 'Windows'" } } } try { $uri = "https://graph.microsoft.com/$graphApiVersion/$Resource" $graphResults = Invoke-MgGraphRequest -Uri $uri -Method Get $results = @() $results += $graphResults.value $pages = $graphResults.'@odata.nextLink' while ($null -ne $pages) { $additional = Invoke-MgGraphRequest -Uri $pages -Method Get if ($pages) { $pages = $additional.'@odata.nextLink' } $results += $additional.value } $results } catch { Write-Error $Error[0].ErrorDetails.Message break } } Function Get-ManagedDevice() { [cmdletbinding()] param ( [parameter(Mandatory = $true)] [ValidateSet('Windows', 'iOS', 'Android', 'macOS')] [string]$os ) $graphApiVersion = 'beta' switch ($os) { 'iOS' { $Resource = "deviceManagement/managedDevices?`$filter=operatingSystem eq 'iOS'" } 'Android' { $Resource = "deviceManagement/managedDevices?`$filter=operatingSystem eq 'Android'" } 'macOS' { $Resource = "deviceManagement/managedDevices?`$filter=operatingSystem eq 'macOS'" } 'Windows' { $Resource = "deviceManagement/managedDevices?`$filter=operatingSystem eq 'Windows'" } } try { $uri = "https://graph.microsoft.com/$graphApiVersion/$Resource" $graphResults = Invoke-MgGraphRequest -Uri $uri -Method Get $results = @() $results += $graphResults.value $pages = $graphResults.'@odata.nextLink' while ($null -ne $pages) { $additional = Invoke-MgGraphRequest -Uri $pages -Method Get if ($pages) { $pages = $additional.'@odata.nextLink' } $results += $additional.value } $results } catch { Write-Error $Error[0].ErrorDetails.Message break } } Function Get-MDMGroup() { [cmdletbinding()] param ( [parameter(Mandatory = $true)] [string]$groupName ) $graphApiVersion = 'beta' $Resource = 'groups' try { $searchTerm = 'search="displayName:' + $groupName + '"' $uri = "https://graph.microsoft.com/$graphApiVersion/$Resource`?$searchTerm" (Invoke-MgGraphRequest -Uri $uri -Method Get -Headers @{ConsistencyLevel = 'eventual' }).Value } catch { Write-Error $_.Exception.Message break } } Function New-MDMGroup() { [cmdletbinding()] param ( [Parameter(Mandatory = $true)] $JSON ) $graphApiVersion = 'beta' $Resource = 'groups' try { Test-Json -Json $JSON $uri = "https://graph.microsoft.com/$graphApiVersion/$($Resource)" Invoke-MgGraphRequest -Uri $uri -Method Post -Body $JSON -ContentType 'application/json' } catch { Write-Error $_.Exception.Message break } } Function Read-YesNoChoice { <# .SYNOPSIS Prompt the user for a Yes No choice. .DESCRIPTION Prompt the user for a Yes No choice and returns 0 for no and 1 for yes. .PARAMETER Title Title for the prompt .PARAMETER Message Message for the prompt .PARAMETER DefaultOption Specifies the default option if nothing is selected .INPUTS None. You cannot pipe objects to Read-YesNoChoice. .OUTPUTS Int. Read-YesNoChoice returns an Int, 0 for no and 1 for yes. .EXAMPLE PS> $choice = Read-YesNoChoice -Title "Please Choose" -Message "Yes or No?" Please Choose Yes or No? [N] No [Y] Yes [?] Help (default is "N"): y PS> $choice 1 .EXAMPLE PS> $choice = Read-YesNoChoice -Title "Please Choose" -Message "Yes or No?" -DefaultOption 1 Please Choose Yes or No? [N] No [Y] Yes [?] Help (default is "Y"): PS> $choice 1 .LINK Online version: https://www.chriscolden.net/2024/03/01/yes-no-choice-function-in-powershell/ #> Param ( [Parameter(Mandatory = $true)][String]$Title, [Parameter(Mandatory = $true)][String]$Message, [Parameter(Mandatory = $false)][Int]$DefaultOption = 0 ) $No = New-Object System.Management.Automation.Host.ChoiceDescription '&No', 'No' $Yes = New-Object System.Management.Automation.Host.ChoiceDescription '&Yes', 'Yes' $Options = [System.Management.Automation.Host.ChoiceDescription[]]($No, $Yes) return $host.ui.PromptForChoice($Title, $Message, $Options, $DefaultOption) } #endregion Functions #region intro Write-Host ' _______ __ __ __ __ | _ |.--.--.| |_.-----.-----.|__| |.-----.| |_ | || | || _| _ | _ || | || _ || _| |___|___||_____||____|_____| __||__|__||_____||____| |__| ' -ForegroundColor Cyan Write-Host ' _______ _______ | __|.----.-----.--.--.-----.|_ _|.---.-.-----.-----.-----.----. | | || _| _ | | | _ | | | | _ | _ | _ | -__| _| |_______||__| |_____|_____| __| |___| |___._|___ |___ |_____|__| |__| |_____|_____| ' -ForegroundColor Red Write-Host 'Autopilot GroupTagger - Update Autopilot Device Group Tags in bulk.' -ForegroundColor Green Write-Host 'Nick Benton - oddsandendpoints.co.uk' -NoNewline; Write-Host ' | Version' -NoNewline; Write-Host ' 0.5 Public Preview' -ForegroundColor Yellow -NoNewline Write-Host ' | Last updated: ' -NoNewline; Write-Host '2025-02-10' -ForegroundColor Magenta Write-Host '' Write-Host 'If you have any feedback, please open an issue at https://github.com/ennnbeee/AutopilotGroupTagger/issues' -ForegroundColor Cyan Write-Host '' #endregion intro #region variables $groupPrefix = 'AGT-Autopilot-' $requiredScopes = @('Device.Read.All', 'DeviceManagementServiceConfig.ReadWrite.All', 'DeviceManagementManagedDevices.Read.All', 'Group.ReadWrite.All') [String[]]$scopes = $requiredScopes -join ', ' $rndWait = Get-Random -Minimum 1 -Maximum 2 #endregion variables #region module check if ($PSVersionTable.PSVersion.Major -eq 7) { $modules = @('Microsoft.Graph.Authentication', 'Microsoft.PowerShell.ConsoleGuiTools') } else { $modules = @('Microsoft.Graph.Authentication') } foreach ($module in $modules) { Write-Host "Checking for $module PowerShell module..." -ForegroundColor Cyan Write-Host '' If (!(Get-Module -Name $module -ListAvailable)) { Install-Module -Name $module -Scope CurrentUser -AllowClobber } Write-Host "PowerShell Module $module found." -ForegroundColor Green Write-Host '' if (!([System.AppDomain]::CurrentDomain.GetAssemblies() | Where-Object FullName -Like "*$module*")) { Import-Module -Name $module -Force } } #endregion module check #region app auth try { if (!$tenantId) { Write-Host 'Connecting using interactive authentication' -ForegroundColor Yellow Connect-MgGraph -Scopes $scopes -NoWelcome -ErrorAction Stop } else { if ((!$appId -and !$appSecret) -or ($appId -and !$appSecret) -or (!$appId -and $appSecret)) { Write-Host 'Missing App Details, connecting using user authentication' -ForegroundColor Yellow Connect-ToGraph -tenantId $tenantId -Scopes $scopes -ErrorAction Stop } else { Write-Host 'Connecting using App authentication' -ForegroundColor Yellow Connect-ToGraph -tenantId $tenantId -appId $appId -appSecret $appSecret -ErrorAction Stop } } $context = Get-MgContext Write-Host '' Write-Host "Successfully connected to Microsoft Graph tenant $($context.TenantId)." -ForegroundColor Green } catch { Write-Error $_.Exception.Message Exit } #endregion app auth #region scopes $currentScopes = $context.Scopes # Validate required permissions $missingScopes = $requiredScopes | Where-Object { $_ -notin $currentScopes } if ($missingScopes.Count -gt 0) { Write-Host 'WARNING: The following scope permissions are missing:' -ForegroundColor Red $missingScopes | ForEach-Object { Write-Host " - $_" -ForegroundColor Yellow } Write-Host '' Write-Host 'Please ensure these permissions are granted to the app registration for full functionality.' -ForegroundColor Yellow exit } Write-Host '' Write-Host 'All required scope permissions are present.' -ForegroundColor Green #endregion scopes #region discovery Start-Sleep -Seconds 2 # Delay to allow for Graph API to catch up Write-Host '' Write-Host 'Getting all Entra ID Windows computer objects...' -ForegroundColor Cyan $entraDevices = Get-EntraIDObject -device -os Windows $entraDevicesHash = @{} foreach ($entraDevice in $entraDevices) { $entraDevicesHash[$entraDevice.deviceid] = $entraDevice } Write-Host "Found $($entraDevices.Count) Windows devices and associated IDs from Entra ID." -ForegroundColor Green Write-Host '' Write-Host 'Getting all Windows Intune devices...' -ForegroundColor Cyan $intuneDevices = Get-ManagedDevice -os Windows $intuneDevicesHash = @{} foreach ($intuneDevice in $intuneDevices) { $intuneDevicesHash[$intuneDevice.id] = $intuneDevice } Write-Host "Found $($intuneDevices.Count) Windows device objects and associated IDs from Microsoft Intune." -ForegroundColor Green Write-Host '' Write-Host 'Getting all Windows Autopilot devices...' -ForegroundColor Cyan $apDevices = Get-AutopilotDevice $autopilotDevices = @() foreach ($apDevice in $apDevices) { # Details of Entra ID device object $entraObject = $entraDevicesHash[$apDevice.azureAdDeviceId] # Details of Intune device object #$intuneObject = $intuneDevicesHash[$apDevice.managedDeviceId] $autopilotDevices += [PSCustomObject]@{ 'displayName' = $entraObject.displayName 'serialNumber' = $apDevice.serialNumber 'manufacturer' = $apDevice.manufacturer 'model' = $apDevice.model 'enrolmentState' = $apDevice.enrollmentState 'enrolmentProfile' = $entraObject.enrollmentProfileName 'enrolmentType' = $entraObject.enrollmentType 'groupTag' = $apDevice.groupTag 'purchaseOrder' = $apDevice.purchaseOrderIdentifier 'Id' = $apDevice.Id } } $autopilotDevicesHash = @{} foreach ($autopilotDevice in $autopilotDevices) { $autopilotDevicesHash[$autopilotDevice.id] = $autopilotDevice } Write-Host "Found $($autopilotDevices.Count) Windows Autopilot Devices from Microsoft Intune." -ForegroundColor Green #endregion discovery #region choices $autopilotUpdateDevices = @() while ($autopilotUpdateDevices.Count -eq 0) { if ($whatIf) { Write-Host '' Write-Host 'WhatIf mode enabled, no changes will be made.' -ForegroundColor Magenta } if ($createGroups) { Write-Host '' Write-Host 'Dynamic Groups will be created based on Group Tags' -ForegroundColor Magenta } Write-Host '' Write-Host 'Please Choose one of the Group Tag options below: ' -ForegroundColor Magenta Write-Host '' Write-Host ' (1) Update All Autopilot Devices Group Tags' Write-Host '' Write-Host ' (2) Update All Autopilot Devices with Empty Group Tags' Write-Host '' Write-Host ' (3) Update All Autopilot Devices with a specific Group Tag' Write-Host '' Write-Host ' (4) Update All selected Manufacturers of Autopilot Device Group Tags' Write-Host '' Write-Host ' (5) Update All selected Models of Autopilot Device Group Tags' Write-Host '' Write-Host ' (6) Update All Autopilot Devices with a specific Purchase Order' Write-Host '' Write-Host ' (7) Update a selection of Autopilot Devices Group Tags interactively' Write-Host '' Write-Host ' (8) Update Autopilot Devices Group Tags using exported data' Write-Host '' Write-Host ' (E) EXIT SCRIPT ' -ForegroundColor Red Write-Host '' $choice = '' $autopilotUpdateDevices = @() $choice = Read-Host -Prompt 'Please select an option from the provided list, then press enter' while ( $choice -notin @('1', '2', '3', '4', '5', '6', '7', '8', 'E')) { $choice = Read-Host -Prompt 'Please select an option from the provided list, then press enter' } if ($choice -eq 'E') { Exit } if ($choice -eq '1') { #All AutoPilot Devices $autopilotUpdateDevices = $autopilotDevices } if ($choice -eq '2') { #All AutoPilot Devices with Empty Group Tags $autopilotUpdateDevices = $autopilotDevices | Where-Object { ($null -eq $_.groupTag) -or ($_.groupTag) -eq '' } if ($autopilotUpdateDevices.count -eq 0) { Write-Host Write-Host 'No Autopilot Devices with Empty Group Tags found.' -ForegroundColor Yellow Write-Host Write-Host 'Please select another option.' -ForegroundColor Yellow Write-Host continue } } if ($choice -eq '3') { # GroupTag prompts $confirmGroupTags = 0 while ($confirmGroupTags -ne 1) { while ($autopilotGroupTags.count -eq 0) { if ($PSVersionTable.PSVersion.Major -eq 7) { $autopilotGroupTags = @($autopilotDevices | Select-Object -Property groupTag -Unique | Out-ConsoleGridView -Title 'Select GroupTags of Autopilot Devices to Update' -OutputMode Multiple) } else { $autopilotGroupTags = @($autopilotDevices | Select-Object -Property groupTag -Unique | Out-GridView -PassThru -Title 'Select GroupTags of Autopilot Devices to Update') } } Write-Host '' Write-Host 'The following Group Tag(s) were selected:' -ForegroundColor Cyan Write-Host '' $autopilotGroupTags.groupTag Write-Host '' $confirmGroupTags = Read-YesNoChoice -Title 'Please confirm Group Tag(s) selection' -Message 'Are these the correct Group Tag(s) to update?' -DefaultOption 1 if ($confirmGroupTags -eq 0) { Write-Host '' Write-Host 'Please re-select the Group Tags to update' -ForegroundColor Yellow $autopilotGroupTags = $null } $autopilotUpdateDevices = $autopilotDevices | Where-Object { $_.groupTag -in $autopilotGroupTags.groupTag } } } if ($choice -eq '4') { # Manufacturer prompts $confirmManufacturers = 0 while ($confirmManufacturers -ne 1) { while ($autopilotManufacturers.count -eq 0) { if ($PSVersionTable.PSVersion.Major -eq 7) { $autopilotManufacturers = @($autopilotDevices | Select-Object -Property manufacturer -Unique | Out-ConsoleGridView -Title 'Select Manufacturer of Autopilot Devices to Update' -OutputMode Multiple) } Else { $autopilotManufacturers = @($autopilotDevices | Select-Object -Property manufacturer -Unique | Out-GridView -PassThru -Title 'Select Manufacturer of Autopilot Devices to Update') } } Write-Host '' Write-Host 'The following Autopilot Device Manufacturer(s) were selected:' -ForegroundColor Cyan Write-Host '' $autopilotManufacturers.manufacturer Write-Host '' $confirmManufacturers = Read-YesNoChoice -Title 'Please confirm the Autopilot Device Manufacturer(s)' -Message 'Are these the correct Manufacturer(s) to update?' -DefaultOption 1 if ($confirmManufacturers -eq 0) { Write-Host '' Write-Host 'Please re-select the Manufacturer(s) to update' -ForegroundColor Yellow $autopilotManufacturers = $null } $autopilotUpdateDevices = $autopilotDevices | Where-Object { $_.manufacturer -in $autopilotManufacturers.manufacturer } } } if ($choice -eq '5') { # Model prompts $confirmModels = 0 while ($confirmModels -ne 1) { while ($autopilotModels.count -eq 0) { if ($PSVersionTable.PSVersion.Major -eq 7) { $autopilotModels = @($autopilotDevices | Select-Object -Property model -Unique | Out-ConsoleGridView -Title 'Select Models of Autopilot Devices to Update' -OutputMode Multiple) } Else { $autopilotModels = @($autopilotDevices | Select-Object -Property model -Unique | Out-GridView -PassThru -Title 'Select Models of Autopilot Devices to Update') } } Write-Host '' Write-Host 'The following Autopilot Device Model(s) were selected:' -ForegroundColor Cyan Write-Host '' $autopilotModels.model Write-Host '' $confirmModels = Read-YesNoChoice -Title 'Please confirm the Autopilot Device Model(s)' -Message 'Are these the correct Model(s) to update?' -DefaultOption 1 if ($confirmModels -eq 0) { Write-Host '' Write-Host 'Please re-select the Models to update' -ForegroundColor Yellow $autopilotModels = $null } $autopilotUpdateDevices = $autopilotDevices | Where-Object { $_.model -in $autopilotModels.model } } } if ($choice -eq '6') { # Purchase Order prompts $confirmPOs = 0 while ($confirmPOs -ne 1) { while ($autopilotPOs.count -eq 0) { if ($PSVersionTable.PSVersion.Major -eq 7) { $autopilotPOs = @($autopilotDevices | Select-Object -Property purchaseOrder -Unique | Out-ConsoleGridView -Title 'Select Purchase Order of Autopilot Devices to Update' -OutputMode Multiple) } else { $autopilotPOs = @($autopilotDevices | Select-Object -Property purchaseOrder -Unique | Out-GridView -PassThru -Title 'Select Purchase Order of Autopilot Devices to Update') } } Write-Host '' Write-Host 'The following Autopilot Device Purchase Order(s) were selected:' -ForegroundColor Cyan Write-Host '' $autopilotPOs.purchaseOrder Write-Host '' $confirmPOs = Read-YesNoChoice -Title 'Please confirm Autopilot Device Purchase Order(s)' -Message 'Are these the correct Purchase Order(s) to update?' -DefaultOption 1 if ($confirmPOs -eq 0) { Write-Host '' Write-Host 'Please re-select the Purchase Order(s) to update' -ForegroundColor Yellow $autopilotPOs = $null } $autopilotUpdateDevices = $autopilotDevices | Where-Object { $_.purchaseOrder -in $autopilotPOs.purchaseOrder } } } if ($choice -eq '7') { while ($autopilotUpdateDevices.count -eq 0) { if ($PSVersionTable.PSVersion.Major -eq 7) { $autopilotUpdateDevices = @($autopilotDevices | Out-ConsoleGridView -Title 'Select Autopilot Devices to Update' -OutputMode Multiple) } else { $autopilotUpdateDevices = @($autopilotDevices | Out-GridView -PassThru -Title 'Select Autopilot Devices to Update') } } } if ($choice -eq '8') { # Report $autopilotDevices | Export-Csv -Path '.\AutopilotDevices.csv' -NoTypeInformation -Force Write-Host '' Write-Host 'Exported All Autopilot Device(s) to AutopilotDevices.csv' -ForegroundColor Cyan while ($autopilotUpdateDevices.count -eq 0 -or ($autopilotUpdateDevices.groupTag | Measure-Object -Maximum).Maximum.length -gt 512) { Write-Host '' if (($autopilotUpdateDevices.groupTag | Measure-Object -Maximum).Maximum.length -gt 512) { Write-Host 'One or more Group Tags are greater than 512 characters.' -ForegroundColor Red Write-Host '' } Write-Warning -Message 'Please update the Group Tags on device(s) in AutopilotDevices.csv and save the file before continuing' -WarningAction Inquire $autopilotImportDevices = Import-Csv -Path .\AutopilotDevices.csv $autopilotUpdateDevices = @() foreach ($autopilotImportDevice in $autopilotImportDevices) { $apObject = $autopilotDevicesHash[$autopilotImportDevice.Id] if ($autopilotImportDevice.groupTag -ne $apObject.groupTag) { $autopilotUpdateDevices += $autopilotImportDevice } } } } } #endregion choices #region group tag prompt if ($choice -ne '8') { #group tags have a maximum of 512 characters $confirmGroupTag = 0 while ($confirmGroupTag -ne 1) { Write-Host 'Press Enter to select an empty Group Tag value which will remove the Group Tag from the Autopilot Device(s).' -ForegroundColor Yellow Write-Host '' [string]$groupTagNew = Read-Host "Please enter the *NEW* group tag you wish to apply to the $($autopilotUpdateDevices.Count) Autopilot device(s)" while ($groupTagNew.length -gt 512) { [string]$groupTagNew = Read-Host "Please enter the *NEW* group tag you wish to apply to the $($autopilotUpdateDevices.Count) Autopilot device(s) but with less than 512 characters" } Write-Host '' Write-Host 'The following Autopilot Device Group Tag was entered:' -ForegroundColor Cyan Write-Host '' $groupTagNew if ($groupTagNew -eq '' -or $null -eq $groupTagNew) { Write-Host 'An empty Group Tag value will remove the Group Tag from the Autopilot Device(s).' -ForegroundColor red Write-Host '' } $confirmGroupTag = Read-YesNoChoice -Title 'Please confirm the Autopilot Device Group Tag' -Message 'Is this the correct Group Tag to use?' -DefaultOption 1 if ($confirmGroupTag -eq 0) { Write-Host '' Write-Host 'Please re-enter a *NEW* Group Tag' -ForegroundColor Yellow Write-Host '' $groupTagNew = $null } } } #endregion group tag prompt #region group tag update Write-Host '' Write-Host "The following $($autopilotUpdateDevices.Count) Autopilot device(s) are in scope to be updated:" -ForegroundColor Yellow $autopilotUpdateDevices | Format-Table -Property displayName, serialNumber, manufacturer, model, purchaseOrder -AutoSize if ($whatIf) { Write-Host '' Write-Host 'WhatIf mode enabled, no changes will be made.' -ForegroundColor Magenta } Write-Warning -Message "You are about to update the group tag(s) for $($autopilotUpdateDevices.Count) Autopilot device(s)." -WarningAction Inquire $progressCount = 0 $progressTotal = $($autopilotUpdateDevices.Count) $progressActivity = 'Updating Autopilot Group Tags' $Host.PrivateData.ProgressBackgroundColor = $Host.UI.RawUI.BackgroundColor $host.PrivateData.ProgressForegroundColor = 'green' Write-Progress -Activity $progressActivity -Status 'Starting' -PercentComplete 0 foreach ($autopilotUpdateDevice in $autopilotUpdateDevices) { Start-Sleep -Seconds $rndWait if ($choice -eq '8') { $groupTagNew = $($autopilotUpdateDevice.groupTag) } #Write-Host "Updating Autopilot Group Tag with Serial Number: $($autopilotUpdateDevice.serialNumber) to '$groupTagNew'." -ForegroundColor Cyan $progressCount++ $progressComplete = (($progressCount / $progressTotal) * 100) $progressStatus = "Group Tag: '$groupTagNew' - Device Serial Number: $($autopilotUpdateDevice.serialNumber)" Write-Progress -Activity $progressActivity -Status $progressStatus -PercentComplete $progressComplete if (!$whatIf) { Set-AutopilotDevice -id $autopilotUpdateDevice.id -groupTag $groupTagNew -Confirm:$false } #Write-Host "Updated Autopilot Group Tag with Serial Number: $($autopilotUpdateDevice.serialNumber) to '$groupTagNew'." -ForegroundColor Green } Write-Host '' Write-Progress -Activity $progressActivity -Status 'Complete' -PercentComplete 100 Write-Host "Successfully updated $($autopilotUpdateDevices.Count) Autopilot device(s) with the new group tag(s)" -ForegroundColor Green #endregion group tag update #region Group Creation if ($createGroups) { $groupTagsArray = @() if ($choice -eq '8') { foreach ($autopilotUpdateDevice in $autopilotUpdateDevices) { $groupTagsArray += $($autopilotUpdateDevice.groupTag) } } else { $groupTagsArray += $groupTagNew } $groupTagsArray = $groupTagsArray | Select-Object -Unique $groupsArray = @() foreach ($groupTagArray in $groupTagsArray) { $groupRule = "(device.devicePhysicalIds -any _ -eq `\`"[OrderID]:$groupTagArray`\`")" $groupsArray += [pscustomobject]@{displayName = "$($groupPrefix + $groupTagArray)"; description = "All Autopilot Devices with Group Tag '$groupTagArray' created by AutopilotGroupTagger"; rule = "$groupRule" } } Write-Host '' Write-Host "The following $($groupsArray.Count) group(s) will be created:" -ForegroundColor Yellow Write-Host '' $groupsArray | Select-Object -Property displayName, rule, description | Format-Table -AutoSize Write-Warning -Message "You are about to create $($groupsArray.Count) new group(s) in Microsoft Entra ID. Please confirm you want to continue." -WarningAction Inquire foreach ($group in $groupsArray) { Start-Sleep -Seconds $rndWait $groupName = $($group.displayName) if ($groupName.length -gt 120) { #shrinking group name to less than 120 characters $groupName = $groupName[0..120] -join '' } if (!(Get-MDMGroup -groupName $groupName)) { Write-Host '' Write-Host "Creating Group $groupName with rule $($group.rule)" -ForegroundColor Cyan $groupJSON = @" { "description": "$($group.description)", "displayName": "$groupName", "groupTypes": [ "DynamicMembership" ], "mailEnabled": false, "mailNickname": "$groupName", "securityEnabled": true, "membershipRule": "$($group.rule)", "membershipRuleProcessingState": "On" } "@ if ($whatIf) { Write-Host 'WhatIf mode enabled, no changes will be made.' -ForegroundColor Magenta continue } else { New-MDMGroup -JSON $groupJSON | Out-Null } Write-Host "Group $($group.displayName) created successfully." -ForegroundColor Green Write-Host '' } else { Write-Host "Group $($group.displayName) already exists, skipping creation." -ForegroundColor Yellow Write-Host '' continue } } Write-Host "Successfully created $($groupsArray.Count) new group(s) in Microsoft Entra ID." -ForegroundColor Green } #endregion Group Creation |