Powershell - Create new line for multiple array objects using Export-csv - powershell

I have an odd one that I haven't seen much writing on. Anyway, here goes.
I'm trying to build an array and export it to CSV. The problem is, if there is more than one result returned, I can't figure out how to add it to the CSV as a new line. I am currently using a -join to throw all the results into the same cell, but that's not optimal. What I'd really like to do is add a new row and throw the extra results underneath it in the same column. Does that make sense? Here's what I have now:
# Grab all VMs, put into variable
$vms = Get-VM
# Use to build report
foreach ($vm in $vms){
New-Object PSObject -Property #{
VMName = $vm.Name
UsedSpaceGB = $vm.UsedSpaceGB
StorageAllocatedGB = ($vm.HardDisks.capacitygb | Measure-Object -Sum).Sum
NumberOfCPUs = $cm.NumCpu
MemoryGB = $vm.MemoryGB
Datastores = Get-Datastore -VM $vm
Application = ($vm | Get-Annotation -CustomAttribute Applications -ErrorAction SilentlyContinue).Value | select
} | select VMName,#{label="Application";expression={$_.Application -join ","}},UsedSpaceGB,StorageAllocatedGB,NumberOfCPUs,MemoryGB,#{l="Datastores";e={$_.Datastores -join ","}} | Export-Csv -Path C:\script\VMCapacityUsedByApp.csv -NoClobber -Append -NoTypeInformation
}
By the way, this is using VMware's PowerCLI snapin. Any help is greatly appreciated.

Ok, looks like datastores and applications are the only fields that are going to return arrays according to your previous code. Assuming that the $cm.NumCpu was supposed to be $vm.NumCpu the following code should do what you want. It will figure out if you have more datastores or applications, and then loop through expanding the arrays for those fields creating new records for the same VM listing additional datastores and applications until it runs out of records. I set it to only list all details of a VM on the first record, but I'm sure you can figure out how to alter that if needed. Try this code and see how it looks to you:
# Grab all VMs, put into variable
$vms = Get-VM
# Use to build report
foreach ($vm in $vms){
$TempVM = New-Object PSObject -Property #{
VMName = $vm.Name
UsedSpaceGB = $vm.UsedSpaceGB
StorageAllocatedGB = ($vm.HardDisks.capacitygb | Measure-Object -Sum).Sum
NumberOfCPUs = $cm.NumCpu
MemoryGB = $vm.MemoryGB
Datastores = Get-Datastore -VM $vm
Application = ($vm | Get-Annotation -CustomAttribute Applications -ErrorAction SilentlyContinue).Value
}
$Records = if($TempVM.Application.count -gt $TempVM.Datastores.Count){$TempVM.Application.Count}else{$TempVM.Datastores.Count}
$ExpandedVM = #()
$ExpandedVM += $TempVM|select Name,UsedSpaceGB,StorageAllocatedGB,NumberOfCPUs,MemoryGB,#{l="Datastores";e={$TempVM.Datastores[0]}},#{l="Application";e={$TempVM.Application[0]}}
for($i=1;$i -lt $Records;$i++){$ExpandedVM += $TempVM|select Name,#{l="Datastores";e={$TempVM.Datastores[$i]}},#{l="Application";e={$TempVM.Application[$i]}}}
$ExpandedVM | Export-Csv -Path C:\script\VMCapacityUsedByApp.csv -NoClobber -Append -NoTypeInformation
}
There may be a more elegant way to do it, but that should be functional for you at the very least. I don't have VM machines to test against, or the plugin you use, so I made up data that should be in line with what you're feeding it (strings for all fields except datastores and application both of which have their own array of strings) and ended up with output like this:
Name UsedSpaceGB StorageAllo NumberOfCP MemoryGB Datastores Applicatio
catedGB Us n
---- ----------- ----------- ---------- -------- ---------- ----------
TestVM 250 500 4 16 Store1 Word
TestVM Store2 Excel
TestVM Store3 Access
TestVM Outlook
TestVM2 487 500 4 32 StoreA WoW
TestVM2 StoreB SC2
TestVM2 StoreC D3
TestVM2 StoreD
TestVM2 StoreE
That is what you were looking for I think.

