Unable to export custom objects to CSV in powershell - powershell

I have written powershell scirpt to generate IIS information to CSV.
$bindingip = Get-WmiObject -Namespace root\webadministration -Class sslbinding2 | Select-Object -
Property PSComputerName,IPAddress
$sitedetails = Get-Website | Select-Object -Property Name,ID,State
$report = New-Object psobject
$report | Add-Member -MemberType NoteProperty -Name "Hostname" -Value $bindingip.PSComputerName
$report | Add-Member -MemberType NoteProperty -Name "IPAddress" -Value $bindingip.IPAddress
$report | Add-Member -MemberType NoteProperty -Name "Site Name" -Value $sitedetails.name
$report | Add-Member -MemberType NoteProperty -Name "ID" -Value $sitedetails.id
$report | Add-Member -MemberType NoteProperty -Name "State" -Value $sitedetails.state
$report | Export-Csv C:\content.csv -NoTypeInformation
Output of CSV:
Hostname IPAddress Site Name ID State
System.Object[] System.Object[] System.Object[] System.Object[] System.Object[]
Do i need to add anything to the code to get exact output, Can anyone help on this.

As Abraham and Ash pointed out in their comments, the values of the properties on your variables $bindingip and $sitedetails are an array instead of a string. This is why when you export the report you get the object type instead of it's actual value.
You could see this by doing for example this:
$bindingip.PSComputerName.GetType()
Which would return something like this:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
However if you select just the first element on the array PSComputerName
$bindingip.PSComputerName[0].GetType()
You would see:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
A workaround for this, is to either convert the values to a multiline string by the use of Out-String or by joining the elements of the arrays with a delimiter i.e. -join '//'.
$bindingip = Get-WmiObject -Namespace root\webadministration -Class sslbinding2
$sitedetails = Get-Website
# Like this (thanks mklement0):
$report = [pscustomobject]#{
Hostname = "$($bindingip.PSComputerName)"
IPAddress = "$($bindingip.IPAddress)"
'Site Name' = "$($sitedetails.name)"
ID = "$($sitedetails.id)"
State = "$($sitedetails.state)"
}
# Or like this:
$report = [pscustomobject]#{
Hostname = ($bindingip.PSComputerName | Out-String).trim()
IPAddress = ($bindingip.IPAddress | Out-String).trim()
'Site Name' = ($sitedetails.name | Out-String).trim()
ID = ($sitedetails.id | Out-String).trim()
State = ($sitedetails.state | Out-String).trim()
}
# Or like this:
$report = [pscustomobject]#{
Hostname = $bindingip.PSComputerName -join '//'
IPAddress = $bindingip.IPAddress -join '//'
'Site Name' = $sitedetails.name -join '//'
ID = $sitedetails.id -join '//'
State = $sitedetails.state -join '//'
}
$report | Export-Csv C:\content.csv -NoTypeInformation
Edit
This can also work, which is what I think mklement0 suggested. It will create a new object for each element on the values of the properties:
$compName = $bindingip.PSComputerName
$ipAddr = $bindingip.IPAddress
$name = $sitedetails.name
$id = $sitedetails.id
$state = $sitedetails.state
$top = ($compName.Count,$ipAddr.Count,$name.Count,$id.Count,$state.Count | Measure-Object -Maximum).Maximum
$report = for($i = 0;$i -lt $top;$i++)
{
[pscustomobject]#{
Hostname = $compName[$i]
IPAddress = $ipAddr[$i]
'Site Name' = $name[$i]
ID = $id[$i]
State = $state[$i]
}
$report | Export-Csv...

