How to export mixed type objects to csv file? - powershell

I'm writing a script that returns a list of objects that most of them have different number of properties. When I print it in the console everything is OK, but when I try to export to CSV only those fields that are common in all objects get exported. All others are cropped.
I use the Add-Member cmdlet to add more properties but not all of the objects get the same number of properties.
For example I try to export 2 objects where one is like this:
FirstObject:{
Network0:nic1,
Network1:nic2,
Network2:nic3,
Network3:nic4,
Name:VirtualMachine1
}
SecondObject:{
Network0:nic1,
Network1:nic2,
Name:VirtualMachine1
}
The Network property is added with Add-Member cmdlet. The problem I get when exporting to CSV is that Network2 and Network3 properties from the first object are cropped and all the columns I get is Network0, Network1, and Name.
What I would like to know is there a way to export all the properties and if one of the objects doesn't have the property, just assign $null?
P.S. I have a solution just to add those fields manually with a loop, but I was wondering maybe there is a cleaner solution built in PowerShell which I missed?
Update:
I found out that it provides the same columns to the file that are in the first object. All other fields are ignored. So to be more exact I need all columns in all objects. If some objects do not have the field, then it should be printed empty.

Just a few lines of code that add missing properties.
#sample setup
$one = [pscustomobject]#{
Network0='nic1'
Network1='nic2'
Network2='nic3'
Network3='nic4'
Name='VirtualMachine1'
}
$two = [pscustomobject]#{
Network0='nic1'
Network1='nic2'
Name='VirtualMachine2'
}
$three = [pscustomobject]#{
Network0='nic1'
Name='VirtualMachine3'
}
$export = ($one,$two,$three)
#build list of all properties available to $allProps
$export | % -begin { $allProps = #() } -process { $allProps = [linq.enumerable]::union([object[]](($_.psobject.Properties).Name), [object[]]$allProps) }
#convert each object in $export to new custom object having all properties and put to $result
$export | % -begin { $result = #() } -process { $__ = $_; $o = #{ }; $allProps | %{ $o += #{ $_ = $__.$_ } }; $result+=[pscustomobject]$o }
#export $result to csv
$result | Export-Csv $env:TEMP\export.csv -NoTypeInformation -Force
Get-Content $env:TEMP\export.csv
"Network1", "Network3", "Network0", "Name", "Network2"
"nic2", "nic4", "nic1", "VirtualMachine1", "nic3"
"nic2",, "nic1", "VirtualMachine2",
,, "nic1", "VirtualMachine3",
>> Script Ended
Things to note:
[linq.enumerable]::union is used to easy build list of all available properties across all objects.
($_.psobject.Properties).Name is shortcut to #($_.psobject.Properties | select -ExpandProperty Name), it contains array of property names
$__ = $_ is a trick for nested loop
$o += #{ $_ = $__.$_ } adds key-value pairs to output object; trick here is that even if property $_='nic4' does not exists in $__ export object, powershell does not throw error and returns $null. Note that this will not work when Set-StrictMode is set -Version 2 or later.

Related

Powershell Array Output to html

Apologies if this is irrelevant but I'm new to powershell and I've been scratching my head on this for a few days on and off now. I'm trying to write a script that will output two columns of data to a html document. I've achieved most of it by learning through forums and testing different combinations.
The problem is although it gives me the result I need within powershell itself; it will not properly display the second column results for Net Log Level.
So the script looks at some folders and pulls the * value which is always three digits (this is the Site array). It then looks within each of these folders to the Output folder and grabs a Net Log Level node from a file inside there. The script is correctly listing the Sites but is only showing the last value for Net Log Level which is 2. You can see this in the screenshot above. I need this to take every value for each Site and display as appropriate. The image of the incorrect result is below. I need the result to be 1,4,2,2,2. Any help would be greatly appreciated!
function getSite {
Get-ChildItem C:\Scripts\ServiceInstalls\*\Output\'Config.exe.config' | foreach {
$Site = $_.fullname.substring(27, 3)
[xml]$xmlRead = Get-Content $_
$NetLogLevel = $xmlRead.SelectSingleNode("//add[#key='Net Log Level']")
$NetLogLevel = $NetLogLevel.value
New-Object -TypeName System.Collections.ArrayList
$List1 += #([System.Collections.ArrayList]#($Site))
New-Object -TypeName System.Collections.ArrayList
$List2 += #([System.Collections.ArrayList]#($NetLogLevel))
}
$Results = #()
ForEach($Site in $List1){
$Results += [pscustomobject]#{
"Site ID" = $Site
"Net Log Level" = $NetLogLevel
}
}
$Results | ConvertTo-HTML -Property 'Site','Net Log Level' | Set-Content Output.html
Invoke-Item "Output.html"
}
getSite
Restructure your code as follows:
Get-ChildItem 'C:\Scripts\ServiceInstalls\*\Output\Config.exe.config' |
ForEach-Object {
$site = $_.fullname.substring(27, 3)
[xml]$xmlRead = Get-Content -Raw $_.FullName
$netLogLevel = $xmlRead.SelectSingleNode("//add[#key='Net Log Level']").InnerText
# Construct *and output* a custom object for the file at hand.
[pscustomobject] #{
'Site ID' = $site
'Net Log Level' = $netLogLevel
}
} | # Pipe the stream of custom objects directly to ConvertTo-Html
ConvertTo-Html | # No need to specify -Property if you want to use all properties.
Set-Content Output.html
As for what you tried:
New-Object -TypeName System.Collections.ArrayList in effect does nothing: it creates an array-list instance but doesn't save it in a variable, causing it to be enumerated to the pipeline, and since there is nothing to enumerate, nothing happens.
There is no point in wrapping a [System.Collections.ArrayList] instance in #(...): its elements are enumerated and then collected in a regular [object[]] array - just use #(...) by itself.
Using += to "grow" an array is quite inefficient, because a new array must be allocated behind the scenes every time; often there is no need to explicitly create an array - e.g. if you can simply stream objects to another command via the pipeline, as shown above, or you can let PowerShell itself implicitly create an array for you by assigning the result of a pipeline or foreach loop as a whole to a variable - see this answer.
Also note that when you use +=, the result is invariably a regular [object[] array, even if the RHS is a different collection type such as ArrayList.
There are still cases where iteratively creating an array-like collection is necessary, but you then need to use the .Add() method of such a collection type in order to grow the collection efficiently - see this answer.
Instead of populating two separate lists, simply create the resulting objects in the first loop:
function getSite {
$Results = Get-ChildItem C:\Scripts\ServiceInstalls\*\Output\'Config.exe.config' | ForEach-Object {
$Site = $_.fullname.substring(27, 3)
[xml]$xmlRead = Get-Content $_
$NetLogLevel = $xmlRead.SelectSingleNode("//add[#key='Net Log Level']")
$NetLogLevel = $NetLogLevel.value
[pscustomobject]#{
"Site ID" = $Site
"Net Log Level" = $NetLogLevel
}
}
$Results | ConvertTo-HTML -Property 'Site', 'Net Log Level' | Set-Content Output.html
Invoke-Item "Output.html"
}
getSite

