Process automation for Intune and Azure AD with Azure Automation

IntuneAndAzureAutomationCloud managed environments benefit from the idea of software as a service, you don’t have to think about upgrading or maintenance of the infrastructure itself. But often we need to automate the tools itself. A very good example here is when an employee quits his job, than we need to trigger a lot of processes like disabling the account, retire of the device(s), wiping of the devices, sending some notes to various people and so on. Another example might be the cleanup of devices within Intune and Azure AD as they get stale over time and they are not used by users anymore.



In the following blog post I like to show how to automate the process to delete old devices from Intune and Azure AD without the help of services from on-premises like servers running scheduled scripts. The established cloud workflow can be used by the service desk to quickly delete a device in both involved services Intune and AAD. After seeing a lot of environments where devices are being cleaned up in Intune and left in AAD, I thought its beneficial to show how to easily automate this with the Microsoft cloud solution Azure Automation. If the basics are built it’s just a matter of combining new tasks within a Runbook to build other workflows which are worthwhile in your environment.

I will show how to setup the Azure environment and create the first Runbook. A Runbook is the actual workflow which runs the PowerShell script. The Runbook will do an unattended authentication against the Inunte API via Microsoft Graph to manage Intune. We do not have a PowerShell module for Intune at the time of writing therefore we use the Intune API in Microsoft Graph. For the AAD operations we use the AzureAD module to perform the management tasks.


How to do unattended authentication with the Intune API?

The problem with the Intune API and Microsoft Graph is, that we can’t authenticate as an application as this is not supported at the time of writing. See section here Intune Device Management permissions > Application permissions: None.

We need to authenticate as an user (service account). This requires additional credentials and a secure storage of them to automate. Microsoft has a good guide how to set up an Azure application to support this scenario: How to use Azure AD to access the Intune APIs in Microsoft Graph. One aspect is that the Microsoft How-To guide will end up in a scenario which still prompts for credentials with a input form. This is because of the usage of:


For Azure Automation we need to change this behavior a bit to support credentials within our code:


We can use the How-To guide or the official GitHub Intune sample scripts which have the following lines of code:

$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
$userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($User, "OptionalDisplayableId")
$authResult = $authContext.AcquireTokenAsync($resourceAppIdURI, $clientId, $redirectUri, $platformParameters, $userId).Result

they need to be changed to support our new AcquireTokenAsync call with support to specify UserPasswordCredentials as additional parameter:

$intuneAutomationCredential = Get-AutomationPSCredential -Name automation

$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" -ArgumentList $authority
$platformParameters = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.PlatformParameters" -ArgumentList "Auto"
$userId = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.UserIdentifier" -ArgumentList ($intuneAutomationCredential.Username, "OptionalDisplayableId")
$userCredentials = New-Object Microsoft.IdentityModel.Clients.ActiveDirectory.UserPasswordCredential -ArgumentList $intuneAutomationCredential.Username, $intuneAutomationCredential.Password
$authResult = [Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContextIntegratedAuthExtensions]::AcquireTokenAsync($authContext, $resourceAppIdURI, $intuneAutomationAppId, $userCredentials);

The credentials will be received from the Azure Automation account in PowerShell via Get-AutomationPSCredential. We will provision the service account credentials securely for the Azure Automation account via Credential assets.


Building the solution

The following steps must be followed to build the solution:

  1. Creation of a native application in Azure AD
  2. Assigning permissions to the registered application
  3. Grant permissions (administrator consent)
  4. Create Azure Automation Account
  5. Add Azure AD module to the Azure Automation Account
  6. Add credentials to the Azure Automation account
  7. Add variables to the Azure Automation account
  8. Add Runbook to the Azure Automation account
  9. Edit Runbook
  10. Start and test Runbook
  11. Add Automation Operator


1. Creation of a native application in Azure AD

The best description for a native application is found in the Intune documentation for the Intune API here: How to use Azure AD to access the Intune APIs in Microsoft Graph. I will outline the necessary steps to setup the environment.

New application registration


fill out the details and give it a name, create a native application with redirect URI: urn:ietf:wg:oauth:2.0:oob


in the end a new registered application is available. Important is to copy the application id as we need it in our PowerShell script Runbook later.



2. Assigning permissions to the registered application

The registered application must have AAD Read and Write permissions, and Intune Read and Write permissions.






3. Grant permissions (administrator consent)

Finally we grant the selected permissions to the newly registered application.




