Change order of columns in the object - powershell

How can I change the column ordering of the output my code produces:
$apps = Import-CSV apps.csv
$computers = Import-CSV compobj.csv
foreach ($computer in $computers) {
$computerLob = $computer.lob
$lobApps = $apps | ? {$_.lob -eq $computerLob }
foreach ($app in $lobApps) {
$computerHostname = $computer.hostname
$appLocation = $app.location
$installed=Test-Path "\\$computerHostname\$appLocation"
New-Object PSObject -Property #{
Computer=$computer.hostname
App=$app.appname
Installed=$installed
}
}
Currently it's producing the columns in the following order: Installed,App,Computer.
I'd like to have it in the following order: Computer,App,Installed.

Powershell V3 added a type accelerator for [PSCustomObject] that creates objects using an ordered hash table, so the properties stay in the order they're declared:
[PSCustomObject] #{
Computer=$computer.hostname
App=$app.appname
Installed=$installed
}

If you want to ensure the output of an object occurs in a certain order i.e. formatted a certain way then use PowerShell's formatting commands.
$obj = [pscustomobject]#{Computer=$computer.hostname;App=$app.appname;Installed=$installed}
$obj | Format-Table Computer,App,Installed
What's more you can better control the output e.g.:
$obj | Format-Table Computer,App,Installed -AutoSize
Or control field width & alignment:
$obj | Format-Table #{label='Computer';expression={$_.Computer};width=20;alignment='right'},App,Installed
Frankly I think it is a better practice to use the formatting commands to get the output order you want rather than to rely upon the order in which the properties are created.

The problem is that you're adding the properties using a hashtable, and hashtables don't preserve order. You can do one of two things:
1. Add the properties in a manner that will set the order (the last line is to output the object to the pipeline):
$obj = New-Object PSObject
$obj | Add-Member -MemberType NoteProperty -Name ComputerName -Value $computer.hostname
$obj | Add-Member -MemberType NoteProperty -Name App -Value $app.appname
$obj | Add-Member -MemberType NoteProperty -Name Installed -Value $installed
$obj
2. Determine the order at the time you output the object to the pipeline using Select-Object:
New-Object PSObject -Property #{
Computer=$computer.hostname
App=$app.appname
Installed=$installed
} | select Computer,App,Installed
Which one is preferable depends on how much you'll be working with the object. If, as your question implies, you're only using the PSObject in order to display the information in tabular format, the second method is quicker. If you're going to output the object multiple times in different parts of the script, the first method allows you to simply output it as $obj rather than having to pipe to select every time.
Note also that the second method can be split up like this if you don't want to output the object immediately after populating it:
$obj = New-Object PSObject -Property #{
Computer=$computer.hostname
App=$app.appname
Installed=$installed
}
[...do other stuff...]
$obj | select Computer,App,Installed

Format-Table it's a good solution when you need to display your object fields in a specific order, but it will change the obect and you won't be able to pipe your object, for example when exporting to csv (Export-Csv).
In the case you just want to change "the order of the fields in the object" use Select-Object. This will preserve object type and fields, and still you will be able to pipe the object to other cmdlets.

A more universal way to change the order is using the Select-Object cmdlet with the list of properties in the required order.
See the example:
PS> $ObjectList = 1..3 |
% {New-Object psobject -Property #{P2="Object $_ property P2"; P1="Object $_ property P1"}}
PS> $ObjectList
P2 P1
-- --
Object 1 property P2 Object 1 property P1
Object 2 property P2 Object 2 property P1
Object 3 property P2 Object 3 property P1
PS> $ObjectList | Select-Object P1,P2
P1 P2
-- --
Object 1 property P1 Object 1 property P2
Object 2 property P1 Object 2 property P2
Object 3 property P1 Object 3 property P2
The full form of these commands is the following:
$ObjectList = 1..3 |
ForEach-Object -Process {New-Object -TypeName psobject -Property #{P2="Object $_ property P2"; P1="Object $_ property P1"}}
$ObjectList | Select-Object -Property P1,P2

Related

PowerShell is re-arranging my columns and i cant figure out why

The code I am using is below:
Import-Csv -Path "E:\CSV\GA.csv" | Group-Object Product | ForEach {
New-Object PSObject -Property #{
Product = $_.Name
Stockcode = $_.Group[0].Stockcode
QuantityCounted = ($_.Group | Measure-Object -Property QuantityCounted -Sum).Sum
}
} | Export-Csv "E:\CSV\test.csv" -NoTypeInformation
The Headers should be in this order:
Product | Stockcode | QuantityCounted
If someone could point me in the right direction that would awesome, thank you.
Hash Tables are not ordered by default, if you need your object's properties to preserve their order you need to use an Ordered Dictionary instead.
The [ordered] attribute is introduced in PowerShell 3.0.
New-Object PSObject -Property ([ordered]#{
Product = 1
Stockcode = 2
QuantityCounted = 3
})
If you're running an up-to-date version of PowerShell, you can create objects with ordered properties using the [pscustomobject] type accelerator. This is more efficient and straight forward than New-Object.
The [pscustomobject] type accelerator was added in PowerShell 4.0.
[pscustomobject]#{
Product = 1
Stockcode = 2
QuantityCounted = 3
}

