PSVersionHashTable is a dictionary and therefore requires index syntax (["<key>"]) to access its entries in C# (whereas PowerShell allows you to alternatively use property syntax (.<key>)).
Therefore, you have two options (using access to the PSVersion entry as an example):
Via a cast to the type that is wrapped by the PSObject-typed elements of the result collection and stored in the latter's .BaseObject property:
var result = powerShell.Invoke();
foreach (var item in result)
{
# Note the required (PSVersionHashTable) cast.
Console.WriteLine(((PSVersionHashTable)item.BaseObject)["PSVersion"]);
}
Via a generic overload of .Invoke(), .Invoke<T>(), which allows you to specify the result type directly:
var result = powerShell.Invoke<PSVersionHashTable>();
foreach (var item in result)
{
# item is now of type PSVersionHashTable
Console.WriteLine(item["PSVersion"]);
}
Note:
In the first snippet, the (PSVersionHashTable) cast is required because the .BaseObject property is object-typed - of necessity, since a PSObject instance is designed to wrap an instance of any type.
An object-typed variable or expression only knows the members of type object (System.Object) itself, not also those of the referenced object's actual type; a cast to the actual type then makes that type's members accessible.
However, you may alternatively use the dynamic keyword in lieu of object in order to defer member resolution until runtime, based on the actual type, which obviates the need for a cast.
In fact, you can even use dynamic with PSObject instances directly (not just via their .BaseObject property), because PSObject implements the IDynamicMetaObjectProvider interface; that is, a PSObject instance accessed via dynamic facilitates access to the members of the wrapped instance's type as if they were members of type PSObject itself. Applied to the case at hand:
var result = powerShell.Invoke();
foreach (dynamic item in result)
{
Console.WriteLine(item["PSVersion"]);
}
While you lose design-time IntelliSense with this approach, it is convenient for processing output collections of heterogeneous types and for accessing the properties of [pscustomobject] instances (whose properties are all dynamic, which makes design-time IntelliSense impossible anyway).
For output collections with elements of the same type (or single-element collections, i.e, in effect single-object outputs), the .Invoke<T>() approach shown above is probably preferable.
That said, there are cases where it's difficult to make types available at design (compile) time, which is a prerequisite for this approach, in which case the dynamic approach is the simplest solution; an example of such a type is Microsoft.PowerShell.Commands.ComputerInfo, as output by the Get-ComputerInfo cmdlet: even though its assembly, Microsoft.PowerShell.Commands.Management, is part of the PowerShell (Core) 7 SDK / stored in the GAC (for Windows PowerShell), the .NET SDK cannot discover the type (for reasons unknown to me).[1]
Here's a complete example that shows the dynamic approach, accessing the .BiosCaption property as an example:
using System;
using System.Management.Automation;
class Program
{
static void Main()
{
using (var powerShell = PowerShell.Create())
{
powerShell.AddCommand("Get-ComputerInfo");
var result = powerShell.Invoke();
foreach (dynamic item in result)
{
Console.WriteLine(item.BiosCaption);
}
}
}
}
[1] In a .NET SDK project targeting Windows PowerShell (the legacy, ships-with-Windows, Windows-only edition of PowerShell whose latest and last version is 5.1), the type can be made discoverable by adding the following element to the *.csproj file: <Reference Include="Microsoft.PowerShell.Commands.Management"><HintPath>{path to Microsoft.PowerShell.Commands.Management.dll in the GAC}</HintPath></Reference></sup>. The specific path can be discovered as follows from a Windows PowerShell session: [Microsoft.PowerShell.Commands.ComputerInfo].Assembly.Location.
This approach, recommended by Santiago Squarzon, also works in an extended form for a project targeting PowerShell (Core) 7, but requires a separate, stand-alone PowerShell 7 installation with the same version, and seemingly only works up to PowerShell 7.5.2, whereas v7.5.3 is the current version as of this writing. See Santiago's Gist for sample code.
As for why the type isn't discoverable by default, he notes: "supposedly it should discover it if using net9.0-windows as target [framework] but no luck."
PSVersionInfo.PSVersionis public in your SDK version, in older ones you'd need reflection unfortunately, e.g.: stackoverflow.com/questions/76645332/…