With the advent of PowerShell V3 instead of having to write:
Get-Process | Where { $_.ProcessName -match "win" }
...one could now write the more terse:
Get-Process | Where ProcessName -match "win"
... a clear win (ahem) for shell use.
Now let's say I had a simple array of strings, call it $stuff. Is it possible to reduce this:
$stuff | Where { $_ -match "win" }
...in an analogous way to the first example, i.e. removing the script block and referring to the entire object, in this case?
To reduce
$stuff | Where { $_ -match "win" }
you can always do it like this (works for all powershell version):
$stuff = "a","b","win", "winona", "d", "windows"
$stuff -match "win"
win
winona
windows
The form Get-Process | Where ProcessName -match "win" is called comparison statement. It's poorly a documented feature, as Where-Object's documentation doesn't really explain what those are about.
The reason comparison statement works for Get-Process but not for $stuff is that the former returns an array of System.Diagnostics.Process objects whlist the later is String. The comparison statements expects property name for filtering.
Let's look at what's available in each array member. First off processes like so,
$proc = get-process
gm -InputObject $proc[0] -MemberType property
TypeName: System.Diagnostics.Process
Name MemberType Definition
...
ProcessName Property string ProcessName {get;}
...
So, there is a ProcessName property, thus Where-Object can filter with it as seen.
The string array:
$stuff = #("foo", "bar", "zoffo", "qazzer")
gm -InputObject $stuff[0] -MemberType property
TypeName: System.String
Name MemberType Definition
Length Property int Length {get;}
There is only one property in a String which is it's length. Sure enough, it can be used for filtering like so,
$stuff | where length -ne 3
zoffo
qazzer
$stuff | where length -eq 3
foo
bar
As there are no other property typed members, the only way to filter the string array is by the classic script block mode.
Related
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.
I'm confused how the $_ variable works in certain contexts of piping. In this example for backing up a Bitlocker key:
Get-BitlockerVolume | % {$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $_.MountPoint}
This is how I read it in English:
Get all BitLockerVolume objects
For each BitLockerVolume object, pipe the KeyProtector fields forwards
Pipe KeyProtector objects forwards further for those with a RecoverPassword
Run the Backup-BitlockerKeyProtector, and supply the MountPoint
However, MountPoint is a field of the BitLockerVolume object, as shown here:
PS C:\Windows\system32> Get-BitLockerVolume | Get-Member | Where-Object {$_.Name -eq "MountPoint"}
TypeName: Microsoft.BitLocker.Structures.BitLockerVolume
Name MemberType Definition
---- ---------- ----------
MountPoint Property string MountPoint {get;}
So, for the entire block wrapped in brakcets { }, will the $_ variable ALWAYS be the same through any amount of piping? For example, the object we are piping forwards is changing. It's no longer a BitLockerVolume Object, but instead a KeyProtector object. So will the $_ always refer to the BitLockerVolume object in this case, or will it change further down the pipeline depending on different types of objects piped further through the chain?
So $_ is the info from the current pipe.
1,2 | %{
$_
}
response
1
2
while
1,2 | %{
"a","b" | %{
$_
}
}
response
a
b
a
b
We can see in the first that the output from %_ is from the last info given which is 1,2. While the next example still loops 1,2 but the output is from the pipe inside a,b.
There are ways around this by storing the first pipe information into a variable in the second pipe
1,2 | %{
$Num = $_
"a","b" | %{
$Num
}
}
which case the output is
1
1
2
2
In the example you gave lets look at it formated
Get-BitlockerVolume | % {
$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $_.MountPoint
}
You have 2 different pipes. The First is getting 'BitlockerVolumevolume'.
The second starts with you sending the BitlockerVolume's KeyProtector.
Its like saying
For each Bitlocker volume, Get KeyProtector.
For each KeyProtector, Get me ones that have the member RecoveryPassword
Foreach KeyProtector with member RecoveryPassword, Backup Bitlocker Key Protector Using KeyProtector's Mountpoints
So on one final note I would also assume the example you gave wouldnt work.
What you might be looking for is this...
Get-BitlockerVolume | % {
$MountPoint = $_.MountPoint
$_.KeyProtector | ? RecoveryPassword | Backup-BitlockerKeyProtector -MountPoint $MountPoint -KeyProtectorId $_.KeyProtectorId
}
Let's expand the aliases and fill in the implied parameters. $_ can only be used inside script blocks '{ }' that are options to cmdlets. Just because you're in a pipe, doesn't mean you can use $_ . The $_ here belongs to Foreach-Object. Where-Object is using a comparison statement.
Get-BitlockerVolume | Foreach-Object -Process {
$_.KeyProtector | Where-Object -Property RecoveryPassword |
Backup-BitlockerKeyProtector -MountPoint $_.MountPoint
}
I know there are already good answers here, but I feel one important question was not addressed. The question of what happens to $_ throughout the Foreach-Object {} block when there is nesting. I am going use ArcSet's example since that was the answer selected.
1,2 | % {
"$_ before second foreach"
'a','b' | % {
"$_ inside second foreach"
}
"$_ after second foreach"
}
1 before second foreach
a inside second foreach
b inside second foreach
1 after second foreach
2 before second foreach
a inside second foreach
b inside second foreach
2 after second foreach
Notice that $_ becomes the current object being processed by the code within the Foreach-Object {} blocks. When entering the second Foreach-Object block, $_ changes. When exiting the second Foreach-Object block, $_ changes back to the object that will be continued to be processed by the remainder of the first block. So $_ neither remains the same nor is lost during the block processing. You will need to either assign $_ as another variable or in applicable situations use the -PipelineVariable switch to access those objects within different blocks.
Id' like to build on ArcSet's answer just a little bit. Since I finally understood the value of the $PSItem is changing when the type changes down the pipeline, I ran this code to do a little check.
Get-BitLockerVolume | % {$_.GetType()}
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False BitLockerVolume System.Object
True False BitLockerVolume System.Object
True False BitLockerVolume System.Object
Here we can see some objects returned by the pipeline are of the BitLockerVolume type.
Now, based on my original question/example, if we pipe it further based on KeyProtector the object type will change for the $PSItem variable.
Get-BitLockerVolume | % { $_.KeyProtector | ? RecoveryPassword | % {$_.GetType()}}
IsPublic IsSerial Name BaseType
-------- -------- ---- --------
True False BitLockerVolumeKeyProtector System.Object
True False BitLockerVolumeKeyProtector System.Object
So at this point, at the end of the pipeline, we execute some other cmdlet like Backup-BitlockerKeyProtector and reference the $PSItem variable, aka $_, then it will refer to the object types last passed through the pipeline, in this case, the objects would be of the BitLockerVolumeKeyProtector type.
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.
I'm trying to get a feel for the idioms to use in PowerShell.
Given this script:
$path = 'hkcu:\Software\Microsoft\Windows\CurrentVersion\Extensions'
$key = Get-Item $path
$key
I get the output at the bottom of this question.
I'd like to get output of the properties (the name/value pairs under the $key) where I can filter on both name and value.
For instance, filter to list all the Extensions that have:
name like xls*
or value like *\MSACCESS.EXE
Or an exclude filter: exclude all names like doc*
Fir the first filter, I'd want a result like this:
Name Value
---- --------
xlsx C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
xls C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
mdb C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
mda C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
This is the original output of the script:
Hive: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion
Name Property
---- --------
Extensions rtf : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.rtf
dot : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.dot
dotm : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.dotm
dotx : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.dotx
docm : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.docm
docx : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.docx
doc : C:\PROGRA~2\MICROS~1\Office15\WINWORD.EXE ^.doc
xlsx : C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
xls : C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
mdb : C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
mda : C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
Edit
I solved part of the problem: getting a list of Name/Value pairs. It uses PSCustomObject:
$namevalues = $key.GetValueNames() | ForEach-Object { [pscustomobject]#{ Name=$_; Value=$key.GetValue($_) } }
$namevalues
(How should I wrap that code?)
Any help with the filtering would be much appreciated
A two part answer.
We start with the $key from the registry:
$path = 'hkcu:\Software\Microsoft\Windows\CurrentVersion\Extensions'
$key = Get-Item $path
$key
$key | Get-Member
Since $key is a Microsoft.Win32.RegistryKey, you cannot get the name value pairs out directly.
The first step is to create a list of PSCustomObjects with Name/Value pairs. The Name comes from the GetValueNames piped through a ForEach-Object. For each of those Names, we get the Value through GetValue:
$namevalues = $key.GetValueNames() |
ForEach-Object {
[PSCustomObject] #{
Name = $_;
Value = $key.GetValue($_)
}
}
$namevalues | Format-Table
An alternative for the first step is to use the Select-Object using -ExpandProperty as explained by Scott Saad:
$namevalues = $key | Select-Object -ExpandProperty Property |
ForEach-Object {
[PSCustomObject] #{
Name = $_;
Value = $key.GetValue($_)
}
}
$namevalues | Format-Table
The second step is to filter the $namevalues either by Name or by Value.
The Where-Object has some pretty cool Comparison operators that accept regular expressions like match, notMatch, etc.
To make the code more readable, you can wrap lines (thanks Joey!) either use the backtick (`) or take advantage of the places in the PowerShell syntax where it does accept line breaks, like after a pipe (|) or opening brace ({):
$matches = $namevalues |
Where-Object {
$_.Name -match '^xls' `
-or $_.Value -match 'msaccess.exe$'
}
$matches | Format-Table
The result is as wanted:
Name Value
---- -----
xlsx C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
xls C:\PROGRA~2\MICROS~1\Office15\EXCEL.EXE
mdb C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
mda C:\PROGRA~2\MICROS~1\Office15\MSACCESS.EXE
There's a much more clever way to enumerate registry values (found it here). And it's more powershell-way IMO.
I've turned it into a one-liner:
(Get-ItemProperty $path).psobject.properties |
where {$_.name -like "xls*" -or $_.value -like "*\MSACCESS.EXE"} |
select name,value
Update: As noted in comments by #mklement0, you should be careful about properties PSPath, PSParentPath, PSChildName, PSDrive, and PSProvider within the psobject.properties.
A more robust PSv4+ alternative to montonero's helpful answer:[1]
Get-Item $path -PipelineVariable key | ForEach-Object Property | ForEach-Object {
$name = ($_, '')[$_ -eq '(default)'] # translate '(default)' to '' for API calls
if ($name -like 'xls*' -or ($value = $key.GetValue($name)) -like "*\MSACCESS.EXE")
{ [pscustomobject] #{ Name = $name; Value = $value } }
}
-PipelineVariable key stores the [Microsoft.Win32.RegistryKey] instance returned by Get-Item in variable $key for later use in the pipeline.
ForEach-Object Property enumerates the target key's value names (via the .Property note property that PowerShell adds to the output [Microsoft.Win32.RegistryKey] instance).
Inside the Where-Object script block, $_ then refers to the value name at hand, and $key.GetValue(<valueName>) is used to retrieve the associated data.
Important: In the .Property array, PowerShell translates the default value name, which is the empty string ('') at the API level, into name '(default)'; thus, if $_ is '(default)', you must translate it to '' before calling $_.GetValue(<valueName>), which is what ($_, '')[$_ -eq '(default)'] does.
[pscustomobject] #{ ... } then constructs and outputs a [pscustomobject] instance with .Name and .Value properties reflecting the matching value's name and data.
[1] montonero's answer is concise and works well in the case at hand, but it comes with caveats:
PowerShell's registry provider automatically adds the following additional note properties (members of type NoteProperty, as reflected in the output from Get-Member) containing metadata about the targeted registry keys to the [pscustomobject] instance that Get-ItemProperty outputs:
PSPath, PSParentPath, PSChildName, PSDrive, PSProvider
These can interfere with filtering based on .psobject.properties in two ways:
Using wildcard matching against $_.Name can accidentally include these properties.
E.g., $_.Name -like '*drive*' would match the PSDrive property, even though it isn't actually part of the registry key.
Perhaps more hypothetically, if the registry key happens to have values of the same name as these provider properties, the provider properties shadow (override) these values.
E.g., if the key has a PSPath value, $_.Value will report the provider property value instead.
When working in the interactive console if I define a new object and assign some property values to it like this:
$obj = New-Object System.String
$obj | Add-Member NoteProperty SomeProperty "Test"
Then when I type the name of my variable into the interactive window Powershell gives me a summary of the object properties and values:
PS C:\demo> $obj
SomeProperty
------------
Test
I basically want to do just this but from within a function in a script. The function creates an object and sets some property values and I want it to print out a summary of the object values to the Powershell window before returning. I tried using Write-Host within the function:
Write-Host $obj
But this just output the type of the object not the summary:
System.Object
How can I have my function output a summary of the object's property values to the Powershell window?
Try this:
Write-Host ($obj | Format-Table | Out-String)
or
Write-Host ($obj | Format-List | Out-String)
My solution to this problem was to use the $() sub-expression block.
Add-Type -Language CSharp #"
public class Thing{
public string Name;
}
"#;
$x = New-Object Thing
$x.Name = "Bill"
Write-Output "My name is $($x.Name)"
Write-Output "This won't work right: $x.Name"
Gives:
My name is Bill
This won't work right: Thing.Name
To print out object's properties and values in Powershell. Below examples work well for me.
$pool = Get-Item "IIS:\AppPools.NET v4.5"
$pool | Get-Member
TypeName: Microsoft.IIs.PowerShell.Framework.ConfigurationElement#system.applicationHost/applicationPools#add
Name MemberType Definition
---- ---------- ----------
Recycle CodeMethod void Recycle()
Start CodeMethod void Start()
Stop CodeMethod void Stop()
applicationPoolSid CodeProperty Microsoft.IIs.PowerShell.Framework.CodeProperty
state CodeProperty Microsoft.IIs.PowerShell.Framework.CodeProperty
ClearLocalData Method void ClearLocalData()
Copy Method void Copy(Microsoft.IIs.PowerShell.Framework.ConfigurationElement ...
Delete Method void Delete()
...
$pool | Select-Object -Property * # You can omit -Property
name : .NET v4.5
queueLength : 1000
autoStart : True
enable32BitAppOnWin64 : False
managedRuntimeVersion : v4.0
managedRuntimeLoader : webengine4.dll
enableConfigurationOverride : True
managedPipelineMode : Integrated
CLRConfigFile :
passAnonymousToken : True
startMode : OnDemand
state : Started
applicationPoolSid : S-1-5-82-271721585-897601226-2024613209-625570482-296978595
processModel : Microsoft.IIs.PowerShell.Framework.ConfigurationElement
...
Tip #1
Never use Write-Host.
Tip #12
The correct way to output information from a PowerShell cmdlet or function is to create an object that contains your data, and then to write that object to the pipeline by using Write-Output.
-Don Jones: PowerShell Master
Ideally your script would create your objects ($obj = New-Object -TypeName psobject -Property #{'SomeProperty'='Test'}) then just do a Write-Output $objects. You would pipe the output to Format-Table.
PS C:\> Run-MyScript.ps1 | Format-Table
They should really call PowerShell PowerObjectandPipingShell.
Some general notes.
$obj | Select-Object ⊆ $obj | Select-Object -Property *
The latter will show all non-intrinsic, non-compiler-generated properties. The former does not appear to (always) show all Property types (in my tests, it does appear to show the CodeProperty MemberType consistently though -- no guarantees here).
Some switches to be aware of for Get-Member
Get-Member does not get static members by default. You also cannot (directly) get them along with the non-static members. That is, using the switch causes only static members to be returned:
PS Y:\Power> $obj | Get-Member -Static
TypeName: System.IsFire.TurnUpProtocol
Name MemberType Definition
---- ---------- ----------
Equals Method static bool Equals(System.Object objA, System.Object objB)
...
Use the -Force.
The Get-Member command uses the Force parameter to add the intrinsic members and compiler-generated members of the objects to the display. Get-Member gets these members, but it hides them by default.
PS Y:\Power> $obj | Get-Member -Static
TypeName: System.IsFire.TurnUpProtocol
Name MemberType Definition
---- ---------- ----------
...
pstypenames CodeProperty System.Collections.ObjectModel.Collection...
psadapted MemberSet psadapted {AccessRightType, AccessRuleType,...
...
Use ConvertTo-Json for depth and readable "serialization"
I do not necessary recommend saving objects using JSON (use Export-Clixml instead).
However, you can get a more or less readable output from ConvertTo-Json, which also allows you to specify depth.
Note that not specifying Depth implies -Depth 2
PS Y:\Power> ConvertTo-Json $obj -Depth 1
{
"AllowSystemOverload": true,
"AllowLifeToGetInTheWay": false,
"CantAnyMore": true,
"LastResortOnly": true,
...
And if you aren't planning to read it you can -Compress it (i.e. strip whitespace)
PS Y:\Power> ConvertTo-Json $obj -Depth 420 -Compress
Use -InputObject if you can (and are willing)
99.9% of the time when using PowerShell: either the performance won't matter, or you don't care about the performance. However, it should be noted that avoiding the pipe when you don't need it can save some overhead and add some speed (piping, in general, is not super-efficient).
That is, if you all you have is a single $obj handy for printing (and aren't too lazy like me sometimes to type out -InputObject):
# select is aliased (hardcoded) to Select-Object
PS Y:\Power> select -Property * -InputObject $obj
# gm is aliased (hardcoded) to Get-Member
PS Y:\Power> gm -Force -InputObject $obj
Caveat for Get-Member -InputObject:
If $obj is a collection (e.g. System.Object[]), You end up getting information about the collection object itself:
PS Y:\Power> gm -InputObject $obj,$obj2
TypeName: System.Object[]
Name MemberType Definition
---- ---------- ----------
Count AliasProperty Count = Length
...
If you want to Get-Member for each TypeName in the collection (N.B. for each TypeName, not for each object--a collection of N objects with all the same TypeName will only print 1 table for that TypeName, not N tables for each object)......just stick with piping it in directly.
The below worked really good for me. I patched together all the above answers plus read about displaying object properties in the following link and came up with the below
short read about printing objects
add the following text to a file named print_object.ps1:
$date = New-Object System.DateTime
Write-Output $date | Get-Member
Write-Output $date | Select-Object -Property *
open powershell command prompt, go to the directory where that file exists and type the following:
powershell -ExecutionPolicy ByPass -File is_port_in_use.ps1 -Elevated
Just substitute 'System.DateTime' with whatever object you wanted to print. If the object is null, nothing will print out.
# Json to object
$obj = $obj | ConvertFrom-Json
Write-host $obj.PropertyName