Powershell: Passing an escaped wildcard expression to -Path in file-creating cmdlets causes the escaped representation to be used as the literal filename

Created on 26 Apr 2019  路  6Comments  路  Source: PowerShell/PowerShell

Note: This seems to affect all file-creating cmdlets - including > and >> - with the laudable exception of Set-Content and Add-Content.

Conceptually related: #7999

Say you want to create a file literally named file[1].txt.

In order to successfully pass this name to a -Path parameter (or -FilePath or -OutFile), it must be escaped as file`[1`].txt, because -Path parameters interpret their arguments as wildcard expressions.

While such an escaped path is _accepted_, the problem is that _it isn't retranslated into its literal equivalent on file creation_, resulting in the _escaped_ representation being used as the filename; in the example above, you end up with a file literally named file`[1`].txt instead of the intended file[1].txt

This problem can generally be avoided by passing the filename to the -LiteralPath parameter instead, but that is not yet always an option - see #3174 - and, of course, the bug is worth fixing either way.

Steps to reproduce

Run the following Pester tests:

Describe '-Path file-creation test' {
  BeforeAll { Push-Location TestDrive:\ }
  AfterAll { Pop-Location }
  It "Escaped wildcard paths are created as their literal equivalents" {
   'hi' | Out-File -FilePath 'file`[1`].txt'
    Test-Path -LiteralPath 'file[1].txt' | Should -Be $true
    Test-Path -LiteralPath 'file`[1`].txt' | Should -Be $false
 }
}

Expected behavior

The tests should pass.

Actual behavior

The tests fail, because the actual name of the file created is literally file`[1`].txt, i.e., the _escaped_ name.

Environment data

PowerShell Core 6.2.0 / Windows PowerShell v5.1
Issue-Question

All 6 comments

https://github.com/PowerShell/PowerShell/pull/9258 is related to this but I am not sure if it covers all situations.

It feels to me the right behavior for Out-File -FilePath may be:

  • when searching for an existing file, wildcard and escaped wildcard characters are respected, meaning Out-File -FilePath [ab].txt should be able to target the existing file a.txt, and Out-File -FilePath '[out].txt' should be able to target the existing file [out].txt.
  • when no existing file is found, the provided path to -FilePath should be used as literal path to create the new file, just like what New-Item does, meaning Out-File -FilePath [hello].txt should create '[hello].txt' when no existing file found that matches the wildcard pattern.
  • redirection operator > and >> may be better always use -LiteralPath parameter when invoking Out-File. But this will be a breaking change.

The part I don't like about this proposal is that Out-File -FilePath would target different files depending on if an existing file that matches the wildcard pattern exists or not. For example, Out-File -FilePath [ab].txt would target a.txt if that file already exists, and [ab].txt if no matching file found.

This is a general issue that exists for all cmdlets that do operations that search existing files before create new files.

https://github.com/PowerShell/PowerShell/pull/9258 is an attempt to address the issue reported here for Out-File -FilePath. But I think this is a general issue that should be fixed in a wider scope.

I have always been surprised that the behavior of the path (literalpath) parameters varies in different cmdlets. This causes an endless stream of fixes.
It would be great if the paths (literalpaths) always behaved in the same way, and any deviations were regulated by separate parameters.

It feels to me the right behavior for Out-File -FilePath may be:

  • when searching for an existing file, wildcard and escaped wildcard characters are respected, meaning Out-File -FilePath [ab].txt should be able to target the existing file a.txt, and Out-File -FilePath '[out].txt' should be able to target the existing file [out].txt.
  • when no existing file is found, the provided path to -FilePath should be used as literal path to create the new file, just like what New-Item does, meaning Out-File -FilePath [hello].txt should create '[hello].txt' when no existing file found that matches the wildcard pattern.
  • redirection operator > and >> may be better always use -LiteralPath parameter when invoking Out-File. But this will be a breaking change.

The part I don't like about this proposal is that Out-File -FilePath would target different files depending on if an existing file that matches the wildcard pattern exists or not. For example, Out-File -FilePath [ab].txt would target a.txt if that file already exists, and [ab].txt if no matching file found.

I believe it is not a good idea to have a single parameter handling two different usages that might conflict with each other. For example, how do we tell if a user typed an incorrect wildcard accidentally, or a literal filename intentionally? To keep the things simple, I would prefer to use the existing Path/LiteralPath concept:

  1. Make -FilePath an alias of -Path
  2. Add -LiteralPath to the cmdlet

As for > and >>, would it be possible to add configurable settings (e.g. environment variable) to define whether it (or even the engine itself) accepts -Path or -LiteralPath by default when it is called from console/script-file, to give users more control?

Agreed re consistency, @kwkam.

The larger issue, however, is that _output_ file paths should never have been treated as wildcards to begin with, which takes us back to #4726, #6729, and also #9225

P.S.: The -FilePath parameters already are aliased to -Path:

Command           Name                   Aliases
-------           ----                   -------
Export-FormatData Path                   {FilePath}
Invoke-Command    FilePath               {PSPath}
Out-File          FilePath               {Path}
Set-TraceSource   FilePath               {PSPath, Path}
Start-Process     FilePath               {PSPath, Path}
Tee-Object        FilePath               {Path}
Trace-Command     FilePath               {PSPath, Path}

Note that PSPath is yet another alias for some of them, which is also problematic, because PSPath should be the alias of -LiteralPath, to ensure treatment of pipeline input from, e.g., Get-ChildItem as the literal paths that they are.

Was this page helpful?
0 / 5 - 0 ratings