How do I get properties that ONLY have populated values? - powershell

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]
}
}

Related

PowerShell: Grep through object string representation [duplicate]

Let's say you have a giant object - one which may or may not have nested arrays / objects,
# Assuming 'user1' exists in the current domain
$obj = Get-ADUser 'user1' -Properties *
and I want to search that object for the string SMTP case-insensitively...
What I tried
$obj | Select-String "SMTP"
But it does not work because the match is inside a nested Collection... to be concise, it sits inside the property $obj.proxyAddresses.
If I run $obj.proxyAddress.GetType() it returns:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False ADPropertyValueCollection System.Collections.CollectionBase
What's the best way to go about this? I know you could loop through the properties and look for it manually using wildcard matching or .Contains(), but I'd prefer a built in solution.
Thus, it would be a grep for objects and not only strings.
Here's one solution. It can be very slow depending on what depth you search to; but a depth of 1 or 2 works well for your scenario:
function Find-ValueMatchingCondition {
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[PSObject]$InputObject
,
[Parameter(Mandatory = $true)]
[ScriptBlock]$Condition
,
[Parameter()]
[Int]$Depth = 10
,
[Parameter()]
[string]$Name = 'InputObject'
,
[Parameter()]
[System.Management.Automation.PSMemberTypes]$PropertyTypesToSearch = ([System.Management.Automation.PSMemberTypes]::Properties)
)
Process {
if ($InputObject -ne $null) {
if ($InputObject | Where-Object -FilterScript $Condition) {
New-Object -TypeName 'PSObject' -Property #{Name=$Name;Value=$InputObject}
}
#also test children (regardless of whether we've found a match
if (($Depth -gt 0) -and -not ($InputObject.GetType().IsPrimitive -or ($InputObject -is 'System.String'))) {
[string[]]$members = Get-Member -InputObject $InputObject -MemberType $PropertyTypesToSearch | Select-Object -ExpandProperty Name
ForEach ($member in $members) {
$InputObject."$member" | Where-Object {$_ -ne $null} | Find-ValueMatchingCondition -Condition $Condition -Depth ($Depth - 1) -Name $member | ForEach-Object {$_.Name = ('{0}.{1}' -f $Name, $_.Name);$_}
}
}
}
}
}
Get-AdUser $env:username -Properties * `
| Find-ValueMatchingCondition -Condition {$_ -like '*SMTP*'} -Depth 2
Example Results:
Value Name
----- ----
smtp:SomeOne#myCompany.com InputObject.msExchShadowProxyAddresses
SMTP:some.one#myCompany.co.uk InputObject.msExchShadowProxyAddresses
smtp:username#myCompany.com InputObject.msExchShadowProxyAddresses
smtp:some.one#myCompany.mail.onmicrosoft.com InputObject.msExchShadowProxyAddresses
smtp:SomeOne#myCompany.com InputObject.proxyAddresses
SMTP:some.one#myCompany.co.uk InputObject.proxyAddresses
smtp:username#myCompany.com InputObject.proxyAddresses
smtp:some.one#myCompany.mail.onmicrosoft.com InputObject.proxyAddresses
SMTP:some.one#myCompany.mail.onmicrosoft.com InputObject.targetAddress
Explanation
Find-ValueMatchingCondition is a function which takes a given object (InputObject) and tests each of its properties against a given condition, recursively.
The function is divided into two parts. The first part is the testing of the input object itself against the condition:
if ($InputObject | Where-Object -FilterScript $Condition) {
New-Object -TypeName 'PSObject' -Property #{Name=$Name;Value=$InputObject}
}
This says, where the value of $InputObject matches the given $Condition then return a new custom object with two properties; Name and Value. Name is the name of the input object (passed via the function's Name parameter), and Value is, as you'd expect, the object's value. If $InputObject is an array, each of the values in the array is assessed individually. The name of the root object passed in is defaulted as "InputObject"; but you can override this value to whatever you like when calling the function.
The second part of the function is where we handle recursion:
if (($Depth -gt 0) -and -not ($InputObject.GetType().IsPrimitive -or ($InputObject -is 'System.String'))) {
[string[]]$members = Get-Member -InputObject $InputObject -MemberType $PropertyTypesToSearch | Select-Object -ExpandProperty Name
ForEach ($member in $members) {
$InputObject."$member" | Where-Object {$_ -ne $null} | Find-ValueMatchingCondition -Condition $Condition -Depth ($Depth - 1) -Name $member | ForEach-Object {$_.Name = ('{0}.{1}' -f $Name, $_.Name);$_}
}
}
The If statement checks how deep we've gone into the original object (i.e. since each of an objects properties may have properties of their own, to a potentially infinite level (since properties may point back to the parent), it's best to limit how deep we can go. This is essentially the same purpose as the ConvertTo-Json's Depth parameter.
The If statement also checks the object's type. i.e. for most primitive types, that type holds the value, and we're not interested in their properties/methods (primitive types don't have any properties, but do have various methods, which may be scanned depending on $PropertyTypeToSearch). Likewise if we're looking for -Condition {$_ -eq 6} we wouldn't want all strings of length 6; so we don't want to drill down into the string's properties. This filter could likely be improved further to help ignore other types / we could alter the function to provide another optional script block parameter (e.g. $TypeCondition) to allow the caller to refine this to their needs at runtime.
After we've tested whether we want to drill down into this type's members, we then fetch a list of members. Here we can use the $PropertyTypesToSearch parameter to change what we search on. By default we're interested in members of type Property; but we may want to only scan those of type NoteProperty; especially if dealing with custom objects. See https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.psmembertypes?view=powershellsdk-1.1.0 for more info on the various options this provides.
Once we've selected what members/properties of the input object we wish to inspect, we fetch each in turn, ensure they're not null, then recurse (i.e. call Find-ValueMatchingCondition). In this recursion, we decrement $Depth by one (i.e. since we've already gone down 1 level & we stop at level 0), and pass the name of this member to the function's Name parameter.
Finally, for any returned values (i.e. the custom objects created by part 1 of the function, as outlined above), we prepend the $Name of our current InputObject to the name of the returned value, then return this amended object. This ensures that each object returned has a Name representing the full path from the root InputObject down to the member matching the condition, and gives the value which matched.
Note: This answer contains background information and offers a quick-and-dirty approach that requires no custom functionality.
For a more more thorough, systematic approach based on reflection via a custom function, see JohnLBevan's helpful answer.
Select-String operates on strings, and when it coerces an input object of a different type to a string, it essentially calls .ToString() on it, which often yields generic representations such as the mere type name and typically not an enumeration of the properties.
Note that an object's .ToString() representation is not the same as PowerShell's default output to the console, which is much richer.
If all you're looking for is to find a substring in the for-display string representation of an object, you can pipe to Out-String -Stream before piping to Select-String:
$obj | Out-String -Stream | Select-String "SMTP"
Out-String creates a string representation that is the same as what renders to the console by default (it uses PowerShell's output-formatting system); adding -Stream emits that representation line by line, whereas by default a single, multi-line string is emitted.
Note: Recent versions of PowerShell come with convenience function oss, which wraps Out-String -Stream:
$obj | oss | Select-String "SMTP"
Of course, this method will only work if the for-display representation actually shows the data of interest - see caveats below.
That said, searching in the for-display representations is arguably what Select-String should do by default - see GitHub issue #10726
Caveats:
If the formatted representation happens to be tabular and your search string is a property name, the value of interest may be on the next line.
You can address this by forcing a list-style display - where each property occupies a line of its own (both name and value) - as follows:
$obj | Format-List | Out-String -Stream | Select-String "SMTP"
If you anticipate multi-line property values, you can use Select-String's -Context parameter to include lines surrounding a match, such as -Context 0,1 to also output the line after a match.
If you know that the values of interest are in a collection-valued property, you can use $FormatEnumerationLimit = -1 to force listing of all elements (by default, only the first 4 elements are displayed).
Caveat: As of PowerShell Core 6.1.0, $FormatEnumerationLimit is only effective if set in the global scope - see this GitHub issue.
However, once you hit the need to set preference variable $FormatEnumerationLimit, it's time to consider the more thorough solution based on a custom function in John's answer.
Values may get truncated in the representation, because Out-String assumes a fixed line width; you can use -Width to change that, but be careful with large numbers, because tabular representations then use the full width for every output line.

Rename Powershell object property names but keep value

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

How to search an object for a value?

Let's say you have a giant object - one which may or may not have nested arrays / objects,
# Assuming 'user1' exists in the current domain
$obj = Get-ADUser 'user1' -Properties *
and I want to search that object for the string SMTP case-insensitively...
What I tried
$obj | Select-String "SMTP"
But it does not work because the match is inside a nested Collection... to be concise, it sits inside the property $obj.proxyAddresses.
If I run $obj.proxyAddress.GetType() it returns:
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False ADPropertyValueCollection System.Collections.CollectionBase
What's the best way to go about this? I know you could loop through the properties and look for it manually using wildcard matching or .Contains(), but I'd prefer a built in solution.
Thus, it would be a grep for objects and not only strings.
Here's one solution. It can be very slow depending on what depth you search to; but a depth of 1 or 2 works well for your scenario:
function Find-ValueMatchingCondition {
Param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
[PSObject]$InputObject
,
[Parameter(Mandatory = $true)]
[ScriptBlock]$Condition
,
[Parameter()]
[Int]$Depth = 10
,
[Parameter()]
[string]$Name = 'InputObject'
,
[Parameter()]
[System.Management.Automation.PSMemberTypes]$PropertyTypesToSearch = ([System.Management.Automation.PSMemberTypes]::Properties)
)
Process {
if ($InputObject -ne $null) {
if ($InputObject | Where-Object -FilterScript $Condition) {
New-Object -TypeName 'PSObject' -Property #{Name=$Name;Value=$InputObject}
}
#also test children (regardless of whether we've found a match
if (($Depth -gt 0) -and -not ($InputObject.GetType().IsPrimitive -or ($InputObject -is 'System.String'))) {
[string[]]$members = Get-Member -InputObject $InputObject -MemberType $PropertyTypesToSearch | Select-Object -ExpandProperty Name
ForEach ($member in $members) {
$InputObject."$member" | Where-Object {$_ -ne $null} | Find-ValueMatchingCondition -Condition $Condition -Depth ($Depth - 1) -Name $member | ForEach-Object {$_.Name = ('{0}.{1}' -f $Name, $_.Name);$_}
}
}
}
}
}
Get-AdUser $env:username -Properties * `
| Find-ValueMatchingCondition -Condition {$_ -like '*SMTP*'} -Depth 2
Example Results:
Value Name
----- ----
smtp:SomeOne#myCompany.com InputObject.msExchShadowProxyAddresses
SMTP:some.one#myCompany.co.uk InputObject.msExchShadowProxyAddresses
smtp:username#myCompany.com InputObject.msExchShadowProxyAddresses
smtp:some.one#myCompany.mail.onmicrosoft.com InputObject.msExchShadowProxyAddresses
smtp:SomeOne#myCompany.com InputObject.proxyAddresses
SMTP:some.one#myCompany.co.uk InputObject.proxyAddresses
smtp:username#myCompany.com InputObject.proxyAddresses
smtp:some.one#myCompany.mail.onmicrosoft.com InputObject.proxyAddresses
SMTP:some.one#myCompany.mail.onmicrosoft.com InputObject.targetAddress
Explanation
Find-ValueMatchingCondition is a function which takes a given object (InputObject) and tests each of its properties against a given condition, recursively.
The function is divided into two parts. The first part is the testing of the input object itself against the condition:
if ($InputObject | Where-Object -FilterScript $Condition) {
New-Object -TypeName 'PSObject' -Property #{Name=$Name;Value=$InputObject}
}
This says, where the value of $InputObject matches the given $Condition then return a new custom object with two properties; Name and Value. Name is the name of the input object (passed via the function's Name parameter), and Value is, as you'd expect, the object's value. If $InputObject is an array, each of the values in the array is assessed individually. The name of the root object passed in is defaulted as "InputObject"; but you can override this value to whatever you like when calling the function.
The second part of the function is where we handle recursion:
if (($Depth -gt 0) -and -not ($InputObject.GetType().IsPrimitive -or ($InputObject -is 'System.String'))) {
[string[]]$members = Get-Member -InputObject $InputObject -MemberType $PropertyTypesToSearch | Select-Object -ExpandProperty Name
ForEach ($member in $members) {
$InputObject."$member" | Where-Object {$_ -ne $null} | Find-ValueMatchingCondition -Condition $Condition -Depth ($Depth - 1) -Name $member | ForEach-Object {$_.Name = ('{0}.{1}' -f $Name, $_.Name);$_}
}
}
The If statement checks how deep we've gone into the original object (i.e. since each of an objects properties may have properties of their own, to a potentially infinite level (since properties may point back to the parent), it's best to limit how deep we can go. This is essentially the same purpose as the ConvertTo-Json's Depth parameter.
The If statement also checks the object's type. i.e. for most primitive types, that type holds the value, and we're not interested in their properties/methods (primitive types don't have any properties, but do have various methods, which may be scanned depending on $PropertyTypeToSearch). Likewise if we're looking for -Condition {$_ -eq 6} we wouldn't want all strings of length 6; so we don't want to drill down into the string's properties. This filter could likely be improved further to help ignore other types / we could alter the function to provide another optional script block parameter (e.g. $TypeCondition) to allow the caller to refine this to their needs at runtime.
After we've tested whether we want to drill down into this type's members, we then fetch a list of members. Here we can use the $PropertyTypesToSearch parameter to change what we search on. By default we're interested in members of type Property; but we may want to only scan those of type NoteProperty; especially if dealing with custom objects. See https://learn.microsoft.com/en-us/dotnet/api/system.management.automation.psmembertypes?view=powershellsdk-1.1.0 for more info on the various options this provides.
Once we've selected what members/properties of the input object we wish to inspect, we fetch each in turn, ensure they're not null, then recurse (i.e. call Find-ValueMatchingCondition). In this recursion, we decrement $Depth by one (i.e. since we've already gone down 1 level & we stop at level 0), and pass the name of this member to the function's Name parameter.
Finally, for any returned values (i.e. the custom objects created by part 1 of the function, as outlined above), we prepend the $Name of our current InputObject to the name of the returned value, then return this amended object. This ensures that each object returned has a Name representing the full path from the root InputObject down to the member matching the condition, and gives the value which matched.
Note: This answer contains background information and offers a quick-and-dirty approach that requires no custom functionality.
For a more more thorough, systematic approach based on reflection via a custom function, see JohnLBevan's helpful answer.
Select-String operates on strings, and when it coerces an input object of a different type to a string, it essentially calls .ToString() on it, which often yields generic representations such as the mere type name and typically not an enumeration of the properties.
Note that an object's .ToString() representation is not the same as PowerShell's default output to the console, which is much richer.
If all you're looking for is to find a substring in the for-display string representation of an object, you can pipe to Out-String -Stream before piping to Select-String:
$obj | Out-String -Stream | Select-String "SMTP"
Out-String creates a string representation that is the same as what renders to the console by default (it uses PowerShell's output-formatting system); adding -Stream emits that representation line by line, whereas by default a single, multi-line string is emitted.
Note: Recent versions of PowerShell come with convenience function oss, which wraps Out-String -Stream:
$obj | oss | Select-String "SMTP"
Of course, this method will only work if the for-display representation actually shows the data of interest - see caveats below.
That said, searching in the for-display representations is arguably what Select-String should do by default - see GitHub issue #10726
Caveats:
If the formatted representation happens to be tabular and your search string is a property name, the value of interest may be on the next line.
You can address this by forcing a list-style display - where each property occupies a line of its own (both name and value) - as follows:
$obj | Format-List | Out-String -Stream | Select-String "SMTP"
If you anticipate multi-line property values, you can use Select-String's -Context parameter to include lines surrounding a match, such as -Context 0,1 to also output the line after a match.
If you know that the values of interest are in a collection-valued property, you can use $FormatEnumerationLimit = -1 to force listing of all elements (by default, only the first 4 elements are displayed).
Caveat: As of PowerShell Core 6.1.0, $FormatEnumerationLimit is only effective if set in the global scope - see this GitHub issue.
However, once you hit the need to set preference variable $FormatEnumerationLimit, it's time to consider the more thorough solution based on a custom function in John's answer.
Values may get truncated in the representation, because Out-String assumes a fixed line width; you can use -Width to change that, but be careful with large numbers, because tabular representations then use the full width for every output line.

Listing all user properties but excluding whitespace/empty fields in Powershell [duplicate]

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]
}
}

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