Large corporate environments often need to provide more than just standard commercial applications. They frequently develop and program their own special applications for terminal servers. How do these applications need to be programmed so that they are compatible with Terminal Services? To answer this question, let us first turn to the application runtime environment.
In some ways, terminal servers are similar to the older, centralized host or mainframe environments, in which users log on, start programs, read and write to joint files, print on joint printers, and access joint databases. Due to the operating system, however, all terminal server sessions in a host environment are completely separate, which also applies to the applications. This is one difference between terminal servers and host environments. Because terminal servers use central operating system services, the separation of sessions can never be as strict as on a host, with its distinctive options for session virtualization.
Another obvious difference is the graphical nature of applications on terminal servers compared to purely text-based applications on hosts. Applications with sophisticated graphics or animation significantly increase data traffic between server and client. In addition, advanced user-interaction functions, such as using a mouse, need to be supported.
Host applications were generally designed to run in multiuser environments. This was not the primary goal in designing many Windows-based applications, yet they often need to run on terminal servers. This section will describe all the parameters for designing your own or testing commercial applications for successful use on terminal servers. Both conventional 32-bit applications and the new .NET Windows Forms applications are addressed.
In principle, both types of applications can produce performance bottlenecks that affect all terminal server users. The bottlenecks are usually due to one of the following four reasons:
Processor capacity On a terminal server, all users can open their own desktops and start all the applications installed. If just one application fails to cooperate, it might overload the existing processor resources.
Main memory Each user has an independent session in which he or she can start any installed application. Each application needs its own space in main memory that the operating system provides as a shared resource.
Hard drive access Access by multiple terminal-server users to run application files, DLLs, and virtual memory significantly increases demand for access to the hard drive. Additionally, multiple-user administration on a physical computer requires the operating system to access the hard drive frequently. If user- specific data are saved to the general areas of the local hard drive, collisions can hardly be avoided.
Network Naturally, the network plays a vital role for the terminal server. Every interactive user accesses his or her session over the network. This is accomplished by the extremely thin RDP protocol and normally does not result in bottlenecks. In turn, the terminal server accesses directory services, file, print, database, e-mail, and other servers. If these accesses consume all the network bandwidth, the user’s connection is inevitably affected. One solution to this problem is to use several network adapters and a corresponding network segmentation.
These potential bottlenecks point to the design requirements for applications appropriate for terminal servers.
There are a number of general conditions required to make conventional 32-bit Windows-based applications suitable for terminal servers, including design specifications and the corresponding development guidelines. Only if all these preconditions are met will the application run properly on a terminal server or terminal server farm.
Here are all the design requirements of terminal server–compatible applications.
Ensure a consistent 32-bit architecture. The entire application logic is provided by 32-bit components; that is, both the executable program and DLLs or COM components must be created using 32-bit technology. The executable file must be in Portable Executable (PE) format. One component should be flagged shareable so that multiple users may work with the same instance of the loaded component.
Adhere to installation standards. Installation is performed using the default settings in the default directory for applications. It is important that you take the differences of the various language versions into account. The default directory for applications is %SystemDrive%\ProgramFiles. The %ProgramFiles% environment variable shows the correct installation directory for applications, even if it was modified. During installation, you need to make sure that all users—not only the installing administrator—can work with the application after you finish.
Avoid reboots. Installing an application should not require a reboot. This also simplifies automatic installation on terminal servers.
Follow the proper uninstallation procedure. An application including all its components, registry entries, and configuration files must be properly uninstalled.
Protect system files. An application should never try to replace system files (system drivers, fonts, and so on) during installation or operation. This could cause the operating system to become unstable and applications to behave unpredictably.
Protect shared components. Shared components, especially COM components and DLLS, cannot be replaced if they are protected by corresponding operating or file system mechanisms.
Personalized handling of user files. User data must be saved in an individual, configurable default path. The default path should be set in the %HomePath% environment variable. On standard systems, this variable usually points to the \Documents and Settings\<Username> directory, if no home directory was set up in the user profile.
Security configuration. An application needs to function properly even in a highly secure environment. The security measure used most often limits access to parts of the file system and the registry. You can also use Group Policies to protect the system.
Adhere to user interface standards. Default settings for size, colors, fonts, input devices, and output options of the user interface should be retained. The option to insert additional elements into the start menu and desktop can be disabled.
Use differentiated configuration settings. Generally valid and global configuration settings are saved at a central location in the registry or file system. User-specific data must be managed individually, for example through the user profile or the home directory.
Do not assume temporary files will persist. Files saved in the temporary directory during a user session might disappear by the next session. They could be deleted, or load sharing could start the next user session on a different terminal server in the farm.
Reduce graphics-intensive start screens. Product, company, or user information is often displayed when an application starts. This is not a problem in traditional Windows environments. On terminal servers, however, these start screens generate additional data streams that need to be transported to the client through the network. Therefore, start screens should be avoided as much as possible, or they should be configurable in a less resource-intensive version for terminal servers.
Reduce animation. Animation in an application consumes many processor resources. On terminal servers, the burden on the network from the constant transfer of animated objects is far from trivial. For this reason, animation should be executed on traditional Windows systems only. They should either be automatically disabled or kept to a minimum on terminal servers.
Use verified drivers.Some applications install and use their own drivers. All hardware drivers and drivers in kernel mode should be verified for Windows Server 2003 if they are to be used in environments with high stability requirements.
What specifically should developers do to create terminal server–compatible applications? Adapting the application logic and using certain API calls is helpful. The following list presents the key developer guidelines.
Check the version. If the application is to run on certain target platforms only, you need to determine the version, usually through invoking the API function named GetVersionEx.
Personalize application settings. Saves all user-specific settings in the \My Documents folder, in the user profile, or in files that were saved to the home directory of the interactive, logged-on user. The advantage of saving to \My Documents is that it can be redirected to any physical location and be identified by application through the SHGetFolderPath API function. The administrator must be able to modify all the associated settings through Group Policies.
Create individual temporary files. Standard API calls (for example, GetTempPath) are best for saving temporary data. Hard-coded paths almost always cause serious problems in multiple-user environments.
Avoid .ini files. No Windows operating system built on Windows NT technology allows users to read or write to the Win.ini, System.ini, Autoexec.bat, or Config.sys files. Generally, .ini files should never be used. If you do use application-specific .ini files, they should be accessed through the respective API calls (for example, GetPrivateProfileString).
The computer name or IP address does not represent a user. Some applications assume that the computer name or IP address can be used for unambiguous user differentiation. This assumption is valid in traditional Windows environments, but not on terminal servers. On terminal servers, the same computer name or IP address is valid for all logged-on users. Therefore, this information cannot be used for checking a user license or for identifying a user.
Do not assume desktop access. Some applications assume that they will run on the desktop, thus giving them access to certain components and resources, such as Windows Explorer or Microsoft Internet Explorer. However, if a terminal server administrator decides to let the application run without the complete desktop, the application must support it.
Avoid memory leaks. Applications that tie up memory resources without releasing them are known as memory leaks. They are hardly trivial even in traditional Windows environments and can easily become a major issue on terminal servers. If multiple users are working with an application that has a memory leak, the available memory rapidly decreases.
Determine system resources. The installation routines of many applications verify the existing resources and processes running on the target system. On a terminal server, however, the resources found are not exclusively for one user. This often compromises the validity of the system analysis on the terminal server. Therefore, your installation routine should always take into account that the target platform is a terminal server.
Support UNC conventions. The application must support long names and UNCs (universal naming conventions) for files and printers. Character strings need to be processed accordingly or default dialog boxes may be used for files and printers.
Exceptions when saving. If access is denied when saving user data, the application must provide alternative options.
Identify the components. All shared components must be identified, either in the installation log or the documentation.
Avoid direct access to graphics hardware. To accelerate graphics output, some developers use functions or methods that allow direct access to the graphics hardware. This approach is used to combine images into one image in the application window (overlay) or to achieve fast animation sequences (double buffering). Such techniques usually cause unwanted results and are disruptive on terminal servers. For this reason, graphics should always be created first in a copy of the video memory of the normal main memory and transferred to the video system only when complete.
Channel user input through foreground applications. User input should be managed through foreground applications only, not by background processes that applications use. Applications often “hang” on terminal servers while waiting for user input that was delegated to a background process.
So how do we verify if an application is compatible with terminal servers? The Windows Application Compatibility Toolkit is a big help. It contains several tools for testing conventional 32-bit applications with Windows Server 2003, Windows XP, and Windows 2000. This includes the application’s potential use on terminal servers.
The Microsoft Application Compatibility Analyzer supports the administrator in a tedious task that must precede a terminal server project: taking inventory of the applications used in an existing environment. The collection of data on existing applications can be configured using command-line parameters. There are several ways to collect data from remote computers on the network: starting a network release, using a logon script, or using predefined user actions. The actual data collection usually takes no longer than one to two minutes per computer.
The Microsoft Application Compatibility Analyzer consists of these two components:
The tool for collecting data (Collector.exe) runs on the client computer or on the terminal server.
The tool for analyzing the collected data (Analyzer.exe) transfers the individual results to a database and allows evaluation of the results through a graphical interface.
Application Verifier (AppVerifier) is another tool in the Windows Application Compatibility Toolkit. It supports developers in verifying application compatibility under Windows Server 2003, Windows XP, and Windows 2000. AppVerifier focuses on reviewing typical problems related to application quality, especially heap errors, security gaps, drivers, system files, and access to certain registry areas. AppVerifier monitors the selected applications when they communicate with the operating system.
AppVerifier marks a file so that it continues to be monitored even when AppVerifier itself is no longer running. To stop monitoring an application, you need to remove it from the list in AppVerifier.
Figure 5-8: Application Verifier examining Notepad.exe.
Much of what applies to 32-bit Windows-based applications is also true for .NET Windows Forms applications. However, the developer specifications and common language runtime make it much easier to adhere to the guidelines. Here are the reasons.
Consistent architecture The entire application logic is provided by .NET components in Microsoft Intermediate Language format. Thus, these components run on all 32-bit and 64-bit platforms on which the common language runtime is available.
Installation and uninstallation Framework applications are no longer registered on the system. Therefore, both installation and uninstallation are relatively uncomplicated. However, you need to make sure that only administrators have permission to perform these tasks. Use the policies for software restrictions for this purpose. (See Chapter 8.)
System files Framework applications do not depend on system files, so system files are not modified when you install a Framework application.
Components Framework applications can use only those components that are provided exclusively or within the strictly controlled Global Catalog Cache.
Personalized handling of user files In Framework applications, user data needs to be saved in a specific, configurable default path. The default path should be linked to the \Documents and Settings\<User Name> folder.
Differentiated configuration settings Generally valid and global configuration settings are saved at predefined locations in the file system (normally in XML format). User-specific information needs to be managed individually, for example through the user profile or home directory.
Security configuration The .NET Framework has very strong security mechanisms that also apply on terminal servers.
Use of graphical and multimedia elements It is recommended that graphical start screens, animation, or other resource-intensive multimedia tasks be avoided for Framework applications if their target platform is a terminal server.
When this book was written, no formal development standards existed of .NET Windows Forms applications for terminal servers. Procedures based on real-world experience will no doubt be established in the months and years ahead. If, however, you want to experiment today, use the .NET Framework SDK and other sample applications. (See the CD that comes with this book.)
A special application variant directly accesses a terminal server’s specific interfaces; this is the application programming interface, or API. This type of application is normally used for multiuser option administration on Windows Server 2003. It is also possible to use self-developed components to expand communication between the terminal server and RDP clients through virtual channels. The libraries and header files required to develop such applications or system components are known as Terminal Server SDK. The Terminal Server SDK is an integral part of the Platform SDK.
All terminal server–specific API functions are encapsulated in the Wtsapi32.dll file located in the Windows Server 2003 system directory.