Getting a better understanding of the dot notation in Powershell - powershell

Why can I do this:
Example 1
Get-NetIPConfiguration -Detailed |
? IPv4DefaultGateWay -NE $null
and
Example 2
Get-NetIPConfiguration -Detailed |
? {$_.IPv4DefaultGateWay -NE $null -and $_.NetAdapter.Status -EQ "Up"}
but not this
Example 3
Get-NetIPConfiguration -Detailed |
? IPv4DefaultGateWay -NE $null -and NetAdapter.Status -EQ "Up"
The error message I get is as follows:
Where-Object : Parameter set cannot be resolved using the specified named parameters.
At line:2 char:3
+ ? IPv4DefaultGateWay -NE $null -and NetAdapter.Status -EQ "Up"
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidArgument: (:) [Where-Object], ParameterBindingException
+ FullyQualifiedErrorId : AmbiguousParameterSet,Microsoft.PowerShell.Commands.WhereObjectCommand
but obviously the Where-Object cmdlet is capable of handling compound conditions with the use of -and and -or, so the error message is confusing.
ALSO, Why can I do this
Example 4
Get-NetIPConfiguration -Detailed |
? {$_.IPv4DefaultGateWay -NE $null -and $_.NetAdapter.Status -EQ "Up"} |
% {
New-Object -TypeName PSObject -Property #{
ComputerName = $_.ComputerName
IPAddress = $_.IPv4Address.IPv4Address
}
}
but not be able to do this
Example 5
Get-NetIPConfiguration -Detailed |
? {$_.IPv4DefaultGateWay -NE $null -and $_.NetAdapter.Status -EQ "Up"} |
Select-Object ComputerName, IPv4Address.IPv4Address
My objective is being met by using Example 4, but that's not what I am really asking here - It is the convention of addressing properties and properties of properties that I am more concerned about here!

