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.