23

I'm using PowerShell on Windows 7, and writing a script to copy a bunch of files from one folder structure to another. Kind of like compiling. The PowerShell Copy-Item cmdlet thinks that square brackets, [ ], are wildcards of some kind, and I am not able to escape them for some reason.

I can't use -LiteralPath, because I want to use an asterisk * wildcard since the filename has a date as part of the filename, and the date changes. The date is used as a version number.

This post was helpful, but no amount of ticks (2x or 4x per bracket) escapes the square brackets.

I am not receiving an error; PowerShell behaves the same as if I entered in the wrong filename.

This is the specific line I'm working on:

#to Fusion Server
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\"[RoomView] Versions explained*.pdf" -Destination $FSG\$containerFolder\$rootFolder\"Fusion Server"\

And this is the whole thing:

# Compiles the Fusion packet for distribution

###############################
###########Variables###########
###############################

#folder structure
$FSG = "F:\FSG"
$containerFolder = "Packet.Fusion for IT and AV Professionals"
$rootFolder      = "Fusion for IT and AV pros $(Get-Date -format “MM-dd-yyyy”)"
$subRoot1        = "Fusion Server"
$subRoot2        = "Scheduling Enhancement and Panels"
$subRoot2sub1    = "Scheduling Panels"
$subRoot3        = "SQL Server"

#source folders
$HW      = "0.Hardware"
$3SMDoc  = "0.Hardware\TPMC-3SM.Documentation"
$4SMDoc  = "0.Hardware\TPMC-4SM.Documentation"
$4SMDDoc = "0.Hardware\TPMC-4SM-FD.Documentation"
$730Doc  = "0.Hardware\TSW-730.Documentation"
$730OLH  = "0.Hardware\TSW-730.OLH"
$CENRVS  = "0.Hardware\CEN-RVS.Notes"

$ProjMgmt = "0.Project Management"

$SW            = "0.Software"
$RVLicensing   = "0.Software\0.RoomView.License"
$RVNotes       = "0.Software\0.RoomView.Notes"
$SQLLicensing  = "0.Software\database.SQL.Licensing"
$SQLNotes      = "0.Software\database.SQL.Notes"
$FRVMarketing  = "0.Software\Fusion RV.Marketing"
$FRVNetworking = "0.Software\Fusion RV.Networking"
$FRVNotes      = "0.Software\Fusion RV.Notes"


###############################
#create the directory structure
###############################

md -Path $FSG\$containerFolder -Name $rootFolder

cd $FSG\$containerFolder\$rootFolder
md "eControl and xPanels"
md "Fusion Server" #$subRoot1
md "Getting Started as a User"
md "Project Management"
md "RoomView Connected Displays"
md "Scheduling Enhancement and Panels" #$subRoot2
md "SQL Server" #$subRoot3

cd $FSG\$containerFolder\$rootFolder\$subRoot1
md "CEN-RVS"
md "Licenseing Information"
md "Networking"
md "Official Documentation"
md "Prerequisites, including powerShell script"
md "Product Info"
md "Requirements"
md "Tech Info"
md "Windows Authentication to Fusion RV"

cd $FSG\$containerFolder\$rootFolder\$subRoot2
md "Outlook Add-in"
md "Scheduling Panels" #$subRoot2sub1

cd $FSG\$containerFolder\$rootFolder\$subRoot2\$subRoot2sub1
md "TPMC-3SM"
md "TPMC-4SM"
md "TPMC-4SM-FD"
md "TSW-730"

cd $FSG\$containerFolder\$rootFolder\$subRoot3
md "Multi-database model only"
md "SQL Licensing"

cd $FSG\$containerFolder
#reset current folder


###############################
#copy the files
###############################

#Copy-Item -Path C:\fso\20110314.log -Destination c:\fsox\mylog.log

#To the root
Copy-item -Path $FSG\$ProjMgmt\starter\"Fusion Support Group Contact info*.pdf" -Destination $FSG\$containerFolder\$rootFolder\
Copy-item -Path $FSG\$containerFolder\"Fusion for IT and AV professionals release notes.txt" -Destination $FSG\$containerFolder\$rootFolder\

#to eControl and xPanels
Copy-item -Path $FSG\$SW\xpanel.Notes\starter\*.* -Destination $FSG\$containerFolder\$rootFolder\"eControl and xPanels"\

#to Fusion Server
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\"[RoomView] Versions explained*.pdf" -Destination $FSG\$containerFolder\$rootFolder\"Fusion Server"\

What can I do to escape the square brackets and still use a wildcard filename part of the Copy-Item cmdlet?

