Grouping objects on the property in PowerShell - powershell

I would like the Application and ShortcutName columns to be grouped into one column, while keeping the Version and Architecture columns as they are :
$object | Select-Object -Property Application, Version, Architecture, ShortcutName |
Sort-Object -Property #{expression="Architecture";Descending=$true},Application |
Out-GridView
How can I do that ?

In PowerShell you group objects by properties with Group-Object. What you want is not grouping in that sense, but defining a new custom property.
You create custom properties a hashtable with the Name and Expression keys.
Something like this will probably do the trick.
$Expression = {
if ($_.Application) {
$_.Application
}
else {
$_.Shortcut
}
}
$object |
Select-Object -Property #{ Name = 'Application'; Expression = $Expression }, Version, Architecture |
Sort-Object -Property #{expression="Architecture";Descending=$true},Application |
Out-GridView
The expression checks whether the Application property is filled. If so, it will use the existing application name, otherwise it will take the Shortcut property as application name.

Related

PowerShell Export-CSV - Missing Columns [duplicate]

This question already has an answer here:
Not all properties displayed
(1 answer)
Closed 1 year ago.
This is a follow-up question from PowerShell | EVTX | Compare Message with Array (Like)
I changed the tactic slightly, now I am collecting all the services installed,
$7045 = Get-WinEvent -FilterHashtable #{ Path="1system.evtx"; Id = 7045 } | select
#{N=’Timestamp’; E={$_.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')}},
Id,
#{N=’Machine Name’; E={$_.MachineName}},
#{N=’Service Name’; E={$_.Properties[0].Value}},#{N=’Image Path’;E=$_.Properties[1].Value}},
#{N=’RunAsUser’; E={$_.Properties[4].Value}},#{N=’Installed By’; E={$_.UserId}}
Now I match each object for any suspicious traits and if found, I add a column 'Suspicious' with the value 'Yes'. This is because I want to leave the decision upto the analyst and pretty sure the bad guys might use something we've not seen before.
foreach ($Evt in $7045)
{
if ($Evt.'Image Path' -match $sus)
{
$Evt | Add-Member -MemberType NoteProperty -Name 'Suspicious' -Value 'Yes'
}
}
Now, I'm unable to get PowerShell to display all columns unless I specifically Select them
$7045 | Format-Table
Same goes for CSV Export. The first two don't include the Suspicious Column but the third one does but that's because I'm explicitly asking it to.
$7045 | select * | Export-Csv -Path test.csv -NoTypeInformation
$7045 | Export-Csv -Path test.csv -NoTypeInformation
$7045 | Select-Object Timestamp, Id, 'Machine Name', 'Service Name', 'Image Path', 'RunAsUser', 'Installed By', Suspicious | Export-Csv -Path test.csv -NoTypeInformation
I read the Export-CSV documentation on MS. Searched StackOverFlow for some tips, I think it has something to do with PS checking the first Row and then compares if the property exists for the second row and so on.
Thank you
The issue you're experiencing is partially because of how objects are displayed to the console, the first object's Properties determines the displayed Properties (Columns) to the console.
The bigger problem though, is that Export-Csv will not export those properties that do not match with first object's properties unless they're explicitly added to the remaining objects or the objects are reconstructed, for this one easy way is to use Select-Object as you have pointed out in the question.
Given the following example:
$test = #(
[pscustomobject]#{
A = 'ValA'
}
[pscustomobject]#{
A = 'ValA'
B = 'ValB'
}
[pscustomobject]#{
C = 'ValC'
D = 'ValD'
E = 'ValE'
}
)
Format-Table will not display the properties B to E:
$test | Format-Table
A
-
ValA
ValA
Format-List can display the objects properly, this is because each property with it's corresponding value has it's own console line in the display:
PS /> $test | Format-List
A : ValA
A : ValA
B : ValB
C : ValC
D : ValD
E : ValE
Export-Csv and ConvertTo-Csv will also miss properties B to E:
$test | ConvertTo-Csv
"A"
"ValA"
"ValA"
You have different options as a workaround for this, you could either add the Suspicious property to all objects and for those events that are not suspicious you could add $null as Value.
Another workaround is to use Select-Object explicitly calling the Suspicious property (this works because you know the property is there and you know it's Name).
If you did not know how many properties your objects had, a dynamic way to solve this would be to discover their properties using the PSObject intrinsic member.
using namespace System.Collections.Generic
function ConvertTo-NormalizedObject {
[CmdletBinding()]
param(
[Parameter(ValueFromPipeline, Mandatory)]
[object[]] $InputObject
)
begin {
$list = [List[object]]::new()
$props = [HashSet[string]]::new([StringComparer]::InvariantCultureIgnoreCase)
}
process {
foreach($object in $InputObject) {
$list.Add($object)
foreach($property in $object.PSObject.Properties) {
$null = $props.Add($property.Name)
}
}
}
end {
$list | Select-Object ([object[]] $props)
}
}
Usage:
# From Pipeline
$test | ConvertTo-NormalizedObject | Format-Table
# From Positional / Named parameter binding
ConvertTo-NormalizedObject $test | Format-Table
Lastly, a pretty easy way of doing it thanks to Select-Object -Unique:
$prop = $test.ForEach{ $_.PSObject.Properties.Name } | Select-Object -Unique
$test | Select-Object $prop
Using $test for this example, the result would become:
A B C D E
- - - - -
ValA
ValA ValB
ValC ValD ValE
Continuing from my previous answer, you can add a column Suspicious straight away if you take out the Where-Object filter and simply add another calculated property to the Select-Object cmdlet:
# create a regex for the suspicious executables:
$sus = '(powershell|cmd|psexesvc)\.exe'
# alternatively you can join the array items like this:
# $sus = ('powershell.exe','cmd.exe','psexesvc.exe' | ForEach-Object {[regex]::Escape($_)}) -join '|'
$7045 = Get-WinEvent -FilterHashtable #{ LogName = 'System';Id = 7045 } |
Select-Object Id,
#{N='Timestamp';E={$_.TimeCreated.ToUniversalTime().ToString('yyyy-MM-ddTHH:mm:ssZ')}},
#{N='Machine Name';E={$_.MachineName}},
#{N='Service Name'; E={$_.Properties[0].Value}},
#{N='Image Path'; E={$_.Properties[1].Value}},
#{N='RunAsUser'; E={$_.Properties[4].Value}},
#{N='Installed By'; E={$_.UserId}},
#{N='Suspicious'; E={
if ($_.Properties[1].Value -match $sus) { 'Yes' } else {'No'}
}}
$7045 | Export-Csv -Path 'X:\Services.csv' -UseCulture -NoTypeInformation
Because you have many columns, this will not fit the console width anymore if you do $7045 | Format-Table, but the CSV file will hold all columns you wanted.
I added switch -UseCulture to the Export-Csv cmdlet, which makes sure you can simply double-click the csv file so it opens correctly in your Excel.
As sidenote: Please do not use those curly so-called 'smart-quotes' in code as they may lead to unforeseen errors. Straighten these ’ thingies and use normal double or single quotes (" and ')

Remove characters inside expression Powershell

I'm trying to remove characters from an expression, without the option to define another integer.
This is the expression I currently have:
Get-ADUser -Identity $PSItem -Properties mail, manager, l, title |
Select-Object -Property Name, SamAccountName, Mail, l, title, #{
Name = "ManagerGID"
Expression = { (Get-Aduser -identity $psitem.manager).samaccountname }
},
#{
Name = "ManagerName"
Expression = { (Get-Aduser -identity $psitem.manager).name }
},
#{
Name = "PicURL"
Expression = {
(ConvertFrom-Json -InputObject (Invoke-WebRequest ('http://picasaweb.google.com/data/entry/api/user/' + $psitem.mail + '?alt=json')) |
Select-Object -ExpandProperty entry |
Select-Object -ExpandProperty 'gphoto$thumbnail')
}
}
For the PicURL option, the results from google for the gphoto$thumbnail will be something similar to this:
#{$t=https://lh3.googleusercontent.com***********.jpg}
I would like to remove the #{$t= and the } in the start and end of the property. Unfortunately, couldn't find a proper way to do that without having to take out this expression from the pipeline, which is not very effective for the rest of the code.
If you're curious, this is for automatic org chart creation from AD, with google profile pics included.
The implication of seeing output #{$t=https://lh3.googleusercontent.com***********.jpg} is that your input object's gphoto$thumbnail property value is a custom object with a single property literally named $t, whose value is the URL of interest.[1]
Therefore, you can simply append another Select-Object call to extract the $t property's value:
... | Select-Object -ExpandProperty '$t'
Note the need to single-quote ('...') property name $t, so that it isn't mistaken for a variable reference, the same way that property name gphoto$thumbnail required single-quoting.
Alternatively, in v3+, consolidate the then-three Select-Object -ExpandProperty calls into a single ForEach-Object call:
... | ForEach-Object { $_.entry.'gphoto$thumbnail'.'$tp' }
[1] This hashtable-like representation is what PowerShell uses to stringify [pscustomobject] instances.

