Managing Active Directory with Windows PowerShell

PowerShell Tutorial 12 – Part 1: Active Directory Management


Microsoft Active Directory Implementations have grown so large and complex that it’s required to have the necessary skills to play at the next level. What is the next level for an Administrator who is chained to the ADUC GUI? One that comes to mind is the skill to implement web systems that empower users to modify their own Active Directory Account information. Another, which our team completed was an implementation that auto generates Active Directory Accounts through our HR system. Accounts are created, modified, and disabled based on their status within the HR system. Basically we have turned the HR department into Active Directory Administrators without them knowing…(Shh!).

What is really going on Inside Active Directory?

Inside Active Directory

I have to mention the book Inside Active Directory Second Edition, as a highly recommended resource for those working with Active Directory and those learning Active Directory. It starts off with basic concepts such at those you find with the Microsoft Press and MSOC training kits. The difference is the other books stop at the GUI tools. Whereas, IAD opens the hood and gets you to the nuts and bolts of the Active Directory Database.

The book documents Active Directory extensively. Anyone working with Active Directory will find this book indispensable. Those new to AD will gain a solid grasp of the fundamentals. Experienced NT, UNIX, and Netware admins will learn how to adapt their skills to Active Directory. Script writers and Developers will benefit from the architectural documentation of AD provided in it’s pages.

With that being said, here are the Active Directory concepts we will learn and apply to our PowerShell script writing:

  • Connecting to Active Directory using the ADSI Provider
  • The Active Directory Name space
  • Creating Active Directory Structure using Organization Units
  • Creating, Modifying, and Deleting User Accounts.

Connecting to Active Directory

As we have learned, PowerShell uses objects to manage our environment. When running cmdlets built into powershell (such as Get-ChildItem) we connect to a .NET object. To view the Properties and Methods of the .NET object we simply use the “Get-Member” cmdlet. For example:

Get-ChildItem | Get-Member

We get a list of Methods and Properties for both the System.IO.DirectoryInfo and System.IO.FileInfo .NET classes.

In the PowerShell Training sessions with WMI, we learned how to connect to WMI classes and work with the class properties and methods.

When working with PowerShell and Active Directory we use the same concepts we’ve become accustomed to (hence the PowerShell learning curve is much quicker), we utilize properties and methods for each object in Active Directory. The only difference is learning how to connect to Active Directory to enable the manipulation of those objects. The tool used to connect to Active Directory is the ADSI Provider. Here is a list of ADSI Providers supported by PowerShell:

Provider Purpose
WinNT Connects with NT 4.0 (PDC or BDC) and local accounts on Windows 2000 or greater member servers.
LDAP Connect to LDAP servers, Exchange 5.x, 2000 or 2003 Active Directory
NDS Connects with Novell NDS
NWCOMPAT Connects with Novell NetWare 3.x

Important: ADSI provider names are case sensitive. If you have issues with your scripts, check that the provider has been properly named.

I’m going to be introducing concepts you may not be familiar with when using Active Directory Services Interface (ADSI). I am providing the link to ADSI on MSDN so that you can cross reference it with this PowerShell training session. It’s also a good idea that you go through this information to give you a better understanding of how we are using ADSI with PowerShell. http://msdn.microsoft.com/en-us/library/aa746512(VS.85).aspx

Since we are interested in connecting to Active Directory, we will be using the LDAP ADSI Provider as our connector. Once connected we supply a path to the target using the Directory Name space.

The Active Directory Name Space

We’ve been taught that Active Directory is a hierarchical directory service database which is reflected looking at its structure in Active Directory Users and Computers(ADUC). Like TCP/IP’s use of numbers to hide binary from us dumb humans, ADUC hides the actual LDAP names, methods, and properties utilized by AD. As a systems administrator/engineer you need to be familiar with the following terms:

  • Active Directory Hierarchical structure
  • LDAP Distinguished Name (DN) – CN=John Doe,OU=Sales,DC=DomainName,DC=com
  • LDAP Relative Distinguished Name (RDN) – John Doe
  • Common Name (CN) – John Doe
  • Canonical Name version one – DC=com/DC=DomainName/OU=Sales/CN=John Doe
  • Canonical Name version two – DomainName.com/Sales/John Doe
  • User Principal Name (UPN) – John.Doe@DomainName.com
  • Down level Name (SAM Account) – DomainName\jdoe -or- jdoe

Clients that connect to Active Directory “talk LDAP” to read and write information to domain controllers. We are going to learn how to talk LDAP as well.

The first thing I would recommend, before moving on, is to install a couple of tools. First, ADSI Edit which can be installed from the Windows Server disk under Support\Tools. If you don’t have a disk handy, download the Support Tools from this link. Think of ADSI Edit as being the same type of tool for Active Directory as CIM Studio is to WMI. With ADSI Edit you can examine:

  • Object Attributes (another fancy word for an object’s properties).
  • View an Object’s Syntax [eg. Data Types (string, boolean, integer, Distinguished Name, etc...)]
  • View and Edit an attributes values.

The next tool I recommend is the Active Directory Schema snap-in. This can be installed and saved in an mmc. Go to Start -> Run -> type in mmc. Add the snap-in and save your console, name it whatever you like.

