Formatting the result set in PowerShell - powershell

Below is my query to get the space details:
$myarray += $finaldata
foreach ($dr in $Drive)
{
Switch ($dr)
{
E { $myarray | select Servername,EDriveFreeSpace | ft}
H { $myarray | select Servername,HDriveFreeSpace | ft}
I { $myarray | select Servername,IDriveFreeSpace | ft}
O { $myarray | select Servername,ODriveFreeSpace | ft}
}
}
}
if(!$Drive)
{
$myarray |ft
}
}
Output:
Servername EDriveFreeSpace
CVRFDGXXXX1 734
CVRFDGXXXX2 986
Servername ODriveFreeSpace
CVRFDGXXXX1 547
CVRFDGXXXX2 718
Can I get the result set in below format:
Servername EDriveFressSpace ODriveFressSpace
CVRFDGXXXX1 734 547
CVRFDGXXXX2 986 718
Any changes I need to do to get output in the above format, Any suggestions would be appreciated.
Thanks

If I understand correctly, you're looking for this (using the full name of the cmdlet that alias ft refers to, Format-Table):
$myArray | Format-Table -Property ServerName, [EHIO]FreeDriveFreeSpace
This assumes that at least the first object in $myArray has all the properties whose name the wildcard expression [EHIO]FreeDriveFreeSpace matches.
If that condition isn't met, you can use an aux. Select-Object call to construct custom objects that do have all these properties:
$myArray |
Select-Object -Property ServerName, EFreeDriveFreeSpace, HFreeDriveFreeSpace, IFreeDriveFreeSpace, OFreeDriveFreeSpace |
Format-Table

Related

How to use PS calculated properties to collate data into a cell?

I'm stumped on this one. I have a PS custom object with strings only and I want to build a report where I'm outputting strings of data into a new pipeline output object.
$myObjectTable |
Select-Object #{
n = "OldData";
e = {
$_ | Select-Object name, *_old | Format-List | Out-String
}
},
#{
n = "NewData";
e = {
$_ | Select-Object name, *_new | Format-List | Out-String
}
}
Running this produces blank output.
I tried running the code above with just the $_ object in the expressions, but I only got ... as the output. Wrapping the expressions in parenthesis did not change the output.
The ... as property value means that the value is either a multi-line string or it just didn't fit in a tabular format. See Using Format commands to change output view more details.
You can fix those empty lines added by Out-String using Trim. Then if you want to properly display this object having multi-line property values, Format-Table -Wrap will be needed.
Here is a little example:
[pscustomobject]#{
Name = 'foo'
Something_Old = 123
Something_New = 456
} | ForEach-Object {
[pscustomobject]#{
OldData = $_ | Format-List name, *_old | Out-String | ForEach-Object Trim
NewData = $_ | Format-List name, *_new | Out-String | ForEach-Object Trim
}
} | Format-Table -Wrap
Resulting object would become:
OldData NewData
------- -------
Name : foo Name : foo
Something_Old : 123 Something_New : 456

ExChange columns with rows in powershell

after a couple of hours I need a help with PS script.
I have an array where :
computername Folder Size
PC1 A 123
PC1 B 18
PC1 C 356
PC2 A 589
PC2 B 58
PC2 C 59
PC2 D 89
I need somehow exchange the columns with rows where result will be as following:
Folder_A Folder_B Folder_C Folder_D
PC1 123 18 356
PC2 589 58 59 89
Can you help me with that ?
If we assume $array does contain your array of objects with the properties specified in your columns, you can do the following:
$output = $array | Group-Object computername | Foreach-Object {
$hash = [ordered]#{Computer=$_.Name}
$_.Group | Foreach-Object {
$hash.Add(('Folder_'+$_.Folder),$_.Size)
}
[pscustomobject]$hash
}
}
$output | Format-List
$output will contain an array of objects. Since those objects will likely have different properties, you may have display issues in your console. For example, PC1 will not have Folder_D and since it is the first item in the array, the default output for all remaining objects will be missing Folder_D. This is why I added the Format-List command, which should be used for display purposes only and not for further processing.
Regarding missing columns in the output, you have a couple of options. One, you can sort your objects to where the one with the most properties is first in the list. Two, you can use additional logic to predetermine your full list of properties and then apply those properties to all objects. See below for predetermining the folder list:
$Folders = $array.Folder | Select -Unique
$output = $array | Group-Object computername | Foreach-Object {
$hash = [ordered]#{Computer=$_.Name}
foreach ($Folder in $Folders) {
$hash.Add(('Folder_'+$Folder),$null)
}
$_.Group | Foreach-Object {
$hash.$('Folder_'+$_.Folder) = $_.Size
}
[pscustomobject]$hash
}
$output | Format-Table

How to export two variables into same CSV as joined via PowerShell?

