Format-Table out of Two Lists? - powershell

I've had this idea about getting the output from 2 separate functions, that return a PSCustomObject as a list, and formatting them into one table. My problem is simple... I don't know how to do it. lol
With the various of combinations that I tried, here's whats given me some promising results:
$Var1 = [PSCustomObject]#{
UserName = $env:USERNAME
Stuff1 = 'stuff1'
} | Format-List | Out-String -Stream
$Var2 = [PSCustomObject]#{
ComputerName = $env:COMPUTERNAME
Stuff2 = 'stuff2'
} | Format-List | Out-String -Stream
[PSCustomObject]#{
TableOne = $Var1.Trim().Foreach({$_})
TableTwo = $Var2.Trim()
} | Format-Table -AutoSize
The output:
TableOne TableTwo
-------- --------
{, , UserName : Abraham, Stuff1 : stuff1...} {, , ComputerName : DESKTOP-OEREJ77, Stuff2 : stuff2...}
I say promising in the respect that it shows the actual content of $var1 and 2, whereas my other attempts didn't. I also left the .foreach() operator there to show one of the many many different tricks I tried to get this working. For a quick second I thought the Out-String cmdlet would've done the trick for me, but was unsuccessful.
Has anyone ever done something similar to this?
EDIT:
Nevermind, I figured it out.
Used a for loop to iterate through each line assigning it the the PSCustomObject one at a time. Also used the .Where() operator to remove white spaces, and compared the two arrays to find the largest number to use it as the count.
$Var1 = $([PSCustomObject]#{
UserName = $env:USERNAME
Stuff1 = 'stuff1'
} | Format-List | Out-String -Stream).Where{$_ -ne ''}
$Var2 = $([PSCustomObject]#{
ComputerName = $env:COMPUTERNAME
Stuff2 = 'stuff2'
ExtraStuff = 'More'
} | Format-List | Out-String -Stream).Where{$_ -ne ''}
$Count = ($Var1.Count, $Var2.Count | Measure-Object -Maximum).Maximum
$(for($i=0;$i -lt $Count; $i++) {
[PSCustomObject]#{
TableOne = $Var1[$i]
TableTwo = $Var2[$i]
}
}) | Format-Table -AutoSize
Output:
TableOne TableTwo
-------- --------
UserName : Abraham ComputerName : DESKTOP-OEREJ77
Stuff1 : stuff1 Stuff2 : stuff2
ExtraStuff : More

It's an interesting way to format two collections with corresponding elements.
To indeed support two collections with multiple elements, a few tweaks to your approach are required:
# First collection, containing 2 sample objects.
$coll1 =
[PSCustomObject] #{
UserName = $env:USERNAME
Stuff1 = 'stuff1'
},
[PSCustomObject] #{
UserName = $env:USERNAME + '_2'
Stuff1 = 'stuff2'
}
# Second collection; ditto.
$coll2 =
[PSCustomObject] #{
ComputerName = $env:COMPUTERNAME
Stuff2 = 'stuff2'
ExtraStuff = 'More'
},
[PSCustomObject]#{
ComputerName = $env:COMPUTERNAME + '_2'
Stuff2 = 'stuff2_2'
ExtraStuff = 'More_2'
}
# Stream the two collections in tandem, and output a Format-List
# representation of each object in a pair side by side.
& {
foreach ($i in 0..([Math]::Max($coll1.Count, $coll2.Count) - 1)) {
[PSCustomObject] #{
TableOne = ($coll1[$i] | Format-List | Out-String).Trim() + "`n"
TableTwo = ($coll2[$i] | Format-List | Out-String).Trim() + "`n"
}
}
} | Format-Table -AutoSize -Wrap
The above ensures that multiple objects are properly placed next to each other, and yields something like the following:
TableOne TableTwo
-------- --------
UserName : jdoe ComputerName : WS1
Stuff1 : stuff1 Stuff2 : stuff2
ExtraStuff : More
UserName : jdoe_2 ComputerName : WS1_2
Stuff1 : stuff2 Stuff2 : stuff2_2
ExtraStuff : More_2

Related

Powershell - Display contents of two tables next to each other

