Search code examples
linqpowershellprojection

How does one Transform a Collection of Objects into a Collection of new Objects of another Type in Powershell?


Supposing I have a method that gets a collection of enums of my enum type ProjectName and a have a collection of Server objects I want to associate with those ProjectNames in a type called Project; how would I do this in a Powershell Select-Object (or some other equivalent of LINQ's Select).

The C# equivalent of what I want to produce in Powershell is this:

var servers = new[]
{
    new Server(/*someOtherProjectsCollection goes here*/),
    new Server(/*someOtherProjectsCollection goes here*/),
    new Server(/*someOtherProjectsCollection goes here*/)
};
var projects = GetProjectNames().Select(projectName => new Project(projectName, servers.Where(server => server.Projects.Any(serverProject => serverProject.Name == projectName))));

But what I have is this:

$servers = [Server]::new(/*someOtherProjectsCollection goes here*/), [Server]::new(/*someOtherProjectsCollection goes here*/), [Server]::new(/*someOtherProjectsCollection goes here*/)

$projects = (GetProjectNames()) | Select-Object {
    $selectedProjectName = $_

    return [Project]::new($_, ($servers | Where-Object { $_.projects.Where({ $_ -eq $selectedProjectName }).Count -gt 0 }))
}

When I try and read $projects back in Powershell LSE (whilst on a breakpoint after this last line), it just returns the code as a string and I can't even cast it to [Project[]]. I think the problem might be with the use of curly braces with Select-Object but I'm not sure how else to create a new Project object within the Select-Object.


Solution

  • You want ForEach-Object instead of Select-Object to return a new [Project] instance for each project name; also, your code can be streamlined:

    $projects = GetProjectNames | ForEach-Object {
        $projectName = $_
        [Project]::new(
          $projectName, 
          $servers.Where({ $_.projects -eq $projectName })
        )
    }
    
    • Select-Object is for creating new custom objects based on select properties from the input objects; by contrast, you're constructing a new, specific type instance from each input object, which must be done in a ForEach-Object call, where you explicitly control the output.

    • $_.projects -eq $projectName as a conditional relies on PowerShell's ability to use -eq with an array as the LHS, in which case filtering is performed, and a filtered subarray is returned; since .Where() interprets the script block's output as a Boolean, an empty subarray is interpreted as $false, whereas one with at least one element is interpreted as $true.

    • Also note that you don't need an explicit return, given PowerShell's implicit output behavior: since the newly constructed [Project] instance isn't assigned to a variable or sent elsewhere, it is automatically returned.