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

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.

Related

Unable to export system.object in a readable form using PowerShell

I've looked through other answers and I am still struggling. I have some data that comes from a RESTAPI and I get that data using Invoke-RestMethod so it is already converted from the returned JSON
$scans = Invoke-RestMethod -URI "https://<url>/api/v1/<api key>/scans"
I get back an object with two fields
Message:
Data:
$Scans.Data contains a hash table converted from the JSON output with each entry in the table having the following key:value pairs
ScanID
Username
Targets
Name
This is the output of $scans | Get-Member
$scans | 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()
name NoteProperty string name=<redacted>
scan_id NoteProperty string scan_id=<redacted>
targets NoteProperty Object[] targets=System.Object[]
user_name NoteProperty string user_name=<redacted>
I need to export all data into a CSV, but $scan.data.targets only shows as System.Object[]
I can't use -expandproperty because I am selecting multiple fields.
How do I convert $scans.data.targets into a readable form for the export, whilst keeping it linked with the other fields for that entry?
something equivalent to:
$scans.data | export-CSV <filepath>
but exporting in a format that is readable when I open the CSV
As commented, you could opt for a combined field of targets in the output:
# demo
$scan = [PsCustomObject]#{data = [PsCustomObject]#{name = 'blah'; scan_id = 123; targets = 'abc','def','ghi'; user_name = 'ItIsMe'}}
$scan.data | Select-Object *, #{Name = 'targets'; Expression = {$_.targets -join '; '}} -ExcludeProperty targets |
Export-Csv -Path 'D:\Test\blah.csv' -NoTypeInformation
which (when displayed in the console) looks like this:
name scan_id user_name targets
---- ------- --------- -------
blah 123 ItIsMe abc; def; ghi
Or create output where every target gets its own row in the CSV:
$result = foreach ($item in $scan.data) {
foreach ($target in $item.targets) {
[PsCustomObject]#{
Name = $item.name
ScanID = $item.scan_id
Target = $target
UserName = $item.user_name
}
}
}
# output on screen
$result
# output to CSV file
$result | Export-Csv -Path 'D:\Test\blah.csv' -NoTypeInformation
which gives you this:
Name ScanID Target UserName
---- ------ ------ --------
blah 123 abc ItIsMe
blah 123 def ItIsMe
blah 123 ghi ItIsMe

Sort-Object produces different objects when using -Descending

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.

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.

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.