Powershell access XML node with multiple attributes within a forloop - powershell

xml sample
<?xml version="1.0" encoding="UTF-8"?>
<CUSTOMERS xml:lang="en">
<CUSTOMER CREATED_DATE="2020-10-16 13:21:09.0" GROUP_ID="" ID="1509999">
<NAME>
<TITLE></TITLE>
<FIRST_NAME>John</FIRST_NAME>
<LAST_NAME>Smith</LAST_NAME>
</NAME>
<GENDER/>
<DATE_OF_BIRTH/>
<CONTACT_DETAILS>
<TELEPHONE MARKETING_OPT_IN="F" TYPE="MOBILE">0123456789</TELEPHONE>
<EMAIL MARKETING_OPT_IN="F">test#yahoo.com</EMAIL>
</CONTACT_DETAILS>
<ATTRIBUTE NAME="theCompany-News and offers on theCompany Womenswear_OPT_EMAIL">T</ATTRIBUTE>
<ATTRIBUTE NAME="Email Soft Opt In_OPT_EMAIL">F</ATTRIBUTE>
<ATTRIBUTE NAME="REGISTERED_ON_WEBSITE">T</ATTRIBUTE>
</CUSTOMER>
</CUSTOMERS>
I have inherited an Azure Powershell function which builds a list of values from XML file.
I need to add some code to access this value:
<ATTRIBUTE NAME="Email Soft Opt In_OPT_EMAIL">F</ATTRIBUTE>
This is the Powershell Code I have so far
[xml]$xml = Get-Content C:\Users\Jason2\Desktop\XMLfolder\theSample.xml
$xml
$CustomerListFull = #()
foreach ($customer in $xml.CUSTOMERS.CUSTOMER)
{ $BuildList = New-Object -TypeName psobject
$BuildList | Add-Member -MemberType NoteProperty -Name TITLE -Value $customer.NAME.TITLE.Trim()
$BuildList | Add-Member -MemberType NoteProperty -Name FIRSTNAME -Value $customer.NAME.FIRST_NAME.Trim()
$BuildList | Add-Member -MemberType NoteProperty -Name LASTNAME -Value $customer.NAME.LAST_NAME.Trim()
$BuildList | Add-Member -MemberType NoteProperty -Name EMAILCONSENT -Value "0"
$BuildList | Add-Member -MemberType NoteProperty -Name EMAILSOFTOPTIN -Value "2"
if ($customer.ATTRIBUTE.NAME -like "*OPT_EMAIL") {
foreach ($value in $customer.ATTRIBUTE.NAME) {
if ($value -match "theCompany-News and offers on theCompany Womenswear_OPT_EMAIL") {
$BuildList.EMAILCONSENT = "1"
}
if ($value -match "Email Soft Opt In_OPT_EMAIL") {
$BuildList.EMAILSOFTOPTIN = $customer.ATTRIBUTE.'#text'
}
}
}
$CustomerListFull += $BuildList
}
$CustomerListFull
which is incorrectly giving me all three Attribute Name values (into field EMAILSOFTOPTIN)
TITLE :
FIRSTNAME : John
LASTNAME : Smith
EMAILCONSENT : 1
EMAILSOFTOPTIN : {T, F, T}
I have tried several things to try and access the value, user marsze yesterday showed me nodes which is great but I can't seem to make it work in my forloop, I'm failing miserably.
$BuildList.EMAILSOFTOPTIN = $customer.SelectNodes("//*[local-name()='ATTRIBUTE'][#NAME='Email Soft Opt In_OPT_EMAIL']").InnerText
$BuildList.EMAILSOFTOPTIN = $customer.[ATTRIBUTE].[NAME='Email Soft Opt In_OPT_EMAIL').InnerText
$nodes = $xml.SelectNodes("//*[local-name()='ATTRIBUTE'][#NAME='Email Soft Opt In_OPT_EMAIL']")
$BuildList.EMAILSOFTOPTIN = $nodes.InnerText
Please help to put me out of my misery

