If you have supported software in an organization of any size, trying to remove HKEY_CURRENT_USER (HKCU) registry keys from all user accounts more than likely has posed a challenge. Whether your goal is to remove software-related keys or to add configuration items to all user accounts, it can become tricky. In this article, I will discuss how to do this with PowerShell.

Traditionally you could accomplish this by using the User Configuration option in a Group Policy object, but with PowerShell you don't have to. Don't get me wrong—Group Policy may be the best option in your situation. If you want to remove software or a configuration item almost immediately, and you do not want to wait until users log in to their systems to apply it, then PowerShell is your answer. PowerShell is nothing but flexible.

There may be many reasons why you would want to remove registry keys from unloaded profiles, but more than likely it is because you need to remove HKCU registry keys that a piece of software left behind. By writing a PowerShell script or function, you can load all unloaded HKCU user hives, make your change, and unload those hives. The general process to do this in PowerShell is to:

  1. Find all unloaded user hives on a system.
  2. Iterate through each of them.
  3. Make the necessary change.
  4. Unload each loaded user hive.
Loading an user hive to HKU

Loading an user hive to HKU

To find all the unloaded user hives on a system, we first need to find all HKCU hives on the machine. We can find all profiles on a machine by looking in the registry:

$PatternSID = 'S-1-5-21-\d+-\d+\-\d+\-\d+$'

# Get Username, SID, and location of ntuser.dat for all users
$ProfileList = @()
  $ProfileList = Get-ItemProperty 'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\*' | Where-Object { $_.PSChildName ‑match $PatternSID } |
    Select  @{ name = "SID"; expression = { $_.PSChildName } },
     @{ name = "UserHive"; expression = { "$($_.ProfileImagePath)\ntuser.dat" } },
     @{ name = "Username"; expression = { $_.ProfileImagePath -replace '^(.*[\\\/])', '' } 
        }

Now that we know which user profiles are on the machine, we need to filter out all the currently loaded profiles.

# Get all user SIDs found in HKEY_USERS (ntuser.dat files that are loaded)
$LoadedHives = Get-ChildItem Registry::HKEY_USERS | ? { $_.PSChildname ‑match $PatternSID } | Select @{ name = "SID"; expression = { $_.PSChildName } }

For backward compatibility with PowerShell V2, we need to separate out the values from $LoadedHives and create a new PSCustomObject:

$SIDObject = @()  
  foreach ($item in $LoadedHives)
  {
      $props = @{
          SID = $item.SID
      }
      $TempSIDObject = New-Object -TypeName PSCustomObject -Property $props
      $SIDObject += $TempSIDObject
  }

Now that we have two lists of SIDs, all HKCU hives, and the currently loaded HKCU hive(s), we need to filter them so that we only load unloaded hives. Additionally, for backward compatibility, we need to use the Measure-Object cmdlet instead of the traditional $var.count approach. In PowerShell V2, if you have an object of fewer than two items, the count property does not work properly, thus the use of Measure-Object:

# We need to use ($ProfileList | Measure-Object).count instead of just ($ProfileList).count
# because in PS V2, if the count is less than 2, it doesn't work. :)
for ($p = 0; $p -lt ($ProfileList | Measure-Object).count; $p++)
{
  for ($l = 0; $l -lt ($SIDObject | Measure-Object).count; $l++)
  {
      if (($ProfileList[$p].SID) -ne ($SIDObject[$l].SID))
      {
          $UnloadedHives += $ProfileList[$p].SID
          Write-Verbose -Message "Loading Registry hives for $($ProfileList[$p].SID)"
          reg load "HKU\$($ProfileList[$p].SID)" "$($ProfileList[$p].UserHive)"

          Write-Verbose -Message 'Attempting to remove registry keys for each profile'
          #####################################################################
          # This is where you can read/modify a user's portion of the registry 
      }
  }
}

In the block comment above, you can search, add, modify, or remove any registry keys for that specific HKCU user hive. After we have made any additions or subtractions from the loaded user hive, we then need to do a little cleanup. You never want to leave items behind or in a state that has the potential to cause issues or conflicts with other system calls. To do this, we just need to add the following piece of code within our inner if statement:

Write-Verbose 'Unloading registry hives for all users'
# Unload ntuser.dat        
# Garbage collection and closing of ntuser.dat ###
[gc]::Collect()
reg unload "HKU\$($ProfileList[$p].SID)"

That's it! Next time you have a piece of software that leaves items around or you need to add registry keys/values to resolve an issue, PowerShell is the answer.

Initial research for this article was provided by Bryce McDonald.

avatar
7 Comments
  1. Itamar 5 years ago

    Can you send or share the entire code?

    and how do I do this remoting

    Tnx

  2. John 4 years ago

    FYI, in Powershel v2 if you force the object type to an Array you can use .count on a single item array. Example, [Array]$LoadedHives.

  3. Jim Satterfield 4 years ago

    When I run this I get an unexpected token message on -match.

    • Harry 4 years ago

      It's an encoding issue with "-" -character, which is not really a "-". Just replace it with a proper one.

  4. NetVicious 4 years ago

    IMHO, it's more easy on this way:

    $users = (Get-ChildItem -path c:\users).name
    foreach($user in $users)
     {
     reg load "hku\$user" "C:\Users\$user\NTUSER.DAT"
     # Do what you need with "hkey_users\$user" here which links to that user HKU
     # Example: reg add "hkey_users\$user\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\People" /v "PeopleBand" /t "REG_DWORD" /d "0" /f
     reg unload "hku\$user"
     }

    With the reg load we're loading the user registry key in a temporal key of the registry. And we can do whatever we need of that temp key.

    Current users logged on the system will be not affected because the ntuser.dat file it's locked by the system.

  5. Anand Prakash Singh 3 years ago

    Easiest PowerShell script to remove all HKCU in system context:

    https://blogs.technet.microsoft.com/pstips/2016/05/07/write-to-hkcu-from-the-system-account/

  6. keerthiga 2 years ago

    i need to check one hkcu registry value for all user  is that possible?

Leave a reply

Please enclose code in pre tags

Your email address will not be published. Required fields are marked *

*

© 4sysops 2006 - 2021

CONTACT US

Please ask IT administration questions in the forums. Any other messages are welcome.

Sending

Log in with your credentials

or    

Forgot your details?

Create Account