Automate Intune Primary User updates efficiently with Log Analytics

What is the Primary User and why does it matter?

The primary user of a device in Intune associates a user with a device. If no primary user is assigned the device is referred to as a “Shared Device”.

What is the importance of the primary user?

Having the correct primary user ensures that the Company Portal experience works, the user can see the device in the Company Portal website and the admin can see the device associated with the user when using troubleshooting in the Intune console.

If another user is set as the primary user, the Company Portal App displays “This device is already assigned to someone in your organization. Contact company support about becoming the primary device user. You can continue to use Company Portal but functionality will be limited.”

If no user is set as the primary user, the Company Portal App displays a “Shared” label and self-service actions aren’t available.

How is the primary user Assigned?

Typically the user who enrolls the device or logs on first is automatically assigned as the primary user, depending on the scenario:

https://learn.microsoft.com/en-gb/mem/intune/remote-actions/find-primary-user#who-is-assigned-as-the-primary-user

Often a technician or DEM account can be automatically assigned as a primary User during enrolment and unless there’s a manual process to correct that in the Intune console which is followed consistently, it’s a good idea to try to automate the process of assigning the correct primary user.

The Challenge

There are a number of different solutions out there to automate the process of updating the primary User of a device in Intune via Graph API. The challenge I encountered when evaluating some of the solutions was that using a single Graph API GET call per device to retrieve the primary User did not scale well to tens of thousands of endpoints. The size of the data set can also be problematic when trying to automate the process in an Azure runbook, as the memory requirements are likely to require a Hybrid worker in larger Enterprise environments.

The Solution

I like the approach of using the Azure AD Sign in logs to identify the users who have signed in to a device, since we don’t need to get the data from the endpoint. If the most frequently signed in user to a device over a specified time period is not configured as the current primary user in Intune, we can then set the correct primary user via Graph API.

Log Analytics is an awesome tool for data collection and reporting purposes, but I also learned that we can leverage the Log Analytics API to run Kusto queries in PowerShell to get back the data we need for automation purposes. Typically, you might have used Graph API to feed data to Log Analytics, in this case we are flipping it around and using Log Analytics to feed data to Graph API (and optionally back to Log Analytics again).

These PowerShell cmdlets can be used to connect to a Log Analytics Workspace and run a Kusto query:

$workspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $workspaceResourceGroupName -Name $workspaceName
Invoke-AzOperationalInsightsQuery -Workspace $workspace -Query $query

The native out-of-box Log Analytics solutions can be used to get the data required. If you have enabled Log Analytics for Intune, you can retrieve all of the devices in Intune and the primary users using the [IntuneDevices] table. If you have enabled Log Analytics for Azure AD, you can also retrieve the sign in logs for those devices using the [SigninLogs] table.

With these two sources of data, we can perform a join of these two tables in a single Kusto query to compare the current primary user of an Intune device with the most frequently signed in users to that device over a specific time period. What if your Identity and Endpoint teams store these logs in separate Log Analytics Workspaces? The query works even if you have separate Log Analytics workspaces for Intune and Azure AD, we can address 2 different Workspaces in a single join query.

We can leverage Kusto to do all of the heavy lifting for us and in a matter of seconds produce a table that returns exactly the information we need and only that, namely the most frequently signed in user to a device if different from the current primary user. Kusto helps to retrieve the data and perform some powerful data analysis and filtering. It also has the advantage of optimizing the solution for speed and lower memory requirements. The following table is the output of the query which includes all of the data we need for logging purposes and for performing the update of the primary user via Graph API. The column names have been re-named using “extend” in Kusto so we can easily differentiate between the current and target primary users for readability.

With this data, we can now leverage Graph API to update the primary user on those devices. Updating the primary user also works for W365 Cloud PCs, which I have found in some cases may not have a primary user assigned.

Once the updates have been performed with Graph API, we can also write the changes back to a custom Log Analytics table [IntunePrimaryUserUpdates_CL] so that there is a full audit trail of any changes made. We add an additional column in Log Analytics to indicate the “Status” [Success/Error] of the update performed via Graph:

Refining the scope

