powershell: неправильное разрешение символьной ссылки

#powershell #symlink #subst

#powershell #символическая ссылка #subst

Вопрос:

я думаю, что нашел ошибку в 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}
  

как вы видите, буква диска в target указана неправильно.
как это происходит?

моя версия 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?

большое спасибо

Ответ №1:

Я согласен, что это ошибка, но:

  • В вашем коде нет символьной ссылки или другой точки повторной обработки NTFS (например, соединения).

  • Таким образом, .Target свойство, которое сообщает о цели точки повторной обработки, даже не должно быть заполнено; это фактическая ошибка, которая больше не существует в PowerShell [Core] v6 .

Таким образом, чтобы отсеять такие ложные .Target значения, вместо этого вы можете фильтровать файлы по их .LinkType свойству:

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

Отдельно, если вы ищете способ преобразовать пути на основе замененных дисков в их базовые физические пути:

К сожалению, ни Convert-Path , ни Get-PSDrive , похоже, не знают о замененных дисках (созданных с subst.exe помощью) — даже в PowerShell 7.0 — поэтому вам придется выполнить собственную команду перевода:

 amp; {
  $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 # 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.CoreFileSystem::'

      # 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

  }

}
  

Комментарии:

1. Спасибо. на самом деле мой вариант использования заключался в разрешении всех символических ссылок по заданному пути, и у меня была функция, которая работала нормально долгое время, и, как ни странно, она наткнулась на замененную букву диска…

2. Я вижу, @mmoossen. Я подозреваю, что вы знаете, но чтобы объяснить это: обходным путем для этого является фильтрация по Where-Object LinkType -eq SymbolicLink — я также добавил это к ответу.