3
votes

In 'HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders' I have some paths set to an old server. e.g.:

'My Pictures' is set to '\\DeadServer\RedirectedFolders\%UserName%\My Documents\My Pictures'

I'd like to replace "\\DeadServer\RedirectedFolders" with "C:\Users"

How can this be done in powershell?


I got as far as trying
Get-ItemProperty -path "Microsoft.PowerShell.Core\Registry::HKEY_USERS\*\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" | ? {$_.PSObject.Properties -like "*DeadServer*"} 

But I think I'm getting confused with how the entry I want to change is a 'Property', and not an 'Item', and I don't know how to iterate through properties like I'd do Items.


Before you ask, I've already made this change with Group Policy, but it's not taking. Users are getting a message

The Recycle Bin on \DeadServer\RedirectedFolders\%UserName%\My Documents\My Pictures` is corrupted. Do you want to empty the Recycle Bin for this drive?

upon login which is keeping Folder Redirection from applying.
This is my attempt to force the change back to local storage manually.

3
Do you actually need to find and replace or can you just set the value regardless? - arco444
Eventually I need to run this on several computers for several accounts. if I just set the value regardless, I need to keep track of which username I'm currently iterating on. My plan is to run this over HKEY_USERS (see my attempt in OP) so users need not be logged in for me to access their HKCU. I think it would be tricky to use a %username% variable in this case. - Derek

3 Answers

4
votes

I figured it out. Took me a long time, but I wrote a rather inelegant script:

Get-Item -ErrorAction SilentlyContinue -path  "Microsoft.PowerShell.Core\Registry::HKEY_USERS\*\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" |
foreach {
    Get-ItemProperty -Path "Microsoft.PowerShell.Core\Registry::$_" | 
    foreach {
        $CurrentUserShellFoldersPath = $_.PSPath
        $SID = $CurrentUserShellFoldersPath.Split('\')[2]
        $_.PSObject.Properties |
        foreach {
            if ($_.Value -like "*DeadServer*") {
                write-host "Path:`t`t"$CurrentUserShellFoldersPath
                write-host "SID:`t`t"$SID
                write-host "Name:`t`t"$_.Name
                write-host "Old Value:`t"$_.Value
                $newValue = $_.Value
                $newValue = $newValue -replace '\\\\DeadServer\\RedirectedFolders', "C:\Users"
                $newValue = $newValue -replace "My Documents\\", ""
                $newValue = $newValue -replace "My ", ""
                Write-Host "New Value:`t"$newValue
                Set-ItemProperty -Path $CurrentUserShellFoldersPath -Name $_.Name -Value $newValue

                Write-host "================================================================"
            }
        }
    }
}

I'd love to learn of a faster or more elegant way to do this if any of you have one.

2
votes

Here's an easy to use registry replace function, which can search a path recursively.

# Replace all registry key values and/or registry key names under a given path.
# Example Usage:
#   RegistryValue-Replace "ExistingValue" "NewValue" 'HKEY_CURRENT_USER\Software\100000_DummyData'
#   RegistryValue-Replace "ExistingValue" "NewValue" 'HKEY_USERS\*\Software\100000_DummyData' -ReplaceKeyNames $true -CaseSensitive $true
#   RegistryValue-Replace 'C:\\Program Files\\Microsoft SQL Server' 'E:\Program Files\Microsoft SQL Server' 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Microsoft SQL Server*' -LoggingOn $true

