I encountered an unusual problem recently – all Windows 7 workstations which had been built with a Microsoft Select Agreement Volume License version of Windows 7 Professional RTM using an unattended install, not via sysprep, had some sort of damage to their legacy filesystem junction points. This had prevented the installer for Kaspersky EndPoint Protection 8 and its Network Agent version 9 from running, though earlier versions had been fine. The error took Kaspersky support a very long time to pin down (several months in fact, despite them having detailed MSI installer logs), and it eventually transpired that many of the links to maintain legacy OS compatibility like C:\Documents and Settings -> C:\Users, or C:\Users\All Users -> C:\ProgramData on these affected systems were resolving to some kind of temporary mounted WIM image path, within the folder C:\Users\ADMINI~1\AppData\Local\Temp\mnt\wim.
This folder no longer existed, and nor was there any phantom mounted WIM image, so any attempt to access the damaged links would fail (in Kaspersky’s case the issue was C:\ProgramData\Application Data). I still have no idea what may have caused this. More recently the unattended install I designed uses Windows 7 Enterprise SP1, with no changes to the core build scripting, and systems built from this do not exhibit this issue. This might suggest it was a problem with Windows itself, and if so then my script to fix the damage could be useful for others.
The repair script requires SetACL.exe which is an extremely versatile tool, but which is syntactically very difficult to use! I compared the ACLs on a clean system, noted the link type (they’re not all junctions, there is one symlink), and whether or not there were deny permissions which prevent recursion on links which resolve to their parent folder e.g. C:\ProgramData\Application Data -> C:\ProgramData. The links are deleted and recreated, but only on systems that are detected to need the fix (see the highlighted line for that logic). If you set line 6 to “set DEBUG=echo” you can test the output before actually invoking the repair commands.
@echo off :: Windows 7 junction point/symlink fix script :: patters 13/03/2012 set DEBUG= setlocal dir /aL C:\ProgramData | find /I "C:\Users\ADMINI~1\AppData\Local\Temp\mnt\wim\" && ( call :junction /J "C:\Documents and Settings" "C:\Users" deny call :junction /J "C:\ProgramData\Application Data" "C:\ProgramData" deny call :junction /J "C:\ProgramData\Desktop" "C:\Users\Public\Desktop" deny call :junction /J "C:\ProgramData\Documents" "C:\Users\Public\Documents" deny call :junction /J "C:\ProgramData\Favorites" "C:\Users\Public\Favorites" deny call :junction /J "C:\ProgramData\Start Menu" "C:\ProgramData\Microsoft\Windows\Start Menu" nodeny call :junction /J "C:\ProgramData\Templates" "C:\ProgramData\Microsoft\Windows\Templates" deny call :junction /D "C:\Users\All Users" "C:\ProgramData" deny call :junction /J "C:\Users\All Users\Application Data" "C:\ProgramData" deny call :junction /J "C:\Users\All Users\Desktop" "C:\Users\Public\Desktop" deny call :junction /J "C:\Users\All Users\Documents" "C:\Users\Public\Documents" deny call :junction /J "C:\Users\All Users\Favorites" "C:\Users\Public\Favorites" deny call :junction /J "C:\Users\All Users\Start Menu" "C:\ProgramData\Microsoft\Windows\Start Menu" nodeny call :junction /J "C:\Users\All Users\Templates" "C:\ProgramData\Microsoft\Windows\Templates" deny call :junction /J "C:\Users\Public\Documents\My Music" "C:\Users\Public\Music" deny call :junction /J "C:\Users\Public\Documents\My Pictures" "C:\Users\Public\Pictures" deny call :junction /J "C:\Users\Public\Documents\My Videos" "C:\Users\Public\Videos" deny call :junction /J "C:\Users\Default User" "C:\Users\Default" deny call :junction /J "C:\Users\Default\Application Data" "C:\Users\Default\AppData\Roaming" deny call :junction /J "C:\Users\Default\Cookies" "C:\Users\Default\AppData\Roaming\Microsoft\Windows\Cookies" deny call :junction /J "C:\Users\Default\Local Settings" "C:\Users\Default\AppData\Local" deny call :junction /J "C:\Users\Default\My Documents" "C:\Users\Default\Documents" deny call :junction /J "C:\Users\Default\NetHood" "C:\Users\Default\AppData\Roaming\Microsoft\Windows\Network Shortcuts" deny call :junction /J "C:\Users\Default\PrintHood" "C:\Users\Default\AppData\Roaming\Microsoft\Windows\Printer Shortcuts" deny call :junction /J "C:\Users\Default\Recent" "C:\Users\Default\AppData\Roaming\Microsoft\Windows\Recent" deny call :junction /J "C:\Users\Default\SendTo" "C:\Users\Default\AppData\Roaming\Microsoft\Windows\SendTo" deny call :junction /J "C:\Users\Default\Start Menu" "C:\Users\Default\AppData\Roaming\Microsoft\Windows\Start Menu" deny call :junction /J "C:\Users\Default\Templates" "C:\Users\Default\AppData\Roaming\Microsoft\Windows\Templates" deny call :junction /J "C:\Users\Default\Documents\My Music" "C:\Users\Default\Music" deny call :junction /J "C:\Users\Default\Documents\My Pictures" "C:\Users\Default\Pictures" deny call :junction /J "C:\Users\Default\Documents\My Videos" "C:\Users\Default\Videos" deny call :junction /J "C:\Users\Default\AppData\Local\Application Data" "C:\Users\Default\AppData\Local" deny call :junction /J "C:\Users\Default\AppData\Local\Temporary Internet Files" "C:\Users\Default\AppData\Local\Microsoft\Windows\Temporary Internet Files" deny ) || echo Legacy filesystem junction points/symlinks are fine. ::odd permissions for this one, so I'm leaving it out ::call :junction /J "C:\Users\Default\AppData\Local\History" "C:\Users\Default\AppData\Local\Microsoft\Windows\History" deny goto :eof :junction :: %1 = type (junction or directory symlink) :: %2 = junction/symlink path :: %3 = target path :: %4 = set the deny permission or not ::delete old junction point %DEBUG% rmdir "%~2" ::create new junction point %DEBUG% mklink %1 "%~2" "%~3" ::set owner to SYSTEM %DEBUG% setacl -on "%~2" -ot file -actn setowner -ownr "n:SYSTEM" :: we need to stop inheritance of permissions before we make changes. This must be done with :: a separate commandline entry owing to the order in which SetACL.exe processes its arguments. %DEBUG% setacl -on "%~2" -ot file -actn setprot -op "dacl:p_c;sacl:p_c" ::clear ACL and set permissions %DEBUG% setacl -on "%~2" -ot file -actn clear -clr "dacl,sacl" -actn ace -ace "n:Everyone;i:np;p:read_ex" -actn ace -ace "n:SYSTEM;i:np;p:full" -actn ace -ace "n:Administrators;i:np;p:full" ::add directory listing deny permission for recursive paths if needed if "%4"=="deny" %DEBUG% setacl -on "%~2" -ot file -actn ace -ace "n:Everyone;s:n;m:deny;i:np;p:list_dir"