The differences ultimately stem from PowerShell's two fundamental parsing modes, explained in this answer; in short:
argument mode is shell-like, for invoking commands with whitespace-separated arguments, with support for unquoted strings.
expression mode works like a traditional programming language, with quoted strings, operators, loop constructs, ...
In both your examples, commands are invoked (more specifically, cmdlets, namely Where-Object (whose built-in aliases are ? and where) and Select-Object (whose built-in alias is select), so what is being passed to them is parsed in argument mode:
Where-Object IPv4DefaultGateWay -NE $null uses argument mode for simplified syntax, i.e. a lower-ceremony argument-mode alternative to an expression-mode script block ({ ... }), also available with the ForEach-Object cmdlet.
The syntax is simpler:
No need for enclosure in { ... }
No need to refer to the input object at hand via the automatic $_ variable, just using the property name by itself is enough.
But it is limited:
You can only perform a single operation, on a single property.[1]
That property must be an immediate property (e.g., IPv4DefaultGateWay, not a nested one (e.g. NetAdapter.Status) - more on that below.
Select-Object too has the constraint that a given property name must be an immediate property.
The only way to work around that is via a calculated property, implemented via a hashtable whose Expression entry contains a script block that calculates the property value for each input object - see this answer.
Why nested property access (e.g., NetAdapter.Status) isn't supported in argument-parsing mode:
An argument such as NetAdapter.Status - whether quoted or not - is passed as a string to commands, and Where-Object and Select-Object interpret such a string passed to their -Property parameter as the verbatim name of a property, not as a nested property-access expression.
That is, the equivalent of the following command, where NetAdapter.Status is parsed in argument mode:
Get-NetIPConfiguration -Detailed |
Select-Object -ExpandProperty NetAdapter.Status
is the following expression-mode property access:
Get-NetIPConfiguration -Detailed |
ForEach-Object { $_.'NetAdapter.Status' }
Note the '...' around NetAdapter.Status, showing that this name was used verbatim as the single and immediate property name on the input object (which doesn't work as intended).
Design rationale:
The challenge is that in argument mode no distinction is made between NetAdapter.Status and 'NetAdapter.Status' and "NetAdapter.Status" - the target command sees verbatim NetAdapter.Status in all cases, so - unlike in expression mode - the original quoting / non-quoting cannot serve to distinguish these argument forms.
However, arguably it is much more useful for cmdlets that specifically accept property names (parameter -Property) to interpret arguments such as NetAdapter.Status as nested property accessor, given that property names with embedded . chars. are rare.
Changing this behavior would be a breaking change, however, given that the following currently works, but wouldn't any longer:
PS> '{ "foo.bar": 42 }' | ConvertFrom-Json | Select-Object foo.bar
foo.bar
-------
42
[1] The two parsing modes are so different that it would be impossible to recreate all expression-mode features in argument mode; you couldn't model the complexities of expression mode with command arguments (parameter definitions). Simplified syntax is a compromise aimed at making simple, but common use cases syntactically easier.

Related

Question about pipeline variables and pipeline shortcut(s)?

In the creation of a script I'm writing, I ran across the use of what appears to be a pipeline shortcut that I have never seen before.
For example:
$acl | select -expandproperty Access | ? {$_.IdentityReference -eq "AD\$Group"}
can be shortened to
$acl.Access.Where({$_.IdentityReference -eq "AD\$Group"})
What is this? $acl.Access makes sense to me as it's just a property reference but I do not see any such .Where() method being available on this object. As such it seems .Where({}) is some sort of pipeline shortcut for " | Where-Object {}". Where can I find documentation for such a thing? Do others like this exist?
Second question. I have a need to add a calculated property to my results, and within the property expression I have to perform piping as well, so where I would typically just reference $_ from the parent pipeline, this is lost within the expression statement.
I can get around this by using -PipelineVariable up within the pipeline, but it seems this only works when used with cmdlets and is not available when starting a pipeline with a variable (which I do often). Is there a way around this?
This works:
$acl = Get-Acl -Path "AD:\$dn"
$acl | select -expandproperty access -PipelineVariable AccessRight | ? {$_.IdentityReference -eq "AD\$Group"} | select *, #{N='ObjectTypeName';E={($ADGuidMap.GetEnumerator() | ? {$_.Value -eq $AccessRight.ObjectType}).Name}}
I'd really like to be able to do this with the shortcut as anywhere I can shorten my oneliner I would like to do. Unfortunately the following will not work as I cannot use pipelinevariable at this location.
$acl.Access.Where({$_.IdentityReference -eq "AD\$Group"}) -PipeLineVariable AccessRight | select *, #{N='ObjectTypeName';E={($ADGuidMap.GetEnumerator() | ? {$_.Value -eq $AccessRight.ObjectType}).Name}}
It's always bugged me about not being able to use Pipeline variables outside of cmdlets so I finally figured I'd ask if there was some sort of way around that.
As for the first question:
The .Where() and .ForEach() methods are intrinsic members, i.e. members that PowerShell implicitly makes available on objects of any type.
While they function similarly to their cmdlet counterparts, Where-Object and ForEach-Object, there are important differences.
See this answer for more information.
As an aside: You could have simplified your command even when using the Where-Object cmdlet, namely with simplified syntax:
$acl.Access | Where-Object IdentityReference -eq "AD\$Group"
As for the second question:
Because the .Where() method isn't a cmdlet, you cannot use the common -PipelineVariable parameter with it.
Given that .Access typically returns multiple objects, using the pipeline with -PipelineVariable enables an elegant solution.
If you do want to avoid the pipeline (operator), you can combine the .Where() and .ForEach() methods as follows, with the help of an intermediate (regular) variable:
$acl.Access.
Where({ $_.IdentityReference -eq "AD\$Group" }).
ForEach({
$aclEntry = $_
Select-Object -InputObject $aclEntry -Property *,
#{
N = 'ObjectTypeName'
E = { ($ADGuidMap.GetEnumerator().Where({ $_.Value -eq $aclEntry.ObjectType })).Name }
}
})
Update:
As you've discovered yourself, if you stick with a pipeline, you can combine -PipeLineVariable with Write-Output in order to capture each element of an array / collection in a pipeline variable as it flows through the pipeline, for use a later script block, as the following (contrived) example shows:
$array = 0..3
Write-Output $array -Pipeline arrayElement |
Where-Object { 0 -eq $_ % 2 } | # filter out odd numbers
ForEach-Object { $_ + 1 } | # add 1
ForEach-Object { "pipeline variable / `$_: $arrayElement / $_" }
Output, which shows that each element of input array $array was individually captured in pipeline variable $arrayElement:
pipeline variable / $_: 0 / 1
pipeline variable / $_: 2 / 3

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.

Get AD user properties from Active Directory

I am trying to filter the values of a property in Active Directory.
I tried:
Get-ADUser -filter * -Properties physicalDeliveryOfficeName | Where-Object (($_.physicalDeliveryOfficeName -like "NICE")) | Select-Object physicalDeliveryOfficeName, name
Get-ADUser -filter * -Properties physicalDeliveryOfficeName | Select-Object physicalDeliveryOfficeName, name | Where-Object (($_.physicalDeliveryOfficeName -like "NICE"))
I did not get any errors, but no results either.
I searched all users with physicaldeliverofficename is (myvalue). I would like to display name and office.
You have a syntax problem:
The Where-Object's (positionally implied) -FilterScript parameter expects a script block argument - { ... } - not a parenthesized expression ((...)).[1]
Therefore:
# Note the { ... } around the expression passed to Where-Object
Get-ADUser -Filter * -Properties physicalDeliveryOfficeName |
Where-Object { $_.physicalDeliveryOfficeName -eq "NICE" } # | ...
Note: Since "NICE" is a literal string rather than a wildcard pattern, I've used the -eq instead of the -like operator. If you doe need to find "NICE" as a substring, use something like -like "*NICE*" or, for case-sensitive matching, -clike "*NICE*", as Mathias R. Jessen suggests.
Note that you may alternatively use simplified syntax, which obviates the need for a script block and allows use of individual parameters (also note the absence of $_., which is implied):
Get-ADUser -Filter * -Properties physicalDeliveryOfficeName |
Where-Object physicalDeliveryOfficeName -eq "NICE" # | ...
Taking a step back:
Santiago Squarzon suggests performing the filtering at the source, by using Get-ADUser's -Filter or -LDAPFilter parameter, which is much more efficient; e.g.:
Get-ADUser -Filter 'physicalDeliveryOfficeName -eq "NICE"'
As an aside: There are many examples out there that use script-block syntax with -Filter (-Filter { ... }), but the -Filter parameter accepts a string and that string, even though it supports PowerShell-like syntax, is interpreted by the AD provider, so it's better to pass a string to begin with - see this answer for more information.
[1] If you use (...), the expression's value gets bound to the -Property parameter instead, and is therefore interpreted as a property name whose value - assuming such a property even exists - is interpreted as a Boolean that determines whether the input object at hand should be filtered in or not. If the expression doesn't evaluate to the name of a property that exists on an input object, $false is implied, and the input object is filtered out. In your case, this predictably resulted in no objects being filtered in and therefore no output.
The Select-Object cmdlet is used to select only the columns you want from a larger object or list.
For instance:
C:\git\Core> gsv Spooler | fl
Name : Spooler
DisplayName : Print Spooler
Status : Running
DependentServices : {Fax}
ServicesDependedOn : {RPCSS, http}
CanPauseAndContinue : False
CanShutdown : False
CanStop : True
ServiceType : Win32OwnProcess, InteractiveProcess
Get-Service returns Service Objects which have a lot of properties. If I only want certain ones, I'd use it like so:
C:\git\Core> gsv Spooler | Select Name,Status
Name : Spooler
Status : Running
You're using the cmdlet and probably discarding the columns which have the values you need. Run your one-liner again and remove the Select-Object cmdlet to see all of the columns availble, till you find the one that pertains to the Office.

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.

Powershell Match a string that may contain asterix

I'm running into issues with a string that gets parsed a certificate friendly name and attemps a lookup to see if it already exists. When the friendly name contains a '*' the script bombs.
I know I could escape the '*', but I don't have control of when it's parsed to the script. What is the best way of either escaping on the fly or matching strings when/if they contain metacharacters?
# this does not work
# Bad argument to operator '-match': parsing "*.test.com" - Quantifier {x,y} following nothing..
if(Get-ChildItem cert:\ -Recurse | Where-Object {$_.FriendlyName -and $_.FriendlyName -match '*.test.com'} | Select-Object -First 1) {
exit 1
} else {
exit 0
}
# this works fine
if(Get-ChildItem cert:\ -Recurse | Where-Object {$_.FriendlyName -and $_.FriendlyName -match 'string.test.com'} | Select-Object -First 1) {
exit 1
} else {
exit 0
}
In your specific case, I think you want to test for an exact match. In that case you should use the -eq operator instead of -match.
mjolinor's answer provides a good general explanation on whild cards.
'*.test.com' is a wildcard pattern. Use that with the -like operator. The -match operator uses a regular expression for matching, which has different rules and metacharacters than the wildcard match.
See:
Get-Help about_Wildcards
Get-Help about_Regular_Expressions