Filter results further based on pattern - powershell

Using PowerShell, we are able to successfully extract lines we need from doc.
Code:
Get-Content "C:\Contract.doc" |
Select-String -Pattern "in relation to any Facility" |
Select -Property #{Name = 'Name'; Expression = {$_.Line}}
Output:
Name
----
in relation to any Facility A Loan [2% ] per cent. per annum;
in relation to any Facility B Loan [ 5% ] per cent. per annum;
What we are looking for is extract 2% ... 5% from the above output
Code, we are trying is not working for us:
Get-Content "C:\Contract.doc" |
Select-String -Pattern "in relation to any Facility" |
Select -Property #{Name = 'Name'; Expression = {$_.Line}} |
Select-String '\[\?+([^?]+)\?+\]' |
ForEach-Object { $_.Matches.Groups[1].Value }
Can anyone please help how to extract like below:
"in relation to any Facility A Loan [2% ] per cent. per annum", "2%"
Part of Word Doc: Contract doc
"Margin" means:
(a) in relation to any Facility A Loan [2% ] per cent. per annum;
(b) in relation to any Facility B Loan [ 5% ] per cent. per annum;
(c) [in relation to any Incremental Facility Loan, the percentage rate per annum specified as such in the Incremental Facility Notice relating to the Incremental Facility under which that Incremental Facility Loan is made or is to be made;]

Check on the next snippet.
Get-Content C:\Contract.doc |
Select-String -Pattern #'
\b(in relation to any Facility [A-Z] Loan \[\s*(\d+%)\s*\] per cent. per annum);
'# |
Select-Object #{Name = 'Line'; Expression = {$_.Matches.Groups[1].Value}},
#{Name = 'Result'; Expression = {$_.Matches.Groups[2].Value}}
To get an explanation of the regex \b(in relation to any Facility [A-Z] Loan \[\s*(\d+%)\s*\] per cent. per annum);, please click here.

With a Regular Expression the solution is more efficient:
Get-Content .\contract.doc|
Where-Object {$_ -match 'in relation to any Facility.*\[([\d% ]+)\]'}|
ForEach-Object{
[PSCustomObject]#{
Name = $_
Value = $Matches[1].trim()
}
}
And I really should scroll down before posting a similar answer.

Try this:
EDIT:
$FinalTable = Get-Content .\Contract.doc |
select-string -pattern "in relation to any Facility" |
Select -Property #{Name = 'Name'; Expression = {$_.Line}} |
ForEach-Object {
$str = $_.Name
$start = $str.indexOf("[") + 1
$end = $str.indexOf("]", $start)
$length = $end - $start
$result = ($str.substring($start, $length)).trim()
#Creating a custom object to display in table format
$Obj = New-Object -TypeName PSCustomObject
Add-Member -InputObject $Obj -MemberType NoteProperty -Name Name -Value $str
Add-Member -InputObject $Obj -MemberType NoteProperty -Name Value -Value $result
$obj
}
$FinalTable

Related

How to summarize value rows of one column with reference to another column in PowerShell object

