Managing Directory Services


Our recommendation is that if you have VBScripts to manage Active Directory using ADSI, continue to use them.

If your directory service management needs are simple, say you want to be able to find users, you can actually use WMI and the Get-Wmiobject cmdlet to query Active Directory:

GetLDAPUsers.ps1

image from book
 #GetLDAPUsers.ps1 $user=read-host "What user credentials do you want to use for" ` "authentication to the" `n ` "domain controller? Use format domain\username." $cred=get-credential $user $server=read-host "What domain controller do you want to connect to?" $rc=read-host "Do you also want to save output to a text file? [YN]" if ($rc -eq "Y") { $file=read-host "Enter the filename and path" write-host "Connecting to" $server "as" $user get-wmiobject -class ds_user -namespace root\directory\ldap ` -computername $server -credential $cred | ` select-object DS_Name,DS_distinguishedname,DS_sAMAccountname |` tee-object -file $file } else { write-host "Connecting to" $server "as" $user get-wmiobject -class ds_user -namespace root\directory\ldap ` -computername $server -credential $cred | ` select-object DS_Name,DS_distinguishedname,DS_sAMAccountname } 
image from book

This script connects to an Active Directory domain controller and returns a list of all user accounts including their distinguished name and downlevel SAM account name. You have the option of saving the output to a text file.

The core of the script is the Get-Wmiobject expression. For most other WMI-related scripts we've used the root\cimv2 namespace. However, in this script we're going to connect to the root\directory\ldap namespace on the specified server. In this namespace we are looking for objects that belong to the ds_user class. Once we have these objects we can use the Select-object cmdlet to return just the properties in which we are interested. This is what you get when you run the script:

 What user credentials do you want to use for authentication to the domain controller? Use format domain\username.: mycompany\administrator What domain controller do you want to connect to?: dc01 Do you also want to save output to a text file? [YN]: n Connecting to dc01 as mycompany\administrator s DS_Name                    DS_distinguishedname      DS_sAMAccountname -------                    --------------------      ----------------- Administrator              CN=Administrator,CN=Use... Administrator Guest                      CN=Guest,CN=Users,DC=my... Guest SUPPORT_388945a0           CN=SUPPORT_388945a0,CN=... SUPPORT_388945a0 krbtgt                     CN=krbtgt,CN=Users,DC=m... krbtgt __vmware_user__            CN=__vmware_user__,CN=U... __vmware_user__ svcSharepoint              CN=svcSharepoint,CN=Use... svcSharepoint Joe Hacker                 CN=Joe Hacker,OU=Testin... jhacker Jane Cracker               CN=Jane Cracker,OU=Test... jcracker ldog                       CN=ldog,OU=Testing,DC=m... ldog test user1                 CN=test user1,OU=omaha,... tuser1 Test User2                 CN=Test User2,OU=Testin... tuser2 Test User3                 CN=Test User3,OU=Testin... tuser3 Test User4                 CN=Test User4,OU=Testin... tuser4 Lucky Dog                  CN=Lucky Dog,OU=home,DC... lucky Steve McQueen              CN=Steve McQueen,OU=Tes... smqueen Bill Shakespeare           CN=Bill Shakespeare,OU=... bills azygot                     CN=azygot,OU=Testing,DC... azygot PS C:\> 

If you're interested in all properties for a specific account such as Administrator, or if you want to learn what user properties are available, you can use an expression like this:

 get-wmiobject -class ds_user -namespace root\directory\ldap ` >> "-computername MYDC -Credential (get-credential) | ` >> where {$_.dssAMAccountname -eq "Administrator"} >> 

If you want to do more than just build Active Directory reports in PowerShell, then you need to use the ADSI type. This feature allows you to create, modify, display, and delete Active Directory objects such as users, groups and computers. In order to use the type accelerator, you need to know the distinguished name of the object.

 PS C:\> $admin=[ADSI]"LDAP://CN=Administrator,CN=Users,DC=MyCo,DC=com" PS C:\> $admin distinguishedName ----------------- {CN=Administrator,CN=Users,DC=MyCo,DC=com} PS C:\> $admin.memberof CN=UnrestrictedUsers,OU=home,DC=MyCo,DC=com CN=Group Policy Creator Owners,CN=Users,DC=MyCo,DC=com CN=Domain Admins,CN=Users,DC=MyCo,DC=com CN=Enterprise Admins,CN=Users,DC=MyCo,DC=com CN=Schema Admins,CN=Users,DC=MyCo,DC=com CN=Administrators,CN=Builtin,DC=MyCo,DC=com PS C:\> $admin.WhenChanged Tuesday, October 17, 2006 2:38:28 AM 

