Intune How to: Dynamic Registry Configuration Based on Entra ID Group Membership
- Sebastian F. Markdanner
- Nov 18, 2024
- 11 min read
Updated: Nov 20, 2024
While I work on the next part of my Identity Governance series, I’ve got a configuration gem to share.

As a firm believer in leaving servers as a footnote in our tech history, I often guide my clients on their journey to a cloud-only environment. A top priority in this journey? Migrating endpoints out of the on-prem domain—because, frankly, there’s rarely a need to keep endpoints tethered to a traditional domain anymore. This transition also tightens security, enabling server isolation and reducing our attack surface.
Table of content
The Mission Brief: Streamlining Intune Registry Configuration
Recently, I was tasked with helping a client move their endpoints from a domain-joined setup to an Entra-joined configuration. This involved combing through their existing Group Policy Objects (GPOs) to ensure a smooth migration.
This client had a long history with GPOs, resulting in a tangled web of settings: many GPOs clashed, became obsolete, or were simply inefficient for the task at hand. While I was able to discard about 90% of these GPOs—either because they weren’t needed for the Entra Joined clients or were better handled in Intune—one stubborn legacy app required extra attention. This app, an Outlook add-in for email archiving, relies on a registry setting in the HKCU hive to determine specific archive locations. Previously, each location was managed by a GPO, with each GPO scoped to a different user group.
Simply lifting these GPOs into Intune would have been a headache both for management and performance. Plus, it risked performance slowdowns and management complexity if a user were ever added to multiple groups.
The Question
Due to the above, the question I was trying to get a handle on ended up as such:
How do I manage dynamic changing registry key / values in the HKCU hive based on Entra ID Group Memberships?
Answer Unlocked
As a rule, I don’t port GPOs to Intune—I start fresh whenever possible. So I got to thinking: how could I implement a cleaner solution?
After talking through the case with some of my knowledgeable colleagues, sketching out potential workflows, and testing several ideas, I finally landed on a solution worth sharing.
The Powershell Script solution
Both scripts used for this solution are available on my GitHub
Required Permissions
To run these scripts, you’ll need an App Registration in Entra ID with the following API permissions:
User.Read.All
Group.Read.All
Installation Script
<#
.SYNOPSIS
Fetches group membership information for the currently logged-in user from Microsoft Graph API and configures registry settings based on group memberships.
.DESCRIPTION
This script leverages the Microsoft Graph API to retrieve the group memberships of the logged-in user within Microsoft Entra ID.
Based on these group memberships, the script applies specific registry values associated with each group. Additionally, the
user's SID is dynamically retrieved and saved to a specified file location, allowing for reuse for detection.
Static registry values are also applied regardless of group membership.
.NOTES
Author: Sebastian Flæng Markdanner
Website: https://chanceofsecurity.com
Email: Sebastian.Markdanner@chanceofsecurity.com
Version: 1.1
Date: 12-11-2024
#>
# Configurable Variables. Modify as needed
$tenantId = "YOUR_TENANT_ID" # Microsoft tenant ID
$clientId = "YOUR_CLIENT_ID" # Microsoft client ID for API access
$clientSecret = "YOUR_CLIENT_SECRET" # Secret for API authentication
$resource = "https://graph.microsoft.com" # Microsoft Graph API resource URL
$graphApiUrl = "https://graph.microsoft.com/v1.0" # Microsoft Graph API base URL
$SIDFilePath = "C:\ProgramData\Microsoft\<company>\SID.txt" # Path to save the user's SID
$domain = "@yourdomain.com" # Domain suffix to complete UPN
$regPath = "Software\ExampleApp" # Registry path for setting values
# Group-to-FilePath Mappings based on user group membership. Modify as needed.
$GroupToFilePath = @{
'Group01' = "D:\Example\Path"
'Group02' = "H:\Example\Path"
'Group03' = "D:\Example\Path\SubPath"
'Group04' = "D:\Example"
'Group05' = "P:\Example\Path"
}
# Static Registry Values to be Set
$StaticRegValues = @{
"StaticKey01" = "true"
"StaticKey02" = "true"
"StaticKey03" = "false"
"StaticKey04" = "false"
"StaticKey05" = "false"
}
# Function to load the user's registry hive, set values, and unload it using reg.exe
function Set-UserRegistryValues {
param (
[string]$UserSID,
[string]$FilePath
)
# Retrieve the user profile path dynamically based on SID
$userProfilePath = Get-UserProfilePath -UserSID $UserSID
if (-not $userProfilePath) {
Write-Log "Unable to retrieve user profile path for SID: $UserSID"
return
}
# Define path to the user's NTUSER.DAT file, used to load their registry hive
$regHivePath = "$userProfilePath\NTUSER.DAT"
# Load the user registry hive into HKEY_USERS if not already loaded
if (-not (Test-Path "HKU\$UserSID")) {
reg.exe load "HKU\$UserSID" $regHivePath
}
# Registry key path for user settings under HKEY_USERS
$RegKey = "HKU\$UserSID\$regPath"
# Initialize a new hashtable for registry values and add static values from $StaticRegValues
$RegValues = @{}
$StaticRegValues.GetEnumerator() | ForEach-Object { $RegValues[$_.Key] = $_.Value }
# Add the dynamic file path based on group membership to the registry values
$RegValues["DynamicFilePath"] = $FilePath
# Ensure the registry key exists
reg.exe add "$RegKey" /f
# Create or modify each registry property using reg.exe, based on the values in $RegValues
foreach ($item in $RegValues.GetEnumerator()) {
$name = $item.Key
$value = $item.Value
# Check if the registry value exists before adding or modifying
Write-Host "Checking if registry key: $name exists"
$regQuery = reg.exe query "$RegKey" /v $name 2>&1
if ($regQuery -like "*ERROR*") {
# If the registry value does not exist, add it
Write-Host "Adding registry key: $name with value: $value"
reg.exe add "$RegKey" /v $name /t REG_SZ /d $value /f
} else {
# If the registry value exists, modify it
Write-Host "Modifying existing registry key: $name with new value: $value"
reg.exe add "$RegKey" /v $name /t REG_SZ /d $value /f
}
}
Write-Log "Registry values applied successfully for FilePath: $FilePath"
}
# Function to retrieve the UPN (User Principal Name) of the currently logged-in user
function Get-LoggedInUserUPN {
# Use WMI to get the currently logged-in user's information
$loggedInUser = (Get-WmiObject -Class Win32_ComputerSystem | Select-Object -ExpandProperty UserName)
if ($loggedInUser -and $loggedInUser -like "*\*") {
# If username is in domain\username format, convert to UPN format
$loggedInUser = $loggedInUser.Split('\')[1] + $domain
}
return $loggedInUser
}
# Function to retrieve the SID of the logged-on user
function Get-LoggedOnUserSID {
# Retrieve all logged-on users
$loggedOnUsers = Get-LoggedOnUser
# Filter to get the active or console session user
$activeUser = $loggedOnUsers | Where-Object { $_.IsActiveUserSession -eq $true }
if ($activeUser) {
return $activeUser.SID
} else {
Write-Log "No active user session found."
return $null
}
}
# Function to save the user's SID to a specified file path
function Save-SIDToFile {
param (
[string]$UserSID,
[string]$filePath
)
try {
# Extract the directory path from the file path
$directoryPath = Split-Path -Path $filePath -Parent
# Check if the directory exists, and create it if necessary
if (-not (Test-Path -Path $directoryPath)) {
Write-Log "Directory does not exist, creating: $directoryPath"
New-Item -Path $directoryPath -ItemType Directory -Force
}
# Save the SID to the specified file
Write-Log "Saving SID to file: $filePath"
$UserSID | Out-File -FilePath $filePath -Force
Write-Log "Successfully saved SID: $UserSID to $filePath"
} catch {
Write-Log "Failed to save SID to file: $($_.Exception.Message)"
}
}
# Retrieve Microsoft Graph API token using service principal credentials
$token = Get-GraphToken -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret -resource $resource
# Retrieve the UPN of the currently logged-in user
$userPrincipalName = Get-LoggedInUserUPN
if (-not $userPrincipalName) {
Write-Log "No logged-in user found, exiting."
exit 1
}
Write-Log "Retrieved UPN: $userPrincipalName"
# URL Encode the UPN to include in API requests
$encodedUpn = [System.Web.HttpUtility]::UrlEncode($userPrincipalName)
# Query Microsoft Graph for the user's group memberships
$headers = @{
Authorization = "Bearer $token"
}
$graphApiUrlWithUpn = "$graphApiUrl/users/$encodedUpn/memberOf"
Write-Log "Graph API URL: $graphApiUrlWithUpn"
try {
$graphResponse = Invoke-RestMethod -Uri $graphApiUrlWithUpn -Headers $headers -Method Get
$userGroups = $graphResponse.value | ForEach-Object { $_.displayName }
Write-Log "User is a member of the following groups: $($userGroups -join ', ')"
} catch {
Write-Log "Failed to retrieve group memberships from Microsoft Graph. Error: $($_.Exception.Message)"
}
# Retrieve the SID of the logged-in user and save it to a file
$userSID = Get-LoggedOnUserSID
Write-Log "Retrieved user SID: $userSID"
# Save SID to the specified file if it was successfully retrieved
if ($userSID) {
Save-SIDToFile -UserSID $userSID -FilePath $SIDFilePath
Write-Log "SID saved to $SIDFilePath"
} else {
Write-Log "No SID was found to save."
}
# Loop through the user's groups and apply the appropriate registry values
foreach ($group in $userGroups) {
if ($GroupToFilePath.ContainsKey($group)) {
$filePath = $GroupToFilePath[$group]
Write-Log "Applying registry changes for group: $group, FilePath: $filePath"
# Set the registry values for the application based on the user's SID
Set-UserRegistryValues -UserSID $userSID -FilePath $filePath
}
}
This script uses the Microsoft Graph API to retrieve the currently logged-in user’s group memberships within Microsoft Entra ID and configures registry settings accordingly, in the HKU Hive. The script dynamically retrieves the user’s SID, saving it for detection, and applies a set of static registry values for all users.
NOTE: The script is build around PSAppDeploymentToolkit, as it reuses some of the functions and methods from the toolkit.
Detection Script
<#
.SYNOPSIS
Detects if registry settings match the expected configuration based on the group memberships of the currently logged-in user, fetched from Microsoft Graph API.
.DESCRIPTION
This script retrieves the group memberships of the currently logged-in user from Microsoft Entra ID using Microsoft Graph API.
It checks if the registry settings match the expected values for these groups, verifying the `FilePath` registry value for the user profile based on membership.
The script dynamically retrieves the user's SID, loading their registry hive if needed, and logs output for successful verification or mismatches.
A static set of registry values is also checked and compared for all users.
.NOTES
Author: Sebastian Flæng Markdanner
Website: https://chanceofsecurity.com
Email: Sebastian.Markdanner@chanceofsecurity.com
Version: 1.3
Date: 12-11-2024
#>
function Log {
# Logs a message with a timestamp
[CmdletBinding()]
param (
[Parameter(Mandatory=$false)] [String] $message
)
$ts = get-date -f "yyyy/MM/dd hh:mm:ss tt"
Write-Output "$ts $message"
}
# Configurable Variables
$tenantId = "YOUR_TENANT_ID" # Microsoft tenant ID
$clientId = "YOUR_CLIENT_ID" # Microsoft client ID for API access
$clientSecret = "YOUR_CLIENT_SECRET" # Secret for API authentication
$resource = "https://graph.microsoft.com" # Microsoft Graph API resource URL
$graphApiUrl = "https://graph.microsoft.com/v1.0" # Microsoft Graph API base URL
$SIDFilePath = "C:\ProgramData\Microsoft\<company>\SID.txt" # Path to save the user's SID
$domain = "@yourdomain.com" # Domain suffix to complete UPN
$regPath = "Software\ExampleApp" # Registry path for checking values
$logPath = "C:\ProgramData\Microsoft\IntuneManagementExtension\Logs" # Directory for log file
$logFile = "PS-<LOGFILEPATH>-v1.0.log" # Log filename
# Start logging
Start-Transcript -Path "$($logPath)\$logFile" -Append
# Group-to-FilePath mappings based on user group membership
$GroupToFilePath = @{
'Group01' = "D:\Example\Path"
'Group02' = "H:\Example\Path"
'Group03' = "D:\Example\Path\SubPath"
'Group04' = "D:\Example"
'Group05' = "P:\Example\Path"
}
# Function to read the user's SID from a specified file
function Get-SIDFromFile {
param ([string]$SIDFilePath)
if (Test-Path $SIDFilePath) {
$SID = Get-Content -Path $SIDFilePath -ErrorAction Stop
if (-not [string]::IsNullOrEmpty($SID)) {
return $SID.Trim()
} else {
Log "SID file is empty."
exit 1
}
} else {
Log "SID file not found at: $SIDFilePath"
exit 1
}
}
# Function to obtain an access token for Microsoft Graph API
function Get-GraphToken {
param (
[string]$tenantId,
[string]$clientId,
[string]$clientSecret,
[string]$resource
)
$body = @{
grant_type = "client_credentials"
client_id = $clientId
client_secret = $clientSecret
scope = "$resource/.default"
}
$response = Invoke-RestMethod -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/v2.0/token" -ContentType "application/x-www-form-urlencoded" -Body $body
return $response.access_token
}
# Function to retrieve the UPN (User Principal Name) of the currently logged-in user
function Get-LoggedInUserUPN {
$loggedInUser = (Get-WmiObject -Class Win32_ComputerSystem | Select-Object -ExpandProperty UserName)
if ($loggedInUser -and $loggedInUser -like "*\*") {
$loggedInUser = $loggedInUser.Split('\')[1] + $domain
}
return $loggedInUser
}
# Function to load the user's registry hive if necessary
function Load-UserRegistryHive {
param ([string]$UserSID)
if (-not (Test-Path "HKU\$UserSID")) {
$userProfilePath = (Get-WmiObject -Class Win32_UserProfile | Where-Object { $_.SID -eq $UserSID }).LocalPath
$regHivePath = "$userProfilePath\NTUSER.DAT"
if (Test-Path $regHivePath) {
reg.exe load "HKU\$UserSID" $regHivePath
Log "User registry hive loaded for SID: $UserSID"
} else {
Log "Could not find NTUSER.DAT for user: $UserSID"
exit 1
}
} else {
Log "User registry hive already loaded for SID: $UserSID"
}
}
# Retrieve Microsoft Graph API token
$token = Get-GraphToken -tenantId $tenantId -clientId $clientId -clientSecret $clientSecret -resource $resource
# Retrieve the current logged-in user's UPN
$userPrincipalName = Get-LoggedInUserUPN
if (-not $userPrincipalName) {
Log "No logged-in user found, exiting."
exit 1
}
Log "Retrieved UPN: $userPrincipalName"
# URL Encode the UPN for use in API requests
$encodedUpn = [System.Web.HttpUtility]::UrlEncode($userPrincipalName)
# Query Microsoft Graph for group memberships of the user
$headers = @{
Authorization = "Bearer $token"
}
$graphApiUrlWithUpn = "$graphApiUrl/users/$encodedUpn/memberOf"
try {
$graphResponse = Invoke-RestMethod -Uri $graphApiUrlWithUpn -Headers $headers -Method Get
$userGroups = $graphResponse.value | ForEach-Object { $_.displayName }
Log "User is a member of $userGroups"
} catch {
Log "Failed to retrieve group memberships from Microsoft Graph. Error: $_"
exit 1
}
# Retrieve user's SID from the SID file
$userSID = Get-SIDFromFile -SIDFilePath $SIDFilePath
if (-not $userSID) {
Log "No SID was found in the file, exiting."
exit 1
}
Log "Retrieved User SID from file: $userSID"
# Load the user's registry hive if required
Load-UserRegistryHive -UserSID $userSID
# Check if the registry key for the application exists and matches expected values
try {
$regKeyExists = Get-ItemProperty -Path "Registry::HKU\$userSID\$regPath" -ErrorAction Stop
Log "Registry key exists: HKU\$userSID\$regPath"
# Retrieve and verify the FilePath value from the registry
$regFilePath = $regKeyExists.FilePath
$expectedFilePath = $null
$groupMatched = $false
# Determine the expected file path based on the user's group memberships
foreach ($group in $userGroups) {
if ($GroupToFilePath.ContainsKey($group)) {
$expectedFilePath = $GroupToFilePath[$group]
$groupMatched = $true
}
}
# Verify if the registry FilePath matches the expected file path
if ($regFilePath -and $groupMatched -and $regFilePath -eq $expectedFilePath) {
Log "Registry key and FilePath are correctly set."
Write-Output "Success!"
} else {
Log "Mismatch in FilePath. Expected: $expectedFilePath, Found: $regFilePath."
exit 1
}
} catch {
Log "Registry key does not exist: HKU\$userSID\$regPath. Error: $_"
exit 1
}
# Successful validation
Log "Validation successful."
Write-Output "Success!"
Stop-Transcript
exit 0
The detection script verifies that the registry settings match the expected configuration based on group memberships. It fetches group memberships from Entra ID and checks the FilePath registry value for each user profile, dynamically retrieving the user’s SID to access their registry hive if needed. Static registry values are also checked across users.
NOTE: As this is not a RunOnce scenario, it bypasses the Active Setup registry key and instead targets the HKU Hive for registry modifications, setting values directly for the user.
Deployment Options
Deploying this script can be handled in multiple ways, specifically through:
Platform Script – This would let the script run once, which doesn’t work for this solution.
Remediation Script – This could work as it allows the script to run at regular intervals (as often as hourly) and verifies that the current registry key aligns with the Entra group membership. Strong contender however as I reuse logic from PSADT, which isn't a possibility for Remediation Scripts.
Win32 Application – Deploying the script as a Win32 application means it runs during synchronization, which happens every 8 hours by default, but can be enforced either locally or via Intune. This approach offers full Win32 capabilities, including requirements, detection, supersedence, and dependencies, and is the only option possible for this script solution.
When packaging the script using PSAppDeploymentToolkit (PSADT), you have the option to embed the script within the main Deploy-Application.ps1 file or place it in the Files folder and call it from there.
Below is an example of how to reference the script in PSADT by adding a line to the installation phase of Deploy-Application.ps1:

Once packaged with PSADT, the app needs to be converted to an .intunewin file using the Microsoft Win32 Content Prep Tool, readying it for Intune deployment.
Deploying via Intune: Step-by-Step
Step 1: Open Intune.microsoft.com and navigate to Apps > Windows.

Step 2: Click “+ Add” and select “Windows app (Win32)” on the next blade.


Step 3: Select your .intunewin file for the application.

Step 4: Configure a meaningful app name and description. Including the app version is useful for testing as it updates in the company portal when a sync is run, making it easy to ensure testing with the newest version deployed.

Side 5: Set your install and uninstall commands. For this app, we don’t need the uninstall command.

Step 6: Configure any necessary requirements based on your environment.

Step 7: Choose a custom detection script and add the provided detection script.

Step 8 (Optional): Configure supersedence or dependencies as needed.

Step 9: Assign the app to the intended scope. In this case, I deployed it to all users, scoped to Entra-joined devices via a device filter.

Once deployed, the app will start setting registry keys based on the user’s group memberships in Entra ID.
Conclusion: A Fresh Take on Registry Changes
What We’ve Learned
Implementing dynamic registry changes based on Entra ID group memberships in a cloud-only environment isn’t always straightforward. Traditional GPO methods don’t translate seamlessly to the cloud, and this process illustrates the complexity involved. However, with a bit of PowerShell wizardry and creativity, it’s absolutely doable. This solution offers a flexible, scalable approach for managing registry changes dynamically—without the headaches of legacy GPOs
And now for a quick laugh, or at least a puff of air, here's another bad joke
How does a computer get drunk?
It takes screenshots! 😎
Try It Out!
If you’re in the process of moving away from legacy setups, this approach could save you hours of manual configuration and troubleshooting. Give it a shot in your environment, and see if it simplifies your migration process.
Stay Connected!
If you found this guide helpful, explore the rest of my blog for more insights and tutorials on cloud management and identity governance. Bookmark this page and check back often—I’m regularly sharing tips and solutions for navigating the challenges of modern IT. Got questions or suggestions? Reach out or leave a comment! Let’s make the cloud migration journey easier, one post at a time.
Comments