4. Create Azure Automation Account

Creation of the Azure Automation Account in a existing or new resource group.




5. Add Azure AD module to the Azure Automation Account

To have access to AzuerAD module we add it via the Gallery, choose Browse Gallery




6. Add credentials to the Azure Automation account

Go to Azure AD and create a new user, in my case user automation with Display Name Intune Automation and use a complex password for it.


At the moment we need to assign the Global Administrator role as we want to delete devices in Azure AD. This information is based on:

After user creation we add the credential to the Azure Automation account.




7. Add variables to the Azure Automation account

The following PowerShell needs the native registered application ID also called Client ID. Therefore we create a Azure Automation variable IntuneClientId and we need the tenant ID as a variable, we use Tenant as identifier.


Below an example for IntuneClientId and add your Application ID from above, do the same for Tenant variable and add your tenant ID.



8. Add Runbook to the Azure Automation account

Adding a Runbook with the name Invoke-RetireDevice




9. Edit Runbook

We verify if the Runbook sees all our important information like AzureAD module, Variables and Credentials. After adding the PowerShell script we need to publish it.


PowerShell script for the Runbook is based on the GitHub samples with the modification to allow non-interactive usage of credentials via Get-AutomationPSCredential and Get-AutomationVariable

$intuneAutomationCredential = Get-AutomationPSCredential -Name automation
$intuneAutomationAppId = Get-AutomationVariable -Name IntuneClientId
$tenant = Get-AutomationVariable -Name Tenant

Now follows the actual PowerShell script with the logic to get the device of the user and delete it form Intune with usage of the automation credentials and variables for client id and tenant. In the end it will use the same credentials to delete the device from AAD also.


10. Start and test Runbook

Everything is setup, now it’s time for the first run. Get a stale Intune device you like to retire and start the Runbook.


The Runbook has two input parameters DeviceName and UserPrincipalName. This is needed to avoid getting duplicate entries for DeviceName. A user should only have a device once. If not we might rethink the PowerShell logic to address this.


After start of the job we can click on Output


and get details as defined in our PowerShell script. If everything runs fine you will get the following output:



11. Add Automation Operator

We add a different user (e.g. service desk operator) to our Runbook as an Automation Operator. This provides the user the possibility to log on to and start the Runbook but it’s protected from modifications as shown below.





Recap what we achieved

We have setup Azure Automation to host our PowerShell script in a managed cloud environment which is able to run as job to delete an Intune device and AAD device. In addition we learned the basics of Azure Automation and how to add modules, work with credentials and variables. Usage of unattended authentication to the Intune API is the basis for Intune API usage in Azure Automation.



  1. Microsoft Flow
  2. Source Control

Microsoft Flow

I thought it would be nice to enhance the Runbook with a Microsoft Flow to trigger it from my mobile phone. I found the following article which is describing how to do that:

Azure Automation new Microsoft Flow Service

Unfortunately as soon as I tried to use it I found that Microsoft Flow does not provide any trigger at the moment for it. The manual trigger as shown in the blog post above is not available for me. Maybe we can provide a nice interface for the runbook in the future via Microsoft Flow.

Microsoft Flow – Azure Automation

Source Control

When working with code it’s important to have a good versioning and a code storage place. For this Azure Automation provides integration with GitHub to link your Runbook source code. How to setup this follow the guide below. I can really recommend it.

Source control integration in Azure Automation


Further information

Azure Automation User Documentation

How to use Azure AD to access the Intune APIs in Microsoft Graph

Credential assets in Azure Automation

Intune Device Management permissions

Graph Explorer – Microsoft Graph

Another very good guide using Azure Automation with Intune and AAD is here:
Unattended authentication against the Microsoft Graph API from PowerShell,graph,api/2017/10/30/unattended-ms-graph-api-authentication.html


Have fun in automation. Feel free to post your process automation ideas in the comment area below! Thanks for reading!

Use Delivery Optimization with DHCP Option on Pre-Windows 10 version 1803

The new Windows 10 Peer 2 Peer feature Delivery Optimization was enhanced by the setting to query DHCP option ID 234 to get a Group ID (DOGroupIdSource). It was implemented into the latest Windows 10 version 1803 based on my feedback. I’m a little proud that the idea was well received and my product feedback was implemented as new option within six month.

It was officially announced with Windows Insider Preview 17063. It can be read here: in the section New features for IT Pros in Delivery Optimization.

