automatically register existing device in AutoPilot

In a scenario where you setup and prepare your devices on-prem but Windows-AutoPilot is used to simplify the OOBE part, you can automatically register the device in AutoPilot during initial OS deployment (e.g. before running Sysprep /OOBE)…
As credentials should never be stored in a script that is running on clients, this workflow is using a webhook to trigger a runbook in Azure Automation.
workflow

Requirements

You will need a few components to get this process running:

  • Microsoft Intune
    • Azure AD Application (type: Native)
    • Application must have permission to access Intune from the Graph API
  • Azure Automation Runbook
    • Webhook to trigger the runbook with a simple HTTP Post
  • PowerShell script to get Hardware ID, Serial Number and to trigger the Webhook

Setup Azure Application

In Azure Active Directory, create a new Application (Tab "App registrations") from type "Native" (Redirect URI is not required)

Image Description

This Application is like a "Gateway" to access the Graph API. Therefore, you have to give the Application the required permissions to access Microsoft Graph.
In the Application Settings/Required Permission, add the "Microsoft Graph" API and enable the permissions:

  • Read and write Microsoft Intune Device Configuration and Policies
  • Read and write Microsoft Intune devices

alt

Note: Click on "Grant permissions" to activate your changes!

Once the Application is created and configured, note your "Application ID" as this ID will be used in the next steps...
alt

Setup Azure Automation

In Azure Automation, create a variable (Name: "Intune-Client-Id") to store the ApplicationID:
alt

As the Runbook will run like a service, we must store username and password for an Azure AD account which has rights to access the Azure AD Application.
alt

Note: These credentials are used from the runbook running in Azure and will not be accessible from the clients.

As there are existing modules to access Azure AD and Autopilot, add the following module from the PowerShell gallery:

  • AzureAD is not required !!!
  • WindowsAutoPilotIntune

alt

Azure Automation Runbook:

Create a runbook from type PowerShell with the following script:

Param  
(
    [Parameter (Mandatory = $false)]
    [string] $SerialNumber,

    [Parameter (Mandatory = $false)]
    [string] $HardwareHash,

    [Parameter (Mandatory = $false)]
    [string] $OrderIdentifier = "",

    [Parameter (Mandatory = $false)]
    [object]$WebhookData
)

if ($WebHookData) {

    # Collect properties of WebhookData
    $WebhookName = $WebHookData.WebhookName
    $WebhookHeaders = $WebHookData.RequestHeader
    $WebhookBody = $WebHookData.RequestBody

    $Input = (ConvertFrom-Json -InputObject $WebhookBody)

    $SerialNumber = $Input.SerialNumber
    $HardwareHash = $Input.HardwareHash
    $OrderIdentifier = $Input.OrderIdentifier
}

try {  
    Import-Module -Name WindowsAutoPilotIntune -ErrorAction Stop
}
catch {  
    throw 'Prerequisites not installed (WindowsAutoPilotIntune PowerShell module not installed'
}

#Get stored credentials
$IntuneCredential = Get-AutomationPSCredential -Name Intune
#Get stored ApplicationID
$IntuneClientId = Get-AutomationVariable -Name Intune-Client-Id

#Get the authentication token to access the Application
$Token = Get-MSGraphAuthenticationToken -Credential $IntuneCredential -ClientId $IntuneClientId

#Set global variable authToken, to pass the Token to the WindowsAutoPilotIntune module
$global:authToken = $Token

#Register Device for AutoPilot
$dev = Add-AutoPilotImportedDevice -serialNumber $SerialNumber -hardwareIdentifier $HardwareHash -orderIdentifier $OrderIdentifier

$processingCount = 1
while ($processingCount -gt 0) {  
    $deviceStatuses = Get-AutoPilotImportedDevice -id $dev.id
    $deviceCount = $deviceStatuses.Length
    if (-not $deviceCount -and $deviceStatuses ) { $devicecount = 1}

    # Check to see if any devices are still processing
    $processingCount = 0
    foreach ($device in $deviceStatuses) {
        if ($device.state.deviceImportStatus -eq "unknown") {
            $processingCount = $processingCount + 1
        }
    }
    Write-Output "Waiting for $processingCount of $deviceCount"

    # Still processing?  Sleep before trying again.
    if ($processingCount -gt 0) {
        Start-Sleep 2
    }
}

