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

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.

Related

Using PoSH to get a value, strange results

I am using PoSH to query our Exchange server to return the largest mailbox by users.
My PoSH is:
$test = Get-MailboxDatabase MBX_* -Status | select Name,#{Name="NumberofUsers";Expression={(Get-Mailbox -resultsize unlimited -Database $_.name).Count}} | Sort -Property NumberofUsers | Select Name -First 1
This works in console and returns:
Name
----
MBX_2
However when I run it like this in a script and return the value, it is like this:
$test = Get-MailboxDatabase -Status MBX_*| select Name,#{Name="NumberofUsers";Expression={(Get-Mailbox -resultsize unlimited -Database $_.name).Count}} | Sort -Property NumberofUsers | Select Name -First 1
Write-Host "Using $test"
Using #<Name=MBX_2>
Why is it including the #<Name=> characters and how can I prevent it from doing that? I need to be able to grab just the returned value of "MBX_2" for the next part of my script and I am confused on how to go about this...
You are selecting a single property of the object, but still passing an object. What you need to do is use the -ExpandProperty parameter of the Select-Object cmdlet. Change your Select command to this:
| Select -ExpandProperty Name -First 1
That should give you the results that you desire.

Powershell to reformat the file content

I have a script which gets content from one file and checks for its ip. Then that is added to some other text file.
[System.Collections.ArrayList]$hlist1 = Get-Content -Path "C:\Timezone\Update\host.txt"
$hiplist = New-Object System.Collections.ArrayList
$hlist2 = New-Object System.Collections.ArrayList
ForEach ($h in $hlist1)
{
$hip = Resolve-DnsName $h
$hiplist.Add($hip)
}
$hiplist | Out-File "C:\Timezone\Update\hiplist.txt"
The file which is getting created is as shown below:
---- ---- --- ------- --------
WIN-JB2A2FS84MQ.domain.com A 1200 Answer 10.3.0.4
8
WIN-QP0BH4SD2H9.domain.com A 1200 Answer 10.3.1.1
9
I need to:
get rid of the first -------- lines.
get the entire ip in the same line (10.3.0.10)
Have tried Format-Table -Autosize, then Select -Skip 1 etc, but no luck.
How can this be achieved.? Please note that the code works fine as expected when it is ran manually, but throws this issue when executed using task scheduler.
Edit Based on Matt's answer
Now the text file contains:
"Address","IPAddress","QueryType","IP4Address","Name","Type","CharacterSet","Section","DataLength","TTL"
"10.3.0.48","10.3.0.48","A","10.3.0.48","WIN-JB2A2FS84MQ.domain.com","A","Unicode","Answer","4","1200"
"10.3.1.19","10.3.1.19","A","10.3.1.19","WIN-QP0BH4SD2H9.domain.com","A","Unicode","Answer","4","1200"
Peter-sal's reply output:
Name Type TTL Section IPAddress
---- ---- --- ------- ---------
WIN-JB2A2FS84MQ.domain.com A 1200 Answer 10.3.0.48
WIN-QP0BH4SD2H9.domain.com A 1200 Answer 10.3.1.19
But again on top of Name there's one space. I need to delete everything present before WIN-JB2.....
I cannot test perfectly but I would like to come back to an earlier comment of mine. Resolve-DNSName returns objects so their output is better destined for something object aware. Export-CSV should be preferable here.
$hlist1 = Get-Content -Path "C:\Timezone\Update\host.txt"
$hlist1 | ForEach-Object{Resolve-DnsName $_} |
Export-Csv "C:\Timezone\Update\hiplist.txt" -NoTypeInformation
I normally don't like this but if you prefer you should be able to use the Format-table output now. This seems to be more inline with what you are looking for.
$hlist1 = Get-Content -Path "C:\Timezone\Update\host.txt"
$hlist1 | ForEach-Object{Resolve-DnsName $_} |
Format-Table -HideTableHeaders | Select-Object -Skip 1 |
Out-File "C:\Timezone\Update\hiplist.txt" -Width 200
Perhaps you prefer that output. The header should be removed now as well as a blank line in the beginning.
That creates some white-space before and after the output. Simple solution is to wrap that up in a Trim()
$hlist1 = Get-Content -Path "C:\Timezone\Update\host.txt"
$results = ($hlist1 | ForEach-Object{Resolve-DnsName $_} |
Format-Table -HideTableHeaders |
Out-string).Trim()`
$results | Out-File "C:\Timezone\Update\hiplist.txt" -Width 200`

Powershell System.Array to CSV file