There are multiple tiny flaws in your code.
1.)
$customer.ATTRIBUTE.NAME -like "*OPT_EMAIL"
If the left operand is a collection, the result is also a collection, namely of all the items that return true for the operation, in this case all values of NAME that match *OPT_EMAIL". Also, this check is unnecessary, because you make another literal comparison later.
2.)
$value -match "Email Soft Opt In_OPT_EMAIL"
-Match is probably not a good choice here, because it treats the right value as regex. It will work in this case, but I think you want -eq
3.)
$customer.ATTRIBUTE.'#text'
This will always return a collection of the inner text of all <ATTRIBUTE> nodes of the current customer, namely {T, F, T}
===
Here is my version, with some additional changes, but that is just a matter of personal taste:
[xml]$xml = Get-Content "C:\Users\Jason2\Desktop\XMLfolder\theSample.xml"
# you can build the array directly, like this
# because the += operator on arrays is terribly slow
$customerListFull = #(foreach ($customer in $xml.Customers.Customer) {
# defaults
$emailConsent = 0 # or $false?
$emailSoftOptIn = "2"
# loop attributes
# the "OPT_EMAIL" check is dispensable here
foreach ($attribute in $customer.Attribute) {
# switch is a good replacement for a lot of if's,
# that basically all do the same check
switch ($attribute.Name) {
"theCompany-News and offers on theCompany Womenswear_OPT_EMAIL" {
$emailConsent = 1 # or $true?
}
"Email Soft Opt In_OPT_EMAIL" {
$emailSoftOptIn = switch ($attribute.InnerText.Trim()) {
"F" { 0 }
"T" { 1 }
}
### OR ###
$emailSoftOptIn = #{F = 0; T = 1}[$attribute.InnerText.Trim()]
}
}
}
# Create and output the object directly.
# This is an easier way to construct an object,
# which also keeps the property order
[PSCustomObject]#{
Title = $customer.Name.Title.Trim()
FirstName = $customer.Name.First_Name.Trim()
LastName = $customer.Name.Last_Name.Trim()
EmailConsent = $emailConsent
EmailSoftOptIn = $emailSoftOptIn
}
### OR ###
# use [ordered]
New-Object -TypeName PSObject -Property ([ordered]#{
Title = $customer.Name.Title.Trim()
FirstName = $customer.Name.First_Name.Trim()
LastName = $customer.Name.Last_Name.Trim()
EmailConsent = $emailConsent
EmailSoftOptIn = $emailSoftOptIn
})
})

Related

Blank records when returning ArrayList of PSObjects from function

