powershellescapingglob

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?


Solution

  • Update:


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

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

    # 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:

    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 ``.

    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:

    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.