Powershell - compare Active Directory usernames with e-mail address - loops

I am currently trying to find all AD users that have been created using the model "firstname#domain.com" versus our new standard of "FirstInitialLastName#domain.com". I'm using the Quest ActiveRoles modules, specifically Get-QADUser to pull down my user details:
Get-QADUser -enabled -IncludedProperties PrimarySMTPAddress | ?{$_.Type -match "User"} | Select-Object FirstName,PrimarySMTPAddress ...
That gets me a list of user first names and their SMTP address. Where I am stumped is how to compare the results.
I thought normalizing the values (either adding "#domain.com" to the first name string or stripping "#domain.com" from the SMTP string) and then doing a -ieq test would be the best approach. I have found I can do the first with:
%{ $address=$($_.FirstName + "#domain.com";) }
But I can't figure out how to then test $address against the PrimarySMTPAddress string. I can create a second variable with:
%{ $smtp=$($_.PrimarySMTPAddress); }
and get the result:
[PS] C:\>$addy -ieq $smtp
True
I'm just unclear how to do it all in stream so that I can process my tree at once. If this is something that's just more suited to a script than a single line, that's fine too. Coming from the glorious world of BASH my brain just wanted to one-line it.

Get-QADUser -Enabled -Email * -SizeLimit 0 |
Where-Object {$_.Email.Split('#')[0] -eq $_.FirstName }

Try this:
Get-QADUser -enable -IncludedProperties PrimarySMTPAddress |
? { $_.PrimarySMTPAddress -match ("^"+[regex]::escape("$($_.firstname)")+"#domain.com") }

Related

Why can't I store the return value of a cmdlet [CimInstance] inside an arraylist?

I'm currently working on a script where i retrieve the following data on a host:
List of installed printers
List of installed printer drivers
List of used printer drivers
At the moment i simply achieve it this way:
$installedPrinters = Get-Printer
$installedDrivers = Get-PrinterDriver | Sort-Object -Property Name
$usedDrivers = $printerList | Sort-Object -Property DriverName | Select-Object DriverName | Get-Unique -AsString
For convenience reasons i'm now trying to use an arraylist instead of 3 different variables for storing this data but i somehow don't seem to get this to work.
As soon as i try something like that ...
$data.Add({Get-Printer})
or
Get-Printer | $data.Add($_)
... i get either a bunch of errors or simply the value 'Get-Printers' as string stored in the arraylist.
Weirdly enough it seems to work if i first store the returned data from the Get-Printer cmdlet inside a dedicated variable and then add this variable to the arraylist.
Can somebody please help me get my head around this? As of now this behaviour doesn't really seem to make any sense to me.
Your syntax is flawed.
Use one of the following (assuming that $data contains a System.Collections.ArrayList instance):
$data.AddRange((Get-Printer))
Or, less efficiently:
Get-Printer | ForEach-Object { $null = $data.Add($_) }

Check a object-string against a array of exceptions

