Set Intune Primary User and Group

I have a customer that uses Intune AutoPatch and wanted to add devices to groups based on domain suffix of the primary user. So I started to add this functionality to my popular script Intune-Set-Primaryuser. This script is described in previous blog. But I later decided to add this as a new script to prevent new issues with the old one. Both the possibility to set primary user based on usage and set group membership based on primary user does not exist in Intune. And when managing hundreds or thousands of Intune devices this is an impossible task to do manually.

So this new script will help you to:
- Automatically discover the “primary user” of each device by analyzing sign-in logs
- Programmatically assign that user as the device’s “Primary User” in Intune
- Sort devices into Azure AD groups based on primary user domain suffix
- Produce a clear, tabular report of exactly what changed (or didn’t)
- And has built-in WhatIf support, verbose logging, retry logic, and throttling safeguards
The new things in the script is a matching object to match a group and it´s ID to a domain suffix:
$groupMatching = @(
@{
GroupName = "SWE Devices"
GroupId = "08c2f2e1-c2c2-416d-bb1c-853b9732a321"
Domain = "tbone.se"
},
@{
GroupName = "USA Devices"
GroupId = "08c2f2e1-c2c2-416d-bb1c-853b9732a322"
Domain = "tbone.com"
},
@{
GroupName = "NOR Devices"
GroupId = "08c2f2e1-c2c2-416d-bb1c-853b9732a323"
Domain = "tbone.no"
}
)
The script then uses this in a new function to add the device to the correct group and remove it from all the other groups in that list.
function Invoke-GroupMemberships {
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory = $true)]
[PSCustomObject]$Device,
[Parameter(Mandatory = $true)]
[array]$AllMembersWithGroupInfo,
[Parameter(Mandatory = $false)]
[string]$PrimaryUserPrincipalName,
[Parameter(Mandatory = $true)]
[PSCustomObject]$GroupMatching
)
Begin {
$MemoryUsage = [Math]::Round(([System.GC]::GetTotalMemory($false) / 1MB), 2)
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Function started. Memory usage: $MemoryUsage MB"
# Initialize result object to track group actions
$groupActionResult = [PSCustomObject]@{
Action = "None"
NewValue = ""
Status = "Not Processed"
}
$targetGroup = $null
$userDomain = $null
} Process {
try {
# Validate GroupMatching array
if (-not $GroupMatching -or $GroupMatching.Count -eq 0) {
throw "GroupMatching array is empty or null. Cannot determine target group."
}
# Determine the correct group based on primary user domain
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Processing device: $($Device.DeviceName) with Azure AD ID: $($Device.ObjectId) and Primary User: $PrimaryUserPrincipalName"
# Determine target group scenario and select appropriate group
$targetGroupScenario = switch ($true) {
([string]::IsNullOrEmpty($PrimaryUserPrincipalName) -or $PrimaryUserPrincipalName -notlike '*@*') { "NoValidUser" }
default {
# Safe email domain extraction
$emailParts = $PrimaryUserPrincipalName.Split('@')
if ($emailParts.Count -lt 2 -or [string]::IsNullOrEmpty($emailParts[1])) {
"NoValidUser"
}
elseif (-not ($GroupMatching | Where-Object { $_.Domain -eq $emailParts[1] })) {
"NoMatchingDomain"
}
else {
"MatchingDomain"
}
}
}
switch ($targetGroupScenario) {
"NoValidUser" {
$targetGroup = $GroupMatching[0]
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),No valid primary user found, using default group '$($targetGroup.GroupName)'"
}
"NoMatchingDomain" {
$emailParts = $PrimaryUserPrincipalName.Split('@')
$userDomain = $emailParts[1]
$targetGroup = $GroupMatching[0]
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),No matching group found for domain '$userDomain', using default group '$($targetGroup.GroupName)'"
}
"MatchingDomain" {
$emailParts = $PrimaryUserPrincipalName.Split('@')
$userDomain = $emailParts[1]
$targetGroup = $GroupMatching | Where-Object { $_.Domain -eq $userDomain }
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Found matching group '$($targetGroup.GroupName)' for domain '$userDomain'"
}
}
# Validate target group was found
if (-not $targetGroup) {
throw "Failed to determine target group for device $($Device.DeviceName)"
}# Check current group memberships for this device
$currentMemberships = $AllMembersWithGroupInfo | Where-Object { $_.MemberId -eq $Device.ObjectID }
$isInCorrectGroup = $currentMemberships | Where-Object { $_.GroupId -eq $targetGroup.GroupId }
$wrongGroups = $currentMemberships | Where-Object { $_.GroupId -ne $targetGroup.GroupId }
# Remove from wrong groups first
if ($wrongGroups) {
# Changing group: remove from wrong groups
$groupActionResult.Action = 'Change'
# Set NewValue to target group name for reporting
$groupActionResult.NewValue = $targetGroup.GroupName
foreach ($wrongGroup in $wrongGroups) {
write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Device $($Device.DeviceName) is in wrong group $($wrongGroup.GroupName), removing from it"
$removeTarget = "Device $($Device.DeviceName) from group $($wrongGroup.GroupName)"
if ($PSCmdlet.ShouldProcess($removeTarget, 'Remove')) {
try {
Invoke-MgGraphRequestSingle `
-RunProfile 'v1.0' `
-Method 'DELETE' `
-Object "groups/$($wrongGroup.GroupId)/members/$($Device.ObjectId)/`$ref"
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Successfully removed $removeTarget"
$groupActionResult.Status = "Success"
}
catch {
Write-Warning "$($script:GetTimestamp.Invoke()),Warning,$($MyInvocation.MyCommand.Name),Failed to remove $removeTarget with error: $($_.Exception.Message)"
$groupActionResult.Status = "Failed: $($_.Exception.Message)"
}
} else {
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),WhatIf: Would remove $removeTarget"
$groupActionResult.Status = "WhatIf"
}
}
} else {
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),No wrong groups for device $($Device.DeviceName)"
}
# Add to correct group if missing
if (-not $isInCorrectGroup) {
$addTarget = "Device $($Device.DeviceName) to group $($targetGroup.GroupName)"
if ($PSCmdlet.ShouldProcess($addTarget, 'Add')) {
try {
$JsonDepth = if ($PSVersionTable.PSVersion -ge [version]'6.0.0') { 10 } else { 2 }
$GraphBody = @{ '@odata.id' = "https://graph.microsoft.com/v1.0/devices/$($Device.ObjectId)" } | ConvertTo-Json -Depth $JsonDepth
Invoke-MgGraphRequestSingle -RunProfile 'v1.0' -Method 'POST' -Body $GraphBody -Object "groups/$($targetGroup.GroupId)/members/`$ref"
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Successfully added $addTarget"
$script:ReportProgress.Success++
$groupActionResult.Action = 'Add'
$groupActionResult.NewValue = $targetGroup.GroupName
$groupActionResult.Status = 'Success'
} catch {
Write-Warning "$($script:GetTimestamp.Invoke()),Warning,$($MyInvocation.MyCommand.Name),Failed to add $addTarget with error: $($_.Exception.Message)"
$script:ReportProgress.Failed++
$groupActionResult.Action = 'Add'
$groupActionResult.NewValue = $targetGroup.GroupName
$groupActionResult.Status = "Failed: $($_.Exception.Message)"
}
} else {
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),WhatIf: Would add $addTarget"
$script:ReportProgress.Skipped++
$groupActionResult.Action = 'Add'
$groupActionResult.NewValue = $targetGroup.GroupName
$groupActionResult.Status = 'WhatIf'
}
} else {
# No wrong groups detected
}
# Add to correct group if missing
if (-not $isInCorrectGroup) {
$addTarget = "Device $($Device.DeviceName) to group $($targetGroup.GroupName)"
if ($PSCmdlet.ShouldProcess($addTarget, 'Add')) {
try {
# ... existing add logic ...
} catch {
# ... existing catch ...
}
} else {
$script:ReportProgress.Skipped++
$groupActionResult.Action = 'Add'
$groupActionResult.NewValue = $targetGroup.GroupName
$groupActionResult.Status = 'WhatIf'
}
} elseif (-not $wrongGroups) {
# Already in correct group with no wrong groups; skip
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Device $($Device.DeviceName) already in correct group $($targetGroup.GroupName), no action needed"
$script:ReportProgress.Skipped++
$groupActionResult.Action = 'None'
$groupActionResult.NewValue = $targetGroup.GroupName
$groupActionResult.Status = 'AlreadyInGroup'
} else {
# Already in correct group after removals; no additional action
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Device $($Device.DeviceName) now in correct group $($targetGroup.GroupName) after removals"
$groupActionResult.Status = 'Success'
}
}
catch {
Write-Error "$($script:GetTimestamp.Invoke()),Error,$($MyInvocation.MyCommand.Name),Failed to process group memberships for device $($Device.DeviceName): $_"
throw
}
}
End {
# Return the group action result object
$MemoryUsage = [Math]::Round(([System.GC]::GetTotalMemory($false) / 1MB), 2)
Write-Verbose "$($script:GetTimestamp.Invoke()),Info,$($MyInvocation.MyCommand.Name),Function finished. Memory usage: $MemoryUsage MB"
return $groupActionResult
}
}
I also had to change the reporting module a bit to report the results in a good way when running in Azure Automate.
If you’re still managing Intune Primary Users and group membership by hand or with half-baked scripts give Intune-SetPrimaryUserAndGroup.ps1
a try.