Script has two variables when done, but when I pipe to SELECT-object only first one returns data to console - powershell

I am trying to query multiple servers with WMI, but I don't always have access to the servers.
The code is below. Alas, it returns "access is denied" to the console, but I can't seem to get rid of it. Oh well.
However, I am trapping the servers that I can't connect to, so that I can tell someone else to look at them, or request access.
But when I run the code, it only returns the first list of servers; even if $failed_servers has values, nothing is returned. If I tell both to pipe to ogv, then two windows pop up.
Why won't both "$variable|select" work? If I remove the select on $failed_servers, then it shows up, albeit just sitting immediately underneath the successful ones. Which is okay-but-not-great.
$list = ("servera","serverb","serverc")
$failed_servers = #()
$final = foreach ($server_instance in $list)
{
$errors=#()
gwmi -query "select * from win32_service where name like '%SQLSERVER%'" -cn $server_instance -ErrorVariable +errors -ErrorAction SilentlyContinue
if ($errors.Count -gt 0) {$failed_servers += $server_instance
}
}
$final|select pscomputername, name, startmode, state |where {$_.pscomputername -ne $null}
$failed_servers |select #{N='Failed Servers'; E={$_}}

What you're experiencing is merely a display problem:
Both your Select-Object calls produce output objects with 4 or fewer properties whose types do not have explicit formatting data associated with them (as reported by Get-FormatData).
This causes PowerShell's for-display output formatting system to implicitly render them via the Format-Table cmdlet.
The display columns that Format-Table uses are locked in based on the properties of the very first object that Format-Table receives.
Therefore, your second Select-Object call, whose output objects share no properties with the objects output by the first one, effectively produces no visible output - however, the objects are sent to the success output stream and are available for programmatic processing.
A simple demonstration:
& {
# This locks in Month and Year as the display columns of the output table.
Get-Date | Select-Object Month, Year
# This command's output will effectively be invisible,
# because the property set Name, Attributes does not overlap with
# Month, Year
Get-Item \ | Select-Object Name, Attributes
}
The output will look something like this - note how the second statement's output is effectively invisible (save for an extra blank line):
Month Year
----- ----
9 2021
Note the problem can even affect a single statement that outputs objects of disparate types (whose types don't have associated formatting data); e.g.:
(Get-Date | Select-Object Year), (Get-Item \ | Select-Object Name)
Workarounds:
Applying | Format-List to the command above makes all objects visible, though obviously changes the display format.
Intra-script you could pipe each Select-Object pipeline to Out-Host to force instant, pipeline-specific formatting, but - given that the results are sent directly to the host rather than to the success output stream - this technique precludes further programmatic processing.
Potential future improvements:
GitHub issue #7871 proposes at least issuing a warning if output objects effectively become invisible.

Related

In a powershell pipeline, how to wait for a cmdlet to complete before proceding to the next one?

Consider the following function:
function myFunction {
100
sleep 1
200
sleep 1
300
sleep 1
}
As you can see it will emit those values, one by one down the pipeline.
But I want to wait for all the values to be emitted before going on. Like
myFunction | waitForThePreviousCommandToComplete | Format-Table
I want the Format-Table above to receive the entire array, instead of one-by-one items.
Is it even possible in Powershell?
Use
(...), the grouping operator in order to collect a command's output in full first, before sending it to the success output stream (the pipeline).
# Due to (...), doesn't send myfunction's output to Format-Table until it has run
# to completion and all its output has been collected.
(myFunction) | Format-Table
# Also works for entire pipelines.
(100, 200, 300 | ForEach-Object { $_; Start-Sleep 1 }) | Format-Table
Note:
If you need to up-front collect the output of multiple commands (pipelines) and / or language statements, use $(...), the subexpression operator instead, e.g. $(Get-Date -Year 2020; Get-Date -Year 2030) | Format-Table; the next point applies to it as well.
Whatever output was collected by (...) is enumerated, i.e., if the collected output is an enumerable, its elements are emitted one by one to the success output stream - albeit without any delay at that point.
Note that the collected output is invariably an enumerable (an array of type [object[]]) if two or more output objects were collected, but it also can be one in the usual event that a single object that itself is an enumerable was collected.
E.g., (Write-Output -NoEnumerate 1, 2, 3) | Measure-Object reports a count of 3, even though Write-Output -NoEnumerate output the given array as a single object (without (...), Measure-Object would report 1).
Typically, commands (cmdlets, functions, scripts) stream their output objects, i.e. emit them one by one to the pipeline, as soon as they are produced, while the command is still running, as your function does, and also act on their pipeline input one by one. However, some cmdlets invariably, themselves collect all input objects first, before they start emitting their output object(s), of conceptual necessity: notable examples are Sort-Object, Group-Object, and Measure-Object, all of which must act on the entirety of their input before they can start emitting results. Ditto for Format-Table when it is passed the -AutoSize switch, discussed next.
In the case of Format-Table, specifically, you can use the -AutoSize switch in order force it to collect all input first, in order to determine suitable display column widths based on all data (by default, Format-Table waits for 300 msecs. in order to determine column widths, based on whatever subset of the input data it has received by then).
However, this does not apply to so-called out-of-band-formatted objects, notably strings and primitive .NET types, which are still emitted (by their culture-invariant .ToString() representation) as they're being received.
Only complex objects (those with properties) are collected first, notably hashtables and [pscustomobject] instances; e.g.:
# Because this ForEach-Object call outputs complex objects (hashtables),
# Format-Table, due to -AutoSize, collects them all first,
# before producing its formatted output.
100, 200, 300 | ForEach-Object { #{ num = $_ }; Start-Sleep 1 } |
Format-Table -AutoSize
If you want to create a custom function that collects all of its pipeline input up front, you have two options:
Create a simple function that uses the automatic $input variable in its function body, which implicitly runs only after all input has been received; e.g.:
# This simple function simply relays its input, but
# implicitly only after all of it has been collected.
function waitForThePreviousCommandToComplete { $input }
# Output doesn't appear until after the ForEach-Object
# call has emitted all its output.
100, 200, 300 | ForEach-Object { $_; Start-Sleep 1 } | waitForThePreviousCommandToComplete
In the context of an advanced function, you'll have to manually collect all input, iteratively in the process block, via a list-type instance allocated in the begin block, which you can then process in the end block.
While using a simple function with $input is obviously simpler, you may still want an advanced one for all the additional benefits it offers (preventing unbound arguments, parameter validation, multiple pipeline-binding parameters, ...).
See this answer for an example.
Sort waits until it has everything.
myFunction | sort-object
Or:
(myFunction)
$(myfunction1; myFunction2)
myFunction | format-table -autosize
myFunction | more
See also: How to tell PowerShell to wait for each command to end before starting the next?
For some unknown reason, just putting the function inside brackets solved my problem:
(myFunction) | Format-Table

PowerShell 5.1 I am not getting the expected output in this simple example

$res = Invoke-Sqlcmd -Query "select * from customer" -ServerInstance "(localdb)\MSSQLLocalDB" -Database "Database1" -OutputAs DataTables
$res | Where-Object FirstName -eq "John"
$res.Where({$_.FirstName -eq "John"})
This is the output:
Id FirstName City
-- --------- ----
1 John Augusta
1 John Augusta
I was expecting this:
Id FirstName City
-- --------- ----
1 John Augusta
Id FirstName City
-- --------- ----
1 John Augusta
Building on the helpful comments:
The behavior is by design:
Objects output by a given script or a single interactively submitted command line are all sent to the success output stream of a single pipeline.
When output from a pipeline is neither captured nor redirected, PowerShell applies for-display output formatting to all output objects in the success output stream of a given pipeline, and if the first output object implicitly selects tabular formatting, all subsequent objects of the same type are formatted as part of the same table that is printed to the host (display, terminal).
Things get tricky if subsequent objects are of a different type as well as if they're also [pscustomobject] instances, but with different property sets - see this answer for more information.
If you want to format a given command's output individually, you have three basic options, all of which are suboptimal in case you also want to option to later programmatically process the output, not just format it for display:
Send the output directly to the host, using Out-Host:
$res | Where-Object FirstName -eq "John" | Out-Host
This bypasses the success output stream, meaning that this output cannot be captured or redirected.
Use a Format-* cmdlet such as Format-Table explicitly:
$res | Where-Object FirstName -eq "John" | Format-Table
This sends objects representing formatting instructions rather than the original objects to the success output stream, which the host (as the default output target) renders correctly, but these objects are meaningless for further programmatic processing.
Use Out-String (possibly preceded by a call to a Format-* cmdlet to select the kind of formatting and/or control the specifics of the formatting):
$res | Where-Object FirstName -eq "John" | Out-String
This sends a single, multi-line string to the success output stream that contains the same representation you would see in the host with Out-Host[1] and since strings always render as-is, you'll see the same host output; in programmatic processing, these strings are relatively more meaningful than the formatting-instruction objects output by Format-* cmdlets, but still amount to a loss of information compared to the original output objects.
[1] Unfortunately, Out-String always appends a trailing newline to this representation; this problematic behavior is the subject of GitHub issue #14444. As zett42 points out, using Out-String -Stream avoids this problem, albeit at the expense of sending the lines of the multi-line string representation individually to the output stream; To avoid that, you can use (... | Out-String -Stream) -join "`n"

What causes the output of select-object to be truncated?

I have the following silly PowerShell script:
$username = 'rny'
$null = mkdir "c:\Users\$username\YYY"
$null = mkdir "c:\Users\$username\YYY\TODO"
$null = mkdir "c:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc"
$files = "C:\Users\$username\one-two-three-four.sql.wxyz",
"C:\Users\$username\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz",
"C:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz"
foreach ($file in $files) {
$null = new-item $file
}
Get-ChildItem . -errorAction silentlyContinue -recurse -filter *.wxyz | select-object fullName
foreach ($file in $files) {
remove-item -literalPath $file
}
rmdir "c:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc"
rmdir "c:\Users\$username\YYY\TODO"
rmdir "c:\Users\$username\YYY"
When I execute it, the output of the get-childItem ... | select-object pipeline is truncated:
FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\an...
Note especially the last line. This behaviour was noted elsewhere on SuperUser and the accepted answer is to pipe the output into format-table with -autoSize. So far, so good.
However, If I comment the second file in the assignment of the $files array like so
$files = "C:\Users\$username\one-two-three-four.sql.wxyz",
# "C:\Users\$username\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz",
"C:\Users\$username\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz"
the output is not truncated anymore:
FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz
This puzzles me because the name of the file that was truncated is now fully visible and I have no explanation for this.
So, what exactly causes the truncation of the file in one case and not in the other case?
This isn't so much to do with Select-Object per se - it's more to do with how PowerShell converts values into string representations, and specifically in this case how it does that when it displays uncaptured output from cmdlets on the console.
PowerShell (Windows and Core) has a bunch of preconfigured "views" that define how some built-in types are rendered - e.g. whether they use Format-List or Format-Table, what properties to display, and in the case of tables, how wide to display each column - see about_Format.ps1xml.
For other types, PowerShell tries to make a best-guess on the fly. To do that it waits for the first N items in arrive from the input to make a decision on the formatting rules to apply. I can't find any definitive documentation that says how many items PowerShell waits for, so that might be a good follow-up question :-).
And you can obviously override these defaults by passing formatting parameters for Format-Table and Format-List.
In your case the top-level script has received pipeline output containing an array of PSCustomObject objects (i.e. the output from Select-Object) and it's decided to show them in a table with a column for the FullName property.
Example 1
In your first example, it's looked at the first two PSCustomObject items and decided to make the FullName column 54 characters wide since that's the length of C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz, and the third item gets truncated to that same width (if you include the ...) because it wasn't included in the decision-making process for column widths.
FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-11_29_abcdefghijklmnop.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\an...
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| 54 characters
Example 2
In your second example, PowerShell sees the longest FullName property in the first couple of PSCustomObjects is C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz and so uses a column width of 70.
FullName
--------
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^| 70 characters
Example 3
Finally, if you do what #notjustme suggests in the comments and add -ExpandProperty FullName onto Select-Object you get an array of string values instead of an array of PSCustomObjects which is why you might see PowerShell apply different formatting rules - you don't get a FullName header for example because the values are strings not objects with properties, and it's using Format-List instead of Format-Table.
C:\Users\rny\one-two-three-four.sql.wxyz
C:\Users\rny\YYY\TODO\2021-12-22_Foo-bar-baz-etc\another-filename.wxyz
To add some background to mcclayton's helpful answer:
Specifically, you're seeing the effects of the infamous 300-msec. delay built into Format-Table formatting, which PowerShell implicitly applies to instances of .NET types that have 4 or fewer properties and do not have explicit formatting data associated with them.
See this answer for details (which is given in the context of a different symptom of the same problem, namely unexpected output ordering), but the short of it is: The delay is used to infer suitable column widths from the specific property values received within the delay period.
This means that objects with property values received after the 300-msec. delay may be truncated in their column display if their values happen to be wider than the widest among the values received during the delay period.
Specifically, your symptom implies that only the first two objects were received within the delay period, and that the longer among the two property values then locked in the column width; when the third object was received later, the column width was already locked in, and the longer value was truncated (indicated with trailing ... in Windows PowerShell (3 . chars.) and … in PowerShell (Core) 7+ (single char))
The only way to avoid truncating is to know the max. column width ahead of time and pass it to an explicit Format-Table call -
notably, this prevents using the output as data. See below.
Here's a simple way to provoke the problem:
Note: The Select-Object calls below aren't strictly needed, but are provided for symmetry with the question.
# Create blocks of two objects with strings of different length in their
# .Prop value: 10 chars. vs. 100 chars.
$count = 10000 # How often to repeat each object in a row.
$objs =
(, [pscustomobject] #{ Prop = ('x' * 10) } * $count) +
(, [pscustomobject] #{ Prop = ('y' * 100) } * $count)
# Depending on the value of $count - which translates into how
# long it takes until the second block of objects starts emitting -
# truncation will occur or not.
$objs | Select-Object Prop
With blocks of 10,000 objects, I do see the truncation: it takes long enough for the first block - with the short property value - to lock in the width of the display column, causing the objects in the second block to be truncated:
Prop
----
xxxxxxxxxx
...
yyyyyyyyy… # <- truncated, because width 10 was locked in during the delay
...
To prevent truncation, pass a calculated property to Format-Table specifying the max. width:
$objs | Select-Object Prop | Format-Table #{ n='Prop'; e='Prop'; width = 100 }

Optimize Get-ADUser filter

In AD, I'm trying to identify user accounts where the same EmployeeID value is populated in 2 or more records. Below is my piece of code (Credit: I'm using a Show-Progress function defined here) and the Get-ADUser command alone has taken more than 2 hours to fetch all the records. The other steps (2 to 5) have been pretty quick. While I've completed the work, I'm trying to know if this could've been done more efficiently with PowerShell.
Get-ADUser -LDAPFilter "(&(ObjectCategory=Person)(objectclass=user)(employeeid=*))" -Properties $properties -Server $server_AD_GC -ResultPageSize 1000 |
# *ISSUE HERE*
# The Get-ADUser extract process seems to work very slow.
# However, it is important to note that the above command will be retrieving more than 200K records
# NOTE: I've inferred that employeeid is an indexed attribute and is replicated to GlobalCatalogs and hence have used it in the filter
Show-Progress -Activity "(1/5) Getting AD Users ..." |
select $selectPropsList -OutVariable results_UsersBaseSet |
Group-Object EmployeeID |
Show-Progress -Activity "(2/5) Grouping on EmployeeID ..." |
? { $_.Count -gt 1 } |
Show-Progress -Activity "(3/5) Filtering only dup EmpID records ..." |
select -Exp Group |
Show-Progress -Activity "(4/5) UnGrouping ..." |
Export-Csv "C:\Users\me\op_GetADUser_w_EmpID_Dupes_EntireForest - $([datetime]::Now.ToString("MM-dd-yyyy_hhmmss")).csv" -NoTypeInformation |
Show-Progress -Activity "(5/5) Exporting ..." |
Out-Null
PS: I've also tried to first export all the user accounts to a csv file and then post-process with Excel but I had to frown because of the size of the dataset and it was both time and memory crunching.
Any suggestion is highly appreciated.
Since we don't know what is in $properties or $selectPropsList, your question is really only about finding out to which users the same EmployeeID has been issued, right?
By default, Get-ADUser already returns these properties:
DistinguishedName, Enabled, GivenName, Name, ObjectClass, ObjectGUID, SamAccountName, SID, Surname, UserPrincipalName
So all you need extra is the EmployeeID I guess. Trying to collect LOTS of properties does slow down, so keeping this to a bare minimum helps to speed things up.
Next, by using the Show-Progress script you have linked to, you will slow down the execution of the script considerably. Do you really need to have a progress bar?
Why not simply write the lines with activity steps directly to the console?
Also, piping everything together doesn't help in the speed department either..
$server_AD_GC = 'YourServer'
$selectPropsList = 'EmployeeID', 'Name', 'SamAccountName', 'Enabled'
$outFile = "C:\Users\me\op_GetADUser_w_EmpID_Dupes_EntireForest - $([datetime]::Now.ToString("MM-dd-yyyy_hhmmss")).csv"
Write-Host "Step (1/4) Getting AD Users ..."
$users = Get-ADUser -Filter "EmployeeID -like '*'" -Properties EmployeeID -Server $server_AD_GC -ResultPageSize 1000
Write-Host "Step (2/4) Grouping on EmployeeID ..."
$dupes = $users | Group-Object -Property EmployeeID | Where-Object { $_.Count -gt 1 }
Write-Host "Step (3/4) Collecting duplicates ..."
$result = foreach ($group in $dupes) {
$group.Group | Select-Object $selectPropsList
}
Write-Host "Step (4/4) Exporting ..."
$result | Export-Csv -Path $outFile -NoTypeInformation
Write-Host "All done" -ForegroundColor Green
P.S. Get-ADUser already returns user objects only, so there is no need for the LDAP filter (ObjectCategory=Person)(objectclass=user). Using -Filter "EmployeeID -like '*'" is probably faster
This answer complements Theo's helpful answer and focuses on showing progress during the operation:
The linked Show-Progress function, which is the latest as of this writing:
has an outright bug, in that it doesn't pass pipeline input through (the relevant line is accidentally commented out)
is conceptually flawed in that it doesn't use a process block, which means that all pipeline input is collected first, before it is processed - which defeats the idea of a progress bar.
Therefore, you Show-Progress calls won't show progress until the previous command in the pipeline has output all of its output. A simple alternative is to break the pipeline into separate commands and to simply emit one progress message before each command, announcing the next stage of processing (rather than per-object progress) as shown in Theo's answer.
Generally, there is no way to show the progress of command-internal processing, only the progress of a command's (multi-object) output.
The simplest way to do this via a ForEach-Object call in which you call
Write-Progress, but that comes with two challenges:
In order to show a percent-complete progress bar, you need to know how many objects there will be in total, which you must determine ahead of time, because a pipeline cannot know how many objects it will receive; your only option is to collect all output first (or find some other way to count it) and then use the collected output as pipeline input, using the count of objects as the basis for calculating the value to pass to Write-Progress -PerCentComplete.
Calling Write-Progress for each object received will result in a significant slowdown of overall processing; a compromise is to only call it for every N objects, as shown in this answer; the approach there could be wrapped in a properly implemented function a la Show-Progress that requires passing the total object count as an argument and performs proper streaming input-object processing (via a process block); that said, the mere act of using PowerShell code for passing input objects through is costly.
Conclusion:
Percent-complete progress displays have two inherent problems:
They require you to know the total number of objects to process beforehand (a pipeline has no way of knowing how many objects will pass through it):
Either: Collect all objects to process in memory, beforehand, if feasible; the count of elements in the collection can then serve as the basis for the percent-complete calculations. This may not be an option with very large input sets.
Or: Perform an extra processing step beforehand that merely counts all objects without actually retrieving them. This may not be practical in terms of the additional processing time added.
Object-by-object processing in PowerShell code - either via ForEach-Object or an advanced script/function - is inherently slow.
You can mitigate that somewhat by limiting Write-Progress calls to every N objects, as shown in this answer
Overall it's a tradeoff between processing speed and the ability to show percent-complete progress to the end user.

Differences between | and $

Can anyone explain to me the differences I'm seeing in either using a | to pipe one command to another or using $ to 'pipe' it a different way (sorry not sure if the use $ is actually considering piping).
So… this works:
Get-Mailbox -ResultSize Unlimited |
where { $_.RecipientTypeDetails.tostring() -eq "SharedMailbox" } |
Get-MailboxPermission
Which is great, however because I want to place another where command after the Get-MailboxPermission which doesn't work above I then tried to use this:
$Mailbox = Get-Mailbox -ResultSize Unlimited |
where { $_.RecipientTypeDetails.tostring() -eq "SharedMailbox" }
Get-MailboxStatistics -Identity $Mailbox |
where { $_.IsInherited.tostring() -eq "False" }
It causes me to get this error:
Cannot process argument transformation on parameter 'Identity'. Cannot convert the "System.Collections.ArrayList" value of type "System.Collections.ArrayList" to type "Microsoft.Exchange.Configuration.Tasks.GeneralMailboxOrMailUserIdParameter".
Surely using | or $ is the same in the sense that it pipes through the results to the next command or am I completely wrong?
I don't have an exchange shell here to test but I guess I can still explain the basics and point you in the right direction.
The pipe | is used to redirect output from one command to another command. $ in Powershell is the character which defines that the character sequence right behind it is either a variable (e.g. $Mailbox as an example for a normal variable or $_ as an example for a variable that holds data that has been piped through from a previous command) or an expression. An example for an expression one is $(4+5).
Or in a more frequently used example:
PS C:\Users\Administrator> $file = (get-childitem)[0]
PS C:\Users\Administrator> write-output "The fullname of $file is $($file.fullname)"
The fullname of .ssh is C:\Users\Administrator\.ssh
In that example it is actually necessary to use an expression, because variable detection inside a string doesn't recognize dots as separator between variable and a variable member (fullname is a member of $file).
If it's not clear to you why there is a point and what members are, you should probably look into object oriented programming a bit because Powershell is object oriented through and through.
In your 2nd example you just save everything that's returned by your Get-Mailbox command in the $Mailbox variable. The $Mailbox variable is available as long as you don't delete it or leave its scope (in this case, the powershell session). You can actually use the variable as input for multiple commands without losing its data.
When using the pipe, the data returned by your first command is only accessible for the command behind the pipe and after that it's gone.
That's probably the difference you're interested in.
As for your actual problem: Powershell tells you that it's not expecting to be handed a variable of type System.Collections.ArrayList, which is what Get-Mailbox returns. The technet help is unclear as to what Get-Mailbox specificly returns, but I strongly guess it's an ArrayList of Mailbox-Objects. You can check it like this:
$Mailbox.GetType()
$Mailbox[0].GetType() # gets the type of the first object in $Mailbox
To fix your code, you need to loop over what's been returned by Get-Mailbox. Try this:
$Mailboxes = Get-Mailbox -ResultSize Unlimited | where { $_.RecipientTypeDetails.tostring() -eq "SharedMailbox" }
$Mailboxes | ForEach-Object { Get-MailboxStatistics -Identity $_ }
The ForEach-Object cmdlet loops over an array or a list and works on each item individually.
Your first example works so far because Powershell has been made smarter about piped data a few versions ago (See paragraph about 'Member Enumeration'). It's actually ForEach-ing over the passed in data.
Follow up links:
The $_ variable in powershell
Powershell is an object oriented language
Sorry to have to say this, but you're completely wrong. Pipelines and variables are two entirely different things.
The pipe (|) connects the output of one cmdlet to the input of another cmdlet. List output is processed one item at a time, i.e. the second cmdlet receives each list item separately.
If you collect a list of items in a variable ($mailbox) and call a cmdlet with that variable you're passing the list object instead of individual list items. That only works if the cmdlet parameter accepts array input.
The pipe operator | i used to flow the output of one command into the input of another command.
The dollar symbolc, $ is used to denote that the name following it is a variable, and has nothing to do with piping data between cmdlets. The where cmdlet create a $_ variable for use within its expression.