Sort-Object produces different objects when using -Descending - powershell

Why does Sort-Object produce different objects when -Descending is used? The NoteProperty members are not the same.
Also, when writing to the console, the Name property does not appear unless -Descending is used. Why is that?
C:>Get-Type | Select-Object -Property BaseType,Name | gm
TypeName: Selected.System.RuntimeType
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
BaseType NoteProperty RuntimeType BaseType=System.Object
Name NoteProperty string Name=Registry
C:>Get-Type | Select-Object -Property BaseType,Name | Sort-Object -Property BaseType,Name | gm
TypeName: Selected.System.RuntimeType
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
BaseType NoteProperty object BaseType=null
Name NoteProperty string Name=_Activator
C:>Get-Type | Select-Object -Property BaseType,Name | Sort-Object -Property BaseType,Name -Descending | gm
TypeName: Selected.System.RuntimeType
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
BaseType NoteProperty RuntimeType BaseType=System.Xml.Xsl.XsltException
Name NoteProperty string Name=XsltCompileException
My apologies for not including information about Get-Type. https://gallery.technet.microsoft.com/scriptcenter/Get-Type-Get-exported-fee19cf7

This is not a cmdlet that is natively part of PoSH. It's either something the OP wrote or got from another source. If from another source than the OP should reach out to that author to ask what is to be expected.
If you do this same things using a built-in cmdlet, say Get-Date, we see all members are the same.
Get-Date |
Select-Object -Property BaseType,Name |
Sort-Object -Property BaseType, Name |
Get-Member
TypeName: Selected.System.DateTime
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
BaseType NoteProperty object BaseType=null
Name NoteProperty object Name=null
Get-Date | Select-Object -Property BaseType,Name |
Sort-Object -Property BaseType, Name -Descending |
Get-Member
TypeName: Selected.System.DateTime
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
BaseType NoteProperty object BaseType=null
Name NoteProperty object Name=null
So, would seem to point specifically to the implementation of this cmdlet the OP is looking for clarity on.

It is possible that your GetType invocation results in an array starting with $null as it has a try-catch block providing an error value of $null. Then, should any parameters be passed to the Get-Type, that null would be filtered out, but you pass none, so null is still present in the output. Then, as the result is piped to Select-Object, only the first object in the pipe is parsed to check if all the columns are present to display, and should the null be the first object, it has no properties thus nothing gets displayed.
To fix, add the | Where-Object {$_ -ne $null} into the Get-Type.ps1 script right before | Where-Object -FilterScript $WhereBlock in the last significant line. This will filter out any nulls produced by previous try-catch block, and you will only get an array of objects that have values.

Related

Members of CimClass is different through pipeline