New! - Windows 2008 now provides an Attribute tab when looking at Active Directory objects. I call it ADSIEdit lite as it doesn’t provide one critical element, the syntax (data types). ADSIEdit is still required to determine which data types to work with.

With PowerShell we are going to learn how to Create, Modify, and Delete objects in AD.

Creating an Organizational Unit

I am providing a standard Microsoft training script as our domain name spaces are not the same. Wherever you see an attribute(Property) in italics like DC=nwtraders,DC=msft -Subtitute these attributes with the values appropriate for your Active Directory.
Warning: whenever possible use a test environment before making changes in your production environment.

Here is the script we are working with, Save as CreateOU.ps1

$Class = “organizationalUnit”
$OU = “OU=TestOU”

$objADSI = [ADSI]“LDAP://DC=nwtraders,DC=msft
$objOU = $objADSI.create($Class, $OU)
$objOU.setInfo()

Running the script code will create a new Organizational Unit called TestOU under the root of your Domain. After running the script, refresh ADUC if you don’t see the new OU. Let’s examine what the code does.

$Class = “organizationalUnit”
Creates the $Class variable which holds the “Class” type of the object that is going to be created in Active Directory.

This is a good place to show you how to find classes in Active Directory. In simple terms think of a class as an object in Active Directory. It does get a little more complex but since this is not an AD tutorial, you can dig deeper by using other resources such as the AD book mentioned earlier.

Open the Active Directory Schema mmc and open the Classes folder. Nice and simple, you should now see all the classes available in AD. Find the class “organizationalUnit.” I didn’t want you to think that I was just pulling that name out of a hat…

$OU = “OU=TestOU”
Creates the $OU variable which holds the Name of the Organizational Unit we wish to create. When creating objects in Active Directory we are required to use the relative distinguished name (RDN). Attribute Data Types are expected by ADSI, such as:

  • OU – Organizational Unit
  • CN – Common Name
  • DC – Domain Component

In the Active Directory Schema mmc, right-click on the organizationUnit class and choose properties. Then click on the Attributes tab. Notice that there are two types of attributes, Mandatory and Optional. Not all classes have Mandatory attributes, but in this case the attribute is ou (Mandatory meaning it must be set). So we used the RDN (relative distinguished name) to set the value of the ou attribute (property) to “TestOU” and then we assigned the value to a variable called $OU.

$objADSI = [ADSI]“LDAP://DC=nwtraders,DC=msft
This line of code is doing a process called binding. Binding means connecting to an object, which is required when working with objects in Active Directory. Just like we did in WMI using “Get-WmiObject” to bind to a class. And like WMI, we need to bind to AD object to use its methods and properties. Since we are connecting with AD we used the LDAP Provider, see chart above for other providers.

Note: Remember that the provider is case sensitive. You must use LDAP not ldap, Ldap, LDaP, or ldaP.

$objOU = $objADSI.create($Class, $OU)
Now that we are bound to our domain root (nwtraders.msft) we use the create method to create a new OU called TestOU. The OU is assigned to the varible $objOU. So really nothing has happened up to this point. Sure we have a new OU but it’s stored in memory not in Active Directory, yet!

$objOU.setInfo()
We now use the “setInfo()” method against the object (our OU) stored in the $objOU variable. SetInfo commits our change by writing to the object into Active Directory database. Please don’t forget to commit your changes…

Bonus: Let’s say you want to create an OU(s) under the TestOU or somewhere other than the root. You just need to change your binding to Active Directory. For example, we want to create a computer and a user OU under the newly created TestOU. Let’s just say you are creating two OUs because you going to apply different group policy to each.

$Class = “organizationalUnit”
$OUUsers = “OU=TestOUUsers”
$OUPC = “OU=TestOUComputers”

$objADSI = [ADSI]“LDAP://OU=TestOU,DC=nwtraders,DC=msft
$objOU = $objADSI.create($Class, $OUUsers)
$objOU.setInfo()

$objOU = $objADSI.create($Class, $OUPC)
$objOU.setInfo()

We changed our binding to the testOU – [ADSI]“LDAP://OU=TestOU,DC=nwtraders,DC=msft

Note: in the above code I committed my changes after each create method. Did this because I’m using the same variable “$objOU” for each new OU. If I didn’t commit my change after creating the TestOUUsers, then the value of my variable would have been over-written during the create process for TestOUComputers. The result would have been the creation of only one new OU (TestOUComputers). I might not normally write the code as above, I just provided it so I could demonstrate how variables can be overwritten if you are not careful.

Creating a User Account

Now that we have our test OUs created we can start to add some new users to the mix. I’m sure that I have given you enough information regarding “How to find classes,” Binding, and the setInfo method in the OU example above. I’m just going to supply examples and explain only new concepts as they arrive.

This example create a new user object called John Doe in the TestOUUsers OU.

$Class = “User”
$strUserName = “CN=John Doe”

$objADSI = [ADSI]“LDAP://OU=TestOUUsers,OU=TestOU,DC=nwtraders,DC=msft”
$objUser = $objADSI.Create($Class, $strUserName)
$objUser.Put(“sAMAccountName”, “jdoe”)
$objUser.setInfo()

New Concept – the put method. So what are we doing with this line of code $objUser.Put(“sAMAccountName”, “jdoe”)?

