Powershell Grouping acting differently in different versions - powershell

Question: How do I write this so it gives the same result in v4 and v5?
I am trying to group the following dataset by SiteCode.
I have a dataset as follows [Array of Hashes]:
Assume AppointmentId is always unique
$groupedDataset = #{}
$dataset = #(
#{
Program = "x"
AppointmentId = "1234567891"
AdminDate = "x"
CountryName = "x"
SiteCode = "x1111"
DateRequested = "x"
SubjectID = "x"
AccountID = "x"
},
#{
Program = "x"
AppointmentId = "1234567892"
AdminDate = "x"
CountryName = "x"
SiteCode = "x1112"
DateRequested = "x"
SubjectID = "x"
AccountID = "x"
},
#{
Program = "x"
AppointmentId = "1234567893"
AdminDate = "x"
CountryName = "x"
SiteCode = "x1113"
DateRequested = "x"
SubjectID = "x"
AccountID = "x"
},
#{
Program = "x"
AppointmentId = "1234567894"
AdminDate = "x"
CountryName = "x"
SiteCode = "x1111"
DateRequested = "x"
SubjectID = "x"
AccountID = "x"
}
)
When I run the following code below in PS Version: 5.1
$dataset |
ForEach-Object { [PSCustomObject]$_ } |
Group-Object -Property SiteCode |
ForEach-Object {
$groupedDataset[$_.Name] = $_.Group
}
It returns the result I require:
Name Value
---- -----
x1113 {#{SiteCode=x1113; Program=x; Appointment...
x1111 {#{SiteCode=x1111; Program=x; Appointment...
x1112 {#{SiteCode=x1112; Program=x; Appointment...
When I run the exact code in PS Version: 4.0 it returns the following:
Name Value
---- -----
{System.Collections.Hashtable, System.Collect...

In PowerShell v5.0 improvements have been made, eg. PSCustomObject casting, which is in your script
ForEach-Object { [PSCustomObject]$_ }
Try doing it the old school way and instead of building a hashtable yourself, use the one returned by the groupby.
$groupedDataset = $dataset |
ForEach-Object {
[PSCustomObject]#{
Program = $_.Program
AppointmentId = $_.AppointmentId
AdminDate = $_.AdminDate
CountryName = $_.CountryName
SiteCode = $_.SiteCode
DateRequested = $_.DateRequested
SubjectID = $_.SubjectID
AccountID = $_.AccountID
}
} |
Group-Object -Property SiteCode -AsHashTable
This results in
$groupedDataset | out-host
Count Name Group
----- ----- ------
2 x1111 {#{Program=x; AppointmentId=1234567891; AdminDate=x; CountryName=x; SiteCode=x1111; DateRequested=x; SubjectID=x; AccountID=x}, #{Program=x; AppointmentId=1234567894; AdminDate=x; CountryN...
1 x1112 {#{Program=x; AppointmentId=1234567892; AdminDate=x; CountryName=x; SiteCode=x1112; DateRequested=x; SubjectID=x; AccountID=x}}
1 x1113 {#{Program=x; AppointmentId=1234567893; AdminDate=x; CountryName=x; SiteCode=x1113; DateRequested=x; SubjectID=x; AccountID=x}

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.

To get Values from CSV instead of manual values

Question: This Powershell code have manual values defined "Value 1 & value 2" , want these two to be from CSV file.
#Value 1
$blockedConnector1 = [pscustomobject]#{
id = "/providers/Microsoft.PowerApps/apis/shared_salesforce"
name = "Salesforce"
type = "Microsoft.PowerApps/apis"
}
#value 2
$blockedConnector2 = [pscustomobject]#{
id = "/providers/Microsoft.PowerApps/apis/shared_postgresql"
name = "PostgreSQL"
type = "Microsoft.PowerApps/apis"
}
#Grouping of Connectors
$blockedConnectors = #()
$blockedConnectors += $blockedConnector1
$blockedConnectors += $blockedConnector2
$blockedConnectorGroup = [pscustomobject]#{
classification = "Blocked"
connectors = $blockedConnectors
}
$blockedConnectorGroup | Format-List
Desired Output
classification : Blocked
connectors : {#{id=/providers/Microsoft.PowerApps/apis/shared_salesforce; name=Salesforce; type=Microsoft.PowerApps/apis}, #{id=/providers/Microsoft.PowerApps/apis/shared_postgresql; name=PostgreSQL;type=Microsoft.PowerApps/apis}}
If I understand correctly, you have a CSV file like this:
"id","name","type"
"/providers/Microsoft.PowerApps/apis/shared_salesforce","Salesforce","Microsoft.PowerApps/apis"
"/providers/Microsoft.PowerApps/apis/shared_postgresql","PostgreSQL","Microsoft.PowerApps/apis"
so all you have to do is to Import that data and use it in your $blockedConnectorGroup
$blockedConnectorGroup = [pscustomobject]#{
classification = "Blocked"
connectors = (Import-Csv -Path 'D:\Test\blockedConnectors.csv')
}
$blockedConnectorGroup | Format-List
If you do not yet have such a CSV file, you can create it by exporting the PsCustomObjects you defined
[pscustomobject]#{
id = "/providers/Microsoft.PowerApps/apis/shared_salesforce"
name = "Salesforce"
type = "Microsoft.PowerApps/apis"
},
[pscustomobject]#{
id = "/providers/Microsoft.PowerApps/apis/shared_postgresql"
name = "PostgreSQL"
type = "Microsoft.PowerApps/apis"
} | Export-Csv -Path 'D:\Test\blockedConnectors.csv' -NoTypeInformation

How to join and group 2 tables with a comma list in Powershell?

I have two array which have the following objects:
$dataset1 = #(
#{
MachineName = "AAA"
ID = "111"
},
#{
MachineName = "BBB"
ID = "222"
},
#{
MachineName = "CCC"
ID = "111"
},
#{
MachineName = "DDD"
ID = "333"
},
#{
MachineName = "EEE"
ID = "111"
}
)
$dataset2 = #(
#{
ID = "111"
TagName = "ALPHA2"
},
#{
ID = "222"
TagName = "ALPHA0"
},
#{
ID = "333"
TagName = "ALPHA8"
},
#{
ID = "444"
TagName = "ALPHA29"
},
)
Now I want to create an array which have an object of TagName and for each object of TagName it should contain a list of MachineNames separated by comma so something like this:
TagName | MachineName
ALPHA2 AAA,CCC,EEE
ALPHA0 BBB
ALPHA8 DDD
This is the code that I have tried:
$Joined= Foreach ($row in $dataset1)
{
[pscustomobject]#{
ID = $row.ID
MachineName = $row.MachineName -join ','
TagName = $dataset2 | Where-Object {$_.ID -eq $row.ID} | Select-Object -ExpandProperty TagName
}
}
But it is not generating a comma list of Machine names instead it is printing individual rows for each machine name.
I would iterate $dataset2 instead of $dataset1.
$joined = foreach($row in $dataset2){
[PSCustomObject]#{
TagName = $row.tagname
MachineName = ($dataset1 | Where-Object id -eq $row.id).MachineName -join ','
}
}
If you're trying to exclude those entries in $dataset1 that don't have a corresponding entry in $dataset2, change to this.
$joined = foreach($row in $dataset2 | Where-Object id -in $dataset1.id){
[PSCustomObject]#{
TagName = $row.tagname
MachineName = ($dataset1 | Where-Object id -eq $row.id).MachineName -join ','
}
}
it is not generating a comma list of Machine names instead it is printing individual rows for each machine name.
That's actually a great starting point!
Just pipe the resulting objects to Group-Object and extract the machine names from each resulting group:
$joined |Group-Object TagName |Select Name,#{Name='Machines';Expression = {$_.Group.MachineName -join ','}}
Which should give you something like:
Name Machines
---- --------
ALPHA2 AAA,CCC,EEE
ALPHA0 BBB
ALPHA8 DDD