So I'm refactoring a Powershell script and moving a lot of stuff into functions. When I return an ArrayList of 42 PSObjects from a function (called Get-OutList) with return $out, 42 blank records are inserted into the beginning of the ArrayList, and my original records then follow.
My function looks like this:
function Get-OutList {
param (
[Parameter(Position=0,mandatory=$true)]
[PSObject]$RoleCollection,
[Parameter(Position=1,mandatory=$true)]
[PSObject]$MemberCollection
)
$out = New-Object System.Collections.ArrayList
$MemberCollection.result | ForEach-Object {
$currentMember = $_
$memberDetail = New-Object PSObject
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name FirstName -Value $($currentMember.user.first_name)
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name LastName -Value $($currentMember.user.last_name)
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name Email -Value $($currentMember.user.email)
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name Status -Value $($currentMember.status)
$RoleCollection.result | ForEach-Object {
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name $_.name -Value (&{If($currentMember.roles.name -contains $_.name){"Y"}Else{""}})
}
$out.Add($memberDetail)
}
return $out
}
I understand Powershell enumerates each record back to where the function was called, and I've tried a few things to no avail:
Behaviour is the same in PS v5.1.x and v7.3.0.
Returning with return #($out) makes no difference (i.e. results in a System.Object with 42 blank records followed by my 42 original records for a total of 84 records).
Returning with return Write-Output -NoEnumerate $out results in a System.Object with 42 blank records and my 42 original records nested in a 43rd record.
Typing the result of the function call as ArrayList with $results = [System.Collections.ArrayList](Get-OutList) makes no difference.
Why can't I get my object to be the same as before it's returned from a function?? Any assistance would be greatly appreciated!
Edit #1
Including an easily-reproducible example:
function Get-OutList {
param (
[Parameter(Position=0,mandatory=$true)]
[PSObject]$MemberCollection
)
$out = New-Object 'System.Collections.ArrayList'
$MemberCollection | ForEach-Object {
$memberDetail = New-Object PSObject
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name FirstName -Value "One"
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name LastName -Value "Two"
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name Email -Value "Three"
Add-Member -InputObject $memberDetail -MemberType NoteProperty -Name Status -Value "Four"
$out.Add($memberDetail)
}
return $out
}
$members = #("Joe Bloggs", "Some Dude", "The Dude")
$memberDetails = Get-OutList -MemberCollection $members
Write-Output $memberDetails
If you add a breakpoint before $out is passed back, you'll see there are three records, and if you keep stepping, you should see $memberDetails will have six records (the first three blank).
Edit #2
Appears there's no such problem when using Generic.List instead of ArrayList. Used $out = [System.Collections.Generic.List[PSObject]]::new() instead of $out = New-Object 'System.Collections.ArrayList' and it's working just fine.
Your problem stems from the fact that the System.Collections.ArrayList.Add() method has a return value (the index at which the object has been added) and that that return value becomes part of your function's "return value", i.e. stream of output objects.
This is a manifestation of PowerShell's implicit output behavior, which is explained in this answer.
Notably, return is not needed in PowerShell to produce output from a function - any statement can produce output. return $foo is simply syntactic sugar for Write-Output $foo; return or, relying on implicit output behavior, $foo; return
Therefore, the immediate fix to your problem is to discard (suppress) the implicit output produced by your $out.Add($memberDetail) call, which is typically done by assigning the call to $null (there are other methods, discussed in this answer):
# Discard the unwanted output from .Add()
$null = $out.Add($memberDetail)
In your own answer, you indirectly applied that fix by switching to the System.Collections.Generic.List`1 list type, whose .Add() method has no return value. It is for that reason, along with the ability to strongly type the list elements, that System.Collections.Generic.List`1 is generally preferable to System.Collections.ArrayList
Taking a step back:
In simple cases such as yours, where the entire function output is to be captured in a list, you can take advantage of the output-stream behavior and simply emit each output object individually from your function (from the ForEach-Object script block) and let PowerShell collect the individual objects emitted in a list-like data structure, which will be a regular PowerShell array, namely of type [object[] (i.e., a fixed-size analog to System.Collections.ArrayList):
This is not only more convenient, but also performs better.
Applied to your sample function:
function Get-OutList {
param (
[Parameter(Position=0,mandatory=$true)]
[PSObject]$MemberCollection
)
$MemberCollection | ForEach-Object {
$first, $last = -split $_
# Construct and implicitly output a custom object.
[pscustomobject] #{
FirstName = $first
LastName = $last
Email = 'Three'
Status = 'Four'
}
}
# No need for `return` - all output objects were emitted above.
}
$members = #("Joe Bloggs", "Some Dude", "The Dude")
# By assigning the function's output stream to a variable,
# the individual output objects are automatically collected in an array
# (assuming *two or more* output objects; if you want an array even if
# only *one* object is output, use [Array] $memberDetails = ...)
$memberDetails = Get-OutList -MemberCollection $members
# Output (implicitly).
$memberDetails
Also note the use of the [pscustomobject] #{ ... } syntactic sugar for simplified custom-object creation - see the conceptual about_Object_Creation help topic.
Used $out = [System.Collections.Generic.List[PSObject]]::new() instead of $out = New-Object 'System.Collections.ArrayList' and it's working just fine. No extra blankies.

Get VID PID from Device ID in Powershell

I am fairly new to PowerShell and I want for a USB key plugged, to retrieve some info. Right now my script is:
Get-WmiObject win32_diskdrive |
ForEach-Object{
$disk = $_
$_.GetRelated('Win32_PnPEntity')|
ForEach-Object{
$pnp = $_
$_.GetRelated('Win32_USBController') |
ForEach-Object{
$usb = $_
[pscustomobject]#{
SerialNumber = $disk.SerialNumber
Model = $disk.Model
Size = $disk.Size
if ($usb.DeviceID -match '.*VID_(?<vid>[0-9A-F]{4})&PID_(?<pid>[0-9A-F]{4}).*') {VID=$matches['vid']; PID=$matches['pid']}
}
}
}
}
The line beginning with
if ($usb.DeviceID -match '.*VID_(?<vid>[0-9A-F]{4})&PID_(?<pid>[0-9A-F]{4}).*') {VID=$matches['vid']; PID=$matches['pid']}
does not work. I want to translate deviceid (which I can get by doing USBDeviceID = $usb.DeviceID) ID in PID UID directly.
It throws the following error
Error with code “Missing = operator after key in hash literal" for the statement "if ($usb.DeviceID -match '.* ...
What am I missing ? many thanks for helping me .
Gerald
This is because the way you intend to add properties to the PsCustomObject is wrong.
Either do this:
$result = [PsCustomObject]#{
SerialNumber = $disk.SerialNumber
Model = $disk.Model
Size = $disk.Size
}
# add items to the object if the condition is true
if ($usb.DeviceID -match '.*VID_(?<vid>[0-9A-F]{4})&PID_(?<pid>[0-9A-F]{4}).*') {
$result | Add-Member -MemberType NoteProperty -Name 'VID' -Value $matches['vid']
$result | Add-Member -MemberType NoteProperty -Name 'PID' -Value $matches['pid']
}
# output the PsCustomObject
$result
or use a Hashtable as temporary storage:
# create a Hastable to temporarily store results in
$hash = [ordered]#{
SerialNumber = $disk.SerialNumber
Model = $disk.Model
Size = $disk.Size
}
# add items to the hash if the condition is true
if ($usb.DeviceID -match '.*VID_(?<vid>[0-9A-F]{4})&PID_(?<pid>[0-9A-F]{4}).*') {
$hash['VID']=$matches['vid']
$hash['PID']=$matches['pid']
}
# next cast to PsCustomObject and output
[PsCustomObject]$hash

