USRLOGON.CMD Processing

Posted: December 1, 2013 in Citrix
Tags: , , , , , , ,

The usrlogon.cmd process is a hold-over from the Windows NT 4.0 TSE days, but it is still a very useful mechanism for running user level scripts – it is especially useful when a GPO is not available.  I do not know if it is available in Win2k12, but it definitely is in Win2k8R2 and all previous versions back to NT 4.0 TSE.

What is it?:
This is a script the is launched as part of the user logon process. It is standard in every Terminal Services/Remote Desktop Services installation. The script has several parts that serve different purposes, from home directory mappings to external scripts provided by the administrator.

The script exists in %systemroot%\system32, and as denoted by the extension, it is a plain text batch file. The script is launched from the registry. The launch point is HKLM\Software\Microsoft\Windows NT\CurrentVersion\WinLogon. Depending on the TS/RDS version, usrlogon.cmd will be part of the AppSetup value or the UserInit value.

Script breakdown:
The first step of the script is

Call "%SystemRoot%\Application Compatibility Scripts\SetPaths.Cmd"

This step launches the SetPaths.cmd batch file and waits for it to return.
Setpaths.cmd is a script that uses the acregl.exe utility to extract common program paths and set that information in Environment variables. (It is a several step process in and of itself, but it is effectively outside of the scope of this article). The key fact is that this step will set an environment variable called _SetPaths as either SUCCEED or FAIL.

The second step of this script is to check the status of the first step. If it fails, it will exit, if it succeeds, it will continue. The biggest issue is that the SetPaths step will very frequently fail and over years of experience has been proven highly *unreliable*. If you do not write your application compatibility scripts using those variables from SetPaths.cmd, then it is perfectly safe to simply comment out the 2nd step. I do this by adding 2 : marks at the beginning of the line.

::If "%_SETPATHS%" == "FAIL" Goto Done

The third step looks to see if is a file called %systemroot%\system32\usrlogn1.cmd (More on this file later): If it is there, go to %systemroot%\application compabitility scripts\logon and executes it. Once the usrlogn1.cmd file and it’s child process are complete, the script continues.

If the usrlogn1.cmd file doesn’t exist, then the script changes into the %systemroot%\application compatibility scripts” directory and then executes the rootdrv.cmd file. (This is/was an important step).

This harkens back to those NT 4 TSE days.
In those days, when user’s home directory was specified in the directory, it would connect the user to the directory as the specified home directory. The issue was that in *most* environments, a top level directory would be shared out that contained the user directories.

So, if drive U: was mapped to the home directory, it would connect as u:\username because the actual directory was \\server\share\username. In those days, there was frequently a need to configure applications to point to a unique directory for a user for long term storage, configuration information, etc. Since Windows was generally a single interactive user operating system, the applications generally never expected to have multiple interactive users connected at the same time and didn’t take their application configurations into consideration.

Frequently, the applications would simply store their user configuration information in the HKEY_LOCAL_MACHINE registry hive because it was a “convenient” way to make it accessible to all the users of a machine. Combining all of these elements was a challenge in the NT4 TSE environment.

The reason this is important is to establish a drive letter that is the same for all the users, but points to a unique path. This is called the Root Drive. This next step changes from the %systemroot%\application compatibility scripts\logon directory to the %systemroot%\application compatibility scripts directory and tries to execute the RootDrv.cmd command. RootDrv.cmd gets executed – it checks for the existence of the RootDrv2.cmd and executes if it exists. By default, RootDrv2.cmd does not exist.

RootDrv2.cmd sets an environment variable RootDrive equal to the intended drive letter. The subsequent step checks for an environment variable called RootDrive that gets created by RootDrv.cmd. If the environment variable does not exist, then the script exits and runs the end.cmd file which simply echoes a blank line. If the administrator desires, special cleanup routines can be run from end.cmd, although I have never seen it used.

RootDrv2.cmd is created by the administrator executing the script %systemroot%\application compatibility files\chkroot.cmd. ChkRoot.cmd tries to execute rootdrv.cmd, and if the ROOTDRIVE does not exist, then chkroot.cmd echos the script lines into RootDrv2.cmd and opens the file in notepad and waits for the administrator to enter in the appropriate variable. The script lines are all comments except for one:
set rootdrive=

The comments explain what to put there. For example:

set rootdrive=h:

Chkroot.cmd then tries to execute rootdrv2.cmd again, and if it still fails, it will set an environment variable _CHKROOT to FAIL and changes into the %systemroot%\application compatibility scripts\install directory. If it does *not* fail, then it executes the usrlogon.cmd file and when usrlogon.cmd completes, it will write the RootDrive value to the registry under HKLM\Software\Microsoft\Windows NT\Terminal Server key.

Now that the RootDrive variable is set, usrlogon.cmd uses the value of ROOTDRIVE to create a drive letter that is the same for all users, but points to a unique location. The “magic” of this is the old SUBST command. This creates a drive letter that points to a specific path. The command is