Creation and modification of Objects in Powershell

I'm a newbie of powershell, I'm starting right now to look at objects, etc.
I'm creating an object in this way:
$myObject = [PSCustomObject]#{
ComputerName = "abc"
Data = "xxx"
}
If I then print $myObject what i get is:
ComputerName Data
------------ ----
abc xxx
And everything is ok, now I want to add a property to that object, and I saw (tell me if i'm wrong) that I can do it in 2 ways: with add-member and with select-object
For example with add-member what I did was:
$myObject | Add-member -NotePropertyName Level -NotePropertyValue Highest
Instead with Select-object I did:
$myobject = 2 (cause i want to add 2 properties, is it right?) | Select-Object -Property Level, Privilege
$myobject.Level = "High"
$myobject.Privilege = "Elevated"
Now if I run $myobject I still only get:
ComputerName Data
------------ ----
abc xxx
What should I do to see all the data, even the one that I added later?
Can I directly add the values to the properties added through Select-Object?
Thanks
You can use the Add-Member method on a PsCustomObject.
$myObject = [PSCustomObject]#{
ComputerName = "abc"
Data = "xxx"
}
$myObject | Add-Member -NotePropertyName Level -NotePropertyValue High
$myObject | Add-Member Privilege Elevated
$myObject
#Output looks like,
ComputerName Data Level Privilege
------------ ---- ----- ---------
abc xxx High Elevated
Update
Not sure at the moment why but will elaborate on it ...
If you print the Pscustomobject and then run the add-member, they seem to be ignored. If you create a hashtable, update it and then convert to PsObject, it works. Following is an example of that hashtable
$myObject = #{
ComputerName = "abc"
Data = "xxx"
}
$myObject | ft
$myObject.Add("Level", "high")
$myObject.Add("Privilege", "Elevated")
[pscustomobject] $myObject | ft
What I found
When you print $myObject then add the data, the data is added but not displayed. This is due to some internal mechanism, unknown to me, that continues to use the same headers from previous command.
If you notice, the data is printed twice under the same heading. If you want to see the differences before and after, pipe it to format-list or format-table to use a different output stream each time.
$myObject = [PSCustomObject]#{
ComputerName = "abc"
Data = "xxx"
}
$myObject | ft
$myObject | Add-Member -NotePropertyName Level -NotePropertyValue High -Force
$myObject | Add-Member Privilege Elevated
$myObject | ft
You can use either Add-Member or Select-Object. Withstanding the advantages or disadvantages in different situations I just want to throw in the Select-Object example. Just so you have both methods:
This example will echo the object with the selected properties:
$myObject |
Select-Object *,
#{Name = 'Level'; Expression = { 'High' } },
#{Name = 'Privilege'; Expression = { 'Elevated' } }
If you want to save the new properties back to the object variable you'll have to reassign like below:
$myObject = $myObject |
Select-Object *,
#{Name = 'Level'; Expression = { 'High' } },
#{Name = 'Privilege'; Expression = { 'Elevated' } }
PowerShell allows you to provide a hash table to define the new properties. You'll note it looks fairly similar to the hash you used to create the object. Typically the expression would leverage the $_ syntax to calculate the property's value. You will often here these referred to as calculated properties.

How to dynamically add new properties to custom object in PowerShell