Powershell pscustomobject with array properties to Csv file

I got an $Object with a ton of properties which look like this:
IsSynchronized : { False, False }
What I want is to do something like :
$Object | Export-Csv C:\Test\Merge.csv -Delimiter ';'
To get a CSV containing :
IsSynchronized
--------------
False
False
But as expected I get
IsSynchronized
---------------
System.Object[]
Is there a good way to get a ton of the object's properties in a .CSV ?
I have 6 .csv files with values and I try to add them in to one big .csv for further processing.
Edit:
I asked about this Topic yesterday but i need to make more clear what i want.
I create a PSCustomObject and fill it with Arrays:
$Object = New-Object -TypeName PSCustomObject
$Object | add-member -membertype NoteProperty -name "CPUHost" -value $global:CPUHost
$Object | add-member -membertype NoteProperty -name "NumCpu" -value $global:NumCpu
$Object | add-member -membertype NoteProperty -name "MemoryMB" -value $global:MemoryMB
$Object | add-member -membertype NoteProperty -name "CPU Usage (Average), Mhz" -value $global:CPUUsageAverageMhz
$Object | add-member -membertype NoteProperty -name "CPU Usage (Average), %" -value $global:CPUUsageAverage
$Object | add-member -membertype NoteProperty -name "Memory Usage (Average), %" -value $global:MemoryUsageAverage
$Object | add-member -membertype NoteProperty -name "Network Usage (Average), KBps" -value $global:NetworkUsageAverageKBps
$Object | add-member -membertype NoteProperty -name "Disk Usage (Average), KBps" -value $global:DiskUsageAverageKBps
...
All of these Global Variables are Arrays because i never know how many Values i get in the first place.
They are filled by lopping through 6 CSV Files i will allways get.
After running this bit i will have a Object looking like this:
CPUHost : {xxxx}
NumCpu : {20}
MemoryMB : {36094}
CPU Usage (Average), Mhz : {3914,33}
CPU Usage (Average), % : {8,91}
Memory Usage (Average), % : {70,17}
Network Usage (Average), KBps : {439,68}
Disk Usage (Average), KBps : {1994,93}
...
What i want is to Export that in to a CSV Displayed like :
CPUHost NumCPU MemoryMb CPUUsage ...
------- ------ -------- ---------
xxxx 20 36094 3914
33
With every Value in its own Cell.
What i get is instead of the values : System.Type.[] which is technically correct but not what i need.
I allready tryed to -join the values but that will leave me with the values in the same cell
You state that you have one object with multiple properties like what was posted. I assume you have an object that looks like the following:
IsSynchronized Property2 Property3
-------------- --------- ---------
{False, False} {1, 2, 3} {string1, string2, string3, string4}
You could do the following:
$loopmax = $object[0].psobject.properties |% {($_.Value | measure-object).Count} | Sort -desc | Select -First 1
$newobject = for ($i = 0; $i -lt $loopmax; $i++) {
$hash = [ordered]#{}
foreach ($p in $object[0].psobject.properties.name) {
$hash[$p] = $object.$p[$i]
}
[pscustomobject]$hash
}
$newobject | convertto-csv -notype
This doesn't seem wise to do. CSV isn't very good for representing or storing rich or hierarchical objects. The problem is even worse when the property is an object, in your case it's a flat array. You can output as you suggest / request, and other answers have demonstrated:
IsSynchronized
--------------
False
False
However, this disrupts the property's relationship to the other properties. If you have other types like [String] & [Int], or even varying numbers of elements in array typed properties things are going to get weird quickly!
If you must stick with CSV you can sub-delimit the field. A great example of this is Exchange Message Tracking logs. They are CSV files delimited on the typical ",", but the recipients field is sub-delimited on a ";".
An example in code might look something like this:
$Object =
[PSCustomObject]#{
Prop1 = "one"
Prop2 = "two"
Arr1 = #( 1,2,3,4 )
}
$Object |
Select-Object Prop1, Prop2,
#{ Name = 'Arr1'; Expression = {$_.Arr1 -join "," } } |
ConvertTo-Csv -Delimiter ";"
Results:
"Prop1";"Prop2";"Arr1"
"one";"two";"1,2,3,4"
Note: To use full fidelity data in a later process would require appropriate handling on input. However, if you follow other solutions and said weirdness occurs you'll be left with a similar issue; having to handle on the input side everywhere you intend to use that data.
Given CSV's shortcomings JSON may be a better choice to store & reuse full fidelity objects. Export/Import CliXML are interesting for this.
To answer the literal question asked (although I'm not sure that's what the OP actually wants)...
If you have a single object with a property that contains an array of values, you can expand them out into a new array and then convert that to csv as follows:
$obj = new-object psobject -Property #{
"IsSynchronized" = #( $false, $false )
}
$obj
# IsSynchronized
# --------------
# {False, False}
$data = $obj.IsSynchronized | foreach-object {
new-object pscustomobject -Property #{ "IsSynchronized" = $_ }
}
$csv = $data | ConvertTo-Csv -NoTypeInformation
$csv
# "IsSynchronized"
# "False"
# "False"