Iterate through XML nodes / objects and pass to variables in Powershell

I'd be wanting to iterate through a set of XML and then pass those to variables which can be printed.
Here is an example of the data:
<applications>
<size>75</size>
<application>
<name>Applications 1</name>
<path>/Applications/Utilities/Application 1</path>
<version>10.14</version>
</application>
<application>
<name>Application 2</name>
<path>/Applications/Utilities/Application 2</path>
<version>6.3.9</version>
</application>
</applications
I've looked at using ForEach-Object when trying to output it but to no avail.
[string]$applicationProperties = $API.applications.application| ForEach-Object {
$_.name
$_.path
$_.version
}
This works but puts them all on one line, I'd like them so they print on individual lines but I couldn't prefix the $_ variable. I'm new to POSH as you can tell.
e.g. so I'd like to have name/path/version data saved to variables
[string]$applicationProperties = $API.applications.application | ForEach-Object {
[string]$name_var = $_.name
[string]$path_var = $_.path
[string]$version_var = $_.variable
}
This gives me one "application", but not all the possible objects. Also mentions that even when I'm putting down $name_var it's not accessing that variable? Do I need to do something to access that variable?
Any advice would be appreciated.
When you assign the output from ForEach-Object to [string]$applicationProperties, you're forcing PowerShell to convert all the strings into a single string because of the cast to [string].
What you'll want to do is create a new object for each application node that you're iterating over:
$appInformation = $API.applications.application | ForEach-Object {
# Create a new custom objects with the values from the XML nodes
[pscustomobject]#{
Name = $_.name
Path = $_.path
Version = $_.version
}
}
Now $appInformation will contain an array of objects each with a Name, Path and Version property. You can then further use and/or manipulate these objects in your scripts rather than just having a bunch of strings:
$appInformation |ForEach-Object {
Write-Host "Here is the version of application '$($_.Name)': $($_.Version)"
}
If you want to see them printed in the console with each property value on a separate line just pipe the array to Format-List:
$appInformation |Format-List

PS Object unescape character