I got a bunch of objects containing multiple string values. I want to manage the exceptions with a object/array consisting of multiple more and less known exceptions. Is there a simple way to get this to work? afraid my brain ain't made for this kind of problemsolving.
Example:
Exceptions =#{
Known1 = 'Minor Error'
Known2 = 'This Have We Seen Before'
Known3 = 'I Remember This Issue'
Special1 = 'Application malfunction'
Special2 = 'Application malfunction severe!'
}
$AllCases = Get-Cases
Foreach ($Case in $AllCases){
if ($Case.Name -match $Exceptions.Values.('^Known+[0-9]$')){'Do That again'}
Elseif ($Case.Name -match $Exceptions.Values.Special1){'OBS! Do This Fast'}
Elseif ($Case.Name -match $Exceptions.Values.Special2){'SEVERE OBS! do this faster!'}
}
Thanks :)
I guess you're trying to create a script to identify incidents where $case.Name whatever that could be (subject of the incident?) matches with your map of words that are commonly found, which in turn points to a specific Error Code or Error Index? If that's the case, It is probably better even though slower, to store a separate CSV file where you can add new Error Indexes and make your script read this CSV each time. In any case, I harcoded an example of how the CSV could look like and added some comments to guide you with the tought process.
$Exceptions = #"
Index,Error
Known1,Minor Error
Known2,This Have We Seen Before
Known3,I Remember This Issue
Special1,Application malfunction
Special2,Application malfunction severe!
"# | ConvertFrom-Csv
# I'll generate 500 random cases here mixing the words on Error
# I'll also added a IncidentNumber property becase I'm guessing you have something like that
$words = $Exceptions.Error -split '\s'
$AllCases = 0..500 | ForEach-Object {
$ran = Get-Random -Minimum 2 -Maximum 10
[pscustomobject]#{
IncidentNumber = Get-Random
Name = ($words | Get-Random -Count $ran) -join ' '
}
}
Example of the cases created
PS /> $AllCases | Get-Random -Count 5
IncidentNumber Name
-------------- ----
2043166219 Application This Remember Before
837011116 malfunction Error Seen Have malfunction Issue Minor severe!
2103904733 This malfunction We This I Have Seen Application
323914959 Minor This Issue Remember This Application Have Seen
1105202359 malfunction Seen We This I malfunction
Back to the script:
$result = foreach($Case in $AllCases)
{
if($Exceptions.Error -match $Case.Name)
{
# Note: I'm using -match here instead of -in or -contains
# because we want to partially match, considering that $Case.Name
# could be a much longer string
# $Exceptions array where the string in $Case.Name
# matches any element of $Exceptions.Error
$Exceptions.Where({$_.Error -match $Case.Name}) |
Select-Object #{n='IncidentNumber';e={$Case.IncidentNumber}},Index,#{n='Status';e={'FOUND'}}
continue
}
# If there are no matches you can use something like below, not sure
# you have some sort of incident number or something short you can use
# for those incidents not found :)
$Case | Select-Object IncidentNumber,#{n='Index';e={$null}},#{n='Status';e={'NOT FOUND'}}
}
Since this is all random, it is possible there will not be any matches but in this run I got these results
PS /> $result | Sort-Object Status | Select-Object -First 10
IncidentNumber Index Status
-------------- ----- ------
505966803 Special1 FOUND
235217253 Known3 FOUND
1034830172 Known3 FOUND
1047600080 Special2 FOUND
481579809 Known3 FOUND
2131683661 Known2 FOUND
505966803 Special2 FOUND
1424263573 NOT FOUND
249127953 NOT FOUND
489126244 NOT FOUND

Powershell: Comparing a value between two arrays, and extracting a related value

so here is what I'm trying to accomplish.
I have a form for a new starter, New Starter Form.csv, that has the following headers and information:
firstname,lastname,teamname,startdate
Joe,Bloggs,Security Admin,01/01/18
I have a different csv called Team List.csv, that has the following headers and information:
teamlead,teamname,resgroup
A B,Marketing,RESMARKETING01G
C D,Product,RESPRODUCT01G
E F,Advertising,RESADVERTISING01G
G H,Security Admin,RESSECURITYADMIN01G
I want to import both CSV files into Powershell, run a comparisson that takes the team name from the New Starter Form, and checks if there are any matches in the Team List, and if so, add the relevant RES group to the new starter in AD.
Currently, I can import them, compare them, find a match, and find an index number for the record, but I'm struggling to the take this index number, and use it to get the relevant RES group. So far the code looks like this:
$teamlist = import-csv "\\location\Team List.csv"
$newstarter = import-csv "\\otherlocation\New Starter Form.csv"
[string]$teamname = Compare-Object -includeequal -excludedifferent -PassThru $newstarter.teamname $teamlist.teamname
$teamname
[array]::indexof($teamlist,$teamname)
And running that, provides us with this in the console, showing that we can indeed see the match, and that the matching record is the last (-1) one:
PS C:\WINDOWS\system32> $teamlist = import-csv "\\location\Team List.csv"
$newstarter = import-csv "\\otherlocation\New Starter Form.csv"
[string]$teamname = Compare-Object -includeequal -excludedifferent -PassThru $newstarter.teamname $teamlist.teamname
$teamname
[array]::indexof($teamlist,$teamname)
Security Administration
-1
I've not got a lot of experience with Powershell, and my coding knowledge is pretty limited overall, but I'm used to the concept that I can save the index value as a variable, and then I could call that variable back to do something like $teamlist.resgroup[VARIABLE HERE].
But if I try and declare a new variable before [array]::indexof($teamlist,$teamname), Powershell isn't happy.
Whilst I've not looked into it, I believe a possible alternative could be to add in a huge switch statement, but I may be looking at having 100+ teams overall, and I'd like to avoid inefficient code wherever I can. Am I missing something obvious though? Is there a better way (Or even just a functioning way would be great!) that this could work?
Any help you can provide would be greatly appreciated!
$teamlist = import-csv "\\location\Team List.csv"
$newstarter = import-csv "\\otherlocation\New Starter Form.csv"
# get a single new starter
$person = $newstarter | Where-Object { $_.firstname -eq 'Joe' -and $_.lastname -eq 'Bloggs' }
# get the new starters team
$team = $teamlist | Where-Object { $_.teamname -eq $person.teamname }
# get the new starters resource group
$resgroup = $team.resgroup
# use the resource group - this simply writes it to the console
Write-Host $resgroup
The code above will:
import your two csvs
grab a single new starter from your new starter csv, based on first &
last name
grab the team & resource group for that new starter from the team list
display the resgroup (this is where you will need to use to populate AD)

