{"id":56093,"date":"2008-02-29T23:21:00","date_gmt":"2008-02-29T23:21:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2008\/02\/29\/hey-scripting-guy-how-can-i-list-all-the-users-in-an-ou-who-are-not-members-of-a-specified-group\/"},"modified":"2008-02-29T23:21:00","modified_gmt":"2008-02-29T23:21:00","slug":"hey-scripting-guy-how-can-i-list-all-the-users-in-an-ou-who-are-not-members-of-a-specified-group","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/hey-scripting-guy-how-can-i-list-all-the-users-in-an-ou-who-are-not-members-of-a-specified-group\/","title":{"rendered":"Hey, Scripting Guy! How Can I List All the Users in an OU Who Are Not Members of a Specified Group?"},"content":{"rendered":"<p><img decoding=\"async\" height=\"34\" width=\"34\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/q-for-powertip.jpg\" align=\"left\" alt=\"Hey, Scripting Guy! Question\" border=\"0\" title=\"Hey, Scripting Guy! Question\" class=\"nearGraphic\" \/><\/p>\n<p>Hey, Scripting Guy! How can I get a list of all the users in an OU who are <i>not<\/i> a member of a specified group?<\/p>\n<p>&#8212; TJ<\/p>\n<p><img decoding=\"async\" height=\"5\" width=\"5\" src=\"https:\/\/devblogs.microsoft.com\/scripting\/wp-content\/uploads\/sites\/29\/2019\/05\/spacer.gif\" alt=\"Spacer\" border=\"0\" \/><img decoding=\"async\" height=\"34\" width=\"34\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/a-for-powertip.jpg\" align=\"left\" alt=\"Hey, Scripting Guy! Answer\" border=\"0\" title=\"Hey, Scripting Guy! Answer\" class=\"nearGraphic\" \/><a href=\"http:\/\/go.microsoft.com\/fwlink\/?linkid=68779&amp;clcid=0x409\"><img decoding=\"async\" height=\"288\" width=\"120\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/ad.jpg\" align=\"right\" alt=\"Script Center\" border=\"0\" title=\"Script Center\" class=\"farGraphic\" \/><\/a><\/p>\n<p>Hey, TJ. You know, the Scripting Guys are having so much fun with the <a href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/funzone\/games\/default.mspx\"><b>2008 Winter Scripting Games<\/b><\/a> that we just can&rsquo;t bear to see the excitement come to an end; therefore, we&rsquo;ve decided to extend the Games for a couple more days. The Scripting Games will now close at 8:00 AM on Thursday, March 6, 2008.<\/p>\n<p>Actually, that&rsquo;s not entirely true; the Games still officially end on Monday, March 3<sup>rd<\/sup>. However, one hallmark of the Scripting Games is that you always get a second chance (and even a third or fourth chance if needed): if you get a 0 in an event you always have the opportunity to fix the problem and resubmit your entry. If the revised script works then we&rsquo;ll change your score and award you the points. <\/p>\n<p>That&rsquo;s all fine and dandy, except for one thing: at the moment the Scripting Guys are a &hellip; bit &hellip; behind in their scoring. And that&rsquo;s a definite problem: after all, how can you resubmit an entry if the Games are over before you know that you even <i>need<\/i> to resubmit an entry? (Which is very possible: there&rsquo;s <i>zero<\/i> chance that we&rsquo;ll have every single script tested and score by Monday morning.)<\/p>\n<p>What&rsquo;s that? Why <i>are<\/i> the Scripting Guys so far behind in their scoring? Well, it&rsquo;s not for lack of trying; both Scripting Guys were up past 2:00 AM last night testing scripts. (Although we might note that only <i>one<\/i> of those Scripting Guys made it into work by 7:00 AM.) The problem is simply the sheer volume of entries: we have almost twice as many entrants as we did last year, and last year it was all we could do to keep up. Oh, and just to make things even more exciting, we&rsquo;re also dealing with an overzealous spam filter that rejects a goodly portion of the scripts sent our way.<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" class=\"dataTable\" id=\"E1D\">\n<thead><\/thead>\n<tbody>\n<tr valign=\"top\" class=\"record\">\n<td>\n<p><b>Note<\/b>. Why? Well, as near as we can tell, the spam filter sees an email filled with &ldquo;nonsensical&rdquo; text (i.e., script code), and automatically marks it as spam. As you probably know, a common trick used by spammers is to compose an email filled with a random selection of words that make absolutely no sense. Because the spam filter can&rsquo;t speak VBScript, Perl, or Windows PowerShell, it will occasionally deem emails with that code as being spam.<\/p>\n<p>Incidentally, this also explains why we can&rsquo;t send <i>Hey, Scripting Guy!<\/i> out as a daily email (something a number of you have requested). Like we said, the spam filter automatically rejects any email filled with a random selection of words that make absolutely no sense.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"dataTableBottomMargin\"><\/div>\n<p>Anyway, although the Scripting Games officially end on Monday, we&rsquo;ll give you at least until Thursday morning to resubmit any entries. (And longer if need be.) In other words, relax: you&rsquo;ll have plenty of time to correct your mistakes and get that perfect score that everyone covets so much.<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" class=\"dataTable\" id=\"ELE\">\n<thead><\/thead>\n<tbody>\n<tr valign=\"top\" class=\"record\">\n<td>\n<p><b>Note<\/b>. So does this extended deadline apply only to scripts being resubmitted for an event? Yes. Does that mean that those of us who missed the Scripting Games are out of luck until next year? Officially, yes. Unofficially, though, if you really want to participate in the Games and you can get your scripts to us by Thursday morning, well, what the heck.<\/p>\n<p>Just don&rsquo;t tell the Scripting Editor, though. She&rsquo;s not going to like that idea at all.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"dataTableBottomMargin\"><\/div>\n<p>Unfortunately, none of the Scripting Games events require you to list all the users in an OU who are <i>not<\/i> members of a specified Active Directory group. Why is that unfortunate? Because if there <i>was<\/i> such an event you could simply submit the following script and call it good:<\/p>\n<pre class=\"codeSample\"><pre class=\"codeSample\">Set objDictionary = CreateObject(\"Scripting.Dictionary\")\n\nSet objGroup = GetObject(\"LDAP:\/\/CN=Finance Users,OU=Finance,DC=fabrikam,DC=com\")\n\nFor Each strMember in objGroup.Member\n    objDictionary.Add strMember, strMember   \nNext\n\nSet objOU = GetObject(\"LDAP:\/\/OU=Finance,DC=fabrikam,DC=com\")\nobjOU.Filter = Array(\"User\")\n\nFor Each objUser in objOU\n    strUser = objUser.distinguishedName    \n    If Not objDictionary.Exists(strUser) Then\n       Wscript.Echo strUser\n    End If       \nNext<\/pre>\n<p>Let&rsquo;s see if we can figure out how this all works. To begin with, we create an instance of the <b>Scripting.Dictionary<\/b> object. Why? Because we&rsquo;re going to store the list of group members in the Dictionary. Why? We&rsquo;ll get to that in a minute. After creating the Dictionary object we then use this line of code to bind to an Active Directory group named Finance Users:<\/p>\n<pre class=\"codeSample\">Set objGroup = GetObject(\"LDAP:\/\/CN=Finance Users,OU=Finance,DC=fabrikam,DC=com\")<\/pre>\n<p>Before we can figure out which users are <i>not<\/i> members of the Finance Users group we need to figure out which users <i>are<\/i> members of this group. As it turns out, each Active Directory group includes a <b>Member<\/b> attribute; as the name implies (well, sort of) this attribute stores a list of all the group members.<\/p>\n<table cellpadding=\"0\" cellspacing=\"0\" class=\"dataTable\" id=\"ERF\">\n<thead><\/thead>\n<tbody>\n<tr valign=\"top\" class=\"record\">\n<td>\n<p class=\"lastInCell\"><b>Note<\/b>. In that case, shouldn&rsquo;t that be the <i>Members<\/i> attribute? Probably. Apparently no one ever considered the possibility that a group might have more than one member.<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<div class=\"dataTableBottomMargin\"><\/div>\n<p>In order to get at the group membership all we have to do is set up a For Each loop that walks through all the items (members) in the Member attribute:<\/p>\n<pre class=\"codeSample\">For Each strMember in objGroup.Member<\/pre>\n<p>Inside this loop we do one thing and one thing only: we add the member&rsquo;s name (technically, his or her <b>distinguishedName<\/b>) to the Dictionary:<\/p>\n<pre class=\"codeSample\">objDictionary.Add strMember, strMember<\/pre>\n<p>As you can see, this is a pretty simple operation: all we do is call the <b>Add<\/b> method, using the member&rsquo;s name (stored in the loop variable strMember) as both the Dictionary key and the Dictionary item.<\/p>\n<p>By the time the loop is finished our Dictionary object will contain a list of all the members of the Finance Users group.<\/p>\n<p>Our next step is to retrieve a list of all the users in a specified OU (in this case, the Finance OU in fabrikam.com) and then determine if any of those users are <i>not<\/i> members of the Finance Users group. To do that, we first bind to the OU in Active Directory, then set a <b>Filter<\/b> on the returned data, a Filter that limits that data to user account objects:<\/p>\n<pre class=\"codeSample\">objOU.Filter = Array(\"User\")<\/pre>\n<p>After that we set up another For Each loop, this one designed to walk through the collection of user accounts stored in the Finance OU. Inside <i>this<\/i> loop we use the following line of code to retrieve the value of the user&rsquo;s distinguishedName attribute, storing that value in a variable named strUser:<\/p>\n<pre class=\"codeSample\">strUser = objUser.distinguishedName<\/pre>\n<p>Good question: why the distinguishedName attribute? Well, as you might recall, the Member attribute stores the distinguished name of each group member; in turn, that means that our Dictionary object also contains the distinguished name of each group member. How do we know if a given user is a member of the Finance Users group? That&rsquo;s easy: we simply check to see if the user&rsquo;s distinguished name can be found anywhere in the Dictionary. If it can then that means the user is a member of the group.<\/p>\n<p>As a matter of fact, that&rsquo;s what this line of code is for:<\/p>\n<pre class=\"codeSample\">If Not objDictionary.Exists(strUser) Then<\/pre>\n<p>Here we&rsquo;re simply using the <b>Exists<\/b> method to determine if the user&rsquo;s distinguished name (stored in the variable strUser) appears in the Dictionary. If it does then we simply go back to the top of the loop and repeat the process with the next user in the Finance OU. If it doesn&rsquo;t, then we echo back the user&rsquo;s distinguished name:<\/p>\n<pre class=\"codeSample\">Wscript.Echo strUser<\/pre>\n<p>And then it&rsquo;s back to the top of the loop where we try again with the next user account.<\/p>\n<p>When we&rsquo;re all done we should have output that reports back the distinguished name of each user in the Finance OU who is <i>not<\/i> a member of the Finance Users group. In other words, we should see something similar to this:<\/p>\n<pre class=\"codeSample\"><pre class=\"codeSample\">CN=Ken Myer,OU=Finance,DC=fabrikam,DC=com\nCN=Pilar Ackerman,OU=Finance,DC=fabrikam,DC=com\nCN=Jonathan Haas,OU=Finance,DC=fabrikam,DC=com\n<\/pre>\n<p>What&rsquo;s that? Well, you know, now that you mention it that <i>is<\/i> kind of goofy-looking output, isn&rsquo;t? OK, tell you what; try this modified block of code instead:<\/p>\n<pre class=\"codeSample\"><pre class=\"codeSample\">For Each objUser in objOU\n    strUser = objUser.distinguishedName \n    strUserName = objUser.displayName   \n    If Not objDictionary.Exists(strUser) Then\n       Wscript.Echo strUserName\n    End If       \nNext<\/pre>\n<p>We&rsquo;ve done two things different here. First, we&rsquo;ve added a line of code that retrieves the user&rsquo;s <b>displayName<\/b> attribute and stores that value in a variable named strUserName:<\/p>\n<pre class=\"codeSample\">strUserName = objUser.displayName<\/pre>\n<p>Second, inside our If statement we display <i>this<\/i> value (the display name) rather than the distinguished name:<\/p>\n<pre class=\"codeSample\">Wscript.Echo strUserName<\/pre>\n<p>Will that give us output that&rsquo;s a little less goofy-looking? See for yourself:<\/p>\n<pre class=\"codeSample\">Ken Myer\nPilar Ackerman\nJonathan Haas<\/pre>\n<p>Much better.<\/p>\n<p>That should do it, TJ. Remember, if you or anyone else out there would like to resubmit a script for the 2008 Winter Scripting Games there&rsquo;s still plenty of time to do so. And don&rsquo;t worry; this is definitely <i>not<\/i> cheating. We always have, and always will, give people a chance to fix their mistakes. After all, our goal is to help people learn something about scripting (and maybe have some fun while doing so). To tell you the truth, we&rsquo;d be thrilled if <i>everyone<\/i> ended up getting a perfect score.<\/p>\n<p>Besides, if it <i>was<\/i> cheating, do you think the Scripting Guys would let you get away with it?<\/p>\n<p>OK, good point: the Scripting Guy who writes this column probably would. But the Scripting Editor? No way.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey, Scripting Guy! How can I get a list of all the users in an OU who are not a member of a specified group? &#8212; TJ Hey, TJ. You know, the Scripting Guys are having so much fun with the 2008 Winter Scripting Games that we just can&rsquo;t bear to see the excitement come [&hellip;]<\/p>\n","protected":false},"author":595,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[7,44,43,3,198,5],"class_list":["post-56093","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-active-directory","tag-groups","tag-ous","tag-scripting-guy","tag-users","tag-vbscript"],"acf":[],"blog_post_summary":"<p>Hey, Scripting Guy! How can I get a list of all the users in an OU who are not a member of a specified group? &#8212; TJ Hey, TJ. You know, the Scripting Guys are having so much fun with the 2008 Winter Scripting Games that we just can&rsquo;t bear to see the excitement come [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/56093","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\/595"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=56093"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/56093\/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=56093"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=56093"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=56093"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}