I'm learning to work with the import-excel module and have successfully imported the data from a sample.xlsx file. I need to extract out the total amount based on the values of another column values. Basically, I want to just create a grouped data view where I can store the sum of values next to each type. Here's the sample data view.
Type Amount
level 1 $1.00
level 1 $2.00
level 2 $3.00
level 3 $4.00
level 3 $5.00
Now to import I'm just using the simple code
$fileName = "C:\SampleData.xlsx"
$data = Import-Excel -Path $fileName
#extracting distinct type values
$distinctTypes = $importedExcelRows | Select-Object -ExpandProperty "Type" -Unique
#looping through distinct types and storing it in the output
$output = foreach ($type in $distinctTypes)
{
$data | Group-Object $type | %{
New-Object psobject -Property #{
Type = $_.Name
Amt = ($_.Group | Measure-Object 'Amount' -Sum).Sum
}
}
}
$output
The output I'm looking for looks somewhat like:
Type Amount
level 1 $3.00
level 2 $3.00
level 3 $9.00
However, I'm getting nothing in the output. It's $null I think. Any help is appreciated I think I'm missing something in the looping.
You're halfway there by using Group-Object for this scenario, kudos on that part. Luckily, you can group by the type at your import and then measure the sum:
$fileName = "C:\SampleData.xlsx"
Import-Excel -Path $fileName | Group-Object -Property Type | % {
$group = $_.Group | % {
$_.Amount = $_.Amount -replace '[^0-9.]'
$_
} | Measure-Object -Property Amount -Sum
[pscustomobject]#{
Type = $_.Name
Amount = "{0:C2}" -f $group.Sum
}
}
Since you can't measure the amount in currency format, you can remove the dollar sign with some regex of [^0-9.], removing everything that is not a number, or ., or you could use ^\$ instead as well. This allows for the measurement of the amount and you can just format the amount back to currency format using the string format operator '{0:C2} -f ....
I don't know what your issue is but when the dollar signs are not part of the data you pull from the Excel sheet it should work as expected ...
$InputCsvData = #'
Type,Amount
level 1,1.00
level 1,2.00
level 2,3.00
level 3,4.00
level 3,5.00
'# |
ConvertFrom-Csv
$InputCsvData |
Group-Object -Property Type |
ForEach-Object {
[PSCustomObject]#{
Type = $_.Name
Amt = '${0:n2}'-f ($_.Group | Measure-Object -Property Amount -Sum).Sum
}
}
The ouptut looks like this:
Type Amt
---- ---
level 1 $3,00
level 2 $3,00
level 3 $9,00
Otherwise you may remove the dollar signs before you try to summarize the numbers.

PowerShell ForEach-Object Only Matching first Value passed