Related

Merge two PSCustomObjects into one- PowerShell

I need help in PowerShell to combine two outputs or two PSCustomObjects into One.
For example,
$services = Get-Service | Select Name, Starttype,Status
$processes = Get-Process | Select ID
I need the output with the table headers
Name, Starttype, Status, ID
I have already tried creating CSV and joining them but the problem is Process ID starts when the entire output ends for the services. I need them to a parallel.
Second I have tried to create PSCustomObjects but no luck.
Please help me with the PowerShell code.
Actual code that I'm trying to achieve.
**$exclusionItems = #()
$OasHighItems = #()
foreach($item in $items){
$exclusionItems += [PSCustomObject]#{
EXCLUSION_BY_NAME_OR_LOCATION = $item.EXCLUSION_BY_NAME_OR_LOCATION
EXCLUSION_EXCLUDE_SUBFOLDERS = $item.EXCLUSION_EXCLUDE_SUBFOLDERS
EXCLUSION_ON_READ= $item.EXCLUSION_ON_READ
}
}
foreach($oas in $oashigh){
$oashighItems += [PSCustomObject]#{
OAS_PROCESSES_LIST = $oas
}
}
$Array = #()
$Array = $exclusionItems,$oashighItems
$Array | Update-FirstObjectProperties | Export-Excel $ExcelParams -TableName Table -Show**
I'm assuming you want to join the two objects by their names, i.e. match the Process-Name with the Service-Name. For this you can loop over all processes & services, keep only those where service-name equals process-name, and use a calculated property to merge the result into one object:
$services = Get-Service;
Get-Process | ForEach-Object {$p = $_; $services |
Where-Object{$p.ProcessName -eq $_.Name} |
Select-Object Name,StartType,Status,#{n='ID';e={$p.ID}}}
The output on my machine is:
Name StartType Status ID
---- --------- ------ --
CcmExec Automatic Running 14856
CmRcService Automatic Running 5748
FusionInventory-Agent Automatic Running 5996
IBMPMSVC Automatic Running 3540
IntelAudioService Automatic Running 6104
... and so on ...

Parsing multiple valus in multiple variables

I am trying to find a way to execute a command in powershell and put each line of the result in a different variable to use them later in my script. For example if i execute the below command:
C:\> Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
I will get the below:
Name
----
HQ-LinServ-01a
HQ-Win2012-01a
HQ-Web-02a
I want to have a script that will add each line in a different variable in a script (excluding the first which is name).
how can i do that.
Thank you for your help.
You can use Set-Variable in a loop to put each value in a separate variable:
$i = 0
... | Get-Vm | Select-Objet -Expand Name | ForEach-Object {
Set-Variable -Name "vm$i" -Value $_
$i++
}
However, that usually isn't good advice. It's more common to put all names in one (array) variable:
$vmList = ...| Get-Vm | Select-Object -Expand Name
so you can access individual names via $vmList[<index>], or (if you need access by some kind of name) in a hashtable:
$i = 0
$vmList = #{}
... | Get-Vm | Select-Objet -Expand Name | ForEach-Object {
$vmList["vm$i"] = $_
$i++
}
Best practice would depend on the particular scenario you need this for, though.
Thank you for your reply,
I have tried you answer but it seems that i am using PowerCLI for VMware it does not include Select-Object -Expand (not sure i had an exception), However your answer have mad me reach to a suitable answer for this.
I have used the below and it worked fine using foreach and adding the values in an array and then reading them as below:
$p1vmname = Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
$p1vmlist = #()
foreach ($p1line in $p1vmname)
{
$p1vmlist += $p1line
}
$p1 = 0
do {
$x = $p1+1
Write-Host -BackgroundColor:Black -ForegroundColor:Yellow "vm number $x is "$p1vmlist[$p1]"."
$p1++
}
until ($p1 -eq $p1vmc)
}
However when using this the names was not totally correct as they had some additional characters as below:
vm number 1 is #{Name=HQ-Web-01a}
vm number 2 is #{Name=HQ-LinServ-01a}
vm number 3 is #{Name=HQ-Win2012-01a}
so i used split and trim to get rid of these as below and worked fine.
$p1vmname = Get-VMHost -Name hq-esxi-prod-01a.nsx.gss | Get-VM | select Name
$p1vmlist = #()
foreach ($p1line in $p1vmname)
{
$p1vmlist += $p1line
}
$p1 = 0
do {
$x = $p1+1
$p1vmlist[$p1] = ($p1vmlist[$p1]) -split("=") | Select-Object -Last 1
$p1vmlist[$p1] = $p1vmlist[$p1].trimend("}")
Write-Host -BackgroundColor:Black -ForegroundColor:Yellow "vm number $x is "$p1vmlist[$p1]"."
$p1++
}
until ($p1 -eq $p1vmc)
}
Output:
vm number 1 is HQ-Web-01a .
vm number 2 is HQ-LinServ-01a .
vm number 3 is HQ-Win2012-01a .
Thank you so much for your answer that helped me a lot.
I am really enjoying scripting now.

