Home > database >  How to initialise a custom object list in Powershell 5.1?
How to initialise a custom object list in Powershell 5.1?

Time:03-01

What I want to achieve:
Create a list of files, show their name and version and sort alphabetically.

My attempt:

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

My Powershell version:

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

My problems and/or questions (first is the major question, second and third are optional):

  • I don't know how to initialise a list of custom objects (I've been looking on the site, but this question only gives the possibility to initialise with an existing value, while I want to initialise to an empty list).
    The error I receive is:

      You cannot call a method on a null-valued expression.
      At line:... char:...
          $FileList = $FileList.Add($C)
          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
            CategoryInfo          : InvalidOperation: (:) [], RuntimeException
            FullyQualifiedErrorId : InvokeMethodOnNull
    
  • Currently I ask for the same thing twice: I have tried Get-Item(*.dll,*.exe), Get-Item(*.dll;*.exe) and Get-Item(*.dll|*.exe) but none of them worked.
    Is it even possible to search for different patterns?

  • Until now I am struggling just to get Write-Output $FileList working. Are the other two commands (Sort-Object -Property "Name", Format-Table) correct?

CodePudding user response:

Why use a class and a List object for that at all? You can just use Select-Object on the files of interest you gather using Get-ChildItem and output the needed properties there:

# 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

To use two different name patterns on Get-ChildItem, you need to also add switch -Recurse, or append \* to the path.
If you do not want recursion, you will need to add a Where-Object clause like:

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

CodePudding user response:

Your code actually works fine, with two small exceptions.

Problem #1: overly restrictive property attributes can cause errors

First off, in your custom class, you should be very sure you want to append this attribute [ValidateNotNullOrEmpty()], as it will throw a non-terminating error when you try to new up an instance of a class if any one of the properties with this attribute are null.

For instance, on my PC, I have a dll or two with empty file.VersionInfo.FileVersion properties. When that happens, PowerShell will fail to create the object, and then also fail trying to add the object to the list because there is no object. Very confusing.

Fix : remove the attribute of ValidateNotNullOrEmpty unless you're 100% positive this info is present.

Next up, the real problem here is that in each of your for-each loops, you're accidentally breaking your FileList object which you correctly setup in your code as it is already.

Problem #2: Small syntax issue is causing your list to be nulled out, causing errors for every op after the first.

The issue happens in this line below, which I will explain.

$FileList = $FileList.Add($C)

The List.Add() method's return type is either a void or an integer (it's only an int when you provide a generic object, if you provide the type specified for the list, in your case being your custom class of File_Information, it provides a VOID.)

Void is the same as null. We can confirm that the output of .Add is the same as $null with the following code.

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

You can see the output for the various ways of calling .Add() in detail by just typing out the method name with no parameters, like this.

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)          

So in effect your for each loops are adding an item to the list, then resaving the variable $FileList to the output of the .Add method, which is a void, or null in other words. You're killing your own list!

We don't need to resave $FileList as we go, since the purpose of a list is to be populated in code, and the only thing we have to do is to call the .Add() method.

Fix: stop killing your list object in your loops by changing these lines in your code.

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

#after
$FileList.Add($C)
  • Related