一个返回多个对象的 cmdlet,它所属的集合类型是什么(如果有的话)?【PowerShell】

4

Get-ADuser cmdlet的示例:

$Users = Get-ADuser -Filter *

在大多数情况下,它会返回多个AD用户对象,但这是什么“集合”类型呢?文档只说它将返回一个或多个Microsoft.ActiveDirectory.Management.ADUser用户对象。
尝试使用例如 ($Users -is [System.Collections.ArrayList]),但我无法确定“集合”类型是什么?
2个回答

11
Cmdlets本身通常在其输出中不使用集合类型。它们向管道发出单独的对象,这在特定情况下可能意味着零个、一个或多个对象。
这正是Get-ADUser所做的:输出对象的具体数量取决于给定的参数;这就是为什么Get-AdUser帮助主题只提到标量类型ADUser作为输出类型,并声明它“返回一个或多个”对象。
通常情况下,PowerShell管道被设计为一个对象流的通道,其数量不需要事先知道。命令通常会逐个输出对象,一旦它们可用就会立即输出。而接收命令通常也会逐个处理它们,一旦它们被接收到就会立即处理。详见关于管道
是PowerShell引擎本身会自动为您收集多个输出,并将其放入一个[object[]]数组中(如有需要)[2],特别是如果您通过变量赋值或使用命令调用(...)的方式捕获输出,或者使用分组运算符(或$(...),子表达式运算符[3],或@(...),数组子表达式运算符,下面将详细讨论)作为表达式。
# Get-ChildItem C:\Windows has *multiple* outputs, so PowerShell
# collects them in an [object[]] array.
PS> $var = Get-ChildItem C:\Windows; $var.GetType().Name
Object[]

# Ditto with (...) (and also with $(...) and always with @(...))
PS> (Get-ChildItem C:\Windows).GetType().Name
Object[]

然而,如果一个给定的命令 - 可能是在特定情况下 - 只输出一个对象,那么你将得到这个对象本身 - 它不会被包装在一个数组中(除非你使用@(...) - 请参见下文):
# Get-Item C:\ (always) returns just 1 object.
PS> $var = Get-Item C:\; $var.GetType().Name
DirectoryInfo # *not* a single-element array, 
              # just the System.IO.DirectoryInfo instance itself

有时候会变得棘手的是,一个给定的命令在不同的输入和运行时条件下,可能会产生一个或多个输出,因此引擎可能会返回一个单一的对象或者一个数组。
# !! What $var receives depends on the count of subdirs. in $HOME\Projects:
PS> $var = Get-ChildItem -Directory $HOME\Documents; $var.GetType().Name
??? # If there are *2 or more* subdirs: an Object[] array of DirectoryInfos.
    # If there is only *one* subdir.: a DirectoryInfo instance itself.
    # (See below for the case when there is *no* output.)

@(...)数组子表达式运算符,旨在消除这种歧义,如果需要的话: 通过将命令包装在@(...)中,PowerShell确保其输出始终被收集为[object[]] - 即使命令只产生一个输出对象甚至没有输出对象:

PS> $var = @(Get-ChildItem -Directory $HOME\Projects); $var.GetType().Name
Object[] # Thanks to @(), the output is now *always* an [object[]] array.

使用变量赋值时,一个可能更高效的替代方法是使用一个数组类型约束来确保输出变成一个数组:
# Alternative to @(...)
# Note: You may also create a strongly typed array, with on-demand type conversions:
#       [string[]] $var = ...
PS> [array] $var = Get-ChildItem -Directory $HOME\Documents; $var.GetType().Name
Object[]