I am attempting to display information for administrators processing user changes to easily see what changes are being made to certain fields in Active Directory users. I've written a PowerShell script to process these changes, but I'm having trouble formatting the output for the administrators who are running the script. See this post for a similar, but different problem.
My code:
$output = [pscustomobject]#{'UserName' = $user.samAccountName; 'FirstName' = $user.GivenName;'LastName' = $user.Surname;'DisplayName' = $user.DisplayName;'UPN' = $user.UserPrincipalName;'E-Mail_Address' = $user.proxyAddresses;'Title' = $user.Title;'Office' = $user.PhysicalDeliveryOfficeName;'Dept' = $user.Department;}
$output[$index++] | Add-Member -NotePropertyMembers #{'NewUserName' = $samAccountName; 'NewFirstName' = $firstName;'NewLastName' = $lastname;'NewDisplayName' = $displayName;'NewUPN' = $UPN;'NewE-Mail_Address' = $SMTP1;'NewTitle' = $Title;'Newoffice' = $office;'NewDept' = $department;}
$output
Output:
UserName : FirstLast
FirstName : First
LastName : Last
DisplayName : First Last
UPN : FirstLast#company.com
E-Mail_Address : SMTP:FirstLast#company.com
Title : CEO
Office : HQ
Dept : Executives
NewDept : Maintenance
NewE-Mail_Address : SMTP:FirstLast#company.com
NewOffice : The Dump
NewFirstName : First
NewLastName : Last
NewUserName : FirstLast
NewDisplayName : First Last
NewUPN : FirstLast#company.com
NewTitle : Trash Collector
Desired Output:
UserName : FirstLast NewUserName : FirstLast
FirstName : First NewFirstName : First
LastName : Last NewLastName : Last
DisplayName : First Last NewDisplayName : First Last
UPN : FirstLast#company.com NewUPN : FirstLast#company.com
E-Mail_Address : SMTP:FirstLast#company.com NewE-Mail_Address : SMTP:FirstLast#company.com
Title : CEO NewTitle : Trash Collector
Office : HQ Newoffice : The Dump
Dept : Executives NewDept : Maintenance
Below will create the two Format-List outputs next to eachother.
Because more than likely this will not fit the width of the console screen, the output is captured first and output using Out-String with a high number for parameter -Width.
Like this, you can also save to text file.
$outputOld = [pscustomobject]#{
'UserName' = $user.samAccountName; 'FirstName' = $user.GivenName;
'LastName' = $user.Surname;'DisplayName' = $user.DisplayName;
'UPN' = $user.UserPrincipalName;'E-Mail_Address' = $user.proxyAddresses;
'Title' = $user.Title;'Office' = $user.PhysicalDeliveryOfficeName;
'Dept' = $user.Department}
$outputNew = [pscustomobject]#{
'NewUserName' = $samAccountName; 'NewFirstName' = $firstName;
'NewLastName' = $lastname;'NewDisplayName' = $displayName;'NewUPN' = $UPN;
'NewE-Mail_Address' = $SMTP1;'NewTitle' = $Title;'Newoffice' = $office;
'NewDept' = $department}
# capture the Format-List output of each object and split into a string array
$outLeft = #(($outputOld | Format-List | Out-String) -split '\r?\n')
$outRight = #(($outputNew | Format-List | Out-String) -split '\r?\n')
# determine the maximum length of each string in the list that goes to the left
$maxLength = ($outLeft | Measure-Object -Property Length -Maximum).Maximum
# get the maximum number of lines
$maxLines = [math]::Max($outLeft.Count, $outRight.Count)
# collect the combined output
$result = for ($i = 0; $i -lt $maxLines; $i++) {
$left = if ($i -lt $outLeft.Count) { "{0,-$maxLength}" -f $outLeft[$i] } else { ' ' * $maxLength }
$right = if ($i -lt $outRight.Count) { $outRight[$i] } else {''}
('{0} {1}' -f $left, $right).TrimEnd()
}
# display on screen
$result | Out-String -Width 1000
# save to text file
$result | Out-String -Width 1000 | Set-Content -Path 'D:\Test\output.txt'
This doesn't produce exactly the same character-by-character output as you're after, but it does produce something similar using the built-in formatting methods without lots of stringifying of values.
First, set up some test data, which would need a small change to your sample code to generate as two objects, instead of your combined $output variable:
$oldValues = [pscustomobject] [ordered] #{
"UserName" = "FirstLast"
"FirstName" = "First"
"LastName" = "Last"
"DisplayName" = "First Last"
"UPN" = "FirstLast#company.com"
"E-Mail_Address" = "SMTP:FirstLast#company.com"
"Title" = "CEO"
"Office" = "HQ"
"Dept" = "Executives"
};
$newValues = [pscustomobject] [ordered] #{
"NewDept" = "Maintenance"
"NewE-Mail_Address" = "SMTP:FirstLast#company.com"
"NewOffice" = "The Dump"
"NewFirstName" = "First"
"NewLastName" = "Last"
"NewUserName" = "FirstLast"
"NewDisplayName" = "First Last"
"NewUPN" = "FirstLast#company.com"
"NewTitle" = "Trash Collector"
};
Next, we map the variables into an array of PropertyName, OldValue, NewValue objects:
$rows = $oldValues.psobject.properties `
| foreach-object {
$property = $_;
[pscustomobject] [ordered] #{
"PropertyName" = $property.Name
"OldValue" = $property.Value
"NewValue" = $newValues.("New" + $property.Name)
}
};
And then we can format it using the built-in cmdlets:
$rows | format-table
# PropertyName OldValue NewValue
# ------------ -------- --------
# UserName FirstLast FirstLast
# FirstName First First
# LastName Last Last
# DisplayName First Last First Last
# UPN FirstLast#company.com FirstLast#company.com
# E-Mail_Address SMTP:FirstLast#company.com SMTP:FirstLast#company.com
# Title CEO Trash Collector
# Office HQ The Dump
# Dept Executives Maintenance
The nice thing is you can adjust things like column widths, word wrap, etc just using the Format-Table parameters, or even use Format-List instead:
$rows | format-list
# ... etc ...
#
# PropertyName : Title
# OldValue : CEO
# NewValue : Trash Collector
#
# PropertyName : Office
# OldValue : HQ
# NewValue : The Dump
#
# ... etc ...
And you can filter it to show just changed values:
$rows | where-object { $_.NewValue -ne $_.OldValue } | format-table
# PropertyName OldValue NewValue
# ------------ -------- --------
# Title CEO Trash Collector
# Office HQ The Dump
# Dept Executives Maintenance
It relies on the property names being in the format "Xyz -> NewXyz", but there's ways around that if it's not true in all cases...
you can use ANSI ESCAPE codes to apply decorations. This sample is based in pscustomobject with all values, but you can create two pscustomobject to make clear the code.
Notes:
Change ESC to $([char]27) or `e in Ps6 (thank you Mr. mclayton)
I prefer use Add-Member item per item.
Tested with PowerShell 5.1 ($PSVersionTable command).
$output = [pscustomobject]#{'UserName' = 'User1'; 'FirstName' = '$user.GivenName';'LastName' = '$user.Surname';'DisplayName' = '$user.DisplayName';'UPN' = '$user.UserPrincipalName';'E-Mail_Address' = '$user.proxyAddresses';'Title' = '$user.Title';'Office' = '$user.PhysicalDeliveryOfficeName';'Dept' = '$user.Department';}
$output | Add-Member -NotePropertyMembers #{'NewUserName' = 'User2'}
$output | Add-Member -NotePropertyMembers #{'NewFirstName' = '$firstName'}
$output | Add-Member -NotePropertyMembers #{'NewLastName' = '$lastname'}
$output | Add-Member -NotePropertyMembers #{'NewDisplayName' = '$displayName'}
$output | Add-Member -NotePropertyMembers #{'NewUPN' = '$UPN'}
$output | Add-Member -NotePropertyMembers #{'NewE-Mail_Address' = '$SMTP1'}
$output | Add-Member -NotePropertyMembers #{'NewTitle' = '$Title'}
$output | Add-Member -NotePropertyMembers #{'Newoffice' = '$office'}
$output | Add-Member -NotePropertyMembers #{'NewDept' = '$department'}
# Counter for column 1 and column 2
$Counter =0
$Counter1 =0
# Temporally variables
$Text
$Label
# Do the magic
foreach( $property in $output.psobject.properties.name )
{
if ( $Counter -lt 9 )
{
$Counter = $Counter +1
$Text = $output.$property
$Label = $property
echo "ESC[$Counter;1H $Label : $Text "
} else
{
$Counter1 = $Counter1 +1
$Text = $output.$property
$Label = $property
echo "ESC[$Counter1;60H $Label : $Text "
}
}
#Move to end
echo "ESC[20;1H "
Result
Other ANSI commands
Happy coding my friends.