In addition to Santiago's very thorough and excellent answer, it appears to me that we are just combining the objects of one array with another to produce all possible combinations. I might do something like the following to accomplish this.
$bindingip = #(
[PSCustomObject]#{
PSComputerName = 'Public1'
IPAddress = '192.168.0.1'
},
[PSCustomObject]#{
PSComputerName = 'Public1'
IPAddress = '127.0.0.1'
}
)
$siteDetails = #(
[PSCustomObject]#{
Name = 'site 1'
Id = 'site1'
State = 'up'
},
[PSCustomObject]#{
Name = 'site 2'
Id = 'site2'
State = 'down'
}
)
$combined = foreach ($ip in $bindingip) {
foreach ($details in $siteDetails) {
$out = [ordered]#{}
$ip.psobject.properties | ForEach-Object {
$out[$_.Name] = $_.Value
}
$details.psobject.properties | ForEach-Object {
$out[$_.Name] = $_.Value
}
[pscustomobject]$out
}
}
$combined | Format-Table
Output
PSComputerName IPAddress Name Id State
-------------- --------- ---- -- -----
Public1 192.168.0.1 site 1 site1 up
Public1 192.168.0.1 site 2 site2 down
Public1 127.0.0.1 site 1 site1 up
Public1 127.0.0.1 site 2 site2 down
One might wrap this in a function for reusability
function Combine-ObjectsFromTwoArrays {
param (
[array]$array1,
[array]$array2
)
foreach ($obj1 in $array1) {
foreach ($obj2 in $array2) {
$out = [ordered]#{}
$obj1.psobject.properties | ForEach-Object {
$out[$_.Name] = $_.Value
}
$obj2.psobject.properties | ForEach-Object {
$out[$_.Name] = $_.Value
}
[pscustomobject]$out
}
}
}

Related

PowerShell: Unique Array of Objects

I've got an array of objects for team names and emails as well as the tickets associated to that team in a seperate array however its not working quite as expected and I've been stumped on this one for sometime...
code:
$teamConfig = #(
[pscustomobject]#{
TeamName='Team1';
}
)
$query = "select * from INCAutomation"
$results = Invoke-Sqlcmd -query $query -ServerInstance 'localhost' -Database 'AyushTest'
$collectionWithItems = #()
$array = #()
foreach ($i in $results) {
foreach ($x in $teamConfig) {
if ($i.TeamName -eq $x.TeamName) {
$array += $i.TicketID
$temp = New-Object System.Object
$temp | Add-Member -MemberType NoteProperty -Name "TeamName" -Value $i.TeamName
$temp | Add-Member -MemberType NoteProperty -Name "TeamEmail" -Value $i.TeamEmail
$temp | Add-Member -MemberType NoteProperty -Name "TicketID" -Value $array
$collectionWithItems += $temp
}
}
}
$collectionWithItems = $collectionWithItems | select 'TeamName', 'TeamEmail', 'TicketID' -Unique
$collectionWithItems
Output:
TeamName TeamEmail TicketID
-------- --------- --------
Team1 Team1#test.com {INC0001}
Team1 Team1#test.com {INC0001, INC0002}
Team1 Team1#test.com {INC0001, INC0002, INC0003}
Team1 Team1#test.com {INC0001, INC0002, INC0003, INC0004}
Desired output:
TeamName TeamEmail TicketID
-------- --------- --------
Team1 Team1#test.com {INC0001, INC0002, INC0003, INC0004}
The other thing I am confused about as well is that this will need to work with multiple teams for example:
$teamConfig = #(
[pscustomobject]#{
TeamName='Team1';
}
[pscustomobject]#{
TeamName='Team2';
}
[pscustomobject]#{
TeamName='Team3';
}
)
and as a result, the desired output should be:
TeamName TeamEmail TicketID
-------- --------- --------
Team1 Team1#test.com {INC0001, INC0002, INC0003, INC0004}
Team2 Team2#test.com {INC0005, INC0006, INC0007, INC0008}
Team3 Team3#test.com {INC0009, INC0010, INC0011, INC0012}
Thanks in advance!
Your outer loop should iterate over $teamConfig so that you only go through each team once.
To get the tickets associated with the current team name, you can use Where-Object, like so:
$ticketIds = $results |Where-Object TeamName -eq $team.TeamName |Select -ExpandProperty TicketID
Allowing you to simplify your code greatly:
$teamConfig = #(
[pscustomobject]#{
TeamName = 'Team1'
TeamEmail = 'team1#domain.tld'
}
[pscustomobject]#{
TeamName = 'Team2'
TeamEmail = 'team2#domain.tld'
}
[pscustomobject]#{
TeamName = 'Team3'
TeamEmail = 'team3#domain.tld'
}
)
$query = "select * from INCAutomation"
$results = Invoke-Sqlcmd -query $query -ServerInstance 'localhost' -Database 'AyushTest'
$teamTickets = foreach($team in $teamConfig){
# Filter tickets based on team name
$ticketIds = $results |Where-Object TeamName -eq $team.TeamName |Select -ExpandProperty TicketID
# Output a single object per team, with all ticket IDs attached
$team |Select TeamName,TeamEmail,#{Name='TicketID';Expression={ $ticketIds }}
}
$teamTickets now contains exactly one object per team in $teamConfig.

