Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to escape square brackets in file paths with Invoke-WebRequest's -OutFile parameter

When you include something like [1] in a file name like File[1].txt used with the Invoke-WebRequest and the -OutFile parameter you get an error Cannot perform operation because the wildcard path File[1].txt did not resolve to a file.

This is caused by the behavior documented here.

With other cmdlets you would use -LiteralPath to force the path to be taken literally but in this case that is not an option.

I have tried escaping the [ and ] characters with ` or \ but it still gives the same error.

To simplify testing you can reproduce the same issue with Out-File, Test-Path, etc.

#Fails
Out-File -FilePath "file[1].txt"
Out-File -FilePath "file`[1`].txt"
Out-File -FilePath "file\[1\].txt"

#Succeeds
Out-File -LiteralPath "file[1].txt"

#Fails
Test-Path -Path "file[1].txt"

#Succeeds
Test-Path -LiteralPath "file[1].txt"

How can I escape characters that would be used to express wildcards in -Path, -FilePath, -OutFile, etc. so that they function like the string was specified with -LiteralPath since -LiteralPath isn't available with Invoke-WebRequest?

like image 493
Chris Magnuson Avatar asked Jan 26 '23 09:01

Chris Magnuson


1 Answers

Update:

  • In PowerShell (Core) 7.1+, file paths passed to the -OutFile parameter of Invoke-WebRequest and Invoke-RestMethod are now interpreted literally:

    • That is, -OutFile now acts like -LiteralPath,[1] and there is no longer a need to escape [ and ] characters, so that the following example command works as-is:

      # PowerShell 7.1+ only.
      Invoke-WebRequest http://example.org -OutFile File[1].txt
      
  • Therefore, the following applies only to Windows PowerShell (and to now-obsolete PowerShell (Core) versions v7.0 and below):


Escaping the [ and ] characters as `[ and `] so that they are treated literally when interpreted as a wildcard expression with -Path (-FilePath) and -OutFile unfortunately only half works at the moment, due to a bug discussed in the bottom section:

  • Performing the escaping ensures that the target parameter accepts the path (the command doesn't break anymore) ...

  • ... but on creation of the file it is mistakenly the escaped representation that is used as the literal filename - see bottom section.

Workaround for now:Tip of the hat to hashbrown for helping to simplify it.

  • Make Invoke-RestMethod / Invoke-WebRequest save to a temporary file...
  • ... and then rename (move) the temporary file to the desired output file path.
# Literal output file path.
$outFile = '.\file[1].txt'

# Simulate a call to Invoke-RestMethod / Invoke-WebRequest -OutFile. 
# Save to a *temporary file*, created on demand - such
# a temporary file path can be assumed to never contain '[' or ']'
'hi' |  Out-File -FilePath ($tempFile = New-TemporaryFile)

# Rename (move) the temporary file to the desired target path.
Move-Item -Force -LiteralPath $tempFile -Destination $outFile

In Windows PowerShell v4-, use [IO.Path]::GetTempfileName() in lieu of New-TemporaryFile.


Escaping [literal] paths for use as wildcard patterns:

Use any of the following string-literal representations, which ultimately result in the same string with verbatim content file`[1`].txt, which, when interpreted as a wildcard expression, is the escaped equivalent of literal string file[1].txt:

  • 'file`[1`].txt'
  • "file``[1``].txt"
  • file``[1``].txt

To create this escaping programmatically, use:

$literalName = 'file[1].txt'
$escapedName = [WildcardPattern]::Escape($literalName) # -> 'file`[1`].txt'

What matters is that the target cmdlet sees the [ and ] as `-escaped in the -Path (-FilePath) argument it is passed for them to be treated verbatim.

If you use "..." quoting or an unquoted argument (which mostly behaves as if it were enclosed in "..."), PowerShell's string parsing gets in the way: ` is also used as the escape character inside expandable strings ("..."), so in order to pass ` through, you must escape it itself, as ``.

  • Otherwise something like `[ inside "..." turns into just [ - the ` is "eaten" - because `[ is an escaped [ from "..."'s perspective, and escaping a character that doesn't need escaping turns into just that character; in short: both "file`[1`].txt" and file`[1`].txt turn into plain file[1].txt, as if you had never used `.

By contrast, ` characters are used verbatim inside '...'-quoted strings and need no escaping.


Flawed file-creation behavior of many cmdlets with -Path:

The bug mentioned above - that on file creation the escaped representation is mistakenly used as the literal filename - affects most cmdlets, unfortunately: That is, they unexpectedly retain the ` characters in the escaped pattern on creating a file, so that by specifying -Path 'file[1].txt' you'll end up with a file literally named file`[1`].txt.

Fortunately, most cmdlets do support -LiteralPath, so use of -LiteralPath file[1].txt is the better choice anyway and avoids this bug.

Some of the affected cmdlets:

  • Invoke-WebRequest and Invoke-RestMethod

  • Out-File and therefore also redirection operators > and >>, which effectively call Out-File behind the scenes.

  • Note that Set-Content and Add-Content do not exhibit this problem.

  • All(?) Export-* cmdlets.

  • Others?

The bug has been reported in GitHub issue #9475.


[1] This was technically a breaking change, but it was considered acceptable, due to the counterintuitive nature of the original behavior. Unfortunately, the counterintuitive behavior still surfaces in many other contexts - including still with Out-File unless -LiteralPath is explicitly used. See GitHub issue #17106 for a summary.

like image 140
mklement0 Avatar answered Jan 29 '23 21:01

mklement0