powershell:错误的符号链接解析

问题描述

我认为我在PS中发现了一个错误

我创建一个新的替换驱动器号:

C:\> subst k: c:\test
C:\> subst
K:\: => c:\test

但是PS告诉:

PS C:\> get-item 'K:\' | Format-list | Out-String

Directory:
Name           : K:\
Mode           : d-----
LinkType       :
Target         : {K:\test}

如您所见,目标中的驱动器号错误。 怎么来的?

我的Windows版本:

Windows 10 Enterprise
Version 1809
OS Build 17763.1457

我的PS版本:

PS C:\> $PsversionTable
Name                           Value
----                           -----
Psversion                      5.1.17763.1432
PSEdition                      Desktop
PSCompatibLeversions           {1.0,2.0,3.0,4.0...}
BuildVersion                   10.0.17763.1432
CLRVersion                     4.0.30319.42000
WSManStackVersion              3.0
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1

如何使用ps获得正确的目标?

非常感谢

解决方法

我同意这是一个错误,但是:

  • 您的代码中没有符号链接或其他NTFS 解析点(例如 junction )。

  • 因此,.Target属性(报告一个修复点的目标)甚至不应被填充;这是实际的错误,在PowerShell [Core] v6 +中已不再存在。

因此,为了清除错误的.Target值,您可以改用文件的.LinkType属性来过滤文件:

Get-ChildItem | Where-Object LinkType -eq SymbolicLink # now,.Targets are valid

另外,如果您正在寻找一种方法来将基于替代驱动器的路径转换为其基础物理路径

不幸的是,Convert-PathGet-PSDrive似乎都没有意识到 替换驱动器(由subst.exe创建)-甚至没有在PowerShell 7.0中-因此您必须滚动您自己的翻译命令

& {
  $fullName = Convert-Path -LiteralPath $args[0]
  $drive = Split-Path -Qualifier $fullName
  if ($drive.Length -eq 2 -and ($substDef = @(subst.exe) -match "^$drive")) {
    Join-Path ($substDef -split ' ',3)[-1] $fullName.Substring($drive.Length)
  } else {
    $fullName
  }
} 'K:\'

根据您的情况,以上内容应返回C:\test\

注意:由于使用Convert-Path以上内容仅适用于现有路径;使其支持不存在的路径需要大量工作(请参见下文)。
请注意,长期存在的GitHub feature request #2993要求增强Convert-Path以使其也适用于不存在的路径。

在此期间,此高级功能Convert-PathEx 填补了空白。

定义后,您可以改为执行以下操作:

PS> Convert-PathEx K:\
C:\test\
function Convert-PathEx {
  <#
.SYNOPSIS
Converts file-system paths to absolute,native paths.

.DESCRIPTION
An enhanced version of Convert-Path,which,however only supports *literal* paths.
For wildcard expansion,pipe from Get-ChildItem or Get-Item.

The enhancements are:

* Support for non-existent paths.
* On Windows,support for translating paths based on substituted drives
  (created with subst.exe) to physical paths.

#>
  [CmdletBinding(PositionalBinding = $false)]
  param(
    [Parameter(Mandatory = $true,Position = 0,ValueFromPipeline,ValueFromPipelineByPropertyName)]
    [Alias('PSPath','LP')]
    [string[]] $LiteralPath
  )

  begin {

    $isWin = $env:OS -eq 'Windows_NT'

    # Helper function for ignoring .Substring() exceptions.
    function fromPos ($str,$ndx) {
      try { return $str.Substring($ndx) } catch { return '' }
    }

  }

  process {

    foreach ($path in $LiteralPath) {

      $path = $path -replace '^.+::' # strip any PS provider prefix,such as 'FileSystem::' or 'Microsoft.PowerShell.Core\FileSystem::'

      # Analyze drive information.
      $driveSpec = Split-Path -ErrorAction Ignore -Qualifier $path
      $driveObj = if ($driveSpec) { (Get-PSDrive -ErrorAction Ignore -PSProvider FileSystem -Name $driveSpec.Substring(0,$driveSpec.Length - 1)) | Select-Object -First 1 } # !! Get-PSDrive can report *case-sensitive variations* of the same drive,so we ensure we only get *one* object back.
      if ($driveSpec -and -not $driveObj) {
        Write-Error "Path has unknown file-system drive: $path" -Category InvalidArgument
        continue
      }

      $rest = if ($driveObj) { fromPos $path $driveSpec.Length } else { $path }
      $startsFromRoot = $rest -match '^[\\/]'
      if ($startsFromRoot) { $rest = fromPos $rest 1 } # Strip the initial separator,so that [IO.Path]::Combine() works correctly (with an initial "\" or "/",it ignores attempts to prepend a drive).
      $isAbsolute = $startsFromRoot -and ($driveObj -or -not $isWin) # /... paths on Unix are absolute paths.

      $fullName =
      if ($isAbsolute) {
        if ($driveObj) {
          # Prepend the path underlying the drive.
          [IO.Path]::Combine($driveObj.Root,$rest)
        } else {
          # Unix: Already a full,native path - pass it through.
          $path
        }
      } else {
        # Non-absolute path,which can have one three forms:
        #  relative: "foo","./foo"
        #  drive-qualified relative (rare): "c:foo"
        #  Windows drive-qualified relative (rare): "c:foo"
        if ($startsFromRoot) {
          [IO.Path]::Combine($PWD.Drive.Root,$rest)
        } elseif ($driveObj) {
          # drive-qualified relative path: prepend the current dir *on the targeted drive*.
          # Note: .CurrentLocation is the location relative to the drive root,*wihtout* an initial "\" or "/"
          [IO.Path]::Combine($driveObj.Root,$driveObj.CurrentLocation,$rest)
        } else {
          # relative path,prepend the provider-native $PWD path.
          [IO.Path]::Combine($PWD.ProviderPath,$rest)
        }
      }

      # On Windows: Also check if the path is defined in terms of a
      #             *substituted* drive (created with `subst.exe`) and translate
      #             it to the underlying path.
      if ($isWin) {
        # Note: [IO.Path]::GetPathRoot() only works with single-letter drives,which is all we're interested in here.
        #       Also,it *includes a trailing separator*,so skipping the length of $diveSpec.Length works correctly with [IO.Path]::Combine().
        $driveSpec = [IO.Path]::GetPathRoot($fullName)
        if ($driveSpec -and ($substDef = @(subst.exe) -like "$driveSpec*")) {
          $fullName = [IO.Path]::Combine(($substDef -split ' ',3)[-1],(fromPos $fullName $driveSpec.Length))
        }
      }

      # Finally,now that we have a native path,we can use [IO.Path]::GetFullPath() in order
      # to *normalize*  paths with components such as "./" and ".."
      [IO.Path]::GetFullPath($fullName)

    } # foreach

  }

}