Delayed targeting in Intune

Have you ever had the need to delay targeting in Intune for some of your app deployments, PowerShell script , policies or Proactive Remediations? In a normal situation we would like most of this to be targeted immediately, but there are some caveats here. We might have an app or policy that will brake the provisioning process of the device. This can be related to Windows Autopilot or other provisioning methods. When it comes to Windows Autopilot, Microsoft has documented some of these potential issues here: Windows Autopilot policy conflicts | Microsoft Docs

Another upcoming change from Microsoft that can make this very relevant is the Windows Autopilot MFA changes to enrollment flow

My approach to this was to try to create a Azure AD Group contain devices that have enrolled for at least ex. 4 hours ago. There are no options to look at time of enrollment or time of joining Azure AD in a dynamic group creation. I was trying to look in the Intune Filters as well but there was nothing to find there for my scenario. This approach might not solve all scenarios like device object reuse in Windows Autopilot, but it solves my scenario.

The problem

As I where building a automated deployment solution for Teams Meeting Room devices using Configuration Manager for deployment, removal of the ConfigMgr client after deployment and onboarding to Azure AD / Intune using a Bulk token we bumped into a few issues. We realized that some of the scripts and policies that came down from Intune after enrollment could interfere with the provisioning of the Teams Meeting Room application from Microsoft and break our provisioning. We had to find a way to delay targeting of the conflicting scripts and policies.

The Solution

As the meeting room devices are being onboarded with a provisioning package, the rooms are onboarded with a brand new Azure AD device object every time, that is in our advantage this time, as it simplifies the solution it self. The solution here might not work for Windows Autopilot related scenarios when a device is being redeployed.

As there is no ruleset in Azure AD Dynamic groups to use enrollment time in Intune or Join time to Azure AD, this need to be automated in another way.

Azure Automation

Azure Automation allows us to run a PowerShell script on a schedule. The script it self will authenticate to Azure AD and Intune using a Managed System Identity. This allows us to have a script running authenticated without having to deal with any passwords or secrets. I will not go into the details of what a managed system identity is, but you can read more about it here: Managed identities for Azure resources | Microsoft Docs. To enable a Managed Identity on your Automation Account, go to Identity(Preview) and turn it on.

You will get a Object ID and you can find this under Enterprise Applications in Azure AD -> Managed Identites.

To follow the practive of Least Privilege we use delegated access for maintaining the group (Write operations), and Microsoft Graph API permission for talking to Intune (Read). Lets start with setting up the permissions we need.

Setting up the permissions

We need to have write access to the dynamic group we are using. This is achieved by giving the managed system identity itself owner permissions on the group. Then we need to have read access for managed devices in Intune, this is achived by giving the managed identity graph api permissions (DeviceManagementManagedDevices.Read.All).

Going back to our Enterprise Application, we see that we can assign any Graph API permissions from there.

The Graph API permissions can’t be given in the Azure Portal so we need to use PowerShell with the Azure AD Module. This code will assign the required permission to your Managed Identity.

$DisplayNameofMSI = "<Enter the name of your Automation Account>"
$MSI = (Get-AzureADServicePrincipal -Filter "displayName eq '$DisplayNameOfMSI'").ObjectId
$PermissionName = "DeviceManagementManagedDevices.Read.All"
$GraphServicePrincipal = Get-AzureADServicePrincipal -SearchString "Microsoft Graph" | Select-Object -first 1
$AppRole = $GraphServicePrincipal.AppRoles | Where-Object {$_.Value -eq $PermissionName -and $_.AllowedMemberTypes -contains "Application"}
New-AzureAdServiceAppRoleAssignment -ObjectId $MSI -PrincipalId $MSI -ResourceId $GraphServicePrincipal.ObjectId -Id $AppRole.Id

Verifying on the Managed Identity in Enterprise Applications we now see that we have the correct permissions assigned.

Now just go the the group you want to maintain in Azure AD Portal and add the Managed Identity as a Group Owner the same way you would add a user as group owner. Note that the Managed Identity shows up as a Service Principal instead of a user.

The script

When it comes to the script we need to be able to authenticate with the Managed System Identity in 2 different ways. We will connect to Azure AD with delegated permissions and to Intune using application permissions. We will be using the MSGraphRequest module for Graph API related operations, AZ.Resources for Azure AD related operations, MSAL.PS for authentication to Graph and AZ.Accounts to auhenticate to Azure AD. All these modules is required for this to work. Import them into your Automation Account.

Now we have the modules, so how do we authenticate? The reason for Az.Resources over Azure AD module is that the Azure AD module does not work well with Managed Identities. We only need one operation that is to add a device to a group. Az.Resources have what we need. To connect with Az.Accounts we simply add Connect-AzAccount -Identity to our script and we are connected. Graph is a bit more complicated so I created a simple function to achieve this.