I have a PowerShell script employing poshwsus module like below:
$FileOutput = "C:\WSUSReport\WSUSReport.csv"
$ProcessLog = "C:\WSUSReport\QueryLog2.txt"
$WSUSServers = "C:\WSUSReport\Computers.txt"
$WSUSPort = "8530"
import-module poshwsus
ForEach ($Server in Get-Content $WSUSServers)
{
& connect-poshwsusserver $Server -port $WSUSPort | out-file $ProcessLog -append
$r1 = & Get-PoshWSUSClient | select #{name="Computer";expression={$_.FullDomainName}},#{name="LastUpdated";expression={if ([datetime]$_.LastReportedStatusTime -gt [datetime]"1/1/0001 12:00:00 AM") {$_.LastReportedStatusTime} else {$_.LastSyncTime}}}
$r2 = & Get-PoshWSUSUpdateSummaryPerClient -UpdateScope (new-poshwsusupdatescope) -ComputerScope (new-poshwsuscomputerscope) | Select Computer,NeededCount,DownloadedCount,NotApplicableCount,NotInstalledCount,InstalledCount,FailedCount
}
What I need to do is to export CSV outpout including the results with the columns (like "inner join"):
Computer, NeededCount, DownloadedCount, NotApplicableCount, NotINstalledCount, InstalledCount, FailedCount, LastUpdated
I have tried to use the line below in foreach, but it didn't work as I expected.
$r1 + $r2 | export-csv -NoTypeInformation -append $FileOutput
I appreciate if you may help or advise.
EDIT --> The output I've got:
ComputerName LastUpdate
X A
Y B
X
Y
So no error, first two rows from $r2, last two rows from $r1, it is not joining the tables as I expected.
Thanks!
I've found my guidance in this post: Inner Join in PowerShell (without SQL)
Modified my query accordingly like below, works like a charm.
$FileOutput = "C:\WSUSReport\WSUSReport.csv"
$ProcessLog = "C:\WSUSReport\QueryLog.txt"
$WSUSServers = "C:\WSUSReport\Computers.txt"
$WSUSPort = "8530"
import-module poshwsus
function Join-Records($tab1, $tab2){
$prop1 = $tab1 | select -First 1 | % {$_.PSObject.Properties.Name} #properties from t1
$prop2 = $tab2 | select -First 1 | % {$_.PSObject.Properties.Name} #properties from t2
$join = $prop1 | ? {$prop2 -Contains $_}
$unique1 = $prop1 | ?{ $join -notcontains $_}
$unique2 = $prop2 | ?{ $join -notcontains $_}
if ($join) {
$tab1 | % {
$t1 = $_
$tab2 | % {
$t2 = $_
foreach ($prop in $join) {
if (!$t1.$prop.Equals($t2.$prop)) { return; }
}
$result = #{}
$join | % { $result.Add($_,$t1.$_) }
$unique1 | % { $result.Add($_,$t1.$_) }
$unique2 | % { $result.Add($_,$t2.$_) }
[PSCustomObject]$result
}
}
}
}
ForEach ($Server in Get-Content $WSUSServers)
{
& connect-poshwsusserver $Server -port $WSUSPort | out-file $ProcessLog -append
$r1 = & Get-PoshWSUSClient | select #{name="Computer";expression={$_.FullDomainName}},#{name="LastUpdated";expression={if ([datetime]$_.LastReportedStatusTime -gt [datetime]"1/1/0001 12:00:00 AM") {$_.LastReportedStatusTime} else {$_.LastSyncTime}}}
$r2 = & Get-PoshWSUSUpdateSummaryPerClient -UpdateScope (new-poshwsusupdatescope) -ComputerScope (new-poshwsuscomputerscope) | Select Computer,NeededCount,DownloadedCount,NotApplicableCount,NotInstalledCount,InstalledCount,FailedCount
Join-Records $r1 $r2 | Select Computer,NeededCount,DownloadedCount,NotApplicableCount,NotInstalledCount,InstalledCount,FailedCount, LastUpdated | export-csv -NoTypeInformation -append $FileOutput
}
I think this could be made simpler. Since Select-Object's -Property parameter accepts an array of values, you can create an array of the properties you want to display. The array can be constructed by comparing your two objects' properties and outputting a unique list of those properties.
$selectProperties = $r1.psobject.properties.name | Compare-Object $r2.psobject.properties.name -IncludeEqual -PassThru
$r1,$r2 | Select-Object -Property $selectProperties
Compare-Object by default will output only differences between a reference object and a difference object. Adding the -IncludeEqual switch displays different and equal comparisons. Adding the -PassThru parameter outputs the actual objects that are compared rather than the default PSCustomObject output.

Parametrization of a pipe with a function

