Corrupt Windows 7 NTFS junction points

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=

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

:: %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"

7 thoughts on “Corrupt Windows 7 NTFS junction points

  1. Helge Klein

    Thanks for using my tool SetACL. I agree, the command line options may be a bit overwhelming at first ;-)
    If you prefer something more intuitive have a look at the new SetACL Studio – a very friendly graphical program for managing permissions, built on the strong foundation of SetACL:

  2. J.

    Thank you for your hard work but I am balking at the notion of changing so many permissions at once. My only option for fixing this dysfunctional junction mess are to go back to being denied access to the folders on my system? Guess I will just live with endlessly recursing Application Data folders, take it as a cost of using Windows, and wait patiently for this PC to die so I can go back to XP.

  3. herbmartin52

    I have a similar problem, probably due to the same root cause or something very much like it.

    Built a master Windows 2008-R2 image using ImageX to capture the image and the Microsoft Deployment Toolkit to build out master for installations — the image was applied using a PXE boot (I doubt that last is relevant however.)

    Each of the junction points (in both C:\ProgramFiles and throughout the user profile directories) were set to V:\…..

    There was of course no V:\ drive, and to my knowledge there was never a time during the build and capture where this drive was even available, but that is what they were all set to use.

    I wrote a naive, trivial script to delete these and recreate them correctly. This worked fine on the large majority of junctions EXCEPT those in C:\ProgramData.

    There I needed to find a way to delete the existing junctions since nothing else was working (linkd, rd, Explorer, unlocker) to remove them.

    I could not take ownership nor change the permissions despite having the requisite permissions — I did NOT try setacl (although I know about it and have used it for years, I presumed that if icacls couldn’t do it then setacl couldn’t either.) — I will be trying this shortly.

    Finally, I removed them (once) with the PendingFileRenameOperations registry key (and reboot) but when I decided to repeat this process I could not.

    I did manage to get “Application Data” mostly straightened out, but I am still unable to modify the permissions to prevent the infinite recursion. (And I am worried that stupid backup programs will ignore the convention of avoiding junction cycles and fail due to this.)

    Even though my trivial script worked mostly, for the other junctions with C:\ProgramData, the mklink program didn’t function correctly; the junctions all ended up displaying “[..]” as if they point literally at “[the parent directory]” — rechecking my script shows the mklink program was called with the correct parameters.

    ….I am still working on fixing this and intend to re-read and apply the ideas and script above as appropriate.

    Thanks so much for posting — if nothing else, it makes it easier to explain what happened to others.


  4. Mathias Möwitz

    I’m quite a beginner, so sorry if my question sounds stupid. The script has multiple lines beginning “call :junction /J” As I tried to determine what happens in each step I found out that on my win7/x64 the command “junction” is not available. What is my fault? THX

    1. lvbernardo

      There are no “stupid” questions. You question might be someone’s too!

      The function ‘call’ on a batch file “calls” the “junction” block (defined on line 54), so the code goes back and forth using parameters set by %1 %2 %3 and %4, which means the 1st block before 1st space, the 2nd block before 2nd space, 3rd block and 4th block, respectively. See example below.

      call :junction /J “C:\ProgramData\Application Data” “C:\ProgramData” deny
      ….(carries all 4 parameters to the command lines inside the block junction…e.g.:)
      mklink %1 “%~2” “%~3”

      ….is in fact doing:
      mklink /J “C:\ProgramData\Application Data” “C:\ProgramData”

      The %4 (or the 4th portion of the call :junction) is used on the last line 77. Only if it is equal to “deny”, the code next to it runs. So:
      if “%4″==”deny” %DEBUG% setacl -on “%~2” -ot file -actn ace -ace “n:Everyone;s:n;m:deny;i:np;p:list_dir”
      ….our 4th parameter is “deny”, though:
      setacl -on “C:\ProgramData\Application Data” -ot file -actn ace -ace “n:Everyone;s:n;m:deny;i:np;p:list_dir”

      Hope these little instructions help you read batches/scripts


Leave a Reply to chefwear Cancel reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s