{"id":283,"date":"2014-11-25T00:01:00","date_gmt":"2014-11-25T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2014\/11\/25\/active-directory-week-explore-group-membership-with-powershell\/"},"modified":"2019-02-18T10:36:57","modified_gmt":"2019-02-18T17:36:57","slug":"active-directory-week-explore-group-membership-with-powershell","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/active-directory-week-explore-group-membership-with-powershell\/","title":{"rendered":"Active Directory Week: Explore Group Membership with PowerShell"},"content":{"rendered":"<p><span style=\"font-size:12px\"><strong>Summary<\/strong>: Learn about the nuances involved in reporting group memberships with Active Directory PowerShell.<\/span><\/p>\n<p>Microsoft Scripting Guy, Ed Wilson, is here. Today we continue our series about Active Directory PowerShell by Ashley McGlone. If you missed it, you may enjoy reading <a href=\"https:\/\/devblogs.microsoft.com\/scripting\/active-directory-week-get-started-with-active-directory-powershell\/\" target=\"_blank\">Get Started with Active Directory PowerShell<\/a> first. Now, here&#039;s Ashley&#8230;<\/p>\n<h2>Security priority<\/h2>\n<p>In our world today, security is getting more focus with every headline about data breaches. One key strategy to keep your environment secure is regular audits of group memberships. Hopefully, you or your company&rsquo;s information security team are regularly checking group memberships. For example, I&rsquo;ve met many customers who get an email every time a sensitive group membership changes (for example, JoeUser was added to Enterprise Admins).<\/p>\n<h2>Who&rsquo;s on first?<\/h2>\n<p>In, there are multiple techniques for reporting group memberships. Today, I will share with you some of the nuances involved. Here are the questions we really want to answer on an operational level with scripting:<\/p>\n<ul>\n<li>Who are all of the Domain Admins? (Traversing down the membership tree)<\/li>\n<li>Is JoeUser a member of Domain Admins? (Traversing up the membership tree)<\/li>\n<li>How did JoeUser get added? (By whom? When? Where?)<\/li>\n<\/ul>\n<p><a href=\"https:\/\/msdnshared.blob.core.windows.net\/media\/TNBlogsFS\/prod.evol.blogs.technet.com\/CommunityServer.Blogs.Components.WeblogFiles\/00\/00\/00\/76\/18\/1732.1.PNG\"><img decoding=\"async\" src=\"https:\/\/msdnshared.blob.core.windows.net\/media\/TNBlogsFS\/prod.evol.blogs.technet.com\/CommunityServer.Blogs.Components.WeblogFiles\/00\/00\/00\/76\/18\/1732.1.PNG\" alt=\"Image of flow chart\" title=\"Image of flow chart\" \/><\/a><\/p>\n<p>I&rsquo;ve referenced the Domain Admins group here&mdash;but you could substitute any group name of interest.<\/p>\n<h2>Down the rabbit hole<\/h2>\n<p>Group nesting makes these questions a challenge for new scripters. I&rsquo;ve seen people write some rather interesting code to do their own recursion routines to mine group memberships. Although those are fun to write, there are some built-in cmdlets to help. Let&rsquo;s look at what you get for free before launching into a scripting project.<\/p>\n<h2>Who are all the Domain Admins?<\/h2>\n<p>The cmdlet <b>Get-ADGroupMember<\/b> gives you a list of group members. (I&rsquo;ve heard some complaints that it doesn&rsquo;t do too well with cross-domain memberships, however.) Immediate members are helpful, but we want to know who is a member of all those nested groups. Thankfully, the cmdlet has a <b>\u2011Recursive<\/b> parameter. Here is a quote from the Help:<\/p>\n<p>If the <b>Recursive<\/b> parameter is specified, the cmdlet gets all members in the hierarchy of the group that do not contain child objects. For example, if the group SaraDavisReports contains the user KarenToh and the group JohnSmithReports, and JohnSmithReports contains the user JoshPollock, then the cmdlet returns KarenToh and JoshPollock.<\/p>\n<p style=\"margin-left:30px\"><b>Note&nbsp;<\/b> When you use this parameter you will not see the nested group names, only the members of all the nested groups. The nice thing here is that you can get all true group members even if a user is nested in groups many layers deep.<\/p>\n<p>The <b>-Recursive<\/b> parameter is resource intensive and should be used with care. Be a good steward of resources by running your query once and storing it in a variable. Then work with the variable instead of issuing the same query multiple times.<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">$members = Get-ADGroupMember &ldquo;Domain Admins&rdquo; -Recursive<\/p>\n<h2>Going the other way<\/h2>\n<p>What if I want to start with the user and find all of their group memberships recursively (that is, going up the membership tree)? The <b>Get-ADPrincipalGroupMembership<\/b> cmdlet is your first choice. Here is a quote from the Help:<\/p>\n<p>The <b>Get-ADPrincipalGroupMembership<\/b> cmdlet gets the Active Directory groups that have a specified user, computer, group, or service account as a member. This cmdlet requires a global catalog to perform the group search. If the forest that contains the user, computer or group does not have a global catalog, the cmdlet returns a non-terminating error. If you want to search for local groups in another domain, use the ResourceContextServer parameter to specify the alternate server in the other domain.<\/p>\n<p>It has bells and whistles for a number of search scenarios. By best practice, all of your domain controllers are likely global catalogs, so that requirement should not be a concern.<\/p>\n<p class=\"Code\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; $members = Get-ADPrincipalGroupMembership JoeUser<\/p>\n<p>However, this cmdlet does not have a parameter to do a recursive nested group search. It only shows the immediate group membership of a user. If you want to map out all of the nested group memberships, see <a href=\"http:\/\/blogs.msdn.com\/b\/adpowershell\/archive\/2009\/09\/05\/token-bloat-troubleshooting-by-analyzing-group-nesting-in-ad.aspx\" target=\"_blank\">Token Bloat Troubleshooting by Analyzing Group Nesting in AD<\/a> on the Active Directory PowerShell blog.<\/p>\n<h2>Is JoeUser a nested member of Domain Admins?<\/h2>\n<p>How can I traverse up the group nesting to check if JoeUser is in Domain Admins? You do not need to reinvent the wheel. You do not need to write a bunch of code to trace all the nested groups. This happens to be a little-known feature built into the LDAP protocol, and it is supported by Windows Server 2008 and later. I discovered this trick when reading through the <b>Get-Help about_ActiveDirectory_Filter<\/b> Help topic under <i>Example 11<\/i>. To quote the Help:<\/p>\n<p>The LDAP_MATCHING_RULE_IN_CHAIN is a matching rule OID that is designed to provide a method to look up the ancestry of an object.<\/p>\n<p>We can use the <b>-RecursiveMatch<\/b> operator in a filter string to employ this LDAP feature. It searches all of the parent-child relationships of any Active Directory object.<\/p>\n<p style=\"margin-left:30px\"><b>Pro Tip&nbsp; <\/b>If you cannot find the about_ActiveDirectory* Help topics, first make sure you have the RSAT installed for Active Directory. Then run <b>Update-Help -Module ActiveDirectory<\/b> in an elevated Windows PowerShell console (version 4.0 or 3.0). You must import the Active Directory module before the topics will be available.<\/p>\n<p>Check out this syntax:<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">Get-ADUser -Filter &#039;memberOf -RecursiveMatch &quot;&lt;distinguished name of group&gt;&quot;&#039; -SearchBase &quot;&lt;distinguished name of user&gt;&quot;<\/p>\n<p>Here is an example:<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">Get-ADUser -Filter &#039;memberOf \u2011RecursiveMatch &quot;CN=Administrators,CN=Builtin,DC=Fabrikam,DC=com&quot;&#039; \u2011SearchBase &quot;CN=Administrator,CN=Users,DC=Fabrikam,DC=com&quot;<\/p>\n<p>If the user is a member of the group, the query will return an AD object representing the user. If not a member of the group, the query will return nothing. The beauty is that it does all of the recursive group checking for you.<\/p>\n<p>Now let&rsquo;s make this more interesting. I really don&rsquo;t like typing those long Active Directory distinguished names, and it is easier to supply these from a query like this:<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">Get-ADUser -Filter &quot;memberOf -RecursiveMatch &#039;$((Get-ADGroup &quot;Domain Admins&quot;).DistinguishedName)&#039;&quot; -SearchBase $((Get-ADUser Guest).DistinguishedName)<\/p>\n<p><strong>&nbsp; &nbsp;Note<\/strong> &nbsp;We can use a Windows PowerShell variable subexpression <strong>$()<\/strong> to retrieve the user and group <br \/>&nbsp; &nbsp;distinguished names dynamically and supply them to the filter properties.<\/p>\n<p>Finally, we can make a handy function by adding a couple variables like this:<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">Function Test-ADGroupMember {<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">Param ($User,$Group)<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp; Trap {Return &quot;error&quot;}<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp; If (<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; Get-ADUser `<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -Filter &quot;memberOf -RecursiveMatch &#039;$((Get-ADGroup $Group).DistinguishedName)&#039;&quot; `<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -SearchBase $((Get-ADUser $User).DistinguishedName)<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; ) {$true}<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; Else {$false}<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">}<\/p>\n<p>Now we have a simple function to check if a user is nested into a privileged group:<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">PS C:\\&gt; Test-ADGroupMember -User Guest -Group &quot;Domain Admins&quot;<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">True<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">PS C:\\&gt; Test-ADGroupMember -User JoeJrAdmin -Group &quot;Domain Admins&quot;<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">False<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">PS C:\\&gt; Test-ADGroupMember -User bogus -Group &quot;Domain Admins&quot;<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">error<\/p>\n<p>Uh oh! How did the Guest account get nested into Domain Admins? For that answer, you&rsquo;ll need to read my post, <a href=\"\/b\/ashleymcglone\/archive\/2012\/10\/17\/ad-group-history-mystery-powershell-v3-repadmin.aspx\" target=\"_blank\">AD Group History Mystery: PowerShell v3 REPADMIN<\/a>.<\/p>\n<p>This function performs three queries, so it is not very efficient. If you wanted to process a large number of users, you could try this approach instead:<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">$Group = &quot;Domain Admins&quot;<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">$Users = (Get-ADUser -Filter &quot;name -like &#039;ad*&#039;&quot;).DistinguishedName<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">$Members = (Get-ADGroupMember $Group -Recursive).DistinguishedName<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">ForEach ($User in $Users) {<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp; [PSCustomObject]@{<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; User = $User<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; Group = $Group<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp;&nbsp;&nbsp; IsMember = $Members.Contains($User)<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">&nbsp; }<\/p>\n<p class=\"Code\" style=\"margin-left:30px\">}<\/p>\n<p>Adjust the <b>Get-ADUser -Filter<\/b> parameter to suit your needs. The output looks similar to this:<\/p>\n<p class=\"Code\">User&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;Group&nbsp;&nbsp;&nbsp; &nbsp;IsMember<\/p>\n<p class=\"Code\">&#8212;-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&#8212;&#8211;&nbsp;&nbsp;&nbsp; &nbsp;&#8212;&#8212;&#8211;<\/p>\n<p class=\"Code\">CN=adbarr,OU=Migrated,DC=CohoVineyard,DC=com&nbsp; &nbsp;Domain Admins&nbsp; False<\/p>\n<p class=\"Code\">CN=adcarter,CN=Users,DC=CohoVineyard,DC=com&nbsp; &nbsp;Domain Admins&nbsp; False<\/p>\n<p class=\"Code\">CN=addumitr,CN=Users,DC=CohoVineyard,DC=com&nbsp;&nbsp; Domain Admins&nbsp; False<\/p>\n<p class=\"Code\">CN=Administrator,CN=Users,DC=CohoVineyard,DC=com Domain Admins&nbsp; &nbsp;True<\/p>\n<p>This table conveniently summarizes the techniques we have discussed in this blog post.<\/p>\n<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\">\n<tbody>\n<tr>\n<td width=\"96\" valign=\"top\">\n<p>&nbsp;<\/p>\n<\/td>\n<td width=\"210\" valign=\"top\">\n<p><strong>Immediate<\/strong><\/p>\n<\/td>\n<td width=\"246\" valign=\"top\">\n<p><strong>Nested\/Recursive<\/strong><\/p>\n<\/td>\n<\/tr>\n<tr>\n<td width=\"96\" valign=\"top\">\n<p><strong>Members of a Group<\/strong><\/p>\n<\/td>\n<td width=\"210\" valign=\"top\">\n<p>Get-ADGroupMember <i>GroupName<\/i><\/p>\n<\/td>\n<td width=\"246\" valign=\"top\">\n<p>Get\u2011ADGroupMember <i>GroupName<\/i> \u2011Recursive<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td width=\"96\" valign=\"top\">\n<p><strong>Group membership of a User<\/strong><\/p>\n<\/td>\n<td width=\"210\" valign=\"top\">\n<p>Get-ADPrincipalGroupMembership <i>UserName<\/i><\/p>\n<\/td>\n<td width=\"246\" valign=\"top\">\n<p><strong>All groups<\/strong>:<\/p>\n<p>Reference blog post: <br \/> <a href=\"http:\/\/blogs.msdn.com\/b\/adpowershell\/archive\/2009\/09\/05\/token-bloat-troubleshooting-by-analyzing-group-nesting-in-ad.aspx\" target=\"_blank\">Token Bloat Troubleshooting by Analyzing Group Nesting in AD<\/a><\/p>\n<p><strong>Test for one group<\/strong>:<\/p>\n<p>Get-ADUser \u2011Filter &#039;memberOf \u2011RecursiveMatch &quot;&lt;<i>distinguished name of group<\/i>&gt;&quot;&#039; \u2011SearchBase &quot;&lt;<i>distinguished name of user<\/i>&gt;&quot;<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Armed with this knowledge, you now have some scripting tools to help you trace group memberships.<\/p>\n<p>~ Ashley<\/p>\n<p>Thanks for this great series, Ashley!&nbsp;<span>Come back tomorrow for Day 3 of Active Directory Week.&nbsp;<\/span><\/p>\n<p><span style=\"font-size:12px\">Ashley recently recorded a full day of free Active Directory PowerShell training: <\/span><a href=\"https:\/\/aka.ms\/MvaPsAdHsg\" target=\"_blank\">Microsoft Virtual Academy: Using PowerShell for Active Directory<\/a><span style=\"font-size:12px\">. Watch these videos to learn more insider tips on topics like getting started with Active Directory PowerShell, routine administration, stale accounts, managing replication, disaster recovery, and domain controller deployment.<\/span><\/p>\n<p>I invite you to follow me on&nbsp;<a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a>&nbsp;and&nbsp;<a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at&nbsp;<a href=\"mailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the&nbsp;<a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><b>Ed Wilson, Microsoft Scripting Guy&nbsp;<\/b><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: Learn about the nuances involved in reporting group memberships with Active Directory PowerShell. Microsoft Scripting Guy, Ed Wilson, is here. Today we continue our series about Active Directory PowerShell by Ashley McGlone. If you missed it, you may enjoy reading Get Started with Active Directory PowerShell first. Now, here&#039;s Ashley&#8230; Security priority In our [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[7,313,56,3,45],"class_list":["post-283","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-active-directory","tag-ashley-mcglone","tag-guest-blogger","tag-scripting-guy","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: Learn about the nuances involved in reporting group memberships with Active Directory PowerShell. Microsoft Scripting Guy, Ed Wilson, is here. Today we continue our series about Active Directory PowerShell by Ashley McGlone. If you missed it, you may enjoy reading Get Started with Active Directory PowerShell first. Now, here&#039;s Ashley&#8230; Security priority In our [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/283","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=283"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/283\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=283"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=283"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=283"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}