在 PowerShell 中,我需要解析连接的目标路径(符号链接)。
例如,假设我有一个路口c:\someJunction
,其目标是c:\temp\target
我尝试了 的变体$junc = Get-Item c:\someJunction
,但只能得到c:\someJunction
在此示例中,如何找到c:\temp\target
给定交汇点的交汇点目标路径?
在 PowerShell 中,我需要解析连接的目标路径(符号链接)。
例如,假设我有一个路口c:\someJunction
,其目标是c:\temp\target
我尝试了 的变体$junc = Get-Item c:\someJunction
,但只能得到c:\someJunction
在此示例中,如何找到c:\temp\target
给定交汇点的交汇点目标路径?
New-Item、Remove-Item 和 Get-ChildItem 已得到增强,可支持创建和管理符号链接。New-Item 的 -ItemType 参数接受一个新值 SymbolicLink。现在,您可以通过运行 New-Item cmdlet 在一行中创建符号链接。
我检查了我的 Windows 7 机器上的符号链接支持,它工作正常。
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-Item .\TestSymlink | Select-Object -ExpandProperty Target
C:\
这个问题有一些非常复杂的答案!这是一个超级简单且不言自明的:
(Get-Item C:\somejunction).Target
您可以通过执行以下操作获取路径:
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)
这可以减少工作量,甚至适用于远程服务器上的联结:
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
}
至少在 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~
(其他答案也包含了这一点,但不容易看到/理解)
我们最终使用了这个函数
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 ''
}
似乎使用 PS5(如这里或其他地方提到的?),系统定义的连接点/重解析点有一个错误,而符号链接和用户定义的连接点/重解析点没有。这些解决方案似乎适用于符号链接,但不适用于连接。我唯一可以开始工作以显示正确信息的是 fsutil 程序。唯一的问题是它输出类似于 Format-Hex 的数据,如下所示。
fsutil 工具在与 $path 中存储的 Windows 应用商店应用程序一起使用时为我提供此输出
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 应用商店应用程序工作。这个可以。请在您的使用中对其进行测试,并指出是否存在问题。
***** 已编辑以允许文件或目录。*****
$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
根据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]
}