Get-ADUser filter by property length - powershell

I can't seem to figure this out to save my life.
I want to grab all AD users where their SAMAccountName length is equal to 6.
I'm hoping for something like this
Get-ADuser -filter "samaccountname.length -eq 6" | out-file $outputFile -append
I'm writing a massive script to first dump all AD users, then loop through each dumped user and update some attributes. This script will be run often, so I want to make it as efficient as possible. One area that I thought could be improved is the dump process.
We have about 15 thousand users in AD, but I'm only interested in 4 thousand, specifically, the ones for which their SamAccountName is 6 characters. For this reason, I don't want to fill my ID output file with about 11 thousand unnecessary IDs.
I want to try and avoid an inline for-each if possible.
Any help would be greatly appreciated.

Try this:
Get-ADuser - filter * | ? { $_.samaccountname.length -eq 6} | out-file -$outputfile -append
I usually do it with Get-QADuser (from Quest module) but I think Get-ADUser is same.
If $_.samaccountname isn't a string maybe you have to use:
$_.samaccountname.tostring().length
EDIT:
Get-ADUser -Filter * -Properties samaccountname | ? {$_.samaccountname.length -eq 6}

Get-ADuser | where { $_.samaccountname.length -eq 6 } | out-file $outputFile -append

Related

How to move multiple users to multiple OUs importing users from CSV and filtering by department