ACL "fuzzy" comparision

I'm trying to compare ACLs on a folder with a reference set of ACLs, and then list any exceptions. The "fuzzy" part of the equation is that I want to be able to disregard any unknown SID. So creating a reference folder with the perms I want to test won't work to use Compare-Object between it and my test folder.
The underlying scenario is that I am cleaning up old user directories where the actual user account has been deleted (this is where the non-resolved SID comes in). By default, the folders include perms for Administrator and the like, which I don't care about. There are some folders, however, where another user has been granted explicit permissions, and I want to capture these. Unfortunately, there aren't any shortcuts I can use to check: e.g. -IsInherited or the like to exclude ACLs I don't care about.
Per the below, I can dump the ACLs out into an array
$acl = get-acl f:\user_folder
$access = $acl.Access | ForEach-Object { $_.identityReference.value }
$access
BUILTIN\Administrators
MYDOMAIN\JBLOGGS
S-1-5-21-4444444444-9999999-1111111111-74390
MYDOMAIN\Domain_Group ###Yes, the group has an underscore in the name
I can create another array of the users I want to ignore, including a partial string to match any unresolved SID.
$defaults = #("BUILTIN\Administrators","MYDOMAIN\DomainGroup","S-1-5-21")
So how do I compare my $defaults array with the $access array and output only the exceptions like "MYDOMAIN\JBLOGGS"?
I'm trying a foreach, but I'm stumped about grabbing that exception. The following still outputs the SID I want to avoid. I'm hoping to also avoid too many nested "IFs".
$access | ForEach { If ($defaults -notcontains $_) { Write-Output $_ } }
MYDOMAIN\JBLOGGS
S-1-5-21-4444444444-9999999-1111111111-74390 #Do not want!
If I put the wildcard $_* into the -notcontains, I get the whole contents of $access again.
I'd do something like this:
$defaults = 'BUILTIN\Administrators', 'MYDOMAIN\DomainGroup', 'S-1-5-21*'
$acl.Access | Where-Object {
$id = $_.IdentityReference
-not ($defaults | Where-Object { $_ -like $id })
} | Select-Object -Expand value
$defaults | Where-Object { $_ -like $id } does a wildcard match of the given identity against all items of $defaults. The wildcard * at the end of S-1-5-21* allows to match all strings starting with S-1-5-21. The negation -not inverts the result so that only identities not having a match in $defaults pass the filter.
give the users you want to ignore some right on a dummy folder, get the acl of that folder and then compare whith the acl of your actual folder
$genericACL = get-acl c:\temp\dummy
$folderacl = get-acl f:\user_folder
$exceptions= $folderacl.Access.identityreference.value |?{ ($_ -notin $genericACL.access.identityreference.value) -and ($_.strartswith('S-1-5-21') -eq $false)) }
In the end, it was fairly simple, thanks to the help above.
I managed to omit the fact in the original question where I required it to work in Powershell v2.
$defaults = #("BUILTIN\Administrators","MYDOMAIN\DomainGroup")
$acl = get-acl $folder
$access = $acl.Access | ForEach-Object { $_.identityReference.value }
# check that no other account still has access to the folder
$access | ForEach {
If ($defaultACL -notcontains $_ -and $_ -notlike 'S-1-5-21*') {
write-output "Extra perms:$user $_"
}

Split an Array of Objects to Multiple objects

I wanted to split the output of the following command:
PS C:\Windows\system32> $array = get-vm | Select-Object
#{Name="VMName";Expression=$_.name}},
#{Name='VirtualNetwork';e={ $_.VirtualNetworkAdapters | Foreach-Object{
$_.VirtualNetwork}}},
#{Name='PhysicalMACAddress';e={$_.VirtualNetworkAdapters| Foreach-Object{
$_.physicaladdress}}}
whose output is like
to an output like this, can some one please help.
In my opinion you're in the wrong direction. You'd have to get all objects first, check for the one with most virtual networks, and create objects with a lot of VirtualNetworkN properties.

Resources