I’ve written a blog post how Delivery Optimization works and how to use this feature with new Windows versions 1803+, see here: Configure Delivery Optimization with Intune for Windows Update for Business.  It’s really great to have it with Windows 10 version 1803+ but I need to provide this functionality to older Windows 10 versions (1703 and 1709) also. I want to use this very good and flexible grouping capability from now on for all devices even pre-version 1803.

Therefore I have designed a solution to provide this functionality to older versions of Windows 10 and switch over to the native Windows 10 implemented solution as soon as the device gets upgraded to 1803.

First problem is to get the DHCP option from the DHCP server. Luckily I’ve written a small C++ program to do that in the past. I’ve written it to even work with a WinPE environment. The small binary can be used to send the DHCP option ID x request. When executed the binary expects a result as a string value. Below an example:

DhcpOption.exe <OptionID>


It’s even possible to specify a debug switch

DhcpOption.exe <OptionID> [debug]

to get debug output for troubleshooting:


The C++ solution can be found on my GitHub here:

The compiled x64 binary DhcpOption.exe can be found here:

With this little helper we can use the Intune Management Extension and design a PowerShell script to:

  1. query DHCP server for the Option ID 234
  2. write result (Group ID) to the registry

Prerequisite is an available Option ID 234 on the DHCP server. See Configure Delivery Optimization with Intune for Windows Update for Business how to configure the DHCP server for this.

To support travelling users we need to make sure the device will query the DHCP server from time to time to get the group ID belonging to the current DHCP scope the client is using. For this we schedule the script as a scheduled task and run it at logon and on every unlock of the workstation.

The Delivery Optimization service will query the registry value for every new request and this makes sure a client uses the Group ID delivered by DHCP from the particular site.

To make sure we do not interfere with the native implementation starting with 1803 I implemented a logic to disable the scheduled task and remove the registry key as soon as Windows 10 version 1803, a build greater than 16299 is found.

The DhcpOption.exe will be provided from an Azure Blob Storage account. We need to create a Storage account as type Blob storage and a container to store the DhcpOption.exe. Then we use the provided download link in the script to get the binary during install. A guide how to create the Azure storage account is included in my article Gather Windows 10 AutoPilot info in Azure Blob Storage during wipe and reload. You don’t need the SAS token, just the container and upload DhcpOption.exe. The SAS token is only needed to support POSTS methods for uploading new files to the blob storage. The DhcpOption.exe is uploaded once and then provided for download only.

With this solution we can use the DHCP option for all Windows 10 version until 1803 and as soon as we get upgraded we disable and remove our self and the native implementation takes over. To make sure the native implementation is used as soon 1803 is found we need to implement the new DO settings (DOGroupIdSource). Follow my guide in my previous post for this Configure Delivery Optimization with Intune for Windows Update for Business.

Source Code of the full custom DO install script Register-DOScript.ps1 can be found on my GitHub Gist here:

Make sure to replace the Azure blob storage URL with your own one!

The uninstall script Unregister-DOScript.ps1 is also available via my GitHub Gist:

If we combine the script with Intune Management Extension we can easily deploy this solution to our Azure AD joined modern managed Windows 10 pre-version 1803 devices.

We will get the files for scheduling at C:\ProgramData\CustomScripts


and the scheduled task RunCustomDOscript


with task trigger logon or unlock of workstation


when executed we will get the Group ID from the DHCP server

MD DHCP Group ID Option in Scope

and the received string is written to the registry


for me this bridges the time until all devices are upgraded to Windows 10 version 1803 and gives me enough flexibility to group my devices to achieve maximum Delivery Optimization benefits.

Happy caching!

Gather Windows 10 AutoPilot info in Azure Blob Storage during wipe and reload

The Modern Management strategy is based on Enterprise Mobility + Security and additional services like Office 365. Microsoft created a new SKU called Microsoft 365 for this. To complete the big picture we need some additional services:

The idea is clear, manage the Windows 10 devices like mobile phones. No more Operating System Deployment (OSD) just provisioning and management from everywhere. Everything is powered by the cloud.

A new member in this story is a feature called Windows AutoPilot. You can compare this with the Device Enrollment Program as you might know from Apple. It provides a managed way of provisioning with near zero touch. IT is able to control the experience the end user will have during enrollment process. To make all this work we need to gather some properties of the device to identify it clearly. The AutoPilot needs the Device Serial Number, Windows Product ID and the Hardware Hash. This information is uploaded to the AutoPilot service and then the device will be recognized during OOBE as an AutoPilot device, and will show a customized enrollment experience.