I am writing a PowerShell script that reads a CSV file in the following format:
A,B,ProgrammeSeller,D,E,F,G,H,I,J,K,L,M,N,O,P,SellerCode,Date,Seller,Currency1,InvoiceAmmount,DiscountAmount,PurchasePrice,TotalsCurrency,TotalInvoiceAmmount,TotalDiscountAmmount,TotalPurchasePrice,Reviewed,AC,Signed
,,PROGRAMME,,,,,,,,,,,,,,380,09/12/2021,SELLER_BE,EUR,2813828.46,17594.22,2796234.24,EUR,0,0,0,Reviewed by:,,Signed:
,,PROGRAMME,,,,,,,,,,,,,,383,09/12/2021,SELLER_DE,EUR,3287812.58,17595.8,3270216.78,EUR,0,0,0,Reviewed by:,,Signed:
,,PROGRAMME,,,,,,,,,,,,,,383,09/12/2021,SELLER_DE,USD,1520725.4,11428.98,1509296.42,USD,0,0,0,Reviewed by:,,Signed:
,,PROGRAMME,,,,,,,,,,,,,,381,09/12/2021,SELLER_DK,DKK,6047281.25,26163.13,6021118.12,DKK,0,0,0,Reviewed by:,,Signed:
,,PROGRAMME,,,,,,,,,,,,,,381,09/12/2021,SELLER_DK,EUR,11376479.39,39580.28,11336899.11,EUR,0,0,0,Reviewed by:,,Signed:
,,PROGRAMME,,,,,,,,,,,,,,381,09/12/2021,SELLER_DK,USD,12571895.13,71198.51,12500696.62,USD,0,0,0,Reviewed by:,,Signed:
And I want to group and sum 3 of the columns (InvoiceAmmount,DiscountAmount,PurchasePrice), and update the columns (TotalInvoiceAmmount,TotalDiscountAmmount,TotalPurchasePrice) on a new file which is a copy of the original but with the columns (TotalInvoiceAmmount,TotalDiscountAmmount,TotalPurchasePrice) updated with the values of the sums whenever there is a match on Currency value.
I have written the following script:
$csvFile = Import-Csv "C:\OutputFiles\OTC_Purchase_Report_EUProgramme_20220420_TMP.csv"
$csvFinal = "C:\OutputFiles\OTC_Purchase_Report_EUProgramme_20220420.csv"
function WriteTotalsToCSV
{
$totals | ForEach-Object {
$totalCurrency = $_.Currency
$totalInvoiceAmmount = $_.TotalInvoiceAmmount
$totalDiscountAmmount = $_.TotalDiscountAmmount
$totalPurchasePrice = $_.TotalPurchasePrice
$csvFile | ForEach-Object {
if($_.Currency1 -eq $totalCurrency){
$_.TotalsCurrency = $totalCurrency
$_.TotalInvoiceAmmount = $totalInvoiceAmmount
$_.TotalDiscountAmmount = $totalDiscountAmmount
$_.TotalPurchasePrice = $totalPurchasePrice
}
$_ | Export-Csv $csvFinal -NoTypeInformation -Append
}
}
}
function InvoiceAmmountTotals
{
param ($file)
$headers = ($file | Get-Member -MemberType NoteProperty).Name
$totals = $file | Select-Object $headers | Group-Object Currency1
foreach ($total in $totals)
{
$showtotal = New-Object System.Management.Automation.PsObject
$showtotal | Add-Member NoteProperty Currency $total.Name
$showtotal | Add-Member NoteProperty TotalInvoiceAmmount ($total.Group | Measure-Object -Sum InvoiceAmmount).Sum
$showtotal | Add-Member NoteProperty TotalDiscountAmmount ($total.Group | Measure-Object -Sum DiscountAmount).Sum
$showtotal | Add-Member NoteProperty TotalPurchasePrice ($total.Group | Measure-Object -Sum PurchasePrice).Sum
$showtotal
}
}
#execution
$totals = InvoiceAmmountTotals $csvFile
WriteTotalsToCSV
The script is grouping and summing the totals as expected but when it writes the new CSV file it is supposed to update the columns based on a match of column Currency1, but it is only doing this for the first match (in this case EUR) and ignoring the remaining matches i.e.:
"A","B","ProgrammeSeller","D","E","F","G","H","I","J","K","L","M","N","O","P","SellerCode","Date","Seller","Currency1","InvoiceAmmount","DiscountAmount","PurchasePrice","TotalsCurrency","TotalInvoiceAmmount","TotalDiscountAmmount","TotalPurchasePrice","Reviewed","AC","Signed"
"","","PROGRAMME","","","","","","","","","","","","","","380","09/12/2021","SELLER_BE","EUR","2813828.46","17594.22","2796234.24","EUR","153995434.65","797318.07","153198116.58","Reviewed by:","","Signed:"
"","","PROGRAMME","","","","","","","","","","","","","","383","09/12/2021","SELLER_DE","EUR","3287812.58","17595.8","3270216.78","EUR","153995434.65","797318.07","153198116.58","Reviewed by:","","Signed:"
"","","PROGRAMME","","","","","","","","","","","","","","383","09/12/2021","SELLER_DE","USD","1520725.4","11428.98","1509296.42","USD","0","0","0","Reviewed by:","","Signed:"
"","","PROGRAMME","","","","","","","","","","","","","","381","09/12/2021","SELLER_DK","DKK","6047281.25","26163.13","6021118.12","DKK","0","0","0","Reviewed by:","","Signed:"
"","","PROGRAMME","","","","","","","","","","","","","","381","09/12/2021","SELLER_DK","EUR","11376479.39","39580.28","11336899.11","EUR","153995434.65","797318.07","153198116.58","Reviewed by:","","Signed:"
"","","PROGRAMME","","","","","","","","","","","","","","381","09/12/2021","SELLER_DK","USD","12571895.13","71198.51","12500696.62","USD","0","0","0","Reviewed by:","","Signed:"
Note when column Currency1 value is USD the columns (TotalInvoiceAmmount,TotalDiscountAmmount,TotalPurchasePrice) are not being updated.
Any suggestions on where I am going wrong here?
Note: The CSV files are much larger, I have added a small number of entries for example purpose
Thanks
It looks like you are looping through each line of the csv as many times as you have currencies because you are looping through $totals and inside of each of those loops you are looping through each line of the csv causing duplicates.
Loop only once through the csv lines and for each line find the matching currency in your $totals array to update each csv line with. Finally output all at once to Export-Csv
function WriteTotalsToCSV {
# only one loop through csv lines
$csvFile | ForEach-Object {
$line = $_
# find matching currency in your $totals array using Where()
$totalsMatch = $totals.Where({ $line.Currency1 -eq $_.Currency })
$line.TotalsCurrency = $totalsMatch.Currency
$line.TotalInvoiceAmmount = $totalsMatch.TotalInvoiceAmmount
$line.TotalDiscountAmmount = $totalsMatch.TotalDiscountAmmount
$line.TotalPurchasePrice = $totalsMatch.TotalPurchasePrice
$line
# collect all the lines first and then export to csv once at the end
} | Export-Csv -Path $csvFinal -NoTypeInformation
}

