Click to Rate and Give Feedback
TechNet
TechNet Library

  Switch on low bandwidth view
Administration of Machines
Archived content. No warranty is made as to technical accuracy. Content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.
By Dave Roth

Chapter 3 from Windows NT: Win32 Perl Programming: The Standard Extensions, published by MacMillan Technical Publishing

Administrating a network of machines is quite a large task that can be quite difficult no matter how the operating system attempts to simplify its interface. Several disciplines must be mastered to keep the network free from problems. Simple concepts such as user accounts, machine accounts, trusts, shares, permissions, and privileges become quite tedious to manage when you are contending with hundreds or thousands of users and machines.

This is really not so much of an issue for the user who is running Windows 95 at home. This user probably will never deal with a network other than the Internet via a dial-up modem. For this scenario, much of this chapter really will not be of much use other than for those with a curious nature.

This chapter addresses the Win32 Perl extensions administrators can use to manage machines. This ranges from creating user accounts and groups (so that users can log on to a machine) to RAS permissions. Configuring a machine by means of INI files and the Registry is also discussed as well as looking for events in the Event log.

On This Page

User Account Management
Group Account Management
Machine Management
Summary

User Account Management

One of the most important elements in a domain is the user account. Without these accounts, no users could log on to the domain, and without users logging on no work would get done. Even if your domain is totally automated and needs no user intervention, you still need some accounts. You always need an Administrator account, for example, so that you can log on and manage the domain. Likewise many services need accounts to log on to the machine or domain for them to run.

Testing for User Account Existence

Before creating a new user account, you should use the UsersExist() function to make sure that a user account with the same name that you intend to create does not already exist. The syntax that follows shows the basic format of the UsersExist() function:

Win32::NetAdmin::UsersExist( $Machine, $User );

The first parameter ($Machine) is the name of the machine that is to be checked for the account. The machine name must be a proper machine name (prepended with double backslashes). If you are checking on a domain, you will want to specify a Domain Controller (Primary or Backup). If this is an empty string, the local machine is assumed.

The second parameter ($User) is the name of the account to be checked.

If the account does exist, the function returns a TRUE value; otherwise, it returns FALSE. Refer to Example 3.1 and Example 3.4 to see the UsersExist() function in action.

Creating User Accounts

Creating user accounts is rather straightforward; you use the Win32::NetAdmin's UserCreate() function, the syntax for which is as follows:

Win32::NetAdmin::UserCreate( $Machine, $User, $Password, $PasswordAge, 
$Privileges, 
$HomeDir, $Comment, $Flags, $LogonScript );

If the syntax for UserCreate() appears to be overwhelming, don't fret; it's much simpler than you may think. The nine parameters that you need to pass into this function are described in detail in the text that follows.

The first parameter ($Machine) is the server. This is just the name of the machine that this new user account will reside in. If you are creating an account in a domain, you need to specify the Primary (or Backup) Domain Controller. If you are adding the user to your "Accounting" domain, for example, you could use the Win32::NetAdmin::GetDomainController() function to find the Primary Domain Controller for the domain. You would then use that machine as the server for this function. If this parameter is left blank (""), the account is created on the local machine. You could specify a computer name such as '\\Workstation', which would attempt to create the user account on that particular machine.

The second parameter ($User) is the new userid. This is what the user would enter when logging on. This userid is not case sensitive.

Tip For those who are concerned with network security when passing passwords over a network, it is good to know that passwords are encrypted when performing a logon. These passwords, however, are fairly easy to crack if you have a network protocol analyzer. The techniques to crack NT passwords are documented on several hacker pages on the World Wide Web.

If you truly want to secure passwords from even network sniffers, you should make sure that all passwords are more than seven characters in length. Passwords having eight or more characters encrypt differently from those of up to seven characters. This is so passwords are backward compatible with Microsoft's LAN Manager.

The third parameter for UserCreate() ($Password) is the new password for the user account. Note that after this account has been created you cannot use this function to change the password. The length of the password can be no more than 14 characters.

Note: The 14-character limit is defined by the Win32 API and could change in future versions of Windows. If you are a C programmer, this limit is defined in the LMCONS.H header file.

The fourth parameter for UserCreate() ($PasswordAge) is supposed to represent the number of seconds since the password was last changed. This parameter is ignored, and there have been questions as to why it is even a part of this function. It is possible that some future version of NT may use it, but as of this writing there is no need for it. This parameter can be any value, but it is probably best to keep it 0 or an empty string.

Note: The Win32 API requires the $PasswordAge parameter when creating a new user account; however, it is not used and the password age is managed automatically by the operating system. This parameter may be included in the function for backward compatibility issues. Whoever ported the function to Perl most likely did not consider the fact that it was not necessary to include it.

The fifth parameter for UserCreate() ($Privileges) represents privileges that a user may need. A user account can have a few different privileges, but when creating an account, you must use the USER_PRIV_USER privilege. Table 3.1 documents the full list of privileges.

Table 3.1 User privileges to be used with the Win32::NetAdmin::CreateUser() function

Privilege

Assignment

USER_PRIV_GUEST

Assigned to guest accounts

USER_PRIV_USER

Assigned to regular user accounts

USER_PRIV_ADMIN

Assigned to administrator accounts

A user account's privilege can be changed by group management (such as adding the user account to the Administrators group). The privileges are hierarchical, so if the JOEL account is in the Domain Admins, Domain Users, and the Domain Guests groups, the privilege that JOEL will hold is USER_PRIV_ADMIN.

The sixth parameter for UserCreate() ($HomeDir) is the user's home directory. All NT accounts have a home directory and can either be a local directory such as d:\users\joel or it can be a UNC such as \\server2\home\users\joel. Not everyone makes use of this, but it can be quite handy for users who need to have a location on a file server where they can put their files. This is especially important when a company has several PCs and a user can log on to any one of them, making it a necessity to access personal files from anywhere. (This is sometimes known as hoteling—where a user uses a different workstation every day, as is popular in data processing centers.)

The seventh parameter for UserCreate() ($Comment) serves as a comment. This comment does not have any practical use other than associating a string with the user account. Usually administrators make use of this to make sense of an account. An account called IUSR_MACHINE could have a comment of "The Web Server Account", for example, which makes more sense to a human than the userid does. This string is practically unlimited in size and can be an empty string ("").

The eighth parameter for UserCreate() ($Flags) specifies special flags for the account. These flags consist of account options (such as those shown by the User Manager when displaying a user's properties) and account types. This parameter can be any combination of the options listed in Table 3.2 and any one from Table 3.3. These values are all logically OR'ed together.

Table 3.2 User account flags

Flag

Description

UF_ACCOUNTDISABLE

Disable the account. When used with the Win32::NetAdmin::UserCreate() function, the account will be created but disabled.

UF_DONT_EXPIRE_PASSWD

The password for the account never expires.

UF_HOMEDIR_REQUIRED

A home directory is required. (This flag is ignored in Windows NT.)

UF_PASSWD_CANT_CHANGE

The user cannot change the password, only administrators and account operators can change it.

UF_PASSWD_NOTREQD

No password is required on this account. This overrides any policy that requires all accounts to have passwords, and passwords to be a particular size.

UF_LOCKOUT

The account is locked out. This is caused by too many incorrect attempts to log on. This flag cannot be set, but it can be cleared if already set.

UF_SCRIPT

This indicates that the logon script was executed. Usually you would check for this flag on an account if you wanted to know whether the account's logon script was executed the last time the user logged on. For some reason, the Win32 API requires that this flag must be set when creating an account.

Table 3.3 Account types specified by a user and computer account's flags

Account Type

Description

UF_INTERDOMAIN_TRUST_ACCOUNT

Used between domains that indicate a domain trust. This type of account is used by a Primary Domain Controller when connecting to another domain. A domain that trusts another domain will have an account of this type that the other domain's PDC will log on using.

UF_NORMAL_ACCOUNT

Used for a global user account. This is the type of user account that is typically created.

UF_TEMP_DUPLICATE_ACCOUNT

Indicates a local user account. The user can use this account to access the primary domain, but not domains that trust this domain.

UF_SERVER_TRUST_ACCOUNT

Backup Domain Controllers have a computer account of this type. This type of account indicates that this machine is a BDC for the primary domain.

UF_WORKSTATION_TRUST_ACCOUNT

This is the type of account that a server (not a domain controller) or a workstation computer. If a computer logs on to a domain using an account of this type, it is a member of the domain. This is used only with computer accounts.

Note: When creating a user account, you must specify an account type (typically UF_NORMAL_ACCOUNT) and at least the UF_SCRIPT option. These two are the minimum flags that must be used; otherwise the UserCreate() function will fail. You need to logically OR these values together, such as:

$Flags = UF_SCRIPT | UF_NORMAL_ACCOUNT;

The ninth parameter for UserCreate() ($LogonScript) is the logon script. This is the path to a program or batch file that will be executed when the user is logging on with this account. Any valid local path or UNC is allowed, as is an empty string ("").

Note: Only administrators and account operators can successfully call the Win32::NetAdmin::UserCreate() function.

The code in Example 3.1 illustrates the use of the UserCreate() function.

Example 3.1 Creating new user accounts

01. use Win32::NetAdmin;
02. use Win32::AdminMisc;
03. $User = "Joel";
04. $FullName = "Joel Smith";
05. $Domain = "Accounting";
06. %Account = (
07. password => "\U$User\",
08. homedir  => "\\\\HomeServer\\Home\\$User",
09. priv     => USER_PRIV_USER,
10. flags    => UF_SCRIPT | UF_NORMAL_ACCOUNT,
11. fullname => $FullName,
12. comment  => "The account for $FullName",
13. logon    => "perl \\\\LogonServer\\logon\\Logon.pl",
14. );
15. Win32::NetAdmin::GetDomainController( '', $Domain, $Server );
16. if( ! Win32::NetAdmin::UsersExist( $Server, $User ) )
17. {
18. if( Win32::NetAdmin::UserCreate( $Server, $User, $Account{password}, 0, 
$Account{priv}, $Account{homedir}, $Account{comment},
$Account{flags},$Account{logon} ) )
19. {
20. Win32::AdminMisc::UserSetMiscAttributes( $Server, $User,
USER_FULLNAME=>$Account{fullname} );
21. }
22. }else{
23. print "The account already exists.\n";
24. }

Creating Machine Accounts

It may be interesting to know that when an NT machine is registered as part of a domain, a machine account is created just as a user account is created for a new user. This machine account is not much different from a user account. In fact, the Win32 API uses the same function to create machine accounts that it uses to create user accounts. This means that you can create a machine account by calling Win32::NetAdmin::UserCreate(). To do this, you must specify the UF_WORKSTATION_ACCOUNT flag from Table 3.3. This will work for not only workstations but for adding Backup Domain Controllers to a domain as well as adding interdomain trusts by specifying the corresponding account type flag listed in Table 3.3.

Unlike creating a normal user account, you must follow some tricks to create a machine account. The following list describes these tricks:

  • The first parameter must point to the Primary Domain Controller.

  • The account type used in the eighth parameter must be UF_WORKSTATION_TRUST_ACCOUNT (or whatever appropriate value you require for the type of account you are creating).

  • The name (the second parameter) must be the computer's name in all uppercase characters, and the last character must be a dollar sign ($). The computer name \\BETTYS-COMPUTER would use the name: "BETTYS-COMPUTER$".

  • The name must not be more than 15 characters long. This does not include the trailing dollar sign.

  • The password (the third parameter) must be the name of the computer in all lowercase characters (as opposed to the account name) and must not include a trailing dollar sign. This password cannot be more than 14 characters. If the computer name contains more than 14 characters, truncate the password at the fourteenth character. To use the preceding example, the password for \\BETTYS-COMPUTER would be "bettys-compute".

  • The user calling a script that creates machine accounts needs to have the Administrator privilege on the target computer specified in parameter one.

After a workstation's (or server's, controller's, or domain trust's) account has been created, you need to log the machine on to the domain. The first time the new machine is logged on, it negotiates with the PDC for a new password that will be used every time the new machine logs on. This happens automatically when a machine logs on to a domain in which a new account has been created. The PDC detects that the machine is logging on for the first time and initiates the changes.

After a machine account has been created (either manually by a Perl script or by using any domain administration tool), the account can be managed as if it were a regular user account. The account can be renamed, the password changed (be very careful with this, because you cannot tell a machine which password to use—only set it to the original password setting as described in the preceding list), it can be deleted, and the accounts can be listed using Perl functions found in Win32::NetAdmin and Win32::AdminMisc. If you manage machine accounts, you must follow the rules outlined in the preceding list.

As a side note, only administrators and those users who have been granted the SeMachineAccountPrivilege (also known as "Add Workstations to Domain" in the User Manager) can create and manage machine accounts.

Changing User Account Configuration