As Active Directory is a database we use two common methods for retrieving and entering data, get and put. Looking at the line again we are entering jdoe as the log on name for the user John Doe. sAMAccountName is the attribute jdoe is being assigned to. After you have run the script, open the user account in ADUC. Under the Account tab you will see jdoe (sAMAccountName) attribute set.

sAMAccount Setting

sAMAccount

So we created the user and added one attribute, whoop de do!!! This is where the fun starts…

Here is where we begin our journey of learning to speak LDAP. Looking at the image above we see that ADUC gives us a simple, easy to read, descriptions of each setting. Notice under the “User logon name(pre-Windows 2000)” we have the setting we created “jdoe” – but wait… We used (“sAMAccountName”, “jdoe”) not (“User logon name(pre-Windows 2000)”, “jdoe”). In short the ADUC is not showing us the LDAP names needed for our script writing. Let’s open ADSIEdit.

In ADSIEdit, connect to your Domain and navigate to the TestOU. Under TestOU open TestOUUser, you should see a folder called “CN=John Doe.” Right-Click on the folder open properties. You should see all the attributes for the user object. Scroll down until you find the sAMAccountName attribute.

PowerShell Training - sAMAccount attribute

Attribute

There you go, we see that our PowerShell script assigned the string value jdoe to the sAMAccountName attribute. Now all we have to do is correlate the LDAP names with the names in ADUC. The best way to do that is to just start working with ADSIEdit. For this PowerShell training session were going to work on the General tab for John Doe.

PowerShell Training - LDAP Names

LDAP Names

Modifying the User Account

Using the following LDAP Names we are going to populate the fields in the General tab:

  • givenName
  • initials
  • sn
  • DisplayName
  • description
  • PhysicalDeliveryOffice
  • telephoneNumber
  • mail

This time our code is going to bind to the user object we created (John Doe) and use the put method to set the attribute value and the setInfo method to commit our changes to the AD database. Note: We have talked about data types throughout these PowerShell training sessions. If you are unsure of what data type to use with an AD attribute (property) it is listed in the syntax column on the properties page for the AD object in ADSIEdit.

Save this script as ModJdoe.ps1

$objUser = [ADSI]“LDAP://CN=John Doe,OU=TestOUUsers,OU=TestOU,DC=nwtraders,DC=msft
$objUser.put(“givenName”, “John”)
$objUser.put(“initials”, “D.”)
$objUser.put(“sn”, “Doe”)
$objUser.put(“DisplayName”, “Doe, John”)
$objUser.put(“description”, “IT Manager”)
$objUser.put(“PhysicalDeliveryOfficeName”, “Building 44 suite 195″)
$objUser.put(“telephoneNumber”, “555-555-5555″)
$objUser.put(“mail”, “JohnDoe@nwtraders.msft”)
$objUser.setInfo()

Run the script, and verify the results in ADUC.

PowerShell Training - Genral tab populated.

Properties

Common issue when binding to an AD Object

As shown, when binding to an AD object we used the ADsPath with each entry separated by a commma (,).
“CN=John Doe,OU=TestOUUsers,OU=TestOU,DC=nwtraders,DC=msft”
What if there is a comma in the CN? For example, CN=Doe, John

It is not uncommon to see this as some companies use “Last name, First name” as a standard to sort objects alphabetically. Do the following with the John Doe account:

  • locate “John Doe” in ADUC
  • Right-Click on John Doe and rename to Doe, John. This renames the CN.
  • Refresh ADUC.

Now let’s attempt to bind using the modify script. In bold is the change. Save the script as ModJdoe2.ps1

$objUser = [ADSI]“LDAP://CN=Doe, John,OU=TestOUUsers,OU=TestOU,DC=nwtraders,DC=msft
$objUser.put(“givenName”, “John”)
$objUser.put(“initials”, “D.”)
$objUser.put(“sn”, “Doe”)
$objUser.put(“DisplayName”, “Doe, John”)
$objUser.put(“description”, “IT Manager”)
$objUser.put(“PhysicalDeliveryOfficeName”, “Building 44 suite 195″)
$objUser.put(“telephoneNumber”, “555-555-5555″)
$objUser.put(“mail”, “JohnDoe@nwtraders.msft”)
$objUser.setInfo()

When running the script we get an error: “An invalid dn syntax has been specified.”

We need to tell PowerShell that the comma separating the Last and First name is part of the CN and not a separator for the DN (distinguished name). We do this by adding the back slash character.

Modify the LDAP binding as follows:
$objUser = [ADSI]“LADP://CN=Doe\, John,OU=TestOUUsers,OU=TestOU,DC=nwtraders,DC=org”

The script will run without the syntax error.

What we have seen and done so far is relatively simple. We know how to look up LDAP attributes in ADSIEdit to determine which fields we can look up (get) and modify (put). Take some time and test making changes to other tabs in AD. User Account Control is a different animal all together. It’s not difficult to control once you understand how to configure the settings.

User Account Control

The user account control attribute is used for the following:

  • Enable/Disable User and Computer Accounts
  • Account Lockout
  • User Can’t Change Password
  • Computer Account Types
  • Password Expiration
  • Smart card required for logon

There are more uses as shown in the table below. Up until now we have been modifying the account attributes using string values. The User Account Control attribute requires an integer data type as shown in the screen shot.

