As PowerShell has many SQL query-like cmdlets, is there a fast way to check if object is in list of other objects with the Where-Object cmdlet?
Something like in SQL:
SELECT column_name(s)
FROM table_name
WHERE column_name IN (value1,value2,...)
Of course I can write a simple subroutine, but I was just wondering if there is such feature.
You can use the -contains operator:
Get-ColumnNames $table | Where-Object { value1,value2,... -contains $_ }
It's backwards, though with the collection of values on the left side.
In PowerShell 3 you can also use the -in operator:
Where-Object { $_ -in value1,value2,... }
or even
Where-Object -In value1,value2,...
Also, a quirk of how PowerShell works with comparison operators, you can apply them directly to a collection on the left side:
Where-Object { value1,value2,... -eq $_ }
The -eq operator here will either yield the respective element if it is in the list, or $null (which coerces to $false).
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
I have 2 Arrays $UsersGroup and $UsersActive, i need to find where in $UsersActive i have a line with SamAccountName and the ObjectGUID .
$UsersGroup =
SamAccountName ObjectGUID
-------------- ----------
XXXX00XX 0031e949-9120-4df1-bddb-98067a141448
XXXX01XX 0031e949-9120-4df1-bdgb-99067a141448
XXXX02XX 0031e949-9120-4df1-bdab-97067a141448
and without headers
$UsersActive =
fcb483fa146b
fcb515739a2f
fcb82f1ef74c
fcc5ee8b8722
fcd3f1f471c2
fceb26a598a3
fd0b14cecd0e
98067a141448
I need to have the match user from $UsersActive to $UserGroup.Object like that
$UsersGroup | ForEach-Object {if($_.ObjectGUID -contains $UsersActive) {$_}}
But i don't get the result like that :
XXXX00XX 0031e949-9120-4df1-bddb-98067a141448
Can some one help me , thanks !
-contains is a collection containment operator, testing for the exact occurrence of the right-hand side argument in the left-hand side argument.
To test for the presence of a substring in a string, use the -like wildcard string comparison operator:
$UsersGroup | Where-Object {
$guid = $_.ObjectGUID
$UsersActive.Where({$guid -like "*$_*"}, 'First')
}
Each group entry will now be tested against every $UsersActive value until a match is found (causing Where-Object to pass the object through) or no match is found (causing Where-Object to filter out the object)
If I understand you correctly, using compare-object... This has only one match. Compare-object can be slow for large lists.
$usersgroup =
'SamAccountName,ObjectGUID
XXXX00XX,0031e949-9120-4df1-bddb-98067a141448
XXXX01XX,0031e949-9120-4df1-bdgb-99067a141448
XXXX02XX,0031e949-9120-4df1-bdab-97067a141448' | convertfrom-csv
$usersactive = -split
'fcb483fa146b
fcb515739a2f
fcb82f1ef74c
fcc5ee8b8722
fcd3f1f471c2
fceb26a598a3
fd0b14cecd0e
98067a141448'
compare ($usersgroup.objectguid -replace '.*-') $usersactive -IncludeEqual |
? sideindicator -eq '==' # order doesn't matter
InputObject SideIndicator
----------- -------------
98067a141448 ==
To offer an alternative solution:
As Mathias states in his answer, the -contains operator and its operands-reversed counterpart, -in, only perform equality comparison of a single comparison value against the elements of a collection (array).
As an aside: Given that $_.ObjectGUID is the scalar (single object) and $UsersActive the collection (array) to search in, you would have needed to use -in, not -contains ($_.ObjectGUID -in $UsersActive)
Unfortunately, as of version 7.3.2, PowerShell's pattern-matching operators, -like (with wildcard expressions) and -match (with regexes), do not support matching against multiple (an array of) patterns.
However, since you're looking to match a literal substring of each .ObjectGUID value, you can use -in if you extract that relevant substring first and use it as the comparison value:
# -> Object whose GUID is '0031e949-9120-4df1-bddb-98067a141448'
$usersGroup | Where-Object { ($_.ObjectGUID -split '-')[-1] -in $usersActive }
Note how the -split operator is used to split the GUID into tokens by -, with [-1] returning the last token, which -in then looks for by equality comparison in the $UsersActive array.
As an aside:
Allowing multiple patterns as the RHS of -like and -match - so as to return $true if any of them match - would be a helpful future improvement.
GitHub issue #2132 asks for just that.
Trying to figure out a way to find all the mailboxes that are forwarded to a Distribution List.
No luck with this, any one has better idea?
$group=Get-DistributionGroup | select PrimarySmtpAddress
Get-Mailbox | Where-Object { $_.ForwardingAddress -eq "$group" } | Select-Object Name,ForwardingAddress
Untested (PSv2+; PSv3+ would allow for simpler syntax):
$groupEmailAddresses = Get-DistributionGroup | foreach { $_.PrimarySmtpAddress.ToString() }
Get-Mailbox | where { $_.ForwardingSmtpAddress -and
$groupEmailAddresses -contains $_.ForwardingSmtpAddress.ToString() } |
Select-Object Name, ForwardingSmtpAddress
Note that while many PowerShell operators can operate on arrays (collections),
the array must be on the LHS (left-hand side)
and the RHS (right-hand side) must be a scalar (a non-collection value)
In the case at hand, -contains tells us whether the collection on the LHS has the single RHS value among its elements.
I'm trying to go through the Active Directory, and grab users who meet a certain criteria. I want to get users have either Manager A or Manager B, but I'm not sure how to implement the or statement. Here's my code:
Get-ADUser -Filter * -Properties country, extensionattribute9 | if (extensionattribute9 -eq 'Smith, Joe') or (extensionattribute9 -eq 'Doe, John') {select extensionsattribute9, country}
In this code, it doesn't recognize extensionattribute9, which gives you the user's manager.
I also tried attempted using where instead of if, but to no avail.
The operator is -or, not or. See about_Logical_Operators. Also, if statements don't read from the pipeline. Either put the if statement in a ForEach-Object loop:
... | ForEach-Object {
if ($_.extensionattribute9 -eq 'Smith, Joe' -or $_.extensionattribute9 -eq 'Doe, John') {
$_ | select extensionsattribute9, country
}
}
or use a Where-Object statement instead:
... | Where-Object {
$_.extensionattribute9 -eq 'Smith, Joe' -or
$_.extensionattribute9 -eq 'Doe, John'
} | Select-Object extensionsattribute9, country
And you can't use property names by themselves. Use the current object variable ($_) to access properties of the current object.
For checking if an attribute has one of a given number of values you can also use the -contains operator instead of doing multiple comparisons:
'Smith, Joe', 'Doe, John' -contains $_.extensionattribute9
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' }