为什么仅添加一个PSCustomObject时PowerShell将ArrayList转换为PSCustomObject?

时间:2019-12-30 09:42:12

标签: powershell arraylist

我正在编写代码,以获取所有符合条件的文件。对于找到的每个文件,我创建一个仅包含几个文件($ Obj)值的自定义对象,然后将其添加到ArrayList ($Files)中。然后将此ArrayList返回到调用函数并存储在其($OldFiles)的变量中。

当找不到文件或多个文件时,此方法工作正常。但是,如果仅找到一个文件,则ArrayList会将其自身转换为PSCustomObject,然后将引发异常,因为返回值无法将PSCustomObject转换为ArrayList

奇怪的是,如果我在return语句之前添加简单的行"$Files.getType()",就没有错误,并且ArrayList仍然是ArrayList

基本上,这将不起作用: 在第一个函数中进行了简化:

$Files = [System.Collections.ArrayList]@()
$Files.Add($Obj) > $null
return $Files

这是顶部函数的外观:

$OldFiles = [System.Collections.ArrayList](Check-OldFiles -Path $Directory -Age $Age -FilesOnly:$FilesOnly -Recurse:$Recurse -UserCreationTime:$UserCreationTime)

但这很好用: 在第一个函数中进行了简化:

$Files = [System.Collections.ArrayList]@()
$Files.Add($Obj) > $null
$Files.getType()
return $Files

这是顶部函数的外观:

$OldFiles = [System.Collections.ArrayList](Check-OldFiles -Path $Directory -Age $Age -FilesOnly:$FilesOnly -Recurse:$Recurse -UserCreationTime:$UserCreationTime)

为什么会发生这种情况?

PowerShell的版本是5.0

2 个答案:

答案 0 :(得分:2)

