I recently encountered a server where SQL Server had somehow been installed to the admin user’s mapped U: drive instead of drive C:. As a result all SQL file paths in the registry referred to “U:\Program Files\Microsoft SQL Server\…” but for most users (including the SQL service account) the U: drive did not map to C:. This prevented Management Studio from working and probably many other issues that weren’t as visible.

I wanted a fast way to find all “U:\Program Files” references in the registry and repoint them to drive C:. The standard Windows regedit.exe only supports Find but not Replace (and there were a lot of keys to fix) and third party registry tools available on the Internet fall into the untrustworthy category for fixing servers.

I ended up writing a quick C# console app to perform the job.The C# app was able to solve the problem and the server works properly now but I felt there should be an easier way: PowerShell.

I’ve spent an evening hammering out a basic pair of find and replace functions for PowerShell. They don’t make as much use of PowerShell’s declarative pipelined nature as I’d like but they work well. The replace function is particular dangerous if you misuse it so be careful. Perhaps I will implement the -WhatIf switch some day.

The find function is simply named Find-RegistryValue. At the moment the function only looks in values, not keys or value names because these are already quite easy to search on with basic PowerShell one-liners. As input the function expects a “seek” parameter being the text sought and optionally a path to a registry key to begin searching from. If the “regpath” is not provided it defaults to Get-Location and if it is not a registry path it throws.

The find function will return an array of Hashtable objects with all the information you should require: the RegistryKey, the name of the value in the key, and the value itself containing the sought text. The code follows:

function Find-RegistryValue (
[string] $seek = $(throw “seek required.”),
[System.Management.Automation.PathInfo] $regpath = (Get-Location) ) {

if ($regpath.Provider.Name -ne “Registry”) { throw “regpath required.” }

$keys = @(Get-Item $regpath -ErrorAction SilentlyContinue) `
+ @(Get-ChildItem -recurse $regpath -ErrorAction SilentlyContinue);

$results = @();

foreach ($key in $keys) {
foreach ($vname in $key.GetValueNames()) {
$val = $key.GetValue($vname);
if ($val -match $seek) {
$r = @{};
$r.Key = $key;
$r.ValueName = $vname;
$r.Value = $val;
$results += $r;
}
}
}

$results;
}

The replace function is named Replace-RegistryValue and relies on the find function to work, resulting in very similar behaviour. It requires the text sought and the registry path just like the find function but it also requires the “swap” parameter which is the text to replace the sought value with. It calls the find function itself and uses the output to first promote the key to a writable instance then replace the value and return the results. The results include the RegistryKey, the name of the value in the key, the old value and also the new value. Here is the code:

function Replace-RegistryValue (
[string] $seek = $(throw “seek required.”),
[string] $swap = $(throw “swap required.”),
[System.Management.Automation.PathInfo] $regpath = (Get-Location) ) {

$find = Find-RegistryValue -seek $seek -regpath $regpath;
$results = @();

foreach ($target in $find) {
$nval = $target.Value -replace $seek, $swap;
$r = @{};
$r.Key = $target.Key;
$r.ValueName = $target.ValueName;
$r.OldValue = $target.Value;
$r.NewValue = $nval;
$results += $r;
$wKey = (Get-Item $r.Key.PSParentPath).OpenSubKey($r.Key.PSChildName, “True”);
$wKey.SetValue($target.ValueName, $nval);
}

$results;
}

If you have any suggestions for improving the code or perhaps even a better naming convention for the pair, please leave a comment.