{"id":51413,"date":"2009-03-16T21:22:00","date_gmt":"2010-02-03T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2010\/02\/03\/hey-scripting-guy-how-do-i-search-active-directory\/"},"modified":"2009-03-16T21:22:00","modified_gmt":"2010-02-03T00:01:00","slug":"hey-scripting-guy-how-do-i-search-active-directory","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/hey-scripting-guy-how-do-i-search-active-directory\/","title":{"rendered":"Hey, Scripting Guy! How Do I Search Active Directory?"},"content":{"rendered":"<p><H2><IMG class=\"nearGraphic\" title=\"Hey, Scripting Guy! Question\" border=\"0\" alt=\"Hey, Scripting Guy! Question\" align=\"left\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/q-for-powertip.jpg\" width=\"34\" height=\"34\"> <\/H2>\n<P>Hey, Scripting Guy! I am just getting started using Windows PowerShell, and while it is pretty cool, I really need to be able to search Active Directory. I tried to adapt some old VBScripts I found laying around on the Internet, but it gets pretty long and convoluted. Is there an easier way to do this? What I am interested in, to start with, is a listing of the computers in Active Directory and the values of their associated attributes. Can you do such a thing?<BR><BR>&#8211; SL<\/P><IMG border=\"0\" alt=\"Spacer\" src=\"https:\/\/devblogs.microsoft.com\/scripting\/wp-content\/uploads\/sites\/29\/2019\/05\/spacer.gif\" width=\"5\" height=\"5\"><IMG class=\"nearGraphic\" title=\"Hey, Scripting Guy! Answer\" border=\"0\" alt=\"Hey, Scripting Guy! Answer\" align=\"left\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/a-for-powertip.jpg\" width=\"34\" height=\"34\"> \n<P>Hi SL,<\/P>\n<P>That silence you hear is the absence of music in my office. I was so excited when I got your question, I immediately got down to work. So fast in fact that I skipped my afternoon snack, and did not bother turning on my Zune. Well let&#8217;s get to work\u2014I am starting to get hungry. <\/P>\n<P>Luckily, using Windows PowerShell to search Active Directory is a piece of cake, maybe a nice big piece of <A href=\"http:\/\/en.wikipedia.org\/wiki\/Black_Forest_cake\" target=\"_blank\">Black Forest cake<\/A> like I got in Freiburg, Germany, last year. Of course, Black Forest cake isn\u2019t as good without a nice big cup of rich German coffee. Anyway, SL, querying Active Directory using Windows PowerShell is not a problem. The script I put together is called the <B>SearchAllComputersInDomain.ps1<\/B>.<\/P>\n<TABLE id=\"EAD\" class=\"dataTable\" cellSpacing=\"0\" cellPadding=\"0\">\n<THEAD><\/THEAD>\n<TBODY>\n<TR class=\"record\" vAlign=\"top\">\n<TD>\n<P class=\"lastInCell\">This week we are talking about searching Active Directory. The <A href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/hubs\/ad.mspx\" target=\"_blank\">Active Directory Script Center Hub<\/A> has links to a number of resources related to working with Active Directory. You will also find in <A href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/scripts\/ad\/search\/default.mspx\" target=\"_blank\">this section<\/A> of the Script Center Script Repository a good collection of scripts that illustrate searching Active Directory. There are several scripts in the <A href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/csc\/scripts\/ad\/general\/index.mspx\" target=\"_blank\">Community-Submitted Scripts Center<\/A> that also illustrate searching Active Directory. The <A href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/resources\/qanda\/ad.mspx\" target=\"_blank\">\u201cHey, Scripting Guy!\u201d Active Directory Archive<\/A> is also an excellent source of information for searching Active Directory. Not to be outdone, the Scripting Guide has an <A href=\"http:\/\/www.microsoft.com\/technet\/scriptcenter\/guide\/sas_ads_overview.mspx\" target=\"_blank\">ADSI Scripting Primer<\/A>.<\/P><\/TD><\/TR><\/TBODY><\/TABLE>\n<DIV class=\"dataTableBottomMargin\"><\/DIV>\n<P>We can use the <B>SearchAllComputersInDomain.ps1<\/B> script to retrieve a listing of all the attributes and their associated values for all the computers in the domain:<\/P><PRE class=\"codeSample\">$Filter = &#8220;ObjectCategory=computer&#8221;\n$Searcher = New-Object System.DirectoryServices.DirectorySearcher($Filter)\n$Searcher.Findall() | \nForeach-Object `\n  -Begin { &#8220;Results of $Filter query: &#8221; } `\n  -Process { $_.properties ; &#8220;`r&#8221;} `\n  -End { [string]$Searcher.FindAll().Count + &#8221; $Filter results were found&#8221; }\n<\/PRE>\n<P>The first thing we need to do in the script is create our LDAP dialect search filter. The search filter syntax is documented <A href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/aa746475(VS.85).aspx\" target=\"_blank\">on MSDN<\/A>. In our script the filter follows this simple pattern:<\/P><PRE class=\"codeSample\">LDAPAttribute=value<\/PRE>\n<P>We are interested in the <B>ObjectCategory<\/B> attribute with a value of <B>computer<\/B>. <B>ObjectCategory<\/B> is the name of an attribute in Active Directory, and we are supplying the value of <B>computer<\/B> for that attribute. Please note that, while it may look a little strange not having a space on either side of the <B>ObjectCategory=computer<\/B> filter, if you include a space on either side the query will fail, but an error will not be generated. We simply do not see any results returned. This can lead to some extremely costly experimentation sessions late at night as one tries to hone in on the correct syntax for the query. We store the search filter in the variable <B>$Filter<\/B> as seen here:<\/P><PRE class=\"codeSample\">$Filter = &#8220;ObjectCategory=computer&#8221;<\/PRE>\n<P>The LDAP attribute we are interested in is called <B>ObjectCategory<\/B>. The schema for the LDAP attribute <B>ObjectCategory<\/B> tells us that the syntax is a <B>DistinguishedName<\/B> value. We can see this by using the Active Directory Schema snap-in. The properties of the <B>ObjectCategory<\/B> attribute are seen here:<\/P><IMG border=\"0\" alt=\"Image of the properties of the ObjectCategory attribute\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/march\/hey0316\/hsg-3-16-9-1.jpg\" width=\"404\" height=\"443\"> \n<P>&nbsp;<\/P>\n<P>The Active Directory Schema snap-in is not available by default. To use the tool you need to first register the <B>s<\/B><B>chmmgmt.dll<\/B> file by using <B>RegSvr32<\/B>. The command is seen here:<\/P><PRE class=\"codeSample\">Regsvr32 schmmgmt.dll<\/PRE>\n<P>After you have registered the <B>schmmgmt.dll<\/B> file, the dialog box appears that you see here:<\/P><IMG border=\"0\" alt=\"Image of the dialog box that appears after schmmgmt.dll is registered\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/march\/hey0316\/hsg-3-16-9-2.jpg\" width=\"366\" height=\"119\"> \n<P>&nbsp;<\/P>\n<P>In our query, however, we are not supplying a <B>DistinguishedName<\/B> value. Is the documentation in error? So how does our filter actually work? In this case, the Active Directory search engine simplifies the task for us a little and allows the use of the LDAP display name in the filter. If we use only the <B>Display<\/B><B>N<\/B><B>ame<\/B> the Active Directory search engine goes to the definition for the class and picks up its <B>defaultObjectCategory<\/B> value. Every <B>classSchema<\/B> object has an attribute called <B>defaultObjectCategory<\/B>, which is the object category of an instance of the class if none is specified by the user. For most of the classes, the <B>defaultObjectCategory<\/B> value is the class itself. In the search filter, if we specify <B>objectCategory = x<\/B> and <B>x<\/B> is the <B>ldapDisplayName<\/B> of the class, LDAP will automatically expand the filter to and convert the value for the <B>objectCategory<\/B> to the distinguished name format. (Thanks to my friend Luis in Lisbon for helping me out with this.)<\/P>\n<P>So what about using the <B>objectClass<\/B> attribute instead of <B>objectCategory<\/B>? Because of the way the classes are structured in the hierarchy in the schema of Active Directory, every object in Active Directory is a member of many classes. Most objects are actually members of four or five classes on average. Because of this, the index of the <B>objectClass<\/B> attribute is extremely large. Also, <B>objectClass<\/B> has poor selectivity for many possible class values. In addition to possible performance issues, the results can be misleading as well. The query seen here returns both computer objects and user objects:<\/P><PRE class=\"codeSample\">PS C:\\&gt; $Filter = &#8220;ObjectClass=user&#8221;\nPS C:\\&gt; $Searcher = New-Object System.DirectoryServices.DirectorySearcher($Filter)\nPS C:\\&gt; $Searcher.FindAll()\n<\/PRE>\n<P>Next we need to create an instance of the <B>System.DirectoryServices.DirectorySearcher<\/B> class. The <B>DirectorySearcher<\/B> class is documented <A href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.directoryservices.directorysearcher.aspx\" target=\"_blank\">on MSDN<\/A>. There are many different ways in which the <B>DirectorySearcher<\/B> class can be created. Brandon, a Microsoft MVP, recommends supplying the filter to the constructor because it is much faster on medium-to-large networks. We store the returned <B>System.DirectoryServices.DirectorySearcher<\/B> object in the <B>$Searcher<\/B> variable as seen here:<\/P><PRE class=\"codeSample\">$Searcher = New-Object System.DirectoryServices.DirectorySearcher($Filter)<\/PRE>\n<P>Now we use the <B>Findall<\/B> method to return all objects that meet our search filter criteria:<\/P><PRE class=\"codeSample\">$Searcher.Findall() |<\/PRE>\n<P>We receive all of the objects from the <B>Findall<\/B> method and send them across the pipeline to the <B>ForEach-Object<\/B> cmdlet. We use the backtick (<B>`<\/B>) character for line continuation here. This is because I want the <B>\u2013Begin<\/B>, <B>\u2013Process<\/B><B>,<\/B> and <B>\u2013End<\/B> parameters to line up with one another. To do this, we need to use line continuation. This is the same as the space underscore used in VBScript. The <B>ForEach-Object<\/B> command is seen here:<\/P><PRE class=\"codeSample\">Foreach-Object `<\/PRE>\n<P>We want to use the <B>\u2013Begin<\/B> parameter from the <B>ForEach-Object<\/B> cmdlet. We use the <B>\u2013Begin<\/B> parameter to do something once before we get the objects coming across the pipeline. The commands in the scriptblock associated with the <B>\u2013Begin<\/B> parameter occur once for the all the objects that come across the pipeline. This is a perfect place to put a header\u2014something that will be printed out at the top of a report or at the top of the screen. We use this to print out a confirmation message that these are the results of the filter query. This is seen here (note that at the end of the line we also need to use the line continuation character): <\/P><PRE class=\"codeSample\">-Begin { &#8220;Results of $Filter query: &#8221; } `<\/PRE>\n<P>The <B>\u2013Process<\/B> parameter works on each object that comes across the pipeline. Inside the scriptblock we use the semicolon to indicate a separate logical line of code. We print out the properties and values of each computer object and then use the backtick r character (<B>&#8220;`r&#8221;<\/B>) to print out a blank line between each computer. At the end of the script block, we use the backtick character to continue to the next line:<\/P><PRE class=\"codeSample\">-Process { $_.properties ; &#8220;`r&#8221;} `<\/PRE>\n<P>The last thing we want to do is to print out how many computer accounts were found. We convert the number of the computer accounts into a string so we can concatenate with the remainder of our ending message. This line of code is seen here:<\/P><PRE class=\"codeSample\">-End { [string]$Searcher.FindAll().Count + &#8221; $Filter results were found&#8221; }<\/PRE>\n<P>When we run the script at the end of the last record, we see the summary message shown here:<\/P><IMG border=\"0\" alt=\"Image of the output shown when the script is run\" src=\"http:\/\/img.microsoft.com\/library\/media\/1033\/technet\/images\/scriptcenter\/qanda\/hsg\/2009\/march\/hey0316\/hsg-3-16-9-3.jpg\" width=\"500\" height=\"409\"> \n<P>&nbsp;<\/P>\n<P>Well, SL, this concludes our discussion about searching Active Directory for a listing of all computers in the domain. Join us tomorrow as Searching Active Directory Week continues. Until then, peace.<\/P>\n<P>&nbsp;<\/P>\n<P><B>Ed Wilson and Craig Liebendorfer, Scripting Guys<\/B><\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hey, Scripting Guy! I am just getting started using Windows PowerShell, and while it is pretty cool, I really need to be able to search Active Directory. I tried to adapt some old VBScripts I found laying around on the Internet, but it gets pretty long and convoluted. Is there an easier way to do [&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,3,8,45],"class_list":["post-51413","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-active-directory","tag-scripting-guy","tag-searching-active-directory","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Hey, Scripting Guy! I am just getting started using Windows PowerShell, and while it is pretty cool, I really need to be able to search Active Directory. I tried to adapt some old VBScripts I found laying around on the Internet, but it gets pretty long and convoluted. Is there an easier way to do [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/51413","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=51413"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/51413\/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=51413"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=51413"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=51413"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}