1

I'm trying to create small script in powershell that would move files and directories to correct localizations. I made the following command:

Get-ChildItem -Path '.\list\' | ForEach-Object { if ($($_.Name) -like '*[1]*') {
$file = $($_.Name)
$path = $($_.FullName)
echo "$file  ==>  $path"
Move-Item -Path $path -Destination .\[1]\}}

and it detects correct files and directories, but doesn't move them.
Then I decided to modify command a bit and create hardlinks instead:

Get-ChildItem -Path '.\list\' | ForEach-Object { if ($($_.Name) -like '*[1]*') {
$file = $($_.Name)
$path = $($_.FullName)
echo "$file  ==>  $path"
New-Item -Path ".\``[1``]\" -Name $file -Type HardLink -Target "$path"}}

and I received the following response (cut to only 1 loop):

[1] dir1  ==>  D:\test_move\list\[1] dir1
New-Item:
Line |
   5 |  New-Item -Path ".\``[1``]\" -Name $file -Type HardLink -Target "$path …
     |  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Cannot find path 'D:\test_move\list\[1] dir1' because it does not exist.

The same error appears both with and without administrative privileges.

What do I have to do to make it work?

mklement0
  • 382,024
  • 64
  • 607
  • 775
plW0lf
  • 13
  • 1
  • 3
  • 1
    If the path does not contain wildcards, then `-LiteralPath` may be a better choice since `[]` is syntax for a wildcard character range – AdminOfThings Jan 03 '21 at 13:45
  • I forgot about -LiteralPath parameter and it works with Move-Object part. Thanks for reminding me. – plW0lf Jan 03 '21 at 15:10

1 Answers1

0

Try the following:

Get-ChildItem -LiteralPath .\list -File -Filter '*[1]*' | ForEach-Object { 
  $file = $_.Name
  $path = $_.FullName
  "$file  ==>  $path" # implicit `echo` aka `Write-Output`
  New-Item -Force -Type HardLink `
           -Path (Join-Path .\[1] $file) `
           -Target ([WildcardPattern]::Escape($path)) ` # !! see PowerShell Core comments below
           -WhatIf
}

Note: The -WhatIf common parameter in the command above previews the operation. Remove -WhatIf once you're sure the operation will do what you want.

  • -Filter '*[1]*' prefilters the Get-ChildItem output to only include files whose name contains substring [1] verbatim, because the -Filter parameter uses a filesystem-native wildcard language that does not treat [ and ] as metacharacters.

    • By contrast, with PowerShell's more powerful wildcard patterns, '*[1]*' would match any name that contains just 1, because the [...] is interpreted as a character set or range. With the -like, the wildcard matching operator operator, you'd have to use '*`[1`]*' (escaping of the metacharacters to be interpreted verbatim with `) to find verbatim [1] substrings.
  • -File limits matching items to files, because hardlinks are only supported for files, not also directories.

  • -Path (Join-Path .\[1] $file) use only a -Path argument - rather than a directory-path-only -Path argument combined with a filename-only -Name argument - which ensures that the argument is treated as a literal (verbatim) path, without interpretation of wildcard metacharacters such as [ and ].

    • Regrettably, combining -Path with -Name causes the -Path argument to be interpreted as a wildcard pattern.
  • -Force creates the target directory on demand, if needed, but note that it would also replace any preexisting target file.

  • Windows PowerShell: ([WildcardPattern]::Escape($path)) escapes the
    -Target (aka -Value) argument (target path) in order to treat it verbatim, because it is - unfortunately - interpreted as a wildcard pattern
    . Not performing this escaping prompted the error you saw.

    • Caveat:

      • In PowerShell [Core] 7+, a breaking change was approved in GitHub proposal #13136 to - more sensibly - treat the -Target argument as a literal (verbatim) path, in which case you would simply use -Target $path.

      • However, as of PowerShell 7.2.2 this change isn't implemented yet, and, unfortunately, targeting a path that contains [ and ] is currently broken altogether - see GitHub issue #14534.

        • As of PowerShell 7.2.2 you must escape twice(!) to make it work: ([WildcardPattern]::Escape([WildcardPattern]::Escape($path)))

Note that many, but not all, file-processing cmdlets offer a -LiteralPath parameter to explicitly pass paths to be taken literally (verbatim), whereas the -Path parameter - usually the implied parameter for the first positional argument - is designed to accept wildcard patterns.

Therefore, your could have made your original approach with Move-Item work as follows:

# Ensure that the target dir. exists.
# No escaping needed for -Path when not combined with -Name.
$null = New-Item -Type Directory -Path .\[1] -Force 

# Move the file, targeted with -LiteralPath, there.
# No escaping needed for -Destination.
Move-Item -LiteralPath $path -Destination .\[1]\

Note: Unlike with New-Item, Move-Item's -Force does not create the target directory on demand. On the flip side, Move-Item's -Destination more sensibly interprets its argument literally (verbatim), unlike New-Item's -Target parameter.

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • sadly the hardlink part is not working as it returns pretty much similar error: Could not find item D:\test_move\list\`[1`] file.txt. – plW0lf Jan 03 '21 at 15:06
  • OK, so I tried something - i moved `([WildcardPattern]::Escape($path))` to the `$path` initialization - and it worked. Powershell never stops to amaze me. Marking this answer as accepted. (also I just remembered that we can't create hardlinks of directories... gonna try something else) – plW0lf Jan 03 '21 at 16:25
  • @plW0lf, I'm glad to hear it, but I wouldn't expect that to make a difference. When I try the code as shown (first snippet), it works for me in _Windows PowerShell v5.1_. By contrast, in _PowerShell [Core] 7+_ using escaped paths appears to be broken altogether. – mklement0 Jan 03 '21 at 16:43
  • @plW0lf: Yes, hardlinks can only point to _files_, which is why I used the `-File` switch in the answer. – mklement0 Jan 03 '21 at 16:44
  • @plW0lf: By contrast, `-Type SymbolicLink` supports directories too, but by default you must run _elevated_ (as admin) to create them. – mklement0 Jan 03 '21 at 16:47
  • @plW0lf: Finally, you can go back to your original plan and _move_ the items of interest, as shown in the 2nd code snippet. – mklement0 Jan 03 '21 at 16:51
  • Well, I'm using Powershell 7, so that explains everything – plW0lf Jan 03 '21 at 17:08
  • 1
    @plW0lf: I see. While the `powershell` tag will presumably increasingly be used for PowerShell [Core] 7+, it doesn't hurt to use the `powershell-core` tag as well, or to at least mention the use of 7+ in the question body. Please also see my update re planned changes in behavior ("Caveat") – mklement0 Jan 03 '21 at 17:40