Automating User Affinity Assignment from Configuration Manager to Entra Groups

Overview

In a hybrid environment, you can have multiple unified endpoint management solutions in your environment, and choosing the right one to package the application can be confusing. Some endpoint management solutions include Intune, MECM, Citrix and Jamf Pro to name a few. Depending on requests or scope, one might be better than another for a period of time. But with cloud based UEMs, new features are constantly becoming generally available thus incentivizing application packages in the cloud. As companies move towards a cloud-only environment, the applications are expected to be moved to the cloud as well.

MECM Device Collection to Entra User Group

The issue at hand was an application being deployed to a device collection in MECM (formerly SCCM). With single sign on and user-based licensing, it can be easier to assign apps to users rather than devices. Here are the prerequisites for the script to work.

Prerequisite Steps

Script to Assign the Primary User of a CM Device to an Entra Group

We want to get the primary user assigned to the device in MECM, then use that value and add the user to the Entra group.

Script Parameters

We declare our parameters to use in the script and import the necessary modules. We also connect to Graph using Connect-MGGraph cmdlet and import our CSV file.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
param (
[String] $groupName,
[String] $siteCode,
[String] $managementPointServer
)
Import-module ConfigurationManager
Import-Module Microsoft.Graph.Beta.Groups
Import-Module Microsoft.Graph.Beta.Users
Connect-MgGraph -Scopes 'Group.ReadWrite.All', 'User.Read'
$workstations = Import-Csv -path "Workstations.csv"
param ( [String] $groupName, [String] $siteCode, [String] $managementPointServer ) Import-module ConfigurationManager Import-Module Microsoft.Graph.Beta.Groups Import-Module Microsoft.Graph.Beta.Users Connect-MgGraph -Scopes 'Group.ReadWrite.All', 'User.Read' $workstations = Import-Csv -path "Workstations.csv"
param (
    [String] $groupName,
    [String] $siteCode,
    [String] $managementPointServer
)
Import-module ConfigurationManager
Import-Module Microsoft.Graph.Beta.Groups
Import-Module Microsoft.Graph.Beta.Users

Connect-MgGraph -Scopes 'Group.ReadWrite.All', 'User.Read'

$workstations = Import-Csv -path "Workstations.csv"
Parameter NameDescription
$groupNameName of the Entra Group to assign the users
$siteCodeAlphanumeric identifier assigned to each Configuration Manager site
$managementPointServerThe Main Point site server in your MECM environment

Connect to Configuration Manager Site