After an account has been created, it can be manipulated. The various properties that make up an account can be both queried and set by using the Win32::NetAdmin extension's UserGetAttributes and UserSetAttributes functions:

Win32::NetAdmin::UserGetAttributes($Machine, $UserName, $Password, $PasswordAge, 
$Privilege, $HomeDir, $Comment, $Flags, $ScriptPath);
Win32::NetAdmin::UserSetAttributes( $Machine, $UserName, $Password, $PasswordAge, 
$Privilege, $HomeDir, $Comment, $Flags, $ScriptPath);

The first parameter ($Machine) is the machine where you want to retrieve the account information. This should be your Primary Domain Controller, but any domain controller will do. (If you use a Backup Domain Controller, it will just take longer for your other DCs to learn about any updates to the account.) If this string is empty, the local machine is assumed. This string should be formatted as a proper machine name with double backslashes as in \\PrimaryDC.

The second parameter ($UserName) is the name of the user account.

The third parameter ($Password) is the password for the account. This is only used with the UserSetAttributes() function. The Win32 API does not support retrieving passwords.

The fourth parameter ($PasswordAge) is the age of the password. This indicates how many seconds since the last time the password has been changed. This value is automatically set by NT and cannot be set, only retrieved.

The fifth parameter ($Privilege) is the privilege that the account has been granted. These can be any one listed value from Table 3.1.

The sixth parameter ($HomeDir) is the home directory for the account. This is any full path or UNC. It can also be an empty string.

The seventh parameter ($Comment) is the comment field for the account. This can be an empty string.

The eighth parameter ($Flags) represents the account's option flags. These flags can consist of any number of flags specified in Table 3.2 and one flag from Table 3.3. If you are setting this, these options are logically OR'ed together. If you are testing for these flags, you need to logically AND this parameter with the constant to test whether the flag is set. (A TRUE result indicates the flag is set.)

The ninth parameter ($ScriptPath) is the full path (either local path or UNC) of the account's logon script. This can be an empty string.

If the functions are successful, they return a TRUE value; otherwise they return a FALSE. The UserSetAttributes() will update the account with the information passed into the function. The UserGetAttributes() will retrieve the values from the account that the passed in parameters represent, as in Example 3.2.

Example 3.2 Using Win32::NetAdmin::UserGetAttributes()

use Win32::NetAdmin;
use Win32::AdminMisc;
$User = "Joel";
$Domain = "Accounting";
Win32::NetAdmin::GetDomainController( "", $Domain, $Server );
if( Win32::NetAdmin::UserGetAttributes( $Server, $User, $Password,$PassAge, $Privilege, $HomeDir, $Comment, $Flags, $ScriptPath ) )
{
print "The user '$User' has a home directory of '$HomeDir'.\n";
}

The Win32::AdminMisc extension has identical functions with the exception that there is an added parameter. The third parameter is the account's full name. This is generally the full name of the user whom the account represents.

These functions and their kin in the Win32::AdminMisc extension are pretty much made obsolete by two functions in the Win32::AdminMisc extension called UserGetMiscAttributes() and UserSetMiscAttributes(). These two functions retrieve and set several different options, ranging from the necessary and obvious to the obscure and vague:

Win32::AdminMisc::UserGetMiscAttributes( $Server, $User, \%Attribs )_
Win32::AdminMisc::UserSetMiscAttributes( $Server, $User, $Option=>$Attribute[, 
$Option2=>$Attribute2[, ...] ] )

Both these functions specify a first parameter ($Server) that is the name of the machine or the domain to retrieve the information. If this is an empty string (""), the domain currently logged on to is assumed. If a machine is specified, you need to prepend the machine's name with double forward- or backslashes, as in \\ServerA or //ServerB. All the functions in the Win32::AdminMisc extension allow for this capability to use either forward- backslashes. Be aware, however, that this is not a practice found in most other extensions.

The second parameter ($User) is the name of the user account.

For UserGetMiscAttributes(), the third parameter (\%Attribs) is the last one passed into the function. This parameter is a reference to a hash. If successful, this hash will be populated with the keys described in Table 3.4.

For UserSetMiscAttributes(), all parameters starting with $Option=>$Attribute must be specified in pairs. These pairs are formatted as either:

attribute, value

or

attribute=>value

The format used is not important (because both of them are equivalent), but the second is preferred because it is easier to see what attribute is associated with which value.

When setting attributes, you need only to specify the attributes that you are changing—unlike the NetAdmin and AdminMisc's UserSetAttributes(), in which you need to specify all values.

Both functions return a TRUE value if successful, and a FALSE value if they fail.

Table 3.4 User Account attributes used with Win32::AdminMisc::UserGetMiscAttributes() and Win32::AdminMisc::UserSetMiscAttributes()

Attribute

Description

USER_ACCT_EXPIRES

Specifies when the account will expire. This value is stored as the number of seconds elapsed since 00:00:00, January 1, 1970. A value of TIMEQ_FOREVER indicates that the account never expires. Note that this is the same time format that Perl uses, so you can pass this value into Perl's time functions such as localtime(). To set the time to expire in one week, you could use: time() + (7 * 24 * 60 * 60).

USER_AUTH_FLAGS

The user's operator privileges. This is a read-only value and cannot be changed using the Win32::AdminMisc's SetUserMiscAttributes() function. This value is based on membership in local groups. If the user is a member of the Print Operations group, AF_OP_PRINT is set. If the user is a member of Server Operations, AF_OP_SERVER is set. If the user is a member of the Account Operations group, AF_OP_ACCOUNTS is set. The value AF_OP_COMM is never set. (It looks like it was designed for future versions of NT.)

USER_BAD_PW_COUNT

Specifies the number of times the user tried to log on to this account using an incorrect password. A value of 0xFFFFFFFF indicates that the value is unknown for some reason. This attribute is read-only and is maintained separately on each Domain Controller in the domain. To get an accurate value, each domain controller in the domain must be queried, and the sum is used.

USER_CODE_PAGE

The code page for the user's language of choice. Windows NT does not use this code page, but it is included for backward compatibility.

USER_COMMENT

The user account comment.

USER_COUNTRY_CODE

The country code for the user's language of choice. Windows NT does not use this country code, but it is included for backward compatibility.

USER_FLAGS

This value consists of several flags that determine features of the user account. Table 3.2 lists these flags. This parameter also describes the type of account being described. Table 3.3 lists a description of account types.

USER_FULL_NAME

The full name of the user.

USER_HOME_DIR

The path of the home directory for the user specified.

USER_HOME_DIR_DRIVE

This specifies the drive letter assigned to the user's home directory. This is used primarily for logon purposes.

USER_LAST_LOGOFF

Specifies when the last logoff occurred. This value is stored as the number of seconds elapsed since 00:00:00, January 1, 1970. A value of zero means that the last logoff time is unknown. This attribute is read-only. This attribute is maintained separately on each Domain Controller in the domain. To get an accurate value, each domain controller in the domain must be queried and the largest value is used.

USER_LAST_LOGON

Specifies when the last logon occurred. This value is stored as the number of seconds elapsed since 00:00:00, January 1, 1970. This attribute is read-only. This attribute is maintained separately on each Domain Controller in the domain. To get an accurate value, each domain controller in the domain must be queried, and the largest value is used.

USER_LOGON_HOURS

Points to a 21-byte (168 bits) bit string that specifies the times during which the user can log on. Each bit represents a unique hour in the week. The first bit (bit 0, word 0) is Sunday, 0:00 to 0:59; the second bit (bit 1, word 0) is Sunday, 1:00 to 1:59; and so on.

USER_LOGON_SERVER

The name of the machine to which logon requests are sent. Machine names are preceded by two backslashes (\\). When a machine name is represented by an asterisk (\\*), the logon request can be handled by any logon server. An empty string indicates that requests are sent to the domain controller. This attribute is read-only. For Windows NT Servers, UserGetMiscAttributes() will return \\* for global accounts.

USER_MAX_STORAGE

Specifies the maximum amount of disk space the user can use. Use the value specified by USER_MAXSTORAGE_UNLIMITED to use all available disk space. As of Windows NT 4.0, this is not used; but future versions that support disk quotas may use this.

USER_NAME

Specifies the name of the user account. This is a read-only value and cannot be set by the UserSetMiscAttributes() function. If you want to change the user name, you need to use the Win32::AdminMisc::Rename() function.

USER_NUM_LOGONS

Counts the number of successful times the user tried to log on to this account. A value of 0xFFFFFFFF indicates that the value is unknown. This attribute is read-only and is maintained separately on each Domain Controller in the domain. To get an accurate value, each domain controller in the domain must be queried, and use the sum of all the values.

USER_PARMS

This is set aside for use by applications. This string can be an empty string, or it can have any number of characters. Microsoft products use this attribute to store user configuration information. Altering this value is not recommended.

USER_PASSWORD

Specifies a one-way encrypted LAN Manager 2.x-compatible password. This is not retrieved due to the Win32 API. It is here for backward compatibility with LAN Manager. For all practical purposes, this attribute is ignored and can neither be set using the UserSetMiscAttributes() function nor retrieved with the UserGetMiscAttributes() function.

USER_PASSWORD_AGE

Specifies the number of seconds elapsed since the password was last changed. This value is managed automatically by NT and cannot be set.

USER_PASSWORD_EXPIRED

Determines whether the password of the user has expired. For UserGetMiscAttributes(), this attribute will be zero if the password has not expired (and nonzero if it has). For UserSetMiscAttributes(), specify nonzero to indicate that the user must change his password at next logon and specify zero to turn off the message indicating that the user must change password at next logon. If this parameter is set to a non-zero value the user will only be able to log on interactively, at which time he will be forced to change passwords. Logging on as a process (non-interactively) will fail.

USER_PRIMARY_GROUP_ID

Specifies the relative ID (RID) of the Primary Global Group for this user. This attribute must be DOMAIN_GROUP_RID_USERS (defined in NTSEAPI.H for you C coders). This attribute must be the RID of a global group in which the user is enrolled. For most Perl scripts, this value is ignored.

USER_PRIV

Consists of one of three values that specify the level of privilege assigned the user account. Refer to Table 3.1 for information on the privileges.

USER_PROFILE

A path to the user's profile. This can be an empty string, a full local path, or a UNC.

USER_SCRIPT_PATH

The path of the user's logon script, .COM, .CMD, .EXE, or .BAT file. Basically any executable program can be placed here. If your machine has mapped the .PL extension to execute PERL.EXE, this value could specify a Perl script (such as LOGON.PL). This value can be an empty string to specify that there is no logon script.

USER_UNITS_PER_WEEK

Specifies the number of equal-length time units into which the week is divided. This attribute uses these time units to compute the length of the bit string in the USER_LOGON_HOURS attribute. This value must be UNITS_PER_WEEK for LAN Manager 2.0. This attribute is read-only. For Windows NT services, the units must be one of the following: SAM_DAYS_PER_WEEK, SAM_HOURS_PER_WEEK, SAM_MINUTES_PER_WEEK.

USER_WORKSTATIONS

This is a list of workstation and server names from which the user can log on. Up to eight machine names can be listed and must be separated by commas (,). An empty string/value indicates that there is no restriction. To disable logons from all workstations to this account, set the UF_ACCOUNTDISABLE value in the USER_FLAGS attribute.

