alex
alex

Reputation: 7443

How do I reference the output of the previous "pipe" in a Powershell pipeline?

I wrote this code to get some (relative) file path:

function Get-ExecutingScriptDirectory() {
    return Split-Path $script:MyInvocation.MyCommand.Path  # returns this script's directory
}

$some_file_path = Get-ExecutingScriptDirectory | Join-Path -Path $_ -ChildPath "foo.json"

This threw the error:

Join-Path : Cannot bind argument to parameter 'Path' because it is null.
+ $some_file_path  = Get-ExecutingScriptDirectory | Join-Path -Path $_ -ChildPath "fo ...
+                                                     ~~
    + CategoryInfo          : InvalidData: (:) [Join-Path], ParameterBindingValidationException
    + FullyQualifiedErrorId : ParameterArgumentValidationErrorNullNotAllowed,Microsoft.PowerShell.Commands.JoinPathCommand

This indicates to me that the output of Get-ExecutingScriptDirectory is null - but it isn't - when I write the script out like this, it's fine:

$this_directory = Get-ExecutingScriptDirectory
$some_file_path = Join-Path -Path $this_directory -ChildPath "foo.json"

So the problem is that $_ is null. I would expect $_ to be reference to the previous pipe's standard output. The MSDN documentation also suggests this, but then it seems to immediately contradict itself:

$_ contains the current object in the pipeline object. You can use this variable in commands that perform an action on every object or on selected objects in a pipeline.

In the context of my code, $_ seems to qualify as the "current object in the pipeline object" - but I am not using this with a command that performs an action on every object or on selected objects in a pipeline.

$$ and $^ looked promising, but the MSDN documentation just says a few vague things here about lexical tokens. The documentation on $PSItem was equally terse.

What I would actually like to do is create a big pipeline:

$some_file_path = Get-ExecutingScriptDirectory | Join-Path -Path {{PREVIOUS STDOUT}} -ChildPath "foo.json" | Get-Content {{PREVIOUS STDOUT}} | Convert-FromJson {{PREVIOUS STDOUT}} | {{PREVIOUS STDOUT}}.data

I'd like to know where I'm going wrong both on a conceptual and a technical level.

Upvotes: 3

Views: 11560

Answers (3)

shadow2020
shadow2020

Reputation: 1351

Just use a code block.

function Get-ExecutingScriptDirectory() {
    return Split-Path $script:MyInvocation.MyCommand.Path  # returns this script's directory
}

$some_file_path = Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json"
$some_file_path 

read-host

The reason it doesn't work in this code @ get-content is because it's evaluating to false there.

Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json" | Get-Content {$_} and Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json" | Get-Content $_

An object can only be passed on using $_ or $psItem if it evaluates to $True.

Here is why it works in the first example.

function Get-ExecutingScriptDirectory() {
    return Split-Path $script:MyInvocation.MyCommand.Path  # returns this script's directory
}

if(Get-ExecutingScriptDirectory -eq $True){
    write-host This is true
}

Output: This is true

You can use Parethesis to isolate your objects in the pipeline also.

example:

Get-Content (Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json")

"|" deals with the object to the left of it, so doing get-content | get-content | doesn't make sense to the script. You will need to separate it into multiple commands with a semi-colon if you're doing something like that. Or use a "Foreach" cmdlet.

gc (Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "foo.json");gc (Get-ExecutingScriptDirectory | Join-Path -Path {$_} -ChildPath "bar.json")

Upvotes: 1

js2010
js2010

Reputation: 27453

Here's a simple example. Search for "accept pipeline input" in the docs. Using a scriptblock with get-content's -path parameter is a little advanced. Most people use a foreach-object instead. It works because -path accepts pipeline input, but only by property name. join-path's -path paramaeter can be over the pipe by value, so it's easier. This will come in handy a lot once you understand it. Maybe it's easier just to try things sometimes.

echo '"hi"' > foo.json

'.\' | Join-Path -ChildPath foo.json | Get-Content -Path { $_ } | ConvertFrom-Json

hi

Or with foreach, which is short for foreach-object in this case. But $_ must always be inside the curly braces.

'.\' | Join-Path -ChildPath foo.json | foreach { Get-Content $_ } | ConvertFrom-Json

Upvotes: 2

AdminOfThings
AdminOfThings

Reputation: 25001

You can do what you want by executing the command below:

(Get-Content -Path (Get-ExecutingScriptDirectory | Join-Path -ChildPath "foo.json" | ConvertFrom-Json)).data

Some commands support pipeline parameter binding. The options are pipeline by value or pipeline by property. A good reference for parameter binding is About Functions Advanced Parameters.

Searching online for the command you are going to use will yield parameter binding information. For example Join-Path, has a parameters section. Each parameter will have a description including the field Accept pipeline input:. For a parameter to accept pipeline input, this must be True. Usually, it will say how the value can be passed into the pipeline (ByPropertyName or ByValue).

ByPropertyName indicates that you must output an object that contains a property name that matches the parameter name. Then once the object is piped, the parameter will bind to the matching property name's value. See below for an example:

$filePath = [pscustomobject]@{Path = 'c:\temp\test1\t.txt'}
$filePath

Path
----
c:\temp\test1\t.txt

$filePath.Path # This binds to -Path in Get-Content
c:\temp\test1\t.txt
$filepath | Get-Content 

ByValue indicates that any value piped in will attempt to bind to that parameter. If there are type discrepancies at the binding time, an error will likely be thrown. See below for example:

"c:\temp" | Join-Path -ChildPath "filepath" # c:\temp binds to `-Path`
c:\temp\filepath

Regarding $_, which is synonymous with $PSItem, is the current input object in a script block. You typically see this used with Foreach-Object and Where-Object. If you don't have a script block, you won't be able to use $_.

You can technically pipe anything into Foreach-Object or Where-Object. Then the current pipeline object will be represented by $_. You don't need a collection as a single item can be piped in. See below:

"c:\temp" | Foreach-Object { $_ }
c:\temp

$filePath | Foreach-Object { $_ }

Path
----
c:\temp\test1\t.txt

$filePath | Foreach-Object { $_.Path }
c:\temp\test1\t.txt

Upvotes: 1

Related Questions