I'm struggling with a PowerShell Where clause with an array that contains wildcards. Here's the command in my much larger code,
$excludedOUs = @("*OU=Loaners,*", "*OU=InStock,*", "*OU=Conference Room,*","*OU=Training,*")
Get-ADComputer -SearchBase $targetOU -Property LastLogonDate,LastLogon |
Where-Object { $_.DistinguishedName -notin $excludedOUs } | Select-Object Name,DistinguishedName, @{l='LastLogonDate';e={[DateTime]::FromFileTime($_.LastLogon)}}
I want to return a list of machines from the $targetOU but I want to exclude a list of sub-OUs from the $targetOU. I've seen multiple posts that explain why this is failing (wildcards not allowed in "notin", need to use -like and -notlike with wilcards, etc.), and I've also seen the suggestions of using Regex to solve the problem. Unfortunately, my eyes always glaze over at the first sight of regular expressions and I can never figure out how to use them correctly. Ultimately, I'd like to not have to use the wildcards in the array at all. If the $excludedOUs array was,
$excludedOUs = @("Loaners", "InStock", "Conference Room", "Training")
I would prefer it. But, I need to use the wildcards because the sub-OU is only a part of the DistinguishedName property. Additionally, the array may not always be limited to just these four items. It might be a single item, or none, or dozens. I won't know how others in the organization will use this. Any suggestions are appreciated.
As you note, -in
and -contains
and their negated variants only perform whole-value, literal equality comparisons.
Unfortunately, neither -like
, the wildcard matching operator, nor -match
, the regular-expression matching operator, support an array of patterns to match against, as of PowerShell (Core) 7.4:
The workaround is to construct a single regex that uses alternation (|
) that you can use with -match
/ -notmatch
, which is easy to do programmatically:
Construct your array with literal substrings, i.e. without *
characters, given that -match
/ -notmatch
performs substring matching by default.
^
) and/or end ($
) of the input strings.[1]Join the array elements with |
via -join
to form a single string that represents a regex with alternation, which means that any one of the substrings matches.
.
or +
, you must additionally escape them, using the [regex]::Escape()
.NET method - see the relevant comment in the source code below.\
, which is only an option in array literals.Use that string with -notmatch
# Define the exclusions as literal substrings, without "*"
$excludedOUs =
@("OU=Loaners,", "OU=InStock,", "OU=Conference Room,","OU=Training,")
# Join them with "|", to construct a single regex that matches
# any one of the substrings.
# In cases where the array elements contain regex metacharacters (see above),
# use the following instead:
# $regexExcludedOUs = $excludedOUs.ForEach({ [regex]::Escape($_) }) -join '|'
$regexExcludedOUs = $excludedOUs -join '|'
# Use the regex with -notmatch
Get-ADComputer -SearchBase $targetOU -Property LastLogonDate,LastLogon |
Where-Object { $_.DistinguishedName -notmatch $regexExcludedOUs } |
Select-Object Name,DistinguishedName, @{l='LastLogonDate';e={[DateTime]::FromFileTime($_.LastLogon)}}
[1] Two examples that in essence use the same technique as in the rest of the answer:
Whole-string matching: $regexes = 'fo+', 'ba[r]'; $combinedRegex = '^({0})$' -f ($regexes -join '|')
$combinedRegex
then contains ^(fo+|ba[r])$
verbatim.
Prefix matching: $literalPrefixes = 'fo.', 'ba+'; $combinedRegex = '^({0})' -f ($literalPrefixes.ForEach({ [regex]::Escape($_) }) -join '|')
$combinedRegex
then contains ^(fo\.|ba\+)
verbatim.