In Example 3.3, the UserGetMiscAttributes() and UserSetMiscAttributes() functions are used to enable any account in the "Accounting" global group that has been previously disabled. Notice that the USER_FLAGS attribute is logically AND'ed with the UF_ACCOUNTDISABLE constant to determine whether the account has been disabled. To turn off the account disabled bit, you need to be careful not to alter any other bits. This is done by AND'ing the USER_FLAGS value with the two's complement of the UF_ACCOUNTDISABLE constant. If you are not familiar with two's complement, consider it to be the binary inverse of a number. If I specify ~4 (the two's compliment of four), for example, I am referring to the binary value "11111011" which is the inverse of the binary value for 4: "00000100".

By using the two's complement, you can be sure to turn off only the UF_ACCOUNTDISABLE bit; which is the same as enabling the account.

Example 3.3 Using AdminMisc's UserGetMiscAttributes() and UserSetMiscAttributes()

01. use Win32::NetAdmin;
02. use Win32::AdminMisc;
03. $Group = "Accounting";
04. if( Win32::NetAdmin::GroupGetMembers( "", $Group, \@Members ) )
05. {
06. foreach $User ( @Members )
07. {
08. my%Attribs;
09. Win32::AdminMisc::GetUserMiscAttributes( "", $User, \%Attribs );
10. 
11. # Check to see if the account disabled bit is set
12. if( $Attribs{USER_FLAGS} & UF_ACCOUNTDISABLE )
13. {
14. # The account is disabled so re-enable it by ANDing the
15. # flags attribute
16. # with the two's compliment of the disable account flag.  This will
17. # turn off only the UF_ACCOUNTDISABLE bit but leave other bits as 
18. # they are
19. $Flags = $Attribs{USER_FLAGS} & ~UF_ACCOUNTDISABLE;
20. Win32::AdminMisc::UserSetMiscAttributes( "", $User,
21  USER_FLAGS=>$Flags );
22. }
23. }
24. } 

Note: To set the USER_PASSWORD_EXPIRED flag, the account's password must be able to expire. See the UF_DONT_EXPIRE_PASSWD value under the USER_FLAGS attribute for more details. You cannot specify zero to negate the expiration of a password that has already expired.

Renaming User Accounts

Quite often, there is a need to rename an account. If a user changes his or her name for any reason (such as a marriage), for example, there may be a need to rename the userid. You could just create a new user account, but that would mean having to reconfigure it, copy over the old profile, add the account to any needed groups, reapply permissions on to directories and files … oh just so much work! It is much easier to just rename the account.

By renaming the account, all configuration information remains the same, as do all file permissions and group memberships. In fact, renaming an account only changes the userid name; everything else is left as it originally was.

The RenameUser() function facilitates this need:

Win32::AdminMisc::RenameUser( $Machine, $User, $NewName );

The first parameter ($Machine) is the name of a machine or domain where the user account resides. If this is an empty string, the current domain logged on to is assumed.

The second parameter ($User) is the name of the user account that will be renamed.

The third parameter ($NewName) is the new name of the userid.

The use of this function requires Administrator or Account Operator privileges. If successful, the function returns a TRUE value; otherwise it returns FALSE. Example 3.4 shows an example of how to use the RenameUser() function.

Example 3.4 Renaming a user account

01. use Win32::NetAdmin;
02. use Win32::AdminMisc;
03. $User = "Joel";
04. $NewUser = "Jane";
05. $Domain = "Accounting";
06. Win32::NetAdmin::GetDomainController( '', $Domain, $Server );
07. if( Win32::NetAdmin::UsersExist( $Server, $User ) )
08. {
09. if( Win32::AdminMisc::RenameUser( $Server, $User, $NewUser ) )
10. {
11. print "The account '$User' was renamed to '$NewUser'.\n";
12. }
13. }else{
14. print "Could not rename account '$User' because it does not exist.\n";
15. }

Managing User Passwords

Passwords are always a problem for any administrator and user. The user must be forced to remember a countless number of passwords, some of which he has no control over. The administrator is always resetting passwords for those users who have forgotten them. There are a few ways to manage this hassle with the Win32 extensions.

The first of the password functions can be run by anyone (no special privileges are required):

Win32::AdminMisc::UserCheckPassword( $Machine, $User, $Password );

The first parameter ($Machine) is the domain name or a proper machine name where the user account resides. If a domain name is specified, the domain's PDC will be looked up and used. If this is an empty string, the domain currently logged on to is assumed.

The second parameter ($User) is the name of the user account. If this is an empty string, the account that is running the script is assumed.

The third parameter ($Password) is the password for the user account.

If the password for the specified user account is correct, the function returns a TRUE value; otherwise FALSE is returned. It is important to know that this function will check the password by attempting to change the password to a new password (which is the same password). UserCheckPassword() does this just as if it were calling Win32::AdminMisc::UserChangePassword() but passing in the same value for both the old and new passwords. This can be a problem on some systems if they do not allow reuse of passwords or if the account is not allowed to change passwords.

Several functions actually provide the capability to change the password for an account. The first two are identical to each other:

Win32::NetAdmin::UserChangePassword( $Machine/$Domain), $User, $OldPassword, 
$NewPassword 
);_
Win32::AdminMisc::UserChangePassword( ($Machine | $Domain), $User, $OldPassword, 
$NewPassword );

Both functions are identical and either can be used.

In both these functions, the first parameter ($Domain or $Machine) is the domain name or proper machine name where the user account resides. If this is an empty string, the domain currently logged on to is assumed.

The second parameter ($User) is the name of the user account. If this is an empty string, the account that is running the script is assumed.

The third parameter ($OldPassword) is the current password for the user account.

The fourth parameter ($NewPassword) is the new password.

If the password is successfully changed, both of these functions return TRUE; otherwise they return FALSE.

The new password is limited to the policies placed down on user accounts such as limits to the number of characters used, if old passwords can be reused, and whether the account is allowed to change passwords. Anyone can change any account password with these functions, provided that he knows the current password for the account.

Administrators usually have to reset a password for users who have forgotten their passwords. This can be done by using the SetPassword() function:

Win32::AdminMisc::SetPassword( ($Machine | $Domain), $User, $NewPassword );

The first parameter ($Domain or $Machine) is the proper machine name or the domain where the account resides. An empty string indicates the domain currently logged on to.

The second parameter ($User) is the name of the user account whose password will be changed.

The third parameter ($NewPassword) is the new password.

The SetPassword() function will work only if the user calling it has administrative rights on the machine or domain which houses the user account. It will return a TRUE if the password was successfully changed; otherwise it returns FALSE. Just like the other password functions, policy restrictions can cause this function to fail.

Removing User Accounts

When an account is no longer needed, it can be removed from the user account database. It is usually a better practice to disable the account rather than delete it. This is because if for whatever reason you need the account later you can reactivate it and its configuration as it was when disabled. When an account is disabled, no one can log on using that account; for all practical purposes, it may as well be deleted. For more details on deleting accounts and their consequences, refer to the following note.

Note: Disabling an account is an alternative to deleting one. When an account is deleted, literally all information related to the account is lost. This includes file ownership and permissions, user privileges, group memberships, Registry permissions, and share accesses to name a few.

Suppose that your manager quits one day and your company hires another one. If you deleted the preceding manager's account, you would have to re-create a new one that would have to be totally reconfigured. If you just disabled the preceding manager's account, he could not log on or access his old data. When the new hire starts, you could just rename the disabled account, change the password, and reactivate the account. This way the new manager has her new account with the exact same privileges and permissions and file ownerships that the preceding manager had.

Disabling an account can be accomplished by setting the UF_ACCOUNTDISABLED bit on the USER_FLAGS attribute. Refer to Win32::AdminMisc::UserSetMiscAttributes() for more details.

If the account will no longer be used, it can be deleted with the UserDelete() function:

Win32::NetAdmin::UserDelete( $Machine, $Account );

The first parameter ($Machine) is the proper name of the machine to remove the account from. If the specified machine is a domain controller, the account will be removed from the domain the server represents. If the machine specified is a workstation or a server, the account is deleted from the machine's local account database. If an empty string ("") is passed in, the account is removed from the current domain that the machine is logged on to.

The second parameter ($Account) is the name of the account that is to be removed.

If the account is successfully deleted, the function returns a TRUE value; otherwise FALSE is returned.

Tip When removing a user account from a domain, it is best to point the first parameter to the Primary Domain Controller for the domain. If the account is removed from a Backup Domain Controller (BDC), the BDC must be synchronized with the Primary Domain Controller (PDC) for the account to truly be removed. From then it must be synchronized from the PDC to the remaining BDCs in the domain. This can take some time unless you force domain resyncing using the Server Manager program. This means that even though you delete an account, someone may log on using it 10 minutes later because he logged on to a domain controller that had not been updated.

When an empty string is specified as the first parameter to the Win32::NetAdmin::UserDelete() function, an attempt will be made to delete the account on the PDC.

Managing User RAS Attributes

When an administrator needs to manage user accounts and whether they have access to RAS and any of its attributes, it can use the Win32::RASAdmin extension's UserGetInfo() function:

Win32::RASAdmin::UserGetInfo( ($Machine | $Domain), $User, \%Info );

The first parameter ($Machine or $Domain) is the proper machine name that you want to get the list of groups from. This can also be a domain name. If you specify a domain name, it will use either the PDC or a BDC (whichever is most readily available) for the specified domain. If this is an empty string, a domain controller for the domain currently logged on to will be used.

The second parameter ($User) is the name of the user account.

The third parameter (\%Info) is a reference to a hash that will be emptied and then populated with RAS attributes.

If UserGetInfo() is successful, the hash is populated with values found in Table 3.5 and it returns a TRUE; otherwise it returns FALSE. If the function fails, the hash is emptied.

The user's RAS attributes can be changed using the UserSetInfo() function:

Win32::RASAdmin::UserSetInfo( ($Machine | $Domain), $User, $Attribute=>$Value[, 
$Attribute2=>$Value2] );

The first parameter ($Machine or $Domain) is the proper machine name that you want to get the list of groups from. This can also be a domain name. If you specify a domain name, it will use either the PDC or a BDC (whichever is most readily available). If you need to be certain that the list comes from the PDC, you need to pass in the proper name of the PDC. If this is an empty string, a domain controller for the domain currently logged on to will be used.

The second parameter ($User) is the name of the user account.

The third and fourth parameters ($Attribute=>$Value and $Attribute2=>$Value2) are the attribute and attributes value respectively. The list of attributes are found in Table 3.5.

Optional fifth and sixth parameters are just like the third and fourth parameters. These optional parameters enable you to set all attributes with one call to this function.

If UserSetInfo() is successful, it returns a TRUE; otherwise, it returns FALSE.

Table 3.5 User attributes used with both the UserGetInfo() and UserSetInfo() from the Win32::RASAdmin extension

Attribute

Description

Callback

If the callback privilege is set, the RAS server will call the user back using this phone number. There is no limit to the number. It can be a long-distance or local call. Any valid phone number syntax is accepted.

Privilege

A set of flags that describe the privileges that the user has. The list of values are a combination of values from Table 3.6.

Table 3.6 RAS privileges

Privilege

Description

RASPRIV_DialinPrivilege

Sets the account such that the user is allowed to dial in to a RAS server. If an account does not have this flag set, that user cannot log on to a RAS server.

RASPRIV_NoCallback

Specifies that there is no callback phone number. After the user dials in and is authenticated, he is then online.

RASPRIV_AdminSetCallback

Specifies that only the administrator can set the callback phone number. This number can be set using the Win32::RASAdmin::UserSetInfo() function.

RASPRIV_CallerSetCallback

Specifies that the user can determine which number the server will use as the callback number. When the user logs on, he will be prompted for a callback phone number.

Group Account Management

Where you have user accounts, you will find groups. Basically speaking, a group is a collection of user accounts. You may want to group all users who have administrative privileges into a group called Admins. Anyone who is a guest could be placed into a Guests group. You could also have groups for Email Users and Accountants. In this respect, an NT group is very similar to a traditional UNIX group.

The beauty about NT groups is that a user can be in multiple groups simultaneously. You could have an accountant who has email access, so her account could be in both the Accountants and Email Users groups.

You need to know about two types of groups: global groups and local groups.

A global group can contain only user accounts and can be accessed from any machine that participates in the domain. You may put all administrators into your global Domain Admins group, for example. This way all the machines in your domain can see and use this global group. You can configure each workstation to allow the Domain Admins total unrestricted access over it. If one of your administrators leaves, you can just remove his name from the Domain Admins global group so that he does not have access over any of the machines in the domain.

A local group can contain both user accounts and global groups. Unlike global groups, a local group can only be accessed from the workstation that defines the group. You can add the domain's global Domain Admins group into the local Administrators group on each of your workstations so that each machine allows all members of the Domain Admins group administrative access over the machine.

Each group must have a unique name. No two groups can be named the same even though one may be global and the other is local.

Note: Global and local groups are not the same as global and local user accounts. The terms global and local have different meanings when used for groups than they have when used for user accounts.

A full description of these meanings is beyond the scope of this book, but it is important to know the differences that exist between the use of global versus local. A good NT administrator's book is recommended.

Almost all the group functions that appear in the Win32 extensions come in two flavors: one for local groups and another for global groups. Ideally, this would be hidden from the Perl coder by using one function that determines whether the specified group is local or global; but alas this is not the case. The rationale for having two functions for each type of group comes from the Win32 API, which specifies two variations for every group function. It would appear that the original group management functions were direct interfaces to these Win32 API functions. All this means is that you may need to try both functions to get an accurate answer. If you are testing to see whether a user is a member of a group, for example, you need to first test by checking the local group; if it fails, you then check the global group. Because there is typically no way to determine whether a given group is local or global, you should indeed test for both possibilities.

Creating Groups

You can create groups by using one of two functions:

Win32::NetAdmin::GroupCreate( $Machine, $Name, $Comment );
Win32::NetAdmin::LocalGroupCreate( $Machine, $Name, $Comment );

Both functions are easy to use. You just pass in the proper machine name, the name of the new group, and a comment. If the call is successful a TRUE value is returned; otherwise a FALSE is returned.

The first parameter ($Machine) can be any valid machine name or an empty string. If it is a valid machine name, the group will be added in that machine if you have permissions to add to it. If an empty string ("") is specified, the group will be created on the local machine. If you are adding a global group from a domain, specify the Primary Domain Controller.