Piping $admin to Get-Member will list what appears to be all the available ADSI properties of the object. If you are familiar with ADSI you'll realize that some properties are missing. Get-Member only displays properties with defined values. You can modify other properties as long as you already know the property name which we'll show you later. One other important reminder is that when you create an object, the property values are stored in a local cache. If the object is modified in Active Directory, you won't see the changes locally unless you refresh the cache. Here's how we would do it with our previous example:

 $admin.psbase.refreshcache() 

We can also use the ADSI type accelerator to create an object in Active Directory. Take a look at CreateUser.ps1:

CreateUser.ps1

image from book
 #CreateUser.ps1 #specify the OU where you want to create the account $OU=[ADSI] "LDAP://OU=Testing,DC=MyCo,DC=Local" #using the ADSI type specifier #Add the user object as a child to the OU $newUser=$OU.Create("user","CN=Francis Bacon") $newUser.Put("sAMAccountName","fbacon") #commit changes to Active Directory $newUser.SetInfo() #set a password $newUser.SetPassword("P@ssw0rd") $newUser.SetInfo() #Define some other user properties $newUser.Put("DisplayName","Francis Bacon") $newUser.Put("UserPrincipalName","Fbacon@MyCo.com") $newUser.Put("GivenName","Francis") $newUser.Put("sn","Bacon") #enable account = 544 #disable account = 546 $newUser.Put("UserAccountControl","544") $newUser.Put("Description","Created by PowerShell "` +(get-date).ToString()) #commit changes to Active Directory $newUser.SetInfo() #flag the account to force password change at next logon $newUser.Put("pwdLastSet",0) $newUser.SetInfo() 
image from book

Before you can create an object, you first must create an object for the parent container. In this example we're using the Testing organizational unit. To create a new user object we simply invoke the parent object's Create method and specify the type of child object and its name:

 $newUser=$OU.Create("user","CN=Francis Bacon") 

To define properties we'll use the Put method. When you create a user account you have to also define the sAMAccountname:

 $newUser.Put("sAMAccountName","fbacon") 

Before we can set any other properties, the object needs to be written from the local cache to Active Directory. This is accomplished by calling SetInfo:

 $newUser.SetInfo() 

To set the user's password, there is a SetPassword method that takes the new password as a parameter:

 $newUser.SetPassword("P@ssw0rd") 

Once this is accomplished, we can define some additional properties by using the Put method as you can see in the remainder of the script.

To modify an existing user, it is merely a matter of creating an ADSI user object and using the Put method to define user attributes. Don't forget to call SetInfo or none of your changes will be committed to Active Directory.

 PS C:\> $user=[ADSI]"LDAP://CN=Bill Shakespeare,OU=Testing` >> ,DC=MyCo,dc=local" >> PS C:\> $user.put("Title","Playwright") PS C:\> $user.Setinfo() PS C:\> $user.Title Playwright PS C:\> 

To delete a user we create an object for the parent container, typically an organizational unit then simply call the Delete method:

 PS C:\> $ou=[ADSI]" OU=SAPIEN,DC=MyCo,DC=local PS C:\> $ou.psbase.get_children() distinguishedName ----------------- {CN=SAPIEN Authors,OU=SAPIEN,DC=MyCo,DC=local} {CN=Test User2,OU=SAPIEN,DC=MyCo,DC=local} PS C:\> $ou.delete("user","CN=Test User2") PS C:\> 

There is no need in this situation to call SetInfo. As soon as you invoke the Delete method, the object is gone. You can use this method to delete any object. All you have to do is specify the object class and its name.