User Account Control Attribute

Account Control

Through ADSIEdit we see that the userAccountControl attribute value is 546. So what in the heck does that mean? The attribute is not a single string attribute but rather a sum of the values that are listed in the table below, these values are aka “Flags.”

User Access Control Flags

Ads Constant Hex Value Decimal Value
ADS_UF_SCRIPT 0x0001 1
ADS_UF_ACCOUNTDISABLE 0x0002 2
ADS_UF_HOMEDIR_REQUIRED 0x0008 8
ADS_UF_LOCKOUT 0x0010 16
ADS_UF_PASSWD_NOTREQD 0x0020 32
ADS_UF_PASSWD_CANT_CHANGE 0x0040 64
ADS_UF_ENCRYPTED_TEXT_PASSWORD_ALLOWED 0x0080 128
ADS_UF_TEMP_DUPLICATE_ACCOUNT 0x0100 256
ADS_UF_NORMAL_ACCOUNT 0x0200 512
ADS_UF_INTERDOMAIN_TRUST_ACCOUNT 0x0800 2048
ADS_UF_WORKSTATION_TRUST_ACCOUNT 0x1000 4096
ADS_UF_SERVER_TRUST_ACCOUNT 0x2000 8192
ADS_UF_DONT_EXPIRE_PASSWD 0x10000 65536
ADS_UF_MNS_LOGON_ACCOUNT 0x20000 131072
ADS_UF_SMARTCARD_REQUIRED 0x40000 262144
ADS_UF_TRUSTED_FOR_DELEGATION 0x80000 524288
ADS_UF_NOT_DELEGATED 0x100000 1048576
ADS_UF_USE_DES_KEY_ONLY 0x200000 2097152
ADS_UF_DONT_REQUIRE_PREAUTH 0x400000 4194304
ADS_UF_PASSWORD_EXPIRED 0x800000 8388608
ADS_UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION 0x1000000 16777216

Note: in the table above I have added the decimal column to assist with conversion from hexadecimal. When creating a constant in a PowerShell Script (for Active directory) we use the decimal value.

So the value of 546 represents an answer to an equation. It is the decimal values (Flags) added together that gives us the value for the attribute. Okay… so it sounds like “Rocket Surgery” and is difficult to figure out – right? Let’s see how difficult it really is… Before we do that, I’ve introduced a term called a Constant. A constant is a variable with one major difference. A variable’s value can be changed where a Constant’s value always stays the same throughout the script. More on this a little later…

Using the Decimal Value column in the table let’s look at what type of Control has been set on John’s Account. By process of elimination we figure the puzzle out like this:

1. Looking at John’s control value of 546, what is the highest attribute number we can start with? Should be 512… correct? So we know this is a NORMAL_ACCOUNT (basically a user account). Subtract 512 from 546 and we have 34 left over.

2. With 34 left over, what is the next attribute number we can apply? Should be PASSWD_NOTREQD which has a decimal value of 32. Subtract 32 from 34 and we should have a value of 2 left over.

3. What’s the next value that is assigned? ACCOUNTDISABLE has a decimal value of 2. Subtract 2 from 2 and we have no value left over.

So, according to the userAccountControl attribute; John Doe is a Normal account that doesn’t require a password and is currently disabled.

Modify User Account Control (Enable the Account)

Just by looking at the ACCOUNTDISABLE flag, it should be apparent that we can enable the account by removing a decimal value of 2 from the current userAccountControl attribute. Before we do this, open ADUC and find John Doe’s account. The icon that represents the user account should have a red “X” indicating that the account is disabled. Now let’s enable the account by changing the value from 546 to 544.

$objUser = [ADSI]“LDAP://CN=John Doe,OU=TestOUUsers,OU=TestOU,DC=nwtraders,DC=msft
$objUser.put(“userAccountControl”, 544)
$objUser.SetInfo()

John Doe’s account is now enabled in your Domain, verify that the red “X” has been removed in ADUC. To verify that a password is not required, log on the network with the jdoe account without a password. You should be prompted to change password at first logon. The Old password field leave blank and configure the new password.

More info on Constants

As you work with Active Directory you are going to come across attributes (properties) that hold integer values in which you will need to know what these values represent. Here is a list of the Constants used in VBScript, WSH, Windows, and Active Directory\ADSI. This information comes from when I was learning to script with VBScript and is a good tool to keep in your scripting kit. In the file, when you see a value given as &H…. it means hexadecimal. So as I did with the table above, you’ll want to convert hex into decimal. The easiest way is to use calc.exe in scientific mode.

The script example below provides how constants are used in PowerShell.

Set-Variable -name ADS_UF_ACCOUNTDISABLE -value 2 -option constant

$objUser = [ADSI]“LDAP://CN=John Doe,OU=TestOUUsers,OU=TestOU,DC=nwtraders,DC=msft
$objUser.put(“userAccountControl”,$ADS_UF_ACCOUNTDISABLE)

$objUser.setInfo()

