
Introduction
A good dictum to maintaining a strong password is to change it often. In an Active Directory environment, enforcing a password change policy is a simple matter as it is all centralized. One account that very often gets overlooked on an Active Directory domain is local administrator accounts. These accounts are important for managing the computer when Active Directory is not applicable.
This application takes on the challenge of remotely and enmass modification of the local admin account password of computers in your Active Directory topology.
To make the task more efficient, the application also includes code to enumerate and list all of the computers under a user specified LDAP path. Also included is a BackgroundWorker
to asynchronously do the actual password changing, with a progress bar to keep the user up to date on the progress; this is important as each password change can take up to 5 seconds.
If you notice, this application is not very OO, mostly for simplicity of explanation. It is not a good idea to put actual problem domain logic in your UI source files.
Background
If you are familiar with using ADSI with WSH, some of this will be familiar; in fact, the DirectoryServices
namespace classes are implemented on top of the ADSI COM interface, so most things you are familiar with from ADSI can map in some way to the DirectoryServices
classes.
Using the code
The code is commented fairly well, and is only about two pages long, so there is not much specifically to mention about the code.
I will call out two or three pieces of interest from the source below. I will remove exception handling for clarity/brevity.
Finding computers
DirectoryEntry dir;
if (rootBox.Text.Length > 0)
dir = new DirectoryEntry(string.Format("LDAP://{0}", rootBox.Text));
else
dir = new DirectoryEntry();
DirectorySearcher srch = new DirectorySearcher(dir);
srch.Filter = "(objectClass=computer)";
SearchResultCollection results = srch.FindAll();
foreach (SearchResult res in results)
{
DirectoryEntry ent = res.GetDirectoryEntry();
computerList.Items.Add(ent.Name.Substring(3));
ent.Dispose();
}
Interesting to note that if you do not specify a root for the directory entry object, it defaults to the domain that you are currently logged in to.
Once you have the root you want to search from, you can instantiate a DirectorySearcher
to do the actual searching for you. Here, we set the filter to (objectClass=computer)
so that it will show us all the objects that derive from the computer schema class.
Then, it is as simple as asking the searcher to FindAll to return a collection of DirectoryEntry
objects. All ADSI objects are wrapped as DirectoryEntry
objects, with unique properties and methods available via Invoke*
functions.
Here, we are iterating over the list and putting the name of each computer in a sorted listbox.
Referencing the Local User
PasswordWorkerArgs args = (PasswordWorkerArgs)e.Argument;
int x = 0;
foreach (string cname in args.Names)
{
DirectoryEntry admin = new DirectoryEntry(string.Format("WinNT://{0}/" +
"Administrator, user", cname));
admin.Invoke("SetPassword", new object[] { args.Password });
admin.CommitChanges();
admin.Dispose();
x++;
passwordWorker.ReportProgress((int)(((double)x / args.Names.Length) * 100));
}
In this snip, you can see how we reference a computer's local Administrator user the WinNT ADSI provider.
PasswordWorkerArgs
is a simple class to carry a list of computers and the password to set into the async function of the BackgroundWorker
.
The args.Names
property is a string[]
of computer names. We reference the computer's Administrator user using the WinNT ADSI provider with the URI WinNT://COMPUTERNAME/Administrator, user. This tells the provider that we want a reference to a user called Administrator on a computer called COMPUTERNAME.
Once we have the ADSI object, we can call the SetPassword
method using the Invoke
method of the DirectoryEntry
object. This will execute the SetPassword
on the ADSI user COM object wrapped by the DirectoryEntry
. We pass arguments to the method using a simple object array.
Any changes that are made to a DirectoryEntry
object must be committed using the CommitChanges
method. And, don't forget to dispose of your DirectoryEntry
because it is just a managed wrapper over an unmanaged resource.
And at the end, we have the BackgroundWorker
report its progress to the UI thread so that the progress bar can get updated.
Points of interest
If you are familiar with ADSI using WSH, you should be able to map that knowledge into using the DirectoryServices
namespace to accomplish a lot of repetitive management tasks.
Why would you want to do directory scripting in a .NET language? There are a number of reasons:
- Following the deluge of viruses that used WSH to do their dirty deeds, a lot of enterprises have disabled all WSH on their computers; a .NET application interacting with ADSI does not require WSH.
- You may want management heads or even users to have some latitude in helping you manage themselves. I cringe at the thought of having a user edit or even run a .vbs or .js file. All users are comfortable with the Windows UI, not to mention the error avoided by letting the user interact with a predefined UI.
- Obviously, a strongly typed and compiled language will give you more power and functionality. Not that you couldn't do almost anything in WSH with COM objects, but it will be orders of magnitude easier with .NET languages. Imagine exposing ADSI functions as webservices (I imagine Microsoft will do this eventually).
See my other article on a simple application to add users to Active Directory including their addresses, telephone numbers, etc... All from an XML document exported from an Excel template that any user in your company can edit.
History
- Aug 8 2006: Initial article posted.