0

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:

  • Get all BitLockerVolume objects
  • For each BitLockerVolume object, pipe the KeyProtector fields forwards
  • Pipe KeyProtector objects forwards further for those with a RecoverPassword
  • Run the Backup-BitlockerKeyProtector, and supply the MountPoint

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?

Birdman
  • 1,404
  • 5
  • 22
  • 49
  • I voted this down because there is ample information online and also right here on StackOverflow: https://stackoverflow.com/questions/3494115/what-does-mean-in-powershell – I.T Delinquent May 24 '19 at 15:00
  • @I.TDelinquent Except I asked with a specific example and scenario with piping, not just a generic "I used $_ to print a number" – Birdman May 24 '19 at 15:03
  • @I.TDelinquent Clearly, I did not produce a post about "what is $_", and have an understanding of it, and asked for a specific context. – Birdman May 24 '19 at 15:03
  • `$_` refers to an object that come from the last pipe. – montonero May 24 '19 at 15:14
  • @montonero this is what I'm confused about. So in this case, the $PSItem (aka $_) object in the last pipe section should be KeyProtector, and not infact BitLockerVolume object? If that is the case, then my $_.MountPoint would not work, since that's a field of the original object earlier in the pipeline, and not a field of the KeyProtector. – Birdman May 24 '19 at 15:21
  • In your first code snippet `$_` holds the value coming from `Get-BitlockerVolume`, in your second code snippet `$_` holds the value coming from `Get-Member`. – Ansgar Wiechers May 24 '19 at 15:23
  • @AnsgarWiechers In the first code snippet, does $_ hold the value coming from Get-BitlockerVolume for the ENTIRE block? This is my main confusion. Later in the first code snippet, after piping a few times, I eventually am piping a KeyProtector object instead, as I take the field from the BitlockerVolume object. In the last section of the code snippet, does the $PSItem (aka $_) still refer to the BitlockerVolume object it originally did earlier in the pipeline? Or does it change based on objects passed through the pipeline? – Birdman May 24 '19 at 15:27
  • `$_` is the default variable in powershell. Its value always changes to the output of the last pipe. – Sid May 24 '19 at 16:13
  • Interesting usage of `$_` in a `try-catch` loop. `try { Test-Connection "10.10.10.10" -ErrorAction Stop } catch { $_.Exception.Message }` . In this, `$_` holds the error message. – Sid May 24 '19 at 16:15
  • *"In the first code snippet, does $_ hold the value coming from Get-BitlockerVolume for the ENTIRE block?"* Yes. Because for the parser it's still the same context, whereas in your second snippet the `$_` is in a different context. – Ansgar Wiechers May 24 '19 at 20:58
  • @AnsgarWiechers I'm not sure that's correct. As the other answer have shown here even though it's just 1 code block int the first snipped, $_ changes type when throughout the pipeline. It's no longer the value coming from Get-BitlockerVolume further down the pipeline. – Birdman May 24 '19 at 21:53
  • @Birdman `... | % { $_ | foo | bar $_.Something }` -> both `$_` are in the same context and refer to the same object. `... | % { $_ | foo | bar { $_.Something } }` -> each `$_` is in a different context and refers to a different object. – Ansgar Wiechers May 27 '19 at 08:39

4 Answers4

3

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
}
ArcSet
  • 6,518
  • 1
  • 20
  • 34
  • I see. So this is a bit different from my example but I think it makes sense. So in your 2nd example, with numbers and characters, you have two blocks wrapped in { } brackets. So the context of $PSItem aka $_, is scoped to the block, correct? In my example, the $_ variable is only contained within 1 set of brackets, so it should remain the same, throughout regardless of piping, correct? – Birdman May 24 '19 at 15:29
  • Can you just add a tad bit more information. I'm still slightly confused what $_ is referring to at the point of $_.MountPoint. Is that the BitLockerVolume object or the KeyProtector object? Everything else is making sense though that you are showing. – Birdman May 24 '19 at 15:39
  • is that better? – ArcSet May 24 '19 at 15:40
  • I gave you the answer, but it's still slightly confusing at the part of "Using KeyProtector's Mountpoints". It may just be wording. Sure, that is technically the MountPoint of the KeyProtector, but at the point where it's executing $_.MountPoint, comparing to other answer and comments, I believe the $PSItem variable aka $_, is technically a BitlockerVolumeObject. That was my main confusion all along. From your answer, it makes it sound like $_.MountPoint is accessing the MountPoint field from the KeyProtector object. "Using KeyProtector's Mountpoints" – Birdman May 24 '19 at 16:54
  • In the example you gave it is. Mountpoint is located in `keyprotector.mountpoint`based on your example – ArcSet May 24 '19 at 18:09
  • I would also assume your example you gave wouldnt work. – ArcSet May 24 '19 at 18:12
  • Updated answer to input the answer you were probably looking for at the bottom – ArcSet May 24 '19 at 18:17
  • yeah, your final code snippet is what I ended up doing, except I found that for some other magical piping reason, the KeyProtectorID field is not required to provide to the Backup cmdlet. It's apparently because the cmdlet will automatically retrieve the required info from the objects passed through the pipeline... – Birdman May 24 '19 at 18:36
  • This is not magical. Its part of piping. You can write functions that automatically take the piped content and deal with it. There are already functions that do that, you already use like `Get-Member` – ArcSet May 24 '19 at 18:40
  • If you have a sec ArcSet, take a look at my posted answer which build upon yours a bit. Maybe you could just confirm it is correct. Thank you. – Birdman May 24 '19 at 19:01
1

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
}
js2010
  • 23,033
  • 6
  • 64
  • 66
  • So even though the object being passed through the pipeline is changing, will the $PSItem aka $_ still remain the same throughout the script block? Therefore, even though KeyProtector objects are being passed to the last section of the pipeline, we can still refer to the original BitlockerVolume object through the $PSItem variable at that point? – Birdman May 24 '19 at 16:12
  • That makes sense to me but there's multiple comments on this thread stating it's referring to the object passed through the pipe, which at that point would be KeyProtector. Needless to say I'm pretty confused still – Birdman May 24 '19 at 16:17
  • It's the object passed through the pipe to foreach-object. – js2010 May 24 '19 at 16:19
  • okay, I understand it's originally the item passed through the pipeline from Get-BitlockerVolume into the foreach-object. I just wanted to confirm that throughout the entire Foreach-Object script block section, $_ will remain the same regardless of piping within that script block. – Birdman May 24 '19 at 16:21
0

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.

AdminOfThings
  • 23,946
  • 4
  • 17
  • 27
0

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.

Birdman
  • 1,404
  • 5
  • 22
  • 49