Powershell - Group-Object PSObject with multiple properties

I'm trying to take an array of PSObjects similar to
#{BakId=27; Name=DB_A; Lsn=123; File=A_01; Size=987}
#{BakId=28; Name=DB_B; Lsn=456; File=B_01; Size=876}
#{BakId=28; Name=DB_B; Lsn=456; File=B_02; Size=765}
#{BakId=28; Name=DB_B; Lsn=456; File=B_03; Size=654}
And create a new grouped object that removes redundant header info.
BakId Lsn Name Files
27 123 DB_A {#{File=A_01.bak;Size=777}}
28 456 DB_B {#{File=B_01.bak;Size=888}, #{File=B_02.bak;Size=999}, ...}
I tried using group-object but can only get it to work for one property. (all grouped properties go into Group.Name as a a string of comma separated values.)
This is the best I've come up with, but feels hacky.
$list | Group-Object -Property BakId | % {
$BakId = $_.Name
$Lsn = $_.Group[0].Lsn # <--- is there a better way than this?
$Name = $_.Group[0].Name # <--- Ditto
$Files = $_.Group | Select-Object -Property SequenceNumber, Size
Write-Output (New-Object -TypeName PSObject -Property #{ BakId=$BakId;Files = $Files })
}
Is there a better way?
Thanks
You can simplify the approach to constructing the output objects by using a single Select-Object call with calculated properties, and, relying on the order of the grouping properties, access their group-specific values via the .Values collection:
$list | Group-Object -Property BakId, Lsn, Name |
Select-Object #{n='BakId'; e={ $_.Values[0] }},
#{n='Lsn'; e={ $_.Values[1] }},
#{n='Name'; e={ $_.Values[2] }},
#{n='Files'; e={ $_.Group | Select-Object File, Size }}
Note:
$_.Values[<ndx>] takes the place of $_.Group[0].<name> in your approach; note that the latter only makes sense for the actual grouping properties, because any others will not be uniform throughout the group.
The .Values collection ([System.Collections.ArrayList]) on each group object output from Group-Object ([Microsoft.PowerShell.Commands.GroupInfo] instance) contains that group's shared grouping-property values as-is (original type), in the order specified.
By contrast, property .Name contains the stringified combination of all grouping-property values: a string containing a comma-separated list.
Unfortunately, such details are currently missing from Get-Help Group-Object.
If you wanted to avoid having to repeat the grouping properties (e.g., in preparation for writing a function wrapper; PSv3+):
$props = 'BakId', 'Lsn', 'Name'
$list | Group-Object -Property $props | ForEach-Object {
$propDefs = [ordered] #{}
foreach ($i in 0..($props.Length-1)) { $propDefs.[$props[$i]] = $_.Values[$i] }
$propDefs.Files = $_.Group | Select-Object File, Size
New-Object PSCustomObject -Property $propDefs
}

Select-Object of multiple properties

I am trying to find an elegant way to put the metadata of a table of type System.Data.DataTable into a multi-dimensional array for easy reference in my program. My approach to the issue so far seems tedious.
Assuming $DataTable being the DataTable in question
What I tried to do so far was:
$Types = $DataTable.Columns | Select-Object -Property DataType
$Columns= $DataTable.Columns | Select-Object -Property ColumnName
$Index = $DataTable.Columns | Select-Object -Property ordinal
$AllowNull = $DataTable.Columns | Select-Object -Property AllowDbNull
Then painfully going through each array, pick up individual items and put them in my multi-dimensional array $TableMetaData.
I read in the documentation of Select-Object and it seems to me that only 1 property can be selected at 1 time? I think I should be able to do all the above more elegantly and store the information in $TableMetaData.
Is there a way to easily pick up multiple properties and put them in a multi-dimensional array in 1 swoop?
I read the documentation of Select-Object and it seems to me that only 1 property can be selected at 1 time?
This is not true, Select-Object can take any number of arguments to the -Property parameter
$ColumnInfo = $DataTable.Columns | Select-Object -Property DataType,ColumnName,ordinal,AllowDbNull
Now $ColumnInfo will contain one object for each column, having all 4 properties.
Rather than using a multi-dimensional array, you should consider using a hashtable (#{}, an unordered dictionary):
$ColumnInfo = $DataTable.Columns | ForEach-Object -Begin { $ht = #{} } -Process {
$ht[$_.ColumnName] = $_
} -End { return $ht }
Here, we create an empty hashtable $ht (the -Begin block runs just once), then store each column object in $ht using the ColumnName as the key, and finally return $ht, storing it in $ColumnInfo.
Now you can reference metadata about each column by Name:
$ColumnInfo.Column2
# or
$ColumnInfo["Column2"]
One easy way to do this is to create an "empty" variable with Select-Object. Here is a sample command:
$DataTableReport = "" | Select-Object -Property DataType, ColumnName, ordinal, AllowDbNull
Then, link the $DataTableReport to the $Types, $Columns, $Index, and the $AllowNull properties as shown below:
$DataTableReport.Types = $DataTable.DataType
$DataTableReport.Columns = $DataTable.ColumnName
$DataTableReport.Index = $DataTable.ordinal
$DataTableReport.AllowNull = $DataTable.AllowDbNull
Finally, call the DataTableReport variable.
$DataTableReport # will display all the results in a tabular form.

Issue with Powershell custom table

I'm trying to create a custom table based on two other tables (csv-imported) - some kind of a VLOOKUP, but I can't seem to find a solution. I've come up with the following (failing) code:
$DrawPlaces | select Module, Workplace, #{ Name = "IPaddress"; Expression = {$Workstations.workstation.where($_.WorkPlace -eq $Workstations.Workplace)}} -First 15
Both Drawplaces and $Workplaces are PSCustomObject. The result of this would then go to another variable.
I'm not even sure the logic or syntax is correct, but the result table has the IPaddress column empty. I've also tried with -match instead of -eq.
This doesn't make sense: $Workstations.workstation.where($_.WorkPlace -eq $Workstations.Workplace)
.where() requires a scriptblock parameter like .where({}).
Keeping in mind that inside the where-statement $_ is refering to the current object in the $workstations.workstation-loop, your where-statement is testing ex. $workstations.workstation[0].workplace -eq $workstations.workplace. Is that really what you want?
Are you trying to achieve this?
$DrawPlaces |
Select-Object -First 15 -Property #(
"Module",
"Workplace",
#{ Name = "IPaddress"; Expression = {
#Save the Workspace-value for the current object from $DrawPlaces
$wp = $_.WorkPlace;
#Find the workstation with the same workplace as $wp
$Workstations | Where-Object { $_.WorkPlace -eq $wp} | ForEach-Object { $_.Workstation }
}
}
)