New to powershell and scripting in general. Trying to improve automation in our onboarding process, we have to move multiple user accounts to multiple OUs every couple of months.
Import-Module ActiveDirectory
$dept1 = "OU=Path1,DC=SOMEWHERE,DC=OUTTHERE"
$dept2 = "OU=Path2,DC=SOMEWHERE,DC=OUTTHERE"
Import-Csv "C:\Scripts\Incoming.csv" | ForEach-Object {
$samAccountName = $_."samAccountName"
Get-ADUser -Identity $samAccountName -Filter {Department -eq "Dept1"} | Move-ADObject -TargetPath $dept1
Get-ADUser -Identity $samAccountName -Filter {Department -eq "Dept2"} | Move-ADObject -TargetPath $dept2
}
This actually moves ALL users with the department marked into the path I have set.. but I only want it to look at those users in the csv and then filter their departments from AD - not the CSV. I feel like I'm missing a step but I've searched everywhere and tried the get-help. I feel like I need to get the identity, then search/filter against something else but I'm not quite sure how. Would appreciate any help.
edit
Okay, if I run the following:
Get-ADUser -Filter {Department -eq "Dept1"} -Properties Department
It returns everyone that fits that property but how do I compare those to the $samAccountName and ONLY try to move those accounts or run the commands against the accounts on the list? When I ran the second half of the command it tried to move them all and failed.
Move-ADObject $samAccountName -Target $dept1
I feel dumb.
It's ok to feel dumb. You're not and everyone feels that way at times when trying to learn a new thing. You're also here asking for help, so you're ahead of the game compared to a lot of others.
#Lee_Daily's comment is correct that Get-ADUser doesn't support using both -Identity and -Filter in the same command. They're part of different parameter sets. You can tell from the syntax output of Get-Help Get-ADUser or the online docs. Both show 3 different sets of parameters and Identity and Filter are not in the same one. What's odd is that your original script should have thrown an error because you tried to use both in the same command. No need to worry about that now though.
Here's a typical way one might approach this task. First, you query the user's details including the department you want to make a decision on. Then, you write your condition and perform the appropriate action. Doing it this way means you're only querying AD once for each user in your CSV rather than twice like your original script which is good for script performance and load on your AD. The inside of your ForEach-Object loop might look something like this. Note the addition of -Properties department in Get-ADUser. We need to ask for it explicitly because department isn't returned in the default result set.
# all of this goes inside your existing ForEach-Object loop
$u = Get-ADUser -Identity $_.samAccountName -Properties department
if ($u.Department -eq 'Dept1') {
$u | Move-ADObject -TargetPath $dept1
} elseif ($u.Department -eq 'Dept2') {
$u | Move-ADObject -TargetPath $dept2
}
Now let's talk about some alternative ways you might approach this.
The first way sort of flips things around so you end up only calling Get-ADUser once total, but end up doing a lot more filtering/processing on the client side. It's not my favorite, but it sets things up to understand my preferred solution. In particular, the Get-ADUser call uses the -LDAPFilter parameter. LDAP filter syntax is a little strange if you've never seen it before and this particular example could use the more common -Filter syntax just as easily. But in the next example it would be much more difficult and learning LDAP filter syntax enables you to query AD from anything rather than just PowerShell.
# instead of immediately looping on the CSV we imported, save it to a variable
$incoming = Import-Csv "C:\Scripts\Incoming.csv"
# then we make a single AD query for all users in either Dept1 or Dept2
$users = Get-ADUser -LDAPFilter '(|(department=Dept1)(department=Dept2))' -Properties department
# now we filter the set of users from AD by department and whether they're in the CSV and do the moves as appropriate
$users | Where-Object { $_.department -eq 'Dept1' -and
$_.samAccountName -in $incoming.samAccountName } | Move-ADObject -TargetPath $dept1
$users | Where-Object { $_.department -eq 'Dept2' -and
$_.samAccountName -in $incoming.samAccountName } | Move-ADObject -TargetPath $dept2
The benefit of this method is the single AD round trip for users rather than one for each in the CSV. But there are a lot more local loops checking the samAccountNames in the results with the samAccountNames from the CSV which can get expensive cpu-wise if your CSV and/or AD is huge.
The final example tweaks the previous example by expanding our LDAP filter and making AD do more of the work. AD is ridiculously good at returning LDAP query results. It's been fine-tuned over decades to do exactly that. So we should take advantange of it whenever possible.
Essentially what we're going to do is create a giant 'OR' filter out of the samAccountNames from the CSV so that when we get our results, the only check we have to do is the check for department. The way I verbalize this query in my head is that we're asking AD to "Return all users where SamAccountName is A or B or C or D, etc and Department is Dept1 or Dept2. The general form of the filter will look like this (spaces included for readability).
(& <SAM FILTER> <DEPT FILTER> )
# Where <DEPT FILTER> is copied from the previous example and
# <SAM FILTER> is similar but for each entry in the CSV like this
(|(samAccountName=a)(samAccountName=b)(samAccountName=c)...)
So let's see it in action.
# save our CSV to a variable like before
$incoming = Import-Csv "C:\Scripts\Incoming.csv"
# build the SAM FILTER
$samInner = $incoming.samAccountName -join ')(samAccountName='
$samFilter = "(|(samAccountName=$samInner))"
# build the DEPT FILTER
$deptFilter = '(|(department=Dept1)(department=Dept2))'
# combine the two with an LDAP "AND"
$ldapFilter = "(&$($samFilter)$($deptFilter))"
# now make our single AD query using the final filter
$users = Get-ADUser -LDAPFilter $ldapFilter -Properties department
# and finally filter and move the users based on department
$users | Where-Object { $_.department -eq 'Dept1' } | Move-ADObject -TargetPath $dept1
$users | Where-Object { $_.department -eq 'Dept2' } | Move-ADObject -TargetPath $dept2
There are more efficient ways to build the LDAP filter string, but I wanted to keep things simple for readability. It's also a lot easier to read with better PowerShell syntax highlighting than StackOverflow's. But hopefully you get the gist.
The only limitation with using this method is when your incoming CSV file is huge. There's a maximum size that your LDAP filter can be. Though I'm not sure what it is and I've never personally reached it with roughly ~4000 users in the filter. But even if you have to split up your incoming CSV file into batches of a few thousand users, it's still likely to be more efficient than the other examples.

Get-Aduser -Filter Option -notlike does not work

