Passing a parameter to a function - powershell

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

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

ForEach-Object OutString, Powershell Awk Equivalents

I'm a long time Bash enthusiast trying to get my bearings with Powershell. I'm trying to do something that could easily be accomplished with Awk, but I can't seem to find a solution in Powershell documentation. I'm essentially trying to select the third value from the output of the command delimited by /
Get-ADOrganizationalUnit -Properties CanonicalName -Filter * | Select-Object -ExpandProperty CanonicalName | Select-String ".*/Example/.*"
example.local/Example/ExampleOU1
example.local/Example/ExampleOU2
I just want to select the last value shown here. In Bash land this could easily be accomplished by an awk -F "/" '{print $3}' however I'm struggling to find the equivalent in Powershell. I found Out-String | %{ $_.Split('/')[2]; }' which is nice, but only works if there's one object. I'm assuming I need to ForEach-Object, then convert to a string, then split, but I'm not sure how.
I almost never use out-string. Canonicalname is a property of the object, so you need to reference that property. This would work:
[pscustomobject]#{canonicalname = 'example.local/Example/ExampleOU1'} |
% { $_.canonicalname.Split('/')[2] }
ExampleOU1
There's also -split which uses regex:
[pscustomobject]#{canonicalname = 'example.local/Example/ExampleOU1'} |
% { ($_.canonicalname -split '/')[2] }
Paths come up so often there's a command for it:
[pscustomobject]#{canonicalname = 'example.local/Example/ExampleOU1'} |
% { split-path -leaf $_.canonicalname }

Filter Records on Multiple Values

I have to get a list of AD users where the Manager field contains one of the following names on my FilterValues list.
I see that someone recommended to use the Where-Object, however when I run the script nothing gets returned. I have tried many approaches but I can't seem to find where I am going wrong. Any advise will be greatly appreciated, TIA.
$FilterValues = $('Williams', 'Smith', 'Johnson')
Get-ADUser -Filter "extensionAttribute10 -like '1.0'" -Properties *
| Where-Object {$_.Manager -contains $FilterValues}
| Format-Table Name, Manager
*** If I run the script without the Where-Object portion I get values back.
The -contains is for testing values in an array, unlike the string .Contains() method. In this case, I'd suggest using the regex -match operator like this:
# an array of manager names
$FilterValues = 'Williams', 'Smith', 'Johnson'
# create a regular expression of the manager names by combining them with the regex OR symbol '|'
# the [regex]::Escape() is there to make sure characters that have special meaning in regex are escaped.
$regexManagers = ($FilterValues | ForEach-Object { [regex]::Escape($_) }) -join '|'
Get-ADUser -Filter "extensionAttribute10 -like '1.0'" -Properties Name, Manager |
Where-Object {$_.Manager -match $regexManagers } |
Format-Table
P.S. It is always a bad idea to search for ALL properties with -Properties * when you only want two properties returned.

Is there a PowerShell 3 simplified syntax for $_ comparisons?

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

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