The Problem

Many organizations are still using Windows 7 and are on it’s way to Windows 10.  Windows 10 is the new “baseline” in this story. It’s aligned with the complete modern management story. It provides the capability to join Azure AD and the usage of a Windows as a Service model.

How do we get to the new baseline?

If we purchase a new device, the OEM vendor takes care of installing Windows 10 with a signature edition and all necessary drivers. In future the hardware information will be synced into our tenant from the OEM vendor. We will get the device information in Intune and we can start to assign an AutoPilot Deployment Profile and start to enroll the device.


What if we have a bunch of Windows 7 devices in the environment?

A way to handle this is that we are playing the role of the OEM vendor and do the install of a Windows 10 signature edition on the existing Windows 7 devices. Depending what is available we can use ConfigMgr or MDT. In the context of modern management I like to keep on-premises software as low as possible. I use MDT for that simple task now. If ConfigMgr is available we can build the following the same way.

I use MDT to create a Deployment USB media (removable drive) for that and build up a Standard Task Sequence to deploy Windows 10 for this. We take care of the right drivers and in the end we let the device start the OOBE again (sysprep.exe /oobe /reboot|shutdown). Now we have the same situation like a newly delivered device by the OEM vendor. But we can’t deliver the hardware information directly into our tenant like the OEM vendor will do in the future. Good to know that we can get the hardware information with the PowerShell Script Get-WindowsAutoPilotInfo and upload the information provided via a .csv file our self.

Now imagine a situation where a rollout team is preparing a lot of machines. We would end up in a lot of .csv files on different USB removable drives. To make this a little easier for IT to import the hardware information of new devices to AutoPilot, we build up the following logic:


First of all we prepare the Blob Storage for easy csv file storage.

Login to Azure portal and click on “Storage accounts


Click Add


fill out name, Account kind: Blob storage


after creation you should see the storage account


create a container called hashes


create a shared access signature for Blob | Write | an extended expiry date/time | HTTPS only and create a SAS token. Shared Access Signature is used to limit the permission and the limit the period of time to access the account. See Delegating Access with a Shared Access Signature


Copy the SAS token as we need it in the following script.

Download PowerShell Script Get-WindowsAutoPilotInfo and AzCopy. Install AzCopy and get the files from here: C:\Program Files (x86)\Microsoft SDKs\Azure\AzCopy

Copy AzCopy files and Get-WindowsAutoPilotInfo.ps1 into MDT share e.g. C:\DeploymentShare\Scripts\CUSTOM\HardwareInfo

Create PowerShell script: Get-HardwareInformation.ps1 and copy to the MDT folder HardwareInfo as well. Replace the SAS token (ending with XXXX) in the script example with the newly created one. Replace ZZZZ with your Storage account name.

The script will look for the Get-WindowsAutoPilotInfo.ps1 script, executes it and creates a computername.csv file in C:\Windows\Temp. From here it will be copied to the blob storage account and copied to the USB removable drive folder autopilot-script-success or autopilot-script-failed. This provides the chance in case of failure (missing internet access during deployment) that the computername.csv can be gathered from the USB drive as well.


# Author: Oliver Kieselbach
# Date: 11/15/2017
# Description: Generate AutoPilot .csv file and upload to Azure Blob Storage.

# The script is provided "AS IS" with no warranties.

# Downlaod URL for AzCopy:

# Downlaod URL for Get-WindowsAutoPilotInfo:

