This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.


December 1998

Microsoft Systems Journal Homepage

Run Your Applications on a Variety of Desktop Platforms with Terminal Server

Download TerminalServer.exe (9KB)

Frank Kim is a support engineer for Microsoft specializing in kernel/base technologies. He enjoys spending his free time playing the stock market. He can be reached at franki@microsoft.com.

Computer technology is always evolving, but sometimes it revolves. Multiuser technology appeared to have disappeared from our memories like the Volkswagen Bug, but in 1998, both reappeared. Microsoft® Windows NT® Server 4.0, Terminal Server Edition is one new multiuser technology.
      This article will give you an overview of Terminal Server from a developer's perspective. You'll see the environmental differences applications will encounter on Terminal Server and Windows NT Server. You'll learn how to make your application run well on this new product. To demonstrate some of the unique features of this new environment, I have written a sample application you can download from the link at the top of this article. All information on Terminal Server is based on the final release of the operating system.
      Terminal Server was developed in response to two needs: reducing TCO (Total Cost of Ownership) and the requirement to run 16-bit and 32-bit Windows® and MS-DOS®-based applications on a variety of different desktop hardware. Desktops supported include Windows for Workgroups, Windows 95, Windows 98, Windows NT, OEM Windows-based terminals, network PCs and non-Windows-based clients. The non-Windows-based clients (MS-DOS, Apple Macintosh, Unix, and network computer) will require the Citrix MetaFrame add-on (http://www.citrix.com).
      So how does this work? Terminal Server runs the entire application on the server side. Only the application's graphics, keyboard, and mouse input are transmitted to the server. All applications running off the server simplifies the administrative work in a large network. Instead of installing an application to every desktop computer, the administrator only has to install it on the server. And since a variety of clients are supported, it is now possible to run Win32® applications just about anywhere. The client software will allow you to run Microsoft Excel 97 on your old 386 Windows for Workgroups-based machine. Terminal Server provides you with a multitude of options for moving an organization toward a 32-bit Windows desktop environment.
      One architectural goal of Terminal Server was to minimize the impact to any existing Win32 applications. Although Windows NT has gone through some internal changes, it is still the same operating system to applications. Most Win32 applications will run on Terminal Server without any changes. But remember, the user running your application is sharing the server (CPU, disk space, memory, network access, and local devices) with other users. There are many optimizations that can be done on the development side to make your application run well.

Terminal Server Architecture
      Terminal Server has three components: the server itself, the user interface transfer protocol, and the client.
      The user interface transfer protocol allows the client to connect to the server. Terminal Server supports several user interface transfer protocols. The Citrix ICA protocol allows non-Windows-based clients to connect to Terminal Server. I'll focus on the Remote Desktop Protocol (RDP). This protocol is based on the T.120 protocol suite, a standard multichannel conferencing protocol. Some areas of RDP are already implemented in Microsoft NetMeeting.
      RDP has many great features, such as support for multiple channels. Currently, only one is used, but many more are available for future use such as audio streaming between the client and server. Another feature is that RDP can run on a variety of network protocols, although only TCP/IP is supported right now. RDP also supports encrypted sessions, which are necessary to protect the user's credentials. Later I will go into more detail about the different types of encryption levels.
      Another Terminal Server component is the client, which is responsible for presenting the familiar Windows desktop. The client is available as either a 16-bit Windows-based or Win32-based application. Windows-based Terminals are Windows CE-based and are similar to the X terminals that many of us are familiar with, except that, obviously, the Windows desktop hosts the user interface. Non-Windows-based clients are implemented using a variety of technologies. The client is referred to as a "thin" client because of its low memory requirements. For example, the Win32-based client is approximately 130KB in size, uses a 300KB working set, and 100KB for display data.
      The client handles both input and output. It renders all remote desktop output including graphics and text. The client receives input from the keyboard, mouse, and other local input devices. Since network bandwidth is limited, the client uses a variety of techniques to minimize the information transmitted. Caching is one way it does this. For example, all the icons presented on your desktop are bitmaps. The server must transmit the entire bitmap to the client. The client has a bitmap cache so that, instead of having to ask the server to resend the bitmap, it can refer to the bitmap cache. The cache uses a LRU (least recently used) algorithm to throw away stale data. The bitmap cache's default size is configured for 1.5MB, configurable for Win32 clients through the HKEY_CURRENT_USER\Software\ Microsoft\Terminal Server Client\Default registry key. The value is BitmapCacheSize and it is a DWORD type.
      Another optimization involves fonts. When a client first connects to the server, the client notifies the server of all the fonts it has installed. The server will only send the entire font to the client if that font is not available. If it is available, only string data is sent. This is another way to minimize the amount of data exchanged.
      Terminal Server is always keeping track of user interaction from the client. The client will refresh either at a slower or faster rate depending on the frequency of user interaction. Typically the desktop refresh rate is around 20 times per second. If the user walks away from the client to get some coffee, the server will notice that interaction has decreased and the refresh rate for the screen will decrease to approximately 10 times per second. Again, this is an attempt to reduce data transmitted over the network.
      A final example relates to the carets displayed by the client. Both console and Windows-based applications running on the client do not have blinking carets. Again, the client is trying to reduce the amount of graphical information transmitted over the network.

Supporting Multiple Interactive Users
      I want to review some Windows NT operating system basics before I discuss Terminal Server itself. Windows NT currently has much of the infrastructure to support multiple interactive users. This includes a home directory, user profiles, security, desktops, and windowstations. The big difference is that only one user can be interactively logged on to the machine. Yes, it is possible to have processes running as different users, like with services, but only one user is interactively logged on. Remember, the interactive user is the person who automatically has keyboard input and can visually see their applications on the screen. The interactive user logs on to Windows NT via the Ctrl+Alt+ Delete mechanism. This mechanism brings up the logon dialog box and ensures that the user is not logging on to a Trojan horse type of application. There is only one interactive user on Windows NT. Terminal Server changes this paradigm to allow multiple concurrent interactive users.
      The biggest changes to the Windows NT operating system involve the addition of other interactive users. A lot of information related to the interactive user was stored globally by the operating system. Two important components associated with the interactive user are the Windows Manager and GDI. The Windows Manager is responsible for input and managing the screen, while GDI is responsible for graphics. These two components were user-level processes on Windows NT 3.51, but were moved to the kernel on Windows NT 4.0 to improve GUI performance. Windows Manager and GDI now run directly in kernel mode instead of having to go through the client server runtime subsystem (CSRSS), also known as the Win32 subsystem. These two components now reside in Win32K.sys.
      Other global information stored by the system includes display and printer drivers. Global data makes sense if there's only one interactive user, which implies only one display associated with a machine, and all installed printers technically available to all users (ignoring Windows NT security). In addition, the namespace was global information shared by the entire system. When a process creates a named mutex, it is available to all processes on the system (again ignoring Windows NT security). This means that another object cannot be given the same name as an existing named object. You cannot have both a mutex and an event named foo.
      To deal with global information based on the one interactive user assumption, some changes were made to two important Windows NT executive components, the Virtual Memory Manager and the Object Manager. First, let's talk about the Virtual Memory Manager. Every process has a 4GB address space, 2GB of kernel and 2GB of user address space. (On Windows NT Server, Enterprise Edition, a process has 1GB of kernel and 3GB of user address space.) The next thing to remember is that this is a virtual address space. The Virtual Memory Manager does the magic of mapping virtual address space with actual physical memory. All processes share the same kernel space while having their own user address space. User-level threads can only access user address space while kernel mode threads can access both user and kernel memory, so your application cannot corrupt the address space of another process or the operating system's address space.
      Since kernel memory is shared between all processes, the key to solving the global information problem was to have kernel address space that had the same characteristics as user address space. Well, Terminal Server has introduced a new type of address space, session space. This is kernel address space that is private to a process group (see Figure 1). Session space allows every interactive user to have their own private data copy of Win32K.sys, printer drivers, and display drivers. This resolves the problems with global data and simplifies cleanup. Plus, keeping every interactive user's Win32K.sys isolated maintains the robustness of the operating system.
      Terminal Server also introduces process groups. This allows the operating system to group processes together through a common ID. The system uses process groups to associate processes with an interactive user. Processes belonging to the same process group will share the same session space.
      The Windows NT Object Manager underwent a major change, and it's now called the Multi-User Object Manager. Currently the namespace in Windows NT is global. Since one of the goals for Terminal Server was to minimize the impact for existing applications, Microsoft did not want to introduce a new set of Ex APIs to deal with the global namespace issue. This would have meant that any application with a hardcoded named object would have to be rebuilt, which was not a feasible solution. The problem was resolved by assigning every interactive user a unique namespace. An application could continue to create named objects that would be available to all processes associated with that interactive user. As long as the processes sharing the named object are associated with the same interactive user, the applications won't have any problems.
      Since every interactive user has their own namespace, two different interactive users can use the same name. This means if two interactive users have created a mutex named Fester, the object manager will consider them to be two different objects. Although interactive users have their own namespace, the global namespace still exists because some named objects, like named pipes and services, are required to be available for all interactive users. Applications relying on global objects do not need any modifications.
      What about global drive letters? In the current version of Windows NT 4.0, drive letters are shared between all users on the system. If I have a service running and it establishes a drive letter mapping, that drive letter is not available to the interactive user. In addition, the service could not unmap a drive letter established by the interactive user. The system associates drive letter mappings based on logon session ID. Only processes running from the same logon session can unmap a drive letter. So for the service to unmap the drive letter established by the interactive user, it would have to impersonate the security context of the interactive user.
      Let's look at an example. Since drive letter mappings are based on your logon session ID, you could have a service running as foo and also be interactively logged on as foo. Does this mean the service foo has access to the interactive user's foo drive letter mapping? Yes and no. If a mapping was established by interactive user foo and the service foo attempts to enumerate drive letter mappings, the service foo will not see the mapping. If for some reason the service foo knew the drive letter mapped by interactive user foo, it could directly access the share. Since drive letters are global, the only check done by the system is to determine whether the user has security access to the share. In this case, since foo has security access to the share, the service foo will be able to access it. As you can see, you can have two different logon sessions both logged on as the same user. This situation is very common.
      In Terminal Server, each interactive user receives a unique namespace, and thus a unique set of drive letters. But the global drive letter problem still exists and can now occur for every interactive user. If a particular interactive user launches processes running as other users, you will still have the same problems.
      So how does the system support multiple interactive users? Every interactive user on Terminal Server is associated with something called a session ID, also known as a session. Don't confuse this with logon sessions. Processes launched in a session are associated with an interactive user through process groups. This allows the system to easily track all processes associated with a particular interactive user. This also means the system associates every process with a session ID. On an ordinary Windows NT-based machine, all processes will always be associated with session ID 0; on Terminal Server this will depend on the interactive user who launched the process.
      The sessions are numbered by the system starting with zero. The interactive user physically logged on to the machine is always associated with session 0, also known as the console session (see Figure 2). All system processes such as SMSS.exe, RPCSS.exe, SERVICES.exe, and LSASS.exe are associated with session 0. In addition, all Windows NT services are associated with this same session.

Figure 2 Sessions, Processes, and Users
      Figure 2 Sessions, Processes, and Users


      A new system service called Terminal Server (termsrv.exe) was added to the operating system to manage all of the sessions as well as the creation and destruction of additional sessions.

Server-side and Client-side Initialization
      Now, let's go through the initialization of Terminal Server and see what happens when a client connects to the server. After the operating system gets loaded, the Terminal Server service is started. It listens and waits for an incoming client connection. Each client receives a unique session ID. As I mentioned earlier, all processes associated with the interactive user have the same session ID. The first session created by the system is session 0, or the console session. The console session is associated with the interactive user physically logged on to the machine. The Session Manager (SMSS.exe) creates two idle client sessions. The role of the Session Manager is to create and delete sessions but also deal with the different subsystems (Win32, POSIX, and OS/2). The Session Manager executes a copy of CSRSS and assigns a session ID for each of the two idle sessions. When your application calls a Win32 API, it is usually CSRSS that actually communicates with the kernel to execute the API call. After CSRSS is executed, it launches Winlogon (the system process that handles user logons), and then Win32K.sys is loaded. Next, Win32K.sys data is associated with each session. The Terminal Service now listens for a client to connect via TCP port number 3389.
      On the client side, the first thing the client does is initiate a connection to Terminal Server though port 3389. The server handles the new connection and continues to listen for additional connections. The first stop in the client/server connection is establishing an encryption level for the session. Terminal Server has three different levels. Low is client-to-server-encryption only. This, of course, would be used for the user to pass their credentials to the server. Medium is low plus encryption of the display packets. This means you couldn't sniff the display seen on the client. High encrypts everything in both directions. All three levels use RC4 with a 40-bit key. There will be a non-export version that substitutes the 40-bit key with a 128-bit key.
      Once an encryption level has been established, a font exchange occurs. The client tells the server which fonts are installed. As I mentioned earlier, if the client has the fonts, network bandwidth is reduced.
      Next, Terminal Server assigns the client one of the two existing idle sessions. After this is completed, the user is prompted with the familiar Windows NT Winlogon screen. If no idle sessions are available, the Terminal Service calls the Session Manager to create a new session for the client.
      Once the Winlogon screen appears, the interactive user is prompted to enter their account name and password. Once the user types their credentials, the packets are encrypted and sent to the server. The Winlogon associated with the interactive user's session authenticates the user. When an interactive user ends their session, they have two options: disconnect or logoff. A disconnect logs the user off from the session but the system keeps the session around. With a logoff, the user is logged off the system and their session is destroyed. Because of this, the Terminal Server service keeps a list of session IDs and user names. The Terminal Server service first checks to determine if the user has an existing session. If so, the user is associated with that session and the newly created session is destroyed. What if a user has several existing sessions associated with their name? The client will prompt the user to pick which session it would like to be associated with (see Figure 3).

Figure 3 Existing Session Connections
      Figure 3 Existing Session Connections


      After this has been completed, Winlogon launches the shell, typically Explorer.exe, specified for the user. This information is located in the registry under HKEY_LOCAL_ MACHINE\SOFTWARE\Microsoft\Windows NT\ CurrentVersion\Winlogon. The registry value is named Shell and it is a REG_SZ type.

Windowstations and Desktops
      The console session is associated with the actual interactive user logged on to the machine. The windowstations associated with the console are the same as the interactive user on an ordinary Windows NT machine. There is winsta0, which is, of course, the interactive or visible windowstation, and Service-0x0-3e7$, the nonvisible windowstation associated with noninteractive LocalSystem account-based services. Remember, there may be additional nonvisible windowstations associated with nonLocalSystem account-based processes.
      As for all the other sessions on Terminal Server, they will have by default only one windowstation, winsta0. As for desktops, only focus on the desktops associated with winsta0. For the console session, you still have the default and winlogon desktops. The default desktop is where the interactive user's applications run. The winlogon desktop is used to display the Ctrl+Alt+Delete dialog box. With the other sessions, you have the same two desktops already mentioned plus a new desktop, Disconnect. The system switches to this desktop when a user selects to disconnect from their session (versus ending it). The disconnect desktop serves to reduce the amount of data transferred over the network (see Figure 4).

Figure 4  Windowstations and Desktops
      Figure 4 Windowstations and Desktops


      Because every session has a winsta0 windowstation and a default desktop, no current applications should break. For example, the CreateProcess API call allows you to specify the windowstation and desktop to associate with the process. This is done through the lpDesktop member of the STARTUPINFO parameter. Many applications currently specify "winsta0\\default" in the lpDesktop member to associate the launched process with the interactive desktop. Terminal Server does not change this behavior.
      You're probably wondering which "winsta0\\default" will be associated with interactive services. As I mentioned earlier, they will be associated with the console session. This means if your interactive service attempts to display a MessageBox, it will only be seen by the interactive user on the machine itself. Other clients connected to Terminal Server will not see anything. The MB_DEFAULT_DESKTOP_ONLY and MB_SERVICES_NOTIFICATION flags will only display the MessageBox in the console session.
      Since I'm on the subject of interactivity, where does Terminal Server stand on the interactive user used by DCOM? Like interactive services, they are also only associated with the console session. This may not be the desired session by the DCOM server. To avoid this undesirable behavior, you should install DCOM servers on a non-Terminal-Server-based machine.

Terminal Server APIs
      There are no APIs being released for Terminal Server 4.0. I have been informed by the Terminal Server development team that there are plans for some new APIs for a future Terminal Server Service Pack, but you'll have to wait and see. This means that if you want to make a service interactive to a Terminal Server client, you'll have to separate the GUI components from the service and use a client-server architecture. You'll need to use some form of IPC to communicate display information to a client that would display the information instead of having the service directly display the information.
      I discovered that they have introduced two new environment variables named WINSTATION and CLIENTNAME. This WINSTATION variable contains the session name of the process and the CLIENTNAME contains the name of the client machine. Earlier I mentioned that every interactive user has a session ID. The session name consists of the session ID, user interface transfer protocol, and the network protocol. This is most likely going to be RDP for the user interface transfer protocol and TCP/IP for the network protocol. A sample session name would be RDP-tcp#29. Other than the variable, the only way to get this kind of information is either through the Task Manager or the Terminal Server Administrator application.
      Terminal Server has introduced a new performance object, Session, which contains a variety of counters. Two useful counters are the output bytes and input bytes counters. The output bytes counter indicates the number of bytes being transferred from the server to the client and as you'd expect, the input bytes counter shows the number of bytes moving from the client to the server. As shown in Figure 5, the blue line spiked up and stayed up for a while before it came down. I was violently moving the mouse cursor then. Next, the red line spiked up when I launched Network Neighborhood.

Figure 5 Performance Counters
      Figure 5 Performance Counters


       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 information—background bitmap, fonts, and screen colors—is 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 session—the 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).

Figure 6 TermSrvInfo
      Figure 6 TermSrvInfo


      The application creates two named objects, a local and global named mutex. TermSrvInfo allows you to specify how the security should be created for the two named objects. By default, the application will pass a NULL to the SECURITY_ATTRIBUTES parameter for CreateMutex. The other three options are: a NULL DACL, creating a new DACL with only one ACE granting the Everyone group full access to the mutex, and modifying the default DACL in the sample application. This last option allows you to continue to pass a NULL SECURITY_ATTRIBUTES parameter, but the initial security created for the mutex will include an ACE granting the Everyone group full access to the two named objects.
      When the mutexes are created, the application will display whether it is a new mutex, if it's opening a handle to an existing mutex, or whether you don't have security access to it. Try running the application as different users and configuring the security using the four methods. Named objects created in this session are always global; the sample application will always return an existing mutex when creating the global since the local mutex was created first.
      I hope I have given you an informative introduction to Terminal Server. You should have a good understanding of the architecture behind it, the differences between Terminal Server and Windows NT from an application's perspective, and some things you should consider in creating a well-behaved Terminal Server application.

From the December 1998 issue of Microsoft Systems Journal..