Important: The userAccessControl attribute holds only one integer, which is the sum of all the integer values for each Control Flag that you wish to set. Keep this in mind when changing this attribute. Let’s say you have a user account with the attribute set to 8388608 (Password Expired). Your boss has asked that you disable the account but he also wants to make sure the password stays expired should someone at the help desk get duped into re-enabling the account. In this case you would want to set the userAccessControl attribute to 8388610. Can you see why changing the attribute to a value of just 2 would be an issue? If you disabled the account using the value of 2 (ACCOUNTDISABLE) you have removed the value of 8388608 and now the Password is not expired. Hope that makes sense… You can get into a lot of trouble setting attributes, so be careful and thorough.

Examples of Modifying other tabs in Active Directory

The code below should be customized for your organization and is presented only for demonstration.

Setting Users Address Tab Information:

$objUser.put(“streetAddress”, “12345 First St.”)
$objUser.put(“postOfficeBox”, “PO Box 12345″)
$objUser.put(“l”, “Newport Beach”)
$objUser.put(“st”, “California”)
$objUser.put(“postalCode”, “12345″)
$objUser.put(“co”, “USA”)

Setting User Profile Tab:

$objUser.put(“profilePath”, “\\FileServerName\%username%”)
$objUser.put(“ScriptPath”, “logon.ps1″)
$objUser.put(“homeDrive”, “H:”)
$objUser.put(“homeDirectory”, “\\FilesServerName\%username%”)

Setting User Telephone Tab:

$objUser.put(“homePhone”, “(555)555-5555″)
$objUser.put(“pager”, “(555)555-5555″)
$objUser.put(“mobile”, “(555)555-5555″)
$objUser.put(“facsimileTelephoneNumber”, “(555)555-5555″)
$objUser.put(“ipPhone”, “(555)555-5555″)
$objUser.put(“info”, “User can be contacted by pager 24 hours a day.”

Setting User Oganization Tab:

$objUser.put(“Title”, “The Boss”)
$objUser.put(“Department”, “Human Resources”)
$objUser.put(“Company”, “NWTraders”)

Deleting a User Account

It’s just as easy to delete an account as it is to create it. We just need to change the “create” method we used earlier to the “delete” method.

$Class = “User”
$strUserName = “CN=John Doe”

$objADSI = [ADSI]“LDAP://OU=TestOUUsers,OU=TestOU,DC=nwtraders,DC=msft”
$objUser = $objADSI.Delete($Class, $strUserName)
$objUser.setInfo()

Working in the Real World

Now that we have a little understanding of the tools, how can we be creative with them? As mentioned earlier, we turned our HR department into ghost Active Directory Administrators. We did this because we were looking to streamline work flow. Meaning, why would we spend the time to manually input the same data twice? Once by HR and again by an AD Administrator, from a cost perspective it make no sense. Obviously I’m not going to go into detail about how to build this type of system as the documentation is over 150 pages and I don’t want to bore you to death. But here is the basic concept:

1. HR inputs new employee information in the HR System.

  • Employee Name
  • Employee Number
  • Department
  • Department Number (cost center)
  • Title
  • Location
  • Contact Info (phone, cell, pager, etc…)

2. We built an interface using LDAP that stores information (using variables) inputed into the HR system. It then uses the information to create the user in Active Directory and populates the required fields in AD.

Here is a simple script that works somewhat in the same fashion as above. You have to use some imagination though…

Save this code in a script call UserAcctCreate.ps1

# HR System Interface
Write-Host “Welcome to Nwtraders HR System”

# Set Variables from User Input
$FirstName = Read-Host “Enter Employee’s First Name”
$LastName = Read-Host “Enter Employee’s Last Name”
$DisplayName = ($LastName + “, ” + $FirstName)
$Discription = Read-Host “Enter Empolyee’s Title”
$Office = Read-Host “Enter Employee’s Location”
$Phone = Read-Host “Enter Employee’s Phone Number”
$CN = (“CN=” + $FirstName + ” ” + $LastName)
$Class = “User”
$strUserName = $CN

#connect to AD and create user
$objADSI = [ADSI]“LDAP://OU=TestOUUsers,OU=TestOU,DC=NWTraders,DC=Mstf
$objUser = $objADSI.Create($Class, $strUserName)
$objUser.Put(“sAMAccountName”, $FirstName)

# Commit the object in AD
$objUser.setInfo()

#Set General Tab Properties
#Bind to the user Object
$objADSI = [ADSI]“LDAP://$CN,OU=TestOUUsers,OU=TestOU,DC=NWTraders,DC=Mstf
$objUser.Put(“givenName”, “$firstName”)
$objUser.Put(“SN”, “$LastName”)
$objUser.Put(“DisplayName”, “$DisplayName”)
$objUser.Put(“description”, “$Discription”)
$objUser.Put(“PhysicalDeliveryOfficeName”, “$office”)
$objUser.Put(“telephoneNumber”, “$phone”)

#set UserAccessControl
$objUser.put(“userAccountControl”, 544)

#Commit changes
$objUser.setInfo()

When the script runs it asks a series or questions and saves the user’s input into variables. The script uses the input to create the account and set the user object attributes. Simple an crude, I’m just attempting to open your eyes to the possibilities available when creating/managing accounts in AD. Possibilities such as:

  • Creating a custom interface that allows users to add/modify accounts. Example, a tool created for help desk that only allows modification of certain fields. Phew… I no longer have to give them an MMC with ADUC as a snap-in.
  • A Web Interface that can be programmed with text fields, check boxes, and drop-down lists. Which also would allow you to control which fields in Active Directory can be modified. Most MS programmers choose to use Visual Studio to work with .NET when creating web interfaces for Active Directory. Don’t ask them to do it in PowerShell (this is just an example). Can this be done with PowerShell? After all we are able to build web interfaces utilizing VBScript and .ASP. To be honest – I continue to work with VBScript when building web interfaces to Active Directory. Since PowerShell uses .NET objects I would imagine there is or will be a way, I have not attempted this as of this writing (still researching). If someone has, please provide a comment at the end of this tutorial.
  • Build a Script that queries information stored in one system and use that information to build user accounts and user attributes in AD. Such as we did with our HR system.

These examples are a bit advanced for a beginer, but that’s ok! The examples are provided so that you can think outside the GUI (ADUC).

Searching Active Directory

Let’s Talk LDAP. Really, let’s learn how to speak LDAP. This section may take sometime to digest and understand, so try your best and then some. I’ll attempt to teach it as well as I can…

Have you used custom LDAP queries? If yes and you know how to write them, you can skip this section. If No, buckle-up! I was taught how to write custom LDAP queries by a friend whom I’ve mentioned in earlier PowerShell training sessions, Paul S. Chapman. Remember him? If his resume ever hits your desk… He is the “just hire him guy with no questions asked.” He wrote a nice cheat sheet that explains custom LDAP queries and how to write them. I’m going to share the file with you. – Just Click Here – Copy the information into a text file an place it in your scripting toolbox. Study the file and start writing your own custom LDAP searches in ADUC.

PowerShell Training - Custom LDAP Search

Custom LDAP Query

To do Custom LDAP searches in ADUC:

  • Right-Click on the Domain Name and choose “find”
  • In the Find: drop-down box choose Custom Search
  • Click on the Advanced Tab
  • Write your custom query in the text field

What is the query doing? It is searching for:

[Users] with [P/W never expires flag is not set and not disabled] and [P/W age >180 days] and [Password change flag is not set]

If this is not making sense(yet!), continue to study the LDAP Query file above… It will come to you!!!

Custom LDAP Searches using PowerShell

We’re going to be working with the new-object cmdlet to bind to the DirectoryServices.DirectorySearcher .NET object. We will then use the new object to assist us with our searches.

Example 1. Find a specific user account. Let’s search for the account we create “John Doe.” Keep in mind the last script we ran (UserAcctCreate.ps1) used the sAMAccountName of “John”

Save the code below as FindUser.ps1

$User = Read-Host “Enter the Users’ Logon Name”

$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
$Search.filter = “(&(objectClass=user)(sAMAccountName=$User))”
$Search.Findall()

Notice the importance of the $Search.filter – It uses a custom LDAP query to find the results.

Run the script, you should get the following results.

PowerShell Training - Find User

Query

Example 2. You boss wants a report of all AD accounts with the password set “Not to Expire.” He is getting heat from the Security Administrators stating that the company is not in compliance. You say??? “No Problem, I’ll have the report to you ASAP.”

Save code as PassNotExp.ps1

$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
$Search.filter = “(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536))”
$results = $Search.Findall()

