Can I use splatting within a Where-Object using PowerShell? - powershell

What I want to find out if I can do, is splatting in a Where-Object clause or something similar. I know you can do this with parameters already.
I am trying to filter out multiple values from one property using -notlike.
I have looked at https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_splatting?view=powershell-5.1 but nothing is mentioned in here.
An example of what I'm currently trying to do:
$allUsers | Where-Object {($_.UserPrincipalName -notlike "Health*") -and ($_.UserPrincipalName -notlike "admin*")}
I am trying to do it this way as there are lots of accounts I want to exclude that contain the the word "admin" in their UPN. Unfortunately there is a long list of what I need to exclude, as I am running through a cleanup process.
I have been suggested to use an array of exclusions and then try using -notcontains or -notin but this has not worked for me, I'm assuming because I need it to be wildcard friendly.

I think it is a lot easier to use the regex -notmatch operator:
# create a regular expression string by combining the keywords with the OR '|' character
$exclude = ('test','admin','health' | ForEach-Object { [regex]::Escape($_) }) -join '|'
# get all users except the users that have any of the keywords in their UserPrincipalName
Get-ADUser -Filter * | Where-Object { $_.UserPrincipalName -notmatch $exclude }

You can do the following to build a dynamic filter by just comma-separating your exclusion strings, which effectively creates an array of strings ($Exclusions).
$Exclusions = 'Health*','admin*'
$FilterArray = $Exclusions | Foreach-Object {
"UserPrincipalName -notlike '$_'"
}
$Filter = #{'Filter' = "{0}" -f ($FilterArray -join " -and ")}
Get-ADUser #Filter

-notin works fine for me, maybe supply a bit more of your data in your question to see what you;re trying to filter out? Is it the wildcards causing an issue?
$listofexclusions = #("userid1","userid2")
Get-ADUser -Filter * | Where-Object { $_.SamAccountName -notin $listofexclusions } | Select Name,SamAccountName | ft
Edit:
If you need wildcards, there are some previous threads about this here

If you're using something like Get-ADUser you can do what other answers have suggested and let the cmdlet filter the results for you using its built in functionality.
However, if you just want to apply a bunch of -like operations to a collection of objects you can write your own Test-LikeAny function as follows:
function Test-LikeAny
{
param( [string] $value, [string[]] $patterns )
foreach( $pattern in $patterns )
{
if( $value -like $pattern )
{
return $true
}
}
return $false
}
and then you can use it like this:
$values = #(
(new-object pscustomobject #{ "Property1" = "...value1..." }),
(new-object pscustomobject #{ "Property1" = "...value2..." }),
(new-object pscustomobject #{ "Property1" = "...value3..." })
)
$patterns = #( "*value1*", "*value2*" )
$values | Where-Object { -not (Test-LikeAny $_.Property1 $patterns) }
which gives:
Name Value
---- -----
Property1 ...value3...

Related

Powershell if condition returning no results when some exist

The code block below executes without the CSV being generated suggesting it has no return. However the conditions given are definitely valid for at least one object.
A Mail Enabled Security group in O365 has the "SecurityEnabled" property set to true, the "MailEnabled" property set to true and the property "GroupTypes" is an empty array? string? "{}" whatever the curly brackets are supposed to represent but they're empty.
$Groups = Get-MgGroup -Property "Members" -All
foreach($group in $Groups){
if ( (($group.SecurityEnabled) -and ($group.MailEnabled)) -and ($group.GroupTypes -ne 'Unified')){
$members = Get-MgGroupMember -GroupId $group.Id
[PSCustomObject]#{
'Display Name' = $group.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
} | Export-Csv -path "$OutputFolder\MailEnabledSecurityGroups.csv" -Append -NoTypeInformation
}
continue
}
The GroupTypes property from the MicrosoftGraphGroup instance is an array, in this case you want to use containment operators, more specifically -notin or -notcontains, the condition would look like:
# with `-notcontains`
if ($group.SecurityEnabled -and $group.MailEnabled -and $group.GroupTypes -notcontains 'Unified') {
# with `-notin`
if ($group.SecurityEnabled -and $group.MailEnabled -and 'Unified' -notin $group.GroupTypes) {
As for why it was not working before, -ne and -eq can act as a filter when the LHS (left hand side) of the comparison is an array and because $group.GroupTypes was an empty array, the comparison returned null and the if condition evaluated to false because of this.
$obj = [pscustomobject]#{
GroupTypes = #()
}
$obj.GroupTypes -ne 'Unified' # => null
$obj = [pscustomobject]#{
GroupTypes = #('something else')
}
$obj.GroupTypes -ne 'Unified' # => something else
As aside, it's probably a better idea to export the output all at once instead of appending to the CSV file on each loop iteration (Disk I/O is expensive and will slow down your script a lot!):
Get-MgGroup -Property "Members" -All | ForEach-Object {
if ($_.SecurityEnabled -and $_.MailEnabled -and 'Unified' -notin $_.GroupTypes) {
$members = Get-MgGroupMember -GroupId $_.Id
[PSCustomObject]#{
'Display Name' = $_.DisplayName
'Members' = $members.AdditionalProperties.displayName -join ";"
}
}
} | Export-Csv -path "$OutputFolder\MailEnabledSecurityGroups.csv" -NoTypeInformation
Filtering on Azure side may increase the speed of your code and also reduce the amount of conditions being evaluated on client side:
Get-MgGroup -Property "Members" -Filter "securityEnabled eq true and mailenabled eq true" | ForEach-Object {
if ('Unified' -notin $_.GroupTypes) {
# code here
}
}