If a user exists in both objects, return their value from object 1 [Powershell]

I'm trying to compare two different objects and return the ID of the user from Object 1 if their email address exists in object 2.
I.e. Object 1
| user | id | emailaddress |
+-----------+------------+--------------------+
| test user | asfasfasdf | test.user#test.com |
| ima test | bsvxcffasd | ima.test#test.com |
+-----------+------------+--------------------+
Object 2
+--------------------+
| email |
+--------------------+
| test.user#test.com |
| ima.test#test.com |
+--------------------+
Consider the 2 objects above, my goal objective is to check if a user exists in Object 2 and Object 1. If they exist in Object 2 then I want to return their ID value.
This code is where i'm up to, this will return the users who email address exists in both objects but not their ID:
$x = $object1 | Select-Object -ExpandProperty emailaddress
$y = $object2 | Select-Object -ExpandProperty email
$z = Compare-Object $x $y -IncludeEqual -ExcludeDifferent
$userids = #()
foreach($a in $z.inputobject){
if($object2.email -contains $a){
$userids += $a
}
}
Attempt 2 based on Olaf's reply:
$object1 = New-Object -Typename psobject
$object1 | Add-Member -MemberType NoteProperty -Name email -Value $otherobject.members.email
$object1 | Add-Member -MemberType NoteProperty -Name id -Value $otherobject.members.id
$object2 = New-Object -Typename psobject
$object2 | Add-Member -MemberType NoteProperty -Name email -Value $otherobject2.emailaddress
$ComparedUsers = Compare-Object -ReferenceObject $object1 -DifferenceObject $object2 -IncludeEqual -ExcludeDifferent -PassThru
You should not use -ExpandProperty when you want to use other properties of the object as well. And I'd recommend to use the same property names for both objects.
Something like this should push you to the right direction:
$object1 =
#'
user,id,email
testuser,asfasfasdf,test.user#test.com
imatest,bsvxcffasd,ima.test#test.com
other,lkjshfdlakjs,other.test#test.com
'# |
ConvertFrom-Csv
$object2 =
#'
email
test.user#test.com
ima.test#test.com
any.test#test.com
'# |
ConvertFrom-Csv
Compare-Object -ReferenceObject $object1 -DifferenceObject $object2 -Property 'email' -IncludeEqual -ExcludeDifferent -PassThru
The output of that would be ...
user id email SideIndicator
---- -- ----- -------------
testuser asfasfasdf test.user#test.com ==
imatest bsvxcffasd ima.test#test.com ==

Powershell - Export-CSV outside loop only last line is printed/exported