澄清,注意(@Bender最大)和解释(@Phragos):

  1. @()不会“包装”其内容。它将其内容转换为Array。如果其内容已经是Array,则它按原样返回。如果它是一个值或单个对象,则将其复制到一个元素Object[]中。如果是其他某种形式的集合(例如ArrayList),则会将每个元素复制到Object[]中。 (注意:“ @()”为空的Object[]。)

  2. 请勿使用,(...)强制...成为Array。输出对象(显示,文件,打印机等)时,自动展开行为可能会引起误解。

    $myCollection = ,(get-process)
    
    除非只有一个进程(例如来自$myCollection的进程),否则

    不会将Array留给get-process -name个进程。相反,$myCollection将是包含整个输出(单个对象或输出对象的Array)的单个元素的Array。格式输出cmdlet(Format-TableFormat-ListFormat-Wide)掩盖了这一事实,该命令检查接收到的对象,如果有Arrays,则将它们解包。 (更多详细信息How PowerShell Formatting and Outputting REALLY works)。

    您说“但是,如果我这样做可以正常工作”:

    $myCollection | foreach { $_.starttime }
    

    我说,是的,您会获得启动时间列表,但但是不是您的想法。在上面的示例中,$myCollection被解包,每个元素都被发送到foreach的管道中。只有一个元素是Array,因此foreach循环了一次,并且$_设置为Array个进程。由于Array是一个集合,但是没有启动时成员,因此PowerShell(V3 +)执行成员枚举,创建一个Array,该{由每个{的元素的启动时成员的值组成{1}}。然后将此$_展开并发送到管道中。

    您说“那,效果是一样的”,我回答,是的,直到您尝试更复杂的方法为止:

    Array

    预期的输出是文件名和长度的列表。实际输出是名称,空白行和文件数的列表。为什么? $myFiles = ,(gci -file *) $myFiles | foreach { $_.name+' '+$_.length } 是包含文件列表的一个元素的$myFiles,因此Array循环一次,其中foreach是此列表。 $_没有 name 成员,因此PowerShell执行成员枚举产生一个Array的名称。接下来,将一个空格的Array添加(连接)到该String。最后,添加Array。由于$_.length确实具有长度成员,因此此值(Array)被添加到名称的Int32Array中。然后将此' '拆开并发送出去。

    摘要,如果您希望某个可能是Array的东西肯定是Array,请不要使用,(...)。始终使用@(...),就是这样。

    但是,在其他情况下,使用((...)是适当的,但仍可能造成混淆。

    解析命令行时,请使用空格分隔令牌而不是参数。因此,以下是等效的:

    Array

    使用前置的,创建一个由一项组成的数组时,这可能使您绊倒。假设您有一个接受1,2,3 1 , 2, 3 作为参数的cmdlet,但只想提供一个本身就是Array的元素。例如,

    Array

    由于解析器实际上忽略了之前的空格(因为是单个字符标记,所以不需要分隔),因此将其解析为:

    new-object collections.arraylist ,(1,2,3)
    New-Object : Cannot convert 'System.Object[]' to the type 'System.String' required by parameter 'ComObject'. Specified method is not supported.
    At line:1 char:12
    + new-object collections.arraylist ,(1,2,3)
    +            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidArgument: (:) [New-Object], ParameterBindingException
    + FullyQualifiedErrorId : CannotConvertArgument,Microsoft.PowerShell.Commands.NewObjectCommand
    

    因此在这种情况下应该是:

     new-object ("collections.arraylist",(1,2,3))
    

    注意:@(1,2,3)无效,因为1,2,3已经是 new-object collections.arraylist (,(1,2,3))

  3. Powershell中的return语句与功能语言不同,它不是产生输出的唯一方法。每个产生除Array 之外的值的表达式都被发送到管道中,即被收集以返回(数组的值被展开并且元素被单独收集)。 return 使函数在生成值后返回(它不会“返回”值)。我假设函数的看不见的部分没有生成任何值。但是,将$ Files.gettype()放入函数将导致生成一个值(void)。因此,该函数总是返回比以前多1个对象。我必须假定未测试0个匹配文件的情况(该功能已经在这种情况下起作用了,对吗?),因为现在我们回到一个对象,因此该功能再次返回“值”(System.RuntimeType) 。我不知道如何使用返回的对象,但是没有提到由System.RuntimeType的第一个元素是ArrayList而不是System.RuntimeType引起的问题。大概,用法并不介意为不存在的属性获取$ null(仅PSCustomObjectSystem.RuntimeType都使用[String] Name和[String] Fullname)和{{1 }}不会引起类型冲突(System.IO.FileInfoAttributes都是System.RuntimeType.Attributes,但是System.IO.FileInfo.Attributes的值不是有效的System.Enum值,或者PowerShell会这样说)

†赋值语句的类型为[Collections.ArrayList].Attributes,而赋值表达式(表达式中的赋值)具有非空类型。因此,

IO.FileInfo.Attributes

答案 1 :(得分:0)

当单个元素沿管道向下传递时,集合会在PowerShell中自动展开。如果您想保证即使只有一个元素,变量也仍然是一个集合,可以使用一些技巧(以下示例使用Get-Process,但是可以使用任何返回集合的集合变量或cmdlet) ):

  1. 将命令包装在数组中(例如$myCollection = @(Get-Process)
  2. 用逗号,(例如$myCollection = , (Get-Process)

@()指定了一个数组,您可以为它提供一个元素列表或使用一个可能返回集合的cmdlet。

, (Get-Process)语法看起来很奇怪,但这与为数组指定元素的硬编码列表的语法相同。请考虑以下内容:

$myArray = 1, 2, 3, 4, 5, "Bender"

这将创建一个包含元素12345"Bender"的数组。不过,在上面的简写中,仅将,作为表达式中的第一个字符表示这是一个数组,请照此处理。由于PowerShell展开集合的性质,在该初始逗号之后返回的所有后续数组/集合都将添加到最终集合中。