We can use PowerShell variables to inject parameters in to the Kusto query to refine the scope of the devices and users in scope in order to minimize the chance of setting the wrong user as Primary. Here are some of the parameters and some suggested defaults to be evaluated and adjusted as needed to fit your requirements.

  • Number of days of Intune device logs to review: 7
  • Number of days of sign-in logs to review: 7
  • Number of days old a device should be before it is considered for the primary user review: 2
  • Minimum number of sign-ins during the specified timeframe before a user is considered as a primary user: 3

We can also apply exclusions to the query so that we do not update the primary user on shared devices such as Kiosks, Teams Rooms, Surface Hubs;

  • Exclude device names that contain a specified string, e.g. “Kiosk”, “MTR”
  • Exclude device models that contain a specified string, eg. “Surface Hub”
  • Exclude devices where the primary user is currently empty [true / false]

We might need to have exclusions for users who we do not wish to replace as primary user, such as robotics accounts. Additionally, we might need to exclude certain accounts from being set as the primary user, such as a DEM accounts or service accounts.

  • Exclude existing primary users that contain a specified string, e.g. “Robot”
  • Exclude devices where the target primary users contains a specified string, e.g. “SVC-“, “DEM”

With that we build our KQL query to invoke via Log Analytics API:

[workspace('/subscriptions/<subscriptionID>/<resourcegroup>/providers/microsoft.operationalinsights/workspaces/<workspace>').IntuneDevices
| where TimeGenerated >= ago(7d) and OS contains 'Windows' and Ownership == 'Corporate' and ManagedBy == 'Intune'
| where todatetime(CreatedDate) <= ago(2d)
| where Model !contains 'Surface Hub'
| where UPN !contains 'robot'
| summarize arg_max(TimeGenerated, *) by DeviceId
| extend ExistingPrimaryUser = UPN, ExistingPrimaryUserID = PrimaryUser, AzureADDeviceID = ReferenceId, IntuneDeviceID = DeviceId
| project DeviceName, IntuneDeviceID, AzureADDeviceID, ExistingPrimaryUser, ExistingPrimaryUserID
| join kind=inner (workspace('/subscriptions/<subscriptionID>/<resourcegroup>/providers/microsoft.operationalinsights/workspaces/<workspace>').SigninLogs 
| where TimeGenerated >= ago(7d)
| where AppDisplayName == 'Windows Sign In' and UserType == 'Member' 
| where UserPrincipalName !contains 'svc' 
| extend DeviceName = tostring(DeviceDetail.displayName),DeviceId = tostring(DeviceDetail.deviceId)
| summarize UserSignInCount = count(UserId) by DeviceId,DeviceName,UserPrincipalName,UserId // Count how many times each user has logged in to a device
| where UserSignInCount >= 3
| summarize arg_max(UserSignInCount,*) by DeviceId,DeviceName // Get the top user with most sign ins on the device
| extend AzureADDeviceID = DeviceId, NewPrimaryUser = UserPrincipalName, NewPrimaryUserID = UserId
| project DeviceName,AzureADDeviceID,NewPrimaryUser,NewPrimaryUserID,UserSignInCount)
on $left.AzureADDeviceID==$right.AzureADDeviceID // Join the tables on the Azure AD Device ID
| where isnotempty(NewPrimaryUser)
| where ExistingPrimaryUserID !~ NewPrimaryUserID 
| project DeviceName, IntuneDeviceID, AzureADDeviceID, ExistingPrimaryUser, ExistingPrimaryUserID, NewPrimaryUser, NewPrimaryUserID, UserSignInCount // Return the IntuneDeviceID required to update the Primary user in Graph
Tip

In case your KQL query fails, adding the the -Debug Parameter to the Get-AzOperationalInsightsWorkspace and Invoke-AzOperationalInsightsQuery cmdlets gives some very useful feedback to help identify any issues such as permissions or syntax. As I discovered, the “$left” and “$right” Kusto variables are interpreted as a PowerShell variables so need to be escaped.

Requirements

Here are the requirements for running the script.

If you have not set up Log Analytics for Intune or used Azure runbooks, I highly recommend referring to the brilliant team on the MSEndpointMgr site who have written blog posts covering the setup and permissions: MSEndpointMgr – Microsoft Endpoint Manager Community

Configuring and running the script

Download the script from GitHub: Intune/IntunePrimaryUser at main · seanlillis/Intune (github.com)

Variables
The following are mandatory variables to be configured with details of the Log Analytics Workspaces.

# Log Analytics Workspace details for Intune
$subscriptionIDIntune = "" # Subscription ID for Intune Log Analytics
$workspaceResourceGroupNameIntune = "" # Resource Group Name for Intune Log Analytics
$workspaceNameIntune = "" # Workspace Name for Intune Log Analytics

# Log Analytics Workspace details for Azure AD
$subscriptionIDAzureAD = "" # Subscription ID for Azure AD Log Analytics
$workspaceResourceGroupNameAzureAD = "" # Resource Group Name for Azure AD Log Analytics
$workspaceNameAzureAD = "" # Workspace Name for Azure AD Log Analytics

The following variables can be adjusted to suit your requirements:

# Parameters
$testRun = $true # If set to true, the script will perform a dry run and will not make any changes to the Primary User attribute.
$intuneDeviceDays = 7 # Specify the number of days of Intune Log Analytics to review
$signInDays = 7 # Specify the number of days of Azure AD Sign In Logs to review
$minDeviceAgeDays = 2 # Specify the minimum number of days a device should be in Intune before it is included in scope
$minUserSignIns = 3 # Specify the minimum number of times a user should have signed in to the device within the specified timeframe in order to be considered as the primary user

# Exclusions
$excludeDevicesWithNoPrimaryUser = $false # Specify whether to exclude devices that have no primary user currently assigned, e.g. shared devices
$deviceNameExclusions = "","" # Specify a comma separated list of device names that you wish to exclude (if any). The KQL query will perform a "contains" match, e.g. "Kiosk"
$deviceModelExclusions = "",""  # Specify a comma separated list of device models that you wish to exclude (if any). The KQL query will perform a "contains" match, e.g. "Surface Hub"
$userNameExistingExclusions = "","" # Specify a comma separated list of existing Primary users whose devices you wish to exclude (if any). The KQL query will perform a "contains match", e.g. "Robot"
$userNameTargetExclusions = "","" # Specify a comma separated list of target user names that you do not wish to assign as primary user on any device (if any). The KQL query will perform a "contains match", e.g. "User01", "User02"

Running in Azure Runbook
In order to write the changes back to a custom Log Analytics table, you will need to create the following variables in your automation account:
CustomerID # Your Log Analytics Workspace ID
SharedKey # Your Primary Key for the Log Analytics Workspace
TenantID # Your Tenant ID

One downside to using Log Analytics is that any changes made via Graph API are not immediately available in Log Analytics, so if you run the script in immediate succession, it will likely look to make the same updates again. Therefore, it’s better to run this with a frequency not shorter than 24 hours, though worst case you’ll just repeat the same Graph API calls.
If running the script in an Azure Runbook, I would recommend using a Managed Identity since “Run As Accounts” will no longer be supported from September 30 2023.

Running Manually
In order to write the changes back to a custom Log Analytics table when running the script manually, you will need to update these variables in the script.

# Log Analytics Workspace used to capture output
$tenantID = ''
$logAnalyticsLogName = "IntunePrimaryUserUpdates" # Name of the custom Log Analytics log table that will be used to store a log of changes performed by the script
$customerID = '' # Replace with your Log Analytics Workspace ID
$sharedKey = ''  # Replace with your Primary Key for the Log Analytics Workspace

Wrapping Up

Running this script on an automated schedule will help you ensure that the correct primary user is assigned and kept current over time. There isn’t a gold standard solution to fix this problem, but this is another approach to consider. If you have any suggestions on how to improve the solution, please get in touch.

2 thoughts on “Automate Intune Primary User updates efficiently with Log Analytics”

  1. John Ferragamo

    Hello.

    You noted that “Co-managed devices will carry over the primary user from ConfigMgr to Intune.” However the table referenced states that for Co-management, the first user to sign into Windows will be the Primary.

    We have not witnessed the SCCM primary user being pushed to Intune so that they match.
    Instead, the first user to sign into Windows is the primary, and in our case where we have a staff of build technicians signing in to validate, they are being assigned as the primary.

    Is there a setting or policy that needs to be enabled in order for SCCM to push up the Primary user and update the device?

    Thank you

    1. You are spot on, I have updated the post. There isn’t a policy I’m aware of to push the primary user from ConfigMgr to Intune. Thanks.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top