YetAnotherRandomUser
  • 1,320
  • 3
  • 13
  • 31

10 Answers10

34

In this situation, you have to use double-backticks with single quotes in order to escape the brackets. You can also use quadruple backticks when you use double quoted strings.

So the fixed line of code is:

Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\'``[RoomView``] Versions explained*.pdf' -Destination $FSG\$containerFolder\$rootFolder\'Fusion Server'\

Another good resource on file paths and wired characters etc. is to read this article: Taking Things (Like File Paths) Literally


EDIT

Thanks to @mklement0 for highlighting that the true cause of this inconsistency is because of a bug currently in PowerShell1. This bug causes escaping of wildcard characters, as well as backticks with the default -Path parameter to behave differently than other parameters e.g. the -Include and -Filter parameters.

To expand on @mklement0's excellent answer, and comments, and other answers below:

To better understand why we need single quotes and two back ticks in this situation; (and to highlight the bug and inconsistencies) let's run through some examples to demonstrate what is going on:

Get-Item, and associated cmdlets (Get-ChildItem, Copy-Item, etc.), handle the -Path parameter differently when dealing with a combination of escaped wildcard characters and unescaped wildcard characters *at the same time***!

TLDR: The underlying reason that we need a combination of single quotes and double backticks is how the underlying PowerShell provider parses the -Path parameter string for wildcards. It appears to parse it once for the escape characters, and a second time for the evaluation of the wildcard.

Let's go through some examples to demonstrate this odd outcome:

First, let's create two files to test with called File[1]a.txt and File[1]b.txt

"MyFile" | Set-Content '.\File`[1`]a.txt'
"MyFriend" | Set-Content '.\File`[1`]b.txt'

We'll try different ways to get the file. We know that Square brackets [ ] are wildcards, and so we need to escaped them with the backtick character.

We will try to get one file explicitly.

Let's start by using single quoted literal strings:

PS C:\> Get-Item 'File[1]a.txt'
PS C:\> Get-Item 'File`[1`]a.txt'

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt

PS C:\> Get-Item 'File``[1``]a.txt'

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt

For single quoted strings, one backtick is all that is required to retrieve the file, but two backticks also work.

Using Double quoted strings we get:

PS C:\> Get-Item "File[1]a.txt"
PS C:\> Get-Item "File`[1`]a.txt"
PS C:\> Get-Item "File``[1``]a.txt"

    Directory: C:\  

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt

For double quoted strings, as expected, we can see that we need two backticks to make it work.

Now, we want to retrieve both files and use a wildcard.

Let's start with single quotes:

PS C:\> Get-Item 'File[1]*.txt'
PS C:\> Get-Item 'File`[1`]*.txt'
PS C:\> Get-Item 'File``[1``]*.txt'

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt
-a----       2019-09-06   5:49 PM             10 File[1]b.txt

With the single quotes, when we have a wildcard character, we need two sets of backticks. One to escape the bracket, and a second backtick to escape the backtick that we used to escape the bracket when the wildcard is evaluated.

Similarly for double quotes:

PS C:\> Get-Item "File[1]*.txt"
PS C:\> Get-Item "File`[1`]*.txt"
PS C:\> Get-Item "File``[1``]*.txt"
PS C:\> Get-Item "File```[1```]*.txt"
PS C:\> Get-Item "File````[1````]*.txt"

    Directory: C:\

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       2019-09-06   5:42 PM              8 File[1]a.txt
-a----       2019-09-06   5:49 PM             10 File[1]b.txt

With double quotes it's a little more verbose to evaluate with a wildcard. In this case, we need four sets of back ticks. For double quotes we need two backticks to escape the bracket, and another two backticks to escape the escape characters once it comes to evaluation of the star wildcard.


EDIT

As @mklement0 mentions, this behavior with the -Path parameter is inconsistent, and behaves differently than the -Include parameter, where only a single backtick is required to properly escape the brackets. This may be "fixed" in a later version of PowerShell.


1 As of Windows PowerShell v5.1 / PowerShell Core 6.2.0-preview.3

