Dec 192014

Like the title says this is for a very specific use case which I have run into, so definitely not for everyone.


The company I work for plans on switching the user home directories to a DFS path in order to accommodate our cloud DR solution. We currently redirect folders via Citrix policies in XenDesktop to the user home directories (ie: \\server\path\username). This path is to change to the DFS path as well, so when a user logs onto the cloud Desktop as a Service (DaaS) desktop their folders are redirected like normal. The new path will be \\domain\dfspath\userpath\username, but it is really the same place as before (2 different paths pointing to the same place). If you change the Citrix policy to redirect the folders to \\domain\dfspath\userpath from \\server\path it will copy the contents of the old path to the new (which really does nothing because it is already there), BUT then it will delete it from the old path (since the “old” and “new” path are really the same the redirected folders are deleted)!!! Using good ol’ Microsoft redirection policies you can set the policy to not move data, but with Citrix policies I don’t see this option.

Basically, if I switch the policy to point the redirected folders to the new DFS path, a user logs on, and all their redirected folders go *POOF*. In our environment that’s pretty much everything but AppData.

I screwed around with the registry a bit and found if you delete the values under HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders and User Shell Folders that point to the share along with the History key under HKCU:\Software\Citrix\UserProfileManager\FolderRedirection, the folders are redirected fine after the DFS path is in place with no problems.


The Script

My choices after finding the work around were to:

  1. Go the easy route – Delete all the current profiles
    1. Obviously that won’t fly
  2. Load each NTUSER.DAT file into the registry one at a time and edit them
    1. With so many profiles that would take forever
    2. Would need a very long window as I couldn’t have users logging on/off hosing my work and their redirected folders
  3. Get a window to disable logons to our XD environment, get everyone logged off, and use a script to load every ntuser.dat file/edit/unload
    1. Only need a short window

I chose option 3 and got to work on a script below. Read the comments between the <# #> marks!  As always test test test…

Your best bet is to copy the script below into your favorite powershell editor it doesn’t format very nicely here… I just use powershell_ise.exe



$start = Get-Date <# Get the start time to calculate how long the script took at the end #>
<# we get the folders under the root of the profile store (full names), for each append the path to where the NTUSER.DAT would be found, and save that as the $profiles variable. THIS WILL ALMOST CERTAINLY BE DIFFERENT FOR YOU, SO FIGURE OUT YOUR PATH #>
$profiles = gci \\server\profiles | ?{$_.psiscontainer -eq $true} |select -expand fullname | sort | %{Join-Path $_ "v2x64\UPM_Profile\NTUSER.DAT"}
<# This is where the fun starts. If you want to test this against a single test user profile comment out the $profile variable above with a pound sign before it, and make a new line like this: $profiles = "\\server\profiles\testuser\upm_profile\ntuser.dat" #>
foreach ($profile in $profiles) {
<# We check to see if the NTUSER.DAT file exists... if not it continues on to the next profile in the list #>
if ((Test-Path $profile) -eq $false) {continue}
<# This checks for completed jobs and outputs the messages to the screen (job starts in a sec). Then removes the completed job #>
if ((Get-Job -State Completed).count -gt 0) {
$jobs = Get-Job -State Completed
$jobs | Receive-Job
$jobs | Remove-Job
<# This sets the max running jobs to 10 - adjust this at your own risk - ie: if you change the 10 to 100 it will load 100 NTUSER.DAT files into the registry at a time ... that could be bad resource wise #>
while ((Get-Job -State Running).count -ge 10) {
Start-Sleep -s 1
<# The hive variable below is important... this is the name the NTUSER.DAT hive will get in HKLM. YOU WILL HAVE TO PLAY WITH THIS SO YOU GET IT RIGHT IN YOUR ENVIRONMENT. in my case it will name the hive username_temp - ie: user1_temp if the username is user1 #>
$hive = (($profile -split ".domain") -split "\\")[2] + "_temp"
<# Here we start the job - jobs run a separate instance of powershell, so it will load multiple hives. In this case there will be 10 hives loaded into the registry at a time (DO NOT OPEN REGEDIT WHILE THIS SCRIPT IS RUNNING... IT WILL STOP THE HIVES FROM UNLOADING) #>
Start-Job -name $hive {param($profile,$hive)
<# Here we start reg.exe with arguments to load the user hive, and saves the process info as $load.  Then it checks the exitcode so it can tell you if it loaded
or not#>
$load = start-process -passthru -filepath reg.exe -argumentlist "load HKLM\$hive $profile" -Wait -WindowStyle Hidden
if ($load.ExitCode -ne "0") {
Write-Host "$profile could not be loaded" -f Red
} else {
Write-Host "$profile loaded"
<# $change variable comes into play in a sec... default is 0 #>
$change = "0"
<# Here we set the location to the registry path where the shell folders and user shell folders keys live #>
Set-Location hklm:\$hive\Software\Microsoft\windows\CurrentVersion\Explorer
<# We search for any key which has *shell* in the name (shell folders and user shell folders).  Then we get the key path of those keys, and all the properties
under those keys, and look for any values that are pointing to the server where the current share is hosted.  Once we have those we remove the property, and set
$change to 1... indicating that the shell folders were modified#>
gci .\ | ?{($_.psiscontainer -eq $true) -and ($ -like "*shell*")} | %{
$regpath = $_.pspath
$properties = $
foreach ($property in $properties) {
$val = (Get-ItemProperty $regpath).$property
<# edit your server name here  #>
if ($val -like "*server*") {
$change = "1"
Remove-ItemProperty -Path $regpath -Name $property
<# If $change is 1 then we remove the folder redirection "history" key under the Citrix path #>
if ($change -eq "1") {
Set-Location HKLM:\$hive\Software\Citrix\UserProfileManager\FolderRedirection
if ((Test-Path .\History) -eq $true) {
ri .\History -Recurse -Force
<# We pop-location to leave the registry paths, do some cleanup so that we can unload the hive cleanly, and attempt to unload it.  If it fails it will try up
to 5 times to unload the hive.  And will let you know after the job runs #>
gci env: | Out-Null
gci variable: | Out-Null
$att = 0
while ((Test-Path HKLM:\$hive) -and ($att -le 5)) {
$unload = start-process -PassThru -FilePath reg.exe -ArgumentList "unload HKLM\$hive" -Wait -WindowStyle Hidden
$att += 1
if ($unload.ExitCode -ne "0") {
Write-Host "Unable to unload $profile" -f Red
} else {
Write-Host "$profile successfully unloaded"
<# This cleans up the NTUSER.LOG files that get created when the hive is loaded #>
gci (Split-Path $profile -Parent) -Force | ?{($_.psiscontainer -eq $false) -and ($_.Name -like "ntuser*") -and ($ -ne "ntuser.dat") -and ($ -ne "ntuser.ini") -and ($_.Name -ne "ntuser.pol")} | ri -Force
} -ArgumentList $profile,$hive | Out-Null
Get-Job | Wait-Job | Receive-Job
Get-Job | Remove-Job
$end = Get-Date
$minutes = ($end - $start).Minutes
$seconds = ($end - $start).Seconds
Write-Host "Total run time $minutes minutes $seconds seconds."