Fix for incorrectly named devices enrolled using Autopilot through Azure Automation

Ondrej SebelaOndrej Sebela
4 min read

We are using Device name attribute of the Autopilot device record for setting the device hostname during Autopilot enrollment (OOBE). image.png It's convenient and can be used as the single source of truth because this value cannot be changed by your users, unlike the device's real hostname.

This solution has one caveat though and that's Autopilot's well-known unreliability. That can lead to devices with the wrong (a.k.a. default 'DESK-%RAND%') hostname instead of the one you've defined.

This typically happens when Autopilot enrollment is run on a Wi-Fi network or using an external USB ethernet dongle. So in general it is caused by network latency that results in the client not downloading the correct policies from Intune before enrollment continues.

Solution

In general, you can solve this issue with wrongly named clients using Remediation script which would change the device hostname using some hardcoded data (for example CSV with device serial number and corresponding hostname).

But this post is about a more robust, set-once-and-forget solution. We will use Azure Automation Runbook that will:

  • on schedule cycle through all Intune Windows devices
  • for each device finds corresponding Autopilot record using its serial number
  • if current name doesn't match the Autopilot ones, invoke Intune Rename functionality

Some of the code and idea of using Intune built-in rename functionality is based on this blog post ๐Ÿ™.

Requirements

  • permissions to create Azure Automation Account
  • permissions to grant permission to Azure Service principal

How to

Create Azure Automation Account

  1. Log in to your Azure portal
  2. Create new Automation Account image.png
    • my account will be named device-monitoring
  3. Scroll down to Identity section and create System assigned managed identity image.png
    • make a note of this newly created service principal ID as we are going to use it for granting permissions

Grant Graph API permissions to the created Automation Account

  1. Use PowerShell code below to grant required permissions
$servicePrincipalId = '<setThisToManagedIdentityID>'

$resourceAppId = '00000003-0000-0000-c000-000000000000' # graph api
$permissionList = 'Device.ReadWrite.All', 'DeviceManagementServiceConfig.Read.All', 'DeviceManagementManagedDevices.ReadWrite.All', 'DeviceManagementManagedDevices.PrivilegedOperations.All'

$servicePrincipal = (Get-AzureADServicePrincipal -ObjectId $servicePrincipalId)
if (!$servicePrincipal) { throw "Service principal '$servicePrincipalId' doesn't exist" }

# get application whose permissions will be granted
$resourceServicePrincipal = Get-AzureADServicePrincipal -Filter "appId eq '$resourceAppId'"
if (!$resourceServicePrincipal) { throw "Resource '$resourceAppId' doesn't exist" }

# grant requested permissions
foreach ($permission in $permissionList) {
    $AppRole = $resourceServicePrincipal.AppRoles | Where-Object { $_.Value -eq $permission -and $_.AllowedMemberTypes -contains "Application" }
    if (!$AppRole) {
        Write-Warning "Application permission '$permission' wasn't found in '$resourceAppId' application. Therefore it cannot be added."
        continue
    }

    New-AzureADServiceAppRoleAssignment -ObjectId $servicePrincipal.ObjectId -PrincipalId $servicePrincipal.ObjectId -ResourceId $resourceServicePrincipal.ObjectId -Id $AppRole.Id
}

And the result in Azure Portal should look like this image.png

Create a new Runbook

  1. Create a new Runbook image.png
  2. Paste following PowerShell code using edit button image.png
# get authentication token
function Get-AuthToken {
    try {
        # obtain AccessToken for Microsoft Graph via the managed identity
        $ResourceURL = "https://graph.microsoft.com"
        $Response = [System.Text.Encoding]::Default.GetString((Invoke-WebRequest -UseBasicParsing -Uri "$($env:IDENTITY_ENDPOINT)?resource=$resourceURL" -Method 'GET' -Headers @{'X-IDENTITY-HEADER' = "$env:IDENTITY_HEADER"; 'Metadata' = 'True' }).RawContentStream.ToArray()) | ConvertFrom-Json

        # construct AuthHeader
        $AuthHeader = @{
            'Content-Type'  = 'application/json'
            'Authorization' = "Bearer " + $Response.access_token
        }
    } catch {
        throw $_
    }
    return $authHeader
}
$header = Get-AuthToken

$managedDeviceList = $null
$uri = 'https://graph.microsoft.com/v1.0/deviceManagement/managedDevices?$filter=startswith(operatingSystem,''Windows'')'
$managedDeviceList = (Invoke-RestMethod -Uri $uri -Headers $header -ErrorAction Stop).value

$autopilotDeviceList = $null
$uri = 'https://graph.microsoft.com/beta/deviceManagement/windowsAutopilotDeviceIdentities' #?$select=displayName,serialNumber
$autopilotDeviceList = (Invoke-RestMethod -Uri $uri -Headers $header -ErrorAction Stop).value

foreach ($device in $managedDeviceList) {
    $currentDeviceName = $device.deviceName
    $deviceID = $device.id
    $deviceSerial = $device.serialNumber

    if ($device.model -eq "Virtual Machine") {
        "Device $currentDeviceName is a VM. Skipping"
        continue
    }

    $correctDeviceName = $autopilotDeviceList | ? serialNumber -eq $deviceSerial | select -expandProperty displayName

    if (!$correctDeviceName) {
        Write-Error "Unable to get displayName of the $currentDeviceName ($deviceSerial) from the Autopilot database"
        continue
    }

    # Virtual computers have the text "SerialNumber" as serialnumber...
    if ($currentDeviceName -ne $correctDeviceName) {
        $uri = "https://graph.microsoft.com/beta/deviceManagement/managedDevices/$deviceID/setDeviceName"
        $JSONPayload = @{
            "deviceName" = $correctDeviceName
        }
        $convertedJSONPayLoad = $JSONPayload | ConvertTo-Json

        Write-Warning "Renaming $currentDeviceName to $correctDeviceName"
        $null = Invoke-RestMethod -Uri $URI -Method POST -Body $convertedJSONPayLoad -Headers $header -Verbose -ErrorAction Stop
    }
}

Save & publish it. image.png

image.png

Thats it ๐Ÿ‘

You can test your new Runbook using Start button in the top menu.

0
Subscribe to my newsletter

Read articles from Ondrej Sebela directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Ondrej Sebela
Ondrej Sebela

I work as System Administrator for more than 10 years now and I love to make my life easier by automating work & personal stuff via PowerShell (even silly things like food recipes list generation).