The second parameter ($Name) is the name of the group. It can be any string up to 256 characters, with the exception that it cannot be an empty string.

The third parameter ($Comment) is the group's comment and can be any string up to 256 characters long, including an empty string. Example 3.5 shows how to create a global group.

Example 3.5 Creating a new group

Use Win32::NetAdmin;
$Machine = "\\\\server1";
if( Win32::NetAdmin::GroupCreate($Machine, _"My Group", "This group is a test") )
{
print "Successful.";
}else{
print "Failed: " . Win32::FormatMessage( Win32::GetLastError() );
}

Adding Users to a Group

After a group has been created, you need to add users to the group. This is achieved by using the GroupAddUsers() function:

Win32::NetAdmin::GroupAddUsers( $Machine, $Group, $User[, $User2[, ... ] ] );
Win32::NetAdmin::LocalGroupAddUsers( $Machine, $Group, $User[, $User2[, ... ] ] );

The first parameter ($Machine) is the proper name of the machine that contains the group. If this is an empty string, the local machine is assumed.

The second parameter ($Group) is the name of the group.

The third (and optionally more) parameter ($User) is the name of a valid user account. You can specify multiple user accounts by just tacking them to the end of the function's parameter list. You could just use an array of user accounts for this parameter.

If successful, this function returns a TRUE value; otherwise, it returns FALSE.

Notice that Example 3.6 first tries to retrieve a list of users from the group first. This is done to make sure that the group exists before attempting to add any users. This way you can also determine whether the group is a local or global group.

Example 3.6 Adding users to a group

01. use Win32::NetAdmin;
02. @Users = (
03. "Patrick",
04. "Jonathan",
05. "William",
06. "Leonard" );
07. $Domain = "Staff";
08. $Group = "Crew";
09. Win32::NetAdmin::GetDomainController( '', $Domain, $Server );
10. 
11. # Let's check to see if the group exists and if it does then we
12. # will know what type of group it is.
13. if( Win32::NetAdmin::GroupGetAttributes( $Server, $Group, $Comment ) )
14. {
15. $Result = Win32::NetAdmin::GroupAddUsers( $Server, $Group, @Users );
16. }else{
17. if( Win32::NetAdmin::LocalGroupGetAttributes( $Server, $Group, 
$Comment ) )
18. {
19. $Result = Win32::NetAdmin::LocalGroupAddUsers( $Server, 
$Group, @Users );
20. }else{
21. die "The group $Group does not exist.\n";
22. }
23. }
24. if( $Result )
25. {
26. print "Users have been added to the group $Group.\n";
27. }else{
28. print "Could not add users to the group $Group.\n";
29. }

Removing Users from a Group

Removing users from a group is very similar to adding users to a group. To remove users from a group, you can use the global or local form of the GroupDeleteUsers() function:

Win32::NetAdmin::GroupDeleteUsers( $Machine, $Group, $User[, $User2[, ... ] ] )
Win32::NetAdmin::LocalGroupDeleteUsers( $Machine, $Group, $User[, $User2[,
 ... ] ] );

The first parameter ($Machine) is the proper name of the machine that contains the group. If this is an empty string, the local machine is assumed.

The second parameter ($Group) is the name of the group.

The third (and optionally more) parameter ($User) is the name of a valid user account. You can specify multiple user accounts by just tacking them to the end of the function's parameter list. You could just use an array of user accounts for this parameter.

If successful, this function returns a TRUE value; otherwise it returns FALSE.

Removing a Group

To remove a group, you need to know what kind of group it is: local or global. Two functions accommodate the removal:

Win32::NetAdmin::GroupDelete( $Machine, $Group );
Win32::NetAdmin::LocalGroupDelete( $Machine, $Group );

Just like the functions that add a group, the first parameter is a valid machine name (in the form of '\\machine') or an empty string ("") that indicates the local machine. If you are removing a global group from a domain, specify the Primary Domain Controller.

The second parameter is the name of the group to be deleted.

If successful, the group will be removed from the machine and a TRUE value will be returned; otherwise, a FALSE value will be returned.

The code in Example 3.7 defines a subroutine that accepts a machine name and group name. This example also attempts to remove the group regardless of whether it is global or local.

The GroupDelete() and LocalGroupDelete() functions will return a TRUE value if successful and a FALSE value if they fail.

Example 3.7 Removing a group name

01. Use Win32::NetAdmin;
02. if( MyDeleteGroup( "\\\\server1", "My Group" ) )
03. {
04. print "Successful!\n";
05. }else{
06. print "Failed: " . Win32::FormatMessage( Win32::GetLastError() );
07. }
08. 
09. sub MyDeleteGroup
10. {
11. my($Machine, $Group) = @_;
12. my($Result) = 1;
13. if( ! Win32::NetAdmin::GroupDelete ($Machine, "My Group" ))
14. {
15. if( ! Win32::NetAdmin::LocalGroupDelete($Machine, "My Group"))
16. {
17. $Result = 0;
18. }
19. }
20. return $Result;
21. }

Retrieving Lists of Groups

If you need to discover which groups exist, you can use the GetGroups() function:

Win32::AdminMisc::GetGroups( $Machine, $GroupType, (\@List | \%List) [, $Prefix ] );

The first parameter ($Machine) is the machine that you want to get the list of groups from. This can also be a domain name. If you specify a domain name it will use either the PDC or a BDC (whichever is most quickly available). If you need to be certain that the list comes from the PDC, you must pass in the name of the PDC. If this is an empty string, a domain controller for the domain currently logged on to will be used.

The second parameter ($GroupType) is the type of group you are looking for. Table 3.7 lists possible values.

Table 3.7 Group types to be used with Win32::AdminMisc::GetGroups()

Group type

Description

GROUP_TYPE_LOCAL

Retrieves the list of local groups

GROUP_TYPE_GLOBAL

Retrieves the list of global groups

GROUP_TYPE_ALL

-Retrieves all group names (both local and global)

The third parameter for the GetGroups() function (\@List or \%List) is a reference to an array or a hash that will be populated with a list of group names. If a hash is specified, it will be populated with subhashes of group information.

The fourth parameter ($Prefix) is optional. If this is specified, only group names that begin with the same characters as are specified in this parameter are returned. Suppose that you need to collect only the group names that begin with "Test" (your test groups). You would specify "Test" as this parameter. This is convenient if your domain has thousands of groups and you need to access only a few of them.

Because of restrictions of the Win32 API, you can only retrieve the list of local groups if you hold administrative privileges (such as administrators and account operators). Anyone can retrieve the global group list. The GetGroups() function will populate the specified array or hash and return a value of TRUE if successful and a value of FALSE if not. Example 3.8 shows the GetGroups() function in action.

Example 3.8 Retrieving the list of group names

01. use Win32::AdminMisc;
02. $Domain = "Accounting";
03. if( Win32::AdminMisc::GetGroups( $Domain, GROUP_TYPE_LOCAL, \@Groups ) )
04. {
05. DumpGroups( "local groups", @Groups );
06. }
07. if( Win32::AdminMisc::GetGroups( $Domain, GROUP_TYPE_GLOBAL, \@Groups ) )
08. {
09. DumpGroups( "global groups", @Groups );
10. }
11. 
12. sub DumpGroups
13. {
14. my( $Type, @List ) = @_;
15. my( $iCount ) = 0;
16. print "This is the list of $Type:\n";
17. map { printf( "\t%03d) %s\n", ++$iCount, $_ ); }, @List;
18. }

Managing Group Attributes

After a group has been created, you can get/set its comment by using the GroupGetAttributes(), LocalGroupGetAttributes(), GroupSetAttributes(), and LocalGroupSetAttributes() functions, the syntax for which is as follows:

Win32::NetAdmin::GroupGetAttributes( $Machine, $Group, $Comment );
Win32::NetAdmin::LocalGroupGetAttributes( $Machine, $Group, $Comment );
Win32::NetAdmin::GroupSetAttributes( $Machine, $Group, $Comment );
Win32::NetAdmin::LocalGroupSetAttributes( $Machine, $Group, $Comment );

All four of these functions accept the same three parameters.

The first parameter ($Machine) is the name of the machine that defines the group. An empty string indicates the local machine. To access a domain's global group, specify the Primary Domain Controller.

The second parameter ($Group) is the name of the group.

The third parameter ($Comment) is the comment for the group.

The GroupSetAttributes() and LocalGroupSetAttributes() functions will change the comment of the group, whereas the GroupGetAttributes() and LocalGroupGetAttributes() functions will retrieve the comment for the specified group and assign it to the third variable.

If the function used is successful, it will return a TRUE value; otherwise it returns a FALSE.

Example 3.9 demonstrates the functions for retrieving the comment for a given group.

Example 3.9 Retrieving a group's comment

01. use Win32::NetAdmin;
02. $Domain = "Accounting";
03. $Group = "Domain Admins";
04. $GroupFound = 1;
05. Win32::NetAdmin::GetDomainController( '', $Domain, $Server );
06. if( ! Win32::NetAdmin::LocalGetAttributes( $Server, $Group, $Comment ) )
07. {
08. if( ! Win32::NetAdmin::GetAttributes( $Server, $Group, $Comment ) )
09. {
10. $GroupFound = 0;_11    }
12. }
13. if( $GroupFound )
14. {
15. print "The comment for $Group is: '$Comment'.\n";
16. }else{
17. print "The group $Group could not be found.\n";
18. }

Listing Users in a Group

Quite often, you need to process a list of users who are associated with some group. If you have a group called Email Users, for example, you may need to change their logon scripts. To do this, you would first need to know exactly who is in the Email Users group. This is where the global and local versions of the GroupGetMembers() function come into play:

GroupGetMembers( $Machine, $GroupName, \@Users );
LocalGroupGetMembers( $Machine, $GroupName, \@Users );
LocalGroupGetMembersWithDomain( $Machine, $GroupName, \@Users );

There is no difference between the first two functions other than one only works with global groups and the other with local groups. The third function is available from the core distribution's Win32 library version .12 (libwin32-0.12) and later versions. It retrieves both the domain and the user name or each account in a local group.

The first parameter ($Machine) is the name of the machine that holds the list. If this is an empty string the local machine is assumed.

The second parameter ($GroupName) is the name of the group.

The third parameter is a (\@Users) reference to an array that will be populated with the user accounts that are members of the group. If using the LocalGroupGetMembersWithDomain() function, the array will be populated with the members' user names and their domains in the form of 'domain\username'.

Both functions will return a TRUE value if successful; otherwise, they will return FALSE.

Example 3.10 uses the GroupGetMembers() function to change the logon script for all members in a particular group. Notice that the code tries both versions of the function because it does not know whether the group is a local or global group.

Example 3.10 Retrieving the members in a group

01. use Win32::NetAdmin;
02. use Win32::AdminMisc;
03. $Group = "Email Users";
04. $Domain = "Accounting";
05. $LogonScript = "perl \\\\ServerA\\Logon\\EmailUsers.pl";
06. Win32::NetAdmin::GetDomainController( '', $Domain, $Server );
07. # Get the list of group members
08. if( ! Win32::NetAdmin::GetGroupMembers( $Server, $Group, \@UserList ) )
09. {
10. Win32::NetAdmin::LocalGetGroupMembers( $Server, $Group, \@UserList ) || 
die "There is no group called '$Group'.\n";
11. }
12. foreach $User ( @UserList )
13. {
14. if( Win32::AdminMisc::UserSetMiscAttributes( $Server, $User, 
      USER_LOGON_SCRIPT=>$LogonScript ) )
15. {
16. print "Successfully changed logon script for user '$User'.\n";
17. }
18. }

Verifying User Membership in a Group

Sometimes it is quicker to just test and see whether a particular user is a member of a group as opposed to retrieving the list of members and searching through it. You can use the GroupIsMember() function for this:

Win32::NetAdmin::GroupIsMember( $Machine, $GroupName, $User );
Win32::NetAdmin::LocalGroupIsMember( $Machine, $GroupName, $User );

The first parameter ($Machine) is the proper name of the machine that houses the group. If this is an empty string, the local machine is assumed.

The second parameter ($GroupName) is the name of the group.

The third parameter ($User) is the user account to be checked.

If these functions are successful, they return TRUE; otherwise, they return FALSE.

Machine Management

Administrating a domain of machines can be quite a job. Users are always deleting system files, changing bootup configurations, and reconfiguring software. Something is always happening to keep a team of administrators running from desktop to desktop. Luckily Perl's Win32 extensions have some handy functions that make this administration easier.

Administrators of Win32 machines need to manage INI files and the Registry. Additionally, NT machines need the Event log to be managed. Perl provides the capability to perform such tasks.

Managing INI Files

For many years now, INI files have plagued administrators because they are so easy for any user to change. Because there are no tools in NT or Windows 95 (let alone Win 3.x and DOS) to manage INI files, administrators have been using Perl to do the work.