How to use Select-Object, Where-Object to get correct value in hash table

I have the following hash table, $finalArray:
#{Name1=Yellow; Name2=Pallet Town; Name3=Ash; ID=12; Date=2019-07-01; DeviceID=1234} #{Name1=Blue; Name2=Pallet Town; Name3=Gary; ID=14; Date=2019-07-02; DeviceID=5678}
I'm attempting to get Name3 where the value of ID = 12, which should return Ash, but get the following error:
Select-Object $finalArray.Name3 | Where-Object $finalArray.ID -eq "12"
Where-Object : A positional parameter cannot be found that accepts argument 'System.Object[]'.
Anyone got any pointers/better way of evaluating the value?
Assuming you mean you have array of hashtables (since your variable name means that), first you should define it as follows:
$finalarray = #(
#{
Name1 = 'Yellow';
Name2 = 'Pallet Town';
Name3 = 'Ash';
ID = 12;
Date = '2019-07-01';
DeviceID = 1234
}, #{
Name1 = 'Blue';
Name2 = 'Pallet Town';
Name3 = 'Gary';
ID = 14;
Date = '2019-07-02';
DeviceID = 5678
}
)
Now you want to get Name3 of the hashtable that's ID = 12 as follows:
($finalarray | where ID -eq 12).Name3
Note
If you are defining the ID as string (i.e ID = "12") then you should use Where ID -eq "12" not Where ID -eq 12.
No worries about this, I figured out I could use something like:
($finalArray | Where-object {$_.ID -like "*12*"}).Name