# Display the statuses
$deviceStatuses | ForEach-Object {
    Write-Output "Serial number $($_.serialNumber): $($_.state.deviceImportStatus) $($_.state.deviceErrorCode) $($_.state.deviceErrorName)"
}

#Cleanup
Remove-AutoPilotImportedDevice -id $dev.id  

Parameters

If the script is triggered from a Webhook, all the parameters are stored in the object WebhookData. For troubleshooting it may be helpful to manually start the runbook. That's why the parameters "SerialNumber", "HardwareHash" and "OrderIdentifier" are optional. If you only run the runbook from a Webhook, you can remove these three Parameters.

Authentication Token

We are using Get-MSGraphAuthenticationToken to get an authentication token for the stored credentials. This token must be set in the GlobalVariable $global:authToken so other functions from the module WindowsAutoPilotIntune can use this token.

Register Device

Add-AutoPilotImportedDevice will now add the HardwareHash, SerialNumber and OrderIdentifier to Autopilot in Intune. It does wait until the device is fully registerred (can take a while) before it cleans the request by using Remove-AutoPilotImportedDevice.

Azure Automation Webhook

Once you have published the runbook, you will get the option to create Webhooks. By using a Webhook, the PowerShell script on the client side will not contain any credentials. The WebHook is just an URL with a secure token.

Image Description

Copy the URL as we need it in the PowerShell script running on the devices.
You have to confirm the parameters page to save and activate the Webhook.

Client side Script

We are now ready to register an existing device (e.g. during unattended setup of Windows10) in Windows Autopilot. Therefore, the script does get the SerialNumber and HardwareHash from WMI and registers the device in AutoPilot.
Change the $uri in the following script with the URL from your Webhook.

#Azure Automation Webhook URI
$uri = "https://s2events.azure-automation.net/webhooks?token=IzxFg%2baOung4RWr8w45SF8WfHRot5RmIwIsnVP9ltkg%3d"

#Get Hardware Hash
$hwid = ((Get-WMIObject -Namespace root/cimv2/mdm/dmmap -Class MDM_DevDetail_Ext01 -Filter "InstanceID='Ext' AND ParentID='./DevDetail'").DeviceHardwareData)
#Get SerialNumber
$ser = (Get-WmiObject win32_bios).SerialNumber
#Use Computername if SerialNumber is empty
if([string]::IsNullOrWhiteSpace($ser)) { $ser = $env:computername}  
$orderIdentifier = "DevCDR"

#Create object with the required parameters
$devdata  = @{ SerialNumber=$ser;HardwareHash=$hwid;OrderIdentifier=$orderIdentifier}
$body = ConvertTo-Json -InputObject $devdata 

#Send request to Webhook
$response = Invoke-RestMethod -Method Post -Uri $uri -Body $body
$response.JobIds

Just make sure that this script runs before the OOBE phase and also note that it may take a long time until the device is fully registered in Autopilot. That's why I recommend to shutdown the device before OOBE runs by using: sysprep.exe /oobe /shutdown.
When the device is powered off, you can ship it to the end-user. The next time the device starts up, OOBE and Autopilot will help the user to finalize his device.

http://www.powershell.no/azure,graph,api/2017/10/30/unattended-ms-graph-api-authentication.html
https://docs.microsoft.com/en-us/azure/automation/automation-webhooks
https://docs.microsoft.com/en-us/windows/deployment/windows-autopilot/windows-10-autopilot
https://blogs.technet.microsoft.com/mniehaus/2017/12/12/gathering-windows-autopilot-hardware-details-from-existing-machines/
https://docs.microsoft.com/en-us/windows/deployment/windows-autopilot/windows-autopilot-requirements-network