There are numerous blog posts and resources online with similar functions, I referenced and slightly modified the function from an SCCMPowerShell post.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
function Connect-CMSite {
if($null -eq (Get-Module ConfigurationManager)) {
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
}
if($null -eq (Get-PSDrive -Name $siteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) {
New-PSDrive -Name $siteCode -PSProvider CMSite -Root $managementPointServer
}
Set-Location "$($siteCode):\"
}
function Connect-CMSite { if($null -eq (Get-Module ConfigurationManager)) { Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" } if($null -eq (Get-PSDrive -Name $siteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) { New-PSDrive -Name $siteCode -PSProvider CMSite -Root $managementPointServer } Set-Location "$($siteCode):\" }
function Connect-CMSite {

    if($null -eq (Get-Module ConfigurationManager)) {
        Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    }
    
    if($null -eq (Get-PSDrive -Name $siteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) {
        New-PSDrive -Name $siteCode -PSProvider CMSite -Root $managementPointServer  
    }

    Set-Location "$($siteCode):\" 
}

The

$ENV:SMS_ADMIN_UI_PATH
$ENV:SMS_ADMIN_UI_PATH should be an environmental variable on the server hosting the site system. This variable points to the location of the Configuration Manager console installation files, including the ConfigurationManager.psd1 module manifest file.

Use Microsoft Graph to Get Group Object and its Members

We get the group object and use its Id. Each member is a IMicrosoftGraphUser object so we can retrieve all attributes such as UserPrincipalName. Open the link to view all available properties on the object. We also call the Connect-CMSite function we declared to connect to our site.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
$entraGroup = Get-MgBetaGroup -Filter "DisplayName eq '$groupName'"
$entraGroupMembers = Get-MgBetaGroupMember -GroupId $entraGroup.Id
Connect-CMSite
$entraGroup = Get-MgBetaGroup -Filter "DisplayName eq '$groupName'" $entraGroupMembers = Get-MgBetaGroupMember -GroupId $entraGroup.Id Connect-CMSite
$entraGroup = Get-MgBetaGroup -Filter "DisplayName eq '$groupName'"
$entraGroupMembers = Get-MgBetaGroupMember -GroupId $entraGroup.Id

Connect-CMSite

Logic to Add Users to Entra Group with Graph

We loop through our array of workstation created by importing the CSV file. We use the Get-CMUserDeviceAffinity to retrieve the primary user of the device, and then use some string manipulation to parse it as a valid userPrincipalName.

The output of the property UniqueUserName is like this: DOMAIN\SamAccountName

So, we split the string to retrieve the SamAccountName and add the domain DNS root. We get the array of IMicrosoftGraphUser objects returned by Get-MgBetaGroupMember. Just a note you have to expand with AdditionalProperty to access the IMicrosoftGraphuser object. I also put some conditional logic using simple regex to exclude any accounts you don’t want to add. You can certainly modify the regex as you please. We then use Get-MgBetaUser to get the IMicrosoftGraphuser object, and we use the Id property to pass it into New-MgBetaGroupMember. You can wrap it in a try catch and use appropriate error handling. When the loop finishes, we set the path back to the location of the script.

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
foreach($workstation in $workstations){
$userAffinity = Get-CMUserDeviceAffinity -DeviceName $workstation
$upn = if($null -ne $userAffinity.UniqueUserName){"$($userAffinity.UniqueUserName.split("\")[1])@$((Get-ADDomain).DNSRoot)"} else {Continue}
if(($entraGroupMembers.AdditionalProperties.userPrincipalName) -notcontains $upn){
# Write-Output $upn
if($upn -notmatch '^excludedUser'){
$entraUser = Get-MgBetaUser -Filter "UserPrincipalName eq '$upn'"
Write-Host "Adding $($entraUser.UserPrincipalName) to $groupName" -ForegroundColor Green
New-MgBetaGroupMember -GroupId $entraGroup.Id -DirectoryObjectId $entraUser.Id
}
}
}
Set-Location $PSScriptRoot
foreach($workstation in $workstations){ $userAffinity = Get-CMUserDeviceAffinity -DeviceName $workstation $upn = if($null -ne $userAffinity.UniqueUserName){"$($userAffinity.UniqueUserName.split("\")[1])@$((Get-ADDomain).DNSRoot)"} else {Continue} if(($entraGroupMembers.AdditionalProperties.userPrincipalName) -notcontains $upn){ # Write-Output $upn if($upn -notmatch '^excludedUser'){ $entraUser = Get-MgBetaUser -Filter "UserPrincipalName eq '$upn'" Write-Host "Adding $($entraUser.UserPrincipalName) to $groupName" -ForegroundColor Green New-MgBetaGroupMember -GroupId $entraGroup.Id -DirectoryObjectId $entraUser.Id } } } Set-Location $PSScriptRoot
foreach($workstation in $workstations){

    $userAffinity = Get-CMUserDeviceAffinity -DeviceName $workstation
    $upn = if($null -ne $userAffinity.UniqueUserName){"$($userAffinity.UniqueUserName.split("\")[1])@$((Get-ADDomain).DNSRoot)"} else {Continue}

    if(($entraGroupMembers.AdditionalProperties.userPrincipalName) -notcontains $upn){
        # Write-Output $upn
        if($upn -notmatch '^excludedUser'){
            $entraUser = Get-MgBetaUser -Filter "UserPrincipalName eq '$upn'"
            Write-Host "Adding $($entraUser.UserPrincipalName) to $groupName" -ForegroundColor Green
            New-MgBetaGroupMember -GroupId $entraGroup.Id -DirectoryObjectId $entraUser.Id
        }
    }
}

Set-Location $PSScriptRoot

Calling the Script

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
addUserFromCMDevice.ps1 `
-groupName "Entra Group Name" `
-siteCode "SITE" `
-managementPointServer "mpsiteserver.dnssuffix.com"
addUserFromCMDevice.ps1 ` -groupName "Entra Group Name" ` -siteCode "SITE" ` -managementPointServer "mpsiteserver.dnssuffix.com"
addUserFromCMDevice.ps1 `
    -groupName "Entra Group Name" `
    -siteCode "SITE" `
    -managementPointServer "mpsiteserver.dnssuffix.com"

Completed Script

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
param (
[String] $groupName,
[String] $siteCode,
[String] $managementPointServer
)
Import-module ConfigurationManager
Import-Module Microsoft.Graph.Beta.Groups
Import-Module Microsoft.Graph.Beta.Users
Connect-MgGraph -Scopes 'Group.ReadWrite.All', 'User.Read'
$workstations = Import-Csv -path "Workstations.csv"
function Connect-CMSite {
if($null -eq (Get-Module ConfigurationManager)) {
Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
}
if($null -eq (Get-PSDrive -Name $siteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) {
New-PSDrive -Name $siteCode -PSProvider CMSite -Root $managementPointServer
}
Set-Location "$($siteCode):\"
}
$entraGroup = Get-MgBetaGroup -Filter "DisplayName eq '$groupName'"
$entraGroupMembers = Get-MgBetaGroupMember -GroupId $entraGroup.Id
Connect-CMSite
foreach($workstation in $workstations){
$userAffinity = Get-CMUserDeviceAffinity -DeviceName $workstation
$upn = if($null -ne $userAffinity.UniqueUserName){"$($userAffinity.UniqueUserName.split("\")[1])@$((Get-ADDomain).DNSRoot)"} else {Continue}
if(($entraGroupMembers.AdditionalProperties.userPrincipalName) -notcontains $upn){
if($upn -notmatch '^excludedUser'){
$entraUser = Get-MgBetaUser -Filter "UserPrincipalName eq '$upn'"
Write-Host "Adding $($entraUser.UserPrincipalName) to $groupName" -ForegroundColor Green
New-MgBetaGroupMember -GroupId $entraGroup.Id -DirectoryObjectId $entraUser.Id
}
}
}
Set-Location $PSScriptRoot
param ( [String] $groupName, [String] $siteCode, [String] $managementPointServer ) Import-module ConfigurationManager Import-Module Microsoft.Graph.Beta.Groups Import-Module Microsoft.Graph.Beta.Users Connect-MgGraph -Scopes 'Group.ReadWrite.All', 'User.Read' $workstations = Import-Csv -path "Workstations.csv" function Connect-CMSite { if($null -eq (Get-Module ConfigurationManager)) { Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1" } if($null -eq (Get-PSDrive -Name $siteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) { New-PSDrive -Name $siteCode -PSProvider CMSite -Root $managementPointServer } Set-Location "$($siteCode):\" } $entraGroup = Get-MgBetaGroup -Filter "DisplayName eq '$groupName'" $entraGroupMembers = Get-MgBetaGroupMember -GroupId $entraGroup.Id Connect-CMSite foreach($workstation in $workstations){ $userAffinity = Get-CMUserDeviceAffinity -DeviceName $workstation $upn = if($null -ne $userAffinity.UniqueUserName){"$($userAffinity.UniqueUserName.split("\")[1])@$((Get-ADDomain).DNSRoot)"} else {Continue} if(($entraGroupMembers.AdditionalProperties.userPrincipalName) -notcontains $upn){ if($upn -notmatch '^excludedUser'){ $entraUser = Get-MgBetaUser -Filter "UserPrincipalName eq '$upn'" Write-Host "Adding $($entraUser.UserPrincipalName) to $groupName" -ForegroundColor Green New-MgBetaGroupMember -GroupId $entraGroup.Id -DirectoryObjectId $entraUser.Id } } } Set-Location $PSScriptRoot
param (
    [String] $groupName,
    [String] $siteCode,
    [String] $managementPointServer
)

Import-module ConfigurationManager
Import-Module Microsoft.Graph.Beta.Groups
Import-Module Microsoft.Graph.Beta.Users

Connect-MgGraph -Scopes 'Group.ReadWrite.All', 'User.Read'

$workstations = Import-Csv -path "Workstations.csv"

function Connect-CMSite {

    if($null -eq (Get-Module ConfigurationManager)) {
        Import-Module "$($ENV:SMS_ADMIN_UI_PATH)\..\ConfigurationManager.psd1"
    }
    
    if($null -eq (Get-PSDrive -Name $siteCode -PSProvider CMSite -ErrorAction SilentlyContinue)) {
        New-PSDrive -Name $siteCode -PSProvider CMSite -Root $managementPointServer  
    }

    Set-Location "$($siteCode):\" 
}

$entraGroup = Get-MgBetaGroup -Filter "DisplayName eq '$groupName'"
$entraGroupMembers = Get-MgBetaGroupMember -GroupId $entraGroup.Id

Connect-CMSite

foreach($workstation in $workstations){

    $userAffinity = Get-CMUserDeviceAffinity -DeviceName $workstation
    $upn = if($null -ne $userAffinity.UniqueUserName){"$($userAffinity.UniqueUserName.split("\")[1])@$((Get-ADDomain).DNSRoot)"} else {Continue}

    if(($entraGroupMembers.AdditionalProperties.userPrincipalName) -notcontains $upn){

        if($upn -notmatch '^excludedUser'){
            $entraUser = Get-MgBetaUser -Filter "UserPrincipalName eq '$upn'"
            Write-Host "Adding $($entraUser.UserPrincipalName) to $groupName" -ForegroundColor Green
            New-MgBetaGroupMember -GroupId $entraGroup.Id -DirectoryObjectId $entraUser.Id
        }
    }
}

Set-Location $PSScriptRoot

Conclusion

In summary, this script automates the process of adding users to a specified group in Entra based on workstation usage. It imports workstation data, connects to the Configuration Manager site, retrieves group information, and iterates through workstations to determine user affinity. If a user is not already a member of the Entra group and is not excluded, the script adds them to the group. Finally, it ensures the script returns to its original location upon completion. if you have any suggestions drop a comment or Contact me.

Leave a Reply