Creating a group is very similar to creating a user:

 PS C:\> $OU=[ADSI]"LDAP://OU=SAPIEN,DC=MyCo,dc=local" PS C:\> $newGroup=$OU.Create("group","CN=SAPIEN Authors") PS C:\> $newGroup.Put("sAMAccountName","SAPIEN-Authors") PS C:\> $newGroup.Put("Description","Contract Writers") PS C:\> $newGroup.SetInfo() PS C:\> 

Modifying group membership is not especially difficult. If you are familiar with this task in ADSI, conceptually it's not too different in PowerShell. As with ADSI, you need a DirectoryEntry object for the group. You also need to know the distinguished name of the user object you want to add. Armed with that information, it's a matter of adding the user's distinguished name to the object's Member property.

Here's a script that demonstrates how to modify group membership:

AddToGroup.ps1

image from book
 #AddToGroup.ps1 $Grp=[ADSI]"LDAP://CN=SAPIEN Authors,OU=SAPIEN,DC=MyCo,DC=local" $NewUserDN="CN=Bill Shakespeare,OU=Testing,DC=MyCo,DC=local" #create an array object from current group members $grpMembers=@($Grp.Member) #display current group membership Write-Host "There are currently" $grpMembers.Count "members in" $Grp.Name foreach ($user in $grpMembers) {$user} Write-Host `n; Write-Host "Adding" $NewUserDN ($grp.Member).add($NewUserDN) > $NULL #commit changes to Active Directory $Grp.SetInfo() #refresh object and display new membership list $Grp.psbase.refreshCache() $grpMembers=@($grp.Member) #display new membership Write-Host "There are now" $grpMembers.Count "members in" $grp.Name foreach ($user in $grpMembers) {  if ($user -eq $NewUserDN) {   write-Host -foregroundcolor Green $user  }  else  {  write-Host $user  } } 
image from book

This script creates an ADSI object for the SAPIEN Authors group and also creates an object for the current membership list that is displayed using ForEach.

Adding the new user appears a little confusing at first:

 ($grp.Member).add($NewUserDN) > $NULL 

What we need to do is to call the Add method for the group's Member property, which is a collection, and specify the user's distinguished name. By the way, if we wanted to nest another group, we would specify that group's distinguished name. The reason we redirect output to $Null is purely cosmetic. Without the redirection, the expression returns the number of members currently in the group. In the course of running the script, displaying that number here serves no purpose and is distracting. We eliminate it by redirecting any output to $Null.

None of this work means anything until we commit the change to Active Directory using SetInfo. The script finishes by refreshing the local cache and listing its new members, indicating the new user in a green font.

Even though we've been showing you how to use the ADSI type with the LDAP provider and Active Directory, it will also work with the WinNT provider. You can use this provider if you want a flat view of your domain or if you are working with member servers and desktops.

ListWinNT.ps1

image from book
 #ListWinNT.ps1 $member=[ADSI]"WinNT://MyServer"  foreach ($item in $member.psbase.children) {   if ($item.psbase.schemaclassname -eq "user") {    Write-Host $item.Name   } } 
image from book

This script will list every object that is of the user schema class and display the user name. If MyServer is a member workstation it will list local user accounts. If you substitute MyServer with the flat name of your domain, you will get a list of all user accounts regardless of what organizational unit they might belong. The displayed name will be the sAMAccount name.

We'll wrap up this section by showing you how easy it is to search in Active Directory with PowerShell. Because PowerShell is based on .NET, it can create a DirectorySearcher object using the New-Object cmdlet. Here's a short script that will return the distinguished name of every user account in an Active Directory domain:

SearchForAllUsers.ps1

image from book
 #SearchForAllUsers.ps1 $searcher=New-object DirectoryServices.DirectorySearcher $searcher.Filter="(&(objectcategory=person)(objectclass=user))" $users=$searcher.FindAll() #display the number of users Write-Host "There are "$users.count"users in this domain." #display each user's distinguishedname foreach ($user in $users) {  Write-Host $user.properties.distinguishedname } 
image from book

After the Directory Searcher object is created we define a search filter. The filter is an LDAP query string. In this case we want to find all objects that are basically user accounts. The Directory Searcher has two methods you are most likely to use: FindAll and FindOne. The former will return all objects that match the query and the latter will only return the first one it finds. In this script we create a new object to hold the query results. We can then use the For-Each cmdlet to display the distinguishedname property of each user in the result collection.

