Aug 302012
 

I really don’t like using tools like Quest for several reasons, primary among them being that it isn’t portable, or installed by default. I started the AD without Quest segment some time ago and I wanted to continue it again, but in a new direction. I think making scripts that can work in the pipeline is a better approach over making functions that port to each new script. That said, I wanted to make a generic AD searcher that could be used without modification for most queries. After I created it, I found that I prefer it over the UI based tools for information gathering purposes, it really is pretty neat.

Let’s get started!

First, we need to create our variables. As with all of my recent scripts, I have placed all of my variables in the Param block so that I can modify them at run time. I really like this practice since I can now change the way the script works at run time and I can even use “tab complete” to remember what I was thinking if I run the script 7 months from now. Notice that I setup a “default” value for each of these parameters so that it doesn’t need to be provided for the script to run, you are simply modifying the way the script runs with each.

Notice that the only value that I have defined as “from the pipeline” is the search term. This is because I think it’s the only thing that might change if it is coming from the pipeline. I don’t think, for instance that you would have a single search string, but then search on different properties. As well, I have forced the “PropertiesToLoad” to be an array. This is because I will use the .count property later, and if only one property is provided then PowerShell will make the variable a string instead of an array of strings. Strings do not have the .count property.

Now, I wanted to script to work in the pipeline, and there are several semantics that must be used to do this “properly” the first is that you have a “begin”, “process” and “end” block. The begin block will be run once, it is where you can setup any persistent objects or other complete environment items. In my case, I don’t want to create a new .NET AD searcher for every single item in the pipeline, the one will serve for everything, so I create it in the “begin” block.

The page size of the searcher will break the search into chunks returning 1000 objects at a time. The end result of a search with 8000 users is the same, but AD actually limits the number of items that can be returned in a search (by default 1000) so in the background we will perform eight inquiries to get our one return.

Now, I wanted a way get back only certain data. What if I only want to send the name down the pipeline? What if I want a quick way to see users with a logon count of greater than 1000 and their user names? I decided to let the user pass an array of attribute names to be loaded and sent down the pipeline. More on this later, but for now I am going to load them all into the searcher. Again, this is only done once, not for every single item since it’s in the begin block.

Now, we can move to the “process” portion of the script. This will get executed for each item that enters the pipeline, or a single time if executing outside of the pipeline.

The first thing that we need to do is setup the search string for the AD object. I am going to base this whole thing off of the parameters we created at the start. Those familiar with AD search string will recognize this. This is also where the heart of the script resides.

All this does is make a string that makes a typical, quick search. The default value of the parameters would may the string "(&(objectcategory=*)(cn=*))" and return every single AD object. Any modification of the parameters narrows it down from there. If the script is executed with ObjectType user then the string would become "(&(objectcategory=user)(cn=*))" and return every single user. I think you probably get the idea. The second line simply executes the query and places the results in our variable with the same name.

For the last part of the script, I wanted to format the data for use in the most intelligent way. Basically, depending on what the user provides for the “PropertiesToLoad” parameter we will have the script give a slightly different output. I decided that if the user gave nothing, then we would send the native .NET object that the searcher returns, unmodified. That would allow the most open-ended use of the return down the pipeline because all of the properties are included. If the user gives a single property, then they are probably trying to send a single piece of information down the pipeline, and so we can return a basic array of strings. Finally, if they are providing several, specific pieces, then perhaps they are trying to gather information, and we can make an array of objects with a property for each item requested and provide it, this makes the output compatible with cmdlets like Out-Gridview.

To start off we will see if the user provided input for “PropertiesToLoad” and either show the direct results, or start some post processing. If we are going to start post processing we need to make our array of objects and all associated properties. If there was only one object loaded, we’ll convert the array of objects into an array of strings.

To break it down more in case that’s a bit confusing. The first step is to make an object called Return and give it the properties that the user wants. This is a quick, easy way to do just that.

Next we will loop through all of the properties that they would like to have and move it from our results to our return object. We have to do this so that we can pick and choose only the items the user wanted. Even though the native .NET result was limited to the properties we told it above, it will always contain certain things like the distinguished name. Making our own object removes those items and allows PowerShell to display it in a pretty manner.

Here we are checking to see if only one property was provided. If so then we will quickly convert the array of objects into an array of strings. This is the best way I know how to do that. We are simply sending the object down the pipeline and running a ForEach-Object (% is a built-in alias) and printing the property.

If there is more than one property then we will print the whole object.

That’s it. Once you start to play around with the script you will start to see all of the possibilities. The following code block has the entire thing as one with a help block for usage information. This block will make the script compatible with Get-Help as well!