HAL9256
  • 12,384
  • 1
  • 34
  • 46
  • As @HAL9256's linked article suggested, I used the -LiteralPath parameter instead of -Path which ensures that no characters are interpreted as wildcards (see https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.management/copy-item) – David McClelland Jul 11 '19 at 16:59
  • 1
    @mklement0 You are right, with single quotes, a single escape character is sufficient to escape the brackets. In this case, OP *also* wanted to have a normal non-escaped wildcard character apply. In this case you need two sets of escape characters for it to work. I have edited my answer to better illustrate what's going on. – HAL9256 Sep 09 '19 at 16:38
  • 2
    @HAL9256: You are correct (+1): Due to a _bug_ - see [this GitHub issue](https://stackoverflow.com/a/54108210/45375) - mixing (unescaped) `?` or `*` with escaped `[` and `]` requires the latter to be _doubly_ escaped (as seen by the target cmdlet / provider). There is absolutely no reason for this obscure behavior (I had actually [run into this](https://stackoverflow.com/a/54108210/45375)). The bug must be in the provider code, because wildcards themselves function correctly: `'a[b' -like 'a\`[*'` is `$true`, and `'a[b' -like 'a\`\`[*'` - rightfully - complains about an _invalid pattern_. – mklement0 Sep 09 '19 at 17:33
12

An overview and some background information:

  • In order to effectively escape a character that you want to be interpreted verbatim as part of a wildcard expression, it must be `-escaped as seen by the target cmdlet (its underlying PowerShell drive provider).

  • Ensuring that can get tricky, because ` (backtick) is also used as the escape character in double-quoted strings ("...") and unquoted command arguments (which for the most part behave like double-quoted strings).

Note: The scenario in the question doesn't allow use of -LiteralPath, but in cases where you know a path to be a concrete, literal path, use of the -LiteralPath (which can be shorted to -lp in PowerShell Core) is the best choice - see this answer.

When passing an argument to the wildcard-supporting -Path parameter of a PowerShell drive provider-related cmdlet (Get-ChildItem, Copy-Item, Get-Content, ...) and you want [ and ] to be treated verbatim rather than as a character set/range expression:

  • String-literal representations:

    • 'file`[1`].txt'

      • ` chars. are preserved as-is inside '...', so the target cmdlet sees them, as intended.
    • "file``[1``].txt"

      • ``, i.e. doubling is needed inside "..." in order to preserve a single ` in the resulting string (the first ` is the (double-quoted) string-internal escape character, and the second ` is the character it escapes, to be passed through).
    • file``[1``].txt (works only as a command argument, not in an expression)

      • Ditto for unquoted command arguments, which (for the most part) act like "..."
    • Caveat: Due to a bug - see GitHub issue #7999 - mixing (unescaped) ? or * with escaped [ and ] requires the latter to be doubly escaped (with ``, as seen by the target cmdlet / provider):

      • If you wanted to match literal filename file[1].txt with a wildcard pattern that matches [ and ] literally while also containing special character * (to match any run of characters), instead of the expected 'file`[1`]*', you'll have to use 'file``[1``]*' (sic); with a double-quoted or unescaped argument you then have to effectively use quadruple backticks: "file````[1````]*" / file````[1````]* - see this answer for more.

      • Note that direct use of wildcards with the -like operator is not affected:

        • 'a[b' -like 'a`[*' is - correctly - $true,
        • whereas 'a[b' -like 'a``[*' - rightfully - complains about an invalid pattern.
      • Similarly, parameters -Include and -Exclude are not affected.

      • -Filter plays by different rules to begin with: [...] as a construct isn't supported at all, and [ and ] chars. are always considered literals (again, see this answer).

  • To escape a path string programmatically, via a variable, use:

      $literalName = 'file[1].txt'
      $escapedName = [WildcardPattern]::Escape($literalName) # -> 'file`[1`].txt'
    
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Just to add, that with single quotes, a single escape character is sufficient to escape the brackets. If you want to have a normal non-escaped wildcard character also apply, you need two sets of escape characters for it to work. Please see [my edits to my answer](https://stackoverflow.com/a/21009701/2150063) for a more detailed reaon. – HAL9256 Sep 09 '19 at 16:43
  • Thanks, @HAL9256; you are correct. As stated in my comment on your answer, that requirement is a _bug_; I've also updated my answer accordingly. – mklement0 Sep 09 '19 at 17:47
  • Nice answer, it doesn't need to be a variable tho... `$([WildcardPattern]::Escape($literalName))` will work just fine too; no variable needed. Who needs a variable anyway! – Bitcoin Murderous Maniac Jul 02 '20 at 18:03
  • 1
    @BitcoinMurderousManiac: Indeed, no variable needed - the answer uses one to illustrate via the variable name what the call returns. Note that you should use `(...)`, not `$(...)` to enclose expressions and (single) commands when passing them as arguments; `$(...)` can have side effects - see [this answer](https://stackoverflow.com/a/58248195/45375). – mklement0 Jul 02 '20 at 18:43
6

I use this:

 Copy-Item  $file.fullname.replace("[", "``[").replace("]", "``]") $DestDir
Matteo1010
  • 341
  • 7
  • 12
3

The way that Powershell automatically tab-completes the filename is usually the best way,

Example:

copy-item '.\file`[test`].txt'
Vasili Syrakis
  • 9,321
  • 1
  • 39
  • 56
3

On PowerShell v 2.0 and up the escape character to use is the backslash. For example, if we want to remove the brackets from this string "[Servername: QA01]" which is the sort of output we get from the Exchange Admin PowerShell cmdlet activity in System Center Orchestrator, we use the following logic:

$string -replace '\[','' -replace '\]',''
>Servername: QA01

This is pretty weird. See, you have to use a single-quote (which normally implies in PowerShell 'evaluate this precisely as written', so this is very odd syntax).

Don't feel bad for not figuring this out on your own, this is very odd syntax.

FoxDeploy
  • 12,569
  • 2
  • 33
  • 48
  • 2
    Actually I did wind up figuring it out on my own, and I posted the answer down below. but I don't have enough reputation or the system won't allow me to move it up, so other people are promoting other answers, even though I answered my own question. The system sure didn't like something like me with no points answering my own question, lol – YetAnotherRandomUser Mar 27 '15 at 20:48
  • 1
    Yeah, answering your own problem is pretty much a recipe for downvotes here. Usually it's best to pick an answer closest to your own and call it a day. – FoxDeploy Mar 27 '15 at 20:50
  • 1
    Actually, no [yetanotherrandomuser](https://stackoverflow.com/users/2484483), it doesn't appear you answered your own question. [hal9256](https://stackoverflow.com/users/2150063) provided an answer to this question [almost 22 hours earlier](https://stackoverflow.com/questions/21008180/copy-file-with-square-brackets-in-the-filename-and-use-wildcard/#21009701) that contains the essence of your answer. To my mind, the community doesn't like what was done and is using it's voting power to illustrate as such. – user66001 Mar 07 '18 at 06:37
  • Thanks for this answer [foxdeploy](https://stackoverflow.com/users/1238413). This helped my reason for coming to this page. – user66001 Mar 07 '18 at 06:37
  • `\ ` (backslash) is the escape character in _regular expressions_, which is _unrelated to PowerShell_ - you need it here to escape the `[` and `]` _regex metacharacters_. _PowerShell's_ escape character is `\`` (backtick), in multiple contexts: double-quoted strings and unquoted command arguments, line continuation and wildcard patterns. It is the latter that the question is about: how to use `[` and `]` _literally_ in a filesystem path that is interpreted as a wildcard pattern. You're answering a different question. – mklement0 Sep 03 '19 at 21:03
1

Apparently, square brackets need double-backticks to escape, which is unusual. Reference here.

You're sure that doesn't work? I've seen it referred to a few times.

Edit: Yes, it works, you used double quotes instead of backticks.

Double quote is above the apostrophe character, next to the Enter key. Backtick is right underneath the Escape key, sharing the key with the tilde, ~.

Keeler
  • 2,102
  • 14
  • 20
  • Neither 2x nor 4x ' characters escape the [ or ] for me. Am I using it wrong? I have tried the source path within " " and without " " – YetAnotherRandomUser Jan 08 '14 at 23:00
  • Is there more than 1 type of tick? I've only got the one as far as I can tell.... By "not working" I mean that the file does not get copied. – YetAnotherRandomUser Jan 08 '14 at 23:02
  • As an aside (of little consequence): As evidenced [by the OP's own answer](https://stackoverflow.com/a/21054644/45375), it was the _single_ quote (apostrophe) that was mistaken for the backtick. – mklement0 Sep 05 '19 at 01:25
  • 1
    combination of quotes and backticks is what is required. @mklement0 answered this first, and correctly. – Jeter-work Apr 12 '23 at 22:16
0

One option is to get the filenames using the legacy dir, which will let you use the * wildcard character, but doesn't try to "blob" the square brackets. Then feed that list to move-item using -literalpath.

cmd /c dir *]* /b |
  foreach { Move-Item -LiteralPath $_ -Destination <destination path> }
user16217248
  • 3,119
  • 19
  • 19
  • 37
mjolinor
  • 66,130
  • 7
  • 114
  • 135
0

Assuming nothing else matches, you can use ? instead of the brackets. A file named "a[h-j]", copying to directory "foo":

 copy-item a?h-j? foo 
js2010
  • 23,033
  • 6
  • 64
  • 66
0

I think this works using Get-ChildItem to do your wildcard, then piping the results to Copy-Item and using LiteralPath.

Get-ChildItem $FSG\$ProjMgmt\starter\"Fusion Support Group Contact info*.pdf" | %{Copy-Item -LiteralPath $_.FullName -Destination "$FSG\$containerFolder\$rootFolder\"}

I do this often, but not with variables in my paths - may need to do something to get the variables inside the script block to scope correctly. Generally this approach makes it so that you aren't using wildcards in your Copy-Item or Move-Item paths, so LiteralPath can work. No need for escape characters, especially when you have no idea where or what those special characters are going to be in your file path.

Sedj
  • 1
-3

There's a difference between ' and `:

  • The first is the single quote that is the non-shift character on the " key.
  • The second is the backtick that I thought I was using but actually wasn't. It's the nonshift character on the ~ key.

This works:

# to Fusion Server
Copy-item -Path $FSG\$SW\0.RoomView.Notes\starter\'``[RoomView``] Versions explained*.pdf' -Destination $FSG\$containerFolder\$rootFolder\"Fusion Server"\
mklement0
  • 382,024
  • 64
  • 607
  • 775
YetAnotherRandomUser
  • 1,320
  • 3
  • 13
  • 31
  • 2
    -1 [hal9256](https://stackoverflow.com/users/2150063/) provided exactly the [same answer elsewhere on this page](https://stackoverflow.com/questions/21008180/copy-file-with-square-brackets-in-the-filename-and-use-wildcard/#21009701), sans some formatting differences. Reward those that answer people's questions by marking their answer, not a copy you make of it, as the answer. – user66001 Mar 07 '18 at 06:33
  • Really? @user66001 Trying to dig up drama on a 3 year old post? Keep reading all that text and those timestamps, because you missed some. From what I can tell 3 years later, this answer came 1 minute before the comment on your favorite answer, leading me to believe I didn't come back to this post until AFTER I figured out what my problem was and solved it. You can clearly see in other comments AND THIS ANSWER that I didn't know that ticks and single quotes were different. You'll also notice that this is the only answer that explains that ticks and single quotes are different. – YetAnotherRandomUser Mar 09 '18 at 00:44
  • Therefore, this was the answer, because my problem was that I didn't know what characters I was supposed to be using, and all the other material I consulted before posting this question was already cluing me in to the right answer. Are you going to say that @hal9256 plagiarized other websites because they were posted years prior to him posting his answer??? No, because that would be absurd. Jut like your expectations that everyone in this Q&A is connected to the internet 100% of the time, that the OP waited on answers and didn't keep trying to solve his own problem, and instantaneously – YetAnotherRandomUser Mar 09 '18 at 00:47
  • did everything recommended, regardless of sleep time, work, travel, or off time. That's absurd. – YetAnotherRandomUser Mar 09 '18 at 00:48
  • 1
    "Trying to dig up drama on a 3 year old post?" - No - I only found this post via google yesterday, as that is when I had a problem solve, and due to the decisions Stack Exchange made years ago, it was still available to not only see, but add to. – user66001 Mar 09 '18 at 04:47
  • "You'll also notice that this is the only answer that explains that ticks and single quotes are different." - No - From hal9256's answer I could only extrapolate the community thinks should be the answer (though I can't deny your answer makes it more obvious) "But, the problem is that you are using __double quotes__ instead of single quotes to enclose your string. The __double-backtick__ solution seems to only work with single quotes". This implies there is a difference, and can be seen when looking at exactly the same example that hal9256 and yourself provide in each's answer. – user66001 Mar 09 '18 at 04:53
  • 1
    "Are you going to say that @hal9256 plagiarized other websites because they were posted years prior to him posting his answer" - Good. Something we can agree on. We are not talking about the rest of the internet, this is about this page, on this Q&A site. Again based on which answer has the most votes, which is the communities way of ranking answers, what would have been appropriate here is accepting the first person to provide the correct answer and putting a comment on it on the subpoint that you didn't understand the difference between '`' and '''.This promotes people answering questions... – user66001 Mar 09 '18 at 04:57
  • ... If everyone posted on this site, went to research the problem themselves, found the solution, then came back and answered their own question, how large do you think the community would be when people get little recognition (10 points, rather than just 1) for __their__ time to answer other's questions. However, I am not the authority on this site, and I have been informed before that comments are not to be used for anything but notes on an answer, not discussion such as this, so think we should save our time debating these points further. – user66001 Mar 09 '18 at 05:00
  • @YetAnotherRandomUser: Mistaking `'` for `\`` is not reflected _in your question_, and I have rarely seen this confusion in the wild. Therefore, you're answering something that - while undoubtedly useful to you - is unlikely to help future readers (and the rest of your answer is covered by HAL9256's answer). – mklement0 Sep 09 '19 at 23:22