Skip to content

Windows (NTFS): Inconsistent support for the long-path prefix (\\?\) in filesystem paths #10805

@mklement0

Description

@mklement0

Note: This is a generalization of #4439. @SteveL-MSFT, if you agree, please close the latter.

Prefixing full, normalized, native filesystem paths with \\?\ allows targeting filesystem items whose path is longer than the legacy limit of 259 characters.

Update:

  • Prefix \\?\ isn't needed in PS Core (in .NET Core altogether), so long paths just work as-is, even if not enabled system-wide.
  • However, old code may still use it, and even new code may have to, when creating cross-edition scripts.
  • Additionally, and separately, \\?\ is useful for targeting files or directories with irregular names, such as ones with trailing spaces, so you can remove them with Remove-Item, for instance.

As an aside: In Windows 10 you can now opt in system-wide to support long paths, but individual applications must also opt in (PowerShell indirectly does) - see https://blogs.msdn.microsoft.com/jeremykuhne/2016/07/30/net-4-6-2-and-long-paths-on-windows-10/

As of PowerShell Core 7.3.0-preview.2, the inconsistent support for \\?\ is as follows:

  • With the -Path parameter, \\?\ isn't supported at all , so wildcard expressions cannot be used.

    • Get-Item and Get-ChildItem output nothing, and Remove-Item is a quiet no-op (silent failure) too.
  • With the -LiteralPath parameter in combination with \\?\:

    • Only file paths can be targeted.
    • Directory paths exhibit the following behavior:
      • Get-Item and Remove-Item complain about not finding the path.
        • Adding -Force makes Get-Item output a broken DirectoryInfo instance.
        • Using Remove-Item without -Recurse on a nonempty directory presents the usual confirmation prompt (implying the ability to recognize the path as existent), but then fails on confirmation.
      • Get-ChildItem reports the root directory's content instead(!)

Additionally, \\?\ paths do not work in the following cases:

Note:

  • Some of these problems are regressions from Windows PowerShell, where only the invocation / Start-Process tests fail and > only with a new file.

  • I haven't looked into whether invoking an executable with an overly long path is supported in principle by the underlying APIs.

Steps to reproduce

Run the following from a Pester test script (*.Tests.ps1) on Windows:

Describe "Support for long paths, via \\?\" {
  BeforeAll {
    Push-Location (Convert-Path TestDrive:\)
    $dirPath = $PWD.ProviderPath
    $PrefixedDir = "\\?\$dirPath"
    $fileName = ('a' * 248 + '.cmd')
    $fileNameAlt = ('b' * 248)
    $PrefixedFullName = "$PrefixedDir\$fileName"
    $PrefixedFullNameAlt = "$PrefixedDir\$fileNameAlt"
    # Create the file with the overly long path using .NET,
    # to avoid issues with New-Item
    [IO.File]::WriteAllText($PrefixedFullName, '')
  }
  It "Get-ChildItem -LiteralPath" {
    Get-ChildItem -LiteralPath $PrefixedFullName | % FullName | Should -Be $PrefixedFullName
  }
  It "Get-ChildItem -Path" {
    Get-ChildItem -Path $PrefixedFullName | % FullName | Should -Be $PrefixedFullName
  }
  It "Remove-Item -LiteralPath" {
    { Remove-Item -LiteralPath $PrefixedFullName } | Should -Not -Throw
    # Recreate the file.
    [IO.File]::WriteAllText($PrefixedFullName, '')
  }
  It "Remove-Item -Path" {
    { Remove-Item -Path $PrefixedFullName } | Should -Not -Throw
    # Recreate the file, if necessary
    [IO.File]::WriteAllText($PrefixedFullName, '')
  }
  It "> with new file" {
    { '' >  $PrefixedFullNameAlt } | Should -Not -Throw
  }
  It "> / >> with existing file." {
    { '@echo Hi.' >  $PrefixedFullName } | Should -Not -Throw
    { 'REM ' >> $PrefixedFullName } | Should -Not -Throw
  }
  It "Invocation / &" {
    & $PrefixedFullName | Should -Be 'Hi.'
  }
  It "Start-Process" {
    Start-Process -FilePath $PrefixedFullName
  }
  It "New-Item" {
    { New-Item -Force -Type File $PrefixedFullName } | Should -Not -Throw
  }
  It "Set-Location -LiteralPath" {
    { Set-Location -EA Stop -LiteralPath $PrefixedDir } | Should -Not -Throw
  }
  It "Set-Location -Path" {
    { Set-Location -EA Stop -Path $PrefixedDir } | Should -Not -Throw
  }
  AfterAll {
    # Use .NET to remove the overly long path, so that Pester itself doesn't 
    # fail on trying to remove the dir. underlying TestDrive:
    [IO.File]::Delete($PrefixedFullName)
    [IO.File]::Delete($PrefixedFullNameAlt)
    Pop-Location
  }
}

Expected behavior

All tests should pass.

Actual behavior

All tests but the first one fail, with various error messages.

Environment data

PowerShell Core 7.0.0-preview.4

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-FileSystem-Providerspecific to the FileSystem providerIssue-BugIssue has been identified as a bug in the productWG-Engine-Providersbuilt-in PowerShell providers such as FileSystem, Certificates, Registry, etc.WG-NeedsReviewNeeds a review by the labeled Working Group

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions