Here's another concept I know I've written about in the past but that needed an update. A common technique I use when exploring and discovering objects is to pipe the object to Select-Object specifying all properties, get-service spooler | select *. There's nothing wrong with this approach but depending on the object I might get a lot of empty values. This is especially true with WMI objects or items from Active Directory like a user account. The other issue I have is that when using this technique with a WMI object, I also get the system properties like __PATH, which I'd often like to ignore. The solution I came up is a function called Select-PropertyValue. Pipe objects to the function and it will write a custom object to the pipeline only with properties that have a value.
ManageEngine ADManager Plus - Download Free Trial
Exclusive offer on ADManager Plus for US and UK regions. Claim now!
[cc lang="PowerShell"]
Function Select-PropertyValue {
[cmdletbinding()]
Param(
[Parameter(Position=0,ValueFromPipeline=$True)]
[object]$InputObject,
[Switch]$NoSystem,
[Switch]$Inquire = $False
)
Begin {
Write-Verbose "Starting $($myinvocation.mycommand)"
#define a variable we will be using to keep StrictMode happy.
$properties=@()
} #begin
Process {
<# set debug preference to continue when using -Debug unless user has also used -Inquire. Otherwise, PowerShell will prompt for each command. Due to scoping issues I found the best solution was to set the debug preference for each object in the pipeline #>
if (($PSCmdlet.MyInvocation.BoundParameters.ContainsKey("debug")) -AND (-NOT $Inquire)) {
$DebugPreference="Continue"
}
Write-Debug "In process"
Write-Debug "Checking for `$properties"
<#
because this only takes pipelined objects, we only need to get the properties from
Get-Member once. When the second object is processed, this IF block will get skipped.
The assumption that all pipelined objects are of the same type.
#>
if (-Not $properties) {
Write-Verbose "Creating property list"
Write-Debug "Creating property list for pipelined object"
#get properties for the pipelined object sorted by property name
$properties=$_ | Get-Member -membertype Properties | sort Name
$typename=($_ |Get-Member)[0].TypeName
Write-Debug "Typename =$typename"
#filter out WMI System properties if -NoSystem
if ($NoSystem) {
Write-Verbose "Filtering out WMI System properties"
Write-Debug "Filtering out WMI System properties."
$properties=$properties | where {$_.name -notlike "__*"}
}
Write-Verbose "Found $($properties.count) properties"
Write-Debug "Found $($properties.count) properties"
}
#create an empty custom object
Write-Debug "Creating empty object"
$obj=New-Object PSObject
#enumerate the list of properties
Write-Verbose "Checking properties for values"
foreach ($property in $properties) {
Write-Debug "Checking $($property.name)"
#if object has a value for the current property
if ($_.($property.name)) {
Write-Debug "found value for: $($_.($property.name))"
#assign properties to the custom object
Write-Debug "Adding property and value to custom object"
$obj | Add-Member -MemberType Noteproperty -name $property.Name -value ($_.($property.name))
} #end If
} #end ForEach
#Add the typename to the object
Write-Debug "Adding typename to custom object"
$obj | Add-Member -MemberType Noteproperty -Name "Type" -Value $typename
#write the custom object to the pipeline
Write-Debug "Writing custom object to the pipeline"
Write $obj
} #end process
End {
Write-Verbose "Ending $($myinvocation.mycommand)"
}
} #end function
[/cc]
The function is intended to be used in a pipelined expression and assumes that all the objects are of the same type. The essence of the function is to run each object through Get-Member and keep a list of property names.
[cc lang="PowerShell"]
if (-Not $properties) {
Write-Verbose "Creating property list"
Write-Debug "Creating property list for pipelined object"
$properties=$_ | Get-Member -membertype Properties | sort Name
[/cc]
Because this happens in the process script block, which runs once for every piped in object, this line only runs if $properties doesn't already exist. If the user includes the -NoSystem parameter, then any property that starts with __ is removed.
[cc lang="PowerShell"]
if ($NoSystem) {
Write-Verbose "Filtering out WMI System properties"
Write-Debug "Filtering out WMI System properties."
$properties=$properties | where {$_.name -notlike "__*"}
}
[/cc]
Armed with the array of property names, the pipelined object is checked for each property name.
[cc lang="PowerShell"]
foreach ($property in $properties) {
Write-Debug "Checking $($property.name)"
#if object has a value for the current property
if ($_.($property.name)) {
Write-Debug "found value for: $($_.($property.name))"
[/cc]
If a value is found
[cc lang="PowerShell"]
if ($_.($property.name))
[/cc]
Then the property and value are added to the blank custom object which is created for each piped object.
[cc lang="PowerShell"]
$obj | Add-Member -MemberType Noteproperty -name $property.Name -value ($_.($property.name))
[/cc]
I include the object type name and write the custom object to the pipeline.
[cc lang="PowerShell"]
#Add the typename to the object
Write-Debug "Adding typename to custom object"
$obj | Add-Member -MemberType Noteproperty -Name "Type" -Value $typename
#write the custom object to the pipeline
Write-Debug "Writing custom object to the pipeline"
Write $obj
[/cc]
Here's the end result.
[cc lang="DOS"]
BiosCharacteristics : {4, 7, 8, 9...}
BIOSVersion : {TOSQCI - 6040000, Ver 1.00PARTTBL}
Caption : Ver 1.00PARTTBL
Description : Ver 1.00PARTTBL
Manufacturer : TOSHIBA
Name : Ver 1.00PARTTBL
PrimaryBIOS : True
ReleaseDate : 20101210000000.000000+000
SerialNumber : Z9131790W
SMBIOSBIOSVersion : V2.90
SMBIOSMajorVersion : 2
SMBIOSMinorVersion : 6
SMBIOSPresent : True
SoftwareElementID : Ver 1.00PARTTBL
SoftwareElementState : 3
Status : OK
Version : TOSQCI - 6040000
Type : System.Management.ManagementObject#root\cimv2\Win32_BIOS
[/cc]
You probably also noticed the Write-Verbose and Write-Debug lines in the script. Continuing the discussion I started in my post on Write-Verbose vs Write-Debug, I'm including the suggestion on controlling the debug preference. By default if you the function with -debug you'll get all the debug messages. But if you also use -Inquire, then you'll get prompted for each command which is the normal process when using -Debug. I wanted to see how this would work in one of my own scripts.
As always, I hope you'll let me know how this works out for you. Download Select-PropertyValue. The full script includes comment-based help as well as an optional alias. Uncomment the last line of the script if you want to use it or define your own.