function RegistryValue-Replace (
  [string]$OldValue = $(throw “OldValue (the current value) required.”),
  [string]$NewValue = $(throw “NewValue (the replacement value) required.”),
  [string]$RegkPath = $(throw “RegkPath (The full registry key path) required.”),
  [bool] $CaseSensitive = $false, # If true, search and replace is case sensitive
  [bool] $WholeWord = $false, # If true, searches for whole word within the value.
  [bool] $ExactMatch = $false,  # If true, the entire value must match OldValue, and partial replacements are NOT performed
  [bool] $ReplaceKeyNames = $false, # If true, replaces registry key names
  [bool] $ReplaceValues = $true,
  [bool] $LoggingOn = $false ) 
{
    $PowershellRegPrefix = 'Microsoft.PowerShell.Core\Registry::'
    $MatchFor = if ($WholeWord -eq $true) {".*\b$OldValue\b.*"} else { ".*$OldValue.*" }
    if ($RegkPath -NotLike "$PowershellRegPrefix*") { $RegkPath = $PowershellRegPrefix + $RegkPath }

    @(Get-Item -ErrorAction SilentlyContinue -path  $RegkPath) +
    @(Get-ChildItem -Recurse $RegkPath -ErrorAction SilentlyContinue) |
    foreach {
        Get-ItemProperty -Path "$PowershellRegPrefix$_" | 
        foreach {
            $CurrentShellFoldersPath = $_.PSPath
            $SID = $CurrentShellFoldersPath.Split('\')[2]
            $_.PSObject.Properties |
            foreach {
                if ($_.Name -cne "PSChildName" -and (($ExactMatch -eq $true -and $_.Value -clike $OldValue) -or ($ExactMatch -eq $false -and
                    (($CaseSensitive -eq $false -and $_.Value -match $MatchFor) -or ($CaseSensitive -eq $true -and $_.Value -cmatch $MatchFor))))) {
                    $Original = $_.Value
                    $Create_NewValue = $_.Value
                    $SubKeyName = $_.Name
                    if ($CaseSensitive -eq $true){ $Create_NewValue = $Create_NewValue -creplace $OldValue, $NewValue }
                    else                         { $Create_NewValue = $Create_NewValue -replace  $OldValue, $NewValue }
                    if ($_.Name -eq "PSPath" -and $_.Value -eq $CurrentShellFoldersPath) {
                        if ($ReplaceKeyNames -eq $true) {
                            Move-Item -Path $CurrentShellFoldersPath -Destination $Create_NewValue
                            if ($LoggingOn -eq $true){ Write-host "Renamed registry key '$CurrentShellFoldersPath' to '$Create_NewValue'" }
                        } else {
                            if ($LoggingOn -eq $true){ Write-host "....Skipping renaming key '$CurrentShellFoldersPath->$SubKeyName' due to input option!!!" } }
                    } else {
                        if ($ReplaceValues -eq $true) {
                            Set-ItemProperty -Path $CurrentShellFoldersPath -Name $_.Name -Value $Create_NewValue
                            if ($LoggingOn -eq $true){ Write-host "Renamed '$Original' to '$Create_NewValue' for registry key '$CurrentShellFoldersPath->$SubKeyName'" }
                        } else {
                            if ($LoggingOn -eq $true){ Write-host "....Skipping renaming value '$CurrentShellFoldersPath->$SubKeyName' due to input option!!!" } }
                    }
                }
            }
        }
    }
}
1
votes

Not really happy with this so I will be happy and sad if someone puts this to shame. It's been mostly tested as far as verifying that it is locating the correct keys.

If(!(Test-Path HKU:)){New-PSDrive -PSProvider Registry -Name HKU -Root HKEY_USERS}
$registrySearchPath = "HKU:\*\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders"
$pathToReplace = [regex]::Escape("C:\Users")
$newPath = '%USERPROFILE%'
Get-Item -path $registrySearchPath -ErrorAction SilentlyContinue | 
        Select-Object -ExpandProperty Name | 
        Where-Object{$_ -match "^HKEY_USERS\\S-1-5-21"} |
        ForEach-Object{
    $key = $_ -replace "^HKEY_USERS","HKU:"
    (Get-ItemProperty $key).psobject.Properties | Where-Object{$_.Value -match $pathToReplace} | 
            Select-Object Name,Value | ForEach-Object{
        Set-ItemProperty -Path $key -Name $_.Name -Value ($_.Value -replace $pathToReplace,$newPath) -WhatIf
    }
}

Use a Psdrive to map HKU since its not a default drive in PowerShell. Get all keys back that have at least a path to "\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders". Omit the default keys and any other localesque accounts by only looking for the ones with "S-1-5-21" as part of the key. Then for each of those that is located find every registry value with data that matchs the path you are looking for.

Set-ItemProperty -Path $key -Name $_.Name -Value ($_.Value -replace $pathToReplace,$newPath) -WhatIf

Drawing a little more attention on the last part here. With all the values that matched we replace the data with a simple -replace. I have a -WhatIf on there to be sure you test in case something bad happens. I would suggest commenting out that line and outputing just $_.Value -replace $pathToReplace,$newPath to verify that it is doing what you expect it to.

Make sure that you change the values for $pathToReplace and $newPath then Test twice, execute once.