{"id":1776,"date":"2019-02-19T09:05:46","date_gmt":"2019-02-19T07:05:46","guid":{"rendered":"https:\/\/msb365.abstergo.ch\/?p=1776"},"modified":"2019-02-19T09:05:46","modified_gmt":"2019-02-19T07:05:46","slug":"net-assemblies-in-powershell-part-1-manage-active-directory-groups","status":"publish","type":"post","link":"https:\/\/www.msb365.blog\/?p=1776","title":{"rendered":".NET Assemblies In PowerShell &#8211; Part 1: Manage Active Directory groups"},"content":{"rendered":"<p>The most popular way to manage Active Directory with PowerShell is to use the cmdlets provided by the PowerShell module <em>ActiveDirectory<\/em>. 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.<\/p>\n<p>But: There\u2019s another way to manage Active Directory with PowerShell.\u00a0<\/p>\n<p>The series\u00a0\u201c.NET Assemblies In PowerShell\u201d is about how to use .Net assemblies in\u00a0PowerShell for daily business tasks, instead of predefined modules and cmdlets.<\/p>\n<p>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 <em>ActiveDirectory<\/em> module. Those of\u00a0 you who don\u2019t like to read a lot and are more interested in some code snippets can just jump to the section <a href=\"#TheFunction\"><strong>As a function<\/strong><\/a> further down in this article. There, you will find a ready-to-use function for adding members to a group.<\/p>\n<p>\u00a0<\/p>\n<h4>The namespace<\/h4>\n<p>First of all, we need the .Net Framework namespace <em>System.DirectoryServices.AccountManagement<\/em>. This namespace provides management capabilities for directory objects like users, computers and groups.\u00a0The required assembly is <strong>available on any Windows machine by default<\/strong>, no matter if client or server OS.<\/p>\n<p>To make the classes and functionalities of a namespace available in PowerShell, you can simply add it with the <em>Add-Type<\/em>\u00a0cmdlet. In this case, the command would be the following:<\/p>\n<pre class=\"theme:powershell-ise toolbar:2 toolbar-overlay:false nums:false nums-toggle:false lang:ps decode:true \" title=\"Add assembly\">Add-Type -AssemblyName System.DirectoryServices.AccountManagement<\/pre>\n<p>\u00a0<\/p>\n<h4>The PrincipalContext object<\/h4>\n<p>Alright, we got the namespace. How do we proceed?<\/p>\n<p>Through adding the namespace, we now have some new classes available for creating objects. One of them is the\u00a0<em><a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.directoryservices.accountmanagement.principalcontext?view=netframework-4.7.2\" target=\"_blank\" rel=\"noopener\">PrincipalContext\u00a0<\/a><\/em>class<em>.<\/em> 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:<\/p>\n<pre class=\"theme:powershell-ise toolbar-overlay:false nums:false lang:ps decode:true\">#store credentials (of account with appropriate permissions)\r\n$creds = Get-Credential \r\n#set the domain name\r\n$dn = 'contoso.com' \r\n\r\n#Create the principal context object (so to say connect to a domain with the credentials provided)\r\n$pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::`\r\nDomain,$dn,$($creds.UserName),$($creds.GetNetworkCredential().Password))\r\n<\/pre>\n<p>\u00a0<\/p>\n<h4>The group object<\/h4>\n<p>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\u00a0\u00a0<em>System.DirectoryServices.AccountManagement.GroupPrincipal.<\/em> During creation, we basically search the group in AD and load it into an object. Here\u2019s how to do it:<\/p>\n<pre class=\"theme:powershell-ise toolbar-overlay:false nums:false lang:ps decode:true\">#set the group name (SamAccount)\r\n$groupname = \"test-group001\"\r\n\r\n#load the group from AD \/ create the group object\r\n$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc,$groupname)<\/pre>\n<p>Now, we have the domain and the group. So, how do we add users to this group?<\/p>\n<p>\u00a0<\/p>\n<h4>Add members<\/h4>\n<p>This time, we don\u2019t 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,\u00a0User Principal Name, etc.) and the actual identity of the new member. We then pass this info together with the <em>PricipalContext<\/em> object to the <em>Add<\/em> method of the group object\u2019s <em>Members<\/em> property.<\/p>\n<pre class=\"theme:powershell-ise nums:false lang:ps decode:true\">#Set the identifier type to \"SamAccountName\"\r\n$IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName\r\n\r\n#define a user's SAMAccountName\r\n$user = \"test001\" \r\n\r\n#Add the user to the group object\r\n$group.Members.Add($pc,$IDType,$user)<\/pre>\n<p>Lastly, we have to save the group object, so that the changes will be provisioned to the Active Directory.<\/p>\n<pre class=\"theme:powershell-ise nums:false lang:ps decode:true\">#save the object to AD\r\n$group.Save()<\/pre>\n<p>\u00a0<\/p>\n<h4>Remove members<\/h4>\n<p>To remove a member from a group you can simply use the <em>Remove <\/em>method instead of the\u00a0<em>Add<\/em> method<em>.<\/em><\/p>\n<pre class=\"theme:powershell-ise nums:false lang:ps decode:true\">#Remove the user from the group object\r\n$group.Members.Remove($pc,$IDType,$user)\r\n<\/pre>\n<p>\u00a0<\/p>\n<h4>All together now<\/h4>\n<p>Here\u2019s a complete example of how you can add Active Directory user accounts to a group and how to remove them again.<\/p>\n<pre class=\"theme:powershell-ise toolbar-overlay:false toolbar-delay:false lang:ps decode:true\" title=\"Manage AD group membership with .Net\">#Load the assembly\r\n$am = Add-Type -AssemblyName System.DirectoryServices.AccountManagement\r\n\r\n#store credentials (of account which can modify AD groups)\r\n$creds = Get-Credential \r\n#set domain name\r\n$dn = 'contoso.com' \r\n\r\n#Create the principal context object (so to say connect to the domain with the credentials provided)\r\n$pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::`\r\nDomain,$dn,$($creds.UserName),$($creds.GetNetworkCredential().Password))\r\n\r\n#set the group name (SamAccount)\r\n$groupname = \"group1\"\r\n#set the user name to add (SamAccount)\r\n$user = \"user1\" \r\n\r\n#load the group from AD\r\n$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc,$groupname)\r\n#Set the identifier type to \"SamAccountName\"\r\n$IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName\r\n\r\n#Add the user to the group object\r\n$group.Members.Add($pc,$IDType,$user)\r\n#save the object to AD\r\n$group.Save()\r\n\r\n#Remove the user from the group object\r\n$group.Members.Remove($pc,$IDType,$user)\r\n#save the object to AD\r\n$group.Save()<\/pre>\n<p>Yeah, I know. That\u2019s a whole lot of code for such a simple task. But we can simplify things\u2026\u00a0<\/p>\n<p>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\u00a0<a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.directoryservices.accountmanagement.principalcontext?view=netframework-4.7.2\" target=\"_blank\" rel=\"noopener\"><em>PrincipalContext<\/em><\/a> constructor. This is how the constructor would then look like:<\/p>\n<pre class=\"theme:powershell-ise toolbar:2 nums:false wrap:true lang:ps decode:true\" title=\"PrincipalContext object - simple\">[System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::Domain)<\/pre>\n<p>By this, the number of necessary lines can be reduced to the following:<\/p>\n<pre class=\"theme:powershell-ise toolbar-overlay:false toolbar-delay:false lang:ps decode:true \" title=\"Manage AD group membership with .Net - Without authentication\">#Load the assembly\r\n$am = Add-Type -AssemblyName System.DirectoryServices.AccountManagement\r\n\r\n#Create the principal context object (current domain with current user)\r\n$pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::`\r\nDomain)\r\n\r\n#set the group name (SamAccount)\r\n$groupname = \"group1\"\r\n#set the user name to add (SamAccount)\r\n$user = \"user1\" \r\n\r\n#load the group from AD\r\n$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc,$groupname)\r\n\r\n#Set the identifier type to \"SamAccountName\"\r\n$IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName\r\n\r\n#Add the user to the group object\r\n$group.Members.Add($pc,$IDType,$user)\r\n#save the object to AD\r\n$group.Save()\r\n\r\n#Remove the user from the group object\r\n$group.Members.remove($pc,$IDType,$user)\r\n#save the object to AD\r\n$group.Save()<\/pre>\n<p>Sure, it\u2019s still a lot. It\u2019s also a lot more coding effort than to just type\u00a0<em>Add-ADGroupMember -Identity group1 -Members user1<\/em>. The advantage, as mentioned in the beginning, is that you can run this on any Windows machine without having the AD RSAT tools installed.<\/p>\n<p>\u00a0<\/p>\n<h4 id=\"TheFunction\">As a function<\/h4>\n<p>If you want to make things a little more convenient for you, just create a function around the code. Here\u2019s a function I wrote to add members to a group.<\/p>\n<pre class=\"theme:powershell-ise toolbar-overlay:false toolbar-delay:false lang:ps decode:true\" title=\"Add group members function\">function cAdd-ADGroupMembers($groupname, [STRING[]]$members)\r\n{\r\n\t#load the assembly (will not throw any error if already loaded)\r\n\t$am = Add-Type -AssemblyName System.DirectoryServices.AccountManagement\r\n\t\r\n\t#create the PrincipalContext object\r\n\t$pc = [System.DirectoryServices.AccountManagement.PrincipalContext]::new([System.DirectoryServices.AccountManagement.ContextType]::`\r\nDomain)\r\n\t\r\n\t#Set the identifier type to \"SamAccountName\"\r\n\t$IDType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName\r\n\t\r\n\t#find and load the group from AD\r\n\t$group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($pc, $groupname)\r\n\t#Check if group exists\r\n\tif ($group -eq $null)\r\n\t{\r\n\t\tWrite-Error \"Group was not found in Active Directory\" -Category ObjectNotFound -ErrorId \"System.DirectoryServices.AccountManagement.ObjectNotFound\"\r\n\t\tReturn\r\n\t}\r\n\t\r\n\t#add each specified member to the group object\r\n\tforeach ($member in $members)\r\n\t{\r\n\t\t#Check if object is already a member of the group\r\n\t\tif (($group.Members.Contains($pc, $IDType, $member)))\r\n\t\t{\r\n\t\t\tWrite-Warning \"Object \"\"$member\"\" is already a member of this group\"\r\n\t\t}\r\n\t\tElse\r\n\t\t{\r\n\t\t\t#add the new member\r\n\t\t\ttry\r\n                        {\r\n                          $group.Members.Add($pc, $IDType, $member)\r\n                          Write-Host \"Added $member\"\r\n                        }\r\n                        catch\r\n                        {\r\n                          write-error \"Could not add object \"\"$member\"\".`n $($_.exception.message)\" -ErrorId $_.FullyQualifiedErrorId     \r\n                        }\r\n\t\t}\r\n\t}\r\n\t#save the group\r\n\t$group.Save()\r\n\t#clean up\r\n\t$group.Dispose()\r\n\t$pc.Dispose()\r\n\tReturn \"Done\"\r\n}<\/pre>\n<p>The function basically works like the Add-ADGroupMember cmdlet. Specify the group name in the \u201cgroupname\u201d parameter and the SAMAccountName(s) of the member(s) you want to add in the \u201cmembers\u201d parameter. Multiple members can be provided by using a comma (\u201c,\u201d) as delimiter. Here\u2019s an example how to add two users to a group, by using the function:<\/p>\n<pre class=\"nums:false nums-toggle:false lang:ps decode:true\">PS C:\\> cAdd-ADGroupMembers -groupname test-group001 -members test001,test0500<\/pre>\n<p>During the execution it will show the result for each member you want to add:<\/p>\n<pre class=\"nums:false nums-toggle:false lang:ps decode:true \">PS C:\\> cAdd-ADGroupMembers -groupname test-group001 -members test001,test0500\r\nAdded test001\r\nAdded test0500\r\nDone<\/pre>\n<p>\u00a0<\/p>\n<h4>List group members<\/h4>\n<p>To list all group members, you just need to access the\u00a0<em>Members\u00a0<\/em>property of the group object.<\/p>\n<pre class=\"toolbar-overlay:false toolbar-delay:false nums:false nums-toggle:false lang:ps decode:true\" title=\"Get members - The members property\">PS C:\\Users> $group.Members\r\n\r\n\r\nGivenName                         : Test\r\nMiddleName                        : \r\nSurname                           : 001\r\nEmailAddress                      : Test.001@lab.com\r\nVoiceTelephoneNumber              : \r\nEmployeeId                        : \r\nAdvancedSearchFilter              : System.DirectoryServices.AccountManagement.AdvancedFilters\r\nEnabled                           : True\r\nAccountLockoutTime                : \r\nLastLogon                         : 02.05.2018 11:52:56\r\nPermittedWorkstations             : {}\r\nPermittedLogonTimes               : {255, 255, 255, 255...}\r\nAccountExpirationDate             : \r\nSmartcardLogonRequired            : False\r\nDelegationPermitted               : True\r\nBadLogonCount                     : 0\r\nHomeDirectory                     : \\\\FileServer01\\TEST001$\r\nHomeDrive                         : H:\r\nScriptPath                        : \r\nLastPasswordSet                   : \r\nLastBadPasswordAttempt            : 02.05.2018 11:51:39\r\nPasswordNotRequired               : False\r\nPasswordNeverExpires              : True\r\nUserCannotChangePassword          : False\r\nAllowReversiblePasswordEncryption : False\r\nCertificates                      : {}\r\nContext                           : System.DirectoryServices.AccountManagement.PrincipalContext\r\nContextType                       : Domain\r\nDescription                       : 001 Test\r\nDisplayName                       : 001 Test\r\nSamAccountName                    : TEST001\r\nUserPrincipalName                 : Test.001@lab.com\r\nSid                               : S-1-5-21-4025995969-482537669-3923321509-3972\r\nGuid                              : de16cf7f-79c4-4257-9de7-adb687ba5fe7\r\nDistinguishedName                 : CN=001 Test,OU=Standard,OU=Users,DC=lab,DC=com\r\nStructuralObjectClass             : user\r\nName                              : 001 Test\r\n<\/pre>\n<p>\u00a0<\/p>\n<h4>Speed<\/h4>\n<p>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 <em>Measure-Command<\/em> cmdlet. Here is the result:<\/p>\n<pre class=\"nums:false nums-toggle:false lang:ps mark:9,26 decode:true \" title=\"Execution time\">#Adding a user to a group using the Add-ADGroupMember cmdlet from the ActiveDirectory module\r\nPS C:\\> measure-command {Add-ADGroupMember -Identity test-group001 -Members test0500}\r\n\r\n\r\nDays              : 0\r\nHours             : 0\r\nMinutes           : 0\r\nSeconds           : 0\r\nMilliseconds      : 70\r\nTicks             : 702174\r\nTotalDays         : 8.12701388888889E-07\r\nTotalHours        : 1.95048333333333E-05\r\nTotalMinutes      : 0.00117029\r\nTotalSeconds      : 0.0702174\r\nTotalMilliseconds : 70.2174\r\n\r\n\r\n#Same with only using the .Net namespace\r\nPS C:\\> Measure-Command {$group.Members.Add($pc, $IDType, \"test0500\");$group.save()}\r\n\r\n\r\nDays              : 0\r\nHours             : 0\r\nMinutes           : 0\r\nSeconds           : 0\r\nMilliseconds      : 45\r\nTicks             : 453936\r\nTotalDays         : 5.25388888888889E-07\r\nTotalHours        : 1.26093333333333E-05\r\nTotalMinutes      : 0.00075656\r\nTotalSeconds      : 0.0453936\r\nTotalMilliseconds : 45.3936<\/pre>\n<p>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.<\/p>\n<h4>Further info<\/h4>\n<p>The <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.directoryservices.accountmanagement?view=netframework-4.7.2\" target=\"_blank\" rel=\"noopener\">\u201cSystem.DirectoryServices.AccountManagement\u201d namespace<\/a><\/p>\n<p>The <a href=\"https:\/\/docs.microsoft.com\/en-us\/dotnet\/api\/system.directoryservices.accountmanagement.principalcontext?view=netframework-4.7.2\" target=\"_blank\" rel=\"noopener\">PrincipalContext class<\/a><\/p>\n<p>The\u00a0<a href=\"https:\/\/docs.microsoft.com\/en-us\/powershell\/module\/microsoft.powershell.utility\/add-type?view=powershell-5.0\" target=\"_blank\" rel=\"noopener\">Add-Type cmdlet<\/a><\/p>\n<p><a href=\"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2010\/11\/11\/use-powershell-to-work-with-the-net-framework-classes\/\" target=\"_blank\" rel=\"noopener\">How to work with .Net classes in PowerShell<\/a><\/p>\n<p>\u00a0<\/p>\n<p>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 \ud83d\ude09<\/p>\n<p>\u00a0<\/p>\n<p>\u00a0<\/p>\n<p>\u00a0<\/p>\n<p>Photo by\u00a0<a href=\"https:\/\/unsplash.com\/photos\/UOJ6vz2khrY?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText\">Blake Guidry<\/a>\u00a0on\u00a0<a href=\"https:\/\/unsplash.com\/search\/photos\/cockpit?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText\">Unsplash<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":2915,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"om_disable_all_campaigns":false,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"_uf_show_specific_survey":0,"_uf_disable_surveys":false,"footnotes":""},"categories":[685,3],"tags":[],"class_list":["post-1776","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-active-directory","category-powershell"],"post_mailing_queue_ids":[],"_links":{"self":[{"href":"https:\/\/www.msb365.blog\/index.php?rest_route=\/wp\/v2\/posts\/1776","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.msb365.blog\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.msb365.blog\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.msb365.blog\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.msb365.blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1776"}],"version-history":[{"count":203,"href":"https:\/\/www.msb365.blog\/index.php?rest_route=\/wp\/v2\/posts\/1776\/revisions"}],"predecessor-version":[{"id":3344,"href":"https:\/\/www.msb365.blog\/index.php?rest_route=\/wp\/v2\/posts\/1776\/revisions\/3344"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.msb365.blog\/index.php?rest_route=\/wp\/v2\/media\/2915"}],"wp:attachment":[{"href":"https:\/\/www.msb365.blog\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1776"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.msb365.blog\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1776"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.msb365.blog\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1776"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}