Select Fields From Duplicate Records for an Array

I've got a CSV file that is imported that looks like this:
Customer ID Contract Start Contract End Region Customer
2-213456 2/20/2018 1/1/2030 NA Acme
2-213456 6/18/2019 6/17/2020 NA Acme
2-213456 6/18/2020 6/30/2021 NA Acme
3-213458 6/27/2019 6/26/2020 CAN Acme Shipping
2-123456 6/27/2020 6/27/2021 AUS Acme Manufacturing
5-123576 6/29/2019 6/28/2020 AUS Acme Storage
Which I'm trying to build an array that only has the unique values (Customer ID) but, would like to include the earliest Contract Start date and the latest Contract End date to get a result like:
Customer ID Contract Start Contract End Region Customer
2-213456 2/20/2018 6/30/2021 NA Acme
3-213458 6/27/2019 6/26/2020 CAN Acme Shipping
2-123456 6/27/2020 6/27/2021 AUS Acme Manufacturing
5-123576 6/29/2019 6/28/2020 AUS Acme Storage
This is what I have but, I keep getting a System.Object[] for the dates
$Data = import-csv -path "C:\Customers.csv"
$Final = #()
$N = 0
$count = $Data.count
foreach ($record in $Data)
{
Write-Host "Record " $N " of " $Count
$Rec = New-Object System.Object
$Rec | Add-Member -type NoteProperty -name "Customer ID" -value $record.'Customer ID'
$StartDate = $Data | Foreach-Object {$_.'Contract Start' = [DateTime]$_.'Contract Start'; $_} | Group-Object 'Customer ID' | Foreach-Object {$_.Group | Sort-Object 'Contract Start' | Select-Object -Property $record.'Contract Start' -first 1}
$Rec | Add-Member -type NoteProperty -name "Contract Start" -value $StartDate
$EndDate = $Data | Foreach-Object {$_.'Contract End' = [DateTime]$_.'Contract End'; $_} | Group-Object 'Customer ID' | Foreach-Object {$_.Group | Sort-Object 'Contract End' | Select-Object -Property $record.'Contract End' -Last 1}
$Rec | Add-Member -type NoteProperty -name "Contract End" -value $EndDate
$Rec | Add-Member -type NoteProperty -name "Region" -value $record.'Region'
$Rec | Add-Member -type NoteProperty -name "Customer" -value $record.'Customer'
$Final += $Rec
$N++
}
I got a lot of errors about Datetime trying to replicate what you have posted above. You've tried to do a lot in one place when setting and sorting the start and end dates, so our first task is to simplify that. Knowing that you could potentially have a lot of customer data, I thought it best to group the customers by their ID in a hashtable. That way we can call the customer ID and immediately just have their records. PowerShell classes allow us to create a couple of methods to import the data in to the hashtable, parse the dates as part of the import. The final method exports your data picking the earliest start date, and the latest end date. Fully tested solution below.
class Customers {
[hashtable]$Accounts
# Constructor
Customers() {
$this.Accounts = #{}
}
# Methods
[void]AddCustomerData([psobject[]]$Records) {
foreach ($Record in $Records) {
# Convert the dates to datetime objects so we can sort them later
$Record = $this.ParseDates($Record)
$ID = $Record."Customer ID"
# If the hashtable already contains the customer ID, we need to add the new record to their existing ones.
if ($this.Accounts.Contains($ID)) {
$CustomerRecords = $this.Accounts[$ID]
$CustomerRecords += $Record
$this.Accounts[$ID] = $CustomerRecords
}
# If it doesn't we create a new record with the value as an array.
else {
$this.Accounts[$ID] = #(,$Record)
}
}
}
[psobject]ParseDates([psobject]$Row) {
# Your dates appear to be US format, so I've kept them that way, change the culture from 'en-US' if you need to.
$Row."Contract Start" = [Datetime]::Parse($Row."Contract Start",[cultureinfo]::new("en-US",$false))
$Row."Contract End" = [Datetime]::Parse($Row."Contract End",[cultureinfo]::new("en-US",$false))
return $Row
}
[psobject[]]PrintCustomerData() {
$CustomerData = #()
# Loop through the hashtable
$this.Accounts.GetEnumerator() | ForEach-Object {
$Contracts = $_.Value
# Find the earliest start date for the current customer by sorting in ascending order
$StartDate = $Contracts."Contract Start" | Sort-Object | Select-Object -First 1
# Find the latest end date for the current customer by sorting in descending order
$EndDate = $Contracts."Contract End" | Sort-Object -Descending | Select-Object -First 1
# Create a new PSObject for each customer, selecting a Unique value for Region and Customer as it should be the same across records
$CustomerData += [PSCustomObject] #{
"Customer ID" = $_.Key
"Contract Start" = $StartDate
"Contract End" = $EndDate
Region = $($Contracts | Select-Object -Unique -ExpandProperty Region)
Customer = $($Contracts | Select-Object -Unique -ExpandProperty Customer)
}
}
return $CustomerData
}
}
Usage:
$csv = Import-Csv -Path .\Desktop\test.csv
# Create a new instance of the class
$customers = [Customers]::new()
# Add the CSV data to a the Accounts hashtable
$customers.AddCustomerData($csv)
# Print out the data from the hashtable in the desired format.
$customers.PrintCustomerData() | Format-Table -AutoSize
Customer ID Contract Start Contract End Region Customer
----------- -------------- ------------ ------ --------
2-213456 20/02/2018 00:00:00 01/01/2030 00:00:00 NA Acme
2-123456 27/06/2020 00:00:00 27/06/2021 00:00:00 AUS Acme Manufacturing
3-213458 27/06/2019 00:00:00 26/06/2020 00:00:00 CAN Acme Shipping
5-123576 29/06/2019 00:00:00 28/06/2020 00:00:00 AUS Acme Storage
And now you have your records in a hashtable, you can do other awesome stuff like look up the records for a particular customer.
$customers.Accounts['2-213456'] | Format-Table -AutoSize
Customer ID Contract Start Contract End Region Customer
----------- -------------- ------------ ------ --------
2-213456 20/02/2018 00:00:00 01/01/2030 00:00:00 NA Acme
2-213456 18/06/2019 00:00:00 17/06/2020 00:00:00 NA Acme
2-213456 18/06/2020 00:00:00 30/06/2021 00:00:00 NA Acme
Using this data.csv as an example input:
Customer ID,Contract Start,Contract End,Region,Customer
2-213456,2/20/2018,1/1/2030,NA,Acme
2-213456,6/18/2019,6/17/2020,NA,Acme
2-213456,6/18/2020,6/30/2021,NA,Acme
3-213458,6/27/2019,6/26/2020,CAN,Acme Shipping
2-123456,6/27/2020,6/27/2021,AUS,Acme Manufacturing
5-123576,6/29/2019,6/28/2020,AUS,Acme Storage
We can use Group-Object to group by Customer ID and use Sort-Object to sort by datetime versions of Contract Start and Contract End. Then we can construct a new System.Management.Automation.PSCustomObject for each compressed record, and format the System.Object[] array with Format-Table.
$array = Import-Csv -Path .\data.csv | Group-Object -Property "Customer ID" | ForEach-Object {
$contractStart = $_.Group |
Sort-Object -Property #{Expression = {[datetime]$_."Contract Start"}} |
Select-Object -First 1
$contractEnd = $_.Group |
Sort-Object -Property #{Expression = {[datetime]$_."Contract End"}} |
Select-Object -Last 1
[PSCustomObject]#{
"Customer ID" = $_.Name
"Contract Start" = $contractStart."Contract Start"
"Contract End" = $contractEnd."Contract End"
"Region" = $contractStart.Region
"Customer" = $contractStart.Customer
}
}
$array.GetType().FullName
$array | Format-Table -AutoSize
Which results in the following table result:
System.Object[]
Customer ID Contract Start Contract End Region Customer
----------- -------------- ------------ ------ --------
2-123456 6/27/2020 6/27/2021 AUS Acme Manufacturing
2-213456 2/20/2018 1/1/2030 NA Acme
3-213458 6/27/2019 6/26/2020 CAN Acme Shipping
5-123576 6/29/2019 6/28/2020 AUS Acme Storage

