用于解析联结目标路径的powershell

时间:2013-06-04 19:40:55

标签: powershell symlink junction

在PowerShell中,我需要解析联结的目标路径(符号链接)。

例如,假设我有一个目标为c:\someJunction

的联结c:\temp\target

我尝试了$junc = Get-Item c:\someJunction的各种变体,但只能获得c:\someJunction

如何找到给定交汇点的交汇点的目标路径,在此示例中为c:\temp\target

8 个答案:

答案 0 :(得分:28)

  

New-Item,Remove-Item和Get-ChildItem已得到增强,可支持创建和管理符号链接。 New-Item的-ItemType参数接受新值SymbolicLink。现在,您可以通过运行New-Item cmdlet在一行中创建符号链接。

What's New in Windows PowerShell v5

我已经检查了我的Windows 7机器上的符号链接支持,它的工作正常。

> 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-Item .\TestSymlink | Select-Object -ExpandProperty Target
C:\

答案 1 :(得分:6)

此问题有一些确实复杂的答案!这是一个超级简单和自我解释的东西:

(Get-Item C:\somejunction).Target

答案 2 :(得分:4)

您可以通过执行以下操作来获取路径:

Get-ChildItem -Path C:\someJunction

编辑以查找路径而不是文件夹的内容

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)

答案 3 :(得分:4)

这样可以减少工作量,甚至可以用于远程服务器上的联结:

fsutil reparsepoint query "M:\Junc"

如果您只想要目标名称:

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

所以

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

此外,下面的代码稍微修改了Josh在上面提供的代码。它可以放在一个多次读取的文件中,并在网络驱动器的情况下正确处理前导\\?\

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)
}

并且给定上面的函数Get_UNCPath,我们可以改进函数Get_JunctionTarget,如下所示:

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
}

答案 4 :(得分:3)

我们最终使用此功能

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 '' 
}

答案 5 :(得分:0)

至少在PSv5中,这样简单就可以列出某些dirs链接的所有目标(或进一步向下链接一个目标),并将其作为对象并经过良好格式化(例如,所有*~ dirs实际上都是结点) :

C:\Jaspersoft> ls | select name, target

Name                          Target
----                          ------
apache-websrv~                {C:\Program Files (x86)\Apache24\}
jasperreports-server-cp-6.3.0 {}
jasperreports-server-cp~      {C:\Jaspersoft\jasperreports-server-cp-6.3.0}
jr-srv-cp~                    {C:\Jaspersoft\jasperreports-server-cp~}

一个链接:

C:\Jaspersoft> ls . apache-websrv~ | select name, target

Name           Target
----           ------
apache-websrv~ {C:\Program Files (x86)\Apache24\}

或(仅获取C:\Jaspersoft\apache-websrv~结的目标作为字符串值):

> ls  C:\Jaspersoft  apache-websrv~  | %{$_.target}
C:\Program Files (x86)\Apache24\

示例中的标准ls如下所示:

C:\Jaspersoft> ls

    Verzeichnis: C:\Jaspersoft


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d----l       01.04.2019     15:05                apache-websrv~
d-----       02.04.2019     10:30                jasperreports-server-cp-6.3.0
d----l       05.10.2018     15:19                jasperreports-server-cp~
d----l       12.02.2019     11:46                jr-srv-cp~

(其他答案也以某种方式包含了这一点,但不容易被看到/理解)

答案 6 :(得分:0)

似乎使用PS5(如此处或其他地方提到的那样),系统定义的连接点/修复点有一个错误,而符号链接和用户定义的连接点/修复点则没有。这些解决方案似乎适用于符号链接,但不适用于结点。我唯一可以显示正确信息的东西是fsutil程序。唯一的问题是它输出类似于Format-Hex的数据,如下所示。

当与$ path中存储的Windows Store应用程序一起使用时,fsutil工具会向我提供此输出

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...

这是我想到的用于解析该输出的代码。我知道这是很乱的代码,但正如我所说,我无法在这里获得其他解决方案以用于Windows Stores应用程序。这一个。请在使用时对其进行测试,并提及是否存在问题。

*****编辑以允许文件或目录。 *****

$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

答案 7 :(得分:0)

根据 The Nerdy Chef 的回答,我稍微简化了代码。优点:这样它也适用于交界处。

function Get-ReparseTarget {
    [cmdletbinding()]
    param(
        [Parameter(Mandatory=$true)][string]$path
    )
    $fsutil = fsutil.exe reparsepoint query $path
    # gets the hex-stream out of fsutil output as array
    $hex = ($fsutil.Where({$_ -match "[0-9a-f]{4}: .*"}) | Select-String "[0-9a-f][0-9a-f] " -AllMatches).Matches.Value.Trim()
    # Convert to Bytestream
    $Bytestream = [byte[]]($hex | foreach{[Convert]::ToInt32($_,16)})
    # Unicode2Ascii + Trim the "Trailing Zero", which is added depending on the target type.
    $Unicode = ([System.Text.Encoding]::Unicode.GetChars($Bytestream) -join '').TrimEnd("`0")
    # We split by "Zero Character" and by "\??\", and keep the latest match, works for a file, a directory and a junction.
    $($Unicode -split "`0" -split "\\\?\?\\")[-1]
}