ForEach-Object管道调用与-InputObject参数调用的差异解析
我推测这与数据通过管道传递的方式有关,以下是两个示例:
$someData = Get-ChildItem $obj = ForEach-Object -InputObject $someData -Process {[pscustomobject] @{Name=$_.Name; LastWriteTime = $_.LastWriteTime}} $obj2 = Get-ChildItem | % {[pscustomobject] @{Name=$_.Name; LastWriteTime = $_.LastWriteTime}}
二者生成的对象类型不同,我原本预期都是[PSCustomObject]类型,但实际只有一个是:
$obj.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True False PSCustomObject System.Object $obj2.GetType() IsPublic IsSerial Name BaseType -------- -------- ---- -------- True True Object[] System.Array
将$Obj2传入Format-Table时,能得到预期的表格输出;但$Obj的Format-Table输出却毫无用处。背后的机制如下:
1. -InputObject参数的执行逻辑
当使用ForEach-Object -InputObject $someData时,$someData作为完整的集合对象被传入参数,Process块只会针对整个集合执行一次,而非遍历集合内的单个元素。
此时$_代表的是整个文件/目录集合,代码中访问的$_.Name和$_.LastWriteTime触发了PowerShell的成员枚举特性:当对集合访问其元素的成员时,PowerShell会自动遍历所有元素,提取对应成员的值,最终返回一个数组。因此生成的PSCustomObject的Name和LastWriteTime属性都是数组——这就是$obj是单个PSCustomObject,但Format-Table输出无效的核心原因:表格无法正常展示数组类型的属性。
2. 管道传递的执行逻辑
当使用管道Get-ChildItem | % { ... }时,PowerShell会自动拆解集合,将其中的每个元素逐个传入ForEach-Object的Process块。此时$_代表单个文件/目录对象,每次执行Process块都会生成一个独立的PSCustomObject,最终这些对象会被PowerShell自动收集为Object[]数组(即$obj2的类型)。
当把这个数组传入Format-Table时,命令会遍历数组中的每个PSCustomObject,提取单个对象的属性值,从而生成正常的表格输出。
3. 统一行为的修正方式
如果想让-InputObject调用方式和管道行为一致,需要让ForEach-Object遍历集合中的元素,可采用以下两种方式:
# 方式1:使用foreach循环手动遍历 $obj = foreach ($item in $someData) { [pscustomobject] @{Name=$item.Name; LastWriteTime = $item.LastWriteTime} } # 方式2:通过管道展开集合传递 $obj = $someData | ForEach-Object -Process {[pscustomobject] @{Name=$_.Name; LastWriteTime = $_.LastWriteTime}}
修正后$obj的类型会变为Object[],传入Format-Table即可得到预期的表格输出。
内容的提问来源于stack exchange,提问作者PnP