Is it possible to adjust this code to export all lines outside foreach loop:
This works fine (inside loop):
$vms = Get-VM | Where { $_.State –eq ‘Running’ } | Select-Object -ExpandProperty Name
foreach($vm in $vms) {
# Get network interface details
$out = Get-VMNetworkAdapter -vmname $vm | select VMName, MacAddress, IPAddresses
$vm_name = $out.VMName | Get-Unique
$ip = ($out.IPAddresses | ForEach-Object {
$_ | ? {$_ -notmatch ':'}
}) -join " "
# If more than 1 MAC , put it in same row separated by space (00:15:5D:58:12:5E 00:15:5D:58:12:5F )
$mac = ($out.MacAddress | ForEach-Object {
$_.Insert(2,":").Insert(5,":").Insert(8,":").Insert(11,":").Insert(14,":")
}) -join ' '
$results = #()
$comp = Get-WmiObject Win32_ComputerSystem | Select-Object -ExpandProperty name
$obj = New-Object -TypeName psobject
$obj | Add-Member -MemberType NoteProperty -Name "VM NAME" -Value $vm_name
$obj | Add-Member -MemberType NoteProperty -Name "IP ADDRESS" -Value $ip
$obj | Add-Member -MemberType NoteProperty -Name "MAC ADDRESS" -Value $mac
$obj | Add-Member -MemberType NoteProperty -Name "HYPER-V HOST" -Value $comp
$results += $obj
Write-Output $results
$results| Export-Csv -Path "c:\1.csv" -NoTypeInformation -append
}
However, when i move $results| Export-Csv -Path "c:\1.csv" -NoTypeInformation -append outside loop,
only one (last) line is saved to CSV
Inside loop, $results variable contains all lines, when i move this variable outside loop write-host $results only one (last) line is printed
For what it's worth, your code can be condensed quite a bit. Many of your steps are not necessary:
$results = Get-VM | Where State –eq Running | Get-VMNetworkAdapter | ForEach-Object {
[pscustomobject]#{
'VM NAME' = $_.VMName
'IP ADDRESS' = ($_.IPAddresses -notmatch ':') -join ' '
'MAC ADDRESS' = ($_.MacAddress -replace '(..)(..)(..)(..)(..)','$1:$2:$3:$4:$5:') -join ' '
'HYPER-V HOST' = $env:COMPUTERNAME
}
}
$results | Export-Csv -Path "c:\1.csv" -NoTypeInformation
Notes:
You can pipe the VMs that Get-VM returns directly into Get-VMNetworkAdapter
If you filter on a single property you don't need a script block for Where-Object. Where State -eq Running is a bit easier to write and read than Where { $_.State -eq 'Running' }.
$_.IPAddresses -notmatch ':' Operators like -notmatch work on arrays. 'a','b','0','c' -notmatch '\d' will return 'a','b','c'.
The same goes for -replace. 'a0','b1','c2' -replace '\d','' will return return 'a','b','c'. No foreach loops necessary at all.
$env:COMPUTERNAME should be faster than using WMI to get the computer name
Any object you create in a script block (like the ForEach-Object {...} script block) that you do not assign to a variable will be in the script block's output. This is why $results = ... | ForEach-Object {...} works. There is no need to explicitly create arrays with #() and add values to them.
Casting a hash table to [pscustomobject] is much easier than using Add-Member.
Figured it out:
moved $results variable outside loop (make it "global")
$vms = Get-VM | Where { $_.State –eq ‘Running’ } | Select-Object -ExpandProperty Name
$results = #()
foreach($vm in $vms) {
# Get network interface details
$out = Get-VMNetworkAdapter -vmname $vm | select VMName, MacAddress, IPAddresses
# Remove duplicate VM names
$vm_name = $out.VMName | Get-Unique
# In case more than 1 IP, put it in same row separated by space (192.168.1.1, 192.168.1.2)
$ip = ($out.IPAddresses | ForEach-Object {
$_ | ? {$_ -notmatch ':'}
}) -join " "
# If more than 1 MAC , put it in same row separated by space (00:15:5D:58:12:5E 00:15:5D:58:12:5F )
$mac = ($out.MacAddress | ForEach-Object {
$_.Insert(2,":").Insert(5,":").Insert(8,":").Insert(11,":").Insert(14,":")
}) -join ' '
$comp = Get-WmiObject Win32_ComputerSystem | Select-Object -ExpandProperty name
$obj = New-Object -TypeName psobject
$obj | Add-Member -MemberType NoteProperty -Name "VM NAME" -Value $vm_name
$obj | Add-Member -MemberType NoteProperty -Name "IP ADDRESS" -Value $ip
$obj | Add-Member -MemberType NoteProperty -Name "MAC ADDRESS" -Value $mac
$obj | Add-Member -MemberType NoteProperty -Name "HYPER-V HOST" -Value $comp
$results += $obj
}
$results| Export-Csv -Path "c:\1.csv" -NoTypeInformation

Display output from multiple variables in a for loop