I have small error when running my code. I assign a string to custom object but it's parsing the string by itself and throwing an error.
Code:
foreach ($item in $hrdblistofobjects) {
[string]$content = Get-Content -Path $item
[string]$content = $content.Replace("[", "").Replace("]", "")
#here is line 43 which is shown as error as well
foreach ($object in $listofitemsdb) {
$result = $content -match $object
$OurObject = [PSCustomObject]#{
ObjectName = $null
TestObjectName = $null
Result = $null
}
$OurObject.ObjectName = $item
$OurObject.TestObjectName = $object #here is line 52 which is other part of error
$OurObject.Result = $result
$Resultsdb += $OurObject
}
}
This code loads an item and checks if an object exists within an item. Basically if string part exists within a string part and then saves result to a variable. I am using this code for other objects and items but they don't have that \p part which I am assuming is the issue. I can't put $object into single quotes for obvious reasons (this was suggested on internet but in my case it's not possible). So is there any other option how to unescape \p? I tried $object.Replace("\PMS","\\PMS") but that did not work either (this was suggested somewhere too).
EDIT:
$Resultsdb = #(foreach ($item in $hrdblistofobjects) {
[string]$content = Get-Content -Path $item
[string]$content = $content.Replace("[", "").Replace("]", "")
foreach ($object in $listofitemsdb) {
[PSCustomObject]#{
ObjectName = $item
TestObjectName = $object
Result = $content -match $object
}
}
}
)
$Resultsdb is not defined as an array, hence you get that error when you try to add one object to another object when that doesn't implement the addition operator.
You shouldn't be appending to an array in a loop anyway. That will perform poorly, because with each iteration it creates a new array with the size increased by one, copies all elements from the existing array, puts the new item in the new free slot, and then replaces the original array with the new one.
A better approach is to just output your objects in the loop and collect the loop output in a variable:
$Resultsdb = foreach ($item in $hrdblistofobjects) {
...
foreach ($object in $listofitemsdb) {
[PSCustomObject]#{
ObjectName = $item
TestObjectName = $object
Result = $content -match $object
}
}
}
Run the loop in an array subexpression if you need to ensure that the result is an array, otherwise it will be empty or a single object when the loop returns less than two results.
$Resultsdb = #(foreach ($item in $hrdblistofobjects) {
...
})
Note that you need to suppress other output on the default output stream in the loop, so that it doesn't pollute your result.
I changed the match part to this and it's working fine $result = $content -match $object.Replace("\PMS","\\PMS").
Sorry for errors in posting. I will amend that.

How to add extra information to a custom PSObject property

Based on an export file and some checks I'm creating an array using the following block of code:
$Result += New-Object PSObject -Property #{
FQDN = $($Server.FQDN)
IP = $($Server.IP)
Description = $($Server.Description)
Remarks = $("")
}
When I reuse this $Result, for another check, how can I add extra information in the "Remarks" property?
If ($Result.IP.Contains($IP.number)){
$Result.Remarks += "Attention for this server" | Where-Object $Result.IP -eq $IP.number
}
You need to loop through the $Result array and modify the specific element with the correct IP value:
# Loop through array
$Result = foreach($object in $Result)
{
# Check if array element is interesting
if($object.IP -eq $IP.Number)
{
# Modify the object in question
$object.Remarks += "Attention!"
}
# Write the (potentially modified) object back to the array
$object
}

Powershell array of arrays loop process

I need help with loop processing an array of arrays. I have finally figured out how to do it, and I am doing it as such...
$serverList = $1Servers,$2Servers,$3Servers,$4Servers,$5Servers
$serverList | % {
% {
Write-Host $_
}
}
I can't get it to process correctly. What I'd like to do is create a CSV from each array, and title the lists accordingly. So 1Servers.csv, 2Servers.csv, etc... The thing I can not figure out is how to get the original array name into the filename. Is there a variable that holds the list object name that can be accessed within the loop? Do I need to just do a separate single loop for each list?
You can try :
$1Servers = "Mach1","Mach2"
$2Servers = "Mach3","Mach4"
$serverList = $1Servers,$2Servers
$serverList | % {$i=0}{$i+=1;$_ | % {New-Object -Property #{"Name"=$_} -TypeName PsCustomObject} |Export-Csv "c:\temp\$($i)Servers.csv" -NoTypeInformation }
I take each list, and create new objects that I export in a CSV file. The way I create the file name is not so nice, I don't take the var name I just recreate it, so if your list is not sorted it will not work.
It would perhaps be more efficient if you store your servers in a hash table :
$1Servers = #{Name="1Servers"; Computers="Mach1","Mach2"}
$2Servers = #{Name="2Servers"; Computers="Mach3","Mach4"}
$serverList = $1Servers,$2Servers
$serverList | % {$name=$_.name;$_.computers | % {New-Object -Property #{"Name"=$_} -TypeName PsCustomObject} |Export-Csv "c:\temp\$($name).csv" -NoTypeInformation }
Much like JPBlanc's answer, I kinda have to kludge the filename... (FWIW, I can't see how you can get that out of the array itself).
I did this example w/ foreach instead of foreach-object (%). Since you have actual variable names you can address w/ foreach, it seems a little cleaner, if nothing else, and hopefully a little easier to read/maintain:
$1Servers = "apple.contoso.com","orange.contoso.com"
$2Servers = "peach.contoso.com","cherry.contoso.com"
$serverList = $1Servers,$2Servers
$counter = 1
foreach ( $list in $serverList ) {
$fileName = "{0}Servers.csv" -f $counter++
"FileName: $fileName"
foreach ( $server in $list ) {
"-- ServerName: $server"
}
}
I was able to resolve this issue myself. Because I wasn't able to get the object name through, I just changed the nature of the object. So now my server lists consist of two columns, one of which is the name of the list itself.
So...
$1Servers = += [pscustomobject] #{
Servername = $entry.Servername
Domain = $entry.Domain
}
Then...
$serverList = $usaServers,$devsubServers,$wtencServers,$wtenclvServers,$pcidevServers
Then I am able to use that second column to name the lists within my foreach loop.