I would have expected to get the same "type" from both of the following commands. The second prepends the type name with "Selected."
>(Get-CimInstance CIM_LogicalDisk).CimClass | gm
TypeName: Microsoft.Management.Infrastructure.CimClass
Name MemberType Definition
---- ---------- ----------
Dispose Method void Dispose(), void IDisposable.Dispose()
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
CimClassMethods Property Microsoft.Management.Infrastructure.Generic.CimReadOnlyKeyedCollection[Microsoft.M
CimClassProperties Property Microsoft.Management.Infrastructure.Generic.CimReadOnlyKeyedCollection[Microsoft.M
CimClassQualifiers Property Microsoft.Management.Infrastructure.Generic.CimReadOnlyKeyedCollection[Microsoft.M
CimSuperClass Property cimclass CimSuperClass {get;}
CimSuperClassName Property string CimSuperClassName {get;}
CimSystemProperties Property Microsoft.Management.Infrastructure.CimSystemProperties CimSystemProperties {get;}
CimClassName ScriptProperty System.String CimClassName {get=[OutputType([string])]...
The second reveals a different type.
>Get-CimInstance CIM_LogicalDisk | Select-Object -Property CimClass | gm
TypeName: Selected.Microsoft.Management.Infrastructure.CimInstance
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
CimClass NoteProperty cimclass CimClass=root/cimv2:Win32_MappedLogicalDisk
>$PSVersionTable.PSVersion.ToString()
5.1.14409.1018
Using Select-Object with the -Property parameter outputs an object with the selected properties.
To get a "bare" property value, use the -ExpandProperty parameter instead.
Get-CimInstance CIM_LogicalDisk | Select-Object -ExpandProperty CimClass | gm

How use "Where-Object" condition with '[PScustomobject]'?

I have some code:
$output = [PSCustomObject]#{
Name = $ws.UsedRange.Columns.Item(1).Value2
Department = $ws.UsedRange.Columns.Item(3).Value2
}
$output | 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()
Department NoteProperty System.Object[,] Department=System.Object[,]
Name NoteProperty System.Object[,] Name=System.Object[,]
I need to sort and filter my $output, but I can't. Nothing happens. Probably doing something wrong.
PS> $output
Name Department
---- ----------
{Numbers, 1,2,3,4,5,6,7...} {Sales,IT,Accounting,Developers...}
And my condition:
PS> $output | Sort-Object Department -Descending | Where-Object {$_.Department -eq "Sales"}
Name Department
---- ----------
{Numbers, 1,2,3,4,5,6,7...} {Sales,IT,Accounting,Developers...}
You created a single object with 2 properties, each of which contains all values of its associated column. Since Sort-Object and Where-Object sort and filter lists of objects by their properties there's nothing for these cmdlets to do here.
What you actually want to do is create one object per row.
$output = foreach ($row in $ws.UsedRange.Rows) {
[PSCustomObject]#{
Name = $row.Columns.Item(1).Value2
Department = $row.Columns.Item(3).Value2
}
}
Untested, since I don't have MS Office at hand here.

Get upgrade code for product code from registry

I need a hint how to get the upgrade code from an installed MSI out of the registry. Actually I'm having the product code, which can be retrieved from HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\.
Now I want to retrieve the upgrade code (based on the product code) from HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes.
My problem is that the product code is used as value-name, which means I've a REG_SZ where the name is the product code guid and the value is empty.
One way to retrieve the product code might be:
PS HKLM:\SOFTW...Codes> Get-ItemProperty * | select -First 1 | 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()
42F79228D77BA4A4EB5150F3DC090CE3 NoteProperty System.String 42F79228D77BA4A4EB5150F3DC090CE3=
...
How can I check if a PSCustomObject has the property 42F79228D77BA4A4EB5150F3DC090CE3?
Does anybody knows if there is a more elegant way?
This is how you can check. Working on that elegant solution...
$properties = Get-ItemProperty * | select -first 1 | Get-Member | Where-object {$_.MemberType -eq "NoteProperty"}
if("42F79228D77BA4A4EB5150F3DC090CE3" -in $properties.Name){
Write-Output "It's in there!"
}
Edit
This is a bit more elegant. It goes to the HKLM path, and checks for a PSChildName (Registry Key) that is the same as the code.
If found, it will return the Name and property. If not found, the variable $codeExists will be $null.
$code = "42F79228D77BA4A4EB5150F3DC090CE3"
$codeExists = Get-ChildItem "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes" | Where-Object {$_.PSChildName -eq $code}
if($codeExists){
Write-Output "It's in there!"
}

powershell outputs argument with (#{Name=name}:String)

I'm trying to run the command Get-VMNetworkAdapter on a list of VMs
I'm getting the list with the command:
Get-VM –ComputerName (Get-ClusterNode –Cluster clustername)|select name
and it looks fine, when I'm using
$vmm=Get-VM –ComputerName (Get-ClusterNode –Cluster clustername)|select name
foreach ($item in $vmm)
{Get-VMNetworkAdapter -VMName $item}
it gives me the exception
nvalidArgument: (#{Name=vmname}:String)
like it adds all those symbols..
What is the proper way to lose them?
You need to expand the property. Select doesn't remove the object otherwise:
$vmm = Get-VM –ComputerName (Get-ClusterNode –Cluster clustername) `
| Select-Object -ExpandProperty name
To explain what -ExpandProperty does:
First of all, the drawback of -ExpandProperty is that you can only do it to one property at a time.
Select-Object normally wraps the results in another object so that properties remain properties. If you say $x = Get-ChildItem C:\Windows | Select-Object Name, then you get an object array with one property: Name.
PS C:\> $x = Get-ChildItem C:\Windows | Select-Object Name
PS C:\> $x
Name
----
45235788142C44BE8A4DDDE9A84492E5.TMP
8A809006C25A4A3A9DAB94659BCDB107.TMP
.
.
.
PS C:\> $x[0].Name
45235788142C44BE8A4DDDE9A84492E5.TMP
PS C:\> $x[0].GetType().FullName
System.Management.Automation.PSCustomObject
Notice the header? Name is a property of the object.
Also, the base object with it's type is still kind of there:
PS C:\> $x | Get-Member
TypeName: Selected.System.IO.DirectoryInfo
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Name NoteProperty string Name=45235788142C44BE8A4DDDE9A84492E5.TMP
TypeName: Selected.System.IO.FileInfo
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
Name NoteProperty string Name=bfsvc.exe
Normally, that's all great. Especially because we normally want multiple properties of the object.
Sometimes, however, not what we want. Sometimes, we want an array that's the same type as the property we selected. When we use it later we want just that property and nothing else and we want it to be the exact same type as the property and nothing else.
PS C:\> $y = Get-ChildItem C:\Windows | Select-Object -ExpandProperty Name
PS C:\> $y
45235788142C44BE8A4DDDE9A84492E5.TMP
8A809006C25A4A3A9DAB94659BCDB107.TMP
.
.
.
PS C:\> $y[0].Name
PS C:\> $y[0]
45235788142C44BE8A4DDDE9A84492E5.TMP
PS C:\> $y.GetType().FullName
System.Object[]
PS C:\> $y[0].GetType().FullName
System.String
Notice there's no header, and any calls to a Name property fail; there is no Name property anymore.
And, there's nothing left over from the original object:
PS C:\> $y | Get-Member
TypeName: System.String
Name MemberType Definition
---- ---------- ----------
Clone Method System.Object Clone(), System.Object ICloneable.Clone()
.
.
.
.
Basically, here it's the equivalent of doing this:
$z = Get-ChildItem C:\Windows | ForEach-Object { $_.Name }
Which I think is how you had to do it in PowerShell v1.0 or v2.0... it's been too many years since I've used that to remember right.

Store just the value of an object in a variable

I want to store just the value of a PowerShell object in a variable.
Example
If we run:
$time = Get-Process System | select TotalProcessorTime
$time
This is the current output:
TotalProcessorTime
------------------
00:03:22.8281250
This is the output I would like:
00:03:22.8281250
Discussion
How do we store just the value? If we run $time | Get-Member, we see that PowerShell has stored a Selected.System.Diagnostics.Process that has the following properties:
Name MemberType Definition
---- ---------- ----------
Equals Method bool Equals(System.Object obj)
GetHashCode Method int GetHashCode()
GetType Method type GetType()
ToString Method string ToString()
TotalProcessorTime NoteProperty System.TimeSpan TotalProcessorTime=00:03:22.8281250
I have tried getting the value by running both $time.TotalProcessorTime and $time.ToString() without success.
The Object which produces that string "00:03:22.8281250":
(Get-Process System).TotalProcessorTime
The string itself:
(Get-Process System).TotalProcessorTime.ToString()