I am trying to output different attributes of a Skype response group queue for documentation purpose.
I want to get Name, TimeoutThreshold, TimeoutAction , Timeouturi, OverflowThreshold, OverflowAction , OverflowCandidate as a .csv file header in row 1 and then the output to be entered in various columns from row 2.
I have tried below, but the formatting is really bad and the headers keep repeating. Can some one please help.
Also tried getting output in HTML, but no luck.
$p = Get-CsRgsQueue | Where-Object {$_.Name -like "IPL*"} | Select-Object Name
foreach ($Name in $p)
{
$q = Get-CsRgsQueue -Name "$Name"
$N = $q.Name
$TT = $q.TimeoutThreshold
$TA = $q.TimeoutAction.Action
$TAU = $q.TimeoutAction.uri
$OF = $q.OverflowThreshold
$OFA = $q.OverflowAction
$OFC = $q.OverflowCandidate
$out = New-Object PSObject
$out | Add-Member NoteProperty QueueName $N
$out | Add-Member NoteProperty Timeout $TT
$out | Add-Member NoteProperty TimeoutAction $TA
$out | Add-Member NoteProperty TransferURI $TAU
$out | Add-Member NoteProperty OverflowThreshhold $OF
$out | Add-Member NoteProperty OverflowAction $OFA
$out | Add-Member NoteProperty OverflowCandidate $OFC
$out | FT -AutoSize | Export-Csv C:\abc.csv -Append
}
I have tried below, but the formatting is really bad and the headers
keep repeating. Can some one please help.
That's because you pipe your objects through FT -AutoSize (Format-Table -AutoSize) - only ever use the Format-* cmdlets when you're about to show/present your data.
You can also save some time by only calling Get-CsRgsQueue once, piping it to ForEach-Object and finally construct a hashtable for the object properties:
Get-CsRgsQueue | Where-Object {$_.Name -like "IPL*"} | ForEach-Object {
New-object psobject -Property #{
QueueName = $_.Name
Timeout = $_.TimoutThreshold
TimeoutAction = $_.TimeoutAction.Action
TransferURI = $_.TimeoutAction.Uri
OverflowThreshhold = $_.OverflowThreshold
OverflowAction = $_.OverflowAction
OverflowCandidate = $_.OverflowCandicate
}
} |Export-Csv c:\abc.csv -NoTypeInformation
short solution of Mathias Jessen
Get-CsRgsQueue | where Name -like "IPL*" | %{
[pscustomobject] #{
QueueName = $_.Name
Timeout = $_.TimoutThreshold
TimeoutAction = $_.TimeoutAction.Action
TransferURI = $_.TimeoutAction.Uri
OverflowThreshhold = $_.OverflowThreshold
OverflowAction = $_.OverflowAction
OverflowCandidate = $_.OverflowCandicate
}
} | Export-Csv C:\result.csv -NoType

Powershell Side by side objects

Heres what I have,
Two objects:
$Global:Object1 = New-Object -TypeName PSObject
$Global:Object1 | Add-Member -MemberType NoteProperty -Name Name1 -Value $Name1.Name1
$Global:Object1 | Add-Member -MemberType NoteProperty -Name Name2 -Value $Name2.Name2
$Global:Object2 = New-Object -TypeName PSObject
$Global:Object2 | Add-Member -MemberType NoteProperty -Name Name3 -Value $Name3.Name3
$Global:Object2 | Add-Member -MemberType NoteProperty -Name Name4 -Value $Name4.Name4
Now when I present that to the user, it appears how underneath each other.
I would like to have them presented side by side:
Object1: Object2:
Name1 Name3
Name2 Name4
I will have one more object, but at the moment only 2. Can anyone help me out please?
If the $variables don't make sense, I replaced them to keep things simple..
I do something similar in that I compare two objects side by side for easy reference, I use the following function:
function Compare-ObjectsSideBySide ($lhs, $rhs) {
$lhsMembers = $lhs | Get-Member -MemberType NoteProperty, Property | Select-Object -ExpandProperty Name
$rhsMembers = $rhs | Get-Member -MemberType NoteProperty, Property | Select-Object -ExpandProperty Name
$combinedMembers = ($lhsMembers + $rhsMembers) | Sort-Object -Unique
$combinedMembers | ForEach-Object {
$properties = #{
'Property' = $_;
}
if ($lhsMembers.Contains($_)) {
$properties['Left'] = $lhs | Select-Object -ExpandProperty $_;
}
if ($rhsMembers.Contains($_)) {
$properties['Right'] = $rhs | Select-Object -ExpandProperty $_;
}
New-Object PSObject -Property $properties
}
}
You can test this out with the following, contrived, example:
$object1 = New-Object PSObject -Property #{
'Forename' = 'Richard';
'Surname' = 'Slater';
'Company' = 'Amido';
'SelfEmployed' = $true;
}
$object2 = New-Object PSObject -Property #{
'Forename' = 'Jane';
'Surname' = 'Smith';
'Company' = 'Google';
'MaidenName' = 'Jones'
}
Compare-ObjectsSideBySide $object1 $object2 | Format-Table Property, Left, Right
Which will result in:
Property Left Right
-------- ---- -----
Company Amido Google
Forename Richard Jane
MaidenName Jones
SelfEmployed True
Surname Slater Smith
It wouldn't be difficult to increase the number of objects being compared side-by-side, or even write it in such a way that the function accepts an array of objects which are printed as a side-by-side table.