Archive for January, 2010


Problem

You want to view the objects that are owned by a user.

Solution

Using a graphical user interface

  1. Open ADSI Edit.
  2. If an entry for the naming context you want to browse is not already displayed, do the following:
    1. Right-click on ADSI Edit in the right pane and click “Connect to….”
    2. Fill in the information for the naming context, container, or OU you want to add an object to. Click on the Advanced button if you need to enter alternate credentials.
  3. In the left pane, browse to the naming context, container, or OU of the object you want to view. Once you’ve found the object, right-click on it and select Properties.
  4. View the managedObjects attribute.

Using a command-line interface

> adfind b “<UserDN>” managedObjects

Using VBScript

‘ This code displays the

managed objects for a user

‘ —— SCRIPT CONFIGURATION ——

strUserDN = “<UserDN>” ‘ e.g. cn=jsmith,cn=Users,dc=rallencorp,dc=com

‘ —— END CONFIGURATION ———

on error resume next

set objUser = GetObject(“LDAP://” & strUserDN)

Wscript.Echo objUser.Get(“cn”) & “‘s

Managed Objects:”

colObjects = objUser.GetEx(“managedObjects”)

if Err.Number = -2147463155 then

Wscript.Echo ” none”

else

for each strObjectDN in colObjects

Wscript.Echo ” ” & strObjectDN

next

end if

Discussion

The managedObjects attribute is linked to the managedBy attribute that can be set on certain objects in Active Directory like computers, OUs, and groups. Setting the managedBy attribute provides a quick way to define who owns an object. If you do use it, you can use the managedObjects attribute on user, contact, or group objects to get the list of objects for which the user has been configured in the managedBy attribute.

Advertisements


Problem

You want to see the hours that a user is permitted to log onto the network.

Solution

Using a graphical user interface

  1. Open the ADUC snap-in.
  2. If you need to change domains, right-click on “Active Directory Users and Computers” in the left pane, select Connect to Domain, enter the domain name, and click OK.
  3. Right-click on the user and select Properties. From the Account tab, click on Logon Hours.
  4. Select the hours that you want to allow or disallow, and click Logon Permitted or Logon Denied. Click OK.
  5. Click Apply, followed by OK.

Using VBScript

Days = Array _

(“Sunday”, “Monday”, “Tuesday”, “Wednesday”, “Thursday”, “Friday”, “Saturday”)

Set objUser = GetObject(“LDAP://<UserDN>“)

arrHours = objUser.Get(”

logonHours”)

For i = 1 To LenB(arrHours)

arrHoursBytes(i-1) = AscB(MidB(arrHours, i, 1))

WScript.Echo “MidB returns: ” & MidB(arrHours, i, 1)

WScript.Echo “arrHoursBytes: ” & arrHoursBytes(i-1)

wscript.echo vbcrlf

Next

intCounter = 0

intLoopCounter = 0

WScript.echo “Day Byte 1 Byte 2 Byte 3”

For Each HourByte In arrHoursBytes

arrHourBits = DisplayLogonHourBits(HourByte)

If intCounter = 0 Then

WScript.STDOUT.Write Days(intLoopCounter) & Space(2)

intLoopCounter = intLoopCounter + 1

End If

For Each HourBit In arrHourBits

WScript.STDOUT.Write HourBit

intCounter = 1 + intCounter

If intCounter = 8 or intCounter = 16 Then

Wscript.STDOUT.Write Space(1)

End If

If intCounter = 24 Then

WScript.echo vbCr

intCounter = 0

End If

Next

Next

Function DisplayLogonHourBits(x)

Dim arrBits(7)

For i = 7 to 0 Step -1

If x And 2^i Then

arrBits(i) = 1

Else

arrBits(i) = 0

End If

Next

DisplayLogonHourBits = arrBits

End Function

Discussion

Using VBScript

The logonHours attribute of a user object is represented as a binary number, rather than a simple string like most of the other attributes we’ve discussed. Because of this, manipulating it directly is a bit trickier than simply inserting a new string in place of an old one. In the VBScript example shown in this recipe, we use a VBScript function that manipulates the various bits of the attribute to produce the correct values.

This recipe requires the Windows Server 2003 forest functional level.

Problem

You want to determine the last time a user logged into a domain.

Solution

Using a graphical user interface

If you install the AcctInfo.dll extension to ADUC, you can view the last logon timestamp:

  1. Open the ADUC snap-in.
  2. In the left pane, right-click on the domain and select Find.
  3. Select the appropriate domain beside In.
  4. Beside Name, type the name of the user you want to modify and click Find Now.
  5. In the Search Results window, double-click on the user.
  6. Click the Additional Account Info tab.
  7. View the value for Last-Logon-Timestamp.
AcctInfo.dll can be downloaded from the Microsoft download site as a part of the Account Lockout and Management Tools:

http://microsoft.com/downloads/details.aspx?FamilyId=7AF2E69C-91F3-4E63-8629-B999ADDE0B9E&displaylang=en

Discussion

Trying to determine when a user last logged on has always been a challenge in the Microsoft NOS environment. In Windows NT, you could retrieve a user’s last logon timestamp from a PDC or BDC, but this timestamp was the last time the user logged on to the individual PDC or BDC itself. That means to determine the actual last logon, you’d have to query every domain controller in the domain. In large environments, this wasn’t practical. With Windows 2000 Active Directory, things did not improve much. A lastLogon attribute is used to store the last logon timestamp, but unfortunately, this attribute isn’t replicated. So again, to get an accurate picture, you’d have to query every domain controller in the domain for the user’s last logon attribute and keep track of the most recent one.

Now with Windows Server 2003 there is finally a viable solution. A new attribute was added to the schema for user objects called lastLogonTimestamp.This attribute is similar to the lastLogon attribute that was available previously, with two distinct differences. First, and most importantly, this attribute is replicated. That means when a user logs in, the lastLogonTimestamp attribute will get populated and then replicate to all domain controllers in the domain.

The second difference is that since lastLogonTimestamp is replicated, special safeguards needed to be put in place so that users that logged in repeatedly over a short period of time did not cause unnecessary replication traffic. For this reason, the lastLogonTimestamp is updated only if the last update occurred a week or more ago by default.(This window is configurable by modifying the msDS-LogonTimeSyncInterval on the domain NC.) This means that the lastLogonTimestamp attribute could be up to a week off in terms of accuracy with a user’s actual last logon. Ultimately, this shouldn’t be a problem for most situations because lastLogonTimestamp is intended to address the common problem where administrators want to run a query and determine which users have not logged in over the past month or more.


Problem

You want a user’s account to expire at some point in the future.

Solution

Using a graphical user interface

  1. Open the ADUC snap-in.
  2. In the left pane, right-click on the domain and select Find.
  3. Select the appropriate domain beside In.
  4. Beside Name, type the name of the user you want to modify and click Find Now.
  5. In the Search Results window, double-click on the user.
  6. Click the Account tab.
  7. Under Account expires, select the radio button beside End of.
  8. Select the date the account should expire.
  9. Click OK.

Using a command-line interface

Valid values for the -acctexpires flag include a positive number of days in the future when the account should expire, to expire the account at the end of the day, or to never expire the account.

> dsmod user “<UserDN>” -acctexpires <NumDays>

Using VBScript

‘ This code sets the

account expiration date for a user.

‘ —— SCRIPT CONFIGURATION ——

strExpireDate = “<Date>” ‘ e.g. “07/10/2004”

strUserDN = “<UserDN>” ‘ e.g. cn=rallen,ou=Sales,dc=rallencorp,dc=com

‘ —— END CONFIGURATION ——–

set objUser = GetObject(“LDAP://” & strUserDN)

objUser.AccountExpirationDate = strExpireDate

objUser.SetInfo

WScript.Echo “Set user ” & strUserDN & ” to expire on ” & strExpireDate

‘ These two lines would disable

account expiration for the user

‘ objUser.Put ”

accountExpires”, 0

‘ objUser.SetInfo

Discussion

User accounts can be configured to expire on a certain date. Account expiration is stored in the accountExpires attribute on a user object. This attribute contains a large integer representation of the date in which the account expires, expressed in 100 nanosecond intervals since January 1, 1601.If you set this attribute to 0, it disables account expiration for the user (i.e., the account will never expire). Note that this is different than the dsmod user command where a value of 0 with -acctexpires will cause the account to expire at the end of the day. Why does it differ from how the accountExpires attribute works? Great question. The accountExpires attribute itself will be updated whenever the existing expiration date passes.


Problem

You want to find the users whose passwords are about to expire.

Solution

Using a command-line interface

> dsquery user -stalepwd <NumDaysSinceLastPwdChange>

You can also use the FindExpAcc joeware tool with the following syntax:

> findexpacc -pwd

Using Perl

#!perl

# This code finds the user accounts whose password is about to expire

# —— SCRIPT CONFIGURATION ——

# Domain and container/OU to check for accounts that are about to expire

my $domain   = ‘<DomainDNSName>’;

my $cont     = ”; # set to empty string to query entire domain

# Or set to a relative path in the domain, e.g. cn=Users

# Days since password change

my $days_ago = <NumDaysSinceLastPwdChange> # e.g. 60;

# —— END CONFIGURATION ——–

use strict;

use Win32::OLE;

$Win32::OLE::Warn = 3;

use Math::BigInt;

# Need to convert the number of seconds from $day_ago

# to a large integer for comparison against pwdLastSet

my $past_secs = time – 60*60*24*$days_ago;

my $intObj = Math::BigInt->new($past_secs);

$intObj = Math::BigInt->new($intObj->bmul(’10 000 000′));

my $past_largeint = Math::BigInt->new(

$intObj->badd(‘116 444 736 000 000 000’));

$past_largeint =~ s/^[+-]//;

# Setup the ADO connections

my $connObj                                = Win32::OLE->new(‘ADODB.Connection’);

$connObj->{Provider}                    = “ADsDSOObject”;

# Set these next two if you need to authenticate

# $connObj->Properties->{‘User ID’}    = ‘<User>’;

# $connObj->Properties->{‘Password’}   = ‘<Password>’;

$connObj->Open;

my $commObj                                  = Win32::OLE->new(‘ADODB.Command’);

$commObj->{ActiveConnection}              = $connObj;

$commObj->Properties->{‘Page Size’}    = 1000;

# Grab the default domain naming context

my $rootDSE = Win32::OLE->GetObject(“LDAP://$domain/RootDSE”);

my $rootNC = $rootDSE->Get(“defaultNamingContext”);

# Run ADO query and print results

$cont .= “,” if $cont and not $cont =~ /,$/;

my $query = “<LDAP://$domain/$cont$rootNC>;”;

$query .= “(&(objectclass=user)”;

$query .= “(objectcategory=Person)”;

$query .= “(!useraccountcontrol:1.2.840.113556.1.4.803:=2)”;

$query .= “(pwdLastSet<=$past_largeint)”;

$query .= “(!pwdLastSet=0));”;

$query .= “cn,distinguishedName;”;

$query .= “subtree”;

$commObj->{CommandText} = $query;

my $resObj = $commObj->Execute($query);

die “Could not query $domain: “,$Win32::OLE::LastError,”\n”

unless ref $resObj;

print “\

nUsers who haven’t set their passwd in $days_ago days or longer:\n”;

my $total = 0;

while (!($resObj->EOF)) {

print “\t”,$resObj->Fields(“distinguishedName”)->value,”\n”;

$total++;

$resObj->MoveNext;

}

print “Total: $total\n”;

Discussion

When a Windows-based client logs on to Active Directory, a check is done against the domain password policy and the user’s pwdLastSet attribute to determine if the user’s password has expired. If it has, the user is prompted to change it. In a pure Windows-based environment, this notification process may be adequate, but if you have a lot of nonWindows-based computers that are joined to an Active Directory domain (e.g., Kerberos-enabled Unix clients), or you have a lot of application and service accounts, you’ll need to develop your own user password expiration notification process. Even in a pure Windows environment, cached logins present a problem because when a user logs into the domain with cached credentials (i.e., when the client is not able to reach a domain controller), this password expiration notification check is not done.

The process of finding users whose passwords are about to expire is a little complicated. Fortunately, the new dsquery user command helps by providing an option for searching for users that haven’t changed their password for a number of days (-stalepwd).The downside to the dsquery user command is that it will not only find users whose password is about to expire, but also users that must change their password at next logon (i.e., pwdLastSet = 0).The Perl solution does not suffer from this limitation.


Problem

You want to prevent a user’s password from expiring.

Solution

Using a graphical user interface

  1. Open the ADUC snap-in.
  2. In the left pane, right-click on the domain and select Find.
  3. Select the appropriate domain beside In.
  4. Beside Name, type the name of the user you want to modify and click Find Now.
  5. In the Search Results window, double-click on the user.
  6. Click the Account tab.
  7. Under Account options, check the box beside “Password never expires.”
  8. Click OK.

Using a command-line interface

> dsmod user “<UserDN>” -pwdneverexpires yes

Using VBScript

‘ This code sets a

users password to never expire

‘ —— SCRIPT CONFIGURATION ——

strUserDN = “<UserDN>” ‘ e.g. cn=rallen,ou=Sales,dc=rallencorp,dc=com

‘ —— END CONFIGURATION ——–

intBit = 65536

strAttr = “userAccountControl”

set objUser = GetObject(“LDAP://” & strUserDN)

intBitsOrig = objUser.Get(strAttr)

intBitsCalc = CalcBit(intBitsOrig, intBit, TRUE)

if intBitsOrig <> intBitsCalc then

objUser.Put strAttr, intBitsCalc

objUser.SetInfo

WScript.Echo “Changed ” & strAttr & ” from ” & _

intBitsOrig & ” to ” & intBitsCalc

else

WScript.Echo “Did not need to change ” & strAttr & ” (” & _

intBitsOrig & “)”

end if

Discussion

Setting a user’s password to never expire overrides any password aging policy you’ve defined in the domain. To disable password expiration, you need to set the bit equivalent of 65536 (i.e., 10000000000000000) in the userAccountControl attribute of the target user.


Problem

You want to require a user to change his password the next time he logs on to the domain.

Solution

Using a graphical user interface

  1. Open the ADUC snap-in.
  2. In the left pane, right-click on the domain and select Find.
  3. Select the appropriate domain beside In.
  4. Beside Name, type the name of the user you want to modify and click Find Now.
  5. In the Search Results window, double-click on the user.
  6. Click the Account tab.
  7. Under Account options, check the box beside “User must change password at next logon.”
  8. Click OK.

Using a command-line interface

You can configure the “User must change password” using either DSMod or AdMod. To modify this setting using DSMod, use the following syntax:

> dsmod user “<UserDN>” -mustchpwd yes

For AdMod, do the following:

> admod b “<UserDN>” pwdLastSet::0

Using VBScript

‘ This code sets the flag that requires a

user to change their

password

‘ —— SCRIPT CONFIGURATION ——

strUserDN = “<UserDN>” ‘ e.g. cn=rallen,ou=Sales,dc=rallencorp,dc=com

‘ —— END CONFIGURATION ——–

set objUser = GetObject(“LDAP://” & strUserDN)

objUser.Put “pwdLastSet”, 0

objUser.SetInfo

WScript.Echo “User must change password at next logon: ” & strUserDN

Discussion

When a user changes her password, a timestamp is written to the pwdLastSet attribute of the user object. When the user logs in to the domain, this timestamp is compared to the maximum password age that is defined by the Domain Security Policy to determine if the password has expired. To force a user to change her password at next logon, set the pwdLastSet attribute of the target user to zero, and verify that the user’s account doesn’t have the “password never expires” option enabled.

To disable this option so that a user does not have to change her password, set pwdLastSet to -1. These two values (0 and -1) are the only ones that can be set on the pwdLastSet attribute.


Problem

You want to disable a user’s ability to change her password.

Solution

Using a graphical user interface

  1. Open the ADUC snap-in.
  2. In the left pane, right-click on the domain and select Find.
  3. Select the appropriate domain beside In.
  4. Beside Name, type the name of the user you want to modify and click Find Now.
  5. In the Search Results window, double-click on the user.
  6. Click the Account tab.
  7. Under Account options, check the box beside “User cannot change password.”
  8. Click OK.

Using a command-line interface

> dsmod user <UserDN> -canchpwd no

Using VBScript

‘ This code disables a user’s ability to change

password

‘ —— SCRIPT CONFIGURATION ——

strUserDN = “<UserDN>” ‘ e.g. cn=rallen,ou=Sales,dc=rallencorp,dc=com

‘ —— END CONFIGURATION ———

Const ACETYPE_ACCESS_DENIED_OBJECT = 6

Const ACEFLAG_OBJECT_TYPE_PRESENT = 1

Const RIGHT_DS_CONTROL_ACCESS = 256

Const CHANGE_PASSWORD_GUID = “{ab721a53-1e2f-11d0-9819-00aa0040529b}”

set objUser = GetObject(“LDAP://” & strUserDN)

set objSD = objUser.Get(“ntSecurityDescriptor”)

set objDACL = objSD.DiscretionaryAcl

‘ Add a deny ACE for Everyone

set objACE = CreateObject(“AccessControlEntry”)

objACE.Trustee = “Everyone”

objACE.AceFlags = 0

objACE.AceType = ACETYPE_ACCESS_DENIED_OBJECT

objACE.Flags = ACEFLAG_OBJECT_TYPE_PRESENT

objACE.ObjectType = CHANGE_PASSWORD_GUID

objACE.AccessMask = RIGHT_DS_CONTROL_ACCESS

objDACL.AddAce objACE

‘ Add a deny ACE for Self

‘ (This is only necessary to prevent a

user from

‘ changing their own password.)

set objACE = CreateObject(“AccessControlEntry”)

objACE.Trustee = “Self”

objACE.AceFlags = 0

objACE.AceType = ACETYPE_ACCESS_DENIED_OBJECT

objACE.Flags = ACEFLAG_OBJECT_TYPE_PRESENT

objACE.ObjectType = CHANGE_PASSWORD_GUID

objACE.AccessMask = RIGHT_DS_CONTROL_ACCESS

objDACL.AddAce objACE

objSD.DiscretionaryAcl = objDACL

objUser.Put “nTSecurityDescriptor”, objSD

objUser.SetInfo

WScript.Echo “Enabled no password changing for ” & strUserDN

Discussion

Even though in the GUI solution you check and uncheck the “User cannot change password” setting, actually making the change in Active Directory is a little more complicated as is evident in the VBScript solution. Not allowing a user to change her password consists of setting two deny Change Password ACEs on the target user object. One deny ACE is for the Everyone account and the other is for Self.

The VBScript solution should work as is, but it is not very robust in terms of checking to see if the ACEs already exist and making sure they are in the proper order. If you need to make the code more robust, we suggest checking out MS KB 269159 for more information on setting ACEs properly.


Problem

You want to set the password for a user.

Solution

Using a graphical user interface

  1. Open the ADUC snap-in.
  2. In the left pane, right-click on the domain and select Find.
  3. Select the appropriate domain beside In.
  4. Type the name of the user beside Name and click Find Now.
  5. In the Search Results window, right-click on the user and select Reset Password.
  6. Enter and confirm the new password.
  7. Click OK.

Using a command-line interface

This command changes the password for the user specified by <UserDN>.Using * after the -pwd option prompts you for the new password. You can replace * with the password you want to set, but it is not a good security practice since other users that are logged into the machine may be able to see it.

> dsmod user <UserDN> -pwd *

You can also use admod with the #setpwd# switch, as follows:

> admod -b “<UserDN>” #setpwd#::<NewPassword>

You can also modify the unicodepwd attribute directly by encrypting the admod connection using the kerbenc switch, as follows:

> admod b “<UserDN>” unicodepwd::<Password> -kerbenc

Using VBScript

‘ This code sets the password for a user.

‘ —— SCRIPT CONFIGURATION ——

strUserDN = “<UserDN>” ‘ e.g. cn=jsmith,cn=Users,dc=rallencorp,dc=com

strNewPasswd = “<NewPasword>”

‘ —— END CONFIGURATION ——–

set objUser = GetObject(“LDAP://” & strUserDN)

objUser.SetPassword(strNewPasswd)

Wscript.Echo “Password set for ” & objUser.Get(“cn”)

Discussion

A one-way hash of a user’s password is stored in the unicodePwd attribute. There are several supported methods to modify this attribute directly, or you can use one of the supported APIs to do so.

With the VBScript solution, you can use the IADsUser::SetPassword method or IADsUser:: ChangePassword. The latter requires the existing password to be known before setting it. This is the method you’d want to use if you’ve created a web page that accepts the previous password before allowing a user to change it.

Problem

You want to copy one user’s group membership to another user.

Solution

Using a graphical user interface
  1. Open the ADUC snap-in.
  2. In the left pane, right-click on the domain and select Find.
  3. Select the appropriate domain beside In.
  4. Beside Name, type the name of the user you want to transfer groups from and click Find Now.
  5. In the Search Results window, double-click on the user.
  6. Click the Member Of tab.
  7. For each group you want to add another user in, do the following:
    1. Double-click on the group.
    2. Click the Members tab.
    3. Click the Add button.
    4. d. Find the user you want to add in the object picker and click OK.
    5. e. Click OK.
Using a command-line interface

The following command line will add <NewUserDN> to all of the groups that <CurrentUserDN> is a member of:

        > for /F "usebackq delims=""" %i in ('dsget user
        "<CurrentUserDN>" -memberof') do dsmod group %i -addmbr "<NewUserDN>"

If you want to get fancy and remove <CurrentUserDN> from each of the groups in the same operation, simply add an -rmmbr option on the end:

        > for /F "usebackq delims=""" %i in ('dsget user
        "<CurrentUserDN>" -memberof') do dsmod group %i -addmbr "<NewUserDN>"
        -rmmbr "<CurrentUserDN>"
Using VBScript
        ' This code adds the "new" user to the groups the "current"
        ' user is a member of
        ' ------ SCRIPT CONFIGURATION -----
        strCurrentUserDN = "<CurrentUserDN>"
        ' e.g. cn=jsmith,ou=Sales,dc=rallencorp,dc=com
        strNewUserDN = "<NewUserDN>"

        ' ------ SCRIPT CONFIGURATION ------

        Const ADS_PROPERTY_APPEND = 3
        set  
objCurrentUser = GetObject("LDAP://" &  
strCurrentUserDN )
        set objNewUser = GetObject("LDAP://" & strNewUserDN )

        on error resume next
        WScript.Echo "Transfering groups from " & strCurrentUserDN & " to " & strNewUserDN
        for each strGroupDN in objCurrentUser.GetEx("memberOf")
           set objGroup = GetObject("LDAP://" & strGroupDN)
           objGroup.PutEx ADS_PROPERTY_APPEND, "member", Array( strNewUserDN )
           objGroup.SetInfo
           if Err then
              WScript.Echo "Error adding user to group: " & strGroupDN
           else
              WScript.Echo "Added user to group: " & strGroupDN
           end if
        next

Discussion

Employees come and go; people take on new responsibilities and move on to new jobs. It is common to have movement within an organization. When this happens, typically someone is replacing the person that is moving on. The new person needs to get up to speed as quickly as possible, including getting accounts set up and access to any necessary resources. A big part of this includes getting added to the correct groups. You can help facilitate this by using one of the processes outlined in the Solution section to help the user gain access to the exact same groups that the former employee was a member of.

One important issue to point out is that the memberOf attribute, which was used in the Solution section to determine a user’s group membership, contains only the groups that are visible to the DC that’s being queried; this can vary depending on whether the DC in question is a Global Catalog and whether the user belongs to any universal groups. Any groups the user is a member of outside of the user’s domain will not be transferred. To transfer universal group membership outside of a domain, you will need to perform a query against the global catalog for all group objects that have a member attribute that contains the DN of the user. You can also search the Global Catalog for the memberOf attribute for a given user to determine a user’s universal group memberships.