An INI file is a file that programs use to hold configuration information. Think of INI to stand for INItialization, as in the configuration used to initialize a program. These files follow a particular format, which makes it easy to manage because all INI files follow this format. The file is broken into sections, each having a unique name. Each section can contain several keys that have equated values. Example 3.11 shows an INI file that contains one section called FileManager. (This was taken from a Windows 95 machine's File Manager INI file.) This section has four keys: Path, SearchSpec, Flags, and SavedSearches. You can see that the values for these flags can be either numeric or a character string.

Example 3.11 Example of an INI file

[FileManager]
Path=C:\TEMP
SearchSpec=*.doc
Flags=260
SavedSearches=0

Occasionally, you may need to change the contents of an INI file. A company I worked with had to roll out a version of Microsoft's Internet Explorer that worked with NT version 3.51. This version (3.0) made use of an INI file for its configuration. It had been installed on several hundred computers and the users were using it left and right. A network change was made, and the INI files had to be altered to reflect new proxy settings. A new INI file could have been copied down to everyone's machine during his or her logon script, but that would reset any personalization that the user had made. Instead we made use of the Win32::AdminMisc extension's capability to read from and write to INI files, as demonstrated in the following sections.

Reading INI Files

Data can be read from an INI file by using the ReadINI() function:

Win32::AdminMisc::ReadINI( $File, $Section, $Key );

The first parameter ($File) is the path to an INI file. This does not need to be a full path, but it does need to point to a valid INI file. If only the file name is specified (with no path), the file will be looked for in the current directory. If it is not found there, the Windows directory is searched followed by the Windows System directory, and then the environmental variable %PATH% is searched.

The second parameter ($Section) is the name of the section in the INI file. This can be an empty string.

The third parameter ($Key) is the name of a particular key in the specified section. If this parameter is an empty string, the function returns an array of key names.

If the ReadINI() function is successful, it returns one of the following:

  • The data associated with the key in the specified section from the INI file.

  • If the $Key parameter is an empty string, an array is returned containing the names of all keys in the specified section.

  • If the $Section parameter is an empty string, an array is returned containing the names of all sections in the file.

Example 3.12 shows this function at work.

Writing INI Files

INI files can be altered by using the WriteINI() function:

Win32::AdminMisc::WriteINI( $File, $Section, $Key, $Value );

The first parameter ($File) is the path to an INI file. This does not need to be a full path, but it does need to point to a valid INI file. If only the file name is specified (with no path), the file will be looked for in the current directory. If it is not found there, the Windows directory is searched, followed by the Windows System directory, and then the environmental variable %PATH% is searched.

The second parameter ($Section) is the name of the section in the INI file. If this is an empty string, the function removes all sections from the specified INI file.

The third parameter ($Key) is the name of a particular key in the specified section. If this is an empty string, the function removes all keys in the specified section.

The fourth parameter ($Value) is the value that will be associated with the specified key. If this is an empty string, the function removes the specified key.

If the WriteINI() function is successful, it returns a TRUE value; otherwise it returns a FALSE. When the function succeeds, the result will be one of the following:

  • The value will be associated with the specified key in the specified section in the INI file. This overwrites any value already present.

  • If the value parameter is an empty string, that key will be removed from the section.

  • If the key parameter is an empty string, all keys in the specified section will be removed.

  • If the section parameter is an empty string, all sections in the file will be removed.

Example 3.12 demonstrates both the ReadINI() and WriteINI() functions.

Example 3.12 Adding and removing data from an INI file

01. use Win32::AdminMisc;
02. $File = "$ENV{WinDir}\\win.ini";
03. $Section = "Devices";
04. @Keys = Win32::AdminMisc::ReadINI( $File, $Section, "" );
05. foreach $Key ( @Keys )
06. {
07. $Devices{$Key} = Win32::AdminMisc::ReadINI( $File, $Section, $Key );
08. }
09. # Remove the entire section...
10. if( Win32::AdminMisc::WriteINI( $File, $Section, "", "" ) )
11. {
12. # Now add the devices again recreating the section...
13. foreach $Key ( @Keys )
14. {
15. print "Adding '$Key=$Device{Key}' to [$Section]...\n";
16. Win32::AdminMisc::WriteINI( $File, $Section, $Key, $Device{$Key} );
17. }
18. }

Tip It may be handy to know that Win32::AdminMisc's ReadINI() and WriteINI() functions are not case sensitive. Therefore you need not concern your script with matching the case of a section or key name.

Additionally, the specified file can use either forward or backslashes. The functions will convert any forward slashes to backslashes before doing its work. This is quite nice if you are using Perl-friendly path names as opposed to DOS paths.

Tieing an INI File

There is another way to access and manipulate INI files: using the Perl tie function. If you are not aware of the tie function, it would be best to look it up in a good Perl reference book. This is a very powerful function.

Basically, the tie function will associate a variable with a Perl module. Whenever any action is performed on the variable, certain methods in the module are called. When you "tie" a variable to a module, you are adding what is called magic to the variable. This makes the variable magic—which is pretty cool!

Consider this example: Suppose that you have hooked up your house lights to your computer so that you can control them by means of a program. Assume also that you have written a Perl extension that controls the lights, enabling you to control them via Perl scripts. You could add code to your extension so that a hash, let's call it %Lights, is created. Now every time you query the hash, such as print $Lights{porch}, the status of the lights returns. If the porch light is on, $Lights{porch} returns a 1; if it is off, it returns a 0. If you were to set $Lights{porch} to 1, as in $Lights{porch} = 1, the porch light would turn on. Likewise setting it to 0 would turn off the porch light.

This could be done using magic (or tied) hashes. When you tell the hash (%Lights) that it is tied to your module, Perl will call your module to handle such things as querying the hashes values, setting the values, enumerating the keys in the hash, and so on. This is what is referred to as tieing or magic (because the hash appears to act as if it were magical).

The Win32::Tie::Ini extension provides magic to any hash that is tied with this extension. The hash will be an interface to an INI file.

To use this extension, you must first load it using the use command:

use Win32::Tie::Ini;

After this has been performed, you can tie a hash to any INI file using Perl's tie command:

tie( %Hash, "Win32::Tie::Ini", $File );

The first parameter (%Hash) is the hash you want to tie.

The second parameter is the name of the module; in this case it must be "Win32::Tie::Ini".

The third parameter ($File) is the name of the INI file. This does not need to be a full path, but it does need to point to a valid INI file. If only the file name is specified (with no path), the file will be looked for in the current directory. If it is not found there, the Windows directory is searched, followed by the windows system directory, and then the environmental variable %PATH% is searched.

If the tie function is successful, it returns a reference to the hash. There is no need to save this reference because it just points to the first parameter, the hash.

After that you have successfully tied a hash to an INI file, you can start to read settings. It is important to note that because INI files consist of sections containing keys, the hash will reflect that (in that the hash's key is a section name). The value of a key is a reference to another hash. That other hash will contain the section's keys, which are accessible by means of the hash's keys.

Reading from a Tied INI File

To read the keys from a section, you must first retrieve a reference to the section's hash, such as:

$SectionRef = $Hash{devices};

Now $SectionRef is a reference to a hash that contains the keys to the section. From this point, you can print out the keys and values of the [Devices] section using the following:

foreach $Key ( keys( %$SectionRef ) )
{
print "$Key=$SectionRef–>{$Key}\n";
}

Notice that you must de-reference $SectionRef to get to its keys because it is a reference to a hash (not just a hash).

You could, of course, de-reference the hash reference when you first assign it by using the following:

%Section = %{ $Hash{devices} };

Now you could use the %Section hash as a regular hash:

foreach $Key ( keys( %Section ) )
{
print "$Key=$Section{$Key}\n";
}

It is very important to note that by de-referencing the hash reference (as in the second example), you have a copy of the INI file's data. Making changes to this new hash will not update the INI file. So, if you were to do this:

%Section = %{ $Hash{devices} };
$Section{WinFax} = "This is a test";

the WinFax key in the [Devices] section of the INI file will not be changed. This is because you are changing a key's data in the %Section hash. This hash is a copy of the INI data; it is not tied to the INI file at all (as %Hash is).

On the other hand, if you used the hash reference, as in the first example, changes made to it will be saved to the INI file. This is because the hash reference is still tied to the INI file. For more details about this, consult a Perl manual regarding hashes, hash references, and tieing (such as O'Reilly & Associate's Advanced Perl Programming).

Writing to a Tied INI File

Writing to the INI file is as easy as setting the value of a hash. Continuing from the preceding section, assume that %Hash is tied to an INI file (the WIN.INI file in particular).

You could set the data for the WinFax key in the [Devices] section by using:

$Hash{devices}–>{WinFax} = "This is my new test data";

It is that easy. You could just as well use:

$Section = $Hash{devices};
$Section–>{WinFax} = "This is my new test data";

Notice that only references are used here. If you use a regular hash, it will be a copy of the data from the hash reference—which is not tied, so any changes will not be saved to the INI file.

Removing Data from a Tied INI File

To remove a key or section in an INI file, all you need to do is use the delete command as you normally would:

delete $Hash{devices}–>{WinFax};

This removes the WinFax key from the [Devices] section of the INI file. Likewise this code:

delete $Hash{devices};

removes the entire [Devices] section from the INI file.

Untieing an INI File

After you finish with the INI file, all you need to do is untie the hash. Then you really are finished! You use Perl's untie command for this:

untie %Hash;

The code in Example 3.13 demonstrates how easy it is to use the Win32::Tie::Ini extension to perform the same task as Example 3.12.

Example 3.13 Using the Win32::Tie::Ini extension version of Example 3.12

01. use Win32::Tie::Ini;
02. $File = "$ENV{WinDir}\\win.ini";
03. $Section = "Devices";
04. tie( %Hash, "Win::Tie::Ini", $File ) || die "Could not tie to $File.\n";
05. # Make a *copy* of the INI files section
06. %Devices = %{ $Hash{$Section} };
07. # Remove the entire section...
08. delete $Hash{$Section};
09. # Now add the devices again recreating the section...
10. %{ $Hash{$Section} } = %Devices;
11. untie %Hash;

Managing the Registry

Not all configuration information is stored in INI files. Since the Win32 platform has been released, Microsoft has been encouraging programmers to make use of the Registry and not use INI files. There is good reason for this, because the Registry is a central repository of configuration data. This makes it easier for administrators to find configuration data because it is always in a database rather than scattered among hundreds of directories and other files on a hard drive.

Note: On Windows NT machines, a user's Registry information can be stored as the user's profile on a file server. If the user's account is configured to use a "roaming profile," when the user logs on to a different machine, it loads the profile from the file server. This enables the user to always have access to his personal configurations regardless of which machine he may be logged on to.

On the other hand, in many cases the Registry becomes so huge with information that it slows the machine down when a program is accessing configuration data from within the Registry. Additionally, it is more difficult to modify the Registry than it is to modify an INI file.

Win32 Perl has a standard Registry extension that provides access to Registry data. This makes administrating these important databases a breeze.

The Registry is a database that consists of keys, subkeys, values, and data. Think of it like a file system. A key is like a directory. Whereas a directory can contain files and other subdirectories, a key can contain values and other subkeys. A value is just a name associated with some type of data, just as a file name is just name associated to data (the data held within the file).

Just like your Win32 machine can have multiple drives, the Registry has multiple root keys. These roots define what kind of data is held underneath them. To illustrate this, imagine that you have only system files on your C: drive, only data files on your D: drive, and only applications on your E: drive. These roots will come into play in the section on opening keys.

The Win32::Registry extension automatically creates Win32::Registry objects that point to the Registry's root keys (or hives). Table 3.8 lists these objects.

Table 3.8 Predefined Win32::Registry objects that reflect the Win32 Registry root keys

Registry object

Description

$HKEY_LOCAL_MACHINE

Points to the hive that contains configuration information about the computer. This includes device drivers and service configurations.

$HKEY_CURRENT_USER

Points to the current user's hive. This hive is not available when accessing a Registry remotely.

$HKEY_CLASSES_ROOT

This is the root key for all OLE/COM-related class information. This includes MIME types and file associations. This root is a link to HKEY_LOCAL_MACHINE \_Software \Classes. This is important to know because this root key is unavailable when connecting a Registry remotely.

$HKEY_USERS

This is the root key for all user hives.

$HKEY_PERFORMANCE_DATA

This is the root key for all performance data. The Win32 does not have a separate API to gather performance data; instead it uses the Registry API (the RegQueryValueEx() function in particular) to retrieve performance data. The Registry API maps requests using this root key to the actual performance data counters and registers.

$HKEY_CURRENT_CONFIG

Points to the current configuration in the Registry.

$HKEY_DYN_DAT

This root key is the Windows 95/98 equivalent to NT's HKEY_PERFORMANCE_DATA.

A hive is any key and all its values and subkeys. Typically a hive will represent a tree of keys and subkeys related to something. A user's personal configuration on an NT machine is stored as part of the user's profile, for example. When the user logs on to a machine, it will load up that user's hive into the Registry and point the HKEY_CURRENT_USER root key to it. If a user's profile does not exist, a new one is created based on the default profile. This way you always know that the current user's configuration information is available via that root key. A hive is basically any collection of keys, subkeys, and values.

Hives can be loaded into and unloaded from the Registry. An administrator may need to modify a particular user's Registry settings, for example. The administrator could log on to a machine using the user's ID and password (presuming that he knew it or reset it). This would cause the user's personal hive to be loaded into the Registry and linked to the HKEY_CURRENT_USER root. The administrator could then proceed to edit the HKEY_CURRENT_USER's contents. When the administrator logs off, the hive is unloaded from the Registry. This could be quite a burden if the administrator had to change several users' configurations.

The alternative to this tedious approach is to manually load the user's hive into the Registry of some machine. When a hive is manually loaded, a temporary key name is specified and created, such as Joel's Hive. If the hive loaded successfully, the administrator can edit the contents of the root key Joel's Hive. When finished, the hive is just unloaded. This technique allows a Perl script to systematically load, modify, and unload any number of hives quickly and without human interaction.

Note: The authors of the Win32::Registry extension included some pretty useful Registry functions. For some unknown reason, however, they did not provide access to all these methods through its module. This means that to use these functions you have to access the extension's functions directly as functions, not as methods. Each function will be discussed when appropriate.

Opening a Registry Key

When accessing data in the Registry, you need to open a key, just like you would open a directory using the opendir() function. After the key has been opened, you can manipulate the values and data within the key. To open a key, use the Open() method:

$RegistryObject–>Open( $Path, $Key )

Notice that Open() is a method of a Win32::Registry object. The first time you are opening a Registry key, you should use one of the root key objects (which are predefined by the extension) listed later in Table 3.9.

The first parameter ($Path) passed into the method is the path to the key that is to be opened. The path is relative to the object that is calling the method. If you want to open HKEY_LOCAL_MACHINE \Software \ActiveState, for example, you would specify a path of Software\ActiveState and the method is called from the $HKEY_LOCAL_MACHINE object.

The second parameter ($Key) is a scalar that will become a Registry object. You will use this later to call other methods.

If successful, the Open() method returns a TRUE value and a new Registry object is created and the scalar specified as the second parameter is set to the new object. If the method fails, it returns a FALSE. The method may fail because the key does not exist or because you do not have permissions to open the key.

Now that a key is opened, you can enumerate subkeys and values as well as retrieve and set data. Refer to Example 3.14.

Example 3.14 Opening a Registry key

use Win32::Registry;
if( $HKEY_LOCAL_MACHINE–>Open( "Software\\ActiveState", $Key ) )
{
...process registry key...
$Key –>Close();
}else{
print "Could not open the key.  It may not exist.\n";
}

Creating a Registry Key

Just like opening a key, you can create a key using the Create() method. Creating a key will not only create the key, but it will open it just like the Open() method. If the key already exists, the key is opened and the method is successful.

$RegistryObject–>Create( $Path, $Key );

The first parameter passed into the Create() method ($Path) is the path to the key that is to be created. The path is relative to the object that is calling the method. If you want to create HKEY_LOCAL_MACHINE \Software \ActiveState \Perl5, for example, you would specify a path of Software\ActiveState\Perl5 and the method is called from the $HKEY_LOCAL_MACHINE object.

The second parameter ($$Key) is a scalar that will become a Registry object. You will use this later to call other methods.

If successful, the Create() method returns a TRUE value and the specified key is created as well as a new Registry object is created and the scalar specified as the second parameter is set to the new object. If the Create() method fails, it returns a FALSE. The Create() method may fail because the key was unable to be created or opened due to permissions. Example 3.15 demonstrates this method.

Example 3.15 Creating a Registry key

01. use Win32::Registry;
02. if( $HKEY_LOCAL_MACHINE–>Create( "Software\\ActiveState\\Perl5", $Key ) )
03. {
04. # We come here if the key was successfully created
05. # or if the key already existed and the Create() method
06. # successfully opened the key.
07. ...process registry key...
08. $Key–>Close();
09. }else{
10. print "Could not create the key.  It may not exist.\n";
11. }

Connecting to a Registry

Because administrators occasionally have to manipulate the Registry on remote machines, the Win32 API defines a function that will connect to a remote Registry. This is done with the Connect() method:

$RegistryObject–>Connect( $Machine, $Root );

The first parameter ($Machine) is the name of a machine. This needs to be a proper machine name in the format of \\MachineName.

The second parameter ($Root) is a scalar variable that will be set to a new Registry object. You will use this later to call other methods.

The Connect() method is called from one of the predefined root keys listed in Table 3.8. If successful, the scalar variable passed into the second parameter will be a Registry object that represents the root key on the remote machine that was used to call the method. The return value, if successful, is TRUE; otherwise it is FALSE.

Assume that you use this method from the $HKEY_LOCAL_MACHINE object:

$HKEY_LOCAL_MACHINE–>Connect( "\\\\ServerA", $ServerRoot );

The result is that you will have a new Registry object that represents the HKEY_LOCAL_MACHINE root key on the machine \\ServerA that $ServerRoot represents. Anywhere you need to access something from the HKEY_LOCAL_MACHINE key on the remote machine (\\ServerA), you would just use $ServerRoot. Example 3.16 demonstrates this.

Note: Some versions of Win32::Registry did not expose the Connect() function, so it was up to the author to hack the REGISTRY.PM file and add one. This can be done by adding the following code to the REGISTRY.PM file:

sub Connect
{
my $self = shift;
if( $#_!= 1 ){
die 'usage: Connect( $Machine, $KeyRef )';
}
($Machine) = @_;
local( $Result, $SubHandle );
$Result = Win32::Registry::RegConnectRegistry( $Machine, $self–>{handle}, $SubHandle);
$_[1] = _new( $SubHandle );
if( !$[1] ){
return 0;
}
return $Result;
}

Example 3.16 Processing Registry entries on all BDCs in a domain

01. use Win32::Registry;
02. use Win32::NetAdmin;
03. $Domain = "Accounting";
04. Win32::NetAdmin::GetServers( '', $Domain, SV_TYPE_DOMAIN_BAKCTRL, \@BDCs );
05. foreach $Machine ( @BDCs )
06. {
07. if( $HKEY_LOCAL_MACHINE–>Connect( $Machine, $Root ) )
08. {
09. # We have a connection to the BDC...
10. if( $Root–>Open( "Software\\ActiveState", $Key ) )
11. {
12. ...process registry key...
13. $Key–>Close();
14. }
15. $Root–>Close();
16. }
17. }

Managing Registry Keys

Registry keys can contain values and subkeys. The values have data associated with them. The names of these elements can be a bit confusing because their relatives in INI files use similar names but have different meanings. In an INI file, for example, a key is the equivalent of a Registry value. Likewise, an INI file has keys associated with values, but the Registry has values associated with data. This seems pretty screwy, but it is not too difficult to get used to.

Tip The Registry functions are not case sensitive with respect to key and value names. When specifying either a key or a value name, you can mix case if you like. To open a key, for example, the following two statements are considered valid and the same:

$HKEY_CURRENT_USER–>Open( "Software\\Microsoft", $Key);
$HKEY_CURRENT_USER–>Open( "SOFwarE\\mICROsofT", $Key);

Any Win32::Registry object can be used to open a key, not just the predefined root keys. This means that a Win32::Registry object created by successful calls to the Open() or Create() methods can be used to open subkeys. In Example 3.17, the "HKEY_LOCAL_MACHINE \Software \ActiveState \Perl5" key is opened twice. The first time it is opened as a subkey from the $Key object, and the second time it's opened by using a root key object. This illustrates that it really does not matter how you open the keys; either method will work.

Example 3.17 Opening subkeys

01. use Win32::Registry;
02. if( $HKEY_LOCAL_MACHINE–>Open( "Software\\ActiveState", $Key ) )
03. {
04. # Open a subkey using a previously created registry object
05. if( $Key–>Open( "Perl5", $SubKey ) )
06. {
07. ...process registry subkey...
08. $SubKey–>Close();
09. }
10. # Open the same subkey using a root key object
11. if( $HKEY_LOCAL_MACHINE–>Open( "Software\\ActiveState\\Perl5", $SubKey ) )
12. {
13. ...process registry subkey...
14. $SubKey–>Close();
15. }
16. $Key–>Close();
17. }else{
18. print "Could not open the key.  It may not exist.\n";
19. }

Generally, if you are processing keys by recursively calling a function, it is easier to pass a new Registry object into the recursive function, as in Example 3.18.

Example 3.18 Processing keys using recursive functions

01. use Win32::Registry;
02. if( $HKEY_LOCAL_MACHINE–>Open( "Software\\ActiveState", $Key ) )
03. {
04. # Dump all value names and sub keys...
05. ProcessKey( $Key );
06. $Key–>Close();
07. }
08. 
09. sub ProcessKey
10. {
11. my( $Key ) = $_;
12. my( $SubKeyName, $SubKey, @KeyList);
13. mY( $Value, %ValueList );
14. 
15. # Dump the key's values...
16. 
17. $Key->GetValues( %Values );
18. foreach $Value ( sort( keys( %ValueList ) ) )
19. {
20. print "$Value=$ValueList{$Value}[2]\n";
21. }
22. # Process all subkeys
23. $Key–>GetKeys( \@KeyList );
24. foreach $SubKeyName ( sort( @KeyList ) )
25. {
26. if( $Key–>Open( $SubKeyName, $SubKey ) )
27. {
28. print "Processing key: $SubKeyName:\n";
29. ProcessKey( $SubKey );
30. $SubKey–>Close();
31. }
32. }

Querying Registry Keys

You can retrieve information about a key such as how many subkeys and how many values it contains by using the QueryKey() method:

$RegistryObject–>QueryKey( $KeyClass, $NumOfKeys, $NumOfValues );

The first parameter ($KeyClass) is a scalar variable that will be set to the class of the key. This class is vaguely defined in the Microsoft SDK, but it seems to be inherited from previous versions of Windows when a Registry key could have only one value (or more accurately one data item associated with the key). In the Win32 world, this is equivalent to the default value for a key—the data which is assigned to a value with no name. (The name is an empty string.)

The second parameter ($NumOfKeys) is a scalar variable that will be set to the number of subkeys contained in the key that the $RegistryObject represents.

The third parameter ($NumOfValues) is a scalar variable that will be set to the number of values that the key has.

The QueryKey() method is handy to determine how many subkeys and values a key has; but other than that, it is really not of much value, as illustrated in Example 3.19.

If the QueryKey() method is successful, it returns a TRUE; otherwise it returns FALSE.

Example 3.19 Querying a Registry key

01. use Win32::Registry;
02. $KeyName = "Software\\Microsoft\\NetDDE\\DDE Shares\\CHAT\$";
03. if( $HKEY_LOCAL_MACHINE–>Open( $KeyName, $Key ) )
04. {
05. if( $Key–>QueryKey( $MyClass, $NumOfKeys, $NumOfValues ) )
06. {
07. print "The '$KeyName' key contains $NumOfKeys subkeys and $NumOfValues values.\n";
08. }
09. $Key–>Close();
10. }

Retrieving Subkey Names

For the sake of enumerating, you can retrieve a list of subkeys that are in a particular key. This can be done with the GetKeys() method:

$RegistryObject–>GetKeys( \@SubKeys );

The only parameter is a reference to an array that will be populated with the names of each subkey.

If the method is successful, it will return a TRUE value and the array will be filled with subkey names. Otherwise the method returns a FALSE. Example 3.18 makes use of this method.

Removing Registry Keys

A key can be removed, or deleted, from the Registry. Care should be used when deleting keys because you can easily delete a key with several subkeys. Consider that if you delete the HKEY_CURRENT_USER \Software key, all software configuration for the user will be lost. Likewise, deleting HKEY_LOCAL_MACHINE \System will remove the configuration for Windows; all system drivers and such will be lost.

Deleting a key is done using the DeleteKey() method:

$RegistryObject–>DeleteKey( $KeyName );

The only parameter passed in ($KeyName) is the name of the key. This is a path to a name relative to the $RegistryObject.

If the key was successfully deleted, DeleteKey() returns TRUE; otherwise, it returns FALSE.

Example 3.20 illustrates the DeleteKey() method.

Warning: Opening a key does not prevent it from being deleted. If you open the HKEY_LOCAL_MACHINE \Software \Microsoft key, for example, you can still delete the HKEY_LOCAL_MACHINE \Software key (which will remove all subkeys, including the Microsoft key you opened).

Example 3.20 Deleting a key

use Win32::Registry;
if( $HKEY_LOCAL_MACHINE–>Open( "Software", $Key ) )
{
$Key–>DeleteKey( "ActiveState\\Perl5" );
$Key–>Close();
}

Retrieving Registry Key Value Data

Any key can have any number of values. Each value has data associated with it. This data can either be a number, binary, or text data. To retrieve the data a value is associated with, you can use the QueryValue() method:

$RegistryObject–>QueryValue( $SubKeyName, $Value );

The first parameter ($SubKeyName) is the name of a subkey that will be queried.

The second parameter ($Value) will be set with the data associated with the default value for the specified key.

Chances are that QueryValue() is not what you think it is. If you want to query a value in a key, this will not do it for you. Let me explain.

This method calls the Win32 API's RegGetValue() function, which was introduced in Windows 3.1. Back in Windows 3.1, there was no concept of a key having multiple values. Actually there was no difference between a value and a key. This function exists only for backward compatibility with Windows 3.1. Because Win32 Perl has never existed in the Windows 3.1 environment, however, there is some question as to why the QueryValue() function was ever exposed by the Win32::Registry extension.

The QueryValue() method retrieves the default data value from the key specified. This is of little value to most programmers because they normally will need to retrieve data for a named value (as opposed to the unnamed values).

The QueryValueEx() method enables you to retrieve data for a value name of "" (an empty string)—this is the exact same as using the QueryValue() method. Because of this, you should consider QueryValue() to be obsolete and of little use. Instead, use QueryValueEx().

A much better way of querying a value's data is using the QueryValueEx() method:

$RegistryObject–>QueryValueEx( $ValueName, $DataType, $Data );

The first parameter ($ValueName) is the name of the value to be retrieved. This can be an empty string.

The second parameter ($DataType) is a scalar variable that will be set to contain the data type. Table 3.9 lists the data types.

The third parameter ($Data) is a scalar variable that will be set to contain the data of the value.

If the QueryValueEx() method call is successful, it will return a TRUE value and the second parameter and third parameter's variables are set to contain their respective information. Otherwise, the method returns FALSE.

In some builds of both ActiveState and core distribution versions of this extension, QueryValueEx() was not exported by the extension's module. If you run into this, you can call the extension's function directly by using the following:

Win32::Registry::RegQueryValueEx( $RegistryObject–>{handle}, $ValueName, $Reserved, 
$DataType, $Data );

Notice that this is not called as an object's method but instead as an extension's function. The first parameter is the Registry's handle from the Registry object. This is just the "handle" key from the Registry object.

The second parameter ($ValueName) is the name of the value.

The third parameter ($Reserved) is a reserved setting that directly translates to a reserved setting in the Win32 API function. Always specify a zero (0).

The fourth parameter ($DataType) is a scalar variable that will be set to contain the data type. Table 3.9 lists the data types.

The fifth parameter ($Data) is a scalar variable that will be set to contain the data of the value.

Just like the method version of this function, if RegQueryValueEx() is successful, it will return a TRUE value and the second parameter and third parameter's variables are set to contain their respective information. Otherwise, the method returns FALSE.

Setting Registry Key Values

To create a value in a key, you use the SetValue() method:

$RegistryObject–>SetValue( $KeyName, $Type, $Data );

The first parameter ($KeyName) is the name of a key that will be created.

The second parameter ($Type) is the type of data the value will hold and must be REG_SZ (refer to Table 3.9).

The third parameter ($$Data) is the data to be associated with the value name.

The SetValue() method will create a subkey with the name specified by the first parameter. This new key will contain one value that will have no name and its data will be set to the data passed in as the third parameter. If successful, SetValueEx() returns a TRUE value; otherwise, it returns a FALSE.

The SetValue() method reflects the same problem as the QueryValue() method. In most cases, a programmer will want to set a value in a key (not create a new key with a non-named value). You should consider SetValue() as obsolete and of little value. Instead use SetValueEx():

$RegistryObject–>SetValueEx( $ValueName, $Reserved, $DataType, $Data );

The first parameter ($ValueName) is the name of the value. If this value does not exist, it will be created.

The second parameter ($Reserved), oddly enough, is not used. You should always pass a zero (0) as this parameter. The Win32 API requires this parameter. Because it is always 0, however, I do not know why the authors of the extension decided to expose it in the Perl interface.

The third parameter ($DataType) is the data format that will be stored. Refer to Table 3.9 for a list of data types.

The fourth parameter ($Data) is the actual data to be stored.

If a call to the SetValueEx() method is successful, the key's specified value will be set with the specified data. A TRUE value will also be returned. If it fails, FALSE is returned. The code in Example 3.21 shows how to use the SetValueEx() method.

Example 3.21 Modifying a value's data

01. use Win32::Registry;
02. $KeyName = "Software\\Microsoft\\Office\\8.0\\Word\\Options";
03. $ValueName = "AutoSave-Path";
04. if( $HKEY_CURRENT_USER–>Open( $KeyName, $Key ) )
05. {
06. if( $Key–>QueryValueEx( $ValueName, $DataType, $Data ) )
07. {
08. if( ( REG_SZ == $DataType ) || ( REG_EXPAND_SZ == $DataType ) )
09. {
10. # If we were autosaving to the C: drive change it
11. # to the D: drive.
12. $Data  =~ s/^c:/d:/i;
13. $Key–>SetValueEx( $ValueName, 0, $DataType, $Data );
14. }
15. }
16. }

Table 3.9 Registry data types

Registry data type

Description

REG_SZ

A typical character string.

REG_EXPAND_SZ

A character string that has embedded environmental variables that need to be expanded.

REG_MULTI_SZ

An array of null terminated strings. The end of the list is terminated by a null character (so there should be two null characters at the very end of the list).

REG_BINARY

Any form of binary data. This could be of any length.

REG_DWORD

A 32-bit number.

REG_DWORD_LITTLE_ENDIAN

A 32-bit number in little endian format (where the most significant byte of the word is the high-order word). Typically this is the same as REG_DWORD. This type is used on Intel processors.

REG_DWORD_BIG_ENDIAN

A 32-bit number in big endian format (where the most significant byte of the word is the low-order word). Motorola processors make use of this format.

REG_LINK

A symbolic link in the Registry. This is the data type used to map root keys such as HKEY_CURRENT_USER to another key within the Registry.

REG_NONE

No defined value.

REG_RESOURCE_LIST

A device driver resource list.

Listing Registry Key Values

To get a list of values from an opened key, you can use the GetValues() method:

$RegistryObject–>GetValues( \%ValueList );

The only parameter passed in this method is a reference to a hash.

If the method is successful, it will return a TRUE value and populate the hash; otherwise, it returns FALSE.

When the GetValues() method completes successfully, the hash is populated with keys that represent the names of the values in the key. Each of the hash's keys is associated with an array consisting of the three elements listed in Table 3.10. Notice that element 0 of the array is identical to the hash's key name. Example 3.18 demonstrates this method.

Table 3.10 The array returned by GetValues()

Array Element

Description

Element 0

The name of the value.

Element 1

The data type of the value's data. Refer to the list of data types in Table 3.9.

Element 2

The value's data.

Removing Registry Key Values

You can delete a value just as you can delete a key. This is done by using the DeleteValue() method:

$RegistryObject–>DeleteValue( $ValueName );

The only parameter specified in this method is the name of the value to be deleted.

If DeleteValue() is successful, it will return a TRUE; otherwise it returns a FALSE value.

Managing Registry Hives

You can save any key (and its values and subkeys) to a file. This file is considered to be a hive that can be loaded into a Registry.

Saving a Hive

Saving a Registry hive is an easy way to migrate Registry modifications across several machines. By saving a hive to a file, a script can later either load the hive or replace an existing key with the saved hive. This way you could configure a program, save the hive that contains the program's configuration values to a file, and then use that file on other Registry. Descriptions about how to load hives and replace keys with hives follow this section.

To save a hive (a key with all its values and subkeys), you use the Save() method:

$RegistryObject–>Save( $File );

The only parameter passed in Save() is the name of a file that will be created.

If successful, the Save() method will save the key and all its values and subkeys to a binary file that will be marked as a hidden, read-only system file. If the file already exists, the method will fail.

The resulting file is considered to be a hive. Example 3.22 demonstrates saving a Registry hive.

Example 3.22 Saving a Registry hive

use Win32::Registry;
$HiveFile = "c:\\temp\\Software.key";
if( $HKEY_LOCAL_MACHINE–>Open( "Software", $Key ) )
{
Key–>Save( $File );
$Key–>Close();
}

Note: It is very important to note that all methods and functions in the Win32::Registry extension that make use of files (such as Save() and Load()) assume that the file's path is relative to the machine housing the Registry. This means that if you are connected to a remote Registry and load a hive from c:\temp\hive.reg, the file must be on the remote machine's c:\temp directory.

Additionally, the Microsoft Win32 documentation specifies that Registry files created with any file extension on a FAT-formatted drive will fail to load. This does not seem to be the case in Windows 95, but your mileage may vary.

Loading a Hive

After a hive has been saved to a file, it can be loaded into a Registry where it can be edited. This is what the Load() method is all about:

$RegistryObject–>Load( $KeyName, $File );

The first parameter ($KeyName) is the name of the key that will represent the hive. This name must not already exist. You cannot load a hive over an existing key; if you try, the method will fail.

The second parameter ($File) is the path to a saved hive file.

The $RegistryObject that calls this method must be one of either $HKEY_LOCAL_MACHINE or $HKEY_USERS. It cannot be any non-root key that you have opened.

Note: The Load() method can only be called from a true root key such as HKEY_LOCAL_MACHINE and HKEY_USERS. Other root keys such as HKEY_CURRENT_USER are not truly root keys because they are just links to other non-root keys.

The root HKEY_CURRENT_CONFIG is really just a link to HKEY_LOCAL_MACHINE \System \CurrentControlSet \Hardware Profiles\Current, for example. Because HKEY_CURRENT_CONFIG is not a true root key, the method cannot be called from it.

If the Load() method is successful the hive will have been loaded and can be accessed by opening the key name specified as the first parameter to this method. The Load() method will return a TRUE if successful; otherwise, it returns a FALSE. Refer to Example 3.23 to see the Load() method in action.

Warning: Registry key names can consist of any character except the backslash (\) because the backslash is used to delimit keys in a Registry path. If you try to create a key containing a backslash in the Registry Editor, it will fail and give you an error message. The Win32::Registry extension, however, enables you to create key names with a backslash.

It is in your best interest to not create keys with backslashes because you may not be able to open the key to either modify or remove it.

Unloading a Hive

If you have a recent version of the Win32::Registry extension that has a method for unloading a hive, you can use the UnLoad() method:

$RegistryObject–>UnLoad( $KeyName );

The only parameter is the name of the key that you had previously loaded a hive with.

This function and method will return a TRUE if it successfully unloaded the hive; otherwise, a FALSE is returned.

When the hive is unloaded, it is saved so that any changes made to the hive (any keys have been renamed, deleted, or added, or any values or data changed) will be saved to the hive's file. Refer to Example 3.23 to see how to unload a Registry hive.

You can only unload hives from one of the root keys. Specifying the handle of any key other than a root key (or in calling the method from any object other than a root object) will result in the function failing.

The UnLoad() method is not exported in all versions of the Win32::Registry extension. Hopefully, this will change in future versions of the module. In the meantime, you can unload any hive by using the RegUnLoadKey() function:

Win32::Registry::RegUnLoadKey( $Handle, $KeyName )

The first parameter ($Handle) is the handle to a root Registry key such as HKEY_LOCAL_MACHINE. The handle is a numeric value held in the Registry object's "handle" member. You can access this as $HKEY_LOCAL_MACHINE->{handle}, assuming that you are looking for the local machine's root handle.

The second parameter ($KeyName) is the name of the key which you had previously loaded a hive with.

Example 3.23 Loading and unloading Registry hives

01. use Win32::Registry;
02. $HiveFile = "c:\\temp\\joel.key";
03. $KeyName = "Joels Hive";
04. # Load the hive into this registry.
05. $HKEY_LOCAL_MACHINE–>Load( $KeyName, $HiveFile ) || _    die "Can not load the hive";
06. if( $HKEY_LOCAL_MACHINE–>Open( "Software", $Key ) )
07. {
08. ...process key...
09. $Key–>Close();
10. }
11. # Remove the hive from this registry (saving the contents).
12. $HKEY_LOCAL_MACHINE–>UnLoad( $KeyName );

Restoring Registry Keys

Two functions in the Win32::Registry extension are wonderfully valuable, but they are just not exposed by the extension's module. Because of this, you must access them as functions. Just like the other such functions, the extension may change in future releases to provide methods to access these functions.

The first of these functions, RegRestoreKey(), restores a key with another key that was saved to a file (refer to the "Saving a Hive" section to learn how to save a key to a file). This function takes the Registry key values and subkeys from a given file and overwrites any currently existing keys. This is great if you want to make backups of important Registry keys and later restore them. The syntax for the RegRestoreKey() function is as follows:

Win32::Registry::RegRestoreKey( $Handle, $File[, $Flag ] );

The first parameter ($Handle) is a handle to an opened key. You can use any key that has been opened (by using either the Open() or Create() methods). Access the handle by referring to the "handle" member of the key's object as in:

$Key–>{handle}

The second parameter ($File) is the name of a file that has had a previously saved key.

The third parameter ($Flag) is optional and specifies the volatility flag. This flag can either be 0 or REG_WHOLE_HIVE_VOLATILE. If REG_WHOLE_HIVE_VOLATILE is specified, the hive will be active until the next time the machine is rebooted. After reboot, the key will resort back to its original state. This flag can be specified only on keys that exist under HKEY_USERS or HKEY_LOCAL_MACHINE roots. The default for this parameter is 0.

If successful, the key will be replaced by the contents of the hive file and the function will return TRUE. FALSE is returned on failure.

If a key is restored, all its original values and subkeys are lost and replaced with the contents of the specified file.

The other function that restores a key but also provides a backup for the previous key is the RegReplaceKey() function:

Win32::Registry::RegReplaceKey( $Handle, $KeyName, $File, $BackupFile )

The first parameter ($Handle) is a handle to an opened key. You can use any key that has been opened (by using either the Open() or Create() methods). Access the handle by referring to the "handle" member of the key's object as in:

$Key–>{handle}

The second parameter ($KeyName) is the name of the key to be replaced.

The third parameter ($File) is the name of a file that has had a previously saved key. The contents of this file will replace the specified key.

The fourth parameter ($BackupFile) is the name of a file that will be created. A backup of the key will be stored into this file before it is replaced.

If the RegReplaceKey() function is successful, it will return TRUE, a backup file will have been created containing the original key (its values and subkeys), and the key will have been replaced by the contents of the file specified in the third parameter. If the RegReplaceKey() function fails, it will return FALSE.

Closing an Open Registry Key

After you have finished with a key that you have opened, you need to close it using the Close() method:

$RegistryObject–>Close();

If successful, Close() returns a TRUE and the Registry object no longer exists. Any further method calls using this object will fail. If the method fails, it returns a FALSE value.

Managing the Event Log

The Windows NT operating system contains a mechanism to track events and messages generated by the operating system, applications, services, and drivers. This is similar to the UNIX SYSLOG daemon. The mechanism is known as the Event log and it can be a powerful tool (if not the only tool) to capture important messages that can predict network failures, non-working devices, and other such information.

Win32 Perl can access the database of messages that comprises the Event log by using the Win32::EventLog extension.

Opening the Event Log

Before performing any actions on the Event log, you need to open the Event log. The function used to open the log will result in a new Win32::EventLog object that will be used to perform all other methods.

To open the Event log, you can use the Open() method:

Win32::EventLog::Open( $Object, $Source [, $Machine ] );

The first parameter ($Object) is a scalar variable that will be set to the newly created Event log object. This object will be used to call other methods.

The second parameter ($Source) is the source name. This is a string that will be used to identify the origin of any events that may be stored in the log. This is usually some string that is meaningful to whichever user will be looking at the logs. By default, there are three sources on a Win32 machine: Application, Security, and System. A script could, however, create its very own source such as "Perl Database Manager" or "Joel's funky event source". This could be an empty string.

The third parameter ($Machine) is optional and represents a computer whose Event log to which this object will be connected. Any methods that the created Win32::EventLog object performs will be done on this machine's Event log. The default for this is the local machine. This is just the computer name (not the proper name with prepended backslashes).

If successful, a TRUE is returned and a new Win32::EventLog object is created representing the Event log on the specified machine. If this fails, Open()returns a FALSE value.

In newer builds of the Win32::EventLog extension, another function exists for creating the object: Perl new command:

$Object = new Win32::EventLog( $Source[, $Machine ] );

All the parameters are the same as the Open() function except that the $Object scalar is moved out of the parameter list.

Counting Events

Because the Event log is a database of messages, you can query the database to determine what the latest message number is by using the GetNumber() method:

$EventlogObject–>GetNumber( $Number );

The only parameter is a scalar variable that will be set to the number of records (or events) in the Event log database.

If the GetNumber() method is successful, it returns TRUE and the scalar variable passed into the method is set to the number of records in the database. The method returns FALSE if it fails.

Similar to the GetNumber() method is the GetOldest() method, which will return the record number of the oldest event in the database:

$EventlogObject–>GetOldest( $Record );

The only parameter passed in is a scalar variable that will be set to the record number that is the oldest record in the Event log database.

If this is successful it returns TRUE and sets the scalar variable to the record number of the oldest record in the database. Failure is indicated by a FALSE return value.

Reading Event Messages

Reading messages from the Event log is fairly straightforward. You just use the Read() method:

$EventlogObject–>Read( $Flags, $Record, $Event );

The first parameter ($Flags) refers to the flags that indicate how the read process is handled. This can be a combination of values from Table 3.13 logically OR'ed together.

The second parameter ($Record) is the record number from the database that you want to read. This number is ignored unless the EVENTLOG_SEEK_READ flag is specified in the first parameter.

The third parameter ($Event) is a reference to a hash. This should be in the form of a scalar ($Event) as opposed to a hash (\%Event) reference because some older versions of this extension did not correctly work with hash references.

If the method is successful the hash is filled with the structure defined in Table 3.11 and a TRUE value is returned. Otherwise FALSE is returned. Example 3.24 shows how the Read() method is used.

Example 3.24 Printing all system errors over the past week

01. use Win32::EventLog;
02. $SecPerWeek = 7 * 24 * 60 * 60;
03. $Now = time();
04. $WeekAgo = $Now - $SecPerWeek;
05. $Event = new Win32::EventLog( "System", "" ) || 
_    die "Unable to open event log.\n";
06. if( $Event–>GetNumber( $Num ) )
07. {
08. $Flag = EVENTLOG_BACKWARDS_READ | EVENTLOG_SEEK_READ;
09. do
10. {
11. if( $Event–>Read( $Flag, $Num, \%Hash ) )
12. {
13. if( $Hash{EventType} == EVENTLOG_ERROR_TYPE ) )
14. {
15. print "$Hash{Source} on $Hash{Computer} indicated an error".
"at". localtime( $Hash{TimeGenerated} ) . ".\n";
16. }
17. }else{
18. undef %Hash;
19. }
20. # This will cause the next reading of the registry to move to the
21. # next record automatically.
22. $Num = 0;
23. } while( $WeekAgo < $Hash{TimeGenerated} );
24. Win32::EventLog::CloseEventLog( $Event–>{handle} );
25. }

Table 3.11 The Event log record hash

Hash Key

Description

Source

The source of the event message. This is normally the name of the application or device that generates the event. This name is mapped to an application or DLL file that contains information on how to format the event message.

Computer

The name of the computer that generated the event.

Length

The number of bytes of data associated with the event. This data is found in the "Data" key.

Data

Binary data of any length that is associated with the message. The length of this data is found in the "Length" key. Usually, this is some sort of data that is reported when an application generates an error. For most practical purposes, this key is ignored.

Category

This represents a category of the event. This is usually a number that is defined by the source that generated the event. You would need to know what this number represents to the particular source for it to be of any use.

RecordNumber

The record number in the Event log database of the event.

TimeGenerated

The time that the event was generated. This number is represented as the number of seconds since January 1, 1970. You can use this number as if it were generated from Perl's time() function.

Timewritten

The time that the event was physically written to the database. This value is managed by the operating system and cannot be written by a Perl script. This number is represented as the number of seconds since January 1, 1970. You can use this number as if it were generated from Perl's time() function.

EventID

The ID number of the event. This number is specific to the source that generated the event. This ID number's meaning changes from source to source.

EventType

This represents the type of event. The different event types are listed in Table 3.12.

ClosingRecordNumber

This is reserved by the Win32 API and currently has no meaning. Ignore this key.

Strings

The different data strings used to format the event's message. These strings can be passed in as an anonymous array.

Table 3.12 Event log event types

Event Type

Description

EVENTLOG_ERROR_TYPE

The event was an error.

EVENTLOG_WARNING_TYPE

The event was a warning.

EVENTLOG_INFORMATION_TYPE

The event was used to inform that some event took place.

EVENTLOG_AUDIT_SUCCESS

The event was a successful audit. This occurs if an administrator has specified an object to be audited (such as a file, directory, or Registry key), and a user successfully accessed the object.

EVENTLOG_AUDIT_FAILURE

The event was a failed audit. This occurs if an administrator has specified an object to be audited (such as a file, directory, or Registry key), and a user unsuccessfully accessed the object.

Table 3.13 Event log reading flags

Flag

Description

EVENTLOG_FORWARDS_READ

The events are read from the database forward in chronological order.

EVENTLOG_BACKWARDS_READ

The events are read from the database backward in chronological order.

EVENTLOG_SEEK_READ

Reads the specified record number.

EVENTLOG_SEQUENTIAL_READ

Reads the next record in the database.

Writing to the Event Log

You can create your own entries into the Event log. This can be used when automated scripts run and errors occur. This is done with the Report() method:

$EventlogObject–>Report( \%Event );

The only parameter is a reference to an event hash, as described in Table 3.11.

If the method is successful, an entry is added to the Event log database consisting of the data contained in the hash whose reference was passed into the method. A TRUE value is also returned. A FALSE value is returned if the method fails.

Clearing the Event Log

The Event log database can become quite large. Because of this, you may want to clear the database using the Clear() method:

$EventlogObject–>Clear( $File );

The only parameter passed in is a file name.

Before the Event log is cleared, it will be backed up into the file specified. If this is successful, the Clear() method will return TRUE; otherwise it returns FALSE.

Note: When you clear the Event log, it is automatically closed. Because of this, the Event log object that called the Clear() method is no longer valid and should be discarded.

Backing Up the Event Log

Just as the Clear() method will create a backup log file of the Event log, so does the BackupEventLog() function. The only difference is that it does not empty the Event log. This function is so very useful that I do not understand why it was not exposed to the Event log object by means of a method. Instead you must access it directly by means of a function call:

Win32::EventLog::BackupEventLog( $Handle, $File );

The first parameter ($Handle) is a handle to an open Event log. You can specify the "handle" member of the Event log object for this parameter.

The second parameter ($File) is the name of the backup file that will be created.

If the BackupEventLog() function is successful, the Event log has been backed up to the file and the function returns TRUE. FALSE is returned to indicate a failure.

Closing the Event Log

For some very odd reason, the authors of the Win32::EventLog extension failed to expose the function that closes an opened database. It is good practice to always close any opened Event log files using the CloseEventLog() function:

Win32::EventLog::CloseEventLog( $Handle );

The only parameter accepted is a handle to an open Event log. You can specify the "handle" member of the Event log object for this parameter.

If the Event log is successfully closed, it returns TRUE; otherwise, it returns FALSE. Refer back to the code in Example 3.24 to see how the CloseEventLog() function is used.

Summary

Administrators using the Win32 extensions should consider themselves lucky to have such a war chest of valuable functions to assist them in their daily chores of running a network of users and machines.

The Win32 extensions provide a wide range of functionality to manage accounts. Not only user but also machine accounts can be managed. This is not limited to the general adding and removing of accounts, but also includes renaming, disabling, and changing passwords, as well as modifying the current state of an account.

As if account management weren't enough, the Win32 extensions also provide access to group management. Creating, modifying, and removing both local and global groups are supported. With these functions, users can be added to, enumerated, and removed from groups. Additionally, you can also check a user's membership in a group.

User accounts and groups are not the only things that need management. Administrators need to also manage machines. The Win32 extensions provide access into the Event logs to both query events as well as add them. Win32 Perl extensions also contain a set of functions that provides access into the Registry, enabling administrators to modify or restore components.

About The Author

Dave Roth is a prolific creator of Win32 Perl extensions, including Win32::ODBC and WIN32::AdminMisc. As a leader in the Perl community, Dave has been featuered at several conferences, including the O'Reilly Perl conference. He is the owner of Roth Consulting, which offers a variety of programming and network administration services, and a contributing writer for The Perl Journal.

Copyright © 1998 MacMillan Technical Publishing

We at Microsoft Corporation hope that the information in this work is valuable to you. Your use of the information contained in this work, however, is at your sole risk. All information in this work is provided "as -is", without any warranty, whether express or implied, of its accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the third-party products or information mentioned in the work are authored, recommended, supported or guaranteed by Microsoft Corporation. Microsoft Corporation shall not be liable for any damages you may sustain by using this information, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages. All prices for products mentioned in this document are subject to change without notice.

International rights = English only.

Copyright © 2000, Microsoft Corporation.

Link
Click to order


© 2009 Microsoft Corporation. All rights reserved. Terms of Use | Trademarks | Privacy Statement
Page view tracker