Birdman
Birdman

Reputation: 1524

How does $_ work when piping in PowerShell?

I'm confused how the $_ variable works in certain contexts of piping. In this example for backing up a Bitlocker key:

Get-BitlockerVolume | % {$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $_.MountPoint}

This is how I read it in English:

However, MountPoint is a field of the BitLockerVolume object, as shown here:

PS C:\Windows\system32> Get-BitLockerVolume | Get-Member | Where-Object {$_.Name -eq "MountPoint"}


   TypeName: Microsoft.BitLocker.Structures.BitLockerVolume

Name       MemberType Definition
----       ---------- ----------
MountPoint Property   string MountPoint {get;}

So, for the entire block wrapped in brakcets { }, will the $_ variable ALWAYS be the same through any amount of piping? For example, the object we are piping forwards is changing. It's no longer a BitLockerVolume Object, but instead a KeyProtector object. So will the $_ always refer to the BitLockerVolume object in this case, or will it change further down the pipeline depending on different types of objects piped further through the chain?

Upvotes: 0

Views: 401

Answers (4)

Birdman
Birdman

Reputation: 1524

Id' like to build on ArcSet's answer just a little bit. Since I finally understood the value of the $PSItem is changing when the type changes down the pipeline, I ran this code to do a little check.

Get-BitLockerVolume | % {$_.GetType()}

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    BitLockerVolume                          System.Object
True     False    BitLockerVolume                          System.Object
True     False    BitLockerVolume                          System.Object

Here we can see some objects returned by the pipeline are of the BitLockerVolume type.

Now, based on my original question/example, if we pipe it further based on KeyProtector the object type will change for the $PSItem variable.

Get-BitLockerVolume | % { $_.KeyProtector | ? RecoveryPassword  | % {$_.GetType()}}

IsPublic IsSerial Name                                     BaseType
-------- -------- ----                                     --------
True     False    BitLockerVolumeKeyProtector              System.Object
True     False    BitLockerVolumeKeyProtector              System.Object

So at this point, at the end of the pipeline, we execute some other cmdlet like Backup-BitlockerKeyProtector and reference the $PSItem variable, aka $_, then it will refer to the object types last passed through the pipeline, in this case, the objects would be of the BitLockerVolumeKeyProtector type.

Upvotes: 0

ArcSet
ArcSet

Reputation: 6860

So $_ is the info from the current pipe.

1,2 | %{
    $_
}

response

1
2

while

1,2 | %{
    "a","b" | %{
        $_
    }
}

response

a
b
a
b

We can see in the first that the output from %_ is from the last info given which is 1,2. While the next example still loops 1,2 but the output is from the pipe inside a,b.

There are ways around this by storing the first pipe information into a variable in the second pipe

1,2 | %{
    $Num = $_
    "a","b" | %{
        $Num
    }
}

which case the output is

1
1
2
2

In the example you gave lets look at it formated

Get-BitlockerVolume | % {
    $_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $_.MountPoint
}

You have 2 different pipes. The First is getting 'BitlockerVolumevolume'. The second starts with you sending the BitlockerVolume's KeyProtector.

Its like saying

For each Bitlocker volume, Get KeyProtector.

For each KeyProtector, Get me ones that have the member RecoveryPassword

Foreach KeyProtector with member RecoveryPassword, Backup Bitlocker Key Protector Using KeyProtector's Mountpoints

So on one final note I would also assume the example you gave wouldnt work. What you might be looking for is this...

Get-BitlockerVolume | % {
    $MountPoint = $_.MountPoint
    $_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_.KeyProtectorId
}

Upvotes: 3

AdminOfThings
AdminOfThings

Reputation: 25011

I know there are already good answers here, but I feel one important question was not addressed. The question of what happens to $_ throughout the Foreach-Object {} block when there is nesting. I am going use ArcSet's example since that was the answer selected.

1,2 | % {
    "$_ before second foreach"
    'a','b' | % {
        "$_ inside second foreach"
    }
    "$_ after second foreach"
}

1 before second foreach
a inside second foreach
b inside second foreach
1 after second foreach
2 before second foreach
a inside second foreach
b inside second foreach
2 after second foreach

Notice that $_ becomes the current object being processed by the code within the Foreach-Object {} blocks. When entering the second Foreach-Object block, $_ changes. When exiting the second Foreach-Object block, $_ changes back to the object that will be continued to be processed by the remainder of the first block. So $_ neither remains the same nor is lost during the block processing. You will need to either assign $_ as another variable or in applicable situations use the -PipelineVariable switch to access those objects within different blocks.

Upvotes: 0

js2010
js2010

Reputation: 27491

Let's expand the aliases and fill in the implied parameters. $_ can only be used inside script blocks '{ }' that are options to cmdlets. Just because you're in a pipe, doesn't mean you can use $_ . The $_ here belongs to Foreach-Object. Where-Object is using a comparison statement.

Get-BitlockerVolume | Foreach-Object -Process {
  $_.KeyProtector | Where-Object -Property RecoveryPassword | 
    Backup-BitlockerKeyProtector -MountPoint $_.MountPoint
}

Upvotes: 1

Related Questions