Function Execute-Command
 Param([Parameter (Mandatory=$true)]
 [Parameter (Mandatory=$false)]

$pinfo = New-Object System.Diagnostics.ProcessStartInfo
 $pinfo.FileName = $Command
 $pinfo.RedirectStandardError = $true
 $pinfo.RedirectStandardOutput = $true
 $pinfo.CreateNoWindow = $true
 $pinfo.UseShellExecute = $false
 $pinfo.Arguments = $Arguments
 $p = New-Object System.Diagnostics.Process
 $p.StartInfo = $pinfo
 $p.Start() | Out-Null
 stdout = $p.StandardOutput.ReadToEnd()
 stderr = $p.StandardError.ReadToEnd()
 ExitCode = $p.ExitCode

$scriptPath = [System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Path)
$fileName = "$env:computername.csv"
$outputPath = Join-Path $env:windir "temp"
$outputFile = Join-Path $outputPath $fileName
$autoPilotScript = Join-Path $scriptPath "Get-WindowsAutoPilotInfo.ps1"

Execute-Command -Command "$psHome\powershell.exe" -Arguments "-ex bypass -file `"$autoPilotScript`" -ComputerName $env:computername -OutputFile `"$outputFile`"" | Out-Null

$url = ""
$sasToken = "?sv=2017-04-17&amp;ss=b&amp;srt=o&amp;sp=w&amp;se=2019-10-16T19:47:51Z&amp;st=2017-10-15T11:47:51Z&amp;spr=https&amp;sig=XXXX"
$result = Execute-Command -Command "`"$scriptPath\azcopy.exe`"" -Arguments "/Source:`"$outputPath`" /Dest:$url /Pattern:$fileName /Y /Z:`"$outputPath`" /DestSAS:`"$sasToken`""

if ($result.stdout.Contains("Transfer successfully: 1"))
 if (-not (Test-Path $(Join-Path $scriptPath "autopilot-script-success")))
 New-Item -Path $(Join-Path $scriptPath "autopilot-script-success") -ItemType Directory | Out-Null
 Copy-Item -Path $outputFile -Destination $(Join-Path $scriptPath "autopilot-script-success") -Force -ErrorAction SilentlyContinue | Out-Null
 if (-not (Test-Path $(Join-Path $scriptPath "autopilot-script-failed")))
 New-Item -Path $(Join-Path $scriptPath "autopilot-script-failed") -ItemType Directory | Out-Null
 Copy-Item -Path $outputFile -Destination $(Join-Path $scriptPath "autopilot-script-failed") -Force -ErrorAction SilentlyContinue | Out-Null

Create PowerShell script: Download-HardwareInformation.ps1
This can be used later on to download all the .csv files from Azure Blob Storage and create the combined .csv for easy upload to AutoPilot. Leave this script on your admin workstation. Replace the StorageAccountKey XXXX with one of your storage account access keys! Replace ZZZZ with your Storage account name.


# Author: Oliver Kieselbach
# Date: 11/15/2017
# Description: Gather AutoPilot .csv file from Azure Blob Storage, delete them and combine into single .csv file.

# The script is provided "AS IS" with no warranties.

#Install-Module AzureRM

$ctx = New-AzureStorageContext -StorageAccountName ZZZZ -StorageAccountKey XXXX
$path = "C:\temp"
$combinedOutput = "C:\temp\combined.csv"

$count = $(Get-AzureStorageContainer -Container hashes -Context $ctx | Get-AzureStorageBlob |measure).Count
if ($count -gt 0)
 Get-AzureStorageContainer -Container hashes -Context $ctx | Get-AzureStorageBlob | Get-AzureStorageBlobContent -Force -Destination $path
 $downloadCount = $(Get-ChildItem -Path $path -Filter *.csv | measure).Count
 if ($downloadCount -eq $count)
 Get-AzureStorageContainer -Container hashes -Context $ctx | Get-AzureStorageBlob | Remove-AzureStorageBlob
 # parse all .csv files and combine to single one for easy upload!
 Set-Content -Path $combinedOutput -Value "Device Serial Number,Windows Product ID,Hardware Hash" -Encoding Unicode
 Get-ChildItem -Path $path -Filter "*.csv" | % { Get-Content $_.FullName | Select -Index 1 } | Add-Content -Path $combinedOutput -Encoding Unicode

I assume the MDT share is build and a Standard Task Sequence for a vanilla Windows 10 installation is available. Then we add a task sequence step “Run PowerShell Script” to the folder “Custom Tasks“:


and configure the Get-HardwareInformation.ps1 script:


Now you are ready to run a MDT deployment of a Windows 10 with an automatic upload of the hardware information to the Azure Blob Storage.

After deployment of the devices you can use the Download-HardwareInformation.ps1 Script to get the combined.csv file and upload it to Microsoft Store for Business (MSfB). The upload is currently available in this portal only.


I recommend to use the MSfB to upload the combined.csv only! Management of the devices and profiles should be done in Intune. Currently the portals do not completely share their information. For example a profile created in MSfB will not be shown in Intune and vice versa. With Modern Management where Intune is used I suggest to use MSfB to upload devices and Intune for management of profiles (creation and assignment).

Happy AutoPiloting!

There is another great article from Per Larsen (MVP):
How to collect hardware hash to use in AutoPilot as part of MDT OSD