Foreach($result in $results){
$User = $result.GetDirectoryEntry()
$user.DistinguishedName
}

Notice that we are pulling the Distinguished Name for our results. I like to see the full path of the object in Active Directory, so I know where to find it. What if you wanted to pull the Logon Names? Just make the following change to the last line in the script – $user.sAMAccountName

Search Options

Using the trusty get-member cmdlet we can look at the properties and methods of a .NET class. Just as we did with WMI.

Let’s look at the Class we are working with. Type this code in the PowerShell command prompt.

$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)<enter>

$Search | Get-Member<enter>

Let’s say we are getting an incomplete result from our password script because our results are larger than the default of 1,000 records returned by Active Directory (A safety net put in place as not to over-load a domain controller). We need to tell AD that we require more results than the default will allow. Looking at the Properties and Methods of the Searcher class we see that we can “Set:” a property called PageSize.

Let’s modify the script to ensure we get all results returned. From what I have read it really doesn’t matter what value you set for the property. As long as the value is set it will return more than the default of 1,000 records (default is 0). I could be wrong but I’m sure someone out there will correct me. So for the sake of argument, let’s set the property to 1000.

From the .NET Library:

After the server has found the number of objects that are specified by the PageSize property, it will stop searching and return the results to the client. When the client requests more data, the server will restart the search where it left off.

PassNotExp.ps1

$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
$Search.filter = “(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536))”
$Search.PageSize = 1000
$results = $Search.Findall()

Foreach($result in $results){
$User = $result.GetDirectoryEntry()
$user.DistinguishedName
}

Here is more info on the DirectorySearcher PageSize property in the .NET Framework Library.

Here is another example of searching. Let’s say you been asked to get a list of all users in a specific OU.

$ADsPath = [ADSI]“LDAP://OU=Disabled Accounts,DC=Nwtraders,DC=Msft
$Search = New-Object DirectoryServices.DirectorySearcher($ADsPath)
$Search.filter = “(objectClass=user)”
$Search.PageSize = 1000
$Search.SearchScope = “OneLevel” $results = $Search.Findall()