I have a custom object that contains device information that looks something like this.
name,model,sn
PC1,Elitebook 850, ABC123,
PC2,EliteDesk 600,123ABC
I have a function that retrieves threats detected by an antivirus product. These are returned as an array of objects. There are more properties than below but this is just an example
file,md5
bad.exe,adfdfdfd
evil.exe,fdfdffdf
I would like to add each member as properties to the custom object so the final output is similar to this.
name,model,sn,01_file,01_md5,02_file,02_md5
Currently, my script does this:
foreach($device in $devices){
$threats = Get-Threats $device
[pscustomobject] #{
name = $device.device_name
make = $device.make
sn = $device.sn
ThreatFileName = $threats.File -join ", "
Threat_md5 = $threats.md5 -join ", "
}
}
This works ok but I'd really like each object returned by the 'Get-Threats' function to be listed as its own set of properties. I need this to be generated dynamically because I don't know how many threats will be returned for each device.
Any thoughts on how to go about this?
You can add properties to objects at any time with the Add-Member cmdlet. Maybe start with an empty object and loop through the elements returned by Get-Threats, adding a member each time?
https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/add-member?view=powershell-6
Edit: Example code to answer for reference.
$o = [pscustomobject]#{
MemberA='aaa'
MemberB='bbb'
MemberC='ccc'
MemberD='ddd'
}
"Before"
$o | ft
$o | Add-Member -MemberType NoteProperty -Name 'MemberE' -Value 'eee'
$o | Add-Member -MemberType NoteProperty -Name 'MemberF' -Value 'fff'
"After"
$o | ft
The answer from #krome got me pointed in the right direction although that answer wouldn't work for me as there could be multiple threats for each device.
I used the answer from #scobi on Dynamically get PSCustomObject property and values to arrive at this answer which meets my requirement that the new properties be generated dynamically.
foreach($device in $devices){
$threats = Get-Threats $device
if($null -ne $threats){
$i = 1
foreach($threat in $threats){
$threat | Get-Member -MemberType NoteProperty | % Name | %{
Add-Member -InputObject $device -NotePropertyName ("Threat"+$i.ToString() + "_" + $_) -NotePropertyValue $threat.$_ -Force
}
$i++
}
}
}
Write-Output $devices
I loop over each device in the devices array and call my
Get-Threats function.
The if statement prevents the loop from running for any devices
that don't have threats.
$i is used as my counter to increment the property name for each
threat found so the properties will all have unique names
I then loop over each threat found piping to Get-Member to retrieve
the property name and values
I use Add-Member to add additional properties for each threat found
to each device in the loop, using the counter to give each propery a unique name

Get the term set name that is being used in sharepoint sites

I have been tasked to get the MMS term sets that are being used based on terms(not with null value of MMS column in the list's items ) in all the sites so that only those MMS terms sets can get migrated to the other sharepoint environment. On a base level I'm using below script
$FieldCollection= (Get-SPWeb https:/sharepoint.com/sites/pssl/mgmt).Lists.Fields
$MetadataField = New-Object psobject
$MetadataField | Add-Member -MemberType NoteProperty -Name "ParentListUrl" -value ""
$MetadataField | Add-Member -MemberType NoteProperty -Name "ParentListTitle" -value ""
$MetadataField | Add-Member -MemberType NoteProperty -Name "FieldTitle" -value ""
$MetadataField | Add-Member -MemberType NoteProperty -Name "FieldId" -value ""
$matches = #();
foreach($field in $FieldCollection)
{
if($field.GetType().Name -ne "TaxonomyField"){
continue;
}
#if($field.TermSetId.ToString() -ne $TermSet.Id.ToString()){continue;}
$tf = $MetadataField | Select-Object *;
$tf.ParentListUrl = $field.ParentList.ParentWeb.Url;
$tf.ParentListTitle = $field.ParentList.Title;
$tf.FieldTitle = $field.Title;
$tf.FieldId = $field.ID;
$matches += $tf;
}
return $matches;
but it returns only managed metadata columns defined in the list, but not they are being used in the list. Can anybody help me to achieve the task.
I'm not an expert in Sharepoint API, but I'm trying to understand what the problem is to help you and I can't.
I notice that you create an object $MetadataField before a loop, then kind of create a replica $MetadataField | Select-Object * and then add in a array.
As you say, the returned objects should only have the columns of ParentListUrl,ParentListTitle,FieldTitle and FieldId which is what I expect from the sample above. Can you elaborate more on what you are looking for? Maybe update the entire function into your question and post your returned expectation. this way I can try to help you out.
Btw, the ; is not required and you should create a new instance of the object within the loop. You can use the same method or first create a hash key that drives the properties of a custom object. For example in your loop adjust the following.
$hash=#{
Property1="Value1"
Property2="Value2"
}
New-Object -Type PSObject -Property $hash
Also if you function returns directly each found item without extra processing, then you can skip adding them in an array and just write in the output like I do in my example. To make it more clear, if I would put a loop around my example in a function and execute, then I would get a recordset with custom object with Property1 and Property2

