Is there a way to search an OU and add missing members to a security group? - powershell

I'm trying to get away from using the Quest tool AD snap-in and need help re-writing a PowerShell script that updates AD group members based on if they are in an OU or not. Can someone help?
Examples of how it is coded currently to the snap-in.
$null=Get-QADUser -NotMemberOf "Domain\Group-A" -SearchRoot "OU=Users,DC=Test" | Add-QADGroupMember "Domain\Group-A"
$null=Get-QADComputer -NotMemberOf "Domain\Group-B" -SearchRoot "OU=Computers,DC=Test" | Add-QADGroupMember "Domain\Group-B"

Depending on availability (and permissions) there's the RSAT ActiveDirectory powershell module providing cmdlets like Get-AdUser -LdapFilter ... -SearchBase ... that you can use to achieve this.
Syntax is more verbose - for example, it doesn't care for RDN -- but is still rather brief.
Without the ActiveDirectory module you need to set up and implement your own LDAP queries using System.DirectoryServices and System.DirectoryServices.ActiveDirectory.
This will be (very) verbose but it will work on any windows machine that's in active support.
Major difference to what you have right now is that you can't just -NotMemberOf <AdGroupPrincipal>. Instead, you'll have to use LdapFilter, memberof attribute and a distinguished name (FQDN rather than RDN) to identify the group you want the member of. Then negate the result for something like
(!(memberof=cn=GroupName,OU=groupOU,DC=my,DC=domain,DC=lan))
If you prefer to stick to one/two liners then you'll probably want to keep using Quest. Especially if using RSAT ActiveDirectory module is not a viable option you're likely to get hundreds of lines rather than the two.

Related

How to change ManagedBy owner from one user to another one for 150+ groups using power shell

I would like to change the Active Directory Group tab ManagedBy user to another one. With PowerShell script, I exported the groups with the old owner (>150) to a csv file. Now I need to change the owner of those groups using the csv file as input.
I don`t have much experience with scripting, I appreciate any help.
Thanks!
The task is very easy with PowerShell. You didn't show an example of the CSV data you exported so an example may not be exact. However, I assume you exported the default output of Get-ADGroup it might look something like this
(Import-Csv C:\temp\managedBy.csv).DistinguishedName| Set-ADGroup -ManagedBy <NewManager's DN>
Note: I like to use the DistinguishedName for these things but samAccountName should also work.
(Import-Csv C:\temp\managedBy.csv).samAccountName | Set-ADGroup -ManagedBy <NewsamAccountName>
Note: Again with the assumption that your Csv data is a direct export Get-ADGroups's output. You cannot pipe Import-Csv directly to Get/Set-ADGroup as the latter will have trouble determining which property to bind to the -Identity parameter.
However, I would point out you really don't need the intermediate Csv file. You can query AD directly for groups managed by the old manager and pipe that to a command to change the owner.
Get-ADGroup -Filter "ManagedBy -eq '<OldOwner'sDN>'" |
Set-ADGroup -ManagedBy "<NewOwner'sDN"
Note: Again you may be able to get away with using the samAccountName instead of the DN.
Note: You can add the WhatIf parameter to the Set-ADGroup` command to preview what will happen before actually running it.

Adding multiple users to multiple OU's using a single line powershell command

