I'm confused what I'm doing wrong in ForEach method syntax of List?
PS D:\ntt> $nicInfo.IpConfigurations.Count
2
PS D:\ntt> $nicInfo.IpConfigurations[0]
PrivateIpAddressVersion Name Primary PrivateIpAddress PrivateIpAllocationMethod Subnet Name PublicIpAddress Name ProvisioningState
----------------------- ---- ------- ---------------- ------------------------- ----------- -------------------- -----------------
IPv4 ipconfig1 True 10.233.0.4 Dynamic Succeeded
PS D:\ntt> $nicInfo.IpConfigurations.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True List`1 System.Object
PS D:\ntt> $nicInfo.IpConfigurations.ForEach({$_})
PS D:\ntt>
The problem is that PowerShell's intrinsic .ForEach()
array method is preempted by the List<T>
type's own .ForEach()
method in this case:
PowerShell's own .ForEach({ ... })
:
$_
as the input object at hand for the script-block argument ({ ... }
)By contrast, List<T>
's .ForEach({ ... })
converts the script block to an Action<T>
delegate, which has the following implications / limitations:
The delegate doesn't know about $_
inside the script block and instead receives a single argument that must be accessed as $args[0]
.
Output from the script block is ignored, because an Action<T>
delegate by definition has no return value.
Write-Host
from within the script block, such output cannot be used programmatically, at least not by default,[1] because it bypasses PowerShell's output streams and can therefore neither be captured nor redirected.Tip of the hat to PetSerAl for providing the crucial pointers in comments.
Workarounds:
If the script block you pass to .ForEach()
need not produce any output, all that's needed is to use $args[0]
in lieu of $_
in your script block, though you may still opt to use one of the other workarounds below in order to avoid confusion.
If output is needed, the simplest solution is to convert the List<T>
instance to an array with .ToArray()
first, on which .ForEach()
works as expected; a simplified example:
$list = [System.Collections.Generic.List[object]] ('foo', 'bar')
$list.ToArray().ForEach({ $_ + '!' }) # Note the .ToArray() call.
The above produces 'foo!', 'bar!'
, as expected.
Alternatively, you may use:
foreach
loop to process the list items, which means you must pick an iteration variable name and refer to that instead of $_
in the loop body; e.g.:foreach ($itm in $list) { $itm + '!' }
ForEach-Object
in a pipeline (slower, but doesn't require changing the script block), as shown in No Refunds No Returns' answer; e.g.:$list | ForEach-Object { $_ + '!' }
[1] In v5.1 of Windows PowerShell and in PowerShell (Core) 7, you can capture Write-Host
output (but not Out-Host
output), via the information output stream, whose number is 6
, using redirection 6>&1
; in the case at hand, this additionally requires enclosing the method call in (...)
, e.g.
$output = ($list.ForEach({ Write-host ($args[0] + '!') })) 6>&1
However, note that what is captured are [System.Management.Automation.InformationRecord]
instances that merely contain the stringified representations of the objects passed to Write-Host
, in their .MessageData.Message
property.