In the first part of this series, I described how you can add and remove members to and from Active Directory groups in PowerShell, without using the ActiveDirectory module, but just by using the .Net namespace System.DirectoryServices.AccountManagement.
In this part I want to show how you can work with the members of a group, as well as with Active Directory accounts in general using this namespace in PowerShell. For the prerequisites, please check out the first part of this series.
The members property
In the last article, we created a GroupPrincipal object for an Active Directory group. One cool thing about this object type: you can easily work with any of the group members by accessing the Members property (type PrincipalCollection) of the group object.
By calling the Members property, a list of all group members will be shown, in which any group member is presented as a PrincipalContext object.
In the following example, we’re accessing the Members property of a group that contains two user accounts.
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 GivenName : TEST0500 MiddleName : Surname : TestAccount EmailAddress : [email protected] VoiceTelephoneNumber : EmployeeId : AdvancedSearchFilter : System.DirectoryServices.AccountManagement.AdvancedFilters Enabled : True AccountLockoutTime : LastLogon : PermittedWorkstations : {} PermittedLogonTimes : {255, 255, 255, 255...} AccountExpirationDate : SmartcardLogonRequired : False DelegationPermitted : True BadLogonCount : 0 HomeDirectory : HomeDrive : ScriptPath : LastPasswordSet : LastBadPasswordAttempt : PasswordNotRequired : False PasswordNeverExpires : False UserCannotChangePassword : False AllowReversiblePasswordEncryption : False Certificates : {} Context : System.DirectoryServices.AccountManagement.PrincipalContext ContextType : Domain Description : DisplayName : TestAccount TEST0500 SamAccountName : TEST0500 UserPrincipalName : Sid : S-1-5-21-4025995969-482537669-3923321509-16697 Guid : 45e85f63-3d47-452f-b071-0572f423aa99 DistinguishedName : CN=TestAccount TEST0500,OU=Standard,OU=Users,DC=lab,DC=com StructuralObjectClass : user Name : TestAccount TEST0500
Well, this is really nice. First, we see a lot more of the members’ attributes than the Get-ADGroupMember cmdlet provides and second, we can directly work with these objects from here on. In my opinion, this is a great advantage over the ActiveDirectory PowerShell module.
The group members
Through the Members property we are not only able to list and manage who the members of a group are, we can even directly modify some attributes of the members (all attributes that are properties of the UserPrincipal class).
This could make some daily business tasks way easier for us.
For example: Let’s say you want to change the description for all members of a specific Active Directory group to “Test account”. Using the PowerShell ActiveDirectory module cmdlets, you would have to do something like this:
- Run the Get-ADGroupMembers cmdlet.
2. Iterate through the result.
3. Pass each item of the result to the Set-ADUser cmdlet.
Of course, you could also just pipe the result of the Get-ADGroupMember cmdlet to Set-ADUser.
Using the System.DirectoryServices.AccountManagement namespace, the steps would be the following:
First, let’s check the the current description of the group members (optional):
PS C:\Users> $group.Members | select SAMAccountName,Description SamAccountName Description -------------- ----------- TEST001 Something TEST0500 Something else
Now, let’s change it (this is actually all we need to do):
foreach ($user in $group.Members) { #Set the new description $user.Description = "Test account" #Save the user to the Active Directory $user.Save() }
There will be no confirmation if the task was successful. In this case, no news is good news.
Finally, if we want to verify the effects of our command, we can just run the first command again (optional):
PS C:\Users> $group.Members | select SAMAccountName,Description SamAccountName Description -------------- ----------- TEST001 Test account TEST0500 Test account
As we can see, the description of all group members was successfully changed to “Test account”. Pretty cool, huh?
Hint: Instead of using a For Each loop, we can even save some lines by calling the ForEach method of the Members property. See the following example:
$group.Members.ForEach({$_.Description = "Test account"}) $group.Members.ForEach({$_.Save()})
Speed
Again, I was curious about how the execution times would compete against the cmdlets of the ActiveDirectory module. Here are the results of my measuring:
#Using "Get-ADGroupMember" and "Set-ADUser" PS C:\> Measure-command {Get-ADGroupMember test-group001 |%{Set-ADUser $_ -Description "Test account"}} Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 213 Ticks : 2131780 TotalDays : 2.46733796296296E-06 TotalHours : 5.92161111111111E-05 TotalMinutes : 0.00355296666666667 TotalSeconds : 0.213178 TotalMilliseconds : 213.178 #Using the "Members" property and a for each loop PS C:\> Measure-Command {$group.Members |%{$_.Description = "Test account";$_.Save()}} Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 69 Ticks : 695725 TotalDays : 8.05237268518518E-07 TotalHours : 1.93256944444444E-05 TotalMinutes : 0.00115954166666667 TotalSeconds : 0.0695725 TotalMilliseconds : 69.5725 #Using the "Members" property and the "ForEach" method of the group object PS C:\> Measure-command {$group.Members.ForEach({$_.Description = "Test account"});$group.Members.ForEach({$_.Save()})} Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 41 Ticks : 414158 TotalDays : 4.79349537037037E-07 TotalHours : 1.15043888888889E-05 TotalMinutes : 0.000690263333333333 TotalSeconds : 0.0414158 TotalMilliseconds : 41.4158
Sure, we already had the group object created before.
Ok, then let’s see how the creation of the group object will affect the execution time:
#Create the group object first #then run the command PS C:\> Measure-Command { $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc, "test-group001") $group.Members |% {$_.Description = "Test account";$_.Save()} } Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 67 Ticks : 678370 TotalDays : 7.85150462962963E-07 TotalHours : 1.88436111111111E-05 TotalMinutes : 0.00113061666666667 TotalSeconds : 0.067837 TotalMilliseconds : 67.837 #Create all necessary objects and add the assembly PS C:\> Measure-Command { $am = Add-Type -AssemblyName System.DirectoryServices.AccountManagement $pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::` Domain) $IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc, "test-group001") $group.Members |% {$_.Description = "Test account";$_.Save()} } Days : 0 Hours : 0 Minutes : 0 Seconds : 0 Milliseconds : 97 Ticks : 979199 TotalDays : 1.13333217592593E-06 TotalHours : 2.71999722222222E-05 TotalMinutes : 0.00163199833333333 TotalSeconds : 0.0979199 TotalMilliseconds : 97.9199
And…we have a winner. Even including all necessary code, we were still more than double as fast as the cmdlets of the ActiveDirectory module are.
The UserPrincipal object
Alright. Now, we know how to modify attributes of users that are members of a specific group. But, how do we work with an Active Directory user directly?
For this, we first need to create an object for the user which we want to manage.
This is done in the same way as for a group, except that we’re now using the UserPrincipal class of the System.DirectoryServices.AccountManagement namespace, instead of the GroupPrincipal class. Following, an example how to create such an object.
#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 username (SAMAccountName) $username = "TEST001" #load the user from AD / create the user object $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($pc,$username)
And, here’s our user:
PS C:\> $user GivenName : Test MiddleName : Surname : 001 EmailAddress : [email protected] VoiceTelephoneNumber : EmployeeId : AdvancedSearchFilter : System.DirectoryServices.AccountManagement.AdvancedFilters Enabled : False AccountLockoutTime : LastLogon : 02.05.2018 11:52:56 PermittedWorkstations : {} PermittedLogonTimes : {255, 255, 255, 255...} AccountExpirationDate : SmartcardLogonRequired : False DelegationPermitted : True BadLogonCount : 0 HomeDirectory : \\fileserver1\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 : Test account 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=Users,DC=lab,DC=com StructuralObjectClass : user Name : 001 Test
Once created, we can access any of the UserPrincipal object’s properties and methods by using the PowerShell standard “.” notation.
Here’s an example how you can display only the DisplayName of the user object:
PS C:\> $user.DisplayName 001 Test
And here’s how you can change it:
#Set a new display name PS C:\> $user.DisplayName = "001 Test / msb365" #save the user object to Active Directory PS C:\> $user.Save()
Display the new value:
#Do a fresh load of the user from AD PS C:\> $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($pc,$username) #Show the DisplayName property PS C:\> $user.DisplayName 001 Test / msb365
Keep in mind: For any changes of properties to take effect, the object has to be saved by using the Save method.
MemberOf
To list all Active Directory groups that a user is a member of, the UserPrincipal object provides the methods: GetGroups and GetAuthorizationGroups.
For a listing of groups of which the user is a direct member, we can use the GetGroups method, as shown below:
PS C:\> $user.GetGroups() IsSecurityGroup : True GroupScope : Global Members : {Administrator, DefaultAccount, AdminIIEM, krbtgt...} Context : System.DirectoryServices.AccountManagement.PrincipalContext ContextType : Domain Description : All domain users DisplayName : SamAccountName : Domain Users UserPrincipalName : Sid : S-1-5-21-4025995969-482537669-3923321509-513 Guid : 0c8e9be6-0271-4c93-a670-8a081a87406b DistinguishedName : CN=Domain Users,CN=Users,DC=iamlab,DC=net StructuralObjectClass : group Name : Domain Users IsSecurityGroup : True GroupScope : Global Members : {001 Test, TEstACCName TEST0500} Context : System.DirectoryServices.AccountManagement.PrincipalContext ContextType : Domain Description : DisplayName : SamAccountName : test-group001 UserPrincipalName : Sid : S-1-5-21-4025995969-482537669-3923321509-16717 Guid : 924c1135-80c0-4e29-aeae-b6ff5f42c9bf DistinguishedName : CN=test-group001,OU=GROUPS,DC=iamlab,DC=net StructuralObjectClass : group Name : test-group001
We see that the user is a member of the two groups “Domain Users” and “test-group001”. To make the output more readable, we can use the dot notation to show only the SAMAccountNames of those groups:
PS C:\> $user.GetGroups().samaccountname Domain Users test-group001
We can also include indirect memberships; groups in which the user is member of by group nesting.
For this, we need to use the GetAuthorizationGroups method. See the example below:
PS C:\> $user.GetAuthorizationGroups().samaccountname Everyone Authenticated UsersDomain Users test-group001 Users
Now, the list contains some more groups. The additional groups are groups of which the user is a indirect member of. For example, we see the built-in group “Users” here, because the user is a member of the “Domain Users” group, which again is a member of the group “Users”.
Other useful methods
The UserPrincipal object provides some more pretty useful methods for daily business tasks on Active Directory user accounts. Following, there’s a brief description of some of them.
Check if an account is a member of a specific group
For this, we first need to create a GroupPrincipal object for the Active Directory group that we want to check. Once we’ve created it, we can check if the user is a member of that group, by calling the IsMemberOf method of the UserPrincipal object and passing the GroupPrincipal as an overload. Return values are “True”, or “False” (type Boolean).
#create the group object PS C:\> $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc, "test-group001") #Call the MemberOf method with the group object as overload PS C:\> $user.IsMemberOf($group) True
Check if an account is locked out
Return values: “True”, or “False” (type Boolean).
PS C:\> $user.IsAccountLockedOut() False
Unlock an account
PS C:\> $user.UnlockAccount()
Refresh an expired password
PS C:\> $user.RefreshExpiredPassword()
Set a user’s password to expired
PS C:\> $user.ExpirePasswordNow()
Set a new password
PS C:\> $user.SetPassword('NewP@$$w0rd')
Delete a user account
Careful! There will be no confirmation question, or second approval option. It just deletes the account right away.
PS C:\> $user.Delete()
Further info
The “System.DirectoryServices.AccountManagement” namespace
The Add-Type cmdlet
How to work with .Net classes in PowerShell
In the next parts of this series, I will focus on creating Active Directory objects. So, stay tuned 😉