Format-Table not taking effect (Exchange - powershell)

first of all sorry if my english is not the best. but ill try to explain my issue with as much detail as i can
Im having an issue where i cant get Format-Table to effect the output i give it.
below is the part im having issues with atm.
cls
$TotalSize = $($mailboxes. #{name = ”TotalItemSize (GB)”; expression = { [math]::Round((($_.TotalItemSize.Value.ToString()).Split(“(“)[1].Split(” “)[0].Replace(“,”, ””) / 1GB), 2) } });
$UserN = $($mailboxes.DisplayName)
$itemCount = $($mailboxes.ItemCount)
$LastLogonTime = $($mailboxes.ItemCount)
$allMailboxinfo = #(
#lager dataen som skal inn i et objekt
#{Username= $UserN; ItemCount = $itemCount; LastLogonTime = $($mailboxes.ItemCount); Size = $TotalSize}) | % { New-Object object | Add-Member -NotePropertyMembers $_ -PassThru }
$Table = $allMailboxinfo | Format-Table | Out-String
$Table
the output of this gives me what almost looks like json syntax below each title of the table.
Username LastLogonTime ItemCount Size
-------- ------------- --------- ----
{username1, username2,username3,userna...} {$null, $null, $null, $null...} {$null, $null, $null, $null...} {$null, $null, $null, $null...}
running the commands by themselves seem to work tho. like $mailboxes.DisplayName gives the exact data i want for displayname. even in table-format.
the reason im making the table this way instead of just using select-object, is because im going to merge a few tables later. using the logic from the script below.
cls
$someData = #(
#{Name = "Bill"; email = "email#domain.com"; phone = "12345678"; id = "043546" }) | % { New-Object object | Add-Member -NotePropertyMembers $_ -PassThru }
$moreData = #(
#{Name = "Bill"; company = "company 04"}) | % { New-Object object | Add-Member -NotePropertyMembers $_ -PassThru }
$Merge = #(
#plots the data into a new table
#{Name = $($someData.Name); e_mail = $($someData.email); phone = $($someData.phone); id = $($someData.id); merged = $($moreData.company) }) | % { New-Object object | Add-Member -NotePropertyMembers $_ -PassThru }
#formatting table
$Table = $Merge | Format-Table | Out-String
#print table
$Table
if you are wondering what im doing with this.
My goal, all in all. is a table with using the info from Exchange;
DisplayName, TotalItemSize(GB), ItemCount, LastLogonTime, E-mail adress, archive + Maxquoata, Quoata for mailbox.
You're creating a single object where each property holds an array of property values from the original array of mailbox objects.
Instead, create 1 new object per mailbox:
# construct output objects with Select-Object
$allMailBoxInfo = $mailboxes |Select #{Name='Username';Expression='DisplayName'},ItemCount,#{Name='LastLogonTime';Expression='ItemCount'},#{Name='Size';Expression={[math]::Round((($_.TotalItemSize.Value.ToString()).Split("(")[1].Split(" ")[0].Replace(",", "") / 1GB), 2) }}
# format table
$Table = $allMailBoxInfo | Format-Table | Out-String
# print table
$Table

Split array column and result into extra Colums

I have a table (CSV) which shows all the users that have ever logged on to a bunch of computers. Users can have 2 accounts, one username starts with "a" the other with "b", like a100 and b100 (the user behind is the same person).
Now I need to get the computers that have more then 2 accounts logged on which do not belong the same users. So A64 and B64 are not reported as separate users.
Here is the base list I have:
PC1,A64,B52,B64,A41
PC2,A51,B42,B51,A23
PC3,A42,B51
PC4,A5,B5
PC5,A1,B1,A14,A6
My plan was to split the "User"-column into more columns, so the table would look like this:
Computername,user1,user2,user3,user4,UserX
After this was done, I could iterate through the table and remove the leading letter in the Username, then I would try to get rid of doubles.
Do you think that makes sense?
Now I got stuck in the first task already. I know how to iterate though the second Column but how do I managed to get the result into another array so the output would be like:
Computername,user1,user2,user3,user4,UserX
Can you help me split?
$UserComputers = import-csv -Delimiter ";" "input.csv" -Header
'Computername','user1','user2','user3','user4'
$UserComputers | Select-Object *,
#{n='User1';e={$_.User1.Split(',')[0]}},
#{n='User2';e={$_.User1.Split(',')[1]}}
I get the error: Select-Object : The property cannot be processed because the property "User1" already exists.
It is useful to make "user" an array.
Get-Content "input.csv" | foreach {
$name, $users = $_.Split(",")
[pscustomobject]#{ Name = $name; Users = $users }
} | Where-Object { ($_.Users.Substring(1) | Select-Object -Unique).Count -gt 2 }
The output is below.
Name Users
---- -----
PC1 {A64, B52, B64, A41}
PC2 {A51, B42, B51, A23}
PC3 {A42, B51}
PC5 {A1, B1, A14, A6}
Input File (input.csv)
PC1,A64,B52,B64,A41
PC2,A51,B42,B51,A23
PC3,A42,B51
PC4,A5,B5
PC5,A1,B1,A14,A6
Powershell Script
Get-Content -Path .\input.csv |
Select-Object #{ Name = "Computer"; Expression = { $_.Split(',')[0] } },
#{ Name="Users"; Expression = { $_.Split(',')[1..($_.Split(',').Length-1)] |
Foreach-Object { $_.Substring(1) } | Select-Object -Unique } } |
Where-Object { $_.Users.Count -gt 2 }
Result:
Computer Users
------------- -----
PC1 {64, 52, 41}
PC2 {51, 42, 23}
PC5 {1, 14, 6}
P.S. Bonus: If you want to see more than 4 elements of the array on the screen change the variable
$FormatEnumerationLimit = 20
Explanation of the variable meaning
If your file is like your base list, you can do the following to build a new file with all the columns you need:
$maxColCount = 0
$data = get-content input.csv
foreach ($line in $data) {
$MaxColCount = [math]::Max($maxcolcount,($line -split ",").count)
}
$headers = #("ComputerName")
$MaxUserCount = $MaxColCount - 1
Foreach ($c in (1..$MaxUserCount)) {
$Headers += "User$c"
}
$Headers = $Headers -join ","
$Headers,$data | Set-Content "output.csv"
The code above assumes input.csv has the following format and each column after the first is a user:
PC1,A64,B52,B64,A41
PC2,A51,B42,B51,A23
PC3,A42,B51
PC4,A5,B5
PC5,A1,B1,A14,A6