function Get-MSIAccessTokenGraph{
    $resourceURL = "" 
    $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 
    $accessToken = $response.access_token

    #Create Global Authentication Header
    $Global:AuthenticationHeader = @{
    "Content-Type" = "application/json"
    "Authorization" = "Bearer " + $accessToken
return $AuthenticationHeader
# Connecting to Azure AD
Connect-AzAccount -Identity 
#Connect to Graph for Graph Operations 

The function will set a global variable AuthenticationHeader. This is the the same variable that is used in the MSGraphRequest module. This allows us to now directly use the MSGraphRequest module for operations.

My scenario is for Teams Meeting Room devices, which all have a prefix MTR-, so that is added in as part of my Graph Query in the script. This filter in line 41 should be modified to your needs.
The full script looks like this:

    Automated script for delayed targeting based on a set timerange. 
    Add devices to group X hours after enrolled to Intune to avoid certain scripts and packages to be targeted during provisioning. 
    TargetingGroupID: The ObjectID of the group you are maintainging in Azure AD
        Author:      Jan Ketil Skanke 
        Contact:     @JankeSkanke
        Created:     2021-09-14 
        Updated:     2021-09-14
        Version history:
        1.0.0 - (2021-09-22 ) Production Ready version
function Get-MSIAccessTokenGraph{
    $resourceURL = "" 
    $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 
    $accessToken = $response.access_token

    #Create Global Authentication Header
    $Global:AuthenticationHeader = @{
    "Content-Type" = "application/json"
    "Authorization" = "Bearer " + $accessToken
return $AuthenticationHeader

#Connect to AzAccount for AZOperations 
$Connecting = Connect-AzAccount -Identity 

#Connect to Graph for Graph Operations 
$Response = Get-MSIAccessTokenGraph

#Set timeslot to check for new devices to add 
$starttime = Get-Date((Get-Date).AddHours(-28)) -Format "yyyy-MM-ddTHH:mm:ssZ"
$endtime = Get-Date((Get-Date).AddHours(-4)) -Format "yyyy-MM-ddTHH:mm:ssZ"

# Fetch all newly deployed MTRs and process them
$Devices = Invoke-MSGraphOperation -APIVersion Beta -Get -Resource "deviceManagement/manageddevices?filter=startswith(deviceName, 'MTR-') and ((enrolleddatetime+lt+$($endtime)) and (enrolleddatetime+gt+$($starttime)))"
# The below line can be used for first run by uncommenting and it will take all devices up until 4 hours ago. 
#$Devices = Invoke-MSGraphOperation -APIVersion Beta -Get -Resource "deviceManagement/manageddevices?filter=startswith(deviceName, 'MTR-') and (enrolleddatetime+lt+$($endtime))"

# Fetch all devices currently in group 
$DeviceIDsInGroup = (Get-AzADGroupMember -GroupObjectId $TargetingGroupID).Id
# Process all newly enrollemd machine accordingly

if (-not([string]::IsNullOrEmpty($Devices))){
foreach($device in $devices){
	$DeviceID = $device.azureADDeviceId
	$DirectoryObjectID = (Invoke-MSGraphOperation -APIVersion Beta -Get -Resource "devices?filter=deviceId+eq+`'$DeviceID`'").id
	if (-not ($DirectoryObjectID -in $DeviceIDsInGroup)){            
		try {
			Add-AzADGroupMember -MemberObjectId $DirectoryObjectID -TargetGroupObjectId $TargetingGroupID -ErrorAction Stop
			Write-Output "Added $($device.deviceName) with ID $($DirectoryObjectID) to group"
		} catch {
			Write-Output "Failed to add $($device.deviceName) to group. Message: $($_.Exception.Message)"
	} else {
		Write-Output "$($device.deviceName) with ID $($DirectoryObjectID) already in group"
} else {
	Write-Output "No new devices to process this time, exiting script"

The result of this script would look something like this:

I am looking into how I can make this work for more scenarios, including Device Object reuse in Windows Autopilot. For now this is what I have working for me. Feel free to comment or propose changes to the script on Github. It can be found here

Jan Ketil Skanke

Jan Ketil is an Enterprise Mobility MVP since 2016 and are working as a COO and Principal Cloud Architect at CloudWay in Norway. He has been in the industry for more than 20 years working for both Microsoft Partners and Microsoft. He loves to speak about anything around Enterprise Mobility and Secure Productivity. He is also the lead for the community conference Experts Live Norway. Jan Ketil has presented at large industry conferences like Microsoft Ignite, Microsoft Ignite The Tour, Microsoft Inspire, Experts Live Europe, Techmentor HQ (3rd best session 2019) and NIC Conference in Oslo.


  • Your script to add permissions to the account has “DeviceManagementManagedDevices.Read.All” but the picture has read and write, was this intentional? I’m asking because when I try this out I am getting “Graph request failed with status code ‘403 (Forbidden)’. Error details: Authorization_RequestDenied – Insufficient privileges to complete the operation.” On each attempt.

  • I was able to get it working by configuring the Azure Automation Runbook to use PS runtime 7.1.

  • Hello. Does the permission to Intune within the Managed Identity in Enterprise Applications need to be Read, or Read and Write? I ask because the sample code is for Read but the posted image shows Read and Write.

    I’m troubleshooting my test because I’m getting an 403 error when testing the ps script within the automation account runbook. The exact error is “Graph request failed with status code ‘403 (Forbidden)’. Error details: Authorization_RequestDenied – Insufficient privileges to complete the operation.”

    Any assistance is greatly appreciated.



  • Thank you Jan !

    Do you know which timezone is used to record enrolleddatetime ?
    I have a similar scenario where i need to run some actions based on enrolled machine in the last hour or so however i have machines all over the world and the enrolleddatetime is displayed differently if i look it up in graph or in intune portal ( but that may be a local cosmetic time adjustment)
    I need to make sure the time filter will be accurate .

    Thanks !


Categories use cookies to ensure that we give you the best experience on our website.