Efficient way to manipulate HKU registry entries
In this post, we’ll explore how to reliably deploy registry changes under the HKEY_USERS (HKU) and HKEY_CURRENT_USER (HKCU) hive using Microsoft Intune. While Group Policy Preferences have long enabled administrators to target HKCU with ease, Intune’s lack of a built-in HKCU registry deployment feature forces us to adopt alternative methods. We’ll first review Intune’s proactive remediation framework and its prerequisites and then introduce a PowerShell-based solution to ensure performant registry updates for both current and future users.

Table of Contents
Registry Changes via Group Policy Preferences
Group Policy Preferences (GPP) let you define registry updates under User Configuration > Preferences > Windows Settings > Registry, where you can create or update HKCU entries in the context of the logged-on user. This built-in mechanism handles both deployment and rollback gracefully, but only applies in Active Directory–joined environments under on-premises Group Policy.
Intune’s Proactive Remediation Framework
To replicate GPP-style registry updates in Intune, Microsoft offers Remediations (apart from configuration profiles that change the user’s registry hive) which consist of paired detection and remediation scripts. The detection script examines the device state—returning 0 if compliant or 1 if not—and, on a non-zero exit code, triggers the remediation script to correct the configuration. Remediations run under the Intune Management Extension on a fixed defined schedule and can also be kicked off on-demand.
Note: Remediation scripts must be UTF-8 encoded to run correctly in Intune.
Note: It is not mandatory to run a script having both a Detection and Remediation script. You can also create a Detection only Remediations script (e.g. to return some info or perform an action that you don’t want to have a remediation).
Limitations and Workarounds for HKU registry paths
While Intune remediations excel at simple file or service checks, targeting HKU poses two main challenges:
- Context: Remediations default to the system context, where HKCU points to the system account, not end users. We can run it as user, but we will not be able to change the registry keys for other users/new users connected to the targeted machine.
- New Users: Even if you impersonate users to update their hives, new users created after script deployment won’t inherit those changes.
Common community workarounds include:
- Deploying .reg files via a Win32 app and launching them under user context on logon (fragile and lacks scalability).
- Scheduled tasks that load each user’s hive (
NTUSER.DAT) at logon to import settings (requires precise timing and error-handling).
Instead, we propose leveraging a PowerShell script to safely load and modify each user’s hive—both for existing and future profiles—under the system account, then clean up.
You can also find the code in my GitHub page.
I am always open to suggestions or improvements.
Detection Script
<#
.SYNOPSIS
Detection script for HKCU remediation: checks whether all non-system users have the desired registry values.
.DESCRIPTION
Enumerates each local user profile (excluding built-in service accounts), loads their NTUSER.DAT hive under HKEY_USERS,<SID>,
verifies each registry value under Software\SysTuNation\Settings matches the expected data and type,
then unloads the hive. Exits with code 0 if fully compliant, or 1 if any mismatch is found.
Logs all actions with Start-Transcript and captures output in $intuneOutput.
#>
param (
[string]$LogFile = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\SysTuNation_HKCUDetect.log'
)
try {
Start-Transcript -Path $LogFile -Append -NoClobber -ErrorAction Stop | Out-Null
Write-Host "Transcript started: $LogFile"
} catch {
Write-Host "ERROR: Failed to start transcript - $_"
exit 1
}
# Initialize Intune output accumulator
$script:intuneOutput = ""
function LogMessage {
param([string]$Msg)
$ts = (Get-Date).ToString('o')
$line = "$ts`t$Msg"
Write-Host $line
$script:intuneOutput += $line + "|"
}
# Define detection settings
$RegKey = 'Software\SysTuNation\Settings'
$Values = @{
'EnableFeatureX' = @{ Data = 1; Type = 'DWord' }
'DefaultRegion' = @{ Data = 'Europe'; Type = 'String' }
}
$TypeMap = @{
'String' = [Microsoft.Win32.RegistryValueKind]::String;
'DWord' = [Microsoft.Win32.RegistryValueKind]::DWord;
'QWord' = [Microsoft.Win32.RegistryValueKind]::QWord
}
LogMessage 'Starting detection of HKCU settings for all users (excluding system/service accounts).'
try {
# Get all profile SIDs, then exclude service accounts (S-1-5-18,19,20)
$profiles = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ErrorAction Stop |
Where-Object { $_.PSChildName -notmatch '^S-1-5-(18|19|20)$' } |
ForEach-Object {
[PSCustomObject]@{
SID = $_.PSChildName
HivePath = Join-Path -Path $_.GetValue('ProfileImagePath') -ChildPath 'NTUSER.DAT'
}
}
LogMessage "Profiles to check: $($profiles.Count) (system/service accounts excluded)."
} catch {
LogMessage "ERROR: Failed to enumerate profiles - $_"
Stop-Transcript | Out-Null
exit 1
}
$nonCompliant = $false
foreach ($p in $profiles) {
$sid = $p.SID
$hive = $p.HivePath
$mounted = $false
LogMessage "Processing SID: $sid"
if (-not (Test-Path $hive -PathType Leaf)) {
LogMessage "SKIP [$sid]: Hive not found at $hive"
continue
}
try {
# Load hive if not already loaded
if (-not (Test-Path "Registry::HKEY_USERS\$sid")) {
Write-Host "Loading hive for SID $sid"
& reg.exe LOAD "HKEY_USERS\$sid" $hive
if ($LASTEXITCODE -ne 0) { throw "reg.exe LOAD failed with code $LASTEXITCODE" }
$mounted = $true
LogMessage "Loaded hive: HKEY_USERS\$sid"
} else {
LogMessage "Hive already loaded: HKEY_USERS\$sid"
}
# Verify each expected value
foreach ($name in $Values.Keys) {
$fullPath = "Registry::HKEY_USERS\$sid\$RegKey"
if (-not (Test-Path $fullPath)) {
LogMessage "[$sid] Key missing: $RegKey"
$nonCompliant = $true; break
}
Write-Host "Checking value '$name' for SID $sid"
$item = Get-ItemProperty -Path $fullPath -Name $name -ErrorAction Stop
$actualData = $item.$name
$actualType = (Get-Item $fullPath).GetValueKind($name)
if ($actualData -ne $Values[$name].Data -or $actualType -ne $TypeMap[$Values[$name].Type]) {
LogMessage "[$sid] Mismatch on $name : expected '$($Values[$name].Data)' ($($Values[$name].Type)), got '$actualData' ($actualType)"
$nonCompliant = $true; break
} else {
LogMessage "[$sid] $name is compliant"
}
}
} catch {
LogMessage "ERROR [$sid]: $_"
$nonCompliant = $true
} finally {
if ($mounted) {
Write-Host "Unloading hive for SID $sid"
& reg.exe UNLOAD "HKEY_USERS\$sid"
if ($LASTEXITCODE -ne 0) {
LogMessage "WARNING [$sid]: reg.exe UNLOAD exit code $LASTEXITCODE"
} else {
LogMessage "Unloaded hive: HKEY_USERS\$sid"
}
}
}
if ($nonCompliant) { break }
}
if ($nonCompliant) {
LogMessage 'Detection result: NON-COMPLIANT'
$exitCode = 1
} else {
LogMessage 'Detection result: COMPLIANT'
$exitCode = 0
}
LogMessage 'Detection complete.'
# Stop transcript
Stop-Transcript | Out-Null
# Output accumulator for Intune
Write-Host "Detection Output: $intuneOutput"
exit $exitCode
Remediation Script
<#
.SYNOPSIS
Deploy HKCU registry values for all users via Intune (no caching).
.DESCRIPTION
Loads each user NTUSER.DAT hive, writes desired HKCU values, then unloads the hive.
Uses Start-Transcript and Write-Host for logging, with -ErrorAction Stop on critical cmdlets.
Captures all Write-Host output in $intuneOutput for later inspection.
#>
param (
[string]$LogFile = 'C:\ProgramData\Microsoft\IntuneManagementExtension\Logs\SysTuNation_HKCUDeploy.log'
)
try {
Start-Transcript -Path $LogFile -Append -NoClobber -ErrorAction Stop | Out-Null
Write-Host "Transcript started: $LogFile"
} catch {
Write-Host "ERROR: Failed to start transcript - $_"
exit 1
}
# Initialize Intune output accumulator
$script:intuneOutput = ""
function LogMessage {
param([string]$Msg)
$ts = (Get-Date).ToString('o')
$line = "$ts`t$Msg"
Write-Host $line
$script:intuneOutput += $line + "|"
}
# Define registry settings
$RegKey = 'Software\SysTuNation\Settings'
$Values = @{
'EnableFeatureX' = @{ Data = 1; Type = 'DWord' }
'DefaultRegion' = @{ Data = 'Europe'; Type = 'String' }
}
LogMessage 'Starting deployment of HKCU settings for all users (excluding system/service accounts).'
try {
# Enumerate all profile SIDs, excluding system/service accounts (S-1-5-18,19,20)
$profiles = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList' -ErrorAction Stop |
Where-Object { $_.PSChildName -notmatch '^S-1-5-(18|19|20)$' } |
ForEach-Object {
[PSCustomObject]@{
SID = $_.PSChildName
HivePath = Join-Path -Path $_.GetValue('ProfileImagePath') -ChildPath 'NTUSER.DAT'
}
}
LogMessage "Profiles to process: $($profiles.Count) (system/service accounts excluded)."
} catch {
LogMessage "ERROR: Failed to enumerate profiles - $_"
Stop-Transcript | Out-Null
exit 1
}
foreach ($p in $profiles) {
$sid = $p.SID
$hive = $p.HivePath
$mounted = $false
LogMessage "Processing SID: $sid"
if (-not (Test-Path $hive -PathType Leaf)) {
LogMessage "SKIP [$sid]: Hive not found at $hive"
continue
}
try {
# Load user hive if not already loaded
if (-not (Test-Path "Registry::HKEY_USERS\$sid")) {
Write-Host "Loading hive for SID $sid"
& reg.exe LOAD "HKEY_USERS\$sid" $hive
if ($LASTEXITCODE -ne 0) { throw "reg.exe LOAD failed with exit code $LASTEXITCODE" }
$mounted = $true
LogMessage "Loaded hive: HKEY_USERS\$sid"
} else {
LogMessage "Hive already loaded: HKEY_USERS\$sid"
}
# Apply each registry setting
foreach ($name in $Values.Keys) {
$exp = $Values[$name]
$fullPath = "Registry::HKEY_USERS\$sid\$RegKey"
if (-not (Test-Path $fullPath)) {
New-Item -Path $fullPath -Force -ErrorAction Stop | Out-Null
LogMessage "[$sid] Created key: $RegKey"
}
New-ItemProperty -Path $fullPath -Name $name `
-Value $exp.Data -PropertyType $exp.Type -Force -ErrorAction Stop | Out-Null
LogMessage "[$sid] Set $name = $($exp.Data) ($($exp.Type))"
}
} catch {
LogMessage "ERROR [$sid]: $_"
} finally {
# Unload hive if loaded
if ($mounted) {
Write-Host "Unloading hive for SID $sid"
& reg.exe UNLOAD "HKEY_USERS\$sid"
if ($LASTEXITCODE -ne 0) {
LogMessage "WARNING [$sid]: reg.exe UNLOAD exit code $LASTEXITCODE"
} else {
LogMessage "Unloaded hive: HKEY_USERS\$sid"
}
}
}
}
LogMessage 'HKCU deployment finished.'
# Stop transcript
Stop-Transcript | Out-Null
# Output accumulator for Intune
Write-Host $script:intuneOutput
As ALWAYS: Test before deploying