Seeking balanced combination of fast, terse, and legible code to add up values from an array of objects

Given the following array of objects:
Email Domain Tally
----- ----- -----
email1#domainA.com domainA.com 4
email1#domainB.com domainB.com 1
email2#domainC.com domainC.com 6
email4#domainA.com domainA.com 1
I'd like to "group by" Domain and add up Tally as I go. The end result would like this:
Domain Tally
------ -----
domainA.com 5
domainB.com 1
domainC.com 6
I have something that works but I feel like it's overly complicated.
$AllTheAddresses = Get-AllTheAddresses
$DomainTally = #()
foreach ($Addy in $AllTheAddresses)
{
if ($DomainTally | Where-Object {$_.RecipientDomain -eq $Addy.RecipientDomain})
{
$DomainTally |
Where-Object {$_.RecipientDomain -eq $Addy.RecipientDomain} |
ForEach-Object {$_.Tally += $Addy.Tally }
}
else
{
$props = #{
RecipientDomain = $Addy.RecipientDomain
Tally = $Addy.Tally
}
$DomainTally += New-Object -TypeName PSObject -Property $props
}
}
In my example, I'm creating the addresses as hashtables, but PowerShell will let you refer to the keys by .Property similar to an object.
If you're truly just summing by the Domain, then it seems like you don't need anything more complicated than a HashTable to create your running total.
The basic summation:
$Tally = #{}
$AllTheAddresses | ForEach-Object {
$Tally[$_.Domain] += $_.Tally
}
Using this sample data...
$AllTheAddresses = #(
#{ Email = "email1#domainA.com"; Domain = "domainA.com"; Tally = 4 };
#{ Email = "email1#domainB.com"; Domain = "domainB.com"; Tally = 1 };
#{ Email = "email1#domainC.com"; Domain = "domainC.com"; Tally = 6 };
#{ Email = "email1#domainA.com"; Domain = "domainA.com"; Tally = 1 }
)
And you get this output:
PS> $tally
Name Value
---- -----
domainC.com 6
domainB.com 1
domainA.com 5
Here is a "PowerShellic" version, notice the piping and flow of the data.
You could of course write this as a one liner (I did originally before I posted the answer here). The 'better' part of this is using the Group-Object and Measure-Object cmdlets. Notice there are no conditionals, again because the example uses the pipeline.
$AllTheAddresses |
Group-Object -Property Domain |
ForEach-Object {
$_ |
Tee-Object -Variable Domain |
Select-Object -Expand Group |
Measure-Object -Sum Tally |
Select-Object -Expand Sum |
ForEach-Object {
New-Object -TypeName PSObject -Property #{
'Domain' = $Domain.Name
'Tally' = $_
}
} |
Select-Object Domain, Tally
}
A more terse version
$AllTheAddresses |
Group Domain |
% {
$_ |
Tee-Object -Variable Domain |
Select -Expand Group |
Measure -Sum Tally |
Select -Expand Sum |
% {
New-Object PSObject -Property #{
'Domain' = $Domain.Name
'Tally' = $_
}
} |
Select Domain, Tally
}
Group-Object is definitely the way to go.
In the interest of terseness:
Get-AllTheAddresses |Group-Object Domain |Select-Object #{N='Domain';E={$_.Name}},#{N='Tally';E={($_.Group.Tally |Measure-Object).Sum}}