Attempting to use Get-Aduser to find entries in Active directory that are not in a text file. The -like option appears to work but cannot seem to get the -notlike to work.
When I use the -nolike option, the entries in the text file appear as part of the output file. Using the -like option the powershell works.
Here is the contents of the text file
svcXXSQL001Agent
svcXXSQL001DBEng
svcXXSQL001Int
svcXXSQLUAT501DBEng
svcxxapp211
Here is my existing code:
$server=get-content C:\temp\test.txt
foreach ($name in $server) {
Get-ADUser -SearchBase “OU=ServiceAccts,DC=nlong,DC=com” -Filter "name -notlike '$name'" | sort | Where-Object {$_.Name -like "svcxxsql*"} | Select- Object Name | Out-File -FilePath C:\temp\foo.txt
}
Thanks for the input, Norm.
Expecting the output without the given names is a false assumption, let me demonstrate using the numbers 1 and 2, and the Where-Object cmdlet in place of the Get-ADUser filter all numbers from 1 to 5 except for 1 or 2:
$numbers = 1,2,3
foreach($number in $numbers){
# Let's output all numbers from 1 to 3, except for $number
1..3 |Where-Object {$_ -notlike $number}
}
You will find the output to be:
2
3
1
3
1
2
In the first iteration of the loop, we receive the number 2 along with the number 3 - this is obviously not our intention, they were supposed to be filtered, but it ended up in the output because we filter only against 1 number at a time.
We can use either the -notcontains or -notin operators to filter against a collection of terms instead:
$numbers = 1,2,3
1..3 |Where-Object {$numbers -notcontains $_}
# or
1..3 |Where-Object {$_ -notin $numbers}
In your example, you would have to retrieve all the AD users and filter using the Where-Object cmdlet:
Get-ADUser -SearchBase "OU=ServiceAccts,DC=nlong,DC=com" |Where-Object {$_.Name -notin $server} | sort | Where-Object {$_.Name -like "svcxxsql*"} | Select-Object Name | Out-File -FilePath C:\temp\foo.txt
Since you're only interested in accounts that start with svcxxsql, we might as well place that as the filter:
Get-ADUser -SearchBase "OU=ServiceAccts,DC=nlong,DC=com" -Filter {Name -like "svcxxsql*"} |Where-Object {$_.Name -notin $server} | sort | Select-Object Name | Out-File -FilePath C:\temp\foo.txt
While this is old, here's a more efficient method using an actual LDAP filter that you construct from information supplied in the file.
Assuming the file contains the actual sAMAccountNames you wish to exclude:
$servers = get-content C:\temp\test.txt
# Begin the filter - sAMAccountType=805306368 is user objects only, no contacts
$filter = '(&(sAMAccountType=805306368)(!(|'
# recursively append each samAccountName to exclude in the filter
foreach ($u in $servers) {
$filter = $filter + "(samAccountName=$u)"
}
#finish the filter
$filter = $filter + ')))'
#ldap filter looks like this
# (&(sAMAccountType=805306368)(!(|(samAccountName=svcXXSQL001Agent)(samAccountName=svcXXSQL001DBEng)(samAccountName=svcXXSQL001Int)(...))))
# run the query
Get-aduser -LDAPFilter $filter -SearchBase "OU=ServiceAccts,DC=nlong,DC=com"
Active Directory can technically take an LDAP query that's 10MB in size, although obviously that'd be really excessive. So I recommend the above method be used only if it's a limited number of items you want to exclude.
I use a similar method to build a query for users that are members of certain groups but not others, and for that, it's significantly faster than grabbing groups with thousands of users each and trying to compare members that are exclusive to one.
As always, test and compare the time to execute the different methods:
grabbing everything and discarding unwanted results after
grabbing a partially-filtered set and discarding after (like the original answer)
constructing a more complex targeted query (this example)
Also consider that processing loads occur in different places. It can take a long time for a DC to execute a very long, complex LDAP query, with extra CPU and potential timeouts. But it can take even longer to grab all the properties from every single object from AD, with potential connection timeouts, with more data travelling across the wire to the target system, then getting all that data processed locally.
In my experience, it's the "grab everything" queries with big result sets that cause the most load, on DCs, network and target systems (Perfmon LDAP monitoring on the DCs can be interesting). That's that's why it's often best for the DC to do the filtering work, as long as the LDAP filters are sensible.

I have a .csv with thousands of emails, and I need to use Active Directory to find the state of a particular, custom variable, using powershell

I'm new and don't know enough about powershell.
I've got a .csv that is nothing but "EMAILS" for the header and some 6000 emails under it. (email1#company, email2#company, etc.)
I need to find the state of a particular, custom property for each one.
Individually, I know that I can run
Get-ADUser -Filter {mail -eq 'email#company'} -properties customproperty
in order to find one particular user's state.
I have been hitting my head against a wall trying to make it work with import-csv and export-csv, but I keep getting red all over my console.
Can someone point me an to example where import-csv and export-csv are used properly, with a command run against the contents?
Here's what I would do.
First, fetch all users that have email addresses in AD, and save them into a hashtable. This will make lookups absurdly faster and place less overall load on your domain controller. If you've got 100,000 user accounts it may not be the best option, but I expect that it will be in general.
$ADUsers = #{};
Get-ADUser -Filter "mail -like '*'" -Properties mail, customproperty | ForEach-Object {
$ADUsers.Add($_.mail, $_.customproperty);
}
Now you import the CSV and do lookup using a calculated property with Select-Object, and export it back out.
Import-Csv -Path $InputFile | Select-Object -Property emails, #{n='customproperty';e={$ADUsers[$_.emails]}} | Export-Csv -Path $OutputFile -NoTypeInformation;
So without seeing the code of what you posted, where I think you will run problems is with how interaction of two things.
The first will be that when you use the Import-CSV cmdlet. You will receive an array of objects with a property for each column and not an array of strings.
This is ties into the second part of the issue which is the sensitivity of the AD module filter. But the short answer is don't use {} inside the filter because it will break if you use {mail -eq $ImportedCSV.EMAILS}.
mklement0 has a wonderful answer that goes into the details. But a simple rule of thumb is "double quote outer, single quote" inner.
So you could expand the EMAILS property either with Select-Object -ExpandProperty EMAILS to have an array which works inside {} or you could use "mail -eq '$ImportedCSV.EMAILS'".
Here is an example with both the expansion and using the "property -eq '$variable'" style filter:
Import-CSV C:\Example\Path.csv |
Select-Object -ExpandProperty EMAILS |
ForEach-Object {
Get-ADUser -Filter "mail -eq '$_'" -Properties customproperty
} |
Select-Object mail,customproperty |
Export-CSV C:\Example\OutputPath.csv -NoTypeInformation
Please use below code
$csvInput = Import-CSV C:\Example\test.csv
Foreach ($line in $csvinput) {
Get-ADUser -Filter {mail -eq $line.mail} -Properties mail, customproperty |
Select-Object mail,customproperty |
Export-CSV C:\Example\OutputPath.csv -NoTypeInformation
}

