如何在 Powershell 5.1 中初始化自定义对象列表?

How to initialise a custom object list in Powershell 5.1?

我想达到的目标:
创建文件列表,显示它们的名称和版本并按字母顺序排序。

我的尝试:

class File_Information
{
    [ValidateNotNullOrEmpty()][string]$Name
    [ValidateNotNullOrEmpty()][string]$FileVersion
}

$FileList = New-Object System.Collections.Generic.List[File_Information]

foreach ($file in Get-Item("*.dll")){
  $C = [File_Information]@{
    Name = $file.Name
    FileVersion = $file.VersionInfo.FileVersion
  }
  $FileList = $FileList.Add($C)
}

foreach ($file in Get-Item("*.exe")){
  $C = [File_Information]@{
    Name = $file.Name
    FileVersion = $file.VersionInfo.FileVersion
  }
  $FileList = $FileList.Add($C)
}

Write-Output $FileList | Sort-Object -Property "Name" | Format-Table

我的 Powershell 版本:

Prompt> Get-Host | Select-Object Version
Version       
-------       
5.1.19041.1320

我的问题and/or个问题(第一个是大题,第二个和第三个是可选的):

为什么要为此使用 class 和 List 对象? 您可以只对使用 Get-ChildItem 收集的感兴趣的文件使用 Select-Object 并在那里输出所需的属性:

# capture the resulting objects in a variable $result
$result = Get-ChildItem -Path 'X:\path\to\the\files' -File -Include '*.dll','*.exe' -Recurse |
          Select-Object Name, @{Name = 'FileVersion'; Expression = {$_.VersionInfo.FileVersion}}

# output on screen
$result | Sort-Object Name | Format-Table -AutoSize

要在 Get-ChildItem 上使用两种不同的名称模式,您还需要添加开关 -Recurse,或将 \* 附加到路径。
如果您不想递归,则需要添加一个 Where-Object 子句,例如:

$result = Get-ChildItem -Path 'X:\path with spaces' | Where-Object {$_.Extension -match '\.(dll|exe)'} |
          Select-Object Name, @{Name = 'FileVersion'; Expression = {$_.VersionInfo.FileVersion}}

您的代码实际上工作正常,只有两个小例外。

问题 #1:过度限制 属性 属性会导致错误

首先,在您的自定义 class 中,您应该非常确定要附加此属性 [ValidateNotNullOrEmpty()],因为当您尝试新建时它会抛出 non-terminating 错误如果具有此属性的任何一个属性为空,则创建 class 的实例。

例如,在我的 PC 上,我有一两个 file.VersionInfo.FileVersion 属性为空的 dll。发生这种情况时,PowerShell 将无法创建该对象,然后也无法尝试将该对象添加到列表中,因为没有对象。很混乱。

Fix:删除 ValidateNotNullOrEmpty 的属性,除非您 100% 肯定此信息存在。

接下来,真正的问题是在你的每个for-each循环中,你不小心打破了您的 FileList 您在代码中正确设置的对象,因为它已经存在。

问题 #2:小的语法问题导致您的列表被清空,导致第一个操作之后的每个操作都出错。

问题出现在下面这一行,我会解释。

$FileList = $FileList.Add($C)

List.Add() 方法的 return 类型是 void 或整数(当您提供通用对象时它只是一个 int,如果您提供为列表指定的类型,在您的案例是 File_Information 的自定义 class,它提供了一个 VOID。)

void 与 null 相同。我们可以通过以下代码确认.Add的输出与$null相同。

PS> $FileList.Add($c) -eq $null
True

您只需键入不带参数的方法名称,就可以详细查看调用 .Add() 的各种方式的输出,如下所示。

PS> $FileList.Add

OverloadDefinitions                                                                                                                 
-------------------                                                                                                                 
void Add(File_Information item)       #< the one you're using!                                                                                              
void ICollection[File_Information].Add(File_Information item)                                                                       
int IList.Add(System.Object value)          

因此,实际上您的 for each 循环是将一个项目添加到列表中,然后将变量 $FileList 重新保存到 .Add 方法的输出中,换句话说,它是一个空值,即 null。你正在扼杀自己的名单!

我们不需要重新保存 $FileList,因为列表的目的是在代码中填充,我们唯一要做的就是调用 .Add()方法。

修复:通过更改代码中的这些行,停止在循环中杀死列表对象。

#before
$FileList = $FileList.Add($C)

#after
$FileList.Add($C)