The most popular way to manage Active Directory with PowerShell is to use the cmdlets provided by the PowerShell module ActiveDirectory. Most of you probably know some of these cmdlets and have already used them quite a bit. If so, you probably also know that this module is only available after you install the Active Directory RSAT tools on your machine, or when PowerShell is run directly on a Domain Controller.
But: There’s another way to manage Active Directory with PowerShell.
The series “.NET Assemblies In PowerShell” is about how to use .Net assemblies in PowerShell for daily business tasks, instead of predefined modules and cmdlets.
In the first part, I want to show you how you can add and remove users from Active Directory groups with PowerShell from any Windows machine, without using the ActiveDirectory module. Those of you who don’t like to read a lot and are more interested in some code snippets can just jump to the section As a function further down in this article. There, you will find a ready-to-use function for adding members to a group.
The namespace
First of all, we need the .Net Framework namespace System.DirectoryServices.AccountManagement. This namespace provides management capabilities for directory objects like users, computers and groups. The required assembly is available on any Windows machine by default, no matter if client or server OS.
To make the classes and functionalities of a namespace available in PowerShell, you can simply add it with the Add-Type cmdlet. In this case, the command would be the following:
Add-Type -AssemblyName System.DirectoryServices.AccountManagement
The PrincipalContext object
Alright, we got the namespace. How do we proceed?
Through adding the namespace, we now have some new classes available for creating objects. One of them is the PrincipalContext class. We will use this one for the first object that we need to create. Here, we can specify the domain we want to operate in, the credentials and we could even provide a specific OU as a base for our operations. To create the object, we do as follows:
#store credentials (of account with appropriate permissions) $creds = Get-Credential #set the domain name $dn = 'contoso.com' #Create the principal context object (so to say connect to a domain with the credentials provided) $pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::` Domain,$dn,$($creds.UserName),$($creds.GetNetworkCredential().Password))
The group object
Next thing we need to do is to create an object for the group that we want to manage. For this, the namespace offers us the class System.DirectoryServices.AccountManagement.GroupPrincipal. During creation, we basically search the group in AD and load it into an object. Here’s how to do it:
#set the group name (SamAccount) $groupname = "test-group001" #load the group from AD / create the group object $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc,$groupname)
Now, we have the domain and the group. So, how do we add users to this group?
Add members
This time, we don’t have to create an object as well, for the member we want to add. We only need to specify by which identity type it will be identified (e.g. SAMAccountName, Distinguished Name, User Principal Name, etc.) and the actual identity of the new member. We then pass this info together with the PricipalContext object to the Add method of the group object’s Members property.
#Set the identifier type to "SamAccountName" $IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName #define a user's SAMAccountName $user = "test001" #Add the user to the group object $group.Members.Add($pc,$IDType,$user)
Lastly, we have to save the group object, so that the changes will be provisioned to the Active Directory.
#save the object to AD $group.Save()
Remove members
To remove a member from a group you can simply use the Remove method instead of the Add method.
#Remove the user from the group object $group.Members.Remove($pc,$IDType,$user)
All together now
Here’s a complete example of how you can add Active Directory user accounts to a group and how to remove them again.
#Load the assembly $am = Add-Type -AssemblyName System.DirectoryServices.AccountManagement #store credentials (of account which can modify AD groups) $creds = Get-Credential #set domain name $dn = 'contoso.com' #Create the principal context object (so to say connect to the domain with the credentials provided) $pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::` Domain,$dn,$($creds.UserName),$($creds.GetNetworkCredential().Password)) #set the group name (SamAccount) $groupname = "group1" #set the user name to add (SamAccount) $user = "user1" #load the group from AD $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc,$groupname) #Set the identifier type to "SamAccountName" $IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName #Add the user to the group object $group.Members.Add($pc,$IDType,$user) #save the object to AD $group.Save() #Remove the user from the group object $group.Members.Remove($pc,$IDType,$user) #save the object to AD $group.Save()
Yeah, I know. That’s a whole lot of code for such a simple task. But we can simplify things…
If you want to run the tasks in the current domain and you also want to use your current user for it, you can simply leave out the domain name and the credentials in the PrincipalContext constructor. This is how the constructor would then look like:
[System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::Domain)
By this, the number of necessary lines can be reduced to the following:
#Load the assembly $am = Add-Type -AssemblyName System.DirectoryServices.AccountManagement #Create the principal context object (current domain with current user) $pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::` Domain) #set the group name (SamAccount) $groupname = "group1" #set the user name to add (SamAccount) $user = "user1" #load the group from AD $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc,$groupname) #Set the identifier type to "SamAccountName" $IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName #Add the user to the group object $group.Members.Add($pc,$IDType,$user) #save the object to AD $group.Save() #Remove the user from the group object $group.Members.remove($pc,$IDType,$user) #save the object to AD $group.Save()
Sure, it’s still a lot. It’s also a lot more coding effort than to just type Add-ADGroupMember -Identity group1 -Members user1. The advantage, as mentioned in the beginning, is that you can run this on any Windows machine without having the AD RSAT tools installed.
As a function
If you want to make things a little more convenient for you, just create a function around the code. Here’s a function I wrote to add members to a group.
function cAdd-ADGroupMembers($groupname, [STRING[]]$members) { #load the assembly (will not throw any error if already loaded) $am = Add-Type -AssemblyName System.DirectoryServices.AccountManagement #create the PrincipalContext object $pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::` Domain) #Set the identifier type to "SamAccountName" $IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName #find and load the group from AD $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc, $groupname) #Check if group exists if ($group -eq $null) { Write-Error "Group was not found in Active Directory" -Category ObjectNotFound -ErrorId "System.DirectoryServices.AccountManagement.ObjectNotFound" Return } #add each specified member to the group object foreach ($member in $members) { #Check if object is already a member of the group if (($group.Members.Contains($pc, $IDType, $member))) { Write-Warning "Object ""$member"" is already a member of this group" } Else { #add the new member try { $group.Members.Add($pc, $IDType, $member) Write-Host "Added $member" } catch { write-error "Could not add object ""$member"".`n $($_.exception.message)" -ErrorId $_.FullyQualifiedErrorId } } } #save the group $group.Save() #clean up $group.Dispose() $pc.Dispose() Return "Done" }
The function basically works like the Add-ADGroupMember cmdlet. Specify the group name in the “groupname” parameter and the SAMAccountName(s) of the member(s) you want to add in the “members” parameter. Multiple members can be provided by using a comma (“,”) as delimiter. Here’s an example how to add two users to a group, by using the function:
PS C:\> cAdd-ADGroupMembers -groupname test-group001 -members test001,test0500
During the execution it will show the result for each member you want to add:
PS C:\> cAdd-ADGroupMembers -groupname test-group001 -members test001,test0500 Added test001 Added test0500 Done
List group members
To list all group members, you just need to access the Members property of the group object.
PS C:\Users> $group.Members GivenName : Test MiddleName : Surname : 001 EmailAddress : [email protected] VoiceTelephoneNumber : EmployeeId : AdvancedSearchFilter : System.DirectoryServices.AccountManagement.AdvancedFilters Enabled : True AccountLockoutTime : LastLogon : 02.05.2018 11:52:56 PermittedWorkstations : {} PermittedLogonTimes : {255, 255, 255, 255...} AccountExpirationDate : SmartcardLogonRequired : False DelegationPermitted : True BadLogonCount : 0 HomeDirectory : \\FileServer01\TEST001$ HomeDrive : H: ScriptPath : LastPasswordSet : LastBadPasswordAttempt : 02.05.2018 11:51:39 PasswordNotRequired : False PasswordNeverExpires : True UserCannotChangePassword : False AllowReversiblePasswordEncryption : False Certificates : {} Context : System.DirectoryServices.AccountManagement.PrincipalContext ContextType : Domain Description : 001 Test DisplayName : 001 Test SamAccountName : TEST001 UserPrincipalName : [email protected] Sid : S-1-5-21-4025995969-482537669-3923321509-3972 Guid : de16cf7f-79c4-4257-9de7-adb687ba5fe7 DistinguishedName : CN=001 Test,OU=Standard,OU=Users,DC=lab,DC=com StructuralObjectClass : user Name : 001 Test
Speed
I had the feeling that these operations are executed a little faster than through the cmdlets of the Active Directory module. So, I measured the execution time with the Measure-Command cmdlet. Here is the result:
#Adding a user to a group using the Add-ADGroupMember cmdlet from the ActiveDirectory module PS C:\> measure-command {Add-ADGroupMember -Identity test-group001 -Members test0500} Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 70 Ticks : 702174 TotalDays : 8.12701388888889E-07 TotalHours : 1.95048333333333E-05 TotalMinutes : 0.00117029 TotalSeconds : 0.0702174 TotalMilliseconds : 70.2174 #Same with only using the .Net namespace PS C:\> Measure-Command {$group.Members.Add($pc, $IDType, "test0500");$group.save()} Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 45 Ticks : 453936 TotalDays : 5.25388888888889E-07 TotalHours : 1.26093333333333E-05 TotalMinutes : 0.00075656 TotalSeconds : 0.0453936 TotalMilliseconds : 45.3936
As we can see, the actual execution is a couple of milliseconds faster than by using the Add-ADGroupMember cmdlet. Although, if you would measure the whole function I provided before, the Add-ADGroupMember cmdlet still wins the race.
Further info
The “System.DirectoryServices.AccountManagement” namespace
The Add-Type cmdlet
How to work with .Net classes in PowerShell
In my second part of this series, I will write more about working with Active Directory group members and with Active Directory users in general. So, stay tuned 😉
Photo by Blake Guidry on Unsplash