Jul 032011
 

This is the second post in my AD without Quest series. I am covering individual functions that can be combine to produce a wide variety of scripts. Today I am going to be covering how to connect to AD to read an object ADSPath. The ADSPath is basically the LDAP string to connect to that object. Once you have an object’s LDAP path it is very easy to work with the object.

When searching in AD all you need to do is use the directory searcher object and continue to narrow down the filter. There are some slight changes between searching for a user, computer and group, so I’ll cover each.

 Function Get-UserADSPathbyName{
    Param([STRING]$User)
   
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter="(&(SamAccountName=$User)(objectcategory=user))"
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()
    If(!$?){"AD searcher failed on $User"}

    If ($Results.Count -eq 1){
        $Results[0].properties.adspath
    }Else{
        Return $false
    }
}

On the line with $Results[0].properties.adspath it is important to note that the “adspath” portion is case sensitive. I really have no clue why that is the case. If you know, drop me a comment to help me out!

 Function Get-ComputerADSPathbyName{
    Param([STRING]$Computer)
   
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter=(&(CN=$Computer)(objectcategory=computer))
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()
    If(!$?){"AD searcher failed on $Computer"}

    If ($Results.Count -eq 1){
        $Results[0].properties.adspath
    }Else{
        Return $false
    }
}

So as you can see, a couple of small changes will convert the function for use with computers instead of users. One final function converts this again for use with groups.

 Function Get-GroupADSPathbyName{
    Param([STRING]$Group)
   
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter=(&(CN=$Group)(objectcategory=group))
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()
    If(!$?){"AD searcher failed on $Group"}

    If ($Results.Count -eq 1){
        $Results[0].properties.adspath
    }Else{
        Return $false
    }
}

So there they are, but what can you do with them? There is a great variable type in PowerShell defined with the ADSI tag. Once a LDAP string is defined as and ADSI variable you can interact directly with that object by reading any property you want, and even changing them.

$UserLDAP = [ADSI]$(Get-UserADSPathbyName “MyUserName”)
$UserLDAP.extensionAttribute1
>MyOldValue
$UserLDAP.Put(“extensionAttribute1”,”MyNewValue”)
$UserLDAP.SetInfo()
$UserLDAP.extensionAttribute1
>MyNewValue
Jul 022011
 

I’ve decided to start an Active Directory series. I have discovered that when you search online for any AD related code everything points back to the Quest tools. I prefer to do everything natively and not rely on a third party tool kits that have to be installed. As an admin, I understand that we do rely on our workstations, but when you start needing to install third party tools on server you are entering dangerous ground. I wouldn’t want to explain that Exchange went down because I installed some tool that locked the server.

I have been using PowerShell since its inception and I still haven’t found an instance where I need to use the Quest tools. Sometimes it might be easier, but once you’ve created a well written function, it’s easy to copy and paste it into your next script. So I am going to base this series off of individual functions that can be added to scripts to work with AD.

We’ll start with the basic ones. The following functions can be used to pull all the server names and user name out of Active Directory. This can be modified to pull whatever property you’d like, but I find that either account name or ADSPath (the LDAP string) are the most useful. I use some variation of these in almost every script I make.

First, let’s look at pulling all AD servers. Now these are only for the domain that you are running it from. There is some extra work to do to change domains, but I’ll cover that in a later blog post. The basic idea is to create a directory searcher, then set a filter (or you will return every object in AD). Then you need to set the page size. That, basically, tells the searcher how many objects it can return in a single search, so if you set it to 1000 and you have 8000 total objects, it will run a total of 8 searches. This step can be skipped if you have a very small domain, but AD has a built in limit to how many objects can be returned in a single search. This value can be set by the admin, but 1000 works with default domains. Finally, you run the search and then return the property you want from the function.

 Function Get-ServerList{
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter="(&(objectcategory=computer)(operatingSystem=*Server*))"
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()

    $Results | % {$_.Properties.name}
}

So, that’s it. You can drop this into your script and then run it with $servers = Get-ServerList to get a list of all your server names. This function runs very quickly and can be put into any script to make things really easily.

However, part of what I am trying to get across is that these functions can be quickly changed to suite your needs. So this is the function very slightly changed to pull all the users instead. In this case the PageSize parameter really comes into use.

 Function Get-UserList{
    $Searcher=New-Object System.DirectoryServices.DirectorySearcher
    $Searcher.Filter="(objectcategory=user)"
    $Searcher.PageSize = 1000
    $Results=$Searcher.FindAll()

    $Results | % {$_.Properties.samaccountname}
}

More AD entries to come! You can find them via my blogs tags to the right.