You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
echo hello world | powershell -f test.ps1 e E
hEllo world
echo hello world | powershell -f test.ps1 e -
CRA$H@#*
echo hello world | powershell -f test.ps1 e "-"
CRA$H@#*
CRA$H@#* = PowerShell crashes
Expected behavior
h-llo world
Actual behavior
CRASH of pwsh.exe !
Environment data
Name Value
---- -----
PSVersion 6.2.0
PSEdition Core
GitCommitId 6.2.0
OS Microsoft Windows 10.0.16299
Platform Win32NT
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
WSManStackVersion 3.0
any.ps1 : Cannot process argument because the value of argument "name" is not valid. Change the value of the "name" argument and run the operation again.
+ CategoryInfo : InvalidArgument: (:) [any.ps1], PSArgumentException
+ FullyQualifiedErrorId : Argument,any.ps1
I debug the code and guess that it is "by design". In "pwsh any.ps1 -" we interpret the dash as parameter name prefix and get null as parameter name. If we run "> .\any.ps1 -" we interpret the dash as argument value.
It happens only with (implied) -File, not with -Command.
With -Command, it behaves as expected: the - is interpreted as a positional argument rather than the start of an (invalid) parameter name.
While there are - regrettable[1] - by-design differences between argument parsing with -File vs. -Command (literals vs. as-code), they should behave the same in this case (and, obviously, there shouldn't be a crash).
[1] In short: for conceptual clarity and predictable parameter passing (in line with POSIX-like shells), only ever the first argument following -Command should be interpreted as a snippet of PowerShell code, whereas all remaining arguments should be treated as literal arguments to pass as arguments to that first argument, as with -File.
I think Michael Klement (@mklement0) is correct here. There is a logic error in the code. It should verify the immediate parameter before was -file or -command
Unfortunately, Marc (@cawoodm)'s code snippet points to a larger (separate) problem:
If you look closely, the process block doesn't actually use the declared parameter variable, $InputObject, but the automatic $Input variable.
Sadly, this is actually necessary in order for the script to receive outside stdin input directly, and while you can apply this hack in your own scripts, it is not an option with standard cmdlets.
In short: outside stdin input doesn't automatically translate to pipeline input - explicit use of $Input is required - see #9497
Michael Klement (@mklement0) My code came about hacking around trying to find a variable which consistently represented the piped stream of input (stdin). What I came up with was that in a begin{} block it's $InputObject, in an end{} block it's $input and in the process{} block it's $_ which seems crazy but it's what I found.
I'd be eternally grateful of a resource describing when $_, $InputObject and $Input are to be used.
Ideally, all you should use in your advanced script is $InputObject (whose freely chosen parameter-variable name is a convention) - from within PowerShell, this will work as expected.
The lack of support for automatic mapping of external stdin input to PowerShell pipeline input detailed in #9497 prompted your use of $Input as a workaround.
A workaround that wouldn't require modification of your script is:
echo hello world | powershell -command '$Input | ./test.ps1 e E'
That is the primary use of $Input, I'd say: enumerating the lines of external stdin input; see below for a secondary use.
Generally:
If you have explicitly declared pipeline-binding parameters, use only them - no need for $_ or $Input.
Your pipeline-binding parameters, say $InputObject, has no meaningful value in begin - unless you've passed a value as an argument instead, though even then you should perform processing in the process block.
In the process block, $InputObject represents the input object at hand (or a property value of it, if the parameter is declared with ValueFromPipelineByPropertyName).
Because external stdin input isn't mapped to pipeline input, $InputObject is not bound in your scenario (if you just used $InputObject, the process block would be entered once, with $InputObject containing the type-specific default value)
That using $Input actually works in this case is surprising, and I wouldn't rely on it; even more obscurely, you could alternatively place $null = $input in your begin(!) block, and then use $InputObject in your process block, as you normally would - I have no idea why that works.
In the end block, $InputObject still contains the last input object.
If not:
use $_ in the process block ($Input works too, but is wrapped in a single-element ArrayList in advanced scripts / functions).
if you don't have a process block and you just want to collect all objects first and process them in the (possibly implied) end block, use $Input there. (If you do have a process block, $Input will be empty in the end block.)
It would appear, however, that $Input is not available in an end{} block if a process{} block is present. So I see no way of making a function/script which can work in both modes - like being able to filter objects in the stream (via process{}) OR, in a different mode, summarize/sort the entire stream (via end{}).
Consider the following function/script which should convert an object or stream of objects to JSON (producing "{object}, {object2}..." output) and, optionally with the -AsArray switch pass the entire stream through in one go (producing "[{object},{object2}...]" output):
[CmdLetBinding()]
[OutputType([String])]
param(
[Parameter(Mandatory,ValueFromPipeline)]
[object]$InputObject,
[switch]$Compress,
[switch]$AsArray
)
process {
if (-not$AsArray) {
return$InputObject|ConvertTo-Json-Compress:$Compress
}
}
end {
if ($AsArray) {
Write-Host$Input# Nothing, unless we remove the process block aboveif ($Input-is [array] -and$Input.length-gt1) {
# We have an array, pass to convertto-json as isreturn$Input|ConvertTo-Json-Compress:$Compress
} else {
# Coerce single object to an arrayreturnConvertTo-Json-InputObject @($Input) -Compress:$Compress
}
}
}
AFAIK this can only be solved with 2 different scripts/functions.
$Input is not available in an end{} block if a process{} block is present
Correct (it's what I tried to say with "if you don't have a process block ...").
If you do have a process block, you must perform collecting all input manually, which is best done by instantiating a list type such as [System.Collections.Generic.List[object]] in begin and appending to it with .Add() in the process block.
While I can't speak directly to the design intent, it makes sense to me to consume the input in a process block, as it is being received, so as to enable streaming processing that doesn't require collecting all input in memory. Collecting therefore only happens in the absence of a process block.
As an aside: $Input is always an array; what you need to guard against is that array only having 1 element, and, if so, use , $Input, not @($Input).
Activity
cawoodm commentedon Apr 15, 2019
NB:
-which crashes thingsiSazonov commentedon Apr 23, 2019
Marc (@cawoodm) Please share the content of test.ps1 file.
cawoodm commentedon Apr 24, 2019
I've updated the original question with the code.
Just double-checked that it crashes PowerShell 6/Core (pwsh.exe) - it does:
iSazonov commentedon Apr 25, 2019
Simple repo from cmd and pwsh:
iSazonov commentedon Apr 25, 2019
I debug the code and guess that it is "by design". In "pwsh any.ps1 -" we interpret the dash as parameter name prefix and get null as parameter name. If we run "> .\any.ps1 -" we interpret the dash as argument value.
I don't know can/should we fix this or no.
/cc Steve Lee (@SteveL-MSFT) Dongbo Wang (@daxian-dbw) Michael Klement (@mklement0)
mklement0 commentedon Apr 25, 2019
It happens only with (implied)
-File, not with-Command.With
-Command, it behaves as expected: the-is interpreted as a positional argument rather than the start of an (invalid) parameter name.While there are - regrettable[1] - by-design differences between argument parsing with
-Filevs.-Command(literals vs. as-code), they should behave the same in this case (and, obviously, there shouldn't be a crash).[1] In short: for conceptual clarity and predictable parameter passing (in line with POSIX-like shells), only ever the first argument following
-Commandshould be interpreted as a snippet of PowerShell code, whereas all remaining arguments should be treated as literal arguments to pass as arguments to that first argument, as with-File.iSazonov commentedon Apr 25, 2019
It seems the problem come from #4019 or #7449 (Travis Plunk (@TravisEz13) could you please look this too?)
PowerShell/src/Microsoft.PowerShell.ConsoleHost/host/msh/CommandLineParameterParser.cs
Lines 1133 to 1141 in ceed73d
Debug shows "i" has value 0 but I think it must be 1.
Michael Klement (@mklement0) Look comment in the code.
mklement0 commentedon Apr 25, 2019
Ilya (@iSazonov): looking at it purely from a behavioral perspective:
-is only special as the first - and then only supported - argument passed to-File- which is not the case here.Since
-f(-File) is used explicitly, #4019 shouldn't come into play - don't know anything about #7449.As an aside: both
-File -and-Command -are currently badly broken (the// Process interactive input...comment provides a hint) - see #3223TravisEz13 commentedon Apr 25, 2019
Based on this comment #9362 (comment), this was not introduced in either of these PRs
iSazonov commentedon Apr 26, 2019
Travis Plunk (@TravisEz13) I checked that Windows PowerShell 5.1 (10.0.17763.437) is not crashed.
TravisEz13 commentedon Apr 26, 2019
I think Michael Klement (@mklement0) is correct here. There is a logic error in the code. It should verify the immediate parameter before was
-fileor-commandSteveL-MSFT commentedon Apr 26, 2019
I have a fix
iSazonov commentedon Apr 28, 2019
Reopen because of #9479 (comment)
TravisEz13 commentedon Apr 28, 2019
Responded to the comment.
Also, That would be a new issue. Please open a new issue for new issues. It is VERY confusing to use the same issue for multiple issues.
mklement0 commentedon Apr 29, 2019
Unfortunately, Marc (@cawoodm)'s code snippet points to a larger (separate) problem:
If you look closely, the
processblock doesn't actually use the declared parameter variable,$InputObject, but the automatic$Inputvariable.Sadly, this is actually necessary in order for the script to receive outside stdin input directly, and while you can apply this hack in your own scripts, it is not an option with standard cmdlets.
In short: outside stdin input doesn't automatically translate to pipeline input - explicit use of
$Inputis required - see #9497cawoodm commentedon Apr 29, 2019
Michael Klement (@mklement0) My code came about hacking around trying to find a variable which consistently represented the piped stream of input (stdin). What I came up with was that in a
begin{}block it's$InputObject, in anend{}block it's$inputand in theprocess{}block it's$_which seems crazy but it's what I found.I'd be eternally grateful of a resource describing when
$_,$InputObjectand$Inputare to be used.mklement0 commentedon Apr 30, 2019
Marc (@cawoodm) - here's my $0.02:
Ideally, all you should use in your advanced script is
$InputObject(whose freely chosen parameter-variable name is a convention) - from within PowerShell, this will work as expected.The lack of support for automatic mapping of external stdin input to PowerShell pipeline input detailed in #9497 prompted your use of
$Inputas a workaround.A workaround that wouldn't require modification of your script is:
That is the primary use of
$Input, I'd say: enumerating the lines of external stdin input; see below for a secondary use.Generally:
If you have explicitly declared pipeline-binding parameters, use only them - no need for
$_or$Input.$InputObject, has no meaningful value inbegin- unless you've passed a value as an argument instead, though even then you should perform processing in theprocessblock.processblock,$InputObjectrepresents the input object at hand (or a property value of it, if the parameter is declared withValueFromPipelineByPropertyName).$InputObjectis not bound in your scenario (if you just used$InputObject, theprocessblock would be entered once, with$InputObjectcontaining the type-specific default value)$Inputactually works in this case is surprising, and I wouldn't rely on it; even more obscurely, you could alternatively place$null = $inputin yourbegin(!) block, and then use$InputObjectin yourprocessblock, as you normally would - I have no idea why that works.endblock,$InputObjectstill contains the last input object.If not:
$_in theprocessblock ($Inputworks too, but is wrapped in a single-elementArrayListin advanced scripts / functions).processblock and you just want to collect all objects first and process them in the (possibly implied)endblock, use$Inputthere. (If you do have aprocessblock,$Inputwill be empty in theendblock.)cawoodm commentedon May 3, 2019
It would appear, however, that
$Inputis not available in anend{}block if aprocess{}block is present. So I see no way of making a function/script which can work in both modes - like being able to filter objects in the stream (via process{}) OR, in a different mode, summarize/sort the entire stream (via end{}).Consider the following function/script which should convert an object or stream of objects to JSON (producing
"{object}, {object2}..."output) and, optionally with the-AsArrayswitch pass the entire stream through in one go (producing"[{object},{object2}...]"output):AFAIK this can only be solved with 2 different scripts/functions.
mklement0 commentedon May 3, 2019
Correct (it's what I tried to say with "if you don't have a
processblock ...").If you do have a
processblock, you must perform collecting all input manually, which is best done by instantiating a list type such as[System.Collections.Generic.List[object]]inbeginand appending to it with.Add()in theprocessblock.While I can't speak directly to the design intent, it makes sense to me to consume the input in a
processblock, as it is being received, so as to enable streaming processing that doesn't require collecting all input in memory. Collecting therefore only happens in the absence of aprocessblock.As an aside:
$Inputis always an array; what you need to guard against is that array only having 1 element, and, if so, use, $Input, not@($Input).