I'm discovering powershell with some joy and some frustation, and I'm currently hitting a wall with respect to my powershell abilities.
I'd like to create a pipe function to summarize member types of a collection of objects.
Without a function, the working code looks like this:
get-process |
foreach { get-member -inputobject $_ } |
foreach { "[$($_.membertype)]$($_.name)" } |
group-object |
sort count, name
Now, my current attempt for a function achieving this is:
function get-membersummary {
process {
get-member -inputobject $_ |
foreach { "[$($_.membertype)]$($_.name)" } |
group-object |
sort count, name
}
}
it should be used like this:
&$anything | get-membersummary
The obvious problem is that the code in the "process" statement is called for each element, which means that the grouping is performed on each item. What I would like is the first result of the whole two lines to be subjected to the grouping.
Is it possible to achieve this in powershell without using an array variable that would induce memory inefficiency ?
Also, I'm pretty sure that this question as already been asked, but I couldn't find the correct words to express it.
You can use SteppablePipeline, but for sorting you have to collect whole input before start sorting it, so using array here hardly induce much more "memory inefficiency" than you already have.
function get-membersummary {
begin {
$Pipeline={
&foreach { get-member -inputobject $_ } |
foreach { "[$($_.membertype)]$($_.name)" } |
group-object|
sort count, name
}.GetSteppablePipeline()
$Pipeline.Begin($MyInvocation.ExpectingInput,$ExecutionContext)
}
process {
if($MyInvocation.ExpectingInput){
$Pipeline.Process($_)
}else{
$Pipeline.Process()
}
}
end {
$Pipeline.End()
$Pipeline.Dispose()
}
}
You can't group and sort until all data is received, so you need to wait until the end {} block in the function. Two examples:
#process using pipeline, but wait until end to group and sort
function get-membersummary {
begin { $res = #() }
process {
$res += Get-Member -inputobject $_ |
ForEach-Object { "[$($_.MemberType)]$($_.Name)" }
}
end {
$res | Group-Object | Sort-Object Count, Name | Select-Object Count, Name
}
}
#do everything after all objects have arrived
function get-membersummary2 {
end {
#in process { }, $input is the object in the pipeline. in end { } it is a collection of all the objects.
$input | % {
Get-Member -InputObject $_ |
ForEach-Object { "[$($_.MemberType)]$($_.Name)" }
} | Group-Object | Sort-Object Count, Name | Select-Object Count, Name
}
}
Get-Process | get-membersummary | ft -AutoSize
#Get-Process | get-membersummary2 | ft -AutoSize
Output:
Count Name
----- ----
75 [AliasProperty]Handles
75 [AliasProperty]Name
75 [AliasProperty]NPM
75 [AliasProperty]PM
75 [AliasProperty]VM
75 [AliasProperty]WS
75 [Event]Disposed
75 [Event]ErrorDataReceived
75 [Event]Exited
75 [Event]OutputDataReceived
....
On a general note, you should avoid using Group-Object and Sort-Object in functions as they break the flow of the pipeline. Select-Object (which I added this time) should also be avoided because it destroys the original objects. I understand the choice in this scenario, but be careful not the use them too much. You could have written a function or filter to process the objects, and then call group and sort manually when needed, like:
filter get-membersummary3 {
$_ |
Get-Member |
ForEach-Object { "[$($_.MemberType)]$($_.Name)" }
}
Get-Process | get-membersummary3 | Group-Object | Sort-Object Count, Name | Select-Object Count, Name

PowerShell - filtering for unique values

I have an input CSV file with a column containing information similar to the sample below:
805265
995874
805674
984654
332574
339852
I'd like to extract unique values into a array based on the leading two characters, so using the above sample my result would be:
80, 99, 98, 33
How might I achieve this using PowerShell?
Use Select-Object and parameter -unique:
$values =
'805265',
'995874',
'805674',
'984654',
'332574',
'339852'
$values |
Foreach-Object { $_.Substring(0,2) } |
Select-Object -unique
If conversion to int is needed, then just cast it to [int]:
$ints =
$values |
Foreach-Object { [int]$_.Substring(0,2) } |
Select-Object -unique
I'd use the Group-Object cmdlet (alias group) for this:
Import-Csv foo.csv | group {$_.ColumnName.Substring(0, 2)}
Count Name Group
----- ---- -----
2 80 {805265, 805674}
1 99 {995874}
1 98 {984654}
2 33 {332574, 339852}
You might use a hash table:
$values = #(805265, 995874, 805674, 984654, 332574, 339852)
$ht = #{}
$values | foreach {$ht[$_ -replace '^(..).+','$1']++}
$ht.keys
99
98
33
80
You could make a new array with items containing the first two characters and then use Select-Object to give you the unique items like this:
$newArray = #()
$csv = Import-Csv -Path C:\your.csv
$csv | % {
$newArray += $_.YourColumn.Substring(0, 2)
}
$newArray | Select-Object -Unique
Just another option instead of using Select-Object -unique would be to use the Get-Unique cmdlet (or its alias gu; see the detailed description here) as demonstrated below:
$values = #(805265, 995874, 805674, 984654, 332574, 339852)
$values | % { $_.ToString().Substring(0,2) } | Get-Unique
# Or the same using the alias
$values | % { $_.ToString().Substring(0,2) } | gu