5

Consider the following code. I am simply passing in an array of 32-bit, signed integers [Int32[]] into the Start-Job cmdlet, by using the -InputObject parameter.

$Job = Start-Job -ScriptBlock { $input.GetType().FullName; } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

The result of this code is:

System.Management.Automation.Runspaces.PipelineReader`1+<GetReadEnumerator>d__0[[System.Object, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089]]

Looking at the documentation for the PipelineReader .NET class, it has a ReadToEnd() method. Therefore, the following code ought to work:

$Job = Start-Job -ScriptBlock { $input.ReadToEnd(); } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

But instead, I get an error message:

Method invocation failed because [System.Int32] does not contain a method named 'ReadToEnd'. + CategoryInfo : InvalidOperation: (:) [], RuntimeException + FullyQualifiedErrorId : MethodNotFound + PSComputerName : localhost

So then I think, I'll just use the PSBase property to get the "real" object.

$Job = Start-Job -ScriptBlock { $input.psbase.ReadToEnd(); } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

But then I get a similar error message:

Method invocation failed because [System.Management.Automation.PSInternalMemberSet] does not contain a method named 'ReadToEnd'. + CategoryInfo : InvalidOperation: (ReadToEnd:String) [], RuntimeException + FullyQualifiedErrorId : MethodNotFound + PSComputerName : localhost

I noticed that there is a Microsoft Connect bug filed around this confusion, but it confuses me even more. Apparently the PipelineReader class has a confusingly-named property <>4__this, which has a Read() method, which you can't actually see by using Get-Member.

Bottom line: Does anyone know how to simply "unwrap" the contents of the $input automatic variable, when input is submitted via the -InputObject parameter on the Start-Job cmdlet, so that I can work with the objects on an individual basis?

This script should simply return 1, not 1, 2, 3.

$Job = Start-Job -ScriptBlock { $input[0]; } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;
4

3 回答 3

5

大概$input是一个枚举器,就像在标准管道中一样。为了处理项目,我们应该使用process带有自动变量的块$_或在块中传递$input另一个管道end (如果未指定,则为隐式)。

# process each item separately
$Job = Start-Job -ScriptBlock {process{$_}} -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

# process the whole $input
$Job = Start-Job -ScriptBlock {$input | %{$_}} -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;

# compare with script blocks in standard pipelines

# each item
@(1,2,3) | . {process{$_}}

# whole input
@(1,2,3) | . {$input | %{$_}}

$input据我所知,也许还有其他枚举项目的方法,但它们在实践中并不经常使用。

于 2014-01-13T20:33:52.030 回答
2
$Input
   Contains an enumerator that enumerates all input that is passed to a
   function. The $input variable is available only to functions and script
   blocks (which are unnamed functions).  In the Process block of a
   function, the $input variable enumerates the object that is currently
   in the pipeline. When the Process block  completes, there are no objects
   left in the pipeline, so the $input variable enumerates an empty
   collection. If the function does not have a Process block, then in the
   End block, the $input variable enumerates the collection of all input to
   the function.

来源:about_Automatic_Variables

总结一下:$input是一个自动变量,它以枚举器的形式包含整个管道,不像管道中$_的“当前对象”。

这是一个如何使用它的示例。

function test {
    #$input is an enumerator that you should use $input | foreach-object { } to access the objects.

    #To to get all items you could e.g. convert the enumerator to an array.
    $arr = @($input)

    #If you need to use the $input enumerator for something else, you need to call .Reset() first as the enumerator has reached the end.
    $input.Reset()

    #Print some values from the data
    $arr.count
    $arr[0]
}

PS> "hello", "world" | test


2
hello

更新:这是这也适用于您的Start-Job场景的证据。样品被评论解释。

$Job = Start-Job -ScriptBlock { 
    #Read the complete pipeline to an array
    $data= @($input)
    "`$data is an $($data.GetType().Name) with $($data.count) objects"

    #Unlike in a pipeline, where the `Start-Job` command would be called once per object like `Start-Job ...... -InputObject $_`, 
    #you're inputing a single `object[]` object the the pipeline. So you only have one item in your $input pipeline.

    #Get our inputobject (our array)
    $arr= $data[0]
    "`$arr is an $($arr.GetType().Name)"

    #Use array
    "$($arr[0]) is less than $($arr[1]) which is less than $($arr[2])"
} -InputObject @(1,2,3);

Wait-Job -Job $Job;
Receive-Job -Keep $Job;

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
54     Job54           BackgroundJob   Completed     True            localhost             ...                     
$data is an Object[] with 1 objects
$arr is an ArrayList
1 is less then 2 which is less then 3

并证明我关于管道的“理论”。这是流水线版本:

$Job = 1, 2, 3 | Start-Job -ScriptBlock { 
    #Read the complete pipeline to an array
    $data= @($input)
    "`$data is an $($data.GetType().Name) with $($data.count) objects"

    #Now the command is run per object, so the $input enumerator contained our 3 seperate Int32 values.

    #Get a single object in the pipeline
    $OneOfTheValues= $data[0]
    "`$OneOfTheValues is an $($OneOfTheValues.GetType().Name)"

    #Use data
    "$($data[0]) is less than $($data[1]) which is less than $($data[2])"
}

Wait-Job -Job $Job;
Receive-Job -Keep $Job;

Id     Name            PSJobTypeName   State         HasMoreData     Location             Command                  
--     ----            -------------   -----         -----------     --------             -------                  
58     Job58           BackgroundJob   Completed     True            localhost             ...                     
$data is an Object[] with 3 objects
$OneOfTheValues is an Int32
1 is less then 2 which is less then 3

所以我坚持我原来的答案。行为是相同的,您只是以不同的方式使用管道/cmdlet。:)

于 2014-01-13T20:23:16.137 回答
2

这似乎有效:

$Job = Start-Job -ScriptBlock { $input.movenext();$input.current[0] } -InputObject @(1,2,3);
Wait-Job -Job $Job;
Receive-Job -Keep $Job;
于 2014-01-14T14:43:03.023 回答