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
Related
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
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.
I previously asked for assistance parsing a text file and have been using this code for my script:
import-csv $File -header Tag,Date,Value|
Where {$_.Tag -notmatch '(_His_|_Manual$)'}|
Select-Object *,#{Name='Building';Expression={"{0} {1}" -f $($_.Tag -split '_')[1..2]}}|
Format-table -Groupby Building -Property Tag,Date,Value
I've realized since then that, while the code filters out any tags containing _His or _Manual, I need to also filter any tags associated with _Manual. For example, the following tags are present in my text file:
L01_B111_BuildingName1_MainElectric_111A01ME_ALC,13-Apr-17 08:45,64075
L01_B111_BuildingName1_MainElectric_111A01ME_Cleansed,13-Apr-17 08:45,64075
L01_B111_BuildingName1_MainElectric_111A01ME_Consumption,13-Apr-17 08:45,10.4
L01_B333_BuildingName3_MainWater_333E02MW_Manual,1-Dec-16 18:00:00,4.380384E+07
L01_B333_BuildingName3_MainWater_333E02MW_Cleansed,1-Dec-16 18:00:00,4.380384E+07
L01_B333_BuildingName3_MainWater_333E02MW_Consumption,1-Dec-16 18:00:00,25.36
The 333E02MW_Manual string would be excluded using my current code, but how could I also exclude 333E02MW_Cleansed and 333E02MW_Consumption? I feel I would need something that will allow me to extract the 8-digit code before each _Manual instance and then use it to find any other strings with a {MatchingCode}
xxx_xxxx_xxxxxxxxxxx_xxxxxxxxxx_MatchingCode_Cleansed
xxx_xxxx_xxxxxxxxxxx_xxxxxxxxxx_MatchingCode_Consumption
I know there are the -like -contains and -match operators and I've seen these posts on using substrings and regex, but how could I extract the MatchingCode to actually have something to match to? This post seems to come closest to my goal, but I'm not sure how to apply it to PowerShell.
You can find every tag that ends with _Manual and create a regex pattern that matches any of the parts before _Manual. Ex.
$Data = Import-Csv -Path $File -Header Tag,Date,Value
#Create regex that matches any prefixes that has a manual row (matches using the value before _Manual)
$ExcludeManualPattern = ($Data | Foreach-Object { if($_.Tag -match '^(.*?)_Manual$') { [regex]::Escape($Matches[1]) } }) -join '|'
$Data | Where-Object { $_.Tag -notmatch '_His_' -and $_.Tag -notmatch $ExcludeManualPattern } |
Select-Object -Property *,#{Name='Building';Expression={"{0} {1}" -f $($_.Tag -split '_')[1..2]}}|
Format-table -GroupBy Building -Property Tag,Date,Value
PowerShell 3 has a simplified syntax:
$people | ? { $_.Name -eq 'Jane' } can be written as $people | ? Name -eq 'Jane'
However, is there a simplified syntax for $_ itself?
E.g $names | ? { $_ -eq 'Jane' } can't be written as $names | ? -eq 'Jane'.
Is there some other way to write it, or is it not supported?
Not that having {} matters much, but I want to understand the full picture.
The simplified syntax in powershell 3.0 is based on parameters in Where-Object cmdlets: -EQ, -LT, -GT, etc. (named exacly like comparision operators), so it is not a "magic" but wisely chosen parameter names that mimics PowerShell's comparison operators.
Unfortunately it is not possible to reference the object itself, you have to use the old syntax (like you show in your question):
$names | Where { $_ -eq 'Jane' }
I'm trying to create a simple function based on the following by passing an argument to it. The function will search my command history looking for a string - the command works:
history | Where-Object {$_.CommandLine -match 'abc'}
From my research the closest thing to this would be:
Function FindHistory {history | Where-Object {$_.CommandLine -match '$args'}}
However I am unable to get this (or any variation) to work.
FindHistory abc - should return all previous commands used with 'abc' in them.
What am I doing wrong?
btw, I've been an avid powershell user for all of 2 days - liking it :)
Powershell won't expand variables in single quoted strings, so you have to use a double quoted string:
Function FindHistory {history | Where-Object {$_.CommandLine -match "$args"}}
Though $args is an array of all arguments, so it may be more robust if you just specify a parameter:
Function FindHistory {PARAM($searchTerm) history | Where-Object {$_.CommandLine -match "$searchTerm"}}
Using $args in a where-object clause is problematic.
http://social.technet.microsoft.com/wiki/contents/articles/7821.powershell-using-args-in-script-blocks.aspx
Try this:
function findhistory ($search) {history | where-object {$_.CommandLine -match $search}}