I am having some difficulty getting an Export-Csv to work. I am creating an array like this...
[pscustomobject] #{
Servername = $_.Servername
Name = $_.Servername
Blk = ""
Blk2 = ""
Method = "RDP"
Port = "3389"
}
The issue I have is when I try to export that to a CSV I get garbage that looks like this...
"9e210fe47d09416682b841769c78b8a3",,,,,
I have read a ton of articles addressing this issue, but I just don't understand how to get the data right.
For testing, I built a CSV file w/ the servernames, and read it in, and the following works in PS4:
$serverList = import-csv "datafile.csv"
$AllObjects = #()
$serverList | ForEach-Object {
$AllObjects += [pscustomobject]#{
Servername = $_.Servername
Name = $_.Servername
Blk = ""
Blk2 = ""
Method = "RDP"
Port = "3389"
}
}
$AllObjects | Export-Csv -Path "outfile.csv" -NoTypeInformation
This happens when you try to pipe out from any of the Format-* commands.
The Format-List, Format-Table and Format-Wide cmdlets are special in PowerShell, in that they're meant to consume the pipeline, and transform it for display in the console. So, you can't pipe from FL, FT or FW into Export-csv. As Don Jones says "Format On the Right".
Don't believe me? Observe, as I run Get-Process, send it through Format-Table and then convert to Csv.
gps | ft | ConvertTo-Csv
#TYPE Microsoft.PowerShell.Commands.Internal.Format.FormatStartData
"ClassId2e4f51ef21dd47e99d3c952918aff9cd","pageHeaderEntry","pageFooterEntry","autosizeInfo","shapeInfo","groupingEntry"
"033ecb2bc07a4d43b5ef94ed5a35d280",,,,"Microsoft.PowerShell.Commands.Internal.Format.TableHeaderInfo",
"9e210fe47d09416682b841769c78b8a3",,,,,
"27c87ef9bbda4f709f6b4002fa4af63c",,,,,
"27c87ef9bbda4f709f6b4002fa4af63c",,,,,
"27c87ef9bbda4f709f6b4002fa4af63c",,,,,
It's even the same string! Why do we get this string? I really wish I knew, but I think it has something to do with the way the Format-* commands convert objects into text instructions for display in the console.
If you REALLY love the way your Format-Table looks, send it to Out-File, which is the only way to redirect the output of a Format-* command.
Another message is to use Tee-Object to dump a copy of your pipe to a variable, and then send that out to Export.
Get-Process | Tee-Object -Variable ExportMe | Format-Table
$exportMe | export-Csv .\Export.csv
I call this the 'have your cake and eat it too approach'.
Use Select-Object to prevent the bad CSV export.
Example:
Get-Services | Select-Object * | export-csv -Path C:\log.csv

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

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.

Change powershell script to output without ellipses (...)