Foreach($result in $results){
$User = $result.GetDirectoryEntry()
$user.DistinguishedName
}

If you wanted to search not only the Disabled Account OU but any sub OUs you would modify the search scope as follows:
$Search.SearchScopt = “SubTree”

I think I have given you enough to chew on for awhile. Take your time and really get an understanding of how to build custom LDAP queries. You will be utilizing them quite a bit. Next time your boss wants some type of report from Active Directory you will be right there to give it to him!!! (you know what I mean).

In the next PowerShell Training session we are going to talk about using ADO (AcriveX Data Object) to talk to the Active Directory Database as well as other databases. See you then!

Email This Page To A Friend Email This Page To A Friend

Comments

DirectorySearcher always pages when you specify a pagesize value, but that value cannot exceed 1000 unless you modify the ldap support on the domain controller (not recommended.)

Effectively pagesize is used to tell the DirectorySearcher to page and how many to fetch per page (not exceeding 1000.)

Thanks bsonposh…
From reading the .NET Library, the search stops at 1000 (if that’s what is set), displays results, then starts a new search where the last one left off. I hope I’m reading this right?

LDAP uses the Server Policy called MaxPageSize (defined below) to determin how many objects to return in a single requested page.

So the way it works (with DirectorySearcher) is that if/when pagesize property is set the DirectorySearcher adds the PagedSearch Control to the LDAP query. This tells the Domain Controller that it should expect multiple queries and to keep track. The DC then returns a cookie back with each page that it (the DC) expects to be returned in the next query. It uses this cookie to continue the orginal request. All this is abstracted by DirectorySearcher, but you can see it clearly when using Protocols.

MaxPageSize = 1000
The maximum number of objects that are returned in a single search result, independent of how large each returned object is. To perform a search where the result might exceed this number of objects, the client must specify the paged search control.

RFC for paged request:
http://www.rfc-archive.org/getrfc.php?rfc=2696

Thank you for this set of tutorials, they have been very useful to someone who has done very little scripting. I’m beginning to make some progress, however I’m obviously missing something. I undertand the principal of methods however in you example script:

$Class = “organizationalUnit”
$OU = “OU=TestOU”

$objADSI = [ADSI]“LDAP://DC=nwtraders,DC=msft”
$objOU = $objADSI.create($Class, $OU)
$objOU.setInfo()

You use the .create and .setinfo methods. Can you explain where these come from in this example and what get-member command I would use to return this information. Many thanks in advance

Gary,
Sure… The best way to explain it is to show you where the information lives on the MSDN. This link provides answers to what you wanted to know about using Active Directory Service Interface.

http://msdn.microsoft.com/en-us/library/aa746512(VS.85).aspx

Jesse,

Thanks for the reply, this is beginning to make more sense. One more question, I am able to search AD when binding to the root domain, however when trying any searches directed at specific OU’s I am getting an error, even with your example scripts above. The code I am running is:

$searcher = new-object DirectoryServices.DirectorySearcher
$searcher.SearchRoot = “LDAP://OU=Users,DC=uug,DC=vcm,DC=cc”
$searcher.filter = “(objectClass=user)”
$Searcher.SearchScope = “OneLevel”
$searcher.findall()

and this outputs:

Exception calling “Findall” with “0″ argument(s) @There is no such
object on the server.”
At c:\MyScripts\test.ps1:13 char:18 + $searcher.findall <<<< ()

Can you tell me where I am going wrong. I have tried numerous variations using different scripts from different sites without success and used different domains.

You are missing [ADSI] in your binding. Change your line of code to this and try again:
$Searcher.SearchRoot = [ADSI]“LDAP://OU=Users,DC=uug,DC=vmc,DC=cc”

By using [ADSI] you are telling PowerShell to use the ADSI provider, which is required…

I think I worked it out, you cannot use this to enumerate against the default OU’s Users and Computers; other OU’s that have been created work fine.

Also, Computer and Users are not OUs but Containers (That didn’t hit me at the time of my reply…). Use the following when binding:
CN=Users,DC=uug,DC=vmc,DC=cc
-and-
CN=Computers…

You should be able to enumerate these containers.

Jesse,

I have got the script below which outputs the two values to the screen, how can I get this to output in columns? I have tried numerous attempts with format-table without success.

$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
$Search.filter = “(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536))”
$Search.PageSize = 1000
$results = $Search.Findall()

Foreach($result in $results){
$User = $result.GetDirectoryEntry()
$user.cn,$user.distinguishedname
}

Jesse-
I’m confused. In a reply above, you say “By using [ADSI] you are telling PowerShell to use the ADSI provider, which is required…” but at the top of this tutorial, “LDAP” is described as “an ADSI provider.” So is ADSI a provider, or LDAP, or is it both in a two-level arrangement?

By Ryan T. Hilton on October 27th, 2008 at 2:13 pm

GaryM,
I know that this does not answer your question, but from my experience your query would require 1001 connections to the DC to perform the lookups. Once to perform the search and 1000 times to retrieve the DirectoryEntry of each user. You would be better off using the $Search.PropertiesToLoad.Add method to specify ‘cn’ and ‘distinguishedname’.

You could then use $User = $result.Properties followed by $User.cn, $User.distinguishedname

As your PageSize grows this can cause some huge overhead.