subst %rootdrive% %homedrive%%homepath%

For example: If the user’s home drive is u:\ and the home path is username, and the ROOTDRIVE is set to H:, then the command is subst H: U:\username. So, effectively, all of the users on the system will have an H: but there home directories are unique. 

The exact steps are – the script attempts to delete the ROOTDRIVE letter as a mapped drive and checks to see if it still exists. If it does, then it deletes it as a substitute (SUBST) drive.

Now that a unique drive letter is established, the script goes on to execute usrlogn2.cmd and when completed it exits.

Remember the usrlogn1.cmd I mentioned above, and now the usrlogn2.cmd scripts? The intent of usrlogn1.cmd is to execute scripts in %systemroot%\application compatibility scripts\logon. The intent of usrlogn2.cmd is to execute scripts in %systemroot%\application compatibility scripts\logon that require a unique drive letter. Neither of these scripts exist by default — they must be created by an administrator. These scripts should be create in %systemroot%\system32. The usrlogon.cmd script expects them to be there, and they will not be used if located somewhere else.

Now that the basic mechanics are explained, we can go over how this can be used, why it can be a good idea to use it, and interesting tidbits about it.

How it can be used:
As the directory name shows, it is for Application Compatibility Scripts (although you can do anything with it). The idea is that these scripts will run and configure whatever needs to be done for an application. Some examples are settings specific environment variables, copying files, adding registry entries, or just about any activity that a user could do that can be scripted.

To this end, Microsoft provides some utilities in %systemroot%\application compatibility scripts that are not well documented, but they are useful. If you search for them on TechNet you can find the documentation for them. http://support.microsoft.com/kb/187627/r

acregl.exe – this is used by the chkroot script, but this utility looks up a registry value and creates a batch file to create an environment variable that contains the registry value data.

acsr.exe – this is *very* fast text search and replace. Typically, this would be used to change a value in a file as the user logs in – for example, taking a baseline configuration file and creating a copy in the user’s home directory containing unique values for the user.

aciniupd.exe – this utility can update INI files. This was especially useful for dealing with 16bit Windows applications that were heavily INI driven.

Why it can be used:
In this day and age, these mechanics are infrequently used. The prevailing standard operating method is to use Group Policy and other methods of deploying scripts. However, sometimes these mechanics will break down, or may simply not be available. Or you may want these scripts to affect your administrators, and they are not affected by the GPO’s.. there are other scenarios, these are just the more common ones.

Interesting tidbits:
These are cmd files and by default, these windows are visible to the logging on user and can be exited by them. The Microsoft way to deal with this is to use a GPO to configure legacy logon scripts to run silently. This works exactly as needed.

The Citrix way is to use a utility called ctxhide.exe that was created several years ago to launch the cmd file (usrlogon.cmd) silently. In fact the Citrix installations will automatically detect the usrlogon.cmd launch point in the registry and modify them to include ctxhide.exe. (So, usrlogon.cmd becomes ctxhide.exe usrlogon.cmd). The problem is that ctxhide.exe has been riddled with problems and frequently the command to be launched (in our case, usrlogon.cmd) simply never gets launched. To avoid this, I simply remove the ctxhide.exe reference from the registry.

One thing I have seen on occasion is people modifying the usrlogon.cmd to execute code, or putting scripting code directly into the usrlogn1.cmd or usrlogn2.cmd. While this does technically work, the intention is for these scripts to call *other* scripts. In fact, the original %systemroot%\application compatibility scripts\install directory used to contain scripts to work around some known issues with existing Microsoft applications.

You would run these install scripts, and they would create scripts in the %systemroot%\application compatibility scripts\logon directory and automatically place a statement to run them in the usrlogn1.cmd or usrlogn2.cmd file. Because of this, it was very important to make sure your modifications to these files ended with a blank line. Otherwise, an automated script might just tack itself on to the very end of the script line and create an unworkable command.

When creating scripts to use for usrlogn1.cmd or usrlogn2.cmd be aware of some key facts.

1. Your scripts can change directories, but they should return to the initial directory (%systemroot%\application compatibility scripts\logon). (They can do this with the pushd and popd commands. Pushd will change into the specified directory and remember where is started from. Popd will return to the directory stored by pushd. Pushd can accept a UNC path – it will simply map the first available drive working backwards from z: and then change the directory).

2. You will typically want to ‘call’ a sub-script. Using CALL instructs the batch file to launch the process and wait for it to return.

3. As an alternate, if you don’t need to wait for the sub-script to return, you can use START. START will launch the process and continue on immediately.

4. Don’t use EXIT in your sub-scripts. This will exit the cmd.exe process that launched with usrlogon.cmd and subsequently stop the processing of any other sub-scripts.

5. You can launch powershell scripts with these mechanics. Just use powershell.exe -file <filepath> -nologo -noninteractive -noprofile

6. This entire process runs in the user’s security context! Don’t put things in there that the user could not do.

This PDF is a diagram of the process: UsrlogonProcessing

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s