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.

About The Author

Mr T-Bone

Torbjörn Tbone Granheden is a Solution Architect for Modern Workplace at Coligo AB. Most Valuable Professional (MVP) on Enterprise Mobility. Certified in most Microsoft technologies and over 23 years as Microsoft Certified Trainer (MCT)

You may also like...

Leave a Reply