Export nested hash table to csv in powershell

I have an array, with a nested hashtable containing data:
tid : 1
token : 12345
participant_info : #{firstname=bob; lastname=smith; email=bob.smith#email.com}
completed : y
tid : 2
token : 67890
participant_info : #{firstname=Alice; lastname=Jones; email=alice.jones#email.com}
completed : n
I would like to output this as a CSV with the nested items of firstname, surname and email pulled out of the inner hashtable - eg
tid,token,firstname,surname,email,completed
1,12345,bob,smith,bob.smith#email.com,y
2,67890,alice,jones,alice.jones#email.com,n
I'm going to guess that the answer is looping through with foreach and creating a custom ps object, but because my nested items aren't named, I can't work out how to do this using the other examples on here.
Any assistance is appreciated! Thanks!
Given your sample:
#(
[pscustomobject]#{
tid = 1
token = 12345
participant_info = #{
firstname = 'bob'
lastname = 'smith'
email = 'bob.smith#email.com'
}
completed = 'N'
}
...
)
And desired output:
id,token,firstname,surname,email,completed
1,12345,bob,smith,bob.smith#email.com,y
2,67890,alice,jones,alice.jones#email.com,n
You can do something like this:
$JsonList = #( ... )
$Path = "$Env:UserProfile\file.csv"
ForEach ($Sample in $JsonList)
{
$Sample | Select-Object -Property #(
#{N = 'id'; E = { $Sample.tid }}
#{N = 'token'; E = { $Sample.token }}
#{N = 'firstname'; E = { $Sample.participant_info.firstname }}
#{N = 'surname'; E = { $Sample.participant_info.lastname }}
#{N = 'email'; E = { $Sample.participant_info.email }}
#{N = 'completed'; E = { $Sample.completed }}
) | Export-Csv -Path $Path -Append
}
Edit: you're dealing with PSCustomObject, not Hashtable, so my previous syntax won't work for that. Here's what I'm presuming your code to look like now (I've updated my above example):
#"
[
{
"tid": 1,
"token": 12345,
"participant_info": {
"firstname": "bob",
"lastname": "smith",
"email": "bob.smith#email.com"
},
"completed": "N"
}
...
]
"# | ConvertFrom-Json