Difference between PSObject, Hashtable, and PSCustomObject

Can anybody explain the details? If I create an object using
$var = [PSObject]#{a=1;b=2;c=3}
and then I look for its type using getType() PowerShell tells me it's of type Hashtable.
When using Get-Member (alias gm) to inspect the object it's obvious that a hashtable has been created, since it has a keys and a values property. So what's the difference to a "normal" hashtable?
Also, what's the advantage of using a PSCustomObject? When creating one using something like this
$var = [PSCustomObject]#{a=1;b=2;c=3}
the only visible difference to me is the different datatype of PSCustomObject. Also instead of keys and value properties, a inspection with gm shows that now every key has been added as a NoteProperty object.
But what advantages do I have? I'm able to access my values by using its keys, just like in the hashtable. I can store more than simple key-value pairs (key-object pairs for example) in the PSCustomObject, JUST as in the hashtable. So what's the advantage? Are there any important differences?
One scenario where [PSCustomObject] is used instead of HashTable is when you need a collection of them. The following is to illustrate the difference in how they are handled:
$Hash = 1..10 | %{ #{Name="Object $_" ; Index=$_ ; Squared = $_*$_} }
$Custom = 1..10 | %{[PSCustomObject] #{Name="Object $_" ; Index=$_ ; Squared = $_*$_} }
$Hash | Format-Table -AutoSize
$Custom | Format-Table -AutoSize
$Hash | Export-Csv .\Hash.csv -NoTypeInformation
$Custom | Export-Csv .\CustomObject.csv -NoTypeInformation
Format-Table will result in the following for $Hash:
Name Value
---- -----
Name Object 1
Squared 1
Index 1
Name Object 2
Squared 4
Index 2
Name Object 3
Squared 9
...
And the following for $CustomObject:
Name Index Squared
---- ----- -------
Object 1 1 1
Object 2 2 4
Object 3 3 9
Object 4 4 16
Object 5 5 25
...
The same thing happens with Export-Csv, thus the reason to use [PSCustomObject] instead of just plain HashTable.
Say I want to create a folder. If I use a PSObject you can tell it is wrong by
looking at it
PS > [PSObject] #{Path='foo'; Type='directory'}
Name Value
---- -----
Path foo
Type directory
However the PSCustomObject looks correct
PS > [PSCustomObject] #{Path='foo'; Type='directory'}
Path Type
---- ----
foo directory
I can then pipe the object
[PSCustomObject] #{Path='foo'; Type='directory'} | New-Item
From the PSObject documentation:
Wraps an object providing alternate views of the available members and ways to extend them. Members can be methods, properties, parameterized properties, etc.
In other words, a PSObject is an object that you can add methods and properties to after you've created it.
From the "About Hash Tables" documentation:
A hash table, also known as a dictionary or associative array, is a compact data structure that stores one or more key/value pairs.
...
Hash tables are frequently used because they are very efficient for finding and retrieving data.
You can use a PSObject like a Hashtable because PowerShell allows you to add properties to PSObjects, but you shouldn't do this because you'll lose access to Hashtable specific functionality, such as the Keys and Values properties. Also, there may be performance costs and additional memory usage.
The PowerShell documentation has the following information about PSCustomObject:
Serves as a placeholder BaseObject when PSObject's constructor with no parameters is used.
This was unclear to me, but a post on a PowerShell forum from the co-author of a number of PowerShell books seems more clear:
[PSCustomObject] is a type accelerator. It constructs a PSObject, but does so in a way that results in hash table keys becoming properties. PSCustomObject isn't an object type per se – it's a process shortcut. ... PSCustomObject is a placeholder that's used when PSObject is called with no constructor parameters.
Regarding your code, #{a=1;b=2;c=3} is a Hashtable. [PSObject]#{a=1;b=2;c=3} doesn't convert the Hashtable to a PSObject or generate an error. The object remains a Hashtable. However, [PSCustomObject]#{a=1;b=2;c=3} converts the Hashtable into a PSObject. I wasn't able to find documentation stating why this happens.
If you want to convert a Hashtable into an object in order to use its keys as property names you can use one of the following lines of code:
[PSCustomObject]#{a=1;b=2;c=3}
# OR
New-Object PSObject -Property #{a=1;b=2;c=3}
# NOTE: Both have the type PSCustomObject
If you want to convert a number of Hashtables into an object where their keys are property names you can use the following code:
#{name='a';num=1},#{name='b';num=2} |
% { [PSCustomObject]$_ }
# OR
#{name='a';num=1},#{name='b';num=2} |
% { New-Object PSObject -Property $_ }
<#
Outputs:
name num
---- ---
a 1
b 2
#>
Finding documentation regarding NoteProperty was difficult. In the Add-Member documentation, there isn't any -MemberType that makes sense for adding object properties other than NoteProperty. The Windows PowerShell Cookbook (3rd Edition) defined the Noteproperty Membertype as:
A property defined by the initial value you provide
Lee, H. (2013). Windows PowerShell Cookbook. O'Reilly Media, Inc. p. 895.
One advantage I think for PSObject is that you can create custom methods with it.
For example,
$o = New-Object PSObject -Property #{
"value"=9
}
Add-Member -MemberType ScriptMethod -Name "Sqrt" -Value {
echo "the square root of $($this.value) is $([Math]::Round([Math]::Sqrt($this.value),2))"
} -inputObject $o
$o.Sqrt()
You can use this to control the sorting order of the PSObject properties (see PSObject sorting)
I think the biggest difference you'll see is the performance. Have a look at this blog post:
Combining Objects Efficiently – Use a Hash Table to Index a Collection of Objects
The author ran the following code:
$numberofobjects = 1000
$objects = (0..$numberofobjects) |% {
New-Object psobject -Property #{'Name'="object$_";'Path'="Path$_"}
}
$lookupobjects = (0..$numberofobjects) | % {
New-Object psobject -Property #{'Path'="Path$_";'Share'="Share$_"}
}
$method1 = {
foreach ($object in $objects) {
$object | Add-Member NoteProperty -Name Share -Value ($lookupobjects | ?{$_.Path -eq $object.Path} | select -First 1 -ExpandProperty share)
}
}
Measure-Command $method1 | select totalseconds
$objects = (0..$numberofobjects) | % {
New-Object psobject -Property #{'Name'="object$_";'Path'="Path$_"}
}
$lookupobjects = (0..$numberofobjects) | % {
New-Object psobject -Property #{'Path'="Path$_";'Share'="Share$_"}
}
$method2 = {
$hash = #{}
foreach ($obj in $lookupobjects) {
$hash.($obj.Path) = $obj.share
}
foreach ($object in $objects) {
$object |Add-Member NoteProperty -Name Share -Value ($hash.($object.path)).share
}
}
Measure-Command $method2 | select totalseconds
Blog author's output:
TotalSeconds
------------
167.8825285
0.7459279
His comment regarding the code results is:
You can see the difference in speed when you put it all together. The object method takes 167 seconds on my computer while the hash table method will take under a second to build the hash table and then do the lookup.
Here are some of the other, more-subtle benefits:
Custom objects default display in PowerShell 3.0
We have a bunch of templates in our Windows-PKI and we needed a script, that has to work with all active templates. We do not need to dynamically add templates or remove them.
What for me works perfect (since it is also so "natural" to read) is the following:
$templates = #(
[PSCustomObject]#{Name = 'template1'; Oid = '1.1.1.1.1'}
[PSCustomObject]#{Name = 'template2'; Oid = '2.2.2.2.2'}
[PSCustomObject]#{Name = 'template3'; Oid = '3.3.3.3.3'}
[PSCustomObject]#{Name = 'template4'; Oid = '4.4.4.4.4'}
[PSCustomObject]#{Name = 'template5'; Oid = '5.5.5.5.5'}
)
foreach ($template in $templates)
{
Write-Output $template.Name $template.Oid
}
Type-1: $PSCustomObject = [PSCustomObject] #{a=1;b=2;c=3;d=4;e=5;f=6}
Type-2: $PsObject = New-Object -TypeName PSObject -Property #{a=1;b=2;c=3;d=4;e=5;f=6}
The only difference between Type-1 & Type-2
Type-1 Property are displayed in same order as we added
Type-1 enumerates the data faster
Type-1 will not work with systems running PSv2.0 or earlier
Both Type-1 & Type-2 are of type “System.Management.Automation.PSCustomObject”
Difference between HashTable and PSCustomObject/PSObject is
You can add new methods and properties to PSCustomObject/PSObject
You can use PSCustomObject/PSObject for pipeline parameter binding using ValueFromPipelineByPropertyName as explained by Zombo
example: [PSCustomObject] #{Path='foo'; Type='directory'} | New-Item