注意:
这在效率上更高,因为如果RHS已经是一个数组,它会被直接赋值,而@(...)实际上会枚举...的输出,然后将元素重新组装成一个新的([object[]])数组。 [array]通过简单地将输入数组传递给它来保留输入数组的特定类型(例如,[array] $var = [int[]] (1..3)[int[]]数组原样存储在$var中)。
请注意,在某些边缘情况下,@(...)[array]的行为可能不同:@($null)返回一个只有一个元素为$null的单项数组,而[array] $null没有任何效果(仍然是$null)。类似地,对于不产生输出的命令,例如& {}@(& {})变成一个空数组,而[array] $arr = & {}则将$null赋值给$arr(除非在禁用变量优化的上下文中,这通常适用于全局范围:此时将意外赋值为特殊的“nothing”值(参见下文),请参阅GitHub问题#20275)。
一个实际的结果是,你只能对@(...)的结果进行盲目索引(假设严格模式关闭,这是默认情况下的情况,或者至多在版本2),而不能对[array] (...)进行索引;例如:@(Get-ChildItem nosuch*)[0]返回$null,而([array] (Get-ChildItem nosuch*))[0]失败,因为你不能对$null进行索引。感谢Walter A
[array]强制转换放在$var = ...左侧,也就是将其作为变量的类型约束,意味着变量的类型被锁定,以后将不同的值赋给$var仍然会将RHS值转换为[array][object[]]),如果需要的话(除非你赋值为$null或“nothing”(参见下文))。
退一步说,通常情况下,确保收集的输出是一个数组是不必要的,因为在PowerShell的3+版本中,标量和集合的处理是统一的。
PowerShell在标量(非集合)对象上暴露了内在成员,使您可以将它们视为集合。
例如,`(42).Count`是`1`(`(42).Length`也可以使用);它被视为一个只有一个元素的整数“集合”,这也适用于索引:`(42)[0]`是`42`,即虚拟集合的第一个和唯一的元素。
注意:类型本地成员优先,这对于字符串是一个陷阱:`'foo'.Length`是`3`,字符串的长度 - 但`'foo'.Count`有效(为`1`);然而,`'foo'[0]`始终是`'f'`,因为`[string]`类型的本地索引器返回数组中的单个字符(解决方法:`@('foo')[0]`)。
有关详细信息,请参见此答案。
如果一个命令没有输出,你会得到“nothing”(严格来说:[System.Management.Automation.Internal.AutomationNull]::Value 单例),在大多数情况下,它的行为类似于$null[4]
# Get-Item nomatchingfiles* produces *no* output.
PS> $null -eq (Get-Item nomatchingfiles*)
True

# Conveniently, PowerShell lets you call .Count on this value, which the
# behaves like an empty collection and indicates 0.
PS> (Get-Item nomatchingfiles*).Count
0

[1] 可以将整个集合作为一个整体输出到管道中(在PowerShell代码中使用Write-Output -NoEnumerate $collection或更简洁地使用, $collection),但这只是管道中的另一个对象,恰好是一个集合。然而,将整个集合输出是一个异常情况,它会改变您将命令传递给的管道中的输出,这可能会出乎意料;一个著名的例子是在v7.0之前ConvertFrom-Json的行为异常
[2] 一个System.Array实例,其元素的类型为System.Object,允许您在一个数组中混合不同类型的对象

[3] 使用(...)通常就足够了;$(...)只在字符串插值(可扩展字符串)和在较大表达式中嵌入整个语句或多个命令时才需要;请注意,与单独的(...)不同,$(...)会解开单元素数组;将(, 1).GetType().Name$(, 1).GetType().Name进行比较;请参见this answer

[4] 有一些情况下,“nothing”与$null的行为不同,尤其是在管道和switch语句中,详见GitHub上的这条评论GitHub问题#13465是一个已经批准的改进,旨在通过支持-is [System.Management.Automation.AutomationNull]作为测试,使“nothing”与$null更容易区分;然而,截至PowerShell(Core)7.3.8,还没有人着手实施它。

1
有益的回答!你可能想要添加下一个例子: var = @(Get-Item nomatchingfiles*); $var[0].count 是有效的,而 [array] $var = Get-Item nomatchingfiles*; $var[0].count 则会显示错误。 - undefined

1

Usually it's just an object array:

$Users.gettype()
$Users.count
$Users -is [object[]]
$error是一个ArrayList数组列表:
$error -is [collections.arraylist]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接