Join-Path allegedly accepts the Path parameter from the pipeline.
This suggests that the two functions below should both work alike:
join-path 'c:\temp' 'x' #returns c:\temp\x
'c:\temp' | join-path 'x' #throws an error
However the second call (i.e. using passing the Path parameter by value to the pipeline) gives the below error:
join-path : The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the
input and its properties do not match any of the parameters that take pipeline input.
At line:1 char:13
+ 'c:\temp' | join-path 'x'
+ ~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (c:\temp:String) [Join-Path], ParameterBindingException
+ FullyQualifiedErrorId : InputObjectNotBound,Microsoft.PowerShell.Commands.JoinPathCommand
NB: Since path may be an array I also tried [array]('c:\temp') | join-path 'x'; but this made no difference.
Have I misunderstood something, or is this a bug in PowerShell?
In your first example, an expression join-path 'c:\temp' 'x' is interpreted by PowerShell as Join-Path -Path 'c:\temp' -ChildPath 'x' (because the names for positional parameters Path and ChildPath are optional).
In your second example, you are passing a pipeline parameter 'c:\temp' to the command Join-Path -Path 'x' (not the absence of ChildPath parameter). It will not work because Join-Path accepts only Path parameter from the pipeline but you have already defined it.
If you want to bind another, not first parameter, you should write it name explicitly, as in
'c:\temp' | Join-Path -ChildPath 'x'
# => 'c:\temp\x'
Related
I modified PowerShell script from PowerShell - Batch change files encoding To UTF-8.
# Modified version of https://stackoverflow.com/q/18684793
[Threading.Thread]::CurrentThread.CurrentUICulture = 'en-US'
$Encoding = New-Object System.Text.UTF8Encoding($True) # If UTF8Encoding($False), It will be UTF-8 without BOM
$source = "C:\Users\AKULA\Desktop\SRC" # source directory
$destination = "C:\Users\AKULA\Desktop\DST" # destination directory
if (!(Test-Path $destination)) {
New-Item -Path $destination -ItemType Directory | Out-Null
}
# Delete all previously generated file
Get-ChildItem -Path $destination -Include * -File -Recurse | ForEach-Object {$_.Delete()}
# Recursively convert all files into UTF-8
foreach ($i in Get-ChildItem $source -Force -Recurse -Exclude "desktop.ini") {
if ($i.PSIsContainer) {
continue
}
$name = $i.Fullname.Replace($source, $destination)
$content = Get-Content $i.Fullname
if ($null -ne $content) {
[System.IO.File]::WriteAllLines($name, $content, $Encoding)
} else {
Write-Host "No content from: $i"
}
}
But after using it, I've found that PS cannot handle [ or ] well.
I made some test files that has diversity in name/content.
Get-Content : An object at the specified path C:\Users\AKULA\Desktop\SRC\FILENAME[[[[[[]]]]]]]].txt does not exist, or
has been filtered by the -Include or -Exclude parameter.
At C:\Users\AKULA\Desktop\Convert_to_UTF-8.ps1:24 char:16
+ $content = Get-Content $i.Fullname
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Since I cannot embed images in question, here is link of IMGUR album.
Full image list: https://imgur.com/a/aN1RG2L
These are what I've tested:
Test files have different names. Their name contains space, ',
[]. Also made up different language(Japanese, Korean).
These files have same content, encoded with UCS-2 BE BOM(UTF-16 BE) so
that I can check if it has re-encoded to UTF-8.
How can I make my script handle [ or ] in file name well?
tl;dr
Indeed, use of the -LiteralPath parameter is the best solution (in PowerShell (Core) v6+, you can shorten to -lp):
$content = Get-Content -LiteralPath $i.Fullname
-LiteralPath ensures that $i.Fullname is taken verbatim (literally); that is, [ and ] in the path are interpreted as themselves rather than having special meaning, as they would have as a -Path argument, due to being interpreted as a wildcard expression - note that -Path is positionally implied if you only pass a value (a string) as the first argument, as you did (Get-Content $i.FullName)
Note: This answer analogously applies to all cmdlets that have both -Path and -LiteralPath parameters, such as Set-Content, Out-File, and Set-Location.
As for what you tried:
$content = Get-Content $i.Fullname
is effectively the same as:
$content = Get-Content -Path $i.Fullname
That is, the (first) positional argument passed to Get-Content is implicitly bound to the
-Path parameter.
The -Path parameter accepts wildcard expressions to allow matching paths by patterns; in addition to support for * (any run of characters) and ? (exactly 1 character), [...] inside a wildcard pattern denotes a character set or range (e.g., [12] or [0-9]).
Therefore an actual path that contains [...], e.g., foo[10].txt, is not recognized as such, because the [10] is interpreted as a character set matching a single character that is either 1 or 0; that is foo[10].txt would match foo0.txt and foo1.txt, but not a file literally named foo[10].txt.
When (implicitly) using -Path, it is possible to escape [ and ] instances that should be interpreted verbatim, namely via the backtick (`), but note that this can get tricky to get right when quoting and/or variable references are involved.
If you know a path to be a literal path, it is best to form a habit of using -LiteralPath (which in PowerShell Core you can shorten to -lp).
However, if your path contains literal [ and ] and you also need wildcard matching, you must use `-escaping - see this answer.
There are at least two situations where the solution's good advice doesn't hold, unfortunately.
Selective error handling
Get-Content -LiteralPath "nobox[]" gives an error message and exception type as if wildcards are involved:
Get-Content : An object at the specified path box[] does not exist, or has been filtered by the -Include or -Exclude parameter.
At line:1 char:1
+ Get-Content -Path "nobox[]"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
whereas without the brackets, we get:
Get-Content : Cannot find path 'nobox' because it does not exist.
At line:1 char:1
+ Get-Content -LiteralPath "nobox"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (nobox:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Therefore, to silently deal with an optional file, something like:
try {
$lines = Get-Content -LiteralPath $path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
$lines = #()
}
chokes on paths with brackets.
Creating a hard or symbolic link
A minor and a major caveat:
The Path parameter, the name of the new item, "works like the LiteralPath parameter of other cmdlets", says the documentation of New-Item clearly, and that seems true and makes sense. Though I wish we could clarify that by writing -LiteralPath.
The Value parameter, the target of the link (also known as Target secretly in v5 and openly later), does not accept wildcard characters according to the same documentation, but that's a lie. The command:
New-Item -ItemType "HardLink" -Path "whatever" -Target "*"
makes Powershell squeal "Cannot set the location because path '*' resolved to multiple containers.".
So you always need the escapes for the target. If you have a file named "f[]", then this will display an error:
New-Item -ItemType "HardLink" -Path "whatever" -Target "f[]"
and this will create a link:
New-Item -ItemType "HardLink" -Path "f[2]" -Target ([WildcardPattern]::Escape("f[]"))
Same for ItemType "SymbolicLink".
I've ran into a problem in my powershell code:
I would like to take $PSScriptRoot.ToString(); and then add it \childfoldername so it should look like this
$PSScriptRoot = C:\New Folder\childfoldername
assuming that is $PSScriptRoot = C:\New Folder.
Tried $PSScriptRoot.ToString() + "\" + foldername but with no luck, still getting an error:
A positional parameter cannot be found that accepts argument '+'.
You can use the Join-Path function.
$newPath = Join-Path -Path $PSScriptRoot -ChildPath "childpath"
A positional parameter cannot be found that accepts argument '+'.
This error message implies that your problem isn't your expression per se ($PSScriptRoot.ToString() + "\" + foldername) but your attempt to pass it as an argument to a command, without enclosing it in (...).
# !! WRONG - attempt to pass an expression as an argument as-is.
Get-Content $PSScriptRoot.ToString() + "\" + 'foldername'
# OK - enclosing the expression in (...) works as intended.
Get-Content ($PSScriptRoot.ToString() + "\" + 'foldername')
Aside from that, as noted:
You don't need to call .ToString() on the automatic $PSScriptRoot variable, because it already is a string.
The preferable way to synthesize a path is to use the Join-Path cmdlet:
Get-Content (Join-Path $PSScriptRoot 'foldername')
I modified PowerShell script from PowerShell - Batch change files encoding To UTF-8.
# Modified version of https://stackoverflow.com/q/18684793
[Threading.Thread]::CurrentThread.CurrentUICulture = 'en-US'
$Encoding = New-Object System.Text.UTF8Encoding($True) # If UTF8Encoding($False), It will be UTF-8 without BOM
$source = "C:\Users\AKULA\Desktop\SRC" # source directory
$destination = "C:\Users\AKULA\Desktop\DST" # destination directory
if (!(Test-Path $destination)) {
New-Item -Path $destination -ItemType Directory | Out-Null
}
# Delete all previously generated file
Get-ChildItem -Path $destination -Include * -File -Recurse | ForEach-Object {$_.Delete()}
# Recursively convert all files into UTF-8
foreach ($i in Get-ChildItem $source -Force -Recurse -Exclude "desktop.ini") {
if ($i.PSIsContainer) {
continue
}
$name = $i.Fullname.Replace($source, $destination)
$content = Get-Content $i.Fullname
if ($null -ne $content) {
[System.IO.File]::WriteAllLines($name, $content, $Encoding)
} else {
Write-Host "No content from: $i"
}
}
But after using it, I've found that PS cannot handle [ or ] well.
I made some test files that has diversity in name/content.
Get-Content : An object at the specified path C:\Users\AKULA\Desktop\SRC\FILENAME[[[[[[]]]]]]]].txt does not exist, or
has been filtered by the -Include or -Exclude parameter.
At C:\Users\AKULA\Desktop\Convert_to_UTF-8.ps1:24 char:16
+ $content = Get-Content $i.Fullname
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Since I cannot embed images in question, here is link of IMGUR album.
Full image list: https://imgur.com/a/aN1RG2L
These are what I've tested:
Test files have different names. Their name contains space, ',
[]. Also made up different language(Japanese, Korean).
These files have same content, encoded with UCS-2 BE BOM(UTF-16 BE) so
that I can check if it has re-encoded to UTF-8.
How can I make my script handle [ or ] in file name well?
tl;dr
Indeed, use of the -LiteralPath parameter is the best solution (in PowerShell (Core) v6+, you can shorten to -lp):
$content = Get-Content -LiteralPath $i.Fullname
-LiteralPath ensures that $i.Fullname is taken verbatim (literally); that is, [ and ] in the path are interpreted as themselves rather than having special meaning, as they would have as a -Path argument, due to being interpreted as a wildcard expression - note that -Path is positionally implied if you only pass a value (a string) as the first argument, as you did (Get-Content $i.FullName)
Note: This answer analogously applies to all cmdlets that have both -Path and -LiteralPath parameters, such as Set-Content, Out-File, and Set-Location.
As for what you tried:
$content = Get-Content $i.Fullname
is effectively the same as:
$content = Get-Content -Path $i.Fullname
That is, the (first) positional argument passed to Get-Content is implicitly bound to the
-Path parameter.
The -Path parameter accepts wildcard expressions to allow matching paths by patterns; in addition to support for * (any run of characters) and ? (exactly 1 character), [...] inside a wildcard pattern denotes a character set or range (e.g., [12] or [0-9]).
Therefore an actual path that contains [...], e.g., foo[10].txt, is not recognized as such, because the [10] is interpreted as a character set matching a single character that is either 1 or 0; that is foo[10].txt would match foo0.txt and foo1.txt, but not a file literally named foo[10].txt.
When (implicitly) using -Path, it is possible to escape [ and ] instances that should be interpreted verbatim, namely via the backtick (`), but note that this can get tricky to get right when quoting and/or variable references are involved.
If you know a path to be a literal path, it is best to form a habit of using -LiteralPath (which in PowerShell Core you can shorten to -lp).
However, if your path contains literal [ and ] and you also need wildcard matching, you must use `-escaping - see this answer.
There are at least two situations where the solution's good advice doesn't hold, unfortunately.
Selective error handling
Get-Content -LiteralPath "nobox[]" gives an error message and exception type as if wildcards are involved:
Get-Content : An object at the specified path box[] does not exist, or has been filtered by the -Include or -Exclude parameter.
At line:1 char:1
+ Get-Content -Path "nobox[]"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
whereas without the brackets, we get:
Get-Content : Cannot find path 'nobox' because it does not exist.
At line:1 char:1
+ Get-Content -LiteralPath "nobox"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (nobox:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Therefore, to silently deal with an optional file, something like:
try {
$lines = Get-Content -LiteralPath $path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
$lines = #()
}
chokes on paths with brackets.
Creating a hard or symbolic link
A minor and a major caveat:
The Path parameter, the name of the new item, "works like the LiteralPath parameter of other cmdlets", says the documentation of New-Item clearly, and that seems true and makes sense. Though I wish we could clarify that by writing -LiteralPath.
The Value parameter, the target of the link (also known as Target secretly in v5 and openly later), does not accept wildcard characters according to the same documentation, but that's a lie. The command:
New-Item -ItemType "HardLink" -Path "whatever" -Target "*"
makes Powershell squeal "Cannot set the location because path '*' resolved to multiple containers.".
So you always need the escapes for the target. If you have a file named "f[]", then this will display an error:
New-Item -ItemType "HardLink" -Path "whatever" -Target "f[]"
and this will create a link:
New-Item -ItemType "HardLink" -Path "f[2]" -Target ([WildcardPattern]::Escape("f[]"))
Same for ItemType "SymbolicLink".
I modified PowerShell script from PowerShell - Batch change files encoding To UTF-8.
# Modified version of https://stackoverflow.com/q/18684793
[Threading.Thread]::CurrentThread.CurrentUICulture = 'en-US'
$Encoding = New-Object System.Text.UTF8Encoding($True) # If UTF8Encoding($False), It will be UTF-8 without BOM
$source = "C:\Users\AKULA\Desktop\SRC" # source directory
$destination = "C:\Users\AKULA\Desktop\DST" # destination directory
if (!(Test-Path $destination)) {
New-Item -Path $destination -ItemType Directory | Out-Null
}
# Delete all previously generated file
Get-ChildItem -Path $destination -Include * -File -Recurse | ForEach-Object {$_.Delete()}
# Recursively convert all files into UTF-8
foreach ($i in Get-ChildItem $source -Force -Recurse -Exclude "desktop.ini") {
if ($i.PSIsContainer) {
continue
}
$name = $i.Fullname.Replace($source, $destination)
$content = Get-Content $i.Fullname
if ($null -ne $content) {
[System.IO.File]::WriteAllLines($name, $content, $Encoding)
} else {
Write-Host "No content from: $i"
}
}
But after using it, I've found that PS cannot handle [ or ] well.
I made some test files that has diversity in name/content.
Get-Content : An object at the specified path C:\Users\AKULA\Desktop\SRC\FILENAME[[[[[[]]]]]]]].txt does not exist, or
has been filtered by the -Include or -Exclude parameter.
At C:\Users\AKULA\Desktop\Convert_to_UTF-8.ps1:24 char:16
+ $content = Get-Content $i.Fullname
+ ~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Since I cannot embed images in question, here is link of IMGUR album.
Full image list: https://imgur.com/a/aN1RG2L
These are what I've tested:
Test files have different names. Their name contains space, ',
[]. Also made up different language(Japanese, Korean).
These files have same content, encoded with UCS-2 BE BOM(UTF-16 BE) so
that I can check if it has re-encoded to UTF-8.
How can I make my script handle [ or ] in file name well?
tl;dr
Indeed, use of the -LiteralPath parameter is the best solution (in PowerShell (Core) v6+, you can shorten to -lp):
$content = Get-Content -LiteralPath $i.Fullname
-LiteralPath ensures that $i.Fullname is taken verbatim (literally); that is, [ and ] in the path are interpreted as themselves rather than having special meaning, as they would have as a -Path argument, due to being interpreted as a wildcard expression - note that -Path is positionally implied if you only pass a value (a string) as the first argument, as you did (Get-Content $i.FullName)
Note: This answer analogously applies to all cmdlets that have both -Path and -LiteralPath parameters, such as Set-Content, Out-File, and Set-Location.
As for what you tried:
$content = Get-Content $i.Fullname
is effectively the same as:
$content = Get-Content -Path $i.Fullname
That is, the (first) positional argument passed to Get-Content is implicitly bound to the
-Path parameter.
The -Path parameter accepts wildcard expressions to allow matching paths by patterns; in addition to support for * (any run of characters) and ? (exactly 1 character), [...] inside a wildcard pattern denotes a character set or range (e.g., [12] or [0-9]).
Therefore an actual path that contains [...], e.g., foo[10].txt, is not recognized as such, because the [10] is interpreted as a character set matching a single character that is either 1 or 0; that is foo[10].txt would match foo0.txt and foo1.txt, but not a file literally named foo[10].txt.
When (implicitly) using -Path, it is possible to escape [ and ] instances that should be interpreted verbatim, namely via the backtick (`), but note that this can get tricky to get right when quoting and/or variable references are involved.
If you know a path to be a literal path, it is best to form a habit of using -LiteralPath (which in PowerShell Core you can shorten to -lp).
However, if your path contains literal [ and ] and you also need wildcard matching, you must use `-escaping - see this answer.
There are at least two situations where the solution's good advice doesn't hold, unfortunately.
Selective error handling
Get-Content -LiteralPath "nobox[]" gives an error message and exception type as if wildcards are involved:
Get-Content : An object at the specified path box[] does not exist, or has been filtered by the -Include or -Exclude parameter.
At line:1 char:1
+ Get-Content -Path "nobox[]"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (System.String[]:String[]) [Get-Content], Exception
+ FullyQualifiedErrorId : ItemNotFound,Microsoft.PowerShell.Commands.GetContentCommand
whereas without the brackets, we get:
Get-Content : Cannot find path 'nobox' because it does not exist.
At line:1 char:1
+ Get-Content -LiteralPath "nobox"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : ObjectNotFound: (nobox:String) [Get-Content], ItemNotFoundException
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
Therefore, to silently deal with an optional file, something like:
try {
$lines = Get-Content -LiteralPath $path -ErrorAction Stop
}
catch [System.Management.Automation.ItemNotFoundException] {
$lines = #()
}
chokes on paths with brackets.
Creating a hard or symbolic link
A minor and a major caveat:
The Path parameter, the name of the new item, "works like the LiteralPath parameter of other cmdlets", says the documentation of New-Item clearly, and that seems true and makes sense. Though I wish we could clarify that by writing -LiteralPath.
The Value parameter, the target of the link (also known as Target secretly in v5 and openly later), does not accept wildcard characters according to the same documentation, but that's a lie. The command:
New-Item -ItemType "HardLink" -Path "whatever" -Target "*"
makes Powershell squeal "Cannot set the location because path '*' resolved to multiple containers.".
So you always need the escapes for the target. If you have a file named "f[]", then this will display an error:
New-Item -ItemType "HardLink" -Path "whatever" -Target "f[]"
and this will create a link:
New-Item -ItemType "HardLink" -Path "f[2]" -Target ([WildcardPattern]::Escape("f[]"))
Same for ItemType "SymbolicLink".
I am trying to get the meta data from a directory and I am getting an error that A positional parameter cannot be found that accepts argument '\'. Not sure how to correct this?
$FileMetadata = Get-FileMetaData -folder (Get-childitem $Folder1 + "\" + $System.Name + "\Test" -Recurse -Directory).FullName
You need to do the concatenation in a subexpression:
$FileMetadata = Get-FileMetaData -folder (Get-childitem ($Folder1 + "\" + $System.Name + "\Test") -Recurse -Directory).FullName
or embed the variables in a string like this:
$FileMetadata = Get-FileMetaData -folder (Get-childitem "$Folder1\$($System.Name)\Test" -Recurse -Directory).FullName
The most robust way in Powershell to build a path when parts of the path are stored in variables is to use the cmdlet Join-Path.
This also eliminate the need to use "\".
So in your case, it would be :
$FoldersPath = Join-Path -Path $Folder1 -ChildPath "$System.Name\Test"
$FileMetadata = Get-FileMetaData -folder (Get-ChildItem $FoldersPath -Recurse -Directory).FullName
If you come from the world of VBScript. With Powershell, every space is interpreted as completely separate parameter being passed to the cmdlet. You need to either place the formula in parentheses to have the formula evaluated prior to passing it as the path parameter or enclosing with quotes also works:
Wont work, Powershell thinks this is two parameters:
$Folder1 + "\" + $System.Name
Will work with brackets:
($Folder1 + "\" + $System.Name)
Will also work together when enclosed in quotes:
"$Folder1\$System.Name"
Ref.
I was trying to run the sc create <name> binPath=... command and kept getting the positional character issue. I resolved this by running the command in Command Prompt not PowerShell. Ugh. 😐