Powershell Sort-Object PSCustomObject with Object[] as value - powershell

I am using RestAPI access to azure. I am getting a list of changesets for a build ID.
I would like to sort the object type with increasing Changeset number.
The type I receive for $changeset.GetType():
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
This is the output $changeset:
count value
----- -----
50 {#{id=68.......
Therefore, I checked the type of value $changeset.value.GetType():
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
And afterwards I checked the type of an element:
$changeset.value[0].GetType():
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False PSCustomObject System.Object
I did try Sort-Object with ascending and descending, but it does not change the order:
$changeset = $changeset | sort-object { $_.value.id } -desc
I would like to keep the structure as it is to not break something. There are lots of properties within the component.

Without an example working dataset, it's hard create an absolute working solution for your use case, however you could sort the data using the below:
$changeset | Select-Object -ExpandProperty value | Sort-Object -Property id
This would return all of the objects under the immediate property value and sorted on the property id.

If you want to retain the object as a whole with its present structure:
$changeset.value = #($changeset.value | Sort-Object -Descending -Property Id)
That is:
You must apply the Sort-Object call to the .value property, which contains the array of [pscustomobject] instances you want to sort by their .Id property values.
Enclosing the command in #(...), the array-subexpression operator, ensures that the sort objects are treated as an array, even if situationally only one object may be present.
Assigning the results back to $changeset.value replaces the original array with a new, sorted array.

Related

Searching for several Operating Systems with Get-ADComputer using LDAP Filter

I am using the below code to get Active Directory information with Get-ADComputer and it works fine
$computersFilter= "(&(operatingSystem=*Windows 7*)(name=*-*)(!name=V7-*)(!name=*-none)(!name=*-oncall)(!name=*-blackbaud)(!name=sc-win7-1)(!name=ut-swclient-01))"
$computers= Get-ADComputer -LDAPFilter $computersFilter -Property LastLogonDate | Select-Object Name, OperatingSystem,LastLogonDate
$computers | Select Name, LastlogonDate, OperatingSystem | Export-Csv $ServiceTagsPath -NoTypeInformation
I'd like to also retrieve computers with operating systems above Windows 7 (Windows 8, 8.1, and Windows 10) but when I change the filter like this:
(&(operatingSystem=*Windows 7*)(operatingSystem=*Windows 8*)(operatingSystem=*Windows 10*) ...
nothing is returned into the $computers variable
so what's the right way to do this?
The way you have defined $computersFilter, if you look at it's type you will notice that it is of type string. And hence it doesn't return anything.
PS C> $computersFilter = "(&(operatingSystem=*Windows7*))(name=*-*)(!name=V7-*)(!name=*-none)(!name=*-oncall)(!name=*-blackbaud)(!name=sc-win7-1)(!name=ut-swclient-01))"
PS C> $computersFilter.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True String System.Object
$computersFilter should be declared as an array, so that the -LDAPFilter parameter is evaluated against the collection of object, like this -
PS C>$computersFilter = #("operatingSystem=*Windows7*", "name=*-*", "!name=V7-*", "!name=*-none", "!name=*-oncall", "!name=*-blackbaud", "!name=sc-win7-1", "!name=ut-swclient-01")
PS C> $computersFilter.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array

What are the rules for automatic arrays wrapping/unwrapping?

Consider this code:
$a = '[{"a":"b"},{"c":"d"}]'
"Test1"
$a | ConvertFrom-Json | ForEach-Object { $_.GetType() }
"Test2"
$b = $a | ConvertFrom-Json
$b | ForEach-Object { $_.GetType() }
This produces the following output:
Test1
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Test2
True False PSCustomObject System.Object
True False PSCustomObject System.Object
Evidently, if we use a temporary variable, whatever is passed down the pipeline is not the same thing that is passed if we do not use one.
I would like to know what the rules that powershell uses for automatic array wrapping / unwrapping are, and if the using a temp var the best course of action if we need to iterate through a json array.
Update 1
Logically ConvertFrom-Json should return an array with the input given and ForEach-Object should iterated on the said array. However in the first test this does not happen. Why?
Update 2
Is it possible that it's ConvertFrom-Json specific? Like bug/issue?
There is only one rule with regard to pipeline items' unwrapping: all arrays and collections written to the pipeline are always getting unwrapped to the items ("unwrapped one level down" or "unwrapped in a non-recursive fashion" would be more correct statement but for the sake of simplicity we are not going to consider nested arrays so far).
There is still a possibility to override this behavior by using unary comma operator:
$a = 1,2
$a | ForEach-Object{ $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
True True Int32 System.ValueType
,$a | ForEach-Object{ $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
In the second case Powershell pipeline engine unwrapped $a but afterwards the result got wrapped back to array by , operator.
As to ConvertFrom-Json case I personally believe that its observed behavior is more predictable as it allows you to capture JSON arrays as a whole by default.
If you are interested in the details, the function Get-WrappedArray in the code below imitates ConvertFrom-Json's behavior:
function Get-WrappedArray {
Begin { $result = #() }
Process { $result += $_ }
End { ,$result }
}
$a | Get-WrappedArray | ForEach-Object{ $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
$b = $a | Get-WrappedArray
$b | ForEach-Object{ $_.GetType() }
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Int32 System.ValueType
True True Int32 System.ValueType

Confused Powershell returned array type

The following shows that the returned type are different depends on how many rows returned. Why it's designed this way? It's very easy to make assumption it always returns an array and write the code $a.Length $a | % { ....} which will raise error when it returns only one row, unless the it's written as $a = #(invoke-....), which is easy to forget.
$a=Invoke-Sqlcmd -ServerInstance server "select 1 a"
$b=Invoke-Sqlcmd -ServerInstance server "select 1 a union all select 2"
$a.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False DataRow System.Object
And the following statement returns an array of object (BTW, why not an array of DataRow?)
$b.GetType()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
However, gm returns the same type for both variables. Why it's designed this way which can be very confused.
Question:
What's the point that the array is removed when only one item is returned?
Why gm get item type of an array? How to gm of an array?
Why getType() cannot return the data type of the item when it returns an array type?
?
PS SQLSERVER:\> $a|gm
TypeName: System.Data.DataRow
Name MemberType Definition
---- ---------- ----------
AcceptChanges Method void AcceptChanges()
......
ToString Method string ToString()
Item ParameterizedProperty System.Object Item(int columnIndex) {get;set;}, System.Object Item(string co...
a Property int a {get;set;}
PS SQLSERVER:\> $b|gm
TypeName: System.Data.DataRow
Name MemberType Definition
---- ---------- ----------
AcceptChanges Method void AcceptChanges()
......
ToString Method string ToString()
Item ParameterizedProperty System.Object Item(int columnIndex) {get;set;}, System.Object Item(string co...
a Property int a {get;set;}
Most of the time in PowerShell, functions/cmdlets that return objects, simply return the object. If more than 1 object is to be returned, the function just keeps returning objects until it's done. PowerShell handles all of the returned objects and gives you an array.
gm is an alias for Get-Member. When you call it by piping to it $a | gm, you are invoking it as a pipeline. In this case, each object in $a is individually passed to Get-Member, so it returns the type of the individual object(s). If they are all the same, then it will only display it once, but it's actually checking all of them. You can prevent this by calling Get-Member -InputObject $a which should show you the array type if it is an array.
Similar to the above, .GetType() gets the type of whatever object it's invoked on, so if it's an array, then it returns that; it's not looking at the individual elements.
I also want to point out that % (ForEach-Object) and foreach() work fine when not used on an array: "hello" | % { $_ }, as does foreach($msg in "hello") { $msg }.
To address the issue of $a.Length not working when the return value is a single object, it depends on your powershell version.
In version 2, you will see the behavior you are seeing: you'll have to wrap it in an array first, or test for an array with something like:
if ($a -is [Array]) {
$itemCount = $a.Length
} else {
$itemCount = 1
}
But, in powershell 3+, you can do $a.Length even if $a is just some object and not an array. It will return 1. Length may not show up in autocomplete or in intellisense, but it will work.

Powershell - how do I edit an existing property in a custom object

I looking for way how update noteproperty in existing psobject, for example I have system.array of psobjects ($a):
Group Assigment
----- ---------
Group1 Home
Group2 Office
Question is how update 'Home' to something other.
$a | gm:
TypeName: System.Management.Automation.PSCustomObject
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Assigment NoteProperty System.String Assigment=Office
Group NoteProperty System.String Group=Group1
$a.GetType():
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Thank you for future help.
It's not really clear what is your problem : select the good object or update it's value ?
$col=#()
$props=#{"group"="group1";"assignment"="home"}
$col += new-object pscustomobject -property $props
$props2=#{"group"="group2";"assignment"="office"}
$col += new-object pscustomobject -property $props2
#select object with home assignment
$rec=$col | where {$_.assignment -eq "home"}
#replace the value
$rec.assignment="elsewhere"
#display collection with updated value
$col
I don't think this works if $rec returns more than one record, though.
For example:
$rec = $col | where {$_.assignment -ne $null}
$rec.assignment = "elsewhere"
In theory that should set every individual record's assignment to "elsewhere" but it really just returns an error that the property "assignment" cannot be found on this object. I think for this to really work you'd need:
$recs = $col | where {$_.assignment -ne $null}
foreach ($r in $recs) {
$r.assignment="elsewhere"
}
Unless there's a way to set a value to every record in a given array, which I freely admit there may be.

Create an array, hashtable and dictionary?

What is the proper way to create an array, hashtable and dictionary?
$array = [System.Collections.ArrayList]#()
$array.GetType() returns ArrayList, OK.
$hashtable = [System.Collections.Hashtable]
$hashtable.GetType() returns RuntimeType, Not OK.
$dictionary = ?
How to create a dictionary using this .NET way?
What is the difference between dictionary and hashtable? I am not sure when I should use one of them.
The proper way (i.e. the PowerShell way) is:
Array:
> $a = #()
> $a.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Object[] System.Array
Hashtable / Dictionary:
> $h = #{}
> $h.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Hashtable System.Object
The above should suffice for most dictionary-like scenarios, but if you did explicitly want the type from Systems.Collections.Generic, you could initialise like:
> $d = New-Object 'system.collections.generic.dictionary[string,string]'
> $d.gettype()
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True True Dictionary`2 System.Object
> $d["foo"] = "bar"
> $d | Format-Table -auto
Key Value
--- -----
foo bar
If you want to initialize an array you can use the following code:
$array = #() # empty array
$array2 = #('one', 'two', 'three') # array with 3 values
If you want to initialize hashtable use the following code:
$hashtable = #{} # empty hashtable
$hashtable2 = #{One='one'; Two='two';Three='three'} # hashtable with 3 values
Hashtable and dictionary in Powershell is pretty much the same, so I suggest using hashtable in almost all cases (unless you need to do something in .NET where Dictionary is required)