The new Session performance object can be helpful in understanding Terminal Server. You can get access to the performance data programmatically. For more information on doing this, check the Win32 Platform SDK Documentation.
Optimizing Apps for Terminal Server
Let's go over what you need to do to make your apps Terminal Server friendly. First, I'll look at optimizations you can implement during new application development, then I'll go over what you can do for your current apps.
Since all interactive users share resources, a poorly written application that consumes lots of CPU or memory will affect all users. If you're counting on the hardware needs to save your application, consider that all other running applications are assuming the same thing. It is always good to keep your application users happy with an efficient and responsive application.
To determine whether your application is running in the Terminal Server Environment, you can query the HKEY_
LOCAL_MACHINE\System\CurrentControlSet\Control\
Product Options registry key. The registry value is ProductSuite and is a REG_MULTI_SZ type. If the string is equal to "Terminal Server," you know your application is running in a Terminal Server environment. I recommend you use the Product suite API VerifyVersionInfo to determine the machine type. This API is planned to be available with Windows NT 5.0.
Your applications will not work correctly if they assume there's one interactive user on one computer. Applications that assume an IP address, computer name, or Network Interface Card (NIC) address corresponding to only one interactive user will run into problems. If you are doing this now, you should use another technique.
Also keep in mind that only information related to input and output is transmitted over the network between the client and the server. The Terminal Server Client does a variety of things to reduce network traffic and it would be a shame to negate all the hard work done by the client. From an administrator's point of view, a good example would be to configure all clients to use the blank screen saver. The default screen saver is the blank one.
If your application has a cool splash screen at startup, disable it when running in the Terminal Server environment. There is no need to tax the network for the sake of your cool splash screen. It also annoys users when it takes forever for an application to start up. Your application should start in the same amount of time no matter what environment it's running in.
If your application uses a lot of animation, give the user the option of turning this off. Not only does it slow down the CPU, it also taxes the network. Again, there is no need to cause hostility between users running in a Terminal Server environment and your application.
Finally, your application should prevent multiple instances of itself from running in the same interactive user's session. There is already a good possibility that many instances of your application may be running. If there is no need for your application to be launched multiple times by the same interactive user, prevent this.
Threads
The most important information to remember for thread scheduling is that there may be multiple instances of your application running on the same machine. This means any thread priority changes or creations made by your application have a multiplying effect. You should refrain from creating too many threads in your application. If your application does a lot of background processing, give
users the option of turning these off in the Terminal
Server environment.
Use threads wisely in your application. In regard to thread priorities, your application may end up competing with itself. For example, an application will raise the priority of a thread in order to accomplish a task in a more timely manner. What if you have 10 users running your application? Remember, they will be running on the same machine. If they all raise the priority of a thread, you now have 10 threads competing with one another. Instead of improving response time for your application, the response time may decrease for you and for all other users running on the system. This definitely means you should avoid REALTIME_PRIORITY_CLASS and THREAD_PRIORITY_TIME_ CRITICAL. One other thing I wanted to mention is polling. Don't do it! WaitForSingleObject and WaitForMultiple Objects were provided to avoid this.
Consider that Terminal Server may have many applications running at the foreground priority. An application running in the foreground receives a priority boost from
the operating system. This enables an application to be more responsive to the interactive user. But with many applications running at a higher priority, your application may not be as responsive as on an ordinary Windows NT-based machine. Also, avoid manipulating processor affinities. Your attempt to improve response time for your application will probably decrease the response time for all users. Plus, if other instances of your application are running, you may have your application competing with itself for a specific processor. Leave the processor affinities to the operating system. You can rest assured that developers have worked hard to make Terminal Server scalable.
Files and Directories
One big area of conflict for a multiuser system is files and directories. Many applications utilize files to save information about the application itself or for the user. Be careful using common directories such as %SYSTEMROOT% and %SYSTEMROOT%\system32. %SYSTEMROOT% refers to the installation directory for the operating system. Again, if you store files in these common directories, you have a chance that multiple interactive users could overwrite changes made by another user.
To avoid this problem, every user has their own Windows directory located in their home directory. If an application calls GetWindowsDirectory from Terminal Server, it will return the user's Windows directory (c:\wtsrv\profiles\
franki\windows, for example). On a Windows NT 4.0 Server, it will always return %SYSTEMROOT%, no matter what user you're running as. Can you still use GetTempPath? Yes. The API will return a unique subdirectory under the temp directory.
If you still insist on using files located in global locations for your application, make sure to use file locks to protect the file when making any changes to it. This also applies
to databases.
Registry and INI Files
The other prominent area for storing application information is the registry. Currently, well-written applications only have to consider that many different users on the same machine may use the application. Since under Terminal Server many different users may use the application concurrently, applications should store all user-specific information under HKEY_CURRENT_USER instead of HKEY_
LOCAL_MACHINE. The purpose of HKEY_CURRENT_
USER is to store registry information specific to that user. For example, desktop configuration informationbackground bitmap, fonts, and screen colorsis stored under the user's hive. A user's hive is loaded under HKEY_USERS and the hive name is based on the user's Security Identifier (SID). HKEY_CURRENT_USER is just a mapping to HKEY_USERS\user's SID. The system determines the mapping based on the user's security context.
If an application stores information under HKEY_
LOCAL_MACHINE, it's now possible that one application running as one interactive user could change a registry
key that another interactive user running the same application depends on. HKEY_LOCAL_MACHINE should
be used to store information that applies to all users running the application. For example, suppose your application supports plug-in components. An application that would like all the components to be available for all users could store the component information under HKEY_
LOCAL_MACHINE.
I guess the big question is how should you handle HKEY_CURRENT_USER when installing your application, since most likely one specific user will initiate the setup process. Well, the registry keys should be dealt with when the application first starts up. The application should check to see if the necessary registry keys are available; if they are not, the application should create them. This ensures that the registry keys are created for the hive of the correct user. Why don't you want to do this during setup? Think about it. You could require every user who runs
your application to run the setup, but it breaks the rule
of reducing TCO. No administrator wants to waste their precious time doing this. The other option would be for the installing application to enumerate all the users on your system, determine the location of their hives, load each hive, and then create the registry keys for every user. This is a lot of work and it isn't worth the trouble.
Eschew INI files. They are available to all users. If you are still using INI, I think it
is time to move on and leave the good old Windows 3.1
days behind.
Now what should you do about existing applications? Terminal Server uses replication to resolve the multiuser issues. This means if an application refers to an INI file or registry key that doesn't exist, it will attempt to replicate either of them from a common system location. Unfortunately, this does not happen by magic. Terminal Server requires that you help it out at install time. A session can either be in install or execute mode. Execute mode is the normal mode. Install mode tells the operating system to monitor all registry keys and INI files that are created. Any registry keys created during this time are replicated under HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\ Windows NT\CurrentVersion\Terminal Server\Install. They will be created under this key, under either Software for any references to HKEY_CURRENT_USER or MACHINE for any references to HKEY_LOCAL_MACHINE.
Under install mode, any references to the Windows directory or any INI files will refer to %SYSTEMROOT% versus the user's Windows directory. The session mode can be changed from either the command line with "change user/install" or through the Add/Remove Control Panel applet. Select the "All users being with common application settings" button. When using the Add/Remove Control Panel applet, it will record the install time mode for the application. This is stored under HKEY_LOCAL_
MACHINE\SOFTWARE\Microsoft\Windows NT\
CurrentVersion\Terminal Server\Install\Change User Option. Under this key are values of type REG_DWORD based on the application's name. A zero indicates the application was installed in install mode and a one indicates it was installed in execute mode.
The system will also record a time stamp for modifications to the registry key or INI file during install mode. This is saved under HKEY_LOCAL_MACHINE\SOFTWARE\
Microsoft\Windows NT\CurrentVersion\Terminal Server\Install\IniFile Times. The registry key timestamp is stored under the LatestRegistryKey. The system will refer to the timestamp information when a user logs on. If the information has been updated, the registry key values or INI file will be updated for the user.
Once an application has been installed, the session mode should be changed back to execute mode. User mode is not persistent across logons, so the next user who logs on will be in execute mode. The only exception is to the runonce registry key. An application uses this key to continue the install. Any application specified to start via the run-
once key will be running in install mode.
In execution mode, if an application attempts to access a registry entry under HKEY_CURRENT_USER and it doesn't exist, Terminal Server will check to see if a copy of the key exists under the Install\Software section. If it exists, the operating system will copy the key (and subkeys) to the appropriate location under HKEY_
CURRENT_USER. If an application refers to an INI file that doesn't exist, the operating system will check the system directory and copy it to the user's Windows directory.
Terminal Server will check if any registry keys are deleted by the application in execution mode. Remember, Terminal Server is trained to replicate keys that do not exist any more for a user. In the case of deleting a registry key, this could lead to a loop with the key continuously being recreated. To prevent this, Terminal Server records these registry keys under the TermSrvCopyKeyOnce in the system copy of (Install\Software). This will tell Terminal Server to duplicate the key on only the first reference.
Sharing Named Objects
An application can share an object across sessions. This is not an issue as long as the applications sharing the named object are in the same session . One thing that might break an application is a service sharing a named object with an application launched by the interactive user. This scenario will work correctly as long as the actual interactive user launched the application. Remember, the service and the physical interactive user share the same sessionthe console session. If your application needs to access the named object from another interactive user, how can you implement a global named object like named pipes? Well, you can now append the word "global\\" in front of the named object to indicate to the operating system to make it available to all processes in any session.
One caveat is that in the console session any named object created is considered global. Basically, if you create a named object foo in the console session and then in a remote session create the named object global\\foo, it would be the same object. In addition, if you create foo
and global\foo in the console session, this will be the
same object.
Security
Now I need to mention a topic most developers dread: security. Sharing global named objects leads to this issue. With any named object, you have an opportunity to set up the initial security for the object, usually through the SECURITY_ATTRIBUTES parameter. For example, the first parameter in the CreateMutex API allows you to pass in this structure. The interesting member of the structure is lpSecurityDescriptor, which tells the system who has what access. Most developers are told to pass NULL to the SECURITY_ATTRIBUTES parameter and let the system handle the security aspects. This is great until you come across the dreaded error code 5, "Access denied."
What is the system doing to cause this error? In most scenarios, a named object is shared by applications running in the same security context. It makes a lot of sense for you to have access to an object that you created. The problem starts appearing when you attempt to share the named object between applications running as different users. A good example of this is a service and the interactive user.
Under Terminal Server, you can create a global named object available to all sessions. There is a good chance that the interactive users will be different, so they are running in different security contexts.
When you pass NULL in the SECURITY_ATTRIBUTES parameter for the creation of a named object, you are telling the system to create a default Discretionary Access Control List (DACL) for the object. What does this mean? DACLs contain a list telling the system who has what access to the object. The list is built of Access Control Entries (ACE). The system refers to the named object's DACL when an application attempts to obtain a handle to it. The system compares the security context of the user with the DACL. If the user doesn't have the requested access to the object, the system denies giving a handle to the object.
The default DACL for the named object is created based on the default DACL stored in your access token. An access token is associated with every process on the system. The token contains information on who you are, what groups you belong to, what privileges you have, and your default DACL. The system uses the default DACL in the token to create the DACL for the named object. The default DACL grants GENERIC_ALL access to the LocalSystem account and the user. If the user belongs to the local administrator group, the DACL grants access to the local administrator group instead of the user.
This means that if a user does not belong to the administrator group or is not the LocalSystem account, they will not have any access to a named object created by your application. The easiest solution for resolving this problem is applying a NULL DACL to the named object. A NULL DACL grants anybody full access to the object. If security is not a concern for your object, this is the way to go. If security is a concern, you have two options. You can apply a specific ACE to the named object, granting access to a specific user or group. You can also modify the default DACL in the token to allow the system to create a DACL based on your modifications. The default DACL allows applications to continue to pass in NULL for the SECURITY_ATTRIBUTES parameter.
Sample App
I have created a sample application, TermSrvInfo, that demonstrates some of the information discussed in this article. The application displays whether you are running in the Terminal Server environment; if you are, it displays the session name and the Windows and temporary directories for the interactive user (see Figure 6).
|