Powershell Array to export-csv shows System.Object[]

Having a simple issue that's only affecting export-csv output, out-gridview and results to the console are fine. Looking to capture the top 5 processes by "handles" on a set of servers.
Code is as follows:
$Servers = "Server1", "Server2", "Server3"
$OutArray = #()
ForEach ($Item in $Servers)
$Top5 = Get-Process -Computer $Item | Sort Handles -descending |Select -First 5
$OutArray += New-Object PSObject -property # {
Server = $Item
Top5 = $Top5
} #OutArray
} #ForEach
$OutArray | Export-csv Test.csv
The results of which come out looking fine via console as follows
Server Top5
------ ----
SERVER1 {#{ProcessName=svchost.exe; PercentCpuLoad=13.79}, #{ProcessName=services.exe; PercentCpuLoad=11.4}, #{ProcessName=WmiPrvSE.exe; PercentCpuLoad=10.03}, #{ProcessName=irfilcol.exe; PercentCpuLoad=9.79}...}
...However, in the csv they show as follows:
Server Top5
Server1 System.Object[]
Server2 System.Object[]
Server3 System.Object[]
I'm thinking it's because the $Top5 variable is an variable with multiple properties (5 each) for one server. How would do I correct the code so that export-csv shows the actual values?
any help appreciated!
I would like the csv results to look like the following that's shown in GRIDVIEW
Using the suggestion from BenH to review the post from Powershell legend Boe Prox, I now have the following working:
$Top5 = Get-Process -Computer $Item | Sort Handles -descending |Select -expand Handles | |Select -First 5
$new = [pscustomobject]#{ Top5 = (#($Top5) -join ',')
}
Just about got this working now:
i'd like to add more piece of formatting, where the Top5Processes have the actual CPU % used in (brackets) right now, I've got the following for output
Top2Proc Top2CPU
services.exe,BESClient.exe 32.76,16.6
However, it would be nicer output-wise, if i could combine the above two values into one, so it looks like this:
Top2Proc
Services(32.76), BesClient.exe(16.6)
Any idea how that would be done?
Use Select-Object to turn your process objects into strings before piping them to Export-Csv:
$OutArray |Select-Object Server,#{Expression={$_.Top5.Name -join ';'}} |Export-Csv test.csv
If you want that table to appear in your csv file then you would need to format the string Top5 property as such. Using Out-String will do just that
Sends objects to the host as a series of strings.
So a simple change should get you what you want.
$Top5 = Get-Process -Computer $Item |
Sort Handles -descending |
Select -First 5 |
Out-String
It will look a little ugly when not displayed with a mono-space font much like you see in Out-GridView. Also consider using .Trim() to remove the leading and trailing whitespace on your $top5.
There are other ways to tackle this. You could use the above in conjunction with Format-Table / Format-List depending what you want. In general if you want the output to be saved as it is displayed in host Out-String is something to test with.
I would have tried to add one row for each process with a the first column being the computer name. That way you would have better structured output that can be sorted or queried as needed.
ComputerName ProcessName Handles
------------ ----------- -------
Computer1 avp 54639
Computer1 OUTLOOK 7708
Computer1 RDTabs 6108
Computer1 svchost 3160
Computer1 chrome 2530
Keep in mind that you can use other methods to export this data while keeping the objects entact. Really depends the data recipeint but remeber there are other cmdlets like Export-CLIMXL and ConvertTo-JSON | Set-Content.