Output of installed programs with CSV-Export

I've several problems with the output and export of my current project. I'm using the cmdlet Get-RemoteProgram to get the installed Software via Network and registry entries.
This is my code for now:
function Get-RemoteProgram ....
$computername = Import-Csv "C:\data\test\test.csv" |
select -ExpandProperty PCName
$regex = #("Program1|Program2|Program3")
$items = #()
foreach ($computer in $computername) {
if (Test-Connection $computer -ErrorAction SilentlyContinue -Count 1) {
$query = Get-RemoteProgram -ComputerName $computer -Property DisplayVersion |
where {$_.ProgramName -match $regex}
$obj = New-Object -Type PSObject
$obj | Add-Member -Name ("Computername") -Value $computer -MemberType NoteProperty -Force
$maxcount = $query.ProgramName.Count
if ($maxcount -gt 1) {
for ($i=0; $i -lt $maxcount; $i++) {
$progandversion = $query.ProgramName[$i] + $query.DisplayVersion[$i]
$obj | Add-Member -Name ($progandversion) -Value "Available" -MemberType NoteProperty -Force
}
} elseif ($maxcount -eq 1) {
$progandversion = $query.ProgramName + $query.DisplayVersion
$obj | Add-Member -Name ($progandversion) -Value "Available" -MemberType NoteProperty -Force
}
$obj | Add-Member -Name ("ProgrammVersion$i") -Value $query.DisplayVersion[$i] -MemberType NoteProperty -Force
$items += $obj
}
$items | Export-Csv c:\daten\inventur\output_final.csv -Append -Force
Write-Host "$computer has been checked.."
}
The problem I now have is that my script does not list all different programs that I am looking for. It should export the computername and afterwards - in the same line - put out an available if the software is installed or keep it clean if the program was not found.
That's the output I get right now:
#TYPE System.Management.Automation.PSCustomObject
Computername,"Program1","Program2"
Computer1,"Available","Available"
Computer1,"Available","Available"
Computer2,,
Computer1,"Available","Available"
Computer3,,
Computer2,,
Computer1,"Available","Available"
I don't know why the computers are multiple times in the output.
I would like to have it like this:
Computername,Program1,Program2,Program3,Program4
Computer1,Available,,Available,,
Computer2,Available,,,,
Computer3,,,Available,
Computer4,,,,
Can you help me somehow?
Your problem is two-fold. First, you want to update existing data in a CSV, but instead you use -Append when you run Export-CSV. This explains while more than one row with a given ComputerName exists. And second, you are not setting default values for a given ProgramName, and thus no properties for programs that are not found anywhere exists in the output CSV. To resolve your first problem, you need to run Export-CSV without appending to save your entire data set into your CSV file. And to resolve your second problem, you should pre-fill your new PSObjects with properties. Preparation should be done like this:
$programs=get-content "programs.txt" # one name one line, or an array of names in #()
$regex='('+($programs -join ',')+')' # regex out of array
Then in your main cycle you add this line after call to New-Object:
$programs | % { $obj | Add-Member -Name $_ -Value "Not found" -MemberType NoteProperty } # create default values
Should do. Swap "Not found" for an empty string if you desire.
The encoded version in c# installed programs via windows registry
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
namespace SoftwareInventory
{
class Program
{
static void Main(string[] args)
{
//!!!!! Must be launched with a domain administrator user!!!!!
Console.ForegroundColor = ConsoleColor.Green;
StringBuilder sbOutFile = new StringBuilder();
Console.WriteLine("DisplayName;IdentifyingNumber");
sbOutFile.AppendLine("Machine;DisplayName;Version");
//Retrieve machine name from the file :File_In/collectionMachines.txt
//string[] lines = new string[] { "NameMachine" };
string[] lines = File.ReadAllLines(#"File_In/collectionMachines.txt");
foreach (var machine in lines)
{
//Retrieve the list of installed programs for each extrapolated machine name
var registry_key = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (Microsoft.Win32.RegistryKey key = RegistryKey.OpenRemoteBaseKey(RegistryHive.LocalMachine, machine).OpenSubKey(registry_key))
{
foreach (string subkey_name in key.GetSubKeyNames())
{
using (RegistryKey subkey = key.OpenSubKey(subkey_name))
{
//Console.WriteLine(subkey.GetValue("DisplayName"));
//Console.WriteLine(subkey.GetValue("IdentifyingNumber"));
if (subkey.GetValue("DisplayName") != null)
{
Console.WriteLine(string.Format("{0};{1};{2}", machine, subkey.GetValue("DisplayName"), subkey.GetValue("Version")));
sbOutFile.AppendLine(string.Format("{0};{1};{2}", machine, subkey.GetValue("DisplayName"), subkey.GetValue("Version")));
}
}
}
}
}
//CSV file creation
var fileOutName = string.Format(#"File_Out\{0}_{1}.csv", "Software_Inventory", DateTime.Now.ToString("yyyy_MM_dd_HH_mmssfff"));
using (var file = new System.IO.StreamWriter(fileOutName))
{
file.WriteLine(sbOutFile.ToString());
}
//Press enter to continue
Console.WriteLine("Press enter to continue !");
Console.ReadLine();
}
}
}

How to get powershell object properties in the same order that format-list does?

I'm writing some reporting scripts in Powershell and collecting up a summary table of items as a blank object with additional properties added one by one:
$cmClusters = #()
foreach ($Cluster in Clusters) {
$cmCluster = New-Object System.Object
$cmCluster | Add-Member -type NoteProperty -Name VC -Value $strVC
$cmCluster | Add-Member -type NoteProperty -Name Name -Value $Cluster.name
# etc...
$cmClusters += $cmCluster;
}
If I just dump $cmClusters at the end of this, I get a format-list output with the properties in the order that I added them.
However, I was hoping to write a generic "dump this collection of objects to an excel tab" function to produce my report, which will be several similar worksheet tabs from different lists of objects.
That looks like this:
function DumpToExcel($workbook, $tabTitle, $list)
{
$sheet = $workbook.worksheets.add()
$sheet.Name = $tabTitle
$col = 1
$row = 1
$fields = $list[0] | Get-Member -MemberType NoteProperty | Select-Object *
Foreach ($field in $fields) {
$sheet.cells.item($row,$col++) = $field.Name
}
$heading = $sheet.UsedRange
$heading.Font.Bold = $True
$row++
Foreach ($cmCluster in $list) {
$col=1
Foreach ($field in $fields) {
$sheet.cells.item($row,$col++) = $cmCluster.($field.Name)
}
$row++
}
$sheet.UsedRange.EntireColumn.AutoFit() | Out-Null
}
which works, but the property names are now in alphabetical order.
What can I use to get my list of properties in the same order that Format-List does?
Try this:
$fields = $list[0].psobject.properties | select name