I need some help with the output of the following script so the output doesn't show with the ellipses (...).
I tried to insert | Format-Table -Wrap -AutoSize but I just can't seem to get it right.
clear-host Add-PSSnapin microsoft.sharepoint.powershell -ErrorAction SilentlyContinue
$services = new-object system.collections.sortedlist
$servers = (get-spfarm).servers
foreach ($server in $servers) {
foreach($service in $server.serviceinstances)
{
if ($service.status = "Online")
{
$s = $service.typename
if ($services.contains($s))
{
$serverlist = $services[$s]
$servername = $server.name
$services[$s] = "$serverlist - $servername"
}
else
{
$services[$s] = $server.name
}
}
} }
$services
output:
Name Value
---- -----
Access Database Service SE5APP - SE5FE - SE7FE - FAQ3
Application Discovery **and L...** SE5APP - SE5FE - SE7FE - FAQ3
Application Registry Service SE5APP - SE5FE - SE7FE - FAQ3
Either Format-List (fl) or Format-Table -auto (ft -auto) should help here.
$services | fl
OR
$services | ft -auto
I came across this post and would like to add some information, as the accepted solution did not resolve my problem and I'm sure others may find the following information useful:
Quick Story: Running commands using Microsoft Online Services Module with Powershell, much of the results were continually be retrieved as truncated with data cutoff and missing as an ellipsis (...).
The fix: As explained in this post by Greig, I inevitably came to the conclusion $FormatEnumerationLimit=-1 is the unlimate solution to the problem. Using any variant of Format-Wide, Format-List, Format-Table, Format-Custom, -AutoSize, Out-String -Width, etc. require a hefty amount of additional considerations/code. In the case where all you want is to see all the data being returned, regardless of columns, arrays, etc., $FormatEnumerationLimit=-1 ensures you will get everything and you don't need to mess around.
Additional information, as credited in Greig's post include:
PowerShell Quick Tip: Creating wide tables with PowerShell, where the author explains:
If you have a specific property that contains a collection of items,
that property may still show an ellipsis in the file produced here if
the number of items in that collection exceeds the number assigned to
the built-in $FormatEnumerationLimit variable.
...and that "passing the results to | Format-Table -Property * [will] show all of the columns." But content from the columns may still be truncated ("PowerShell truncates table output by default"), and that even using | Format-Table -Property * -AutoSize will be limited by your screen buffer
("Auto-sized tables are limited to the width of your screen buffer"). The solution offered, before the absolute $FormatEnumerationLimit=-1, seems to be using | Format-Table -Property * -AutoSize in conjunction with | Out-String -Width 4096 or whatever width you require.
Using Format Commands to Change Output View provides some more delailed documentation on the Format cmdlets: Format-Wide, Format-List, and Format-Table.
What I do in this situation is to create a format description then use that as an argument to my Format-Table command. I've developed a function (Get-MaxLength) to examine the data field with the longest data (helps to have this at the end of the format description) and set the width in the format description with the value it returns. You can see the calculations in the code below. Notice the Number value for the Intel(4) Management Engine Interface. Also notice the use of -Wrap on the Format-Table command. This concept can be modified to calculate all fields widths or just the last one, it's just a little math.
Function Get-MaxLength {
<#
.SYNOPSIS
Finds the length of the longest item in collection.
.DESCRIPTION
Use this Function to get the length of the longest item in a
collection for use in format strings or other places where
needed.
.PARAMETER TestObj
The qualified object to be tested. See example!
.Parameter MinLen
The minimum length of the item (if using for formatting) which
should be the Label (title) length. Note if the object item
being tested does not have a Length property you MUST specify
the label length!
.OUTPUTS
Returns a numerical value
.EXAMPLE
$NameLen = Get-MaxLength -TestObj $DotNet.PSChildName
$VerLen = Get-MaxLength -TestObj $DotNet.Version
$RNLen = Get-MaxLength -TestObj $DotNet.Release -MinLen 11
#--- .Net Information ---
$fmtDotNet =
#{Expression={$_.PSChildName};Label=".Net Type";Width=$NameLen},
#{Expression={$_.Version};Label="Version No:";Width=$VerLen},
#{Expression={$_.Release};Label="Release No:";Width=$RNLen}
$Dotnet | Format-Table $fmtDotNet
#>
Param(
[Parameter(Mandatory=$True)]
[object] $TestObj,
[Parameter(Mandatory=$False)]
[int] $MinLen = 0,
[Parameter(Mandatory=$False)]
[int] $MaxLen = 0
)
$ErrorActionPreference = "SilentlyContinue"
foreach ($x in $TestObj) {
If ($x.Trim().length -gt $MinLen) {
$MinLen = $x.Trim().length
}
}
If ($MaxLen -ne 0) {
If ($MinLen -gt $MaxLen) {
$MinLen = $MaxLen
}
}
$ErrorActionPreference = "Continue"
Return ,$MinLen
} #End Function ----------- Get-MaxLength -------------------
$OstrWidth = 80
$DriverInfo =
Get-CimInstance -ClassName 'Win32_PNPSignedDriver' |
Where-Object -Property DriverProviderName -ne "Microsoft" |
Where-Object -Property DeviceName -ne -Value $Null |
Sort-Object -Property DeviceName -Unique
$DriverCnt = $DriverInfo.Count
$DVLen =
Get-MaxLength -TestObj $DriverInfo.DriverVersion -MinLen 14
$DDLen = $OstrWidth - $DVLen
$fmtDRVR = #{Label="`nDriver Description";Width=$DDLen;
Expression={$_.DeviceName}},
#{Label="Version Number"; Width=$DVLen;
Expression={$_.DriverVersion}}
$DrvTitle = "$($DriverCnt) Non-Windows Unique Drivers and " +
"Version Numbers:" | Out-String
$DriverInfo =
$DriverInfo | Format-Table -Property $fmtDRVR -Wrap |
Out-String -Width $OStrWidth
Sample Output:
Driver Description Number
------------------- -------------
Alcor Micro USB 2.0 Card Reader 2.0.150.10135
ASMedia USB3.1 eXtensible Host Controller 1.16.42.1
...
Intel(R) HD Graphics 630 21.20.16.4550
Intel(R) Management Engine Interface 1914.12.0.125
6
Intel(R) Ready Mode Technology Device 1.2.0.0
...
Realtek Audio 6.0.1.8248
Samsung NVMe Controller 3.0.0.1802