Powershell - Prefix each line of Format-Table with String

I would like to know if there is an easy way of prefixing each line of a powershell table with a String.
For example, if I create an Array using the following code:
$Array = #()
$Object = #{}
$Object.STR_PARAM = "A"
$Object.INT_PARAM = 1
$Array += [PSCustomObject] $Object
$Object = #{}
$Object.STR_PARAM = "B"
$Object.INT_PARAM = 2
$Array += [PSCustomObject] $Object
Calling Format-Table give the following output:
$Array | Format-Table -AutoSize
STR_PARAM INT_PARAM
--------- ---------
A 1
B 2
Instead, I would like to have the following:
$Array | Format-Table-Custom -AutoSize -PrefixString " "
STR_PARAM INT_PARAM
--------- ---------
A 1
B 2
And if possible, I would also like to be able to use the Property parameter like this:
$SimpleFormat = #{Expression={$_.STR_PARAM}; Label="String Param"},
#{Expression={$_.INT_PARAM}; Label="Integer Param"};
$Array | Format-Table-Custom -Property $SimpleFormat -AutoSize -PrefixString "++"
++String Param Integer Param
++------------ -------------
++A 1
++B 2
Any help would be appreciated. Thanks.
You could just use the format expressions directly:
$f = #{Expression={"++" + $_.STR_PARAM}; Label="++String Param"},
#{Expression={$_.INT_PARAM}; Label="Integer Param"};
$Array | Format-Table $f -AutoSize
Output
++String Param Integer Param
-------------- -------------
++A 1
++B 2
Update to use expression and filter
Filter Format-Table-Custom
{
Param
(
[string]
$PrefixString,
[object]
$Property
)
end {
$rows = $input | Format-Table $property -AutoSize | Out-String
$lines = $rows.Split("`n")
foreach ($line in $lines) {
if ($line.Trim().Length -gt 0) {
$PrefixString + $line
}
}
}
}
$f = #{Expression={"--" + $_.STR_PARAM}; Label="--String Param"},
#{Expression={$_.INT_PARAM}; Label="Integer Param"};
$Array | Format-Table-Custom -Property $f -PrefixString "++"
Output
++--String Param Integer Param
++-------------- -------------
++--A 1
++--B 2