Powershell pipe variables into get-user command

I am trying to pipe a list of email addresses into a get-user command in powershell
$email = get-content -path "c:\temp\file.csv" get-user -indentity $email | select-object userprincipalname,department,phone,name | format-table | out-file c:\temp\file.txt
Welcome to SO.
Lets see... where to start
Email Address is not one of the value recognized for Identity
It is spelled -identity
Don't use Format-table for object output.
Department is not one of the default values returned.
There is no AD attribute just called phone
It's Get-Aduser not get-user
Don't know if it was just a copy paste accident but you have multiple lines as one.
-Identity expects one value. Not an array of names.
Knowing that lets see if we can take a stab at what you were trying to do. Assuming that your file "c:\temp\file.csv" only contained addresses with no header (since that is how you were treating it.)
Get-Content c:\temp\file.csv | ForEach-Object{
Get-ADUser -Filter "emailaddress -eq '$_'" -Properties department,OfficePhone
} | Select-Object UserPrincipalName,Department,OfficePhone,Name | Export-CSV C:\temp\outputfile.csv -NoTypeInformation
There is no error correction here so you might need to look into an -ErrorAction try/catch combination. I encourage you to look that up on your own.

Remove white space from PowerShell output

I'm returning the mail property of a distribution group within Active Directory using the command below in PowerShell.
Get-ADGroup $GroupName -Properties Mail | Select-Object Mail | Format-Wide
The output looks like (asterisks used to represent white space):
*
*
mygroup#mycompany.com
*
*
Is there any way that I can remove the white space added at the beginning and end of the output?
I think this should work (V2):
(Get-ADGroup $GroupName -Properties Mail | Select-Object Mail | Format-Wide | out-string).split("`n") -match '\S'
Edit: that's way more complicated than it needs to be.
(Get-ADGroup $GroupName -Properties Mail | Select-Object Mail | Format-Wide | Out-String).trim()
That is how PowerShell formats output. I have complained on several occasions about the excess blank lines before and after output. If you want to avoid that, then you format the output yourself. You can do that like so:
$res = #(Get-ADGroup $GroupName -Properties Mail | Select-Object Mail)
for ($i = 0; $i -lt $res.Length - 1 + 4; $i += 4) {
"{0,-28} {1,-28} {2,-28} {3,-28}" -f $res[$i].Mail,$res[$i+1].Mail,$res[$i+2].Mail,$res[$i+3].Mail
}
This assumes your current console is 120 chars wide. If it is 80, change the -28 above to -18.
BTW the key point here is that PowerShell deals in objects and when it renders those objects to the screen it has a formatting engine that determines things like blank lines before and after the output. If you don't like PowerShell's default formatting, you're free to format objects (displaying whichever properties you want) as you please but it is a bit more work.
All that said, if the command returns only one object, why not just do this:
(Get-ADGroup $GroupName -Properties Mail).Mail
The Select-Object Mail, Format-Wide and Out-String are not necessary. Heck, with PowerShell V3 this will work even if the command returns multiple objects.
A combination of the examples in the checked answer worked for me in a similar situation:
... Format-Table | Out-String).split("`n").trim()
After re-reading the original question it seems I had a different problem to solve. I was looking for a way to trim white space from the end of output lines. The checked answer led me to try the above code which did what I was looking for.