System Administration
Overview
Traditionally, administrators have had to resort to system-specific application programming interfaces (APIs) to perform system maintenance—for example, manipulating network resources, user accounts, or mailboxes. Windows provides Win32 APIs to manipulate such resources. These interfaces are generally difficult to use and not directly accessible to scripting environments such as Windows Script Host (WSH).
The introduction of Active Directory Services Interface (ADSI) makes performing administrative tasks in the Windows environment much more accessible as well as easier.
ADSI on its own doesn't perform any system administrative tasks, such as creating a user account, but it provides an interface to directory services. A directory service exposes access to system administrative tasks through directory providers. A few directory providers are listed in Table 14-1.
PROVIDER |
DESCRIPTION AND AVAILABILITY |
---|---|
WinNT |
Windows NT 4.0 and later. Performs user account, group, domain, service, file share, and print queue operations. |
LDAP |
Exchange server and Windows 2000. Performs user account and Active Directory administration under Windows 2000 and Exchange server administration, such as creating mailboxes and distribution lists. |
IIS |
Internet Information Server. Performs site creation, manipulation, and maintenance. |
NWCOMPAT |
Novell services. |
Each provider, such as WinNT or LDAP, exposes a namespace. A namespace exposes the objects in the provider. A reference to the object must always start with the namespace it resides in, such as WinNT:// for the Windows NT provider, LDAP:// for Active Directory and Exchange administration, or IIS:// for IIS servers.
Though ADSI is used to perform Active Directory operations in a Windows 2000/XP environment, having it installed does not mean you are using Active Directory. Installing ADSI on Windows NT 4.0 or Windows 9x/ME does not provide them with Active Directory capabilities.
The WinNT provider is installed on Windows NT 4.0 and Windows 2000 computers. There are operations and information that cannot be performed through LDAP and Active Directory on Windows 2000 that can be accessed through the WinNT provider.
Note |
For more information, refer to the following resources: "ADSI Overview" (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnmind99/html/cutting0599.asp), "An Introduction to ADSI" (http://www.asptoday.com/articles/19990310.htm),"Manage Directory Resources with Active Directory Services Interface" (http://ntmag.com/Articles/Index.cfm?ArticleID=258), Download page for ADSI (http://www.microsoft.com/ntserver/nts/downloads/other/Adsi25/default.asp),"Exploring the NT Directory Services" (http://www.asptoday.com/articles/19990806.htm), LabMice.net's Active Directory Resources (http://www.labmice.net/ActiveDirectory/default.htm), and the Active Directory newsgroup at news://microsoft.public.activedirectory.interfaces. |
Setting Domain Properties
Problem
You want to set the password length for a Windows NT domain.
Solution
You can reference the domain using the WinNT ADSI provider and then set the required properties:
Dim objDomain 'get a reference to the Acme domain Set objDomain = GetObject("WinNT://ACME") objDomain. MinPasswordLength = 6 objDomain.SetInfo
Discussion
The WinNT provider domain object exposes account policy information, such as password policies.
Use the GetObject function to bind to an NT domain object:
Set objDomain = GetObject("WinNT://DomainName")
DomainName represents the name of the Windows NT domain you are binding to. The WinNT provider name is case-sensitive, but object paths are not.
Once you have a bound to a domain object, you can set any of the properties listed in Table 14-2. These properties correspond to the Account Policies option under Windows NT User Manager for NT 4.0.
PROPERTY |
DESCRIPTION |
---|---|
MinPasswordLength |
Minimum password length required for user. If 0, then no minimum password length enforced. |
MinPasswordAge |
Minimum time a password must be used before it can be changed. Stored in seconds. If 0, a user can change his or her password immediately. |
MaxPasswordAge |
The maximum time a password can be used before it expires and must be changed. Stored in seconds. If 0, the password never expires. |
MaxBadPasswordsAllowed |
Number of bad passwords that can be entered before the account is locked out. |
PasswordHistoryLength |
Number of unique passwords a user must change before reusing a password. The maximum value is 24 passwords. If 0, no password history is kept. |
AutoUnlockInterval |
If the number of bad passwords a user enters exceeds the value set by MaxBadPasswordsAllowed, the user is "locked out" for the duration defined by AutoUnlockInterval. Stored in seconds. If 0, the user account must be unlocked by an administrator. |
LockoutObservationInterval |
Time that will elapse before the bad password counter is reset. Stored in seconds. |
When any properties have been changed, you must invoke the domain object's SetInfo method for any of the changes to take effect.
To get a reference to a Windows 2000 domain object using ADSI, you must reference the domain's distinguished name (DN) using the Lightweight Directory Access Protocol (LDAP) provider:
Set objDomain = GetObject("LDAP://distinguishedname")
A DN is a path to an object, and it is composed of the relative distinguished name (RDN) separated by commas. The RDN is used to identify objects and is composed of the attribute ID (object type) followed by the object's name property. In the case of dc=Acme, the attribute is dc and the value is Acme.
One way of thinking of a DN is as an Internet domain name. For example, microsoft.com is a valid Internet DNS address. The DN for a Microsoft domain controller would be dc=microsoft,dc=com. The name of the highest level object is at the end of the string.
ADSI requires the LDAP:// prefix for the DN so it knows what providers to use. The LDAP must be uppercase, while the DN is not case-sensitive.
To get a reference to the ACME domain, use this code:
Set objDomain = GetObject("LDAP://dc=Acme,dc=com")
LDAP exposes a RootDSE directory service object. This object provides information about the directory services. To get the RootDSE, use the following line:
Set objRootDSE = GetObject("LDAP://RootDSE")
Using the RootDSE, you can automatically find the most available directory server to use. The RootDSE returns a defaultNameContext property, which identifies the name of the current domain:
Dim objRootDSE, objDomain, strDomain 'get a reference to the rootDSE Set objRootDSE = GetObject("LDAP://RootDSE") 'get the domain strDomain = objRootDSE.Get("defaultNamingContext") 'get a reference to the domain object Set objDomain = GetObject("LDAP://" & strDomain)
Windows 2000 implements the WinNT and LDAP providers, but only the WinNT provider is available to Windows NT 4.0. Windows NT 4.0 requires ADSI to be installed separately.
The properties exposed through the WinNT domain object, such as MinPasswordAge, are not available through the LDAP domain object, because Active Directory implements such settings differently using Group Policies.
Corresponding objects exposed by the different providers are not guaranteed to expose the same properties, so in the case of domain information for the LDAP and WinNT providers, the properties implemented are different.
See Also
Search the ADSI SDK for the topic "binding." For more information, read the MSDN Library articles "Binding String" (http://msdn.microsoft.com/library/en-us/netdir/adsi/winnt_binding_string.asp), "ADSI Scripting Tutorial, Binding" (http://msdn.microsoft.com/library/en-us/netdir/adsi/binding.asp), "Domains" (http://msdn.microsoft.com/library/en-us/netdir/adsi/domains.asp), and "Provider Support of ADSI Interfaces" (http://msdn.microsoft.com/library/en-us/netdir/adsi/provider_support_of_adsi_interfaces.asp).
Determining a Computer s OS
Problem
You want to determine a computer's OS.
Solution
You can reference an ADSI computer object using the WinNT provider and then reference the OperatingSystem property:
Set objComputer = GetObject("WinNT://odin,computer") strOS = objComputer.OperatingSystem
Discussion
The Computer object represents an individual computer on your network. To get a reference to a computer object:
Set objComputer = GetObject("WinNT://computername,computer")
computername represents the name assigned to the computer. The ,computer that follows the computer name is optional, but it speeds the resolving of the object.
Table 14-3 lists the properties associated with the Computer object. These properties are read-only.
PROPERTY |
DESCRIPTION |
---|---|
Owner |
Name of individual/organization the OS was registered to. |
Division |
Name of individual/company the OS was registered to. |
OperatingSystem |
Operating system name (e.g., Windows NT). |
OperatingSystemVersion |
OS version (e.g., 4.0, 5.0). |
Processor |
Processor description (e.g., x86 Family 6 Model 5 Stepping 2). Doesn't directly correspond to a chip model such as Pentium II. |
ProcessorCount |
Number of processors (if supported). |
To get a reference to a computer using the LDAP provider and Active Directory, use the following line:
Set objComputer = GetObject("LDAP://ComputerName")
The computer object returned by the LDAP provider does not expose any of the properties the WinNT provider does; however, because Windows 2000 implements the WinNT provider as well as the LDAP provider, this doesn't matter.
See Also
For more information, read the MSDN Library articles "ADSI Scripting Tutorial, Binding" (http://msdn.microsoft.com/library/en-us/netdir/adsi/binding.asp) and "Provider Support of ADSI Interfaces" (http://msdn.microsoft.com/library/en-us/netdir/adsi/provider_support_of_adsi_interfaces.asp).
Listing Users
Problem
You want to list all users from a computer or domain.
Solution
You can bind to a WinNT domain or computer object and then set the Filter property, filtering on the User class:
Dim objDomain, objUser 'get a reference to a domain ojbect Set objDomain = GetObject("WinNT://Acme") 'filter on the user objects objDomain.Filter = Array("user") For Each objUser In objDomain Wscript.Echo objUser.Name Next
Discussion
You can use the Filter property to filter ADSI objects. The property takes an array of class names, which allows for multiple class values to be specified. In the following example, all users and groups for the Acme domain are counted:
Set objDomain = GetObject("WinNT://Acme") 'filter on the user and group objects objDomain.Filter = Array("user", "group") For Each obj In objDomain Wscript.Echo obj.Name Next
The class names specified in the array are not case-sensitive.
If the filter not is set, enumerating a computer or domain container object will return all objects in the container. This can be useful to determine all objects in that container.
'list all objects in the Acme domain and their type Set objDomain = GetObject("WinNT://Acme") 'loop through each object, listing the name and object class For Each obj In objDomain Wscript.Echo obj.Name, obj.Class Next
Filters can be set on Active Directory container objects in exactly the same way as WinNT:
'list all users in the Users container Set objCN = GetObject("LDAP://cn=Users,DC=acme,DC=com") objCN.Filter=Array("User") For Each obj In objCN Wscript.Echo obj.Name Next
When an Active Directory container is enumerated, no attempt is made to enumerate subcontainers. In the following example, a reference is made to the container for the domain object for Acme.com and the user class is enumerated:
'list all users in the Users container Set objCN = GetObject("LDAP://DC=acme,DC=com") objCN.Filter=Array("User") For Each obj In objCN Wscript.Echo obj.Name Next
In this example, you would assume that all users belonging to the Acme domain would be listed, but only user objects from the domain container (and usually there shouldn't be any) will be listed. None of the containers below the domain container, such as the Users container, will be searched. Solution 14.16 provides a method of querying Active Directory containers using ADO.
See Also
Solution 14.16. For more information, read the MSDN Library article "Listing Users" (http://msdn.microsoft.com/library/en-us/netdir/adsi/listing_users.asp).
Creating a New User
Problem
You want to create a new user.
Solution
Using the WinNT provider, get an instance of a domain or computer object where you want to create a new user and invoke the Create method:
'bind to a domain Set objDomain = GetObject("WinNT://ACME") 'create a new user - Fred Smith Set objUser = objDomain.Create("User", "FredS") objUser.SetPassword("iu12yt09") objUser.SetInfo
Discussion
To create a new user with the WinNT provider, you first need to reference the domain or computer object in which you want to create the user.
Then invoke the object's Create method, specifying the User class as the object you want to create. When creating a user, you must be logged on with the appropriate administrative security level to create and manipulate user objects. The syntax is as follows:
Set objUser = objContainer.Create("User", strUserName)
strUserName represents the user name that is used to log on. The objContainer object represents the domain or computer in which you want to create the user.
Once the user has been created, invoke the object's SetInfo method to save the changes. No additional properties need be set once a new user has been created using the WinNT provider.
The solution demonstrates the SetPassword method. The SetPassword method sets the password for the current user object:
objUser.SetPasswordstrPassword
The strPassword parameter is the password you want to set for the user.
Creating a user using the LDAP provider under Windows 2000 requires a similar procedure as using the WinNT provider:
'bind to the container to add user to. In this example the acme domain. Set objContainer = GetObject("LDAP://cn=Users,dc=acme,dc=com") Set objUser = objContainer.Create("User", "cn=Fred Smith ") objUser.Put "samAccountName", "freds" objUser.SetInfo objUser.pwdLastSet = -1 objUser.SetPassword "we12oi90" objUser.AccountDisabled = False objUser.SetInfo
First, a reference to a container is required. Under Windows NT all users were added to a single list, essentially a single large container. Active Directory allows additional containers to be created to organize users. The commonly used container to perform this task is the organizational unit.
Windows 2000/XP and Active Directory provides organizational units to organize objects into logical groupings. For example, users could be organized by departments, so you could create an organizational unit for accounting, finance, marketing, and so on.
To create an organizational unit, get a reference to the parent container, which is the container where the organizational unit will reside.
Then call the Create method, specifying organizationalUnit as the object class to be created followed by the organizational name in RDN format:
Set objCN = GetObject("LDAP://ou=Accounting,DC=Acme,DC=com") Set objOU = objCN.Create("organizationalUnit","ou=Accounting")
To get a reference to a given organizational unit, you must specify the organizational unit name together with full domain DN:
'get a reference to account organizational unit container Set objContainer = GetObject("LDAP://ou=accounting,dc=acme,dc=com")
Organizational units can be nested, allowing even greater organizational granularity:
Set objCN = GetObject("LDAP://ou=Accounting,DC=Acme,DC=com") Set objOU = objCN.Create("organizationalUnit","ou=Taxation")
Users and groups can be created in organizational units. Organizational units may seem like a different way of creating a user group, but they allow for control of administrative delegation. For example, a user in the accounting department could be granted access to administer users in the accounting organizational unit, but nowhere else.
The Users container is a built-in container Windows 2000/XP provides for Windows NT 4.0 user compatibility. If you did not specify a specific container in the user string, the user would have been created at the root of the Windows 2000/XP domain tree. This is a perfectly valid, but not very logical place, to put users.
The LDAP user name specified for the Create method is a bit more involved than the user name specified for the WinNT provider. In the previous code snippet, cn=Fred Smith indicates the user name is Fred Smith.
The following example gets a reference to the Accounting organizational unit (OU) container and adds a user to it:
Set objContainer = GetObject("LDAP://OU=Accounting,DC=acme,DC=com") Set objUser = objContainer.Create("User", "CN=Fred Smith") objUser.Put "samAccountName", "freds" objUser.SetInfo
The user name (Fred Smith) is not used to identify the person when logging on. The samAccountName property that is set after the creation of the user is used to identify the user when logging on, and it must be set when creating a new Active Directory user.
The samAccountName property represents the user ID that non-Windows 2000/XP clients (Windows 9x/ME, Windows NT) would use to log on. Windows 2000/XP uses this ID in NT domain or mixed-mode authentication, in a Windows 2000-only environment, Windows 2000 clients would use a user principal name (UPN).
A UPN user ID looks like an Internet e-mail address. If not set, the UPN is created using the samAccountName property followed by the at sign (@) and an Internet-style fully qualified domain name representing the organizational domain. So in the previous example, the UPN for Fred Smith would be freds@acme.com.
You can set an alternative UPN for a user by setting the userPrincipalName property:
Set objContainer = GetObject("LDAP://OU=Accounting,DC=acme,DC=com") Set objUser = objContainer.Create("User", "CN=Fred Smith") objUser.Put "samAccountName", "freds" objUser.Put "userPrincipalname", "fredsmith@acme.com" objUser.SetInfo
See Also
For more information, read the MSDN Library articles "Creating Local Users" (http://msdn.microsoft.com/library/en-us/netdir/adsi/creating_local_users.asp) and "ADSI Scripting Tutorial, Binding" (http://msdn.microsoft.com/library/en-us/netdir/adsi/binding.asp).
Listing Object Properties
Problem
You want to list properties that are associated with an object and their corresponding set values.
Solution
You can bind to any instance of an ADSI object and then reference the Schema property. Enumerate the MandatoryProperties and OptionalProperties collections.
The following command-line script takes the path to an ADSI object (for any ADSI provider) and enumerates the mandatory and optional properties for the object:
'listprop.vbs 'lists schema properties for specified object Dim objClass,varAttrib, aval, objObject, strLine On Error Resume Next If Wscript.Arguments.Count <> 1 Then ShowUsage Wscript.Quit End If 'get the object Set objObject = GetObject(Wscript.Arguments (0)) If Err Then Wscript.Echo "Unable to get object "& Wscript.Arguments (0) Wscript.Quit End If Set objClass = GetObject(objObject.Schema) Wscript.Echo "Mandatory Attributes: " For Each varAttrib In objClass.MandatoryProperties strLine = " "& varAttrib If IsArray(objObject.Get(varAttrib)) Then If Not Err Then For Each aval In objObject.Get(varAttrib) varAttrib = varAttrib & "," & aval Next End If Else strLine = strLine & " "& objObject.Get(varAttrib) End If If Err Then strLine = strLine & " No value" Err.Clear Wscript.Echo strLine Next Wscript.Echo "Optional Attributes: " For Each varAttrib In objClass.OptionalProperties strLine = " "& varAttrib objObject.GetInfoEx Array(varAttrib), 0 'check if object is an array If IsArray(objObject.Get(varAttrib)) Then If Not Err Then For Each aval In objObject.Get(varAttrib) strLine = strLine & "," & aval Next End If Else strLine = strLine & " "& objObject.Get(varAttrib) End If If Err Then strLine = strLine & " No value" Err.Clear Wscript.Echo strLine Next Sub ShowUsage WScript.Echo "listprop list properties for specified object" _ & vbCrLf & "Syntax:" & vbCrLf & _ "listprop.vbs objectpath" & vbCrLf & _ "objectpath Path to " & vbCrLf & _ "Example: List details about computer Odin" & vbCrLf & _ "listprop WinNT://Odin,computer" End Sub
Discussion
The tables throughout this chapter list a number of important properties related to the various ADSI objects. However, there are literally hundreds of properties that are not listed here. And as a result of the extensible nature of Active Directory, additional attribute properties may be added to an object.
The class schema determines what objects and properties are exposed through the object. For each object, there may be many more properties than the ones that can be set using management applications for the underlying directory store, such as User Manager for Windows NT 4.0.
To get a reference to the schema, reference the Schema property of any given ADSI object.
The Schema object contains two properties that list the properties associated with the object: MandatoryProperties and OptionalProperties.
The MandatoryProperties property is a collection that stores the properties required to make the object functional.
The OptionalProperties property is a collection that stores the optional properties. This might not be always totally accurate, because there may be optional properties that are required to be set before an object is made functional.
To list the associated properties, you must get a reference to an object's schema:
'get an ADSI object Set objClass = GetObject(strPath) 'get the schema for the object Set objClass = GetObject(objObject.Schema)
Once you have the schema, you can enumerate the MandatoryProperties and OptionalProperties collections. They contain the name of each property.
Enumerating the OptionalProperties and MandatoryProperties collections lists all properties associated with an object. If you just need to list properties that contain values, use the object's PropertyCount property, which returns the number of set properties for an object.
The PropertyCount property returns the count for all properties that have been loaded into the cache. To ensure it gives an accurate count of properties, call the GetInfo method before using PropertyCount. The following script lists all set properties for the user freds:
Set objUser = GetObject("WinNT://Acme/freds,user") objUser.GetInfo For nF = 0 To objUser.PropertyCount - 1 Wscript.Echo objUser.Item(nF).Name Next
The solution lists all properties for a given ADSI path:
listprop ADSIpath
ADSIpath is the path to any ADSI object, so use the following line to list all properties for the computer Odin using the WinNT provider:
listprop WinNT://Odin,computer
If the ADSI path contains spaces, it must be surrounded by double quotes:
listprop "LDAP://CN=Fred Smith,OU=Accounting,DC=Acme,DC=com"
With the listprop command-line utility, you can easily determine all properties associated with an object. One way of determining what property is associated with a field in a management interface (such as User Manager or the Active Directory snap-in under Windows 2000) is to fill in all of the fields with easily distinguishable values and then run listprop against the object.
See Also
For more information, read the MSDN Library article "Modifying User Properties" (http://msdn.microsoft.com/library/en-us/netdir/adsi/modifying_user_properties.asp).
Setting Object Properties
Problem
You want to set user properties.
Solution
You can use the dot or Put method to set values.
The following sample sets the name and description properties for an NT user:
'get a reference to user Set objUser = GetObject("WinNT://Acme/freds,user") 'set fullname and description properties objUser.FullName = "Fred Smith" objUser.Put "Description", "Accounting Manager" objUser.SetInfo
Discussion
To set a property, use the dot method:
objUser.Property = Value
Alternatively, you can use the Put method to assign properties:
objUser.Put strProperty, value
strProperty represents the property name and value is the value to assign to the property.
When an object is bound, it is cached locally. When a property is read or set, the property values for the object are loaded into the local cache. Changing a property value updates the local cached copy of the object but not the underlying directory object.
Use the SetInfo method to update the underlying directory store, such as Windows NT security database, Windows 2000 Active Directory, Exchange server, and so on. Any changes to properties do not take effect until SetInfo is invoked:
'get a reference to user Set objUser = GetObject("WinNT://Acme/freds,user") 'set fullname and description properties objUser.FullName = "Fred Smith" objUser.Put "Description", "Accounting Manager" objUser.SetInfo
If you are setting a large number of properties, you should call SetInfo once after all the properties have been set instead of calling it after every single property has been set to limit traffic to the underlying directory store.
To get an object's property, use either the Get or dot method:
'get a reference to user Set objUser = GetObject("WinNT://Acme/freds,user") 'read description using Get and dot method Wscript.Echo objUser.Description Wscript.Echo objUser.Get "Description"
Some properties do not automatically load into the ADSI cache, and when you reference a property it doesn't reflect any changes made by other uses since the object was created. The GetInfo method forces the reload of the cache:
objObject.GetInfo
Some properties fail to load even after using GetInfo and require the GetInfoEx method to read the information. The GetInfoEx method requires you to specify the properties you are attempting to reference:
objMailbox.GetInfoEx aProperties, nValue
The aProperties parameter is an array of property names you want to load. The nValue parameter is currently not used and must be set to 0.
Set objUser = GetObject("WinNT://Acme/freds,user") objUser.GetInfoEx Array("Description"), 0
Unfortunately, there are no rules as to what properties require the GetInfo or GetInfoEx methods to load information.
Table 14-4 lists properties available through the WinNT provider.
PROPERTY |
DESCRIPTION |
---|---|
AccountDisabled |
Boolean. Determines if the user account is disabled. |
AccountExpirationDate |
Date the account will expire. To disable account expiration date, set the property to Empty. |
BadPasswordAttempts |
Number of unsuccessful logon attempts. |
Description |
Account description. |
Fullname |
Full name of the user. |
HomeDirDrive |
Directory letter to associate with the user's home directory (e.g., H:). |
HomeDirectory |
User's home directory. If set to network share using UNC format, the network home directory is assumed and the HomeDirDrive property is required to be assigned a directory. If set to a local directory (e.g., d:data), the HomeDirDrive setting is ignored. |
IsAccountLocked |
Boolean. If the account becomes locked out due to login failure, this flag is set to True. An account cannot be manually locked out by setting this value (the value cannot be set to True). To unlock an account, set the property to False. |
LastLogin |
Date and time of last login. Read-only. |
LastLogoff |
Date and time of last logoff. Read-only. |
LoginHours |
Array of bytes. Hours a user is allowed to log in. See Solution 14.9 for how to set hours. |
LoginScript |
Name of the logon script/batch file to execute upon logon. |
LoginWorkstations |
Array of string values. Determines what machine a user can log on to. See Solution 14.10 for how to set the LoginWorkstations property. |
PasswordExpired |
Indicates if the user's password has expired. If set to 1, the password is flagged as expired and the user must change his or her password at next logon. This is the same as the "User Must Change Password at Next logon" flag under in the Windows NT User Manager. |
PasswordExpirationDate |
Date and time of password expiration. Read-only, cannot be changed. |
Profile |
Path to the directory where profile information is stored. |
UserFlags |
Integer. Combination of bitwise OR-ed values that determine options such as account status and password limitations. See Solution 14.11. |
If you examine the ADSI User object using an object browser as shown in Figure 14-1, you will notice that more properties are available than listed in Table 14-4.
Figure 14-1: Active Directory Object Browser
Some properties, such as EmployeeID, cannot be set under NT 4.0 using administrative tools such as User Manager. This is a result of the fact that the ADSI User object contains generic properties that might be applicable to users in other providers. So while it may seem as if there are more properties available than the provider exposes, they cannot all be used.
When you work with properties, make sure the program has error handling (On Error Resume Next) enabled to catch any errors. Some properties are not set until certain operations take place, and referencing the property may generate an error because it doesn't exist in the cache for the object. For example, the User object does not set the LastLogin property until a user has logged on for the first time. Properties that are not set usually do not contain values to identify them as being empty, such as a Null or Empty value, so it is not easy to test for these conditions.
One way to determine the properties associated with directory objects is to use a directory browser application. This kind of application allows you to view, and in some cases set, values associated with directory objects.
The Windows 2000 CD includes a utility called ADSIEdit. It is not installed with Windows 2000 but it is included as part of a support tools installation located under the support ools directory of the Windows installation CD.
The ADSI Resource Kit 2.5 contains two visual browsers: Active Directory Browser and DSBrowse. The Active Directory Browser can browse any object and lists associated properties.
The Active Directory Browser is located under the ADSI SDKAdsVWi386 directory, while DSBrowse is located under ADSI SDKSamplesGeneralDSbrowse. Figure 14-2 shows a screen shot of the Active Directory Browser.
Figure 14-2: Active Directory Browser
The Active Directory provider exposes more properties than the WinNT provider because there is much more information that can be stored in Windows 2000 Active Directory for each account:
'get a reference to a user object Set objUser = GetObject("LDAP://CN=Fred Smith,OU=Accounting,DC=acme,DC=com") 'set a property objUser.Put "samAccountName", "freds" objUser.SetInfo
Table 14-5 lists a number of Windows 2000 Active Directory properties.
PROPERTY |
DESCRIPTION |
---|---|
BadLoginCount/badPwdCount |
Number of bad passwords. |
c |
Country code (e.g., US). |
Department |
User's department. |
Division |
Company division. |
EmailAddress/mail |
E-mail address. |
EmployeeID |
Employee ID. |
FaxNumber |
Fax number. |
FirstName |
User's first name. |
HomeDrive |
Directory letter to associate with the user's home directory. |
logonCount |
Number of times the user has logged on. |
HomePage |
User's home page. |
L |
Locale/city. |
LastFailedLogin |
Date. Last failed logon. |
LastName |
Last name. |
Manager |
LDAP path to user's manager (e.g., CN=Administrator,CN=Users,DC=Acme,DC=com). |
NamePrefix |
Name prefix (e.g., Mr.). |
Notes/Comment |
User notes. |
OfficeLocations |
Office address. |
PasswordLastChanged |
Date and time password last changed. |
PostalAddress |
User's address. |
PostOfficeBox |
User's post office box. |
PostalCode |
Postal code. |
Sn |
Surname. |
St |
State. |
StreetAddress |
Address property. |
TelephoneHome |
Home phone number. |
TelephoneMobile |
Mobile phone number. |
TelephoneNumber |
Mobile phone number. |
TelephonePager |
Pager number. |
Title |
Employee title. |
userSharedFolder |
User shared document folder. |
UserAccountControl |
Integer. Combination of bitwise OR-ed values that determine options such as account status and password limitations. See Solution 14.11 later in chapter. Same as the WinNT provider's UserFlags property. |
The usermnt.wsf script is a command-line utility that provides the ability to create or update users using the Windows NT ADSI provider:
For example, the following command sequence creates a new user, "freds," and sets the Fullname and Description properties:
usermnt.wsf Acme freds /p:fullname "Fred Smith" /p:description "Accountant"
If you want to update existing users, add the /u switch to the command line. The following command line updates the description for user freds:
usermnt.wsf Acme freds /u /p:description "Accountant"
The usermnt.wsf script includes an adsilib.vbs library of reusable support code. This code is used throughout the chapter:
'adsilib.vbs 'Description: Contains routines used by ADSI scripts 'Gets the value of a server object based on its server comment/name 'Parameters: 'objWebService WebService object 'strSiteName Site name you wish to get value 'Returns: Site number, blank string if not found Function FindSiteNumber(objWebService, strSiteName, strType) Dim nF, objSite nF = "" 'loop through each site, find available site # For Each objSite In objWebService 'check if the object is a web site If strcomp(objSite.Class,"IIs" & strType & "Server",1)=0 Then 'check if server comment is same as specified server name If Ucase(objSite.ServerComment) = Ucase(strSiteName) Then nF = objSite.Name Exit For End If End If Next FindSiteNumber = nF End Function 'returns the 'Parameters 'strType site type, web or FTP Function GetSiteType(strType) Select Case Ucase(strType) Case "FTP" GetSiteType = "MSFTPSVC" Case "SMTP" GetSiteType = "SmtpSvc" Case "NNTP" GetSiteType = "nntpSvc" Case Else GetSiteType = "W3SVC" End Select End Function 'Find next available site number ' Function FindNextSite(objService) Dim nF, objSite nF = 0 'loop through each site, find available site # For Each objSite In objService 'check if object is a IIS site If Left(objSite.Class,3) = "IIs" And _ Right(objSite.Class,6)= "Server" Then If nF < objSite.Name Then nF = objSite.Name End If Next FindNextSite = nF + 1 End Function 'check if script is being run interactively 'Returns:True if run from command line, otherwise False Function IsCscript() If strcomp(Right(Wscript.Fullname,11),"cscript.exe",1)=0 Then IsCscript = True Else IsCscript = False End If End Function 'display an error message and exits script 'Parameters: 'strMsg Message to display Sub ExitScript(strMsg) Wscript.Echo strMsg Wscript.Quit -1 End Sub
See Also
Search for the topic "Getting Properties for Active Directory Objects" in the ADSI 2.5 SDK Help. For more information, read the MSDN Library articles "Getting and Setting Properties" (http://msdn.microsoft.com/library/en-us/netdir/adsi/getting_and_setting_properties.asp), "Modifying User Properties" (http://msdn.microsoft.com/library/en-us/netdir/adsi/modifying_user_properties.asp), "WinNT Schema's Mandatory and Optional Properties" (http://msdn.microsoft.com/library/en-us/netdir/adsi/winnt_schemaampaposs_mandatory_and_optional_properties.asp), and "Provider Support of ADSI Interfaces" (http://msdn.microsoft.com/library/en-us/netdir/adsi/provider_support_of_adsi_interfaces.asp).
Setting Multivalued Properties
Problem
You want to read and update multivalued properties.
Solution
You can use the PutEx method to set, update, and clear multivalued properties:
Const ADS_PROPERTY_CLEAR = 1 Const ADS_PROPERTY_UPDATE = 2 Const ADS_PROPERTY_APPEND = 3 Const ADS_PROPERTY_DELETE = 4 Set objUser = GetObject("LDAP://CN=Freds,CN=Users,DC=Acme,DC=com") 'add new home phone numbers to the user objUser.PutEx ADS_PROPERTY_APPEND, "OtherhomePhone", _ Array("555-1234", "222-2222") objUser.SetInfo 'deletes a fax number objUser.PutEx ADS_PROPERTY_DELETE, "otherFacsimileTelephoneNumber", _ Array("555-3453") objUser.SetInfo 'updates (sets) the addtional mailbox addresses for the user objUser.PutEx ADS_PROPERTY_UPDATE, "otherMailbox", _ Array("freddy@acme.com", "fred@acme.com") objUser.SetInfo 'clear mobile phone objUser.PutEx ADS_PROPERTY_CLEAR, "OtherMobile", Null objUser.SetInfo
Discussion
Provider properties can be multivalued. Multivalued properties may contain one or more values. Use the GetEx method to reference values from multivalued properties:
aValues = objADSI.GetEx(strProperty)
The GetEx method returns an array of values for the property specified by the strProperty argument.
Set objUser = GetObject("LDAP://cn=Fred Smith,cn=Users,dc=acme,dc=com") aOtherPhone = objUser.GetEx("OtherTelephone") For Each strPhone In aOtherPhone Wscript.Echo strPhone Next
GetEx can return an error if the property hasn't been set in the underlying directory. For example, if no additional phone numbers have been set for an Active Directory user, an error will occur if you attempt to retrieve the OtherTelephone property.
The PutEx method adds, deletes, and sets multivalued properties:
objUser.PutEx intAction, strProperty, vValue
The intAction parameter determines how the value will be stored in the multivalued property array. Table 14-6 lists the valid intAction values.
ACTION VALUE |
VALUE |
DESCRIPTION |
---|---|---|
ADS_PROPERTY_CLEAR |
1 |
Clears the property. |
ADS_PROPERTY_UPDATE |
2 |
Sets the property. |
ADS_PROPERTY_APPEND |
3 |
Appends a value to the property. |
ADS_PROPERTY_DELETE |
4 |
Deletes a value from the property. Error occurs if the value specified to delete does not exist. |
The strProperty parameter is the property you want to maintain. The vValue parameter is the value(s) you are adding, setting, or removing from the property.
Table 14-7 contains a number of multivalued properties for the Active Directory provider.
PROPERTY |
DESCRIPTION |
---|---|
OtherFacsimileTelephoneNumber |
Additional fax numbers |
OtherHomePhone |
Additional home phone numbers |
OtherIpPhone |
Additional IP phone addresses |
OtherMailbox |
Additional mailboxes |
OtherMobile |
Additional mobile/cellular phone numbers |
OtherPager |
Additional pager numbers |
OtherTelephone |
Additional office phone numbers |
See Also
Search for "Example Code for Using PutEx" in the ADSI 2.5 SDK Help file.
Deleting a User
Problem
You want to delete a user.
Solution
Using the WinNT ADSI provider, reference a computer or domain object in which the user resides and then invoke the Delete method:
Dim objDomain 'get a reference to the Acme domain Set objDomain = GetObject("WinNT://Acme") 'delete a user objDomain.Delete "user", "Freds"
For the Active Directory provider, you must get a reference to the container the object the user resides in and call the Delete method using the relative distinguished name (RDN) for the user you want to delete:
'delete a user using the Active Directory provider Dim objContainer 'get a reference to the LDAP container that contains the object to delete Set objContainer = GetObject("LDAP://OU=Accountants,DC=Acme,DC=com") 'delete Fred Smith by specifying his relative distinguished name objContainer.delete "user", "CN=Fred Smith"
Discussion
To delete a user, get a reference to the container that you want to delete the user from. Under the Windows NT provider, the container is the domain or computer the user resides in.
Once you have a reference to the container, invoke the Delete method:
objContainer.Delete strClassName, strObjectName
strClassName specifies the object class type to delete, for a user the class name is "user." strObjectName represents the user to delete. For the Windows NT provider, this is the user ID.
See Also
For more information, read the MSDN Library article "Removing Users" (http://msdn.microsoft.com/library/en-us/netdir/adsi/removing_users.asp).
Setting User Logon Time
Problem
You want to set user logon times.
Solution
The following command-line script sets the logon time for a specified user. It uses the WinNT provider to get a reference to a user and updates the LoginHours property with any changes specified from the command prompt:
Discussion
The logon hours allowed control of what hours a user can log on to the system. The user can be allowed/disallowed any 1-hour block within a 7-day period. This can be set from NT User Manager or the Windows 2000 Active Directory plug-in, as shown in Figure 14-3.
Figure 14-3: Logon Hours dialog box
The logon hours are represented by an array of byte (8-bit) values. Each bit represents 1 hour, so the entire week of 168 hours (7×24) is represented by an array of 21-byte values (218). If the bit is set to 1, access is allowed, if it is set to 0, access is disallowed during that period.
The LogonHours property returns the array of values, which has an offset of 0. The first element in the array stores the period of 8 hours from 9 AM Sunday to 5 PM Sunday. The second array element stores from 5 PM Sunday to 1 AM Monday, and so on.
Unfortunately, the LogonHours property is returned as an array of byte values. VBScript cannot handle this data type (it recognizes it, but can't deal with it). The following code binds to a user and returns the LoginHours property, but an error is generated when it tries to enumerate the array:
'get user Set objuser = GetObject("WinNT://acme/freds,user") 'get the login hours obj = objuser.LoginHours 'display data type Wscript.Echo Vartype(obj) 'try to enumerate array, an error will occur: For nF = Lbound (obj) to ubound (obj) Wscript.Echo obj(nF) Next
A simple wrapper object BAC.Convert (Binary Array Conversion) written in Visual Basic is provided to solve this problem. The object converts an array of byte values returned from functions to an array of variant values, and it also converts an array of variants to an array of byte values to pass to COM methods and functions that require this data type as a parameter.
The BAC object can be used for any COM object that returns or requires a parameter as an array of bytes.
The DLL is located in the supporting file for download at the Apress Web site (http://www.apress.com/). To use the BAC component, you must have VB runtime installed. Copy BAConv.DLL to your system and register it using Regsvr32.exe.
The BAC component exposes two functions: ByteToVariant and VariantToByte. Their syntax is as follows:
arrVar = ByteToVariant(arrByte) arrByte = VariantToByte(arrVar)
ByteToVariant converts an array of byte values supplied by the arrByte parameter to an array of variants.
VariantToByte converts an array of variant values supplied by the arrVar parameter to an array of byte values.
The following snippet uses the BAC object to convert the array of bytes the LoginHours property returns to an array of variant values that VBScript can manipulate:
'get a user object Set objUser = GetObject("WinNT://acme/freds,user") 'create a Byte array conversion object Set objBAC = CreateObject("BAC.Convert") 'get the login hours using the BAC object obj = objBAC.ByteToVariant(objUser.LoginHours) 'enumerate array For nF = Lbound (obj) to ubound (obj) Wscript.Echo obj(nF) Next
The sethours.wsf solution script sets the logon hours for a user:
sethours.wsf container user day hour duration onoff
The container parameter is a computer or domain in which the user resides. The user is the user ID for whom you set the logon hours. The day parameter is an integer value representing the day to set the logon access for: 1 is Sunday, 2 is Monday, and so on. Hour is the start hour to set the logon access for in 24-hour clock format: 0 is 12 AM, 13 is 1 PM, and so on. Duration is number of hours to set. Onoff is a Boolean value indicating whether to grant or deny access. If onoff is True, access is granted, and if False, access is denied.
To deny access to user freds in the Acme domain on Sunday from 1 PM to 4 PM, use the following line:
sethours.wsf Acme freds 1 13 3 False
Limiting Computer Access
Problem
You want to limit the machines on which a user can log on.
Solution
Using the ADSI WinNT provider, you can set the LoginWorkstations property to limit the computers on which a user can log on:
'get a reference to the user Set objUser = GetObject("WinNT://Acme/freds") 'list the machines the users have access to For Each station In objUser.LoginWorkstations Wscript.Echo station Next 'set the machines a user is permitted to logon to objUser.LoginWorkstations = Array("thor"," odin"," loki") objUser.SetInfo
Discussion
Windows NT and Windows 2000 provide the ability to limit the machines users can log on to.
The WinNT provider's LoginWorkstations property returns an array of machine names that the user can access.
The ADSI provider can use the LoginWorkstations property, but it includes a UserWorkstations property as well. The UserWorkstations property is a string that contains workstation names separated by commas:
'set the workstations a user can log on to using the Active Directory provider Dim objUser 'get a reference to a user object Set objUser=GetObject("LDAP://CN=Fred Smith,OU=Accounting,DC=acme,DC=com") objUser.UserWorkstations =" odin,thor,loki" objUser.SetInfo 'update user settings
If workstation limitations have not been set under User Manager, an error will occur if you try to read the LoginWorkstations property.
Setting User Flags
Problem
You want to prevent a user's password from expiring.
Solution
You can set the UF_DONTEXPIRE_PASSWD bit of the UserFlags property for the WinNT provider:
Const UF_DONTEXPIRE_PASSWD = 65536 Set objUser = GetObject("WinNT://ACME/fsmith,user") objUser.Put "userFlags", usr.Get("UserFlags") Or ADS_UF_DONTEXPIRE_PASSWD objUser.SetInfo
Discussion
The Windows NT provider's User object's UserFlags property is a combination of values that control various user settings. Table 14-8 lists the WinNT provider's UserFlags values.
FLAG |
VALUE |
DESCRIPTION |
---|---|---|
UF_SCRIPT |
1 |
The logon script will be executed. |
UF_ACCOUNTDISABLE |
2 |
The user's account is disabled. |
UF_LOCKOUT |
16 |
The account is currently locked out. |
UF_PASSWD_NOTREQD |
32 |
No password is required. |
UF_PASSWD_CANT_CHANGE |
64 |
The user cannot change the password. |
UF_TEMP_DUPLICATE_ACCOUNT |
256 |
This is an account for users whose primary account is in another domain. This account provides user access to this domain, but not to any domain that trusts this domain. Sometimes it is referred to as a local user account. |
UF_NORMAL_ACCOUNT |
512 |
This is a default account type that represents a typical user. |
UF_DONTEXPIRE_PASSWD |
65536 |
Represents the password, which should never expire on the account. |
Use the logical operators OR and XOR to perform bitwise operations to set flags. To set a flag, OR the value against the UserFlags property:
'prevent user from changing their own password Const UF_PASSWD_CANT_CHANGE = 65536 Set objUser = GetObject("WinNT://ACME/fsmith,user") objUser.Put "userFlags", usr.Get("UserFlags") Or UF_PASSWD_CANT_CHANGE objUser.SetInfo
To turn a flag off, use the AND NOT Boolean operation. If you want to "toggle" a flag, use the XOR operator. The XOR operator works like a toggle, so it can turn a bit on or off:
Const UF_PASSWD_CANT_CHANGE = 64 Const UF_DONTEXPIRE_PASSWD = 65536 Dim objUser Set objUser = GetObject("WinNT://ACME/fsmith,user") 'turn of the UF_PASSWD_CANT_CHANGE flag objUser.userFlags = objUser.userFlags And Not UF_PASSWD_CANT_CHANGE 'toggle the UF_DONTEXPIRE_PASSWD flag objUser.userFlags = objUser.userFlags Xor UF_DONTEXPIRE_PASSWD objUser.SetInfo
The Active Directory provider does not have a UserFlags property, but it has a userAccountControl property that provides similar options. You can use the flags that are available for the Windows NT provider.
See Also
For more information, read the MSDN Library article "Modifying User Properties" (http://msdn.microsoft.com/library/en-us/netdir/adsi/modifying_user_properties.asp).
Listing Groups
Problem
You want to list all objects from a group.
Solution
To list the members of a group using the WinNT provider, you can reference the group object and then loop through each member of the Members collection property:
Set objGroup = GetObject("WinNT://Acme/Domain Users,group") 'display name of each object in group For Each objUser In objGroup.Members Wscript.Echo objUser.Name Next
The LDAP provider allows for the enumeration of groups in the same way as the WinNT provider:
'get a reference to the Auditors group under the Accounting organizational unit Set objGroup = GetObject("LDAP://cn=Auditors,ou=Accounting,DC=Acme,DC=com") 'display name of each object in group For Each objUser In objGroup.Members Wscript.Echo objUser.Name Next
Discussion
A group object exposes a Members collection, which contains all members of the group.
You may want to determine what groups a user is a member of. This is easily accomplished by enumerating the Groups collection property of the User object:
Set objUser = GetObject("LDAP://CN=Administrator,CN=Users,DC=Acme,DC=com") 'display name of each group user is member of For Each objGroup In objUser.Groups Wscript.Echo objGroup.Name Next
The preceding example demonstrates enumerating the Administrators groups using the LDAP provider. The WinNT provider exposes the same property.
See Also
For more information, read the 15 Seconds FAQ "How do I get all the users in a group using ADSI?" at http://local.15seconds.com/faq/ADSI/623.htm.
Creating or Deleting a User Group
Problem
You want to create or delete a user group.
Solution
Using the WinNT provider, you can reference a domain or computer and then invoke the Create method, specifying the group class as the object that you want to create:
Const ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP = 4 Set objDomain = GetObject("WinNT://Acme") Set objGroup = objDomain.Create("group", "Acctusers") objGroup.groupType = ADS_GROUP_TYPE_DOMAIN_LOCAL_GROUP objGroup.Description = "Accounting Users" objGroup.SetInfo
Discussion
To create a new group using the Windows NT provider, first reference the domain or computer object in which you want to create the user.
Then invoke the object's Create method, specifying the Group class as the object you want to create:
Set objGroup = objDomain.Create("Group", strGroupName)
strGroupName identifies the group.
A groupType property must be set for the new group being created. For the NT provider, there is either the global or local group. Table 14-9 lists the different group types and associated values.
PROPERTY |
VALUE |
DESCRIPTION |
---|---|---|
GROUP_TYPE_GLOBAL_GROUP |
2 |
Can contain user accounts from the current domain. |
GROUP_TYPE_LOCAL_GROUP |
4 |
Can contain local accounts and global groups. |
GROUP_TYPE_UNIVERSAL_GROUP |
8 |
Windows 2000 only. Can contain accounts and account groups from any domain, but not local groups. |
Once a group has been created, invoke the object's SetInfo method to save the changes. No additional properties are needed for the new group. An optional Description parameter can be set to provide a more detailed description of the group.
Creating a new group using the Active Directory provider is similar to creating one using the Windows NT provider.
First, get a reference to the container you want to add the new group in and create the group using the Create method, specifying the Group object as the class you want to create and the RDN for the group you want to create.
You need to set the samAccountName property for the new group. This property represents the name that appears to Windows NT and Windows 9x machines:
Const GROUP_TYPE_SECURITY_ENABLED = &h80000000 Dim objContainer, objGroup Set objContainer = GetObject("LDAP://CN=Users,DC=Acme,DC=com") 'create the group Set objGroup = objContainer.Create("Group", "CN=Accounting Group") 'set the SAM account name for compatibility with existing NT and 'Win9x clients objGroup.samAccountName = "Acctusers" objGroup.groupType = GROUP_TYPE_GLOBAL_GROUP Or GROUP_TYPE_SECURITY_ENABLED objGroup.SetInfo
There are more group options available to Windows 2000 than to Windows NT. Windows 2000 has security groups and distribution groups. A security group is the same as a group in Windows NT. A distribution group is the same as a security group except it cannot be used to apply security access-control lists to Active Directory objects.
To create a security group, set the group type to local, global, or universal, and OR the GROUP_TYPE_SECURITY_ENABLED flag. The GROUP_TYPE_SECURITY_ENABLED flag has a value of -2147483648.
To delete a group using the WinNT provider, get a reference to the container the group exists in (domain or computer) and call the Delete method. You need to specify the group as the class type and the name of the group you are deleting:
Set objDomain = GetObject("WinNT://Acme") Set objGroup = objDomain.Delete("group", "Acctusers")
Similar steps are required to delete an Active Directory group:
Set objCN = GetObject("LDAP://ou=Accounting,DC=Acme,DC=com") objCN.Delete "group"," cn=Accounting Group"
See Also
For more information, read the MSDN Library article "Creating Groups" (http://msdn.microsoft.com/library/en-us/netdir/adsi/creating_groups.asp).
Adding a User or Group to a Group
Problem
You want to add a user or a group to a group.
Solution
You can reference the group object to which you want to add a user or local group and then invoke the Add method:
Dim objGroup 'get the group to add objects to.. Set objGroup = GetObject("WinNT://Acme/Acctusers") objGroup.add "WinNT://Acme/freds,user" 'add a user objGroup.add "WinNT://Acme/joeb" 'add a another user 'add a group - can only add other groups to Local groups, not Global objGroup.add "WinNT://Acme/finance,group"
Discussion
To add groups or users to a group using the Windows NT provider, reference the group to which you want to add. Then invoke the Add method:
objGroup.add (strObjectPath)
strObjectPath represents the ADSI path to the user or group you want to add.
To add an object to an Active Directory group, get a reference to the container to which you want to add. Then invoke the Add method specifying the DN path to the object you want to add to the group:
'get the group to add objects to.. Set objGroup = _ GetObject("LDAP://CN=Accounting Group,CN=Users,DC=Acme,DC=com") 'add a user objGroup.add "LDAP://CN=Fred Smith,CN=Users,DC=Acme,DC=com"
To delete an object from a group using the WinNT provider, get a reference to the group you want to delete the object from and invoke the Remove method, specifying the ADSI path to the object you want to delete:
'get the group to remove objects from Set objGroup = GetObject("WinNT://Acme/Acctusers") objGroup.Remove("WinNT://Acme/freds ") 'remove the user freds
Removing an object from an Active Directory group using the LDAP provider involves the same steps as removing an object using the WinNT provider:
'get the group to remove the object from Set objGroup = _ GetObject("LDAP://CN=Accounting Group,CN=Users,DC=Acme,DC=com") 'remove a user from the group objGroup.Remove "LDAP://CN=Fred Smith,CN=Users,DC=Acme,DC=com"
See Also
For more information, read the MSDN Library article "Adding Domain Groups to Machine Local Groups on Member Servers and Windows 2000 Professional" (http://msdn.microsoft.com/library/en-us/netdir/ad/adding_domain_groups_to_machine_local_groups_on_member_servers_and_windows_2000_professional.asp).
Determining Group Membership
Problem
You want to determine group membership at logon.
Solution
Use the Group object's IsMember method to check group membership:
Set objNetwork = Wscript.Create("Wscript.Network") Set objGroup= GetObject("WinNT://Acme/Accounting Users,group") ' If (objGroup.IsMember("WinNT://ACME/" & objNetwork.UserName)) Then 'connect to printer End If
Discussion
The IsMember method provides the ability to check if a user is a member of a specific group or not.
To use the IsMember method get a reference to the group you want to check the membership of. Invoke the IsMember method, specifying the name of the user you are checking the membership for:
bFlag = objGroup.IsMember(strMember)
The strMember parameter is the name of the user to check for. IsMember returns True if the user is a member of the group. For the WinNT provider, specify the full path to the name to check for (e.g., WinNT://Acme/Freds).
For Active Directory, get a reference to the group you want to check and invoke the IsMember method, specifying the full LDAP path to the object you are checking group membership for:
Set objGroup = _ GetObject("LDAP://CN=Accounting Users,OU=Accounting,DC= Acme,DC=com") 'check group membership.. If (objGroup.IsMember("LDAP://CN=FredS,OU=Accounting,DC=Acme,DC=com")) Then 'do something. . . End If
See Also
For more information, read the MSDN Library article "IADsGroup::IsMember" (http://msdn.microsoft.com/library/en-us/netdir/adsi/iadsgroup_ismember.asp).
Querying Active Directory Values
Problem
You want to query the Active Directory of a Windows 2000/XP server.
Solution
You can use the ADSI ADO interface to execute queries against Active Directory:
Discussion
The Active Directory provider includes an OLE DB interface that can perform queries against the Active Directory namespace. Using this interface together with ADO, you can execute SQL queries to return information on Active Directory objects.
To use the provider, you must have a recent version of ADO installed. Before a query can be executed against the provider, you must create an ADO Connection object and specify the provider:
'create a ADO connection object Set objConn = CreateObject("ADODB.Connection") 'select the provider objConn.Provider = "ADsDSOObject" 'open the connection - the string here can be anything objConn.Open "Active Directory Provider"
Once the Connection object is created, you can execute queries against the provider. The query contains standard SQL statements. The data source is the distinguished name of a LDAP container you want to search in (e.g., LDAP://ou=Sales,dc=Acme,dc=COM).
The query criteria can contain any Active Directory object properties. You can use the asterisk (*) wildcard to perform partial searches on fields:
Set objCmd = CreateObject("ADODB.Command") Set objCmd.ActiveConnection = objConn 'Return all items where the name starts with F objCmd.CommandText = "SELECT cn, name,sn, street, l, st FROM "& _ "'LDAP://OU=Sales,dc=acme,dc=COM' WHERE objectClass='user'" & _ "AND objectCategory='person' AND Name='F*'"
Initially when a search is performed, the source container and all containers below it are searched. If you only want to search a single container, set the SearchScope property of the ADO Command object to ADS_SCOPE_ONELEVEL, which has the value of 1. The default is ADS_SCOPE_SUBTREE, which has the value of 2.
The following sample searches the Sales organizational unit of the Acme domain for all objects in the container:
Const ADS_SCOPE_ONELEVEL = 1 'create a Connection object Set objConn = CreateObject("ADODB.Connection") objConn.Provider = "ADsDSOObject" objConn.Open "Active Directory Provider" 'create a command object Set objCmd = CreateObject("ADODB.Command") Set objCmd.ActiveConnection = objConn 'search the one level only objCmd.Properties("searchscope") = ADS_SCOPE_ONELEVEL objCmd.CommandText = _ "SELECT cn FROM 'LDAP://OU=Sales,DC=Acme,DC=COM' WHERE objectClass='*'" Set objRst = objCmd.Execute While Not objRst.Eof Wscript.Echo objRst("cn") objRst.MoveNext Wend objRst.Close objConn.Close
The ability to execute a query against the provider allows for the whole directory tree to be searched. If you specify the domain-level root object, the search starts from the root and iterates through all subcontainers (as long as the SearchScope property hasn't been changed).
Criteria must be specified in the search string, so if you want to list all object classes from the whole domain, you have to include criteria to include all objects:
SELECT name FROM 'LDAP://DC=Acme,DC=COM' WHERE objectClass='*'
The adsiqry.vbs script is a command-line script that executes a query against the Active Directory provider and outputs the results to standard output. It takes an Active Directory query as a parameter and outputs the results in comma-delimited format:
adsiqry.wsf "SELECT cn, name,sn, street, l, st FROM 'LDAP://OU=Sales,DC=Acme, DC=COM' WHERE objectClass='user' AND objectCategory='person' ORDER BY NAME"
The script has three optional parameters. By default, all subcontainers are searched—if you specify /s, only the source container is searched. If you specify /h, the field names are included in the header. The default comma delimiter can be changed by specifying the /d:X parameter, where X represents an alternative delimiter:
adsiqry.wsf "SELECT name FROM 'LDAP://DC=Acme,DC=COM' WHERE objectClass='*'" /s /h /d:;
You can't query the Windows NT provider using SQL queries through ADO. WinNT provider objects have a Filter property that allows the filtering of object types:
objObject.Filter = aPropArray
aPropArray is an array of object class names. Once a filter is applied to an object, any enumeration operations performed on the object will only return object types specified by the filter. You cannot specify detailed criteria.
'get a reference to a domain object Set objDomain = GetObject("WinNT://Acme") 'filter on the user objects objDomain.Filter = Array("user") For Each objUser In objDomain Wscript.Echo objUser.Name Next
See Also
For more information, read the MSDN Library article "Searching with ActiveX Data Objects (ADO)" (http://msdn.microsoft.com/library/en-us/netdir/adsi/searching_with_activex_data_objects_ado.asp).
Controlling NT Services
Problem
You want to check if a service has stopped.
Solution
You can use the WinNT ADSI provider to reference Windows NT or Windows 2000/XP services. Services can be queried, stopped, started, or paused. The following command-line script enumerates, starts, stops, or pauses services for a specified computer:
Discussion
The WinNT provider provides access to NT services. To get a reference to a service, use the following line:
objService = GetObject("WinNT://Computer/servicename,Service")
Servicename is the internally stored name of the service—it is not the name that appears under Control Panel > Services.
To list all services installed on a computer, get a reference to the computer and filter all class objects of type Service:
'list all services on the computer Odin 'get a reference to a computer Set objComputer = GetObject("WinNT://Odin") 'filter on the Service object class objComputer.Filter = Array("Service") 'enumerate the services For Each objService In objComputer Wscript.Echo objService.Name, objService.DisplayName Next
Table 14-10 lists Service object properties.
PROPERTY |
DESCRIPTION |
---|---|
DisplayName |
Friendly name that appears in Control Panel. |
Dependencies |
Array of values. Lists all services that service is dependent upon. |
LoadOrderGroup |
Name of load order group. |
Path |
String. Path to service executable. |
HostComputer |
String. Computer the service resides on. |
ServiceAccountName |
String. Systems account used to log on user. |
ServiceType |
Service type. Combination of any value that is listed in Table 14-11. |
StartType |
Indicates how service is started. Valid values are listed in Table 14-12. |
StartupParameters |
Parameters pass to service executable at startup. |
Status |
Determines the operational status of the server (stopped, paused, and so on). Valid values are listed in Table 14-13. |
NAME |
VALUE |
---|---|
ADS_KERNEL_DRIVER |
1 |
ADS_FILE_SYSTEM_DRIVER |
2 |
ADS_OWN_PROCESS |
16 |
ADS_SHARE_PROCESS |
32 |
NAME |
VALUE |
DESCRIPTION |
---|---|---|
ADS_BOOT_START |
0 |
Starts by OS loader |
ADS_SYSTEM_START |
1 |
Starts during OS initialization |
ADS_AUTO_START |
2 |
Starts by Service Control Manager during system startup |
ADS_DEMAND_START |
3 |
Manual startup |
ADS_DISABLED |
4 |
Service disable |
NAME |
VALUE |
DESCRIPTION |
---|---|---|
ADS_SERVICE_STOPPED |
1 |
Service stopped |
ADS_SERVICE_START_PENDING |
2 |
Service start initialized |
ADS_SERVICE_STOP_PENDING |
3 |
Service stop initialized |
ADS_SERVICE_RUNNING |
4 |
Service is running |
ADS_SERVICE_CONTINUE_PENDING |
5 |
Service in the process of continuing |
ADS_SERVICE_PAUSE_PENDING |
6 |
Service in the process of pausing |
ADS_SERVICE_PAUSED |
7 |
Service paused |
ADS_SERVICE_ERROR |
8 |
Error occurred |
Table 14-11 lists ServiceType values.
Table 14-12 lists service start type values.
Table 14-13 lists service status values.
A service can be started/stopped/paused by invoking the Start, Stop, or Pause method respectively on a Service object. Make sure that any dependant services are stopped before stopping the service. The Stop method provides the option to stop dependant services such as Control Panel.
When you perform multiple service operations (starting/stopping/pausing) where operations in the sequence are dependant on the successful completion of the previous service operation, checks should be made to determine that the service is in the assumed state before continuing.
For example, if a service is stopped using the Stop method, the code might continue executing before the service has been completely stopped. Following code might not execute correctly if it is dependant upon the service having been stopped.
'get a reference to the Exchange Internet Mail Connector service Set objService = GetObject("WinNT://odin/MSExchangeIMC") objService.Stop 'wait until the service has completely stopped While objService.Status = ADS_SERVICE_STOPPED Wscript.Sleep 100 Wend
The svcmaint.wsf script can stop, start, resume, and list services.
svcmaint.wsf computer [service] [operation] [/l]
Computer is the name of computer the service resides on. Service is the name of the service to manipulate. Operation represents start, stop, or pause. If you pass a /l switch, all services will be listed.
For example, the following stops Exchange server services:
svcmaint.wsf odin MSExchangeIMC stop svcmaint.wsf odin MSExchangeMTA stop svcmaint.wsf odin MSExchangeIS stop svcmaint.wsf odin MSExchangeDS stop svcmaint.wsf odin MSExchangeSA stop
See Also
For more information, read the MSDN Library article "IADsServiceOperations" (http://msdn.microsoft.com/library/en-us/netdir/adsi/iadsserviceoperations.asp).
Listing Connected Resources
Problem
You want to list all connected sessions.
Solution
Using the WinNT provider, you can enumerate connected resources through the Sessions collection of the LanmanServer object for a specified computer:
'lstcnusers.vbs 'list connected users to specified server Dim objFileService, objSession, strComputer 'check argument count If Not Wscript.Arguments.Count = 1 Then ShowUsage Wscript.Quit End If 'get the file service object strComputer = Wscript.Arguments(0) 'get a reference to the LanmanServer service Set objFileService = GetObject("WinNT://" & strComputer & "/LanmanServer") 'loop through each session and display any connected users For Each objSession In objFileService.sessions 'check if the session user ID is not empty If Not objSession.user = "" Then [<> ?] Wscript.StdOut.WriteLine objSession.user End If Next Sub ShowUsage WScript.Echo "lstcnusers.vbs lists connected users "& vbCrLf & _ "Syntax:" & vbCrLf & _ "lstcnusers.vbs computer" & vbCrLf & _ "computer computer to enumerate connected users" End Sub
Discussion
The Windows NT provider exposes the connected session resources through the FileService object. A reference can be retrieved from the NT network management (Lanman) object of a server.
A connected session can represent either a user or a computer. To get a list of all connected sessions, get a reference to the LanmanServer service of the computer you want to check:
'list all sessions on computer odin 'get a reference to the LanmanServer service Set objFileService = GetObject("WinNT://odin/LanmanServer") 'enumerate all sessions For Each objSession In objFileService.sessions objTextStream.WriteLine objSession.Name Next
Table 14-14 lists Session object's properties.
PROPERTY |
DESCRIPTION |
---|---|
Computer |
Name of connected computer |
ConnectTime |
Time connected in seconds |
Name |
Name in format userCOMPUTER (e.g., administratorODIN) |
IdleTime |
Idle time in seconds |
User |
User name |
See Also
For more information, read the MSDN Library articles "IADsFileServiceOperations" (http://msdn.microsoft.com/library/en-us/netdir/adsi/iadsfileserviceoperations.asp) and "IADsSession" (http://msdn.microsoft.com/library/en-us/netdir/adsi/iadssession.asp).
Determining User RAS Access
Problem
You want to determine all users' RAS access.
Solution
User RAS access is not directly exposed through the ADSI User object. The ADSI 2.5 SDK includes an extension DLL that allows for the setting of RAS access permissions. The following command-line script sets RAS settings for a specified user:
Discussion
To use the extensions, register the ADsRAS.dll file that is included with the ADSI 2.5 SDK.
The DLL extends the WinNT provider's User object with a number of RAS-related properties. Table 14-15 lists the ADsRAS properties.
PROPERTY |
DESCRIPTION |
---|---|
DialinPrivilege |
Boolean. If True, user has RAS dial-in access. |
GetRasCallBack |
Integer value. Determines user call back type. |
ADS_RAS_NOCALLBACK |
1 |
ADS_RAS_ADMIN_SETCALLBACK |
2 |
ADS_RAS_CALLER_SETCALLBACK |
4 |
GetRasPhoneNumber |
Returns the RAS phone number used for call back. |
To allow a user RAS, set the DialinPrivilege property to True. If you need to set specific call back access, invoke the SetCallBack method, specifying the type of call back the user has:
Dim objuser Set objUser = GetObject("WinNT://acme/freds") 'all the user dial in access objUser.DialinPrivilege = True 'set users call back access objUser.SetRasCallBack ADS_RAS_CALLER_SETCALLBACK objUser.SetInfo
See Also
Search for the document Rtk.HTM in the ADSI 2.5 SDK directory.
Listing Network Shares
Problem
You want to list all network shares.
Solution
You can reference the computer on which the file shares reside and then enumerate all FileShare objects from the LanmanServer service object:
Dim objFileService, objSession 'get the file service object Set objFileService = GetObject("WinNT://odin/LanmanServer") 'filter on file shares objFileService.Filter = Array("FileShare") 'loop through and display description of all file shares For Each objFileShare In objFileService Wscript.Echo objFileShare.Name Next
Discussion
The WinNT provider exposes network share information. You can enumerate, create, and delete shares.
To reference a file share, reference the LanmanServer service on the computer on which the share resides, followed by the share name:
Set objFileShare = GetObject("WinNT://computer/LanmanServer/sharename")
Sharename is the name of the share you want to reference. The Share object returned exposes the properties listed in Table 14-16.
PROPERTY |
DESCRIPTION |
---|---|
Name |
Name of share. |
Description |
Share description. |
Path |
Path share represents. |
CurrentUserCount |
Current connected users. |
MaxUserCount |
Maximum number of users allowed to connect to the share. For unlimited users, set to -1. |
See Also
For more information, read the MSDN Library article "IADsFileShare" (http://msdn.microsoft.com/library/en-us/netdir/adsi/iadsfileshare.asp).
Creating or Deleting a Network Share
Problem
You want to create a file share.
Solution
You can retrieve a reference to the LanmanServer service on the computer on which you want to create the share using the WinNT provider and then call the Create method, specifying the FileShare class as the object type you want to create:
' 'get the file service object Set objFileService = GetObject("WinNT://Odin/LanmanServer") Set objFileShare = objFileService.create("FileShare", "AcctData") objFileShare.Path = "d:dataaccounting" objFileShare.Description = "Accounting Data" objFileShare.MaxUserCount = -1 'set unlimited users objFileShare.SetInfo
Discussion
To create a file share, you can reference the LanmanServer service on the machine on which you want to create the share. Next, invoke the Create method, specifying the FileShare class as the object you want to create, followed by a share name. Then set the path the share will point to.
To delete a share, reference the LanmanServer service on the machine from which you want to delete the share. Invoke the Delete method, specifying the object class type as FileShare followed by the name of the share you want to delete:
'get the file service object Set objFileService = GetObject("WinNT://odin/LanmanServer") 'delete the share AcctData objFileService.Delete "FileShare", "AcctData"
See Also
For more information, read the MSDN Library article "IADsFileShare" (http://msdn.microsoft.com/library/en-us/netdir/adsi/iadsfileshare.asp).
Print Queue Operations
Problem
You want to purge a print queue of current print jobs.
Solution
You can reference the queue object using the WinNT provider and call the Purge method. The following command-line script pauses or resumes a specified print queue:
Discussion
The Windows NT provider exposes computer-shared printers (print queues). To get a reference to a print queue, use the following line:
Set objPrintQ = GetObject("WinNT://Computer/PrintQ)
Computer is the name of the computer the print queue resides on. PrintQ is the name of the queue represented by the shared name of the printer.
The PrintQueue object provides a Purge method to remove all print jobs from the queue. The queue can also be paused using the Pause method, which prevents any further jobs from being printed. To resume a paused queue, invoke the Resume method.
The PrintQueue object exposes a Status property that identifies the current status of the printer servicing the queue. Table 14-17 lists the values the Status property can return.
CONSTANT |
VALUE |
---|---|
ADS_PRINTER_PAUSED |
1 |
ADS_PRINTER_PENDING_DELETION |
2 |
ADS_PRINTER_ERROR |
3 |
ADS_PRINTER_PAPER_JAM |
4 |
ADS_PRINTER_PAPER_OUT |
5 |
ADS_PRINTER_MANUAL_FEED |
6 |
ADS_PRINTER_PAPER_PROBLEM |
7 |
ADS_PRINTER_OFFLINE |
8 |
ADS_PRINTER_IO_ACTIVE |
256 |
ADS_PRINTER_BUSY |
512 |
ADS_PRINTER_PRINTING |
1024 |
ADS_PRINTER_OUTPUT_BIN_FULL |
2048 |
ADS_PRINTER_NOT_AVAILABLE |
4096 |
ADS_PRINTER_WAITING |
8192 |
ADS_PRINTER_PROCESSING |
16384 |
ADS_PRINTER_INITIALIZING |
32768 |
ADS_PRINTER_WARMING_UP |
65536 |
ADS_PRINTER_TONER_LOW |
131072 |
ADS_PRINTER_NO_TONER |
262144 |
ADS_PRINTER_PAGE_PUNT |
524288 |
ADS_PRINTER_USER_INTERVENTION |
&h00100000 |
ADS_PRINTER_OUT_OF_MEMORY |
&h00200000 |
ADS_PRINTER_DOOR_OPEN |
&h00400000 |
ADS_PRINTER_SERVER_UNKNOWN |
&h00800000 |
ADS_PRINTER_POWER_SAVE |
&h01000000 |
See Also
For more information, read the MSDN Library article "IADsPrintQueueOperations" (http://msdn.microsoft.com/library/en-us/netdir/adsi/iadsprintqueueoperations.asp).
Listing Print Jobs
Problem
You want to list current print jobs.
Solution
You can reference a queue object using the WinNT provider and enumerate the PrintJobs collection to list any jobs and related information:
Discussion
A print queue contains a list of print jobs currently being processed. Each job is represented as a PrintJob object and can be paused or resumed, but not deleted.
To enumerate a print queue, get a reference to the print queue you want to process. The PrintQueue object has a PrintJobs property that contains a list of active jobs in the form of a PrintJob object.
Table 14-18 lists the PrintJob object's properties.
PROPERTY |
DESCRIPTION |
---|---|
User |
User name of the user who submitted the print job. |
TimeSubmitted |
Time the job was submitted. |
TotalPages |
Number of pages in the document. |
Size |
Size in bytes. |
Description |
Name of the document being printed. |
Priority |
The higher the number, the greater the priority. |
StartTime |
Start of the time range when the document can be printed. |
UntilTime |
End of the time range when the document can be printed. |
Notify |
Name of the user to notify when the job is complete. |
TimeElapsed |
Time elapsed in seconds of the current active print job. |
PagesPrinted |
Number of pages in the current active job. |
Position |
Position of the job in the queue. |
Status |
Status of the job in the queue. Any combination of the status flag values listed in Table 14-19. |
NAME |
VALUE |
DESCRIPTION |
---|---|---|
ADS_JOB_PAUSED |
1 |
Job is paused |
ADS_JOB_ERROR |
2 |
Job is in error status |
ADS_JOB_DELETING |
4 |
Job is being deleted |
ADS_JOB_PRINTING |
16 |
Job is being printed |
ADS_JOB_OFFLINE |
32 |
Printer offline |
ADS_JOB_PAPEROUT |
64 |
Printer is out of paper |
ADS_JOB_PRINTED |
128 |
Job is printed |
ADS_JOB_DELETED |
256 |
Job is deleted |
A PrintJob object can be paused by invoking the Pause method and resumed by invoking the Resume method. You cannot delete a print job.
PrintJob properties such as priority and start and end times can be modified. The following example increases the priority of jobs with less than 50 pages so they print before larger jobs:
Dim objPrintQ, objJob 'get a reference to a print queue Set objPrintQ = GetObject("WinNT://Odin/HP5") 'enumerate all print jobs For Each objJob In objPrintQ.printjobs 'if the number of pages is less than 50, increase priority If objJob.TotalPages < 50 Then objJob.Priority = 10 objJob.SetInfo End If Next
Table 14-19 lists PrintJob status flags.
See Also
For more information, read the MSDN Library article "IADsPrintJobOperations" (http://msdn.microsoft.com/library/en-us/netdir/adsi/iadsprintjoboperations.asp).