PowerShell compare CSV to AD Subnets and update / add if required

I've got a CSV file with headers subnet, site, description. I want to import this into PoSH and then compare what is in AD Sites and Services. Now, this might be "null" or it might already contain subnet values. In either case, I need the subnets in AD compared to the CSV and updated/added as required.
I thought about If/Else, but that doesn't seem to handle the scenario where no subnets exist.
So I moved to try/catch and also Compare-Object.
Anyway, an example of what I've tried:
$csvSubnets = Import-Csv -Path C:\subnets.csv
$adSubnets = Get-ADReplicationSubnet -Filter *
Compare-Object -ReferenceObject #($csvSubnets | Select-Object) -DifferenceObject #($adSubnets | Select-Object) -Property Name | Where-Object {
$_.SideIndicator -eq '<='
} | ForEach-Object {
$csvSubnetName = $_.subnet
$csvSiteName = $_.site
$csvDescription = $_.description
$adSubnetName = $adSubnets.Where({$_.Name -eq $csvSubnetName})
New-ADReplicationSubnet -Site $_.site -Name $_.subnet -Description $_.description
}
This returns the following error:
New-ADReplicationSubnet : Cannot validate argument on parameter 'Site'. The argument is null
Would anyone have a suggestion as to how to fix this, or perhaps a more efficient way of achieving what I need? Doesn't need to be computationally efficient as there's probably never more than 10 sites (subnets)
The main thing I'd point out is you aren't passing the original objects coming out of the comparison to the ForEach-Object loop. Either reference the property $_.InputObject or preferably use the -PassThru parameter on the Compare-Object command:
Compare-Object -ReferenceObject #($csvSubnets | Select-Object) -DifferenceObject #($adSubnets | Select-Object) -Property Name -PassThru |
Where-Object { $_.SideIndicator -eq '<='} |
ForEach-Object {
$csvSubnetName = $_.subnet
$csvSiteName = $_.site
$csvDescription = $_.description
$adSubnetName = $adSubnets.Where({$_.Name -eq $csvSubnetName})
New-ADReplicationSubnet -Site $_.site -Name $_.subnet -Description $_.description
}
An aside:
The Compare-Object command is rather hard to read, given intermingled pipelines. It may be better to establish those as variables separately. However, you aren't specifying any properties in those Select-Object commands. I presume you are trying to level-set the typing to [PSCustomObject]'s in which case you should really only need to do that with the rich objects returned from Get-ADReplicationSubnet Import-Csv will always return [PSCustomObject]. So you could revise a little like:
$csvSubnets = Import-Csv -Path 'C:\subnets.csv'
$adSubnets = Get-ADReplicationSubnet -Filter * | Select-Object *
Compare-Object -ReferenceObject $csvSubnets -DifferenceObject $adSubnets -Property Name -PassThru |
Where-Object { $_.SideIndicator -eq '<='} |
ForEach-Object {
$csvSubnetName = $_.subnet
$csvSiteName = $_.site
$csvDescription = $_.description
$adSubnetName = $adSubnets.Where({$_.Name -eq $csvSubnetName})
New-ADReplicationSubnet -Site $_.site -Name $_.subnet -Description $_.description
}
I can't test your code but after solving the first problem you may have another with this line:
$adSubnetName = $adSubnets.Where({$_.Name -eq $csvSubnetName})
You aren't referencing the .Name property after you've isolated the AD Subnet you're interested in. I might be missing something, but if you do have that problem a quick fix might be something like:
$adSubnetName = ($adSubnets.Where({$_.Name -eq $csvSubnetName})).Name
Also, I like the .Where() method, but I'm a little iffy on it's return types, and on the readability of unnecessarily mixing and matching. You could replace with a regular Where{} clause:
$adSubnetName = ( $adSubnets | Where-Object{ $_.Name -eq $csvSubnetName } ).Name
Update:
It is true that if the difference object is null the Compare-Object command will fail. However, that approach stemmed from your original code. There are actually many ways to extract the difference between 2 lists. Here's a more concise example:
$csvSubnets = Import-Csv -Path 'C:\subnets.csv'
$adSubnets = (Get-ADReplicationSubnet -Filter *).Name
$csvSubnets |
Where-Object{ $_.Name -notin $adSubnets } |
ForEach-Object{
$SubnetParams = #{
Name = $_.subnet
Site = $_.site
Description = $_.description
}
New-ADReplicationSubnet #SubnetParams
}
In this case we use the -notin operator against a list of subnet names. $adSubnets isn't being used for anything else. We don't need it to be objects custom or otherwise. Now for each record in the CSV file just see if the name isn't on the list, and if not proceed to create the subnet.
Speed is not a likely concern in this case however you can actually make the above example a little more efficient using If logic in the loop and eliminating the Where{}.
$csvSubnets = Import-Csv -Path 'C:\subnets.csv'
$adSubnets = (Get-ADReplicationSubnet -Filter *).Name
$csvSubnets |
ForEach-Object{
If($_.Name -notin $adSubnets ) {
$SubnetParams = #{
Name = $_.subnet
Site = $_.site
Description = $_.description
}
New-ADReplicationSubnet #SubnetParams
}
}
In either example the key point is that -notin will work fine even if the right hand side of the comparison is null. It's simply return false and therefore the subnet will get created.
Firstly, thank you to the other responses here which lead me down a slightly different path to a solution.
$VerbosePreference = 'Continue'
$csvSubnets = Import-Csv -Delimiter "," -Path C:\subnets.csv
foreach ($subnet in $csvSubnets) {
$ADSubnetName = $subnet.subnet
if ($S = Get-ADReplicationSubnet -Filter {Name -eq $ADSubnetName}) {
Write-Verbose "Subnet $($ADSubnetName) already present"
Continue # to the next subnet
}
$SubnetParams = #{
Name = $subnet.subnet
Site = $subnet.site
Description = $subnet.description
}
Write-Verbose "Created subnet $($ADSubnetName)"
New-ADReplicationSubnet #SubnetParams
}