By Ryan T. Hilton on October 27th, 2008 at 2:51 pm

This would work for GaryM’s question:

$Search = New-Object DirectoryServices.DirectorySearcher([ADSI]“”)
$Search.filter = “(&(objectClass=user)(userAccountControl:1.2.840.113556.1.4.803:=65536))”
$Search.PageSize = 1000
[void]$Search.PropertiesToLoad.Add(“cn”);
[void]$Search.PropertiesToLoad.Add(“distinguishedname”);

$results = $Search.Findall()
$results | select @{e={$_.properties.cn};n=’name’},@{e={$_.properties.distinguishedname};n=’distinguishedname’}

By Joe Pool on May 18th, 2009 at 2:05 pm

I can currently authenticate a person using their user ID and password, but I only know the person’s Full Name.

Can I authenticate using their Full Name and password?

Or, how can I query for their user ID?

Thanks for the great series.

Did the promised Advanced Tutorials ever eventuate?

This is a great series but I am stuck on detail that is killing me.

I want to create a CSV file from output of my search. If I use your search as an example, I feel like I should be able pipe $results into a series of commandlets and get a nice csv but I cannot get it to work. Any pointers?

@Anthony: Just pipe your results to the Export-CSV CmdLet, like this:

$results | Export-CSV myexportfile.csv

By christopher on June 4th, 2010 at 2:13 pm

how would you change password expired to 180 days???

$objUser = [ADSI]“LDAP://CN=$,OU=$,OU=Users,DC=$,DC=$”
$objUser.put(”userAccountControl”, 8388608)
$objUser.SetInfo(180 days)

would this change all AD users to 180days for expired???

Hi there

How do I make a samAccount with a hyphen within??

Eksample for login name:
Honesty-Trace1, Honesty-Trace2 etc.

Whats the best or easiest way to make a script with this function for 1000 users and multiple OU’s??

By Marc van Meer on December 25th, 2010 at 12:54 am

Hi Jesse,

I just wanted to let you know that your tutorial is awesome. It was really easy and fun to read. Now I know why it’s called “Power”Shell. And I don’t feel like a newbie anymore :)

I will follow your advice by reading the books you reccommended. First (just for the fun of it) “PowerShell Programming for the Absolute Beginner”, by Jerry Lee Ford, Jr., and then (to really get the hang of it) “Windows PowerShell in Action”, by Bruce G. Payette.

Thanks a lot, and keep up the good work!

By Gerry-Philippines on January 28th, 2011 at 12:41 am

Hi Guys,

I am really a Fanatic of powershell, and also thank you for the additional knowledge that you posted here and its very useful..but one thing that i did not find in any site on how to remove global security group in an AD user..??..someone can help me please :)..i really need it in my daily task as an IT systems.

thank you in advance
Gerry

I am trying to write a script that will use a csv file to modify approximately 350 User accounts phone numbers (telephoneNumber). I have the DN exported and have the table built. How can I do this?

I am new to scripting.

I am using oyur example above a s a base..
$objUser = [ADSI]“LDAP://OU=Branches,DC=xyzcompany,DC=company”
$objUser.put(“telephoneNumber”, “?)
$objUser.setInfo()

I am also going to be scripting the OCS pieces (tel uri, etc) as well. Hopefully all in one script.

Thanks.

By johnrockfellerz on May 19th, 2011 at 11:50 pm

Apart from Bulk Management for all AD objects, ADManager Plus also offers 150+reports (for IT compliance) & helpdesk delegation! Specialty: Shortcuts for everyday AD & office management actions! Attraction: Affordable pricing and perfect scalability!

http://www.manageengine.com/products/ad-manager/

By Jay Kulsh on July 14th, 2012 at 9:15 pm

Script of Ryan T. Hilton (posted on October 27th, 2008) is really good. Thanks, Ryan.

Only if someone could explain why [void] is used with $Search.PropertiesToLoad and use of @ and e in the last line. Anyway, the script works!

Hi,

I am unable to remove the manager details from the disaled user account. It is one of the step to disable the user. i.e clear his reporing manager details. I am modying the “manager” property to Null but at getting an error:
$objUser.put(“manager”,”$Null”)

Exception calling “SetInfo” with “0″ argument(s): “The attribute syntax speci
ed to the directory service is invalid. (Exception from HRESULT: 0x8007200B)”

Please suggest.

Thanks,
Hussain

Hey there, You’ve done a fantastic job. I will definitely digg it and personally recommend to my friends. I am confident they’ll be benefited from this
web site.

Prescription Swimming Goggles

Spot on with this write-up, I absolutely feel this web site needs a
lot more attention. I’ll probably be back again to see more, thanks for the info!

Prescription Swimming Goggles

Jesse,

Great tutorial. This gives me a great start towards becoming a PowerShell guru – or at least proficient.
Thanks a lot.

Hi Jesse

Thank you for an awesome tutorial. I am not new to computers, scripting & programming, but I am completely new to Power Shell scripting. With your clear tutorial I am up and going in no time.

I would like to ask about the HR interface you referred to in Chapter 12 to add new users to AD. What software do the HR people use? Is it part LDAP & PS script or is this a third party package?

Thanks
W

Well done! A very complete step by step and explanation on the subject!! The best I found.

 

Leave a Comment