I am very new to PowerShell and I have a .csv file that contains 100 different users with the fields Name,Surname,Section and depending on the section the user has to be created in that specific OU. Ex:Joe,Heart,Accounts - When I execute the command I the user has to be created in the Accounts Organizational Unit.
The biggest challenge is that I have to use only a 1 line command to create the 100 users in their respective OU. I tried multiple commands and watched numerous videos but none seem to work. I am working on Windows Server 2012.
Currently, I am trying to make use of this command
Import-Csv C:\Users\Administrator\Desktop\HomeList.csv
| ForEach-Object { Set-ADOrganizationalUnit -Identity $_.Section -Member $_.Name }
And I am getting the error
A parameter cannot be found that matches parameter name 'Member'
Since this is a school exercise I don't think it would be a good thing to give you a working piece of code to simply copy/paste.
I can however give you tips on where to look..
The CSV file has these fields as you say: Name, Surname, Section where
'Name' seems to be the users first name
'SurName' is the users last name
'Section' is the (display)name of the OU
Each user in the CSV must be moved to the specified OU and for that purpose the ActiveDirectory module has the cmdlet Move-ADObject, so you iterate through the data with a Foreach-Object {...}
There are several issues to deal with here.
The first one is that the Move-ADObject cmdlet takes an -Identity parameter that can either be a DistinghuishedName or a GUID. You can also pipe an ADUser object to it.
In your CSV you have the users first name (AD property GivenName) and the users last name (property SurName) and so you will need to get the user object from AD first in order to be able to use Move-ADObject.
For that, there are several answers to be found on the internet, both using the -Filter aswell as the -LDAPFilter parameters of Get-ADUser.
The second issue is that Move-ADObject needs a -TargetPath parameter in the form of a DistinghuishedName and since your CSV file only contains the (Display)Name of the target OU, you need to get that first too.
The cmdlet for that is Get-ADOrganizationalUnit where you can use the -Filter parameter, something like this: -Filter "Name -eq '$($_.Section)'"
Note: you can also use Get-ADObject and filter on "ObjectClass -eq 'organizationalunit'" as an alternative for Get-ADOrganizationalUnit, but that is a bit more difficult.
Once you have both AD objects, you're all set to use the Move-ADObject cmdlet to move the user to the target OU, but always add the -WhatIf switch to the command when trying out your code. Only if you are satisfied with the results shown in the console, you can take that switch off.
Please do not attempt to put all this in a single line. Write it out and add comments to the code. If you got it working you may want to look at speeding things up a little by organising the data from the CSV using Group-Object
Hope this helps

Finding Active Directory user objects in adjacent OU's to built in Users OU using PowerShell

I have the directory structure
test.com
--Hosting
----ParentCompany
------ChildCompany1
--------SubChildCompany1
----------Users <==== Trying to get users from here
----------Groups
----------Workstations
--------Users
--------Groups
--------Workstations
I am using the command
Get-ADUser -Filter * -Properties * -server <servername> -SearchBase "OU=Users,OU=SubChildCompany1,OU=ChildCompany1,OU=ParentCompany,OU=Hosting,DC=test,DC=com"
For some reason this command is unable to get any user objects out of the nested Users ou under SubChildCompany1. If I do the same search but only drill down to the ChildCompany1 Users OU, I can get all users in that container? I know I could refactor the schema of the directory but at this point that is not an option, so I was wonder if anyone else has seen this behavior? Thanks.
In AD Users and Computers, right click the OU and click Properties. Then on the Attribute Editor tab, check the distinguishedName attribute to make sure it matches what you're using for the SearchBase.
It is possible that it's not an OU, but just a container, which means the distinguishedName will start with CN= rather than OU=.
Thanks for the input. I ended up having to pass the credentials to the command. Apparently if you need to search anywhere outside of the default OUs you need to pass the credentials along with it? It even worked without the SearchBase by using the basic Get-ADUser as long as I included the credentials.

How to effectively use the `-Filter` parameter on Active Directory cmdlets?