Fun With LDAP Filters

You don't have to have extensive knowledge about LDAP to build a complex query. If you are running Windows 2003 you already have a tool that will do it for you. In Active Directory Users and Computers there is a Saved Queries feature. When you create a query, an LDAP query string is generated. All you need to do is copy the string and use it as the directory searcher filter. For example, we created a saved query to find all disabled users that created a LDAP query string of (&(objectCategory = person) (objectClass = user) (userAccountControl:1.2.840.113556.1.4.803: = 2)). When we substitute this string for the filter in SearchForAllUsers.ps1, we get a list of every disabled user. The tool is pretty powerful and can create some very complex query strings. Now, you also use them in your PowerShell scripts.

There is a subtle but important fact when using the Directory Searcher object. The objects returned by the query aren't really the Active Directory objects but are more like pointers. The search result can give you some property information like distinguishedname, but if you want more specific object information you need to get the object itself.

This script is slightly modified from SearchForAllUsers.ps1:

SearchForAllUsersAdvanced.ps1

image from book
 #SearchForAllUsersAdvanced.ps1 $searcher=New-object DirectoryServices.DirectorySearcher $searcher.Filter="(&(objectcategory=person)(objectclass=user))" $users=$searcher.FindAll() #display the number of users Write-Host "There are "$users.count"users in this domain." foreach ($user in $users) {  foreach ($user in $users) {   $entry= $user.GetDirectoryEntry()   $entry |Select displayname,samaccountname,description,distinguishedname  } } 
image from book

In the ForEach loop, we create a new object called $entry by invoking the GetDirectoryEntry() method of the object that was returned by the query. This object gives us access to all the properties in Active Directory. In this script we selected to show DisplayName, sAMAccountName,Description and DistinguishedName. When executed we get a result like this:

 displayname    samaccountname       description       distinguishedname -----------    --------------       -----------       ----------------- {Administrator}  {Administrator}   {Built-in ac...    {CN=Administrat.. {Joe Hacker}     {jhacker}         {Another test user} CN=Joe Hacker... {Jane Cracker}   {jcracker}        {Test user}        {CN=Jane Crack... {Bill Shakespeare}{bills}          {Sample User Acc...{CN=Bill Shake... {}               {azygot}          {}                 {CN=azygot,OU=... {Jack W. Frost}  {jfrost}          {TFM}              {CN=Jack Frost... {Francis Bacon}  {fbacon}          {Created by Pow... {CN=Francis Ba... 

We mentioned that the directory searcher also has a FindOne method. This is very useful when you know there is only one result, such as finding the distinguishedname of a user when all you know is their sAMAccountname.

FindUserDN.ps1

image from book
 #FindUserDN.ps1 $sam=Read-Host "What user account do you want to find?" $searcher=New-Object DirectoryServices.DirectorySearcher $searcher.Filter="(&(objectcategory=person)(objectclass=user)"` +"(sAMAccountname="+$sam+"))" $results=$searcher.FindOne() if ($results.path.length -gt 1)  {write-host $results.path} else  {write-host "User" $sam "was not found."} 
image from book

This script is very similar to the other searching scripts. The primary difference is that the search filter is looking for user objects where the sAMAccountname is equal to a value specified by the user with the Read-Host cmdlet. Since we know there will only be one result, the searcher can stop as soon as it finds a match. We've added some error checking in the script. If a match was found, then the length of the path property will be greater than one so we can display it. Otherwise, there is no path which means no match was found so we can display a message to that effect.

The ADSI type accelerator is really just a wrapper for .NET directory service objects. It hides a lot of the underlying functionality. You can create PowerShell scripts that directly create and manipulate the .NET objects, but in our opinion that process is more like systems programming than scripting.

Read More About It

The MSDN documentation for the .NET directory service objects and the System.DirectoryServices.Namespace can be found at: http://msdn2.microsoft.com/en-us/library/system.directoryservices.aspx.



Windows PowerShell. TFM
Internet Forensics
ISBN: 982131445
EAN: 2147483647
Year: 2004
Pages: 289

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net