powershell to resolve junction target path

后端 未结 7 1107
闹比i
闹比i 2020-12-09 15:32

In PowerShell, I need resolve the target path of a junction (symlink).

for example, say I have a junction c:\\someJunction whose target is c:\\te

相关标签:
7条回答
  • 2020-12-09 15:52

    There are some really complicated answers to this question! Here's a super simple and self explanatory one:

    (Get-Item C:\somejunction).Target
    
    0 讨论(0)
  • 2020-12-09 15:55

    This does the trick with less work, and works even for junctions on a remote server:

    fsutil reparsepoint query "M:\Junc"
    

    If you want just the target name:

    fsutil reparsepoint query "M:\Junc" | where-object { $_ -imatch 'Print Name:' } | foreach-object { $_ -replace 'Print Name\:\s*','' }
    

    so

    function Get_JunctionTarget($p_path)
    {
        fsutil reparsepoint query $p_path | where-object { $_ -imatch 'Print Name:' } | foreach-object { $_ -replace 'Print Name\:\s*','' }
    }
    

    Also, the code below is a slight modification of the code that Josh provided above. It can be put in a file that is read multiple times, and it handles the leading \\?\ correctly in the case of a network drive:

    function Global:Get_UNCPath($l_dir)
    {
        if( ( ([System.Management.Automation.PSTypeName]'System.Win32').Type -eq $null)  -or ([system.win32].getmethod('GetSymbolicLinkTarget') -eq $null) )
        {
            Add-Type -MemberDefinition @"
    private const int CREATION_DISPOSITION_OPEN_EXISTING = 3;
    private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
    
    [DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)]
     public static extern int GetFinalPathNameByHandle(IntPtr handle, [In, Out] StringBuilder path, int bufLen, int flags);
    
    [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
     public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
     IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
    
     public static string GetSymbolicLinkTarget(System.IO.DirectoryInfo symlink)
     {
         SafeFileHandle directoryHandle = CreateFile(symlink.FullName, 0, 2, System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero);
         if(directoryHandle.IsInvalid)
         {
             throw new Win32Exception(Marshal.GetLastWin32Error());
         }
         StringBuilder path = new StringBuilder(512);
         int size = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), path, path.Capacity, 0);
         if (size<0)
         {
             throw new Win32Exception(Marshal.GetLastWin32Error());
         }
         // The remarks section of GetFinalPathNameByHandle mentions the return being prefixed with "\\?\"
         // More information about "\\?\" here -> http://msdn.microsoft.com/en-us/library/aa365247(v=VS.85).aspx
         string sPath = path.ToString();
         if( sPath.Length>8 && sPath.Substring(0,8) == @"\\?\UNC\" )
         {
             return @"\" + sPath.Substring(7);
         }
         else if( sPath.Length>4 && sPath.Substring(0,4) == @"\\?\" )
         {
             return sPath.Substring(4);
         }
         else
         {
             return sPath;
         }
     }
    "@ -Name Win32 -NameSpace System -UsingNamespace System.Text,Microsoft.Win32.SafeHandles,System.ComponentModel
        }
        [System.Win32]::GetSymbolicLinkTarget($l_dir)
    }
    

    and given the function Get_UNCPath above, we can improve the function Get_JunctionTarget as follows:

    function Global:Get_JunctionTarget([string]$p_path)
    {
        $l_target = fsutil reparsepoint query $p_path | where-object { $_ -imatch 'Print Name\:' } | foreach-object { $_ -replace 'Print Name\:\s*','' }
        if( $l_target -imatch "(^[A-Z])\:\\" )
        {
            $l_drive = $matches[1]
            $l_uncPath = Get_UncPath $p_path
            if( $l_uncPath -imatch "(^\\\\[^\\]*\\)" )
            {
                $l_machine = $matches[1]
                $l_target = $l_target -replace "^$l_drive\:","$l_machine$l_drive$"
            }
        }
        $l_target
    }
    
    0 讨论(0)
  • 2020-12-09 15:58

    It seems that with PS5 (as mentioned here or somewhere else?), system-defined junctions/reparse points have a bug whereas symlinks and user-defined junctions/reparse points do not. These solutions seem to work for symlinks but not junctions. The only thing that I could get to work to show the proper info was the fsutil program. The only issue is that it outputs data similar to Format-Hex as shown below.

    The fsutil tool give me this output when used with the Windows Store app stored in $path

    PS C:\> $path = "$($env:USERPROFILE)\AppData\Local\Microsoft\WindowsApps\Skype.exe"
    PS C:\> fsutil reparsepoint query $path
    Reparse Data Length: 0x150
    Reparse Data:
    0000:  03 00 00 00 4d 00 69 00  63 00 72 00 6f 00 73 00  ....M.i.c.r.o.s.
    0010:  6f 00 66 00 74 00 2e 00  53 00 6b 00 79 00 70 00  o.f.t...S.k.y.p.
    0020:  65 00 41 00 70 00 70 00  5f 00 6b 00 7a 00 66 00  e.A.p.p._.k.z.f.
    0030:  38 00 71 00 78 00 66 00  33 00 38 00 7a 00 67 00  8.q.x.f.3.8.z.g.
    0040:  35 00 63 00 00 00 4d 00  69 00 63 00 72 00 6f 00  5.c...M.i.c.r.o.
    0050:  73 00 6f 00 66 00 74 00  2e 00 53 00 6b 00 79 00  s.o.f.t...S.k.y.
    0060:  70 00 65 00 41 00 70 00  70 00 5f 00 6b 00 7a 00  p.e.A.p.p._.k.z.
    0070:  66 00 38 00 71 00 78 00  66 00 33 00 38 00 7a 00  f.8.q.x.f.3.8.z.
    0080:  67 00 35 00 63 00 21 00  41 00 70 00 70 00 00 00  g.5.c.!.A.p.p...
    0090:  43 00 3a 00 5c 00 50 00  72 00 6f 00 67 00 72 00  C.:.\.P.r.o.g.r.
    00a0:  61 00 6d 00 20 00 46 00  69 00 6c 00 65 00 73 00  a.m. .F.i.l.e.s.
    00b0:  5c 00 57 00 69 00 6e 00  64 00 6f 00 77 00 73 00  \.W.i.n.d.o.w.s.
    00c0:  41 00 70 00 70 00 73 00  5c 00 4d 00 69 00 63 00  A.p.p.s.\.M.i.c.
    00d0:  72 00 6f 00 73 00 6f 00  66 00 74 00 2e 00 53 00  r.o.s.o.f.t...S.
    00e0:  6b 00 79 00 70 00 65 00  41 00 70 00 70 00 5f 00  k.y.p.e.A.p.p._.
    00f0:  31 00 35 00 2e 00 36 00  34 00 2e 00 38 00 30 00  1.5...6.4...8.0.
    0100:  2e 00 30 00 5f 00 78 00  38 00 36 00 5f 00 5f 00  ..0._.x.8.6._._.
    0110:  6b 00 7a 00 66 00 38 00  71 00 78 00 66 00 33 00  k.z.f.8.q.x.f.3.
    0120:  38 00 7a 00 67 00 35 00  63 00 5c 00 53 00 6b 00  8.z.g.5.c.\.S.k.
    0130:  79 00 70 00 65 00 5c 00  53 00 6b 00 79 00 70 00  y.p.e.\.S.k.y.p.
    0140:  65 00 2e 00 65 00 78 00  65 00 00 00 30 00 00 00  e...e.x.e...0...
    
    OR
    
    PS C:\> $path2 = "$HOME/My Documents"
    PS C:\> fsutil reparsepoint query $path2
    Reparse Tag Value : 0xa0000003
    Tag value: Microsoft
    Tag value: Name Surrogate
    Tag value: Mount Point
    Substitue Name offset: 0
    Substitue Name length: 56
    Print Name offset:     58
    Print Name Length:     48
    Substitute Name:       \??\C:\Users\chefh\Documents
    Print Name:            C:\Users\chefh\Documents
    
    Reparse Data Length: 0x74      
    Reparse Data:
    0000:  00 00 38 00 3a 00 30 00  5c 00 3f 00 3f 00 5c 00  ..8.:.0.\.?.?.\.
    0010:  43 00 3a 00 5c 00 55 00  73 00 65 00 72 00 73 00  C.:.\.U.s.e.r.s.
    0020:  5c 00 63 00 68 00 65 00  66 00 68 00 5c 00 44 00  \.c.h.e.f.h.\.D.
    0030:  6f 00 63 00 75 00 6d 00  65 00 6e 00 74 00 73 00  o.c.u.m.e.n.t.s.
    0040:  00 00 43 00 3a 00 5c 00  55 00 73 00 65 00 72 00  ..C.:.\.U.s.e.r.
    0050:  73 00 5c 00 63 00 68 00  65 00 66 00 68 00 5c 00  s.\.c.h.e.f.h.\.
    0060:  44 00 6f 00 63 00 75 00  6d 00 65 00 6e 00 74 00  D.o.c.u.m.e.n.t.
    0070:  73 00 00 00                                       s...
    

    Here is the code that I came up with to parse that output. I know that it is quite messy code, but as I said I couldn't get the other solutions here to work for Windows Stores apps. This one does. Please test it in your uses and mention if there are issues.

    ***** Edited to allow files or directories. *****

    $path = "$($env:USERPROFILE)\AppData\Local\Microsoft\WindowsApps\Skype.exe"
    $path2 = "$HOME/My Documents"
    
    function Get-ReparseTarget ($path)
    {
        #   Grabs output of fsutil
        $a = fsutil reparsepoint query $path
    
        #   Regex to capture fsutil output
        $regex = '[0-9a-fA-F]+\:\s\s(?<chunk1>([0-9a-fA-F]{2}\s){1,8}\s)(?<chunk2>([0-9a-fA-F]{2}\s){1,8}\s){0,1}.+'
    
        #   Splits and trims the "chunks" then adds them together to create an array of hex character
        $c = $a.foreach({if ($_ -match $regex) {($Matches['chunk1'] -split ' ').trim() | `
            where {$_ -ne ""};($Matches['chunk2'] -split ' ').trim() |where {$_ -ne ""}}})
    
        #   Convert an Array of Hex(String) to Array of Bytes
        $f = [byte[]]($c | foreach{[Convert]::ToInt32($_,16)})
    
        #   Convert the Unicode to Ascii and convert '' (00 in hex) to spaces
        $g = [System.Text.Encoding]::Unicode.GetChars($f).foreach({if([int]$_ -eq 0) {' '} else {$_}})
    
        #   Combine Char[] to String then Split into the important bits and Select the Reparse Target Path 
        #   depending on whether the Path argument is a file or directory
        switch ((get-item -Force -Path $path))
        {
            {$_ -is [System.IO.FileInfo]} {$h = ($g[0..($g.Count -4)] -join "" -split " ", 4)[3]}
            {$_ -is [System.IO.DirectoryInfo]} {$h = ($g[0..($g.Count -2)] -join "" -split " ", 3)[2]}
            Default { Write-Error "Path must be either a file or directory"}
        }
    
        #   Return the path
        return $h
    }
    
    #  Quick Test
    Get-ReparseTarget -path $path
    Get-ReparseTarget -path $path2
    
    
    # Input:
    #   $path = "$($env:USERPROFILE)\AppData\Local\Microsoft\WindowsApps\Skype.exe"
    # Run:
    #   Get-ReparseTarget $path
    # Returns:
    #   C:\Program Files\WindowsApps\Microsoft.SkypeApp_15.64.80.0_x86__kzf8qxf38zg5c\Skype\Skype.exe
    
    # Input:
    #   $path2 = "$HOME/My Documents"
    # Run:
    #   Get-ReparseTarget $path2
    # Returns:
    #   C:\Users\chefh\Documents
    
    0 讨论(0)
  • 2020-12-09 16:00

    You can get the path by doing the following:

    Get-ChildItem -Path C:\someJunction
    

    Edit for finding the path and not the contents of the folder

    Add-Type -MemberDefinition @"
    private const int FILE_SHARE_READ = 1;
    private const int FILE_SHARE_WRITE = 2;
    
    private const int CREATION_DISPOSITION_OPEN_EXISTING = 3;
    private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
    
    [DllImport("kernel32.dll", EntryPoint = "GetFinalPathNameByHandleW", CharSet = CharSet.Unicode, SetLastError = true)]
     public static extern int GetFinalPathNameByHandle(IntPtr handle, [In, Out] StringBuilder path, int bufLen, int flags);
    
    [DllImport("kernel32.dll", EntryPoint = "CreateFileW", CharSet = CharSet.Unicode, SetLastError = true)]
     public static extern SafeFileHandle CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode,
     IntPtr SecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile);
    
     public static string GetSymbolicLinkTarget(System.IO.DirectoryInfo symlink)
     {
         SafeFileHandle directoryHandle = CreateFile(symlink.FullName, 0, 2, System.IntPtr.Zero, CREATION_DISPOSITION_OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, System.IntPtr.Zero);
         if(directoryHandle.IsInvalid)
         throw new Win32Exception(Marshal.GetLastWin32Error());
    
         StringBuilder path = new StringBuilder(512);
         int size = GetFinalPathNameByHandle(directoryHandle.DangerousGetHandle(), path, path.Capacity, 0);
         if (size<0)
         throw new Win32Exception(Marshal.GetLastWin32Error());
         // The remarks section of GetFinalPathNameByHandle mentions the return being prefixed with "\\?\"
         // More information about "\\?\" here -> http://msdn.microsoft.com/en-us/library/aa365247(v=VS.85).aspx
         if (path[0] == '\\' && path[1] == '\\' && path[2] == '?' && path[3] == '\\')
         return path.ToString().Substring(4);
         else
         return path.ToString();
     }
    "@ -Name Win32 -NameSpace System -UsingNamespace System.Text,Microsoft.Win32.SafeHandles,System.ComponentModel
    
    $dir = Get-Item D:\1
    [System.Win32]::GetSymbolicLinkTarget($dir)
    
    0 讨论(0)
  • 2020-12-09 16:02

    New-Item, Remove-Item, and Get-ChildItem have been enhanced to support creating and managing symbolic links. The -ItemType parameter for New-Item accepts a new value, SymbolicLink. Now you can create symbolic links in a single line by running the New-Item cmdlet.

    What's New in Windows PowerShell v5

    I've checked the symlink support on the my Windows 7 machine, it's works fine.

    PS> New-Item -Type SymbolicLink -Target C:\ -Name TestSymlink
    
    
        Directory: C:\Users\skokhanovskiy\Desktop
    
    
    Mode                LastWriteTime         Length Name
    ----                -------------         ------ ----
    d----l       06.09.2016     18:27                TestSymlink
    

    Get target of the symbolic link as easy as to create it.

    > Get-Item .\TestSymlink | Select-Object -ExpandProperty Target
    C:\
    
    0 讨论(0)
  • 2020-12-09 16:08

    We end up using this function

    function Get-SymlinkTargetDirectory {           
        [cmdletbinding()]
        param(
            [string]$SymlinkDir
        )
        $basePath = Split-Path $SymlinkDir
        $folder = Split-Path -leaf $SymlinkDir
        $dir = cmd /c dir /a:l $basePath | Select-String $folder
        $dir = $dir -join ' '
        $regx = $folder + '\ *\[(.*?)\]'
        $Matches = $null
        $found = $dir -match $regx
        if ($found) {
            if ($Matches[1]) {
                Return $Matches[1]
            }
        }
        Return '' 
    }
    
    0 讨论(0)
提交回复
热议问题