Is it possible to return a single value from MemberOf Active Directory and insert into SQL table

I am trying to get a single value from the MemberOf column, Is it possible to parse that value and return only part of that string such as the value 0012?
This is the output from the memberof catagory.
CN=Active VMS Users,OU=VMS,DC=evilcorp,DC=net CN=JOJOE\, MOE- 0012,
OU=Site Groups,OU=VMS,DC=evilcorp,DC=net
This is my code below
$ADARRAY= Get-ADGroupMember -Identity "Domain Users" -Recursive |
Get-ADUser -Filter "Mail -like '*'" -and "MemberOf -like '*'"|
Get-ADUser -Properties ('Mail','MemberOf')
<# this was my first try #>
# $CN = $CN -split '^CN=(.+?-,\),(?:OU|CN)=.+','$1'
ForEach($OBJECT in $ADARRAY){
$NAME = $OBJECT.Name
$USER = $OBJECT.SamAccountName
$EMAIL = $OBJECT.Mail
<# this was my second try #>
ForEach($OBJECT in $ADARRAY.Memberof){
$CN = New-Object PSCustomObject -Property #{'CN' = $CN}
$CN.OBJECT = $CN.Split(',')[0]
$INSERT = "INSERT INTO $TABLE VALUES ('$USER','$CN','$EMAIL', '$NAME');"
$SQL.CommandText = $INSERT
$SQL.ExecuteNonQuery()
}
}
}
$SQLCON.Close()
Yes, you can use RegEx to do this.
Just search for the data you'd like, split and/or extract what you want.
Windows PowerShell: Extracting Strings Using Regular Expressions
$input_path = ‘c:\ps\emails.txt’ $output_file =
‘c:\ps\extracted_addresses.txt’ $regex =
‘\b[A-Za-z0-9._%-]+#[A-Za-z0-9.-]+\.[A-Za-z]{2,4}\b’ select-string
-Path $input_path -Pattern $regex -AllMatches | % { $_.Matches } | % { $_.Value } > $output_file
'techtalk.gfi.com/windows-powershell-extracting-strings-using-regular-expressions'
PowerShell - Get a SubString out of a String using RegEx
The PowerShell way
("OU=MTL1,OU=CORP,DC=FX,DC=LAB" -split ",")[0].substring(3)
Using RegEx
("OU=MTL1,OU=CORP,DC=FX,DC=LAB" -split ',*..=')[1]
'lazywinadmin.com/2013/10/powershell-get-substring-out-of-string.html'
See a full PoSH RegEx write up here:
'community.idera.com/powershell/powertips/b/ebookv2' Chapter 13. Text
and Regular Expressions
'community.idera.com/powershell/powertips/b/ebookv2/posts/chapter-13-text-and-regular-expressions'