How do I join two lines in PowerShell

I'm trying to get Information about our VMs in Hyper-V via PowerShell.
This is what I got so far:
$Path = 'c:/VM.csv'
"Name,CPUs,Dynamischer Arbeitsspeicher,RAM Maximum (in MB),RAM Minimum (in MB), Size" > $Path
$line1 = Get-VM | %{($_.Name, $_.ProcessorCount, $_.DynamicMemoryEnabled, ($_.MemoryMaximum/1MB), ($_.MemoryMinimum/1MB)) -join ","}
$line2 = Get-VM –VMName * | Select-Object VMId | Get-VHD | %{$_.FileSize/1GB -join ","}
$out = $line1+","+ $line2
Write-Output $out | Out-File $Path -Append
Import-Csv -Path $Path | Out-GridView
The Problem is that the second object ($line2) should be in the same column as $line1. As you can see, currently the information about the size of the VMs ($line2) is written in rows under the output of $line1. Also the order is wrong.
Any idea what is wrong with my code?
Thanks in advance.
I think you messed it up a little,
Export-CSV will do the job without the need to manually define the csv structure.
Anyway, regarding your code I think you can improve it a little, (I don't have hyper-v to test this, but I think it should work)
What I've done is create a results array to hold the final data, then using foreach loop i'm iterating the Get-VM Results and creating a row for each VM, at the end of each iteration I'm adding the row to the final results array, so:
$Results = #()
foreach ($VM in (Get-VM))
{
$Row = "" | Select Name,CPUs,'Dynamischer Arbeitsspeicher','RAM Maximum (inMB)','RAM Minimum (in MB)',Size
$Row.Name = $VM.Name
$Row.CPUs = $VM.ProcessorCount
$Row.'Dynamischer Arbeitsspeicher' = $VM.DynamicMemoryEnabled
$Row.'RAM Maximum (inMB)' = $VM.MemoryMaximum/1MB
$Row.'RAM Minimum (in MB)' = $VM.MemoryMinimum/1MB
$Total=0; ($VM.VMId | Get-VHD | %{$Total += ($_.FileSize/1GB)})
$Row.Size = [math]::Round($Total)
$Results += $Row
}
$Results | Export-Csv c:\vm.csv -NoTypeInformation
This is definitely not the proper way to build an object list, instead you should do something like this:
Get-VM | Select Name,
#{Name = "CPUs"; Expression = {$_.ProcessorCount},
#{Name = "Dynamischer Arbeitsspeicher"; Expression = {$_.DynamicMemoryEnabled},
#{Name = "RAM Maximum (in MB)"; Expression = {$_.MemoryMaximum/1MB},
#{Name = "RAM Minimum (in MB)"; Expression = {$_.MemoryMinimum/1MB},
#{Name = "Size"; Expression = {$_.VMId | Get-VHD | %{$_.FileSize/1GB -join ","}} |
Export-Csv c:\vm.csv -NoTypeInformation

How do you export objects with a varying amount of properties?

Warning - I've asked a similar question in the past but this is slightly different.
tl;dr; I want to export objects which have a varying number of properties. eg; object 1 may have 3 IP address and 2 NICs but object 2 has 7 IP addresses and 4 NICs (but not limited to this amount - it could be N properties).
I can happily capture and build objects that contain all the information I require. If I simply output my array to the console each object is shown with all its properties. If I want to out-file or export-csv I start hitting a problem surrounding the headings.
Previously JPBlanc recommended sorting the objects based on the amount of properties - ie, the object with the most properties would come first and hence the headings for the most amount of properties would be output.
Say I have built an object of servers which has varying properties based on IP addresses and NIC cards. For example;
ServerName: Mordor
IP1: 10.0.0.1
IP2: 10.0.0.2
NIC1: VMXNET
NIC2: Broadcom
ServerName: Rivendell
IP1: 10.1.1.1
IP2: 10.1.1.2
IP3: 10.1.1.3
IP4: 10.1.1.4
NIC1: VMXNET
Initially, if you were to export-csv an array of these objects the headers would be built upon the first object (aka, you would only get ServerName, IP1, IP2, NIC1 and NIC2) meaning for the second object you would lose any subsequent IPs (eg IP3 and IP4). To correct this, before an export I sort based on the number of IP properties - tada - the first object now has the most IPs in the array and hence none of the subsequent objects IPs are lost.
The downside is when you then have a second varying property - eg NICs. Once my sort is complete based on IP we then have the headings ServerName, IP1 - IP4 and NIC1. This means the subsequent object property of NIC2 is lost.
Is there a scalable way to ensure that you aren't losing data when exporting objects like this?
Try:
$o1 = New-Object psobject -Property #{
ServerName="Mordor"
IP1="10.0.0.1"
IP2="10.0.0.2"
NIC1="VMXNET"
NIC2="Broadcom"
}
$o2 = New-Object psobject -Property #{
ServerName="Rivendell"
IP1="10.1.1.1"
IP2="10.1.1.2"
IP3="10.1.1.3"
IP4="10.1.1.4"
NIC1="VMXNET"
}
$arr = #()
$arr += $o1
$arr += $o2
#Creating output
$prop = $arr | % { Get-Member -InputObject $_ -MemberType NoteProperty | Select -ExpandProperty Name } | Select -Unique | Sort-Object
$headers = #("ServerName")
$headers += $prop -notlike "ServerName"
$arr | ft -Property $headers
Output:
ServerName IP1 IP2 IP3 IP4 NIC1 NIC2
---------- --- --- --- --- ---- ----
Mordor 10.0.0.1 10.0.0.2 VMXNET Broadcom
Rivendell 10.1.1.1 10.1.1.2 10.1.1.3 10.1.1.4 VMXNET
If you know the types(NICS, IPS..), but not the count(ex. how many NICS) you could try:
#Creating output
$headers = $arr | % { Get-Member -InputObject $_ -MemberType NoteProperty | Select -ExpandProperty Name } | Select -Unique
$ipcount = ($headers -like "IP*").Count
$niccount = ($headers -like "NIC*").Count
$format = #("ServerName")
for ($i = 1; $i -le $ipcount; $i++) { $format += "IP$i" }
for ($i = 1; $i -le $niccount; $i++) { $format += "NIC$i" }
$arr | ft -Property $format
What about getting a list of all unique property headers and then doing a select on all the objects? When you do a select on an object for a nonexistent property it will create a blank one.
$allHeaders = $arrayOfObjects | % { Get-Member -inputobject $_ -membertype noteproperty | Select -expand Name } | Select -unique
$arrayOfObjects | Select $allHeaders
Granted you are looping through ever object to get the headers, so for a very large amount of objects it may take awhile.
Here's my attempt at a solution. I'm very tired now so hopefully it makes sense. Basically I'm calculating the largest amount of NIC and IP note properties, creating a place holder object that has those amounts of properties, adding it as the first item in a CSV, and then removing it from the CSV.
# Create example objects
$o1 = New-Object psobject -Property #{
ServerName="Mordor"
IP1="10.0.0.1"
IP2="10.0.0.2"
NIC1="VMXNET"
NIC2="Broadcom"
}
$o2 = New-Object psobject -Property #{
ServerName="Rivendell"
IP1="10.1.1.1"
IP2="10.1.1.2"
IP3="10.1.1.3"
IP4="10.1.1.4"
NIC1="VMXNET"
}
# Add to an array
$servers = #($o1, $o2)
# Calculate how many IP and NIC properties there are
$IPColSize = ($servers | Select IP* | %{($_ | gm -MemberType NoteProperty).Count} | Sort-Object -Descending)[0]
$NICColSize = ($servers | Select NIC* | %{($_ | gm -MemberType NoteProperty).Count} | Sort-Object -Descending)[0]
# Build a place holder object that will contain enough properties to cover all of the objects in the array.
$cmd = '$placeholder = "" | Select ServerName, {0}, {1}' -f (#(1..$IPColSize | %{"IP$_"}) -join ", "), (#(1..$NICColSize | %{"NIC$_"}) -join ", ")
Invoke-Expression $cmd
# Convert to CSV and remove the placeholder
$csv = $placeholder,$servers | %{$_ | Select *} | ConvertTo-Csv -NoTypeInformation
$csv | Select -First 1 -Last ($csv.Count-2) | ConvertFrom-Csv | Export-Csv Solution.csv -NoTypeInformation