I have a Powershell Object[] ($myObject), I am looking to trim off sci_ from any object property name that begins with sci_ but keep the value of the properties
$myObject.psobject.Properties does not seem to reveal the property names but found $myObject | Get-Member -MemberType Properties | Select-Object Name does, but havent worked out a way to rename or trim off the sci_ from the property names?
The $myObject variable contains an array of objects, so loop over each object in the array to be able to inspect its properties via .psobject:
$myArray = $myObject |ForEach-Object {
# prepare dictionary to hold the (renamed) properties
$newProperties = [ordered]#{}
foreach($property in $_.psobject.Properties){
# remove any `sci_` prefix
$newName = $property.Name -replace '^sci_'
$newProperties[$newName] = $property.Value
}
# create new object based on the renamed properties
[pscustomobject]$newProperties
}
$myArray now contains an array of objects just like $myObject, except all the objects have had the sci_ prefix removed from all relevant property names
Related
I have a script that periodically generates a list of all files in a directory, and then writes a text file of the results to a different directory.
I'd like to change this so it checks the newest text file in the output directory, and only makes a new one if there's differences. It seemed simple enough.
Here's what I tried:
First I get the most recent file in the directory, grab the hash, and write my variable values to the console:
$lastFile = gci C:\ReportOutputDir | sort LastWriteTime | select -last 1 | Select-Object -ExpandProperty FullName
$oldHash = Get-FileHash $lastFile | Select-Object Hash
Write-Host 'lastFile = '$lastFile
Write-Host 'oldHash = '$oldHash
Output:
lastFile = C:\ReportOutputDir\test1.txt
oldHash = #{Hash=E7787C54F5BAE236100A24A6F453A5FDF6E6C7333B60ED8624610EAFADF45521}
Then I do the exact same gci on the FileList dir, and create a new file (new_test.txt), then grab the hash of this file:
gci -Path C:\FileLists -File -Recurse -Name -Depth 2 | Sort-Object | out-file C:\ReportOutputDir\new_test.txt
$newFile = gci C:\ReportOutputDir | sort LastWriteTime | select -last 1 | Select-Object -ExpandProperty FullName
$newHash = Get-FileHash $newFile | Select-Object Hash
Write-Host 'newFile = '$newFile
Write-Host 'newHash = '$newHash
Output:
newFile = C:\ReportOutputDir\new_test.txt
newHash = #{Hash=E7787C54F5BAE236100A24A6F453A5FDF6E6C7333B60ED8624610EAFADF45521}
Finally, I attempt my -eq operator where I'd usually simply remove the newFile if it's equal. For now, I'm just doing a simple :
if ($newHash -eq $oldHash){
'files are equal'
}
else {'files are not equal'}
And somehow, I'm getting
files are not equal
What gives? Also, for the record I was originally trying to save the gci output to a variable and comparing the contents of the last file to the gci output, but was also having trouble with the -eq operator. Fairly new to powershell stuff so I'm sure I'm doing something wrong here.
Select-Object Hash creates an object with a .Hash property and it is that property that contains the hash string.
The object returned is of type [pscustomobject], and two instances of this type never compare as equal - even if all their property names and values are equal:
The reason is that reference equality is tested, because [pscustomobject] is a .NET reference type that doesn't define custom equality-testing logic.
Testing reference equality means that only two references to the very same instance compare as equal.
A quick example:
PS> [pscustomobject] #{ foo = 1 } -eq [pscustomobject] #{ foo = 1 }
False # !! Two distinct instances aren't equal, no matter what they contain.
You have two options:
Compare the .Hash property values, not the objects as a whole:
if ($newHash.Hash -eq $oldHash.Hash) { # ...
If you don't need a [pscustomobject] wrapper for the hash strings, use Select-Object's -ExpandProperty parameter instead of the (possibly positionally implied) -Property parameter:
Select-Object -ExpandProperty Hash
As for why the Write-Host output matched:
When you force objects to be converted to string representations - essentially, Write-Host calls .ToString() on its arguments - the string representations of distinct [pscustomobject] instances that have the same properties and values will be the same:
PS> "$([pscustomobject] #{ foo = 1 })" -eq "$([pscustomobject] #{ foo = 1 })"
True # Same as: '#{foo=1}' -eq '#{foo=1}'
However, you should not rely on these hashtable-like string representations to determine equality of [pscustomobject]s as a whole, because of the inherent limitations of these representations, which can easily yield false positives.
This answer shows how to compare [pscustomobject] instances as a whole, by comparing all of their property values, by passing all property names to Compare-Object -Property - but note that this assumes that all property values are either strings or instances of .NET value types or corresponding properties must again either reference the very same instance of a .NET reference type or be of a type that implements custom equality-comparison logic.
How do I get properties that ONLY have populated values?
So for example if I run
Get-QADUser -Identity "SomeOne" -IncludeAllProperties
the output would of course include.. all properties, including those with and those without values. I want a listing of properties with values only. How is it done generally speaking?
This wouldn't be restricted to Quest Cmdlets, I only use Get-QADUser as an example.
You could try using the built-in (hidden) property of PowerShell objects called PSObject, which includes a property called Properties, i.e. a list of all properties on the parent object.
Maybe easier with an example. Take Get-Process... a process can have many attributes (properties) with or without values. In order to get just the ones with values you do this:
(Get-Process | Select -First 1).PSObject.Properties | ?{$_.Value -ne $null} | FT Name,Value
Note that I limited this to just the first process returned by Get-Process. We then get all the properties defined on that object, filtering where Value is not null and then displaying just the Name and Value for those properties.
To complement Charlie Joynt's helpful answer:
Below is convenience function Remove-NullProperties, which creates custom-object copies of its input objects populated with only the non-$null properties of the input objects.
Example use:
# Sample input collection, with 2 objects with different $null-valued
# properties.
$coll = [pscustomobject] #{ one = 'r1c1'; two = $null; three = 'r1c3' },
[pscustomobject] #{ one = 'r2c1'; two = 'r2c2'; three = $null }
# Output copies containing only non-$null-valued properties.
# NOTE: The `ForEach-Object { Out-String -InputObject $_ }` part is solely
# there to ensure that *all* resulting properties are shown.
# With the default output, only the properties found on the FIRST
# input object would be used in the output table.
$coll | Remove-NullProperties |
ForEach-Object { Out-String -InputObject $_ }
This yields the following - note how the respective null-valued properties were removed:
one three
--- -----
r1c1 r1c3
one two
--- ---
r2c1 r2c2
Remove-NullProperties source code:
<#
.SYNOPSIS
Removes properties with $null values from custom-object copies of
the input objects.
.DESCRIPTION
Note that output objects are custom objects that are copies of the input
objects with copies of only those input-object properties that are not $null.
CAVEAT: If you pipe multiple objects to this function, and these objects
differ in what properties are non-$null-valued, the default output
format will show only the non-$null-valued properties of the FIRST object.
Use ... | ForEach-Object { Out-String -InputObject $_ } to avoid
this problem.
.NOTES
Since the output objects are generally of a distinct type - [pscustomobject] -
and have only NoteProperty members, use of this function only makes sense
with plain-old data objects as input.
.EXAMPLE
> [pscustomobject] #{ one = 1; two = $null; three = 3 } | Remove-NullProperties
one three
--- -----
1 3
#>
function Remove-NullProperties {
param(
[parameter(Mandatory,ValueFromPipeline)]
[psobject] $InputObject
)
process {
# Create the initially empty output object
$obj = [pscustomobject]::new()
# Loop over all input-object properties.
foreach($prop in $InputObject.psobject.properties) {
# If a property is non-$null, add it to the output object.
if ($null -ne $InputObject.$($prop.Name)) {
Add-Member -InputObject $obj -NotePropertyName $prop.Name -NotePropertyValue $prop.Value
}
}
# Give the output object a type name that reflects the type of the input
# object prefixed with 'NonNull.' - note that this is purely informational, unless
# you define a custom output format for this type name.
$obj.pstypenames.Insert(0, 'NonNull.' + $InputObject.GetType().FullName)
# Output the output object.
$obj
}
}
These answers didn't work for me in the case of importing an object from an Infoblox csv file. Some values were the empty string, but not null. Testing whether a property is true or not, seems to work better for me. And the result is an object.
$a = [pscustomobject]#{one='hi';two='';three='there'}
$prop = $a.psobject.Properties | where value | foreach name
$a | select $prop
one three
--- -----
hi there
You first get its properties (since Get-QADUser depends on AD schema, the properties list is dynamic) with get-member -type property, then filter out those that don't have \{.*(get).*\} in its definition (that is, they are not "gettable"), then enumerate the resultant list by name and filter out nulls.
$someone=Get-QADUser -Identity "SomeOne" -IncludeAllProperties
$members=$someone|get-member -type property| where {$_.definition -match '\{.*(get).*\}'}
foreach ($member in $members) {
if ($someone[$member.name] -ne $null) {
write-host $member.name $someone[$member.name]
}
}
How do I get properties that ONLY have populated values?
So for example if I run
Get-QADUser -Identity "SomeOne" -IncludeAllProperties
the output would of course include.. all properties, including those with and those without values. I want a listing of properties with values only. How is it done generally speaking?
This wouldn't be restricted to Quest Cmdlets, I only use Get-QADUser as an example.
You could try using the built-in (hidden) property of PowerShell objects called PSObject, which includes a property called Properties, i.e. a list of all properties on the parent object.
Maybe easier with an example. Take Get-Process... a process can have many attributes (properties) with or without values. In order to get just the ones with values you do this:
(Get-Process | Select -First 1).PSObject.Properties | ?{$_.Value -ne $null} | FT Name,Value
Note that I limited this to just the first process returned by Get-Process. We then get all the properties defined on that object, filtering where Value is not null and then displaying just the Name and Value for those properties.
To complement Charlie Joynt's helpful answer:
Below is convenience function Remove-NullProperties, which creates custom-object copies of its input objects populated with only the non-$null properties of the input objects.
Example use:
# Sample input collection, with 2 objects with different $null-valued
# properties.
$coll = [pscustomobject] #{ one = 'r1c1'; two = $null; three = 'r1c3' },
[pscustomobject] #{ one = 'r2c1'; two = 'r2c2'; three = $null }
# Output copies containing only non-$null-valued properties.
# NOTE: The `ForEach-Object { Out-String -InputObject $_ }` part is solely
# there to ensure that *all* resulting properties are shown.
# With the default output, only the properties found on the FIRST
# input object would be used in the output table.
$coll | Remove-NullProperties |
ForEach-Object { Out-String -InputObject $_ }
This yields the following - note how the respective null-valued properties were removed:
one three
--- -----
r1c1 r1c3
one two
--- ---
r2c1 r2c2
Remove-NullProperties source code:
<#
.SYNOPSIS
Removes properties with $null values from custom-object copies of
the input objects.
.DESCRIPTION
Note that output objects are custom objects that are copies of the input
objects with copies of only those input-object properties that are not $null.
CAVEAT: If you pipe multiple objects to this function, and these objects
differ in what properties are non-$null-valued, the default output
format will show only the non-$null-valued properties of the FIRST object.
Use ... | ForEach-Object { Out-String -InputObject $_ } to avoid
this problem.
.NOTES
Since the output objects are generally of a distinct type - [pscustomobject] -
and have only NoteProperty members, use of this function only makes sense
with plain-old data objects as input.
.EXAMPLE
> [pscustomobject] #{ one = 1; two = $null; three = 3 } | Remove-NullProperties
one three
--- -----
1 3
#>
function Remove-NullProperties {
param(
[parameter(Mandatory,ValueFromPipeline)]
[psobject] $InputObject
)
process {
# Create the initially empty output object
$obj = [pscustomobject]::new()
# Loop over all input-object properties.
foreach($prop in $InputObject.psobject.properties) {
# If a property is non-$null, add it to the output object.
if ($null -ne $InputObject.$($prop.Name)) {
Add-Member -InputObject $obj -NotePropertyName $prop.Name -NotePropertyValue $prop.Value
}
}
# Give the output object a type name that reflects the type of the input
# object prefixed with 'NonNull.' - note that this is purely informational, unless
# you define a custom output format for this type name.
$obj.pstypenames.Insert(0, 'NonNull.' + $InputObject.GetType().FullName)
# Output the output object.
$obj
}
}
These answers didn't work for me in the case of importing an object from an Infoblox csv file. Some values were the empty string, but not null. Testing whether a property is true or not, seems to work better for me. And the result is an object.
$a = [pscustomobject]#{one='hi';two='';three='there'}
$prop = $a.psobject.Properties | where value | foreach name
$a | select $prop
one three
--- -----
hi there
You first get its properties (since Get-QADUser depends on AD schema, the properties list is dynamic) with get-member -type property, then filter out those that don't have \{.*(get).*\} in its definition (that is, they are not "gettable"), then enumerate the resultant list by name and filter out nulls.
$someone=Get-QADUser -Identity "SomeOne" -IncludeAllProperties
$members=$someone|get-member -type property| where {$_.definition -match '\{.*(get).*\}'}
foreach ($member in $members) {
if ($someone[$member.name] -ne $null) {
write-host $member.name $someone[$member.name]
}
}
I find myself continually faced with the need to store mixed-type data in some kind of a structure for later lookup.
For a recent example, I am performing data migration and I will store the old UUID, new UUID, source environment, target environment, and schema for an unknown number of entries.
I have been meeting this need by creating an array and inserting System.Objects with NoteProperty members for each of the columns of data.
This strikes me as a very clumsy approach but I feel like I may be limited by Powershell's functionality. If I need to, for example, locate all entries that used a particular schema, I write a foreach loop that sticks each entry with a matching schema name in a whole new array that I can return. I would really like the ability to more easily search for all objects that contain a member matching a particular value, modify existing members, etc.
Is there a better built-in data structure that will suit my needs, or is creating a custom object the right thing to do?
For reference, I'm doing something like this to create my structure:
$objectArray= #();
foreach(thing to process){
$tempObj = New-Object System.Object;
$tempObj | Add-Member -MemberType NoteProperty -Name "membername" -Value xxxxx
....repeat for each member...
$objectArray += $tempObj
}
If I need to find something in it, I then have to:
$matchingObjs = #()
foreach ($obj in $objectArray){
if($obj.thing -eq value){$matchingObjs += $obj}
}
This really sucks and I know there has to be a more elegant way. I'm still fairly new to powershell so I don't know what utilities it has to help me. I'm using v5.
With PowerShell 3.0 you could use a [PSCustomObject], here's an article on the different object creation methods.
Also setting the array equal to the output of the foreach loop will be more efficient than repeatedly recreating an array with +=.
$objectArray = foreach ($item in $collection) {
[pscustomobject]#{
"membername" = "xxxxx"
}
}
The Where-Object cmdlet or the .where() method looks like what you need in your second loop.
$matchingObjs = $objectArray | Where-Object {$_.thing -eq "value"}
It also sounds like you could use Where-Object/.where() to filter the initial data and just create an object which matches what you are looking for. For example:
$matchingObjs = $InputData |
Where-Object {$_.thing -eq "value"} |
ForEach-Object {
[pscustomobject]#{
"membername" = xxxxx
}
}
If your data can be expressed as key value pairs, then a hashtable will be the most efficient, see about_Hash_Tables for more info.
There is no built-in way to do what you are asking. One way is to segment your data into separate hashtables so you can do easy lookups by a common key, say the ID.
# Create a hastable for the IDs
$ids = #{};
foreach(thing to process){
$ids.Add($uid, 'Value')
}
# Find the $uid exists
$keyExists = $ids.Keys -Contains $uid
# Find value of stored for $uid
$keyValue = $ids[$uid]
As a side note, you don't have to create Syste.Object, you can simple do this:
$objectArray = #();
gci | % {
$objectArray += #{
'Key1' = 'Value 1'
'Key2' = 'Value 2'
}
}
If you need to compare complex objects, you can build them with #{} and then use Compare-Object on the two objects, just another idea.
For example, this will get a file listing of two different directories, and tell me what file exists or doesn't exist between the two directories:
$packages = (gci $boxStarterRepo -Recurse *.nuspec | Select-Object -ExpandProperty Name) -replace '.nuspec', ''
$packages += (gci $boxStarterPrivateRepo -Recurse *.nuspec | Select-Object -ExpandProperty Name) -replace '.nuspec', ''
$packages = $packages | Sort-Object
Compare-Object $packages $done
I have the following output from a PowerShell command and want to update the value for EmployeeID
I can filter the output with $test.identifiers | where {$_.name -like "EmployeeID" }
But if I try to update the value with
$test.identifiers | where {$_.name -like "EmployeeID" } | foreach {$_.values.value = "098324"}
I get an error
How can I update this nested value?
$_.values contains an array (or collection) objects, which explains why you can get (read) the .value property, but not set (write) it (see below).
If you expect the array to have just one element, simply use [0] to access that element directly:
$test.identifiers | where {$_.name -like "EmployeeID" } | foreach {
$_.values[0].value = '098324'
}
If there are multiple elements, use
$_.values | foreach { $_.value = '098324' } to assign to them all, or, alternatively in PSv4+,
$_.values.ForEach('value', '098324')
In PSv3+ a feature called member-access enumeration allows you to access a property on a collection and have the property values from the individual elements returned as an array.
However, that only works for getting properties, not for setting them.
When you try to set, only the collection's own properties are considered, which explains the error you saw - an array itself has no .value property.
While this asymmetry is by design, to avoid potentially unwanted bulk modification, the error message could certainly be more helpful.
Simple reproduction of the problem:
Create an object with property one containing a single-element array with another object, with property two:
$obj = [pscustomobject] #{ one = , [pscustomobject] #{ two = 2 } }
The default output looks as follows:
PS> $obj
one
---
{#{two=2}}
The outer {...} indicate an array, as in your case, and what's inside is a
hashtable-like notation that PowerShell uses to represent custom objects.
Getting the nested-inside-an-array object's two property works as intended:
PS> $obj.two
2
Trying to set it fails:
PS> $obj.two = 2.1
The property 'two' cannot be found on this object. Verify that the property exists and can be set.
To set, use .ForEach(), for instance:
PS> $obj.ForEach('two', 2.1); $obj
one
---
{#{two=2.1}}
Have you tried it this way with the full object path:
$test.identifiers | where {$_.name -like "EmployeeID" } | foreach {$_.identifiers.values.value = "098324"}