Trying to rename several account types at once based on current displayName

This morning some awesome people helped me make a script to move user accounts based on their displayName to a certain OU. I tested and it worked. I cannibalized the script to make another one that will rename the same accounts based off of the same criteria. I've gone through several errors but basically it all boils down to "I am having an identity crisis!". I can't seem to figure out exactly what I need to input as the $Identity. Here is what I have:
Import-Module ActiveDirectory
$Renames = #(
#{
Filter = 'DisplayName -like "*Supply*"'
NewName = "Supplies"
},
#{
Filter = 'DisplayName -like "*Accountant*"'
NewName = "Accounting"
}
) | ForEach-Object {New-Object -TypeName PSCustomObject -Property $_}
$OriginOU = "OU=Test,OU=Standard Users,OU=Domain Users,DC=com"
foreach ($Rename in $Renames) {
Get-ADUser -SearchBase $OriginOU -Filter $Rename.Filter -Properties displayName |
Where-Object {($_.Enabled -eq 'True') -and ($_.DistinguishedName -notlike '*DontTouch*')} |
%{Set-ADUser $_ -DisplayName {$_.DisplayName -replace '(.EPSILON ).+',"`$1$Rename.NewName"}}
}
You can't use the current object variable ($_) if you have Set-ADUser read directly from the pipeline. And since Set-ADUser apparently doesn't play nice with scriptblock arguments, you have to put the statement in a loop:
... | % { Set-ADUser $_ -DisplayName ($_.DisplayName -replace '(.EPSILON ).+',"`$1$($Rename.NewName)") }
Note that if you want to expand object properties inside a string you have to put $Rename.NewName in a subexpression ($()), otherwise the whole object $Rename would be stringified and the string ".NewName" would be appended to it.

Using Quest Powershell to gather info and report

I need some help writing a script as i am struggling to understand the logic.
I basically have a list of user ids that i need to check to see if they have two certain AD groups. If they have, these need to be outputted into a csv and highlighted.
Can anyone help to get me started? I need to use the Quest Powershell cmdlets
Here is the code
$textFileContents = Get-Content C:\temp\powershell\users.txt
$results = #()
foreach($username in $textFileContents){
$groups = get-qaduser $username |select -expand memberof
if ($groups -match "grpuip1" -and $groups -match "group2"){
echo $group
}
}
check this to begin :
"user1","user2" | foreach {
$groups = get-qaduser $_ |select -expand memberof
if ($groups -match "GROUP1" -and $groups -match "GROUP2"){
echo $_
}
}
I'd use the cmdlet Get-QADMemberOf instead of Get-QADUser. There's nothing wrong with what you're doing, but it's retrieving more information than you need.
Try this to start with:
$textFileContents = Get-Content C:\temp\powershell\users.txt
# Rather than initializing the array, and adding new elements,
# just output each element of the loop to the pipeline, and
# assign the results of the whole pipeline to the variable.
# This is *much* faster than adding to an array
$results = $textFileContents | ForEach-Object {
$userGroups = Get-QADMemberOf $_
if ($userGroups -contains "group1" -and $userGroups -contains "group2") {
New-Object -TypeName PSObject -Property #{"UserName" = $_; "Groups" = ($userGroups -join ",");}
}
}
$results | ConvertTo-Csv -NoTypeInformation | Set-Content C:\Filename.txt