All too often I see the following type of code on this site, specific to the AD cmdlets:
Get-ADUser -Filter * | Where-Object { $_.EmailAddress -eq $email }
The problem is that you are returning every single user object in Active Directory, and then processing it a second time. How can we improve upon this, not only to reduce the time it takes to run the script, but to also take the unnecessary load off of Active Directory, and possibly the network?
Note about Azure AD cmdlets
This answer is crafted around the Active Directory cmdlets installed and available from Remote Server Administration Tools (RSAT). However, the Azure AD cmdlets make use of Microsoft Graph (OData v4.0 specification) to run queries against Azure AD while the RSAT cmdlets[1] rely on an implementation of the PowerShell Expression Engine intended to replace LDAP filters.
As such, the filter examples below will not work with Azure AD cmdlets without some modification to be compatible with the Microsoft Graph specification, specifically, its filter syntax. However, the general practices mentioned here should still apply.
[1] - This is the most recent version of this document I could find.
What is so bad about -Filter *?
You are effectively selecting and returning every object that exists in AD, based on the cmdlet you are using (e.g. Get-ADUser, Get-ADComputer, Get-ADGroup, the generic Get-ADObject, etc.). This is an expensive thing to do, especially in larger AD environments. In large enough environments you will want to split up your queries in batches, even if you legitimately need to operate on every AD object of a given type. On top of this, your script will end up processing far more data than it needs to, increasing execution time and used processing time when it just isn't necessary.
The -Filter parameter can do more than just match on everything, which is effectively what -Filter * does. The -Filter string is very much like Powershell syntax (not quite, but most of the way there). You can use most of the same logical operators that Powershell supports, and they work much in the same way that Powershell operators do. This answer aims to clarify this and explain how to use this elusive parameter. These examples will use the Get-ADUser cmdlets but this also extends to the other Get-ADObject cmdlets which use filters as well.
Syntax
The syntax for the -Filter string is "PropertyName -comparisonoperator 'somevalue'", though you can string multiple conditions together with logical operators such as -and and -or. Note that there are no regex matching operators, so you will have to make do with -like and-notlike globbing.
Comparison Operators
MS calls these FilterOperators but they are used in the same way as
PowerShell's comparison operators are (ignoring the fact that technically -bor and -band are arithmetic operators). These are used for comparing values:
Note: AD attributes in DistinguishedName format will not have globbing applied when-like or -notlike are used, in other words you have to look for an exact match. If you need a DN to match any pattern, this cannot be performed with -Filter or-LDAPFilter. You will have to -Filter where you can, and perform additional processing with the -like or -match operators once your Get-ADObject cmdlet returns.
-eq, -le, -ge, -ne, -lt, -gt, -approx, -bor, -band, -recursivematch, -like, -notlike
The only ones which are unique to the -Filter query syntax are -approx and-recursivematch. Don't worry about -approx, it is functionally equivalent to -eq in Active Directory.
Despite its name, -recursivematch is not a regex matching operator, it works like PowerShell's-contains operator in that it will return $true if the collection contains the target value.
Logical Operators
MS calls these JoinOperators but they fill the same role as their PowerShell logical operator equivalent. These are used to join multiple conditions together in a single query:
-and, -or
Strangely enough, MS gives negation to a special operator type called NotOperator, which consists of a single operator:
-not
Matching on a property
To use the example in the question, let's find a user matching an email address, but without piping to Where-Object (crazy right???):
$email = 'box#domain.tld'
Get-ADUser -Filter "EmailAddress -eq '${email}'"
Done. Get-ADUser will return any accounts where the EmailAddress property equals whatever the $email variable is.
What if we want to find all user accounts that haven't been logged onto in the last 30 days? But a date string is more complex than an email! Who cares, still pretty simple!
# Get the date from 30 days ago
$notUsedSince = ( Get-Date ).AddDays( -30 )
Get-ADUser -Filter "LastLogonDate -lt '${notUsedSince}'"
This returns all users who have not logged on in the last 30 days.
Getting users who are members of a group
If you want to get all ADUsers who are members of a certain group, we can make use of the-recursivematch operator for this:
Get-ADUser -Filter "memberOf -recursivematch 'CN=test_group,CN=Users,DC=exampledomain,DC=net'"
memberOf is an array of Distinguished Names, -recursivematch returns true if the array on the lefthand side contains the value on the righthand side.
You can alternatively avoid the use of Get-ADUser at all in this scenario and use Get-ADGroup to retrieve the members from that:
( Get-ADGroup group_name -Properties Members ).Members
While the Get-ADGroup example above is shorter to type, filtering on memberOf withGet-ADUser can be effective when you have multiple conditions and need to return users that are members of a group, but not necessarily need to return the group for local processing. It may be interactively inconvenient but it is a worthy technique in any automated process integrating with Active Directory and may become necessary in cases where you have extremely large groups.
One example is when enumerating Domain Users in a very large domain. You might want to rethink returning 32,000 users from ( Get-ADGroup ).Members to then have to apply additional filtering.
Note: Most users will actually have Domain Users set as their PrimaryGroup. This is the default and most times this doesn't need to be changed. However, you must use-Filter on PrimaryGroup instead as the PrimaryGroup is not stored under MemberOf for an ADUser. It is also a single value, not a collection, so use -eq:
Get-ADUser -Filter "PrimaryGroup -eq 'PRIMARY_GROUP_DN'"
What if the query term contains a quote?
Quotes in the query term will throw a wrench in your query in most cases. Consider the example of searching for users with O'Niel in the name. This can break either the query or your script logic depending on the quoting technique used:
# Our heroic search term
$term = "O'Niel"
# Dragons abound (results in a query parsing error)
Get-ADUser -Filter "Name -like '*${term}*'"
# Your princess is in another castle ($term is not expanded
# and the literal string ${term} is instead searched for)
Get-ADUser -Filter 'Name -like "*${term}*"'
In this case you will have to use double-quoted strings in both places, but fortunately the escape-hell isn't too bad. Using the same value for $term as before:
# Your quest is over (this works as intended and returns users named O'Niel)
Get-ADUser -Filter "Name -like ""*${term}*"""
# Backticks are ugly but this also works
Get-ADUser -Filter "Name -like `"*${term}*`""
Note: If your query looks for a field value which contains both single and double quotes, I'm not sure how to facilitate this with one command when using the -Filter parameter. However, -LDAPFilter should be able to facilitate this, as parentheses (), not quotes, are used for the internal query bounds. See the Filter Examples in about_ActiveDirectory_Filter and the LDAP Filters section of this AD Escape Characters post for more information, as -LDAPFilter is beyond the scope of this answer.
Matching on multiple properties
Matching on multiple properties is not much different, but it's best to wrap each condition in parentheses (). Here's an example, let's find non-domain admin accounts (assuming we know this by the username nomenclature *-da) that don't have an email address associated with them.
Get-ADUser -Filter "(samaccountname -notlike '*-da') -and (EmailAddress -notlike '*')"
This one is a little trickier, because we can't pass in an empty value for the right side of a condition in the -Filter, as in the case for EmailAddress. But '*' matches any non-empty value, so we can leverage that behavior with the -notlike comparison operator to find empty values for EmailAddress. To break down the filter, make sure that any accounts ending in -da aren't matched by the filter, and then also only match accounts that do not have an EmailAddress value.
Things to avoid
Don't try to use a { ScriptBlock } for your filter parameters. Yes, we are all more comfortable with writing a ScriptBlock than worrying about building a string and making sure it's escaped properly. There is definitely an attraction to using them. I've seen so many answers using a ScriptBlock as a -Filter argument, or people having problems (myself included) trying to do something like this, and SURPRISE!!! Nothing gets returned:
Import-Csv C:\userInfoWithEmails.csv | Foreach-Object {
Get-ADUser -Filter { EmailAddress -eq $_.Email }
}
-Filter doesn't support ScriptBlocks, but they Kind of Work Sometimes™ because while they get rendered as a literal string, the PowerShell Expression Engine used by -Filter is capable of rendering your variables before running the query. Because of this, they will technically work if you use simple variable expansion like $_ or $emailAddress, but it will eventually cause you a headache, especially if you try to access an object property (like above) because it simply won't work.
In addition, you get largely undocumented (or difficult to locate info) behavior for how these variables are expanded, because they are not always ToString'd like you would expect. Figuring it out becomes a trial-and-error affair. Some attributes are admittedly easier to obtain this way, but using techniques you don't understand and which have little documentation is a risky move when programming. Because of this, I don't relying on the cmdlet-internal variable expansion that occurs whether you use a literal string or a ScriptBlock with the AD cmdlets.
Use a string filter every time, and if you need to use a variable value or object property as a portion of the filter, use Variable Substitution or Command Substitution.
You do not need to specify additional -Properties if you only care about a property to filter on it. The AD cmdlets can evaluate all properties within the -Filter parameter without needing to pass them down the pipeline.
And while I'm at it, don't ever use -Properties *, excepting maybe if you are inspecting all properties on a returned object for some reason, such as during script development, or interactively you're not quite sure what you're looking for (note that not all attributes are returned by default).Only specify the properties you need to process after the AD object has been returned. There is a reason for this - some properties are particularly expensive to get the values for. Best practice is to only forward the properties you need to process down the pipeline.
You cannot use the -Filter parameter to filter on Constructed Attributes using either-Filter or -LDAPFilter. This is because constructed attributes are, by definition, computed (or "constructed") on the fly, and are not actually stored values within Active Directory. I imagine it's because many Computed Attributes are expensive to compute, which would have to be performed on every relevant ADObject to filter on it from the AD side.
If you need to filter on Constructed Attributes, you will need to first return a set of ADObjects, specifying the Computed Attribute with -Properties, then further filter with Where-Object or some other technique.
Wildcards * do not work for fields that return a DistinguishedName type, such as DistinguishedName, manager, PrimaryGroup, etc. In addition, DistinguishedNames carry their own set of escaping rules.
Some AD attributes are returned as a proper DateTime for easier processing in PowerShell, but the basic time comparison exemplified above requires the underlying ADAttribute to be defined as the Interval type. Some time-based properties, such as whenCreated are defined as Generalized-Time strings, which are UTC timezone and formatted as yyyMMddHHmmss.Z. Additionally, some properties likemsDS-UserPasswordExpiryTimeComputed are in file-time format (and is returned as such with the AD cmdlets).
Convert a target DateTime for filtering to the Generalized-Time string format like so:
( Get-Date ).ToUniversalTime().ToString('yyyMMddHHmmss.z').
Note that this string is not directly convertible back to a DateTime.
Convert a returned file-time to a DateTime like so (using the aforementioned property as an example):
[DateTime]::FromFileTime($adUser.'msDS-UserPasswordExpiryTimeComputed')
In summarium
These techniques using the -Filter parameter with the AD cmdlets will save you costly processing time when iterating over large AD environments, and should improve the performance of your Powershell AD operations. I hope this helps explain some of the elusive behaviors of the AD cmdlets' -Filter parameter.
Additional Resources
As it is a good idea to understand the AD attributes you are working with, below are some Microsoft resources to help you identify and understand how different attributes are defined and function within the AD schema, as well as learn more about the -Filter syntax:
OpenSpecs
AD Schema
about_ActiveDirectory_Filter
While outdated, this document help is still accurate. Due to a bug it has not been updated since 2013. Until this bug is fixed and the documentation has been updated, the above link will have to suffice.

Get Memberships Of User

I have a very simple question but for some reason I can't seem to get my head around it.
I need a line of code that could be ran as a user from a client and lists all the "memeber of" groups from the AD (ONLY FOR THIS CURRENT USER). similar to
Get-ADGroupMember -identity "domain admins" -Recursive | foreach{ get-aduser $_} | select SamAccountName,objectclass,name
I would like the result to be listed.
I either need a way to import the AD module on a client computer or another way to contact the DC and get the users current "memeber of" groups.
/Niklas
I found the best way for my needs but CB.'s answer worked as well!
[ADSISEARCHER]"samaccountname=$($env:USERNAME)").Findone().Properties.memberof -replace '^CN=([^,]+).+$','$1'
I can then keep using this output in my code
you can use dos command line:
net user /domain %username%
The easiest way to do this would be with
Get-ADPrincipalGroupMembership -identity "Username"
Now this also means that you would have to have the active directory module loaded which you can find more information on its use on Technet Get-ADPrincipalGroupMember
If you simply want to produce a list, make a call to the command prompt as I find this works well, although it does truncate group names:
net user %username% /DOMAIN
If you want to programmatically get them and easily do something with that data, you'll want to rely on the Active Directory cmdlets.
To determine if you have these readily available in Powershell, you'll need to run the following command:
Get-Module –ListAvailable
If you don't see ActiveDirectory in the list you will need to first download and install the Windows Management Framework and import the module yourself:
Import-Module ActiveDirectory
Once that's done I believe this command should do the trick:
(Get-ADUser userName –Properties MemberOf | Select-Object MemberOf).MemberOf
Hopefully that gets you started. I'm fairly certain that there's more than one way to accomplish this with Powershell. Take a look at the Microsoft TechNet documentation to see if you can find something that better suits your needs.
Personally I have only ever needed to query AD group memberships ad-hoc for diagnostic purposes and have always relied on Get-ADUser or the command line call, depending on the target audience of the resulting data.