.NET Assemblies In PowerShell – Part 1: Manage Active Directory groups

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                      : Test.001@lab.com
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                 : Test.001@lab.com
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 PrincipalContext class

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


Leave a Reply

Your email address will not be published. Required fields are marked *