Adding objects to array to create multiple columns

I'm trying to get the DisplayName of all Software listed in AddRemovePrograms for each computer, add it to an array under the name of the computer, then export. Here's what I have:
$Computers = gc "C:\Get Software.txt"
$CSV = "C:\Get Software.csv"
$Results = #()
If (Test-Path $CSV) {
Remove-Item $CSV
}
Foreach($Computer in $Computers){
#Get DisplayName of Software Installed on Asset
$Software = Get-WmiObject Win32Reg_AddRemovePrograms -ComputerName $Computer | Select-Object -ExpandProperty DisplayName
$counter = 0
While ($counter -lt $Software.count){
#Create a PSObject. Loops through all software and adds to $Results.
$Obj = New-Object PSOBJECT
Add-member –inputobject $Obj –membertype Noteproperty -Name $Computer -Value $Software[$counter]
$counter++
$Results+=$Obj
}
}
$Results | Export-Csv $CSV -NoTypeInformation
Unfortunately, the output only lists the first Computer in the CSV. I've tried stepping through this to understand it, I just don't understand why I can't add the $Obj variable to $Results with a different Name Property (In this case I'm looping and making a new name based on the computer name in my .txt file). It seems to only take the first input and won't put a new column header then spit out the software. Below is an example of what I'm getting and what I'd like to get instead.
OUTPUT
A01234
Program1
Program2
Program3
EXPECTED OUTPUT
A01234 B05678 C09123
Program1 Program97 Program30
Program2 Program98 Program31
Program3 Program99 Program32
Hopefully this makes some sense. Any assistance would be appreciated, I'm clearly doing something wrong with the objects and I'm not seeing it. Thanks!
This is do-able, it just isn't quite as simple as you would think. What I would suggest is setting things up as a hashtable where the keys are your computer names and your values are an array of software titles for each computer. Then you find out how many titles the computer with the most software has (that's how many rows you'll have, so it's how many times you'll have to loop), and create a loop to make that many objects. Each object will have no properties to start, and then we add a property for each computer in the hashtable, and the value for that property will be the Nth item listed in the hashtable for that computer. Here's the code, it might make this all make more sense:
$Computers = 'A01234','B05678','C09123'
$SftwrPerCmp = [ordered]#{}
ForEach($Computer in $Computers){
$SftwrPerCmp.add($Computer,#())
#Randomly generate 7-15 strings as 'Programs' for current computer
$Software = 1..$(get-random -Maximum 15 -Minimum 7)|%{(Get-Random -inputobject $("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" -split ''|?{$_}) -count 6) -join ''}
ForEach($Title in $Software){
$SftwrPerCmp["$Computer"] += $Title
}
}
$MaxTitleCount = $SftwrPerCmp.Values|%{$_.count}|sort|select -last 1
$Results = #()
For($i=0;$i -lt $MaxTitleCount;$i++){
$Record = New-Object PSObject
$SftwrPerCmp.Keys | ForEach{Add-Member -InputObject $Record -NotePropertyName $_ -NotePropertyValue $SftwrPerCmp["$_"][$i]}
$Results += $Record
Clear-Variable Record
}
$Results|Format-Table -AutoSize
Now obviously you will not randomly generate software titles, you'll use your existing $Software = Get-WmiObject line that's in your existing code instead since that part was at least working for you. This should result in the listings that you were looking for. My code resulted in this:
A01234 B05678 C09123
------ ------ ------
CU7K5E 6GJWOB 97H1TY
7VCZ5T CIPWVK 760NKU
CHKPY0 J4B7D0 1QOSD3
2YEFR4 2VY6DM O68SKU
VI7ZQG WLJQN9 Q5VJAZ
ZQOKNV R9KZG1 H2XZK4
S8IZC4 GRSMPU BIZXKA
LAVNI0 TKBOUC K9DEFU
3U7KVO JZ3X4H
A6GVUK 18AC5H
NMI32Q H14GPJ
50KSZ6 XU0FWC
PAN5TC 9WXR5U
531M04

Out-file format

I am writing a script that after each iteration through a loop (array of selected services) it will gather the 4 values for each service that are: server name, service name, service state, and service start name
So for each iteration, I would like to output the 4 mentioned values to an external file (txt, svc, or html) such that each value will be arranged in its own column. Currently I use tab `t to arrange the values in each column but it doesn't work quite well because some service name is a lot longer or a lot shorter so it screws up the column alignment. What other approach do you suggest so all columns are aligned properly
Below is a snippet of my script on how I currently format the output to a txt file
ForEach($service in services)
$startname = $service.startname
$state = $service.state
$servicename = $service.name
write-output "$server `t $servicename `t $state `t $startname is current" | out-file -append $ScriptDirectory
If you just want to dump the results to text in a nicely-formatted way (i.e. you don't have requirements for making this CSV, or tab-delimited, or anything else besides "easy for a person to read"), then just use Format-Table -AutoSize.
AutoSize does exactly what you want - it inspects the length of all properties you are outputting, then dynamically adjusts the column width so that as much as possible is shown.
You don't explain where $server comes from, I will assume that is defined somewhere else...
$services `
| Format-Table -AutoSize #{N='Server';E={$server}},StartName,State,Name `
| Out-String `
| Out-File results.txt
Instead of using several variables, use a Powershell object to store your output. Something like this:
ForEach($service in $services) {
New-Object PSObject -Property #{
StartName = $service.startname
State = $service.state
ServiceName = $service.name
}
} | Out-File $ScriptDirectory
You may need to add a Select-Object in the chain to ensure the columns are in the correct order that you want for your final output.
If you want to keep the variables, You could try the following String formatting to space out the variable in the string evenly. In the example below the spacing is 20 characters between each value:
ForEach($service in services){
$startname = $service.startname
$state = $service.state
$servicename = $service.name
"{0,-20} | {1,-20} | {2,-20} | {3,-20}" -f $server,$servicename,$state,$startname `
| Out-File -append $ScriptDirectory
}
It's a little unclear what you're looking for as some of the properties of the object Get-Service returns don't exist as written and the code seems incomplete. Taking a guess at your intent though:
$servers = #("server1","server2");
$services = get-service -computername $servers;
$svcCollection = #();
ForEach($service in $services) {
$svccollection+=New-Object PSObject -Property #{
Servername = $service.MachineName;
StartName = $service.servicename;
State = $service.Status;
ServiceName = $service.DisplayName;
}
}
# Various output formats
$svccollection|ConvertTo-Html|Out-File -path Services.html; # Create a full HTML file
$svcCollection|Export-Csv -NoTypeInformation -Path Services.csv; # Create a "traditional" CSV file
$svcCollection|Export-Csv -Delimiter "`t" -Path Services-tab.csv; # Create a tab-delimited CSV file
$svcCollection|ConvertTo-Xml|Out-File -path Services.xml; # Create an XML file
$svcCollection|ConvertTo-Json|Out-File -path Services.js; # Create a JSON object (v3 only)