In January, I showed you how to
move your program files and your profile folder to a different partition using robocopy and mklink. With NTFS junctions, you (and your programs) can transparently use the folders on your C drive, while the actual data lies someplace else.
Unfortunately, today, some Windows Updates where shipped that try to install on every reboot, but fail with error code 80070011. Using
GoogleScroogle, I quickly
found out why (and how to fix it manually).
Cause: If files cannot be replaced because they're open, updates write XML commands to \SystemRoot\WinSxS\pending.xml (usually C:\Windows\WinSxS\pending.xml, unless you've installed Windows to a different partition). This file will be processed on reboot. To replace files, updates create hard links at the destination, like this:
CODE:
<HardlinkFile source="\SystemRoot\WinSxS\amd64_microsoft-windows-ie-internetexplorer_31bf3856ad364e35_6.0.6000.16609_none_89a35f80d52d451d\iexplore.exe" destination="\??\C:\Program Files\Internet Explorer\iexplore.exe"/>
This will fail if "Program Files" is on a different partition, as files can only be hard linked within the same partition. I highly doubt that Microsoft will ever fix this, because moving program data to a different partition is not officially supported.
Workaround: If you don't want to move your program files back to C: (like me), there seems to be only one way to fix this: By copying the updated files the the same partition as the real program folders, and by changing all HardlinkFile references to this new location, it will work.
alantangcs posted a nice step-by-step guide on how to edit pending.xml (you have to change ownership of the file first).
Because I don't expect this to be the last time an update tries to change Program Files, I wrote a little script that does most of the work: It will provide a copy of pending.xml with all necessary replacements and copy the files to a directory called "WUTemp" on the same partition as your real program files. If you don't supply the real location of your program files by command line, the script will ask for it.
A slightly easier workaround might be to replace the affected HardlinkFile commands by CopyFile (not sure if it exists) or MoveFile (exists, but I don't know how it reacts if the target already exists). That way, the files can stay in WinSxS. I haven't tried this, and be warned: If you mess with pending.xml and Vista doesn't boot, it won't even boot in Safe Mode. Try in a VM or keep some recovery disk handy.
This script in the current design will only work on Vista x64 if both "Program Folder" and "Program Folder (x86)" are at the same (real) location.
echo "WUfix 80070011 for junctioned Program Files directory, by Moritz Bartl ( http://www.wiredwings.com/ ). Public domain. Version 1.0 - 2008.03.01"
echo "Use at your own risk!! READ BLOG ENTRY/README.TXT first!"
echo "This script will not damage anything without your manual help."
echo ""
# either program files path was supplied by command line, or ask user
if ($Args.Count -lt 1) {
echo "Enter the NEW path of your program folder including drive letter [D:\Program Files]: ";
$program_files = $Host.UI.ReadLine();
if ($program_files -eq "") { $program_files = "D:\Program Files"; }
} else {
$program_files = [string]$Args[0];
}
# remove trailing slash
if ($program_files.EndsWith("\")) {
$program_files = $program_files.Substring(0, $program_files.Length-1);
}
# determine location where update files are copied to, on the same drive as the relocated Program Files folder
$drive_letter = $program_files[0];
$wutemp = "${drive_letter}:\WUTemp\";
$file = "$env:systemroot\WinSxS\pending.xml";
$destination = "${wutemp}pending.xml";
$pattern = new-object System.Text.RegularExpressions.Regex '<HardlinkFile source="\\SystemRoot\\WinSxS\\(?<source>.*)" destination="\\\?\?\\C:\\Program Files(?<destination>.*)"/>';
$replacement = "<HardlinkFile source=`"\??\$wutemp`$`{source`}`" destination=`"\??\$program_files`$`{destination`}`"/>";
## STARTING HERE, THE ACTUAL FUN BEGINS!
echo ""
echo "Please wait..."
# create/replace $destination
$destination =new-item -ItemType file -force $destination
# open pending.xml and do actual pattern matching/replacing
# extract source files to copy later
$pending = Get-Content $file;
$matchCount = 0;
$sources = @();
for ($i = 0; $i -lt $pending.Count; $i ++) {
$line = $pending[$i];
if ($line -match $pattern) {
$source = $matches["source"];
$sources += ,$source;
$matchCount++;
$line = [regex]::Replace($line, $pattern, $replacement);
$pending[$i] = $line
}
}
echo ""
# if there were matches, write the new pending.xml to the "temp" directory
# and copy all files that will be hardlinked to the new "WUtemp" directory
# on the same drive as your relocated program folder
if ($matchCount -gt 0) {
Add-Content $destination $pending
$winsxs = "$env:systemroot\WinSxS\";
echo "$matchCount HardlinkFile references found and replaced. New pending.xml is located at $destination. Validate if everything is okay, then copy it to $winsxs (first, you have to take ownership of the original file there; a backup copy will not hurt).";
echo "Now, I'll try to copy the update files. If anything fails here, your update will probably fail, too, if you don't manually copy the files."
foreach ($source in $sources) {
# create destination path if it doesn't exist
$destination_path = split-path "$wutemp$source"
New-Item -Path $destination_path -ItemType Directory -Force
# copy file; some error checking would be useful here...
$copied_item = Copy-Item "$winsxs$source" "$wutemp$source"
}
} else {
echo "No matches. Leave a comment in my blog if you think this should have done something.";
}
exit;
Usage:
0. (Re)start installation of the updates within the Update Manager. Make sure there's a pending.xml in C:\Windows\WinSxS afterwards, but do not reboot (yet)!
1. Make sure you have
Microsoft PowerShell installed. Sorry that I've used it, but I thought this would be a good opportunity to enhance my programming language knowledge. Feel free to translate to different languages!
2. Execute "powershell"
as Administrator (Windows button, search "powershell", right-click on "Windows PowerShell", select "Run as Administrator".
3. Enter "Set-ExecutionPolicy RemoteSigned". This will allow execution of (unsigned) local scripts, while remote scripts need to be signed.
4. Enter the full path to the script and run it.
5. WUfix will query for your
real program folder location (the one where the junctions point to). Enter it, including "Program Files".
If everything works as expected, you'll find a new folder "WUtemp" in the root of the same partition as your real program folders. Inside, there's a new pending.xml , and all files that will to be hard linked. Take ownership of C:\Windows\WinSxS\pending.xml ([url=http://forums.techarena.in/showthread.php?p=2950983#post2950983]step-by-step guide]), copy it to a backup location and replace the original by WUtemp\pending.xml .
Reboot and hope for the best. You can delete the WUtemp folder after the updates were successfully installed.
Does anyone have an idea how to detect the actual target of junctions from within PowerShell? This was one of the reasons why I decided to use it in the first place, but I haven't found out how. That way, the whole process could be automated.