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"
