Anda di halaman 1dari 82

Scripting MetaFrame

By Dr. SDK
Copyright 2003

Citrix Systems, Inc.

Scripting MetaFrame
The information presented in this document is subject to change without notice.

THIS PUBLICATION IS PROVIDED .AS IS. WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. CITRIX SYSTEMS, INC. (CITRIX) SHALL NOT BE LIABLE FOR TECHNICAL OR EDITORIAL ERRORS OR OMISSIONS CONTAINED HEREIN, NOR FOR DIRECT, INCIDENTAL, CONSQUENTIAL OR ANY OTHER DAMAGES RESULTING FROM THE FURNISHING, PERFORMANCE, OR USE OF THIS PUBLICATION, EVEN IF CITRIX HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES IN ADVANCE.
Copyright and Trademark Notice Information in this document is subject to change without notice. Companies, names, and data used in examples herein are fictitious unless otherwise noted. No part of this document may be reproduced or transmitted in any form or by any means, electronic or mechanical, for any purpose, without the express written permission of Citrix Systems, Inc. Copyright 2003 Citrix Systems, Inc. All rights reserved. Citrix, ICA (Independent Computing Architecture), and WinFrame are registered trademarks, and MetaFrame, MetaFrame XP are trademarks of Citrix Systems, Inc. in the United States and other countries. Trademark Acknowledgements Microsoft, Windows, Windows 2000, Windows XP, and Win32 are either registered trademarks or trademarks of Microsoft Corp. in the United States and/or other countries.

Scripting MetaFrame

Table of Contents
1 2 3 4 INTRODUCTION .................................................................................................................5 METAFRAME MANAGEMENT TOOLS ........................................................................................6 WHY IS SCRIPTING METAFRAME POSSIBLE ................................................................................7 WHAT IS MFCOM .............................................................................................................8
4.1 4.2 4.3 4.4 PROGRAMMING INTERFACE FOR ADMINISTRATORS ............................................................................8 INTERFACE FOR SOFTWARE INTEGRATION .......................................................................................8 AN NT SERVICE ......................................................................................................................9 A COM INTERFACE FOR THE IMA ..............................................................................................11

5 6

SIMPLIFY YOUR DAILY TASKS ..............................................................................................13 MFCOM BASICS..............................................................................................................14


6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 6.10 DATA TYPES ........................................................................................................................14 INTERFACES .........................................................................................................................15 OBJECTS..............................................................................................................................14 COLLECTIONS .......................................................................................................................16 TIME OBJECT ........................................................................................................................18 ID OBJECT ...........................................................................................................................19

ARRAYS ..............................................................................................................................17 USER DEFINED COLLECTIONS ....................................................................................................18 CREDENTIAL OBJECT...............................................................................................................19

MULTI-STRING OBJECT ...........................................................................................................20

7 8

MANAGING FARM ............................................................................................................20 MANAGING SERVERS .........................................................................................................24


8.1 8.2 ENUMERATING SERVERS ...........................................................................................................24 LISTING HOTFIXES..................................................................................................................27 APPLICATION NAMES ..............................................................................................................30

MANAGING PUBLISHED APPLICATIONS ...................................................................................29


9.1 9.2 9.3 9.4 9.5 9.6 APPLICATION-SERVER BINDING .................................................................................................33 BATCH PUBLISHING OF APPLICATIONS .........................................................................................35 DISPLAYING APPLICATION PROPERTIES.........................................................................................42 MANAGING APPLICATION USERS ................................................................................................44 PUBLISHING APPLICATIONS USING APSDK ...................................................................................40

10 11 12 13

MANAGING SESSIONS AND USERS .....................................................................................46 MANAGING ADMINISTRATORS .........................................................................................46 MANAGING LICENSES ....................................................................................................48 DCOM CAVEATS .........................................................................................................50

Scripting MetaFrame
14 15 16 17 18 19 MFCOM SECURITY .......................................................................................................53 WEB INTERFACE ...........................................................................................................58 GATHERING AND REPORTING DATA ..................................................................................67 WORK AROUND BUGS ....................................................................................................71 SCRIPTING METAFRAME 1.8 ...........................................................................................72 FREQUENTLY ASKED QUESTIONS ......................................................................................76

Introduction

It took me a while to come up with this title. Originally I was thinking along the line of the other existing MetaFrame documents to name this document something like MFCOM Advanced Concept Guide. It would be good enough to the extent that it clearly states the purpose of the document. Then I had a second thought. Because this document is intended to be read by many people inside and outside of Citrix, I thought a sexier name should be given to it to just attract peoples attention. So that they will at least flip through a few pages just to find out what this document is really about upon hearing the title. If I were to write a store about a female scientist working on the hydrogen bomb1, a good title like The Bikini Woman would sound attractive enough. Although many readers would possibly be disappointed to find out that the subject never wore any bikini but rather were more in love with equations like E=MC2, it would at least get a lot more peoples attention. Who knows if some of the readers would really be people who just love pictures of woman in bikini but also serious scientists who really enjoy reading stories about how the hydrogen bombs were made. To our readers, Scripting MetaFrame sounds sexy enough, at least from my point of view. First of all, scripting is always something many Windows users find interesting and exciting. Scripting Unix would have been a poor title for any UNIX documents but scripting anything in Windows world would be something people look for. The simple truth is that scripting management and other tasks in MetaFrame environment has not been considered a valid idea. Citrix includes some command line tools for certain tasks. But comparing to the highly integrated and graphical user interface Citrix Management Console (CMC), those tools are too few and too preliminary. Their existence is probably only known to the few administrators who really needed to perform those tasks and might have searched all through the CMC menus and then ended up with those command line tools. There is no attempt to belittle CMC here. Its one of the few very important reasons people say that MetaFrame XP is better than MetaFrame 1.8. Instead of some individually developed GUI tools, CMC is an integrated all-in-one tool that allows an administrator to do everything from one single management console. Most administrators love this nicely designed and developed GUI interface. For hard core and seasoned MetaFrame administrators, they would also like the ability to perform routine tasks without having to click through many menus in the CMC. I would bet everyone would prefer a single command line to add a user to a published application over the CMC interface where you have to do quite a few mouse clicks. I think most people would agree that scripting and command line tools are useful to system administrators even for the ones who use Windows GUI tools all the time. This is a world in which efficiency and speed are often the utmost important things. So, if theres anything that helps to get certain tasks done faster, then eventually people will like it (or at least be forced to like it, if not voluntarily).

I dont know if I am making a valid assumption here. So far I dont know there were any women scientists who actually worked on the hydrogen bomb. But I guess its ok to make such an assumption. After all, Im just trying to make a point about why I chose the current title for this document.

Scripting MetaFrame For the most part the idea of scripting MetaFrame tasks is unheard of. We havent done a good job to link the enormous power of the MetaFrame SDKs and with the real world problems MetaFrame users and administrators face everyday. People are intimidated when they hear the word SDK because the D stands for Development. This may turn many people off simply by thinking about learning new interfaces and writing code. COM and VBScript have turned many users into developers. The short learning curve to write scripts and the tight integration of objects in VBScript have made it possible for people who dont have extensive training and experience in C++ to be able to successfully write small but efficient and useful tools to make their software management tasks far easier. We intend to tell people in this document that almost anything you can do through the CMC can be scripted. Although certain tasks may be more easily performed by using CMC, other tasks that should be scripted ought to be known by every MetaFrame administrator. We dont want to call this document Advanced Concept because the concepts presented in this documents are really not that advanced to require additional knowledge about programming. Any competent MetaFrame administrator should have no problem understanding the concepts described in the next few sections. Although knowing programming in Visual Basic definitely gives a big advantage to the administrator who wants to do scripting, not everything about Visual Basic is required for someone to write decent VB scripts. Actually most people will find that by modifying some existing VB scripts, they can build a custom tool very quickly. This document is not intended to advocate learning VB scripting nor using the Citrix SDKs. Our purpose it to let people realize the power of the SDKs so that they can take advantage of the power to improve the efficiency of administrative tasks. So, instead of presenting all the details the SDKs (particularly MFCOM) can do, we want to look at the things from a typical MetaFrame administrators point of view and offer solutions to the tasks that are faced by most of the administrators everyday.

MetaFrame Management Tools

The tools used by MetaFrame administrators include the Citrix Management Console (CMC) and some command line utilities. The CMC is an integrated graphical user interface. It is written in Java. The CMC can run on a MetaFrame server but also has the ability to run on a standalone system. When it is running on a non-MetaFrame server, it remotely connects to a MetaFrame server. In this section, we describe some of the properties of CMC from the perspective of how performing the same tasks may be improved when they are scripted. Detailed descriptions of the CMC and its use are found in the MetaFrame XP administrators guide. There are also some command line utilities for some special tasks that are not supported by the CMC. Since these tools are already scriptable, they are not described in this document. Other ways of administering MetaFrame farm and servers are also available. For example, the CWC provides a web interface. These tools are either built based on MFCOM or infrequently used, they are not described in this documented.

Scripting MetaFrame The CMC is designed to allow an administrator to perform almost all the common MetaFrame management tasks from one place. The CMC can be used to manage the following MetaFrame resources. Farm. Properties and configurations that affect the entire farm. Servers. Properties and configurations that are applicable to a single server. Applications. Published applications and contents. Parameters and configurations. Sessions. Static and dynamic information about all the sessions on each server. Administrators. Properties and privileges of all farm administrators. Policies. Properties and configurations. Since it is a graphical user interface, objects are located using the mouse to point and click on them. A tree structure is a natural choice in presenting the objects in a hierarchical way. For example, servers and applications are displayed in folders, similar to directories for files.

Why Is Scripting MetaFrame Possible

The short answer is that we have MFCOM, which provides an API interface to CMC. Ultimately the nature of the tasks determines if they are scriptable. A task can be scripted if it meets the following criteria: No excessive graphical operation. Both the input and output do not contain or contain very little graphical data. Graphical operations definitely cant be scripted easily. Visualization of these operations is a very important part of the task itself. We should not confuse this with the tasks that operate on graphical data files. Those tasks may be scriptable. Fortunately in MetaFrame XP we dont need to worry much about this type of tasks. No excessive amount of user input. This would simply translate into too many command line parameters for the scripts that process the tasks. No excessive user interaction. Scripts can be written to take user inputs during the execution of the task. But if there are too many user interactions, a graphical user interface is a better choice. Single purposed. A simple algorithm can then be developed based on the requirements. If the task is too complicated to be accomplished using a few single steps, it should be programmed using other high level programming languages such as C++. Yields well defined results. Output data will then be easy to present. At most the data may be dumped to a file but it should still be easy to organize. Small and repetitive. These tasks are ideal candidates for scripting. These tasks would otherwise take more time to perform using a GUI tool because the items in the user interface need to be identified first. The identification process takes time because the task is small, which is often located in pages after some mouse clicks. The most common tasks (big tasks) are often presented more visibly in most GUI tools for quick access. Operates on large amount of data in simple format. A typical script should solve these types of tasks by creating a loop that process each block of the data in the same format. These tasks are not suitable for GUI interfaces. Looping as a human action translates into boredom, particularly when the amount of data is large. 7

Scripting MetaFrame Most of the tasks performed through CMC meet some of the above criteria. There is very little graphical data presented in CMC, except some Resource Manager (RM) data. Since MFCOM doesnt have the equivalent calls for accessing RM tasks, they are not applicable in this discussion. Other CMC data are mostly text data. Many tasks are simple enough but may be repetitive, e.g., publishing and managing applications. Although we have tried to be more scientific in the above analysis, in reality, people often have an intuition and inclination to script certain tasks. The best person who decides what can be scripted and should be scripted is the person who actually performs the tasks. This is why MFCOM should be made accessible and usable by not only well versed C++ programmers but also system administrators who are used to sending simple commands to the systems. Scripting offers a bridge between extensive programming and simple command controls. In summary, MetaFrame administrative tasks are scriptable because of their own nature. MFCOM simply makes this type of scripting possible. People would have asked for such an interface if there was no MFCOM. On the other hand, if something should be scripted is not being scripted, the efficiency is lost.

What Is MFCOM

Its not hard to decipher the name MFCOM. As long as an interpretation puts MetaFrame and COM together, it suffices to be a short explanation of the word. So, MetaFrame COM, MetaFrame COM Object, and MetaFrame COM Interface are all valid long names of MFCOM.

4.1

Programming Interface for Administrators

MFCOM can be further explained from several different views. From the view of a MetaFrame developer, it is a programming interface for the administration of MetaFrame XP farms and servers. Since the CMC accomplishes basically the same purpose but is packaged as a graphical user interface tool, MFCOM can be described as a programming interface for the CMC. This is probably the most precise definition for MFCOM, although the role of MFCOM may be extended to tasks outside of the CMC. So far this definition is valid. To a MetaFrame XP administrator, MFCOM provides a scripting interface, which allows him to create tools for tasks that are frequently performed but would otherwise take more mouse clicks if the same tasks are carried out in CMC.

4.2

Interface for Software Integration

To a developer, MFCOM provides the calls for him to integrate MetaFrame XP management functions into another software platform. The richness of the calls available from MFCOM makes this integration task far easier since MFCOM offers a complete set of APIs for almost every manageable object in MetaFrame XP farm. Developers will not need to use other SDKs for the integration. 8

Scripting MetaFrame To an end MetaFrame user, MFCOMs effect is limited. This is largely due to the fact that MFCOM provides a management interface that is not accessible by an end user. Because this is an SDK, end users are not the targeted customers of MFCOM. But this situation may change as we are continuing to improve and enhance MFCOM. In future releases MFCOM may offer calls visible to end users.

4.3

An NT Service

Looking from inside, MFCOM runs on a MetaFrame XP server as an NT service. It can be controlled by an administrator using the Windows Control Panel or commands. The service is automatically started when the system boots. If the service has been stopped for any reason, the next connection request to MFCOM will cause the operating system to automatically start it. The services process is mfcom.exe, which can be found in the Windows Task Managers window. The executable file, mfcom.exe, is typically installed under the %SystemRoot%\System32 directory. A set of registry entries define MFCOM as an NT service to the operating system. The registry key HKLM\SYSTEM\CurrentControlSet\Services\MFCOM has sub-keys and values that are required to define MFCOM as an NT service as well as settings to control MFCOM. The following is a table of the keys and values, which are all under the previously mentioned key. Key or value
DebugOutput2 DependOnGroup DependOnService Description DisplayName ErrorControl FlushTraceFile ImagePath IMAWaitPause IMAWaitTimeout ObjectName Start TraceBufferSize TraceFile Type Enum Enum\0 Enum\Count Enum\NextInstance Security Security\Security TraceLevel5
2

Data type
DWORD MULTI_SZ MULTI_SZ SZ SZ DWORD DWORD EXPAND_SZ DWORD DWORD SZ DWORD DWORD SZ DWORD
Key3

Default setting
0 RPCSS WMI Provides access to MetaFrame farm MetaFrame COM server 0 0 C:\WINNT\System32\mfcom.exe 30000 600000 LocalSystem 2 0 16 Root\LEGACY_MFCOM\0000 1 1
Binary data4

SZ DWORD DWORD
Key Key

BINARY

These shaded entries (of this particular color) are only created by the debug version of MFCOM. Retail versions dont create and recognize these entries. 3 This indicates the entry is a key, therefore it doesnt have a data type. 4 This is not the actual data.

Scripting MetaFrame
TraceLevel\AllLevels TraceLevel\Automation TraceLevel\Collection TraceLevel\Enumeration TraceLevel\Initialization TraceLevel\Method TraceLevel\ObjectCount TraceLevel\Property TraceLevel\ReferenceCounting TraceObject TraceObject\AccountAuthority TraceObject\Application TraceObject\AppServerBinding TraceObject\Channel TraceObject\Event TraceObject\Farm TraceObject\Folder TraceObject\Group TraceObject\License TraceObject\Process TraceObject\Server TraceObject\Session TraceObject\User TraceObject\Zone DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD
Key

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD

The shaded entries are those created and used by MFCOM. Some people may argue that these entries should be placed under something like HKLM\Software\Citrix\MFCOM or HKLM\SYSTEM\CurrentControlSet\Control\Citrix\MFCOM. We have created these entries here because these are the only ones used by MFCOM as an NT service and a regular NT process. If we created more entries, we probably would have put them at more appropriate places. The other reason is that for most users, most of the entries are not created and not visible. Most of the MFCOM entries are only created by the debug build of MFCOM. In fact only two entries, IMAWaitPause and IMAWaitTimeout are created by the retail version of MFCOM. The debug entries are listed here as a reference for users who just happen to run the debug build. The debug registry entries will probably be phased out in the future since a new and better way of tracing all Citrix modules have been developed and the MFCOM code is gradually being migrated to using the new method. MFCOM depends on the IMA service, which is not listed in the list for the entry DependOnService. This is to resolve some issues that could result during the start and stop of both services. The dependency is implemented in another way in MFCOM. To make sure that IMA is running before it should access IMA, MFCOM uses polling to detect the availability of IMA. The value of IMAWaitPause tells MFCOM how often it should poll for the status of IMA. This is the amount of time MFCOM puts itself to sleep in between two polling calls. The time unit is in millisecond, so the default setting for this entry is 30 seconds. The value of IMAWaitTimeout specifies the total amount of time MFCOM should wait for IMA to come up. It is also in millisecond, so the default setting for this entry is 10 minutes. For most configurations, this timeout value should

These trace flags are not honored by every MFCOM call. In fact most of the calls dont generate tracing information.

10

Scripting MetaFrame be more than sufficient as experiments have shown that for typical configurations IMA service starts up in less than 3 minutes. If you receive error messages and event log messages about MFCOM cannot be started and everything else seems to be fine, try to increase the timeout value. If within that timeout value IMA should have been started and yet MFCOM still cannot be started after the timeout, something else may need to be checked to fix the problem. The un-shaded entries are required by the operating system. They tell the system where the image of the service is and how the service should be started. Additional information, such as the service description, is also saved here for the NT service control manager (SCM) to use. Most of these entries are created and used by the SCM.

4.4

A COM Interface for the IMA

Finally, MFCOM is a COM server, which exports COM objects supported by interfaces, through which the objects properties and methods can be accessed by a COM client. To help understanding this description, we can look at the physical impact to the system and the effect to the COM runtime by running MFCOM. Then we can see how the COM clients, COM runtime, and MFCOM work together. MFCOM as a COM object lets itself known to the system by registering itself to the system. The registration basically consists of two parts. One is creating the necessary registry entries so that persistent information about the MFCOMs objects and interfaces can be preserved. The other part is linking itself with the COM runtime. A list of registry entries created by MFCOM attached at the end of the document in appendix A. The list is long because all the objects and interfaces must be described and MFCOM has many objects and interfaces. The registry information is used by both the COM runtime and COM clients to find and connect to MFCOM. A couple of the registry entries are highlighted. They are HKLM\SOFTWARE\Classes\TypeLib\{ED62F4E0-63C2-11D494D8-00C04FB0F326} and HKLM\SOFTWARE\Classes\AppID\{ED62F4E0-63C2-11D494D8-00C04FB0F326}. The Typelib entry tells COM runtime the ID of the type library, which contains everything about the MFCOM objects and interfaces. Basically a type library is a binary form of the interface description (IDL) file. From the description stored in the type library, a COM client will be able to use the properties and methods with the correct data type information for the parameters. The AppID entry is used when MFCOM acts as a DCOM server. A DCOM client uses this key to find the remote server on which MFCOM is running. This key is created on both the MFCOM server and the clients machine. The values under this key are different on the server and the client. On the client machine, a remote server name is specified. This remote server name can also be specified using the DCOMCNFG utility, which will be described in more detail in a later section on DCOM. Creating the registry entries either directly or indirectly by calling some COM library functions is only part of the MFCOM registration process. The other part is to let itself known to the COM runtime. 11

Scripting MetaFrame We dont need to go into details in describing the process. Essentially COM runtime maintains a database of all the COM servers. We can imagine that there is a cross reference table stored in the COM runtime database. This table stores some pointers to the MFCOM functions for various tasks. Other things like security are also initialized during the MFCOM registration. Now we should be ready to walk through a typical scenario when a COM client makes a request to connect to a COM server and create an object. For simplicity, we exclude DCOM in the discussion. Extra steps involving DCOM are discussed in more detail in the section on DCOM. A COM client typically issues a call similar to the VBScript CreateObject method. An object identifier is given as the parameter to this method. This identifier looks something like MetaFrameCOM.MetaFrameFarm. The COM runtime knows the process, in this case mfcom.exe, to which it should connect to make a RPC (Remote Procedure Call) call. The process may look like the following: 1. COM goes to the registry entry HKLM\SOFTWARE\Classes\MetaFrameCOM.MetaFrameFarm to get the the GUID of this object, {ED62F4E2-63C2-11D494D8-00C04FB0F326}. 2. From the given GUID, COM uses the data in the registry entry HKLM\SOFTWARE\Classes\CLSID\{ED62F4E2-63C2-11D494D8-00C04FB0F326} to get the process in which the object should be created as well as the type library, from which everything about the object can be found. 3. COM then calls the class factory exported by MFCOM to create an object. The class factory has been registered during the MFCOM registration. Now COM is using it. The created object resides in the MFCOM process and its pointer is returned to COM, which marshals it, if necessary, and then return the marshaled pointer to the COM client. 4. Then some QueryInterface calls may be made by the COM runtime to the newly created object. A series of such calls may be made for an automation client like VBScript. In particular, the IDispatch interface is queried. 5. Unless the client specifically queries for a particular version of the interface supported by the object, the default interface (specified in the type library) is used by the COM runtime to resolve calls made to the object. 6. When the client uses the returned pointer to make a call to one of the methods, COM does the necessary marshalling of the parameters, finds the pointer to the function in MFCOM and makes the call. The marshalling information is available from the type library. 7. The result of the call is marshalled and returned to the COM client. The above process is highly optimized by the COM runtime. COM doesnt read the registry entries every time it creates an object. The QueryInterface calls are cached. And most importantly, behind the scenes object referencing counting is meticulously maintained by the COM library for the COM clients. For a C++ client, however, many things still need to be taken care of by the programmer. From the above process we can see the importance of the type library. A type library does not have to be stored in the MFCOM binary image. A type library can exist independently as a .tlb file. In fact, we supply such a file in the SDK as mfcom.tlb. For convenience, the MFCOM type library is included in mfcom.exe and mfreg.exe as a resource. 12

Scripting MetaFrame

Simplify Your Daily Tasks

The previous sections are preparatory for the contents from hereon. We have discussed the reasons for scripting and looked at MFCOM from different perspectives in detail. In this section we will discuss the types of administrative tasks that may be good candidates for scripting. Then in the following sections we examine some commonly scriptable tasks and present and analyze some working scripts. The most common tasks include monitoring and control user sessions. Sessions contain the most dynamic data in MetaFrame environment. Administrators gather session information to gain knowledge about the health of the farm and fix problems that may arise. Statistical information helps the system administrators to gauge the load of the farm with respect to certain applications and user count. Also, some firms may charge end users for resource usage. It is essential to be able to know everything about the sessions in the entire farm and on specific servers. Managing published applications is another task that requires frequent attention. Although publishing applications seems to be a one time event that requires concentrated effort at the beginning of the MetaFrame XP deployment, application parameters often change. Users may be added or removed from a published application. Servers that go offline may be removed from the applications server list. The farm and the servers themselves need constant attention. Some parameters may need adjustment from time to time. Printers and policies are two other things that may consume a large portion of an administrators time in certain environments. The Resource Management (RM) component of CMC offers reporting capabilities. But what if people need to gather reports in finer granularities or real-time data? All the above tasks are good candidates for MFCOM scripting. Few people probably would have thought that policy management may be scripted. Even fewer people would have thought that MFCOM offers reporting capabilities that compliment the RM data. But all of the above are possible, as we will demonstrate in the next few sections. Since in the examples we will use VB scripts extensively, its worth to offer a few general guidelines here regarding scripting. First of all, I dont know a good tool to assist writing VBScripts. So far all the scripts included in the document have been written using plain text editors. The good news about this fact is that the reader is not burdened to learn to use a new tool. The bad news is that many times the properties and methods of an object and interface are not easily remembered. For someone who is new to MFCOM and familiar with Visual Basic, it is recommended that the VB environment is used to first create the equivalent VB code. Then convert the VB code to VBScript. Both are almost identical with some subtle differences, which we dont need to discuss for now. The advantage of using the Visual Basic development environment is that the properties and methods of objects declared of certain type are automatically displayed whenever the properties and methods are used. 13

Scripting MetaFrame Another way is to pick some existing examples from the SDK and modify them. We have created many scripts using this method quite efficiently.

MFCOM Basics

Before we go deeper into specific topics, lets spend some time looking at some of the basic MFCOM objects, interfaces, and data types so that we can have a good understanding of the mostly used concepts.

6.1

Data Types

MFCOM defines many data types. Most of them are defined to provide user-friendly names for the constants used by the various properties and methods. Without these friendly names, the values returned by the properties would be hard to describe and to remember. The enum types also make it possible for certain compilers and programming tools to check the syntax for many calls so that runtime errors may be avoided. To use the data types in scripting languages, a reference to the COM library must be made so that the type information included in the type library can be made available to the script statements6. In every WSH script, the following line must be included in the script header.
<reference object="MetaFrameCOM.MetaFrameFarm"/>

Although the reference is about the farm object, any other MFCOM object may be referenced. An alternative is to specify the GUID of the MFCOM type library. But since this requires a GUID, which is not easily remembered as the object names, rarely this method is used. Below is an example of such a reference statement7.
<reference guid="ED62F4E0-63C2-11D4-94D8-00C04FB0F326"/>

With the reference for the type library included, the data types defined in MFCOM can be used directly in a WSH script. For example, the check the state of a session, the returned type by the SessionState property of the session object is MetaFrameSessionState. If we want to see if a session is connected, we can use a statement similar to the following:
If aSession.SessionState = MFSessionStateConnected Then End If

6.2

Objects

The basic programming units in MFCOM are objects. Objects are defined to closely resemble the real world entities, e.g., session, application, and server. Objects have properties and methods for the users
This was not possible until Feature Release 2. Prior to this release, the data types were not included in the type library. Programmers would have to use the numerical values in places where the enum type literals are required. 7 The correct term in HTML, which is used by Windows Scripting Host, is a reference element.
6

14

Scripting MetaFrame of the objects to access. Properties and methods are grouped in interfaces, which are attached to objects. Some objects can be created independently by a COM client. These objects all have the names starting with MetaFrame, for example, MetaFrameSession is the name of the session object. In VB scripts, many times we use the full name of such an object, MetaFrameCOM.MetaFrameSession. The string MetaFrameCOM defines a namespace. So when there is no ambiguity, the namespace string MetaFrameCOM is not needed. For example, the following VBScript statement creates a farm object.
Set theFarm = CreateObject("MetaFrameCOM.MetaFrameFarm")

The pointer to the object is stored in the variable theFarm, which can be used later to access the calls of the interfaces supported by the farm object. MFCOM also exposes objects that cannot be created using a COM library call such as CreateObject. These objects can only be created by some other MFCOM calls. An example of such an object is the MetaFrameTime object, which is returned by the properties that return a time. For example, if we have a session object pointed by a variable aSession, then the following statement gives us a time object.
aSession.ConnectTime(TRUE)

The Boolean is a required argument for the ConnectTime property. If it is TRUE, local time is returned, otherwise the same time value in GMT is returned. For this particular example, the above statement is incomplete since at least one of the properties of the time object must be specified. In other words, there is no default property for the time object. In fact, all COM objects dont support the default properties, which are disallowed by the latest releases of the Microsoft programming languages.

6.3

Interfaces

There are many ways to define the concept of interface. Using a laymans term, an interface is a bunch of calls put together. On the other hand, the calls constitute the properties of the objects that support the interfaces. Although COM defines some generic interfaces that every object must support, the MFCOM interfaces are rarely supported by more than one object. All MFCOM interface names start with a string IMetaFrame. An object is supported by usually more than one MFCOM interface, in addition to the standard COM interfaces such as IUnknown and IDispatch. In MFCOM, interfaces are versioned. The later version of an interface is named using the root name plus a version number with the exception that the first version of the interface doesnt end with the number 1. For example, the interface supported by the MetaFrameFarm object are defined as IMetaFrameFarm, IMetaFrameFarm2, and IMetaFrameFarm3, etc. The latest version of an interface always supports all the calls defined in the previous versions of the same interface. Each object has a default interface, which is usually defined as the latest version of the interfaces supported by the object. The calls defined in the default interface are usually displayed as the properties of the object if the properties and methods are browsed using an object browser, such as the ones available in the Visual Studio environment. 15

Scripting MetaFrame Although the interface names are usually associated with the object that supports the interfaces, some objects support interfaces named slightly differently since it may be a generic object for many different types of object. For example, a folder object is further classified as server, application, and account folder, therefore it may support interfaces like IMetaFrameFolder as well as IMetaFrameAppFolder, IMetaFrameSrvFolder and IMetaFrameAcctFolder. For most MFCOM users, although the concept of interface is important, rarely do they get in the way of coding explicitly. Most people can focus on the objects and the properties of the objects, which are exposed by the default interfaces of the objects.

6.4

Collections

MFCOM defines many collection objects and its safe to say that no code that does even the simplest job will be complete without using at least one MFCOM collection object. Throughout this document almost every example has a loop statement, which is the primary programming logic that uses collection. The Microsoft COM specification and the related reference available from MSDN have extensive information on how the collection objects should be created and used. In this document, well describe only concepts and tips that MFCOM users should know to quickly use them. A collection object is itself an object, but it is a collection of other objects. The collection is made possible by it supporting some predefined COM interfaces and calls that are required by the VBScript engine. In MFCOM, all collection objects are named using the plural form of the name of the object contained in the collection. For example, the server collection object is named as MetaFrameServers, which apparently is a collection of MetaFrameServer objects. Very few MFCOM collection objects can be created by the user. Most of them are returned as a property of another object, which is usually at a hierarchically higher layer of the object hierarchy in MFCOM. For example, the server collection object for the farm object is returned in the following statement:
theFarm.Servers

So it helps to think the collection object using names like MetaFrameServers. But rarely these object names are really used anywhere in code written based on MFCOM. Instead, the interfaces that support the collection objects can be seen in the type library, e.g., IMetaFrameServers. One of the most common ways of using a collection object is to enumerate the object a collection contains. For example, the enumerate all the servers in the farm; the following code can be used.
For Each aServer In theFarm.Servers WScript.Echo aServer.ServerName Next

16

Scripting MetaFrame The above code is valid because the theFarm.Servers clause returns an object that supports the predefined properties and methods required by the Visual Basic For Each statement. The aServer variable is automatically assigned to a MetaFrameServer object fetched one by one by the VB runtime. Since its an object, its properties can be used, e.g., the ServerName property. After you have explored the MFCOM objects for a few minutes, youll see many properties that return collection objects. In addition to the farm object, application, account authority, and even the license objects all have a Servers property defined. So what are the differences for the collection objects returned by the different objects? The answer is that all these contain objects that make the common sense for the object, from which the enumeration is made. For example, the Servers property of a farm object returns a collection of servers in the farm. But the Servers property of an application object returns the servers in the applications server list. MFCOM is so rich in supporting enumeration that almost any naturally sensible enumeration relationship can be found. In other words, if it makes sense to enumerate other objects from an object, the enumeration is probably implemented. This makes navigating through the object hierarchy quite easy and from the top level farm object, its possible to navigate through all other objects. The SDK example CINCOM makes this claim possible and its not hard to see what the above statements in this paragraph mean if you have tried the CINCOM8.

6.5

Arrays

Some MFCOM collections are returned in arrays since the data are all of the elementary types. Even for objects, we sometimes use arrays since an array is easier to implement. Arrays are accessed differently than collections. For example, the hotfixes installed on a server is accessed using an array of objects. The For Each statement to access the hotfixes is different from such a statement to access a collection object. The following is an example script that lists the hotfixes installed on a server. Since the script will be analyzed in more detail later, only the part about the hotfix array is shown here.
hCount = theWinServer.HotfixCount Hotfixes = theWinServer.Hotfixes ' ' Display hotfixes installed on the server. ' WScript.Echo CStr(hCount) & " hotfixes installed on server " & _ Args(0) WScript.Echo "------------------------------------------------" For I = 1 To (hCount - 1) Set aHotfix = Hotfixes(I) Set Date = aHotfix.InstalledOn WScript.Echo "Hotfix Name : " & aHotfix.Name WScript.Echo "Installed by: " & aHotfix.InstalledBy WScript.Echo "Installed on: " & _ Date.Month & "/" & Date.Day & "/" & Date.Year Next

Another example written in C#, CSCMC, is also organized this way.

17

Scripting MetaFrame The above code displays the hotfixes. The For I = 1 To (hCount -1) statement is used instead of the For Each statement. Each element of the array is accessed using the name of the array, e.g., Hotfixes, followed by the array index in a pair of parentheses.

6.6

Time Object

Whenever time is returned by a MFCOM property, a time object is actually returned. We took the pain to create an object for something as simple as time, which usually is stored in a 64-bit integer, because the scripting languages dont usually have access to the Win32 calls for converting the time into different user-friendly formats. Even if these calls can be made available, its still too much for a system administrator, whose main job is to maintain a system, to bother with the syntaxes of using the Win32 calls. All MFCOM properties that return a time take an additional parameter to allow the user to specify if the time should be converted to the local time or the GMT. For example, to query the logon time of a session, we can use the following statement.
aSession.LogonTime(TRUE).Day

The Boolean is necessary and it indicates that we want local time. More apparent for the necessity to specify the Boolean value is seen by the following property of the time object, Day, which can apparently be different from the local time if we specify FALSE to the LogonTime. The following read-only properties are defined for the time object.
Year, Month, Day, DayOfWeek, Hour, Minute, Second, and Millisecond

Another property, IsLocalTime, which is read-write, can be used to query and set if the local time or the GMT time is currently or will be returned for the above properties. The above properties make sense if the time represents a date or a specific moment. Since the time object is also used to return a duration, the above properties dont make sense any more; most of the values will be zero for a duration. So instead, two other properties, LowPart and HighPart, should be used to access the values of a duration. These two properties also form the 64-bit integer of the internal time value for a date. This value is the number of 100 nanoseconds past since January 1, 1601. The time object cannot be created by the user, it is only returned by one of the MFCOM object properties that return time.

6.7

User Defined Collections

As mentioned before, most MFCOM collections cannot be changed by the user. There are, however, a few collection objects that support methods to allow the user to add items to or remove items from a collection. These collection objects usually are named as MetaFrameMyXxxs, e.g., MetaFrameMyServers, MetaFrameMyAccounts, etc.

18

Scripting MetaFrame We need to have these user changeable collections because some calls require a collection object as input parameter. For example, the policy object has calls to allow the user to specify the user accounts. Apparently these accounts can change from time to time. The user defined collection objects support all the calls for a collection object. In addition, they support calls for editing the collection. These calls usually include Add and Remove. For each collection the names of the calls may be different. Internally the collections are usually implemented using very simple data structures, mostly one dimensional arrays. We chose using the simple data structures because the collections are usually small.

6.8

ID Object

The MetaFrameID object is created with the similar purpose to the MetaFrameTime object. We define this object to allow languages such as VBScript to access the 64-bit integer ID, which is used internally by the IMA as the unique identifier for all the IMA objects. The scope of an objects ID is the farm. The ID object can be created by the user. When a call requires such an object, the caller can create an ID, initialize it, and give the ID object to the call as input parameter. Many MFCOM calls also return an ID. For these calls, an ID object is always returned. The ID object has only three read-write properties, ID64, IDH32, and IDL32, which returns the entire 64 bits, the high 32 bits, and the low 32 bits of the ID. Since VBScript doesnt support 64 bit data types, only IDH32 and IDL32 are available for scripting programs. This is the primary reason for offering such an object. No attempt should be made to decipher the bits of an object ID. The bit definition of an object ID is strictly internal to the IMA and supplying an invalid object ID to a call may cause unpredictable problems to your system. Always use the valid IDs returned by the MFCOM calls. The following is an example of accessing the application object ID. Assume that anApp is a MetaFrameApplication object, then the VBScript statement
WScript anApp.AppID.IDH32

prints out the high 32 bits of the ID.

6.9

Credential Object

The MetaFrameCredential object is designed to allow a user to supply user credentials when such information is needed by another MFCOM call. For example, user credentials are needed for the ImportNetworkPrinters method of the farm object. To avoid confusion with the convention of using plural form of a word for collection objects, we use Credential, instead of Credentials as the name of the object although usually user information includes at least a user name and password, both consist the Credentials of the user. 19

Scripting MetaFrame Different calls may require different user credential information, depending on the targeted system of the call. This should not be confused with the information about the user who is accessing MFCOM. The MFCOM user is usually the current logon user. Although it is possible to use another users credentials to access MFCOM9, it is totally a different scenario. The call that takes a user credential object uses this information to access the targeted system, which may or may not be a MetaFrame server. The IMA call that is eventually used to implement the MFCOM call will try to become (impersonate) the given user specified in the credential object to access the target system. This is why the password is usually required. For example, the farm object has an ImportNetworkPrinters call, which queries a remote network print server about the printers it supports and imports the printers to the MetaFrame farm. Since the current MFCOM user may not be allowed to access the network print server, a credential object containing the credentials of a user who is allowed to access the remote network print server is required. Many times a user credential object is also required to resolve some user accounts, which may be from different user account authorities, such as NT domain, Microsoft ADS, and Novell NDS. The current MFCOM user may not be allowed to access those account authorities. For these calls, a credential object containing the information for an allowed user account is needed. For enhanced security, the Password property of the credential object is write-only. The password is stored in the memory encrypted and unencrypted only when it is passed to an IMA call.

6.10

Multi-String Object

An array of strings is sometimes required for some calls. Although the COM library provides such data types, they are not sufficient for our needs. Also, an array of strings is not supported by VBScript, which requires that all elements of an array are of type VARIANT10. The MetaFrameMultiString object can be created by an end user and be given to a call that requires an array of strings, e.g., the ProcessList property of the IMetaFrameSmartCardSetting interface. The strings in the multi-string object can be enumerated and edited. This object may be thought as a collection object. Its name may be an exception since it doesnt end with an s.

Managing Farm

A farm is a group of servers working together to serve user sessions. In this section we discuss the daily routine management of a server farm. We dont discuss the farm design issues here. We assume that a farm is already configured and working as designed. The scripts we will create for farm
9

10

The details are described in an answer in the FAQ section. Dont worry if you dont know what we are talking about here. Im just trying to explain the reasons for defining an object that may seem to be unnecessary given that COM provides many pre-defined data types and objects.

20

Scripting MetaFrame management are tasks that an administrator often performs to keep the farm operating under normal conditions. Like an automobile, we start and drive it. Starting the car needs certain procedures. But keep the car going to the intended destination needs constant attention. The tasks we will discuss regarding the management of a farm are of this nature. A farm has many configurable parameters that can be adjusted. Most of these parameters can be adjusted using MFCOM by scripting. The following is a simple Windows Scripting Host script. It prints out the name of the farm and some properties. To make reading the script easier, comments and explanations are inserted in between the code. The code is in shaded boxes.
<package> <job id="Farm"> <comment> File: Farm.wsf Description: A simple script displaying farm properties. Requirements: WSH 5.5 or higher. Copyright (c) 2002 Citrix Systems, Inc. </comment> <runtime> <description> A simple script displaying farm properties. </description> </runtime>

This is the header of the script. For people who are familiar with HTML, the format is easy to understand. Since almost every script contains this type of header, we dont need to explain much here11.
<reference object="MetaFrameCOM.MetaFrameFarm"/> <script language="VBScript">

These two lines appear to be important. The first line tells the scripting engine that MFCOM is the object to be used. Even if the actual objects used may be different from the farm object, this particular reference line works for all other scripts12.
Dim theFarm, theWinFarm ' ' Create MetaFrameFarm object ' Set theFarm = CreateObject("MetaFrameCOM.MetaFrameFarm") if Err.Number <> WScript.Echo WScript.Echo WScript.Echo WScript.Quit End if
11

0 Then "Can't create MetaFrameFarm object" "(" & Err.Number & ") " & Err.Description "" Err.Number

The author has to confess here that he doesnt know the precise definition of the format of a WSH script file. The script was written by another engineer whom the author considers the real scripting expert. Since the topic is not about learning WSH nor scripting but about using MFCOM and scripting is just a simple and most popular way to explain the usage, the author doesnt feel shameful to claim that he knows some scripting. Even to the extent to pretend that all the WSH statements are well understood. Although this may be a problem to an orthodox programmer, the author has managed to write many scripts like this one that have worked pretty well. I dont know if this revelation will boost the confidence of the reader or lower the credibility of this document. 12 Again, no need to explain here. Just use as they are.

21

Scripting MetaFrame Like Visual Basic, two variables are declared and then one of them, theFarm is set to the created farm object.
' ' Initialize the farm object. ' theFarm.Initialize(MetaFrameWinFarmObject) if Err.Number <> 0 Then WScript.Echo "Can't Initialize MetaFrameFarm object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if

Almost every MFCOM object has an Initialize method that must be called immediately after the object is created and before any other methods and properties of the object can be used. For a farm object, the only parameter to its initialization method is MetaFrameWinFarmObject, which is the only possible value. If you think more about this the initialization makes sense. All objects created by CreateObject do not represent any particular entity in the farm. Initializing the object simply connects an object prototype with the entity that really exists.
' ' Get MetaFrameWinFarm2 object ' Set theWinFarm = if Err.Number <> WScript.Echo WScript.Echo WScript.Echo WScript.Quit End if theFarm.WinFarmObject2 0 Then "Can't receive MetaFrameWinFarm2 object" "(" & Err.Number & ") " & Err.Description "" Err.Number

This is a typical way of getting a new version of the interface pointer. Since type casting and QueryInterface are not available in VBScript, using the properties that provide the new version of the interface is the only possible way to get a pointer to an interface that supports calls not available to the given interface, e.g., theFarm. Many MFCOM objects use this method to support new interfaces of an existing object.
' ' Are you a Citrix Administrator? ' if theWinFarm.IsCitrixAdministrator = 0 then WScript.Echo "You must be a Citrix admin to run this script" WScript.Echo "" WScript.Quit 0 End If

This is the reason that we need to get the pointer to the IMetaFrameWinFarm2 interface, which supports the IsCitrixAdministrator property. This property returns a Boolean, which indicates if the current user is a MetaFrame administrator or not. This call does not fail, it returns either TRUE or FALSE. 22

Scripting MetaFrame Because currently MFCOM is an API interface for the CMC13, only Citrix administrators are allowed to access MFCOM. There are very few calls that work for non-administrators. This call is one of the few. It is a good idea to use this call to ensure that the user has the proper authorization. If the user is not properly authorized, calls to other methods may fail, which may look nasty in languages such as VBScript14.
' ' Print out the farm name. ' WScript.Echo "MetaFrame Farm Name: " & theFarm.FarmName ' ' Print out some of the MetaFrameWinFarm2 object properties. ' WScript.Echo "MetaFrame WinFarm object properties" WScript.Echo "DegradationBias : " & theWinFarm.DegradationBias WScript.Echo "UseClientLocalTime: " & theWinFarm.UseClientLocalTime WScript.Echo "Press ENTER to exit..." WScript.StdIn.Read(1) </script> </job> </package>

Finally we see how the objects properties and methods are used. The farm name is printed. Then additional properties available only to the Windows farm are printed. Based on this script, many farm properties can be viewed or set quite easily. For example, if the ICA keep-alive time out value needs to be changed, instead of using CMC, we can write a simple script. If the user is known to be always a MetaFrame administrator, the above administrator check may not be necessary. Only thing needed in the script would be something like the following:
theWinFarm.ICAKeepAliveTimeout = 100

With this, theres no need to open the CMC, right click on the farm property and they change the value of this setting. This may be an extreme example since very few people would change some farm level setting so frequently. As such, using the CMC to change farm settings may seem to be not a big problem. Many people wouldnt bother to spend the extra time to write and debug a script when the payoff for doing so may not be so great. Our purpose of giving this example, however, is to show that it is very easy to write a script. Once you are familiar with some basic scripting techniques and MFCOM objects properties and methods. It only takes a few minutes to create a simple tool.

13 14

This may change in future releases. MFCOM may be extended to support calls made by regular users. It appears as an exception (a.k.a trap) to such languages. In C++, the equivalent call returns a normal error code.

23

Scripting MetaFrame

Managing Servers

If managing farms seems to be an ill-fitted task for MFCOM, managing servers and other lower level entities in the farm architecture will yield real benefits if scripting is used effectively. Generally the lower the level of an object in the farm hierarchy, the more frequently the objects properties change. Monitoring and setting the properties of these low level objects is more routine for an administrator. On the other hand, more mouse clicks will be needed to navigate the CMC trees to reach a low level object. A server object itself has many properties. Many of them bear the same name as the farms properties but changes to these properties only apply to the server itself. In addition, for these properties that have a farm equivalent, another Boolean flag is also available to indicate if the farms property value should be used instead of that of the servers. So, essentially for a server we can create tools similar to the one illustrated in the previous section to read or set the properties of a server. Since that type of scripting has been demonstrated, we will focus on tasks that are a little more complex than dealing with only one object.

8.1

Enumerating Servers

Since there can be more than one server in a farm, enumerating servers is a common task. Apparently servers can be enumerated from a farm object. The following is a script that performs this task.
<package> <job id="Servers"> <comment> File: Servers.wsf Description: List servers in the farm. Requirements: WSH 5.5 or higher. Copyright (c) 2002 Citrix Systems, Inc. </comment> <runtime> <description> List servers in the farm. </description> <example> CScript //nologo Servers.wsf </example> </runtime> <reference object="MetaFrameCOM.MetaFrameFarm"/>

The usual stuff, still included here just to make the code complete.

24

Scripting MetaFrame
<script language="VBScript"> Dim theFarm, aServer, aWinServer ' ' Create MetaFrameFarm object ' Set theFarm = CreateObject("MetaFrameCOM.MetaFrameFarm") if Err.Number <> 0 Then WScript.Echo "Can't create MetaFrameFarm object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if ' ' Initialize the farm object. ' theFarm.Initialize(MetaFrameWinFarmObject) if Err.Number <> 0 Then WScript.Echo "Can't Initialize MetaFrameFarm object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if ' ' Are you a MetaFrame Administrator? ' if theFarm.WinFarmObject.IsCitrixAdministrator = 0 then WScript.Echo "You must be a Citrix admin to run this script" WScript.Echo "" WScript.Quit 0 End If ' ' Print out the farm name. ' WScript.Echo "MetaFrame Farm Name: " & theFarm.FarmName WScript.Echo ""

Since we are going to enumerate servers for the entire farm, the farm object must be created. The object is initialized and the farm name is printed.
' ' Display all servers in the farm. ' WScript.Echo "All servers in the farm (" & Now & ")" WScript.Echo "------------------------------------------------" For Each aServer In theFarm.Servers

This statement is the core of the code. The farm object supports a method that returns a collection object, which contains the server objects. A collection object is a COM concept. Basically a predefined set of properties and methods must be supported by a collection object. Except those required properties and methods, a collection object is just a normal COM object. The required properties and methods of a collection object enable some Visual Basic statements such as the one used here, For Each. Lets take a close look at what happens when the For Each aServer In theFarm.Servers statement is executed. Although it is a simple VB statement, a lot of things happen behind the scenes. 25

Scripting MetaFrame The clause theFarm.Servers is first executed. Some MFCOM functions are invoked to return an object to the caller. The object supports the required properties and methods and therefore is recognized as a collection object. The interface supported by the collection object is IMetaFrameServers. There are many such collection interfaces defined in MFCOM. Their names are easy to recognize because it is the plural form of the object interface for which the collection is created. MFCOM has collection objects defined for almost all the objects except the farm object, which is the top level object. One of the required methods for a collection object is the __NewEnum method, which is hidden to VB and many other languages but visible to C++. The actual enumeration is executed when this method is called. The For Each statement quietly calls this method after the theFarm.Servers clause returns successfully. If __NewEnum returns successfully, the Next property, which is required for a collection object, is called to get the first object in the collection since this is the first time Next is called. The pointer to this object is stored in the variable aServer, which is available to the statements in the body of the loop.
if Err.Number <> WScript.Echo WScript.Echo WScript.Echo WScript.Quit End if 0 Then "Can't enumerate servers" "(" & Err.Number & ") " & Err.Description "" Err.Number : " & aServer.ServerName : " & aServer.IPAddress

WScript.Echo "Name WScript.Echo "IPAddress

MetaFrameWinServer object. Set aWinServer = aServer.WinServerObject Wscript.Echo Product Code : & aWinServer.MFWinProductCode Wscript.Echo Windows Name : & aWinServer.MFWinName Wscript.Echo

This is the loop body. The variable aServer points to the current server object in the collection. Here we just print out the MetaFrame product code and name. Many other server properties can be changed or displayed. Note that unlike the objects created using the CreateObject or other equivalent object instantiation methods, the objects in a collection are all fully initialized. They dont need to and should not be initialized. Further, here we assume that all the servers returned are Windows servers. We dont check the object type before executing the statement Set aWinServer = aServer.WinServerObject. The reason for using another variable aWinServer is because the product code and name properties are only available through the IMetaFrameWinServer interface. The aServer.WinServerObject statement returns a pointer that supports this interface.
Next

This statement closes the loop. Upon execution, it calls the Next method of the collection object. If Next returns another object, the pointer to the new object is stored in aServer and the execution goes 26

Scripting MetaFrame back to the first statement of the loop body. If Next indicates that there are no more objects in the collection, the loop ends. The scope of aServer ends outside of the For Each Next loop. In other words, aServer should not be used outside of the loop. VB automatically calls the Release method (of aServer) to decrement the references to aServer during the execution of the Next statement.
WScript.Echo "Press ENTER to exit..." WScript.StdIn.Read(1) </script> </job> </package>

So VBScript has made things a lot easier by doing a lot of the leg work behind the scenes. This is why many people like using scripting. Since the collection object is just a regular object, its pointer can be stored in another variable that can be used later. The following statements print out the number of servers in the farm15.
Dim Servers Set Servers = theFarm.Servers Wscript.Echo Number of servers in the farm: & Servers.Count

Servers can be enumerated from a zone, application, and other objects in addition to the farm object. All those objects have a Servers property that returns a collection object of servers, which are included in the collection with respect to the specific logical meaning of the collection. For example, the servers enumerated by a zone are those defined in the zone. Servers enumerated from an application object are those in the applications server list. Regardless how a server is enumerated, all the objects created for the same physical server contain the same information.

8.2

Listing Hotfixes

Although every software company in the world claims that they make great software, none of them can say that their software is defect free. Unlike any other products, customers of software products are willingly purchasing and using software that surely contains bugs. The only good thing is that many companies promise to fix any problems that may be found. Most of the time the fixes are free, but after some time, even managing the fixes becomes part of the administrators job. Citrix releases fixes as hotfix. Each hotfix has a name and MetaFrame hotfixes can also been seen using the CMC. Like many other things, the hotfix information is also available from MFCOM. So, instead of clicking through servers to find the hotfixes installed on a server, the following script can be used.

15

This would not work for MFCOM releases prior to Feature Release 3. The Count property was not initialized in the previous releases. The work around was to quit a For Each statement and then read the Count property.

27

Scripting MetaFrame
<package> <job id="Hotfixes"> <comment> File: Hotfixes.wsf Description: List hotfixes installed on a server. Requirements: WSH 5.5 or higher. Copyright (c) 2002 Citrix Systems, Inc. </comment> <runtime> <description> List hotfixes installed on a server. </description> <example> CScript //nologo hotfixes.wsf ServerName </example> </runtime> <reference object="MetaFrameCOM.MetaFrameFarm"/>

As usual, the header.


<script language="VBScript"> Dim theServer, theWinServer, Args, hCount, aHotfix, I, Hotfixes Dim Date ' ' Parse the command line. ' Set Args = WScript.Arguments If Not Args.Count = 1 Then WScript.Echo "Usage: hotfixes.wsf ServerName" WScript.Quit End If ' ' Create MetaFrameServer object ' Set theServer = CreateObject("MetaFrameCOM.MetaFrameServer") if Err.Number <> 0 Then WScript.Echo "Can't create MetaFrameServer object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if ' ' Initialize the server object. ' theServer.Initialize MetaFrameWinSrvObject, UCase(Args(0)) if Err.Number <> 0 Then WScript.Echo "Can't Initialize MetaFrameServer object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if

The script needs a server name as the only required argument. We directly create a server object and initialize it using the server name. Note that the server name must be all in upper case. There is a bug in MFCOM that would cause some data unavailable if the server name is in lower case. This bug has been fixed in Feature Release 3.

28

Scripting MetaFrame
' ' Get the WinServer interface. ' Set theWinServer = theServer.WinServerObject2 hCount = theWinServer.HotfixCount Hotfixes = theWinServer.Hotfixes ' ' Display hotfixes installed on the server. ' WScript.Echo CStr(hCount) & " hotfixes installed on server " & _ Args(0) WScript.Echo "------------------------------------------------" For I = 1 To (hCount - 1) Set aHotfix = Hotfixes(I) Set Date = aHotfix.InstalledOn WScript.Echo "Hotfix Name : " & aHotfix.Name WScript.Echo "Installed by: " & aHotfix.InstalledBy WScript.Echo "Installed on: " & _ Date.Month & "/" & Date.Day & "/" & Date.Year Next WScript.Echo "Press ENTER to exit..." WScript.StdIn.Read(1) </script> </job> </package>

The WinServerObject2 interface is obtained because it supports IMetaFrameWinServer2, which supports calls to get the hotfixes. The hotfix information is stored in a one dimensional array. Each array element is an object that supports the IMetaFrameHotfix interface, which supports hotfix properties such as name and installation date. The hotfix installation date is returned in an object that supports the IMetaFrameTime interface, which has the usual time/date properties like month, day, and year. The script can be easily modified to loop on all the servers in the farm. It can also be modified to be a query utility to check if a specific hotfix is installed on a particular server.

Managing Published Applications

Published applications are objects that are most suitable for scripting. Customers of MFCOM from both inside and outside of Citrix have used MFCOM to create and manage published applications. Probably more scripts have been written on published applications than any other objects. A typical server farm hosts many published applications. Each application is accessed by many users. A published application has many parameters that can be adjusted for different needs. The two most important parameters of a published application are its server list and user list. The server list defines the servers on which the application will be executed. The server list contains a subset of the servers in the entire farm. The user list defines the users who are allowed to access the application. The user list contains individual user accounts and user groups.

29

Scripting MetaFrame Applications can be enumerated from many different objects. Similar to the server enumeration code described in the previous section, code to enumerate applications for the farm and for a particular server can be created. Note that the published applications enumerated from a server are those that have the server in their server list. Enumerating and displaying properties of an application are only the easy part of managing published applications. First, published applications can be created. Second, published applications relate to many other objects, e.g., servers, users, and sessions. In fact there is so much to do with published applications we have created a separate SDK just for the purpose of managing published applications. Well use one of the tools based on the application publishing SDK (APSDK) to complement the examples in this section.

9.1

Application Names

Managing published applications is probably the number one job of many MFCOM users and I would bet nine out of ten of these users have had some degree of confusion over the different names of the a published application. Hopefully, after reading this section, the reader will have a clear understanding of the application names. Three different names are used in MFCOM for the MetaFrameApplication object. They are the display name, distinguished name, and the browser name. The maximum length for all these names is 256 characters, including the ending NULL character. The display name, a.k.a. friendly name, is a string visible to the end user in the MetaFrame Management Console. For example, My Notepad is a valid application name, as shown in the screen shot below.

30

Scripting MetaFrame

The distinguished name is used only in MFCOM. It is a string that consists of the parent folder name and the applications display name. Since applications are organized in folders, which resembles directories, the distinguished name is very similar to the directory path name for a file16. For example if the application My Notepad is published in folder /Applications/App Folder1, the distinguished name for this published application is /Applications/App Folder1/My Notepad. Distinguished names are so called because they uniquely identify an application in the entire farm. All MFCOM calls that require an application name take only the distinguished name of an application. The root folder for published applications is always /Applications, which cannot be renamed. All published application distinguished names start with /Applications. Although we are using the forward slash / in all examples, the backward slash \ is also accepted as a valid string separator in an application distinguished name. Both slashes can mix in one distinguished name. In the next picture, there are two published application with the same display name but they are in different folders.

16

We actually tried to use the term pathname when we initially designed MFCOM.

31

Scripting MetaFrame

Since the display name, which can be changed, is included in the distinguished name, when it is necessary to track the application object even when its name changes, the application ID returned by the AppID property can be used. This ID doesnt change for the life time of the application. Another name is the browser name, which was not available from MFCOM until Feature Release 2. The browser name is read-only and internally generated by the IMA. This name is unique across the entire farm. It is instead not like the distinguished name, which is structured. The browser name is a simple string and is mainly used by older releases of MetaFrame as well as the Web Interface of MetaFrame XP. The browser name is called Application Name in the CMC. For the above two published applications, the IMA creates a new browser name for the second My Notepad application, as shown in the following picture.

32

Scripting MetaFrame

9.2

Application-Server Binding

The MetaFrameAppSrvBinding object is used to add a server to the server list of a published application. Since for each server, the working directory and the initial command, a.k.a. initial program, can be different, it takes more than a server name when a server is to be added to a published application. The binding object can be created by the user. Then it can be initialized by setting the InitialCommandLine and WorkingDirectory properties, in addition to the server name and application name. Then the binding object can be given as the input parameter for the application objects AddServer method. The following is an example of adding a server to a published application.
<package> <job id="AddServer"> <comment> File: AddServer.wsf Description: Exanmple of how to add a server to an app. Requirements: WSH 5.5 or higher. Copyright (c) 2002 Citrix Systems, Inc. </comment> <runtime> <description> Add a server to an app.

33

Scripting MetaFrame
</description> <example> CScript //nologo AddServer.wsf Applications\calc SERVER1 </example> </runtime> <reference object="MetaFrameCOM.MetaFrameFarm"/> <script language="VBScript"> Dim AppDN, ServerName, theApp, theAppSrvBind ' ' If no parameters specified, quit and ' show the usage of the script ' if WScript.Arguments.Count <> 2 Then WScript.Echo "USAGE: AddServer.wsf AppDN ServerName" WScript.Echo "" WScript.Echo "Example: AddServer.wsf Applications\cmd SRV1" WScript.Quit 0 Else AppDN = WScript.Arguments(0) ServerName = UCase(WScript.Arguments(1)) WScript.Echo "AddServer Application: " & AppDN & " Server: " & ServerName WScript.Echo "" End If ' ' Create App object ' Set theApp = CreateObject("MetaFrameCOM.MetaFrameApplication") if Err.Number <> 0 Then WScript.Echo "Can't create MetaFrameApplication object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if ' ' Initialize the app object. ' theApp.Initialize MetaFrameWinAppObject, AppDN if Err.Number <> 0 Then WScript.Echo "Can't Initialize Application object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if theApp.LoadData(1) ' ' Create AppServerBinding object ' Set theAppSrvBind = CreateObject("MetaFrameCOM.MetaFrameAppSrvBinding") if Err.Number <> 0 Then WScript.Echo "Can't create MetaFrameAppSrvBinding object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if ' ' Initialize the appsrvbind object. ' theAppSrvBind.Initialize MetaFrameWinSrvObject,ServerName,"" if Err.Number <> 0 Then WScript.Echo "Can't Initialize AppSrvBinding object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number

34

Scripting MetaFrame
End if ' ' Add the server. ' theApp.AddServer(theAppSrvBind) if Err.Number <> 0 Then WScript.Echo "Can't add server" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if theApp.SaveData() WScript.Echo "Added Server to App successfully" </script> </job> </package>

9.3

Batch Publishing Of Applications

The following is an example script to create a large number of published applications17. In the internal Citrix test environment, we often need to test scalability issues related to published applications. Publishing a large number of applications, even very simple ones, takes time using the CMC. By running the following script, one command line will create any number of applications you want.
<package> <job id="MoreApps"> <comment> MoreApps.wsf This script requires Windows Scripting Host 5.5 or higher. Copyright (C) 2002, Citrix Systems Inc. To run this script, you must logon your MetaFrame server as a domain user account. This user account must also be a Citrix administrator. This simple script can be used to create a large number of published applications for testing purposes. It requires a root name of the published application, a total number of applications to be created, an the full pathname of the executable file. The applications created all have the same properties. The names of the applications are formed using the root name and a number. </comment> <runtime> <description> This script creates a large number of published applications. ADSI 2.5 or higher and Windows Scripting Host 5.5 or higher are required. Use the CScript.exe WSH engine to execute. </description> <example> Cscript MoreApps.wsf RootName AppCount ExePath </example> </runtime> <reference object="MetaFrameCOM.MetaFrameFarm"/>

17

Jeff Reed, who is probably the best scripter at Citrix, wrote the code.

35

Scripting MetaFrame The header information describes pretty well about what this script does. It takes on the command line as arguments a root name of the application, a count of the applications to be published, and the path of the executable of the application. The root name is used to generate the names of the published application. For example, if the root name is app, the applications published will have name like app00, app01, etc. The total number of applications published is given by the count, which is the second command line argument.
<script language="VBScript"> Option Explicit Dim myFarm, myApp, theWinApp, aFile, Args, SrvList Dim nApps, RootName, AppExe, Zeros, AppName, iApp, aServer Dim AppSrv, myNetwork, myDomain ' Parse the command line. Set Args = WScript.Arguments If Not Args.Count = 3 Then WScript.Echo "Usage: moreapps.wsf RootName AppCount Exe" WScript.Quit End If ' Make sure AppCount is a number. If Not IsNumeric(Args(1)) Then WScript.Echo Args(1) & " is not a number" WScript.Quit End If ' Make sure AppCount is at least 1. nApps = CInt(Args(1)) If nApps < 1 Then WScript.Echo Args(1) & " is not a positive number" WScript.Quit End If ' Make sure the executable exists. AppExe = Args(2) Set aFile = CreateObject("Scripting.FileSystemObject") If Not (aFile.FileExists(AppExe)) Then WScript.Echo Args(2) & " does not exist" WScript.Quit End If

The command line arguments are parsed. Note that some built-in VB objects are used, such as WScript.Arguments and the FileSystemObject. Familiarity with these objects is essential for anyone who wants to write more than just petty scripts.

36

Scripting MetaFrame
' Application root name is a string. RootName = Args(0) ' Create MFCOM farm object. Set myFarm = CreateObject("MetaFrameCOM.MetaFrameFarm") if Err.Number <> 0 Then WScript.Echo "Can't create MetaFrameFarm object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if ' Initialize the farm object. myFarm.Initialize(MetaFrameWinFarmObject) if Err.Number <> 0 Then WScript.Echo "Can't Initialize MetaFrameFarm object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if ' Are you Citrix Administrator? if myFarm.WinFarmObject.IsCitrixAdministrator = 0 then WScript.Echo _ "You must be a Citrix administrator to run this script" WScript.Echo "" WScript.Quit 0 End If

The farm object is needed. So we create a farm object and initialize it. We also check to make sure the current user is a MetaFrame administrator.
Get list of servers. Set SrvList = myFarm.Servers Get the current domain of the user running the script. Set myNetwork = WScript.CreateObject(WScript.Network) myDomain = myNetwork.UserDomain

All the servers in the farm will be added to the server list of the applications to be created. Also, we assume that the users are from the current domain of the server, on which the script is executed. Again, a built-in object WScript.Network is used to query the user domain.
' Creating the applications For iApp = 1 to nApps

Loop on the given number of applications.


Create a new application object. Set myApp = myFarm.AddApplication(MetaFrameWinAppObject) If Err.Number <> 0 Then WScript.Echo Cant create application object WScript.Echo ( & Err.Number & ) & Err.Description WScript.Echo End If Set theWinApp = myApp.WinAppObject

The AddApplication method is the only method available to publish an application. No other ways exist in MFCOM to create a new published application. This method returns a pointer to a newly created published application object. The published application itself, apparently, doesnt exist yet in 37

Scripting MetaFrame the farm. But the object itself is initialized as a WinAppObject, this enables us to take the pointer of it. Only the application parameters need to be further initialized. The Initialize method of the newly created object should not be called.
Zeros = String(Len(CStr(nApps)) - Len(CStr(iApp)), "0") AppName = RootName & Zeros & iApp myApp.AppName = AppName myApp.Description = "Test app " & iApp & " of " & nApps Wscript.Echo "Creating application: " & AppName

The application display name is initialized. The application description is also generated. Then a message is displayed informing the user of the progress of the script.
' Set some common properties. theWinApp.DefaultEncryption = MFWinEncryptionBasic theWinApp.DefaultInitProg = AppExe theWinApp.DefaultSoundType = MFWinSoundBasic theWinApp.DefaultWindowColor = MFWinColor16M theWinApp.DefaultWindowType = MFWinWindow800X600 theWinApp.DefaultWorkDir = "" theWinApp.ParentFolderDN = "/Applications" theWinApp.MFAttributes = MFWinAppMFAttrNone theWinApp.PNAttributes = MFWinAppPNAttrNone theWinApp.DesktopIntegrate = MFWinAppIntegrateNone

These parameters are all the most basic published application parameters and they must be initialized with the proper values. Note that the ParentFolderDN property needs to be set, although the default application folder for applications is /Applications. If the applications are to be put under a different folder, the folder name can be specified here. But the folder must exist when this script is executed. The above parameters are all for Windows applications. This is why we obtained the WinAppObject pointer in the beginning of the loop.
theWinApp.ReadIconFromFile AppExe, 0

The application icon is set. Here we assume that an icon resource exist at index 0 in the given executable. The file is read immediately up execution of this statement, although the application has not been published yet. Note that the file must be accessible for the mfcom.exe process. To avoid possible confusions with relative path names, the given executable should use a full path name.
' Add all farm servers to the application. For Each aServer In SrvList Set AppSrv = CreateObject(_ "MetaFrameCOM.MetaFrameAppSrvBinding") AppSrv.Initialize MetaFrameWinSrvObject, _ aServer.ServerName, myApp.DistinguishedName theWinApp.AddServer AppSrv Next

The entire server in the farm is added to the server list of the new published application. To do this, a new object MetaFrameAppSrvBinding must be created. This object has two properties, a server name and an application distinguished name. As its name implies, the object binds the server and application together. The Initialize method of the binding object initializes these two properties. 38

Scripting MetaFrame Note that although the application has not been published, its DistinguishedName property exists because we have successfully initialized the AppName and ParentFolderDN properties. These two properties are used to form the application distinguished name. Then the binding object is assigned to the application using the AddServer method. Again, here the VB programmer doesnt have to be concerned with object reference counting for the application and server binding object. The reference count for AppSrv is automatically maintained by VB.
' Add Domain Users and Domain Admins from current domain. theWinApp.AddUser MFAccountAuthorityNTDomain, myDomain, _ MFAccountGlobalGroup, "Domain Users" theWinApp.AddUser MFAccountAuthorityNTDomain, myDomain, _ MFAccountGlobalGroup, "Domain Admins"

We add two groups to the applications user list.


' Validate application properties myApp.Validate If Err.Number <> 0 Then WScript.Echo "Can't validate application." WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" End If

The Validate method checks the integrity of the parameters of the application. This is necessary since there are so many parameters that they need to be consistent.
' Create the published application. myApp.SaveData If Err.Number <> 0 Then WScript.Echo "Can't create " & AppName WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" Else WScript.Echo AppName & " created successfully!" End If

The SaveData method is the call that makes everything happen. Until this call is executed, the application is not published. When the call successfully returns, the application is published. Internally, it does a Validate call prior to saving the data to the IMAs persistent store. The SaveData method is also used to save changes made to an existing published application.
Set myApp = Nothing Set theWinApp = Nothing Next </script> </job> </package>

Setting myApp and theWinApp to Nothing causes VB to release the reference counts to the objects they point to. Although this may not be necessary since VB maintains the reference counts, it is a 39

Scripting MetaFrame good practice to set a pointer to Nothing to indicate that the pointer is free to be used for another object.

9.4

Publishing Applications Using APSDK

For now MFCOM has been our focus. Since the topic of this document is about scripting MetaFrame, therefore it should not be a problem for us to talk about something else. In the published application management arena, another very important tool in creating new applications is offered in the APSDK. The APSDK offers a set of API calls for managing published application in C++. One of the examples is newapp, whose usage is the next example we will discuss in detail. We will not analyze the newapp code itself in this document. Interested readers can read the code that is included in the SDK. Essentially, newapp is a parser of a syntax defined in one of the appendices of the APSDK manual. It only uses one call provided by the SDK to create a published application. The example takes a data file with .app suffix. The file is a text file and its content must conform to the defined syntax. The following is a sample .app file.

40

Scripting MetaFrame
# # This is a sample application publishing script file. # notepad.exe. # BeginApplication Program = "notepad.exe" # Must be in string becuase . is keyword. BrowserName = Notepad # The most popular application. Description = "The good old notepad" ProgramNeighborhoodFolder = "Put client folder here" Parent = "The parent folder name" FriendlyName = "My notepad" Directory = "c:\temp" # Must be in string because : and \. Encryption = "128 bit" WindowType = "800x600" WindowWidth = 800 WindowHeight = 600 WindowScale = 0 # We use absolute window size. WindowColor = 256 Sound = None Video = None DesktopIntegrate = StartMenu, Desktop. PNAttributes = "Sound minimum", "Video minimum". MetaFrameAttributes = "Hide titlebar". PublishingFlags = "No PN Enum", "No folder enum". ServerList = Sun; Mercury, "c:\wtsrv\notepad.exe", "f:\temp"; Venus, , "g:\temp"; Earth; Mars, "c:\wtsrv\system32\winword.exe"; Jupiter, "sol.exe"; Saturn. UserList = Domain/Jones; Domain/Smith; Global/Administrators; Global/AuthorizedUsers. IconFile = "notepad.ico" # # # IconData = 0x5, 0x0, 0x8, 0x9, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc. IconMask = 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc. It publishes

EndApplication

Since this is a small file, we dont need to explain each line separately. Its not hard to speculate that the lines starting with the # sign are comments. To be precise, anything appearing between a # and the end of line is discarded by the parser as comments. Most of the words used are key words, which are defined in the syntax. The data that describes an application must start with the key word BeginApplication and end with EndApplication. A .app file may contain descriptions for more than one published applications as long as each applications description is enclosed in a pair of BeginApplication and EndApplication key words. A key word that requires a string as argument must have the string in double quotes . Since the parser is very simple, it defines certain symbols as separators. When a separator is used as data, it must be also enclosed by the double quotes. For example, the period sign (.) is a separator. When it 41

Scripting MetaFrame is used as part of the name of the file notepad.ico, the whole file name must be enclosed using the double quotes. For the most part, a line is composed of a key word that specifies the property of the application, an equal sign, and a value for the property. When a property takes multiple values, the comma sign is used to separate the values. Each assignment must end with a period sign when multiple values are given to a property. The semicolon sign is used to separate the entries for the server and user list. Since for the server list, a triplet may be needed to specify the working directory and initial program for the server, the items of a triplet are separated using the comma. Forward slash is used to separate the domain name and the user name for the items of the user list. When such a file is given to newapp on the command line and there is no syntax error in the data file, the APSDK call used will attempt to publish the application. It does many consistency checking, e.g., making sure that the servers exist. When everything is good, the application is published. Its not hard to modify newapp to use MFCOM calls. With it, application publishing is mainly reduced to editing a text file. This simplification not only saves time but also makes publishing applications using 3rd part software a possibility. Many people have found that the syntax of the .app file is not easy to follow. When they create the .app file from scratch, they have to spend some considerable time just to fix the syntax errors. A better way to create a .app file is actually available. This is done by using another example included in APSDK. The export example can export the data of a published application to a .app file. Using both export and newapp, the application publishing procedure can be summarized as follows: 1. Publish an application template using the CMC or PAM (Published Application Manager on MetaFrame 1.8). The properties of this application should be set to the common set of all the applications to be published. Assume the distinguished name of this application is /Applications/app1. 2. Execute this command: export /Applications/app1 /ea app1.app. This dumps the data of app1 to the file app1.app in .app format. 3. Copy app1.app to app2.app. Modify app2.app, which should contain the data for the second published application. 4. Publish the second application by executing newapp app2.app. 5. Repeat the above process to publish the desired number of applications.

9.5

Displaying Application Properties

It is more common to view and change the parameters of existing published applications in normal administrative routines. In the following example well show a script that lists the properties of an existing application.

42

Scripting MetaFrame
<package> <job id=AppProp> <comment> File: AppProp.wsf Description: Display properties of an application. Requirements: WSH 5.5 or higher. Copyright 2002 Citrix Systems, Inc. </comment> <runtime> <description> Display properties of an application. </description> <example> Cscript //nologo appprop.wsf AppDN </example> </runtime> <reference object=MetaFrameCOM.MetaFrameFarm/> <script language=VBScript> Dim theApp, theWinApp, Args Parse the command line. Set Args = WScript.Arguments If Not Args.Count = 1 Then WScript.Echo Usage: appprop.wsf AppDN WScript.Quit End If Create MetaFrameApplication object Set theApp = CreateObject(MetaFrameCOM.MetaFrameApplication) if Err.Number <> 0 Then WScript.Echo Cant create MetaFrameApplication object WScript.Echo ( & Err.Number & ) & Err.Description WScript.Echo WScript.Quit Err.Number End if Initialize the application object. theApp.Initialize MetaFrameWinAppObject, Args(0) if Err.Number <> 0 Then WScript.Echo Cant Initialize MetaFrameApplication object WScript.Echo ( & Err.Number & ) & Err.Description WScript.Echo WScript.Quit Err.Number End if

Nothing unusual in the above code. The script takes an application distinguished name as the only argument. It creates the application object and initializes it using the application distinguished name.
' ' Load application data. ' theApp.LoadData(1)

The LoadData method must be called! It loads the application data from the persistent data store. The only parameter required by this call is a Boolean to indicate if the data should be refreshed. Since this call can be made repeatedly, to save unnecessary read to the IMA, the Boolean indicates if the data 43

Scripting MetaFrame stored in the objects memory should be refreshed with the data from the IMA data store. Give it a TRUE forces the refresh. If FALSE is given, the data is read only if it hasnt been read before. One of the problems with the application object is that people often forget to call this method to initialize the data. In fact the author did just that when creating the code for this document. Whats frustrating is that the other calls dont fail if data is not loaded; they simply return some invalid data. Although most of the time the invalid data is NULL, it still took the author quite some time and even after using a debugger to realize that the LoadData method must be called. So, always remember to call LoadData before accessing application data and SaveData after accessing the data. This is probably the only tip I can offer to get around this issue.
' ' Get the WinApp interface. ' Set theWinApp = theApp.WinAppObject3 WScript.Echo "Application Name: " & WScript.Echo "Application ID: " & theWinApp.AppID.IDL32 WScript.Echo "Browser Name: " & WScript.Echo "Description: " & WScript.Echo "Initial Program: " & WScript.Echo "Working Directory: " & </script> </job> </package>

theWinApp.AppName theWinApp.AppID.IDH32 & _ theWinApp.BrowserName theWinApp.Description theWinApp.DefaultInitProg theWinApp.DefaultWorkDir

Here we just print out few of the many properties of an application. The other properties can be printed quite easily in similar ways. But instead of having a long list of print statements, the reader may be more interested in using the properties and additional logic to develop more useful tools. For example, the reader may be interested in applications that have the same default working directory. The more important properties are the user and server lists of the application. In the next section we will see how the users of an application can be managed. Servers of an application can be managed in a similar way.

9.6

Managing Application Users

Continuing the script in the previous section, we can enumerate the users of an applications user list.
For Each aUser In theApp.Users WScript.Echo aUser.AAName & "/" & aUser.UserName Next

A user is defined by an account authority and a user name. Further, account authorities have different types. Here we are not printing out the types.
For Each aGroup In theApp.Groups WScript.Echo aGroup.AAName & "/" & aGroup.GroupName Next

44

Scripting MetaFrame User groups are separately returned in an application. This is designed to avoid the complicated issues of supporting multiple interfaces of different types for the user object since users can be enumerated from a group. Adding and removing one or more users from a published application is much easier using the script below than using the CMC GUI interface, which would require multiple mouse clicks.
<package> <job id="AddUser"> <comment> File: AddUser.wsf Description: Add a user to a published application. Requirements: WSH 5.5 or higher. Copyright (c) 2002 Citrix Systems, Inc. </comment> <reference object="MetaFrameCOM.MetaFrameFarm"/> <script language="VBScript"> Dim theApp If WScript.Arguments.Count < 3 Then WScript.Echo "USAGE: AddUser.wsf AppDN AAName UserName" WScript.Echo "" WScript.Quit 0 End If Set theApp = CreateObject("MetaFrameCOM.MetaFrameApplication") If Err.Number <> 0 Then WScript.Echo "Can't create application object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if theApp.Initialize MetaFrameWinAppObject, WScript.Arguments(0) If Err.Number <> 0 Then WScript.Echo "Can't initialize application object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End If theApp.LoadData(TRUE) theApp.AddUser MFAccountAuthorityNTDomain, WSCript.Arguments(1), _ MFAccountDomainUser, WScript.Arguments(2) If Err.Number <> 0 Then WScript.Echo "Can't add user to application" WScript.Echo "(" & Err.Number & ")" & Err.Description WScript.Echo "" WScript.Quit Err.Number Else theApp.SaveData End If </script> </job> </package>

The script is simple and straightforward enough that we are not going to analyze it piece by piece. A few statements should be noted. First, never forget to call LoadData and SaveData before and after modifying the data of an application. Second, we are making some assumptions about the user to 45

Scripting MetaFrame simplify the script. The user is assumed to be an NT user and it is a domain user. The same AddUser call can be used to add a domain user group to the application. To remove a user, use the RemoveUser call. An interactive script may be written if more users are to be added or removed.

10

Managing Sessions and Users

Sessions contain the most dynamic data among all the MetaFrame farm objects. Most of an administrators time is spent on monitoring the status of the sessions after a farm is running under stable conditions. As a result, there are more properties defined for the MFCOM session object. The properties include static session data such as complete client information, and dynamic session data such as session times. In addition, there are methods to logoff and disconnect a session. Sessions can be enumerated from the farm, a server, an application, and a user. The enumeration results contain sessions for the object that initiated enumeration. For example, sessions enumerated from the farm object are all the sessions in the farm. Sessions enumerated from a server object are all the sessions that are found only on that server.

11

Managing Administrators

MetaFrame administrators are users who have access to the CMC. MetaFrame defines three types of users, full administrator, view-only administrator, and custom administrator. A full administrator has all the privileges to manage everything in CMC. A view-only administrator has only view privileges, object properties can not be modified by a view-only administrator. A custom administrator has privileges specifically assigned by a full administrator. View-only and custom administrators are partial administrators. The above is just a rough classification; the privileges of a partial administrator can be enumerated. These privileges define the exact rights an administrator has. Until FR3, MetaFrame administrators can only be managed using the GUI interface provided by the CMC. The FR3 version of MFCOM has been enhanced to fully support the management of MetaFrame administrators. This makes the scripting of the following tasks possible. In many ways the management of administrators is similar to the management of published applications. The administrator objects have fewer properties. Administrator privileges must be loaded prior to being used and the data must also be saved. Administrators are farm objects and they dont have much to do with other objects in MFCOM. Enumerating farm administrators should be easy to do and we dont need to explain the script in this section. The following is a script to add the local NT administrators to all the servers in a farm.

46

Scripting MetaFrame
<package> <job id="AddAdmin"> <comment> File: AddAdmin.wsf Description: Adds local Administrators to all servers in a farm. Requirements: WSH 5.5 or higher. Copyright (c) 2002 Citrix Systems, Inc. </comment> <runtime> <description> Adds local Administrators to all servers in a farm. </description> <example> CScript AddAdmin.wsf </example> </runtime> <reference object="MetaFrameCOM.MetaFrameFarm"/> <script language="VBScript"> Dim NewAdmin, theFarm, theServer ' ' Create the farm object. ' Set theFarm = CreateObject("MetaFrameCOM.MetaFrameFarm") if Err.Number <> 0 Then WScript.Echo "Can't create MetaFrameFarm object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if ' ' Initialize the farm object. ' theFarm.Initialize MetaFrameWinFarmObject ' ' Add the new admins for each server in the farm. ' For each theServer in theFarm.Servers Set NewAdmin = theFarm.AddAdmin NewAdmin.AdminType = MFAdminPermissionFullAccess NewAdmin.Enable = 1 NewAdmin.AAType = MFAccountAuthorityNTDomain NewAdmin.AAName = theServer.ServerName NewAdmin.AccountType = MFAccountLocalGroup NewAdmin.AccountName = "Administrators" NewAdmin.SaveData if Err.Number <> 0 Then WScript.Echo "Can't save Admin object" WScript.Echo "(" & Err.Number & ") " & Err.Description WScript.Echo "" WScript.Quit Err.Number End if Next </script> </job> </package>

The for loop contains the code that adds the Administrators group to each server in the farm. A new administrator object is created using the AddAdmin method of the farm object. This method creates an empty object that needs to be initialized. After all the administrator properties are set, the 47

Scripting MetaFrame
SaveData call actually creates the administrator object in IMA with the given properties. Until SaveData is successfully executed, no change is made to the IMA.

To create the administrator, the administrator type must be specified. Here we assume that all local administrators have the full privileges. If partial administrators are to be created, the privileges need to be specified. Since the administrators are NT users, the domain type is NT. The domain name is the server that has the group account. Apparently the account type is local group. The script can be modified to add a specific user by giving the user name, domain name, user type, and account authority type on the command line or via other means. This type of scripting is convenient for adding full and view-only administrators, for which no specific privileges need to be given. To add custom administrators, a GUI interface is more appropriated to allow the end user to select the specific privileges. The SDK supplies a QAdmin utility written in Visual Basic. It can be used to do all the administrator related management. Detailed discussion of this utility is beyond the scope of this document.

12

Managing Licenses

License management is fully implemented in MFCOM. Calls are available for entering license strings, activating a license, and monitoring license usage. To fully understand the objects defined in MFCOM, we need to take a look at the license scheme used by MetaFrame XP. The concept of licenses in conventional terms is defined as license numbers in MFCOM. Some times they are also called license strings. These encrypted strings are the ones provided by Citrix to customers. These numbers are installed in a farm. For example, CTX**-35+++43F83-22222-KHHNS is a valid license number. A license number has an inherit count programmed in it. This is the count that it contributes to a license set. A license set is a classification of licenses. There are product and connection licenses, which may take count from many license numbers. Once a count is contributed to a set, the counts are aggregated in a set and used indifferently. In other words, it doesnt matter to a license set where a count is from. But license numbers can be enumerated from a license set. This allows the administrator to see which licenses have been installed. A license (number) must be activated for it to be used. A grace period is programmed into the license. This allows the license to be used without being activated during the grace period. This period is in days, which can be queried from the license number object. Licenses are pooled, which means all licenses are used together for all the connections to all the servers in the farm. Administrators, however, are allowed to assign certain number of licenses to a specific server. This is equivalent to reserving a certain number of licenses to a server. Assigned licenses therefore can not be used by connections to other servers and the assigned counts are deducted from the total pooled counts. Here is a simple script that lists all the connection licenses installed in a farm. A few properties of the licenses are also printed. 48

Scripting MetaFrame
<package> <job id="LicConn"> <comment> File: LicConn.wsf Description: List connection licenses and usage count. Requirements: WSH 5.5 or higher. Copyright (c) 2002 Citrix Systems, Inc. </comment> <runtime> <description> List connection licenses and count. </description> <example> CScript //nologo LicConn.wsf </example> </runtime> <reference object="MetaFrameCOM.MetaFrameFarm"/> <script language="VBScript"> Dim theFarm, aLicense Set theFarm = CreateObject("MetaFrameCOM.MetaFrameFarm") ' ' Initialize the farm object. ' theFarm.Initialize(MetaFrameWinFarmObject)

Since license objects are farm objects (directly under the farm object), the farm is the object from which licenses objects are enumerated.
For Each aLicense In theFarm.LicenseSets(MFLIcenseClassConnection) WScript.Echo "" WScript.Echo aLicense.LicenseID WScript.Echo aLicense.Name WScript.Echo "Pooled available: " & aLicense.PooledAvailable WScript.Echo "Total assigned: " & aLicense.TotalAssigned Next </script> </job> </package>

License sets are enumerated since they contain information about how licenses are used. Here we print out some of the properties of a license set. The pooled available count indicates how many licenses are currently available in the farm. Although this document is about scripting, wed like to briefly describe a nice license utility written entirely based on the calls available in MFCOM. This is a nice light weight license tool that can be modified for more specific purposes. Since this is an SDK example, the full source code is available. The utility LicTool, is found under the CS sub-directory of the MFCOM SDK install directory. It is written in C#. The compiled binary is found in the bin.net sub-directory. Executing lictool.exe, the following dialog is displayed.

49

Scripting MetaFrame

The 3 tabs show the license numbers, connection and product licenses. There are buttons for adding, removing, and activating licenses. Readers should take a look this tool because it has all the features you need to manage licenses and you dont need to start the CMC to get the license information. Since installing and activating licenses are essentially one-time events that occur mostly during farm deployment time, scripts that monitor the license usages in production environment are more useful. Since the licenses are identified using long strings that are in encrypted codes, typing them in is not convenient. This makes a GUI tool such as this one more useful for daily tasks than a script. If a deployment needs to install a large number of licenses, a web service type of application that automatically retrieve license numbers and activation codes may be more useful. But this type of development doesnt make much economic sense for end users. The time it takes to develop and debug this service is much longer than even manually entering all the license numbers and activating them. But this may be a good idea for system integrators. In fact a new tool mlicense available in Feature Release 3 offers this type of convenience.

13

DCOM Caveats

One of the most attractive features of MFCOM is its DCOM support, which allows a MFCOM client to run on a non-MetaFrame server just as if it were running on such a server. The SDK provides a utility that makes it much easier to use MFCOM in DCOM environment. Yet configuring DCOM is still one of the most problem prone operations in using MFCOM. We intend to answer all the possible issues that may arise in setting up DCOM in this section. Before going into detailed MFCOM related configuration descriptions, we need to briefly take a look at DCOM to establish some common terminologies for future discussions. DCOM stands for Distributed COM. It is based on Microsofts implementation of the Remote Procedure Call (RPC) technology. Because of this, knowledge about RPC and its use definitely help a great deal in understanding DCOM. Microsoft provides a GUI tool DCOMCNFG to allow administrators to configure COM objects. 50

Scripting MetaFrame To configure a regular Windows system18 (Windows NT 4.0, Windows 2000, or Windows XP) to run MFCOM, all an administrator has to do is run the following command:
mfreg ServerName

Here ServerName is the name of a remote MetaFrame XP server. The tool mfreg.exe is provided in the SDK and starting from FR3, it is also included in the MetaFrame XP installation image. MFREG contains a MFCOM type library. When it is executed, it creates all the necessary MFCOM registry entries. In addition, it creates the registry key HKLM\Software\Classes\AppID\
HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AppID\{ED62F4E0-63C2-11D4-94D800C04FB0F326}, under which a registry value RemoteServerName is created with ServerName

given on the command line to MFREG as its value. This is all COM needs to know that when a MFCOM object is to be created, the remote server is the one that serves the object creation request, instead of the local server. When COM delivers the request to the remote MetaFrame server, it basically processes the request as if the request were made by a process running on the same server. The effect of executing MFREG can be verified by running DCOMCNFG, which displays the following dialog.

18

Remote access to MFCOM using DCOM on other non-NT Windows systems is also possible. Windows 95, 98, ME should all be fine to use as a MFCOM client machine. DCOM configurations of these systems, however, are different. Also, we have not tested these systems and MFREG may not work on these systems.

51

Scripting MetaFrame Scroll down the list and click on MetaFrame COM Server and click on Properties. The following dialog will be displayed.

This shows that we have executed the command mfreg xpserver1 to tell DCOM that remote MFCOM server is xpserver1. Click on the Location tab, the following dialog is displayed.

52

Scripting MetaFrame

This clearly shows that xpserver1 is the server that actually hosts MFCOM. On a MetaFrame server that has MFCOM running locally, the Run application on this computer check-mark will be checked to indicate that requests to MFCOM will be handled locally. In this dialog, the word application refers to MFCOM, or the MetaFrame COM Server, as displayed in the title. Because DCOM is based on RPC, which subjects to restrictions imposed by the different security packages used, the MetaFrame server and the DCOM client must be on the same side of a firewall. They also need to trust at least one common domain and the users who are allowed to connect to the MetaFrame server must be from the common domains.

14

MFCOM Security

We continue the discussion around DCOMCNFG in this section. We emphasize, however, on a different aspect of configuring COM using DCOMCNFG. We focus on the security configuration in this section. The other reason for a separate section on security is that the material presented in this section applies to both the MFCOM server and the DCOM client. The content of the previous section mostly applies only to the DCOM client. If you are running DCOMCNFG on a DCOM client, continue from the previous dialog, click cancel you should be back to the original dialog that displays all the COM objects installed on the system. If DCOMCNFG is now running on a MetaFrame server which hosts MFCOM, the same dialog should be displayed. The second tab at the top of the dialog is Default Properties. Click on this tab to display the following dialog. 53

Scripting MetaFrame

There are two things that are very important to MFCOM. The first check-mark Enable Distributed COM on this computer must be checked. This tells us that DCOM is enabled on the system. The Default Impersonation Level is another property that directly affects the execution of MFCOM. It must be set to Impersonate. The impersonation level is an RPC setting and changing it may require a reboot since the RPC service cannot be easily stopped and restarted without a reboot. MFCOM uses impersonation to implement its security. By impersonating a caller, MFCOM performs accesses to IMA using the calling users identity. This ensures that security settings imposed to the user is effectively enforced. Note that the above settings have global effect. These settings affect all COM objects installed on the system that use the default impersonation level. If a COM object requires a different impersonation level to work effectively, making Impersonate the default setting may break that COM object. This rarely occurs, however, because of two reasons. First, most COM objects dont require security settings if they dont access system resources. For those that do require security, they use impersonation, which is almost the only effective way of implementing security in RPC. Interested readers may read more on RPC and COM security to find out the exact meanings of the different impersonation level settings. The other reason that many COM objects work regardless of the impersonation level setting is because they initialize their own impersonation level. COM objects written in C++ can call CoInitializeSecurity to set the security settings for the COM object. In fact MFCOM users also have this option to write MFCOM clients that set their own security settings. Since we are 54

Scripting MetaFrame focusing on the scripting access in this document, knowing this is important to the reader because most VB scripts are not that complicated and the default security settings are used for these scripts. Click on the Default Security tab, the following dialog should be displayed.

The Default Access Permissions configures how COM objects are accessed. Since MFCOM configures this by itself19, the access permissions are not used by MFCOM. The launch permissions must be specifically configured. This type of permission is outside of the scope of MFCOM since the permissions are checked by the COM runtime and the operating system before MFCOM is started. The launch permissions specify who are allowed or denied to create a COM object in a COM server. A very specific use of this security feature is to avert the denial of service attacks. If a user is granted launch permission, he is allowed to create COM objects, which may be created in a COM server running on a remote machine. The access permissions determine if certain properties or methods of the object can be used. Therefore, if an unauthorized user is given launch permissions, hell be able to create many COM objects and keep the COM server so busy in creating and destroying the COM objects created that he effectively paralyzes the server.

19

This is not true for the initial release of MetaFrame XP 1.0. For the MFCOM in this release, the access permissions still need to be configured using DCOMCNFG. Since most MetaFrame XP users should at least have the Service Pack 1 installed, which contains a version of MFCOM that configures its own access permissions, detailed discussion about this is not necessary here. The access permission configuration is similar to the launch permission configuration.

55

Scripting MetaFrame So, every MetaFrame XP administrator should configure this permission with care and allow only the trusted users to create MFCOM objects. To do this, click on the Edit Default button in the Default Launch Permissions category. The following dialog is displayed.

By examining this dialog, readers can notice that this permission consists basically a list of accounts that are assigned either an Allow Launch or Deny Launch permission. If an account needs to be configured, click the Add button to add the account to the list and assign one of the permissions from the Type of Access drop down list to the account. Both the allow and deny permissions are useful to effectively maintain a short and accurate list. For example, the Deny Launch permission can be used to specifically deny certain users from creating a COM object. When a user is configured with both an allow and deny permission, the deny permission takes precedence. So far the above settings have global effect since they are the default security settings for all COM objects. To configure the launch permission for MFCOM only, use the following steps. Run DCOMCNFG on the MetaFrame XP server, a list of COM applications are displayed. Find and click on MetaFrame DCOM Server n.0, where n is 2, 3, or 4, depending on the version of the Feature Release you have installed on your system. Then click on the Properties button at the bottom of the dialog. The following dialog should be displayed.

56

Scripting MetaFrame

Then click on the Security tab to display the following dialog.

By default, the default launch permissions, which we discussed earlier in this section, are also used as the launch permissions for MFCOM. To customize the launch permission for MFCOM, click on the radio button Use custom launch permissions and click on the Edit in this box. This will bring up a dialog similar to the one we saw before. After the configuration is saved, COM and the system will use this set of permissions to check object creation requests made to MFCOM. 57

Scripting MetaFrame Click on the Identity tab, the following dialog is displayed.

Note the accounts that can be used to launch MFCOM. The default is the local system account, which should remain as the selection for normal operations. For debugging purposes, a user who is a MetaFrame administrator may be selected as the account to run MFCOM. This should allow anyone to access MFCOM with the privileges assigned to the administrator. By all means this selection defeats the security, but it may be useful in debugging some security related problems.

15

Web Interface

A very powerful feature of MFCOM is its ability to support web access of MetaFrame farm. In fact, the Citrix Web Console (CWC) is written entirely based on MFCOM. A web interface allows an administrator to access and manage a Citrix server farm from anywhere a web browser is available. In this section we attempt to briefly describe the CWC. We will focus on the scripting code that retrieves data from IMA using MFCOM. Because its a web application, it contains a lot of HTML code that we will ignore for the most part. The full source code of CWC is available after it is installed. Interested readers may examine the source code as a complimentary source for the information in this section. To use MFCOM in an ASP page, a reference must be made to the file that contains the MFCOM type library. CWC accomplishes this by generating a line similar to the following in the global.asa file of the CWC root directory.
<!--METADATA TYPE="typelib" FILE="c:\winnt\system32\mfcom.exe"-->

58

Scripting MetaFrame This allows all the ASP code under the CWC root directory to use MFCOM. The file path can point to any file that contains the MFCOM type library. Specifically, if the CWC is installed on a nonMetaFrame server, it should point to the file mfreg.exe. This server on which CWC is running can be a web server. The default virtual root of the CWC is citrix/webconsole. So if your machine name is webserver1, the default starting page of the CWC is http://webserver1/citrix/webconsole. When you connect to this page, the following message is displayed.
WARNING! You are attempting to access the Citrix Web Console through a non-secure connection. The use of SSL is strongly recommended with this application, as PASSWORDS ARE SENT UNENCRYPTED with each page request.

Then there are two links to connect to the web console using either a secure connection or a non-secure connection. Click either link will result in a dialog asking for a user name and a password. The user given here should be a MetaFrame administrator. The CWC will make requests to MFCOM using this users identity. After a set of user credentials is entered, the initial page displays a summary of the farm information. On the left pane of the page there are links that allow you to see application, session, and server information. The ASP scripts that implement these links mostly are similar to the scripts described separately in the previous sections. Blended in the scripts are HTML tags for formatting the output. Except a few session control functions, most MFCOM features used in CWC are application, session, and server monitoring functions. Since the full source code of the CWC is available, users can take advantage of the CWC working code and build their own specialized web interface for controlling and monitoring a MetaFrame server farm20. This is probably easier said than done because the CWC is written in many ASP files and navigating through all the files is not an easy task. So, well try to offer a little help here. There are some regularities in the way the files are named. Generally, an ASP source file is named with two object names. The first part is often the name of the object to be expanded. The second part is the name of the object to be enumerated. For example, farmusers.asp contains code to display the users in the farm, although the session objects are mostly used. If the second part is not an object name, its often some kind of action word. For example, farminfo.asp contains code to display the initial farm summary information page. Its not hard to imagine that sessionctrl.asp contains code for controlling sessions. Although the file naming information may be helpful, for most developers who would like to take advantage of the ASP code in CWC, finding the code that performs a specific action visible from the web console may not be an easy task. Many times people see that some pages of the CWC may be useful and would like to find the code that displays those pages. Besides using brute force to walk through all the code, there are a few things we can try to make that process a little faster.

20

I guess copyright is not an issue for using the CWC code. Although the CWC is an official Citrix product, the real intellectual property is in MFCOM. So, if you copy some of the CWC code, just remember to include the Citrix copyright notice in your code and it should be fine. Of course I am not a legal expert, so dont quote me on this.

59

Scripting MetaFrame Assume that I am interested in the code that displays the client module information, something similar to the following.

This is done by clicking the Sessions link, then select the session to be viewed, click on the user name, which is a link, and the session information about this user is displayed. Click on the Client Modules link displays the modules of the client from which the connection was made. Now we need to find the ASP code that produces this page. We know that the objects used include session and user. But more importantly, the page displays information about client modules. So, lets first look at file names that contain the word client. Luckily enough21, there is a file named sessionclientmodules.asp. Open the file, it contains the following code.

21

Or bad choice of example. Actually the author simply picked the example by clicking through a few pages and found this one a good example. The fact that we could find the source file easily is just by coincidence. The author indeed walked through the entire code for writing this part of the document.

60

Scripting MetaFrame
<%Option Explicit%> <!-- #include file="Helpers.asp" --> <!-- #include file="Defs.asp" --> <html> <head> <meta http-equiv="Content-Type" content="text/html;"> <meta name="GENERATOR" content="Microsoft FrontPage 4.0" charset=<%=GetServerCharset()%>> <meta name="ProgId" content="FrontPage.Editor.Document"> <title></title> </head> <body bgcolor="#ffffff">

This is the HTML header.


<% On Error Resume Next Dim SessionName Dim ServerName Dim UserName Dim SessionIDString Dim SessionID UserName = Request.QueryString("UserName") ServerName = Request.QueryString("ServerName") SessionIDString = Request.QueryString("SessionID") SessionName = Request.QueryString("SessionName") SessionName = GetRealSessionNameFromQueryString(SessionName) SessionID = CLng(SessionIDString)

The caller of this page must supply the UserName, ServerName, SessionID, and SessionName as parameters, as shown by the use of Request.QueryString calls. This makes sense since we already know the values of all those variables. So, it appears weve found the right file.
Dim Dim Dim Dim UserSession ReturnURL ReturnText ReturnTarget

Set UserSession = Server.CreateObject("MetaFrameCOM.MetaFrameSession") If Err.Number <> 0 Then Err.Clear Call DisplayErrorMessage(s_ErrorTitle, s_Error_CreateObject_Session) Response.Write "<BR><BR>" Call GetReferringSessionsPageInfo(ReturnURL, ReturnText, ReturnTarget) If (Len(s_ToReturn) > 0 ) Then Response.Write "<a href=""" & ReturnURL & """ target=""" & ReturnTarget & """ >" & ReturnText & " " & s_ToReturn & "</a>" Else Response.Write "<a href=""" & ReturnURL & """ target=""" & ReturnTarget & """ >" & s_ReturnTo & " " & ReturnText & "</a>" End If Response.End End If

A session object is created. For now we dont know how it will be used. If theres an error in creating this object, some links will be created. It appears that those links will bring us back to the sessions page. Since we dont see any errors, this part of the code is not executed. For now, we dont need to care much about the error processing. But sometimes errors occur and tracing back using the error messages will help us find the page in which the error occurred. Well discuss more about that scenario later in this section. 61

Scripting MetaFrame
Call UserSession.Initialize(MetaFrameWinSrvObject,ServerName, SessionName, SessionID) If Err.Number <> 0 Then Err.Clear Call DisplayErrorMessage(s_ErrorTitle, s_Error_NoSessionInfoAvailable) Response.Write "<BR><BR>" Call GetReferringSessionsPageInfo(ReturnURL, ReturnText, ReturnTarget) If (Len(s_ToReturn) > 0 ) Then Response.Write "<a href=""" & ReturnURL & """ target=""" & ReturnTarget & """ >" & ReturnText & " " & s_ToReturn & "</a>" Else Response.Write "<a href=""" & ReturnURL & """ target=""" & ReturnTarget & """ >" & s_ReturnTo & " " & ReturnText & "</a>" End If Response.End End If

The session object is initialized using the given parameters. Error links will be created if this fails.
dim dim Dim Dim Dim Dim Dim Dim Dim dim Dim DisplayAscendIcon DisplayDescendIcon ModuleArray ModuleDateArray ModuleSizeArray ModuleVersionArray IndexArrayCM TempLocalArray TempLocalArray2 bool I

Response.Write "<form method=POST action=""SessionHandler.asp"" id=form1 name=form1>" Response.Write "<INPUT TYPE=HIDDEN NAME=""UserName"" value='" & UserSession.UserName & "' >" Response.Write "<INPUT TYPE=HIDDEN NAME=""SessionName"" value='" & FormatSessionNameForQueryString(UserSession.SessionName) & "' >" Response.Write "<INPUT TYPE=HIDDEN NAME=""ServerName"" value='" & UserSession.ServerName & "' >" Response.Write "<INPUT TYPE=HIDDEN NAME=""SessionID"" value='" & UserSession.SessionID & "' >" Response.Write "<INPUT TYPE=HIDDEN NAME=""ClientName"" value='" & UserSession.ClientName & "' >" Response.Write "<INPUT TYPE=HIDDEN NAME=""Caller"" value='Single' >" Response.Write "<INPUT TYPE=HIDDEN NAME=""SortOnCM"" value=""F000000003"" >" Response.Write "<INPUT TYPE=HIDDEN NAME=""AscendorDescendCM"" value=""F000000003"" >" Response.Write "<INPUT TYPE=HIDDEN NAME=""SessionCM"" value=""CM"" >" Response.Write "<INPUT TYPE=HIDDEN NAME=""LogoffButton"" value=""F0000003"" >" Response.Write "<INPUT TYPE=HIDDEN NAME=""DisconnectButton"" value=""F0000003"" >" Response.Write "<INPUT TYPE=HIDDEN NAME=""ShadowButton"" value=""F0000003"" >" Response.Write "<INPUT TYPE=HIDDEN NAME=""SendMessageButton"" value=""F0000002"" >" Response.Write "</form>" %> <TABLE BORDER="1" CELLSPACING="0" CELLPADDING="0"> <TR HEIGHT="22"> <TD bgcolor = "#BBBBBB" BACKGROUND="gradbkg.gif" HEIGHT="22"> &nbsp; <IMG border=0 align="center" src="client_modules_head.gif" alt="<% = s_ClientModules%>" > </TD> </TR> <TR BGCOLOR="6699CC"> <TD>

More HTML code is created. Looks like the four icons above the client modules listing are created by the above code. 62

Scripting MetaFrame
<% Response.Write "<P STYLE=""font-family: Arial,Helvetica; "">" Response.Write "<table border=""0"" cellpadding=""3"" cellspacing=""3"" >" Response.Write "<TR STYLE=""font-size: 8pt; text-align: left"" >" DisplayDescendIcon = "none" DisplayAscendIcon = "none" bool = SortingEnabledCM(UserName,ServerName,SessionIDString,SessionName,SessionID) if bool then if Session("AscendorDescendCM")= "Ascend" then DisplayAscendIcon = """" end if if Session("AscendorDescendCM")= "Descend" then DisplayDescendIcon = """" end if end if Response.Write "<th onClick=""SortOnModule()"" onmouseover=""this.style.cursor='hand';"" onmouseout =""this.style.cursor = 'default';""><font class=""swb"" color=""ffffff"" size=""3""><U>" & s_FileName & "</U></font><span id=""descIcon1"" style=""display:"& DisplayDescendIcon & """ ><img src=""desc.gif"" height=7 hspace=3 width=7 border=0 alt=""descending order""></span><span id=""ascIcon1"" style=""display:" & DisplayAscendIcon & """ ><img src=""asc.gif"" height=7 hspace=3 width=7 border=0 alt=""ascending order""></span>&nbsp;&nbsp;&nbsp;</th>" Response.Write "<th><font class=""swb"" color=""ffffff"" size=""3"">" & s_FileDate & "</font>&nbsp;&nbsp;&nbsp;</th>" DisplayDescendIcon = "none" DisplayAscendIcon = "none" Response.Write "<th ><font class=""swb"" color=""ffffff"" size=""3"">" & s_Size & "</font><span id=""descIcon2"" style=""display:"& DisplayDescendIcon & """ ><img src=""desc.gif"" height=7 hspace=3 width=7 border=0 alt=""descending order""></span><span id=""ascIcon2"" style=""display:" & DisplayAscendIcon & """ ><img src=""asc.gif"" height=7 hspace=3 width=7 border=0 alt=""ascending order""></span>&nbsp;&nbsp;&nbsp;</th>" Response.Write "<th><font class=""swb"" color=""ffffff"" size=""3"">" & s_Versions & "</font>&nbsp;&nbsp;&nbsp;</th>"

It appears that the above code creates the link for the File Name because if we click on it, it sorts the file names.
Dim SessionClientModules Dim ModuleDate Dim DateString Dim TimeString Dim DateTimeString Dim DateObject Dim ClientModule Dim Counter Dim ModuleCount Counter = 0 SessionClientModules = UserSession.ClientModules ModuleCount = Session("ModuleCount") ReDim ModuleArray(ModuleCount -1) ReDim ModuleDateArray(ModuleCount -1) ReDim ModuleSizeArray(ModuleCount -1) ReDim ModuleVersionArray(ModuleCount -1) ReDim IndexArrayCM(ModuleCount -1) ReDim TempLocalArray(ModuleCount -1) ReDim TempLocalArray2(ModuleCount -1) dim temp1 dim temp2 dim index1 dim ModuleName dim ModuleSize dim ModuleVersion dim ModuleDateTime dim IndexCount

63

Scripting MetaFrame It appears that we start to see the code that does the real job now. Here many variables are defined, including some arrays whose size is defined by the module count. Lets see how these variables are used.
if bool then ModuleArray = Session("ModuleArray") IndexArrayCM = Session("IndexArrayCM") ModuleDateArray = Session("ModuleDateArray") ModuleSizeArray = Session("ModuleSizeArray") ModuleVersionArray = Session("ModuleVersionArray") TempLocalArray = Session("ModuleArray") TempLocalArray2 = Session("ModuleArray") Call StringQuickSort(TempLocalArray, UBound(TempLocalArray),LBound(TempLocalArray)) dim templocal For Counter = LBound(IndexArrayCM) to UBound(IndexArrayCM) if Session("AscendorDescendCM")= "Descend" then IndexCount = IndexArrayCM(UBound(IndexArrayCM) -Counter) templocal = TempLocalArray(UBound(IndexArrayCM) -Counter) else IndexCount = IndexArrayCM(Counter) templocal = TempLocalArray(Counter) end if for temp1 = LBound(TempLocalArray2) to UBound(TempLocalArray2) if TempLocalArray2(temp1) = templocal then IndexCount = temp1 TempLocalArray2(temp1)= "notpresent" Exit For end if next ModuleName = ModuleArray (IndexCount) ModuleDateTime = ModuleDateArray (IndexCount) ModuleSize = ModuleSizeArray (IndexCount) ModuleVersion = ModuleVersionArray (IndexCount) Response.Write "<tr Style=""font-size: 8pt;"">" 'File Name Column Response.Write "<td align=""left"">" & ModuleName & "&nbsp;&nbsp;&nbsp;&nbsp;</td>" 'File Date Column Response.Write "<td align=""left"">" & ModuleDateTime & "&nbsp;&nbsp;&nbsp;&nbsp;</td>" 'Size Column Response.Write "<td align=""left"">" & ModuleSize & "&nbsp;&nbsp;&nbsp;&nbsp;</td>" 'Version Column Response.Write "<td align=""left"">" & ModuleVersion & "&nbsp;&nbsp;&nbsp;&nbsp;</td>" next else

OK, here we see the code that generates the list. The variable bool seems to indicate that the data stored in the arrays should be used when its value is TRUE. This indicates that the data is cached in the arrays and when the page is refreshed, the client module data is not read again. This should be fine since the client module data is static for the session.

64

Scripting MetaFrame
For Each ClientModule in SessionClientModules ReDim preserve ModuleArray(Counter) ReDim preserve ModuleDateArray(Counter) ReDim preserve ModuleSizeArray(Counter) ReDim preserve ModuleVersionArray(Counter) ReDim preserve IndexArrayCM(Counter) ReDim preserve TempLocalArray(Counter) ReDim preserve TempLocalArray2(Counter) Response.Write "<tr Style=""font-size: 8pt;"">" 'File Name Column Response.Write "<td align=""left"">" & ClientModule.FileName & "&nbsp;&nbsp;&nbsp;&nbsp;</td>" ModuleArray(Counter) = ClientModule.FileName 'File Date Column Set ModuleDate = ClientModule.FileLastModifiedDate DateString = GetMonthString(ModuleDate.Month) & " " & CStr(ModuleDate.Day) & ", " & CStr(ModuleDate.Year) TimeString = CStr(GetCorrectHour(ModuleDate.Hour)) & ":" & GetPaddedValue(CStr(ModuleDate.Minute)) & ":" & GetPaddedValue(CStr(ModuleDate.Second)) DateTimeString = DateString & " " & TimeString Response.Write "<td align=""left"">" & DateTimeString & "&nbsp;&nbsp;&nbsp;&nbsp;</td>" ModuleDateArray(Counter) = DateTimeString 'Size Column Response.Write "<td align=""left"">" & ClientModule.FileSize & "&nbsp;&nbsp;&nbsp;&nbsp;</td>" ModuleSizeArray(Counter) = ClientModule.FileSize 'Version Column Response.Write "<td align=""left"">" & ClientModule.FileVersion & "&nbsp;&nbsp;&nbsp;&nbsp;</td>" ModuleVersionArray(Counter) = ClientModule.FileVersion IndexArray(Counter) = Counter Counter = Counter + 1 Next Session("ModuleArray") = ModuleArray Session("ModuleDateArray") = ModuleDateArray Session("ModuleSizeArray") = ModuleSizeArray Session("ModuleVersionArray") = ModuleVersionArray Session("IndexArrayCM") = IndexArrayCM Session("ModuleCount") = Counter - 1 Session("UserNameCM") = UserName Session("ServerNameCM") = ServerName Session("SessionIDStringCM") = SessionIDString Session("SessionNameCM") = SessionName Session("SessionIDCM") = SessionID end if %>

This confirms the speculation that bool is used to indicate if cached data should be used. The else part apparently stores the data in the array in addition to displaying the data.

65

Scripting MetaFrame
</table> </tr> </td> </table> <SCRIPT LANGUAGE="JAVASCRIPT"> <! function SortOnModule(){ document.form1.SortOnCM.value = "SortOnModule"; document.form1.SessionCM.value = "CM"; if (descIcon1.style.display ==''){ document.form1.AscendorDescendCM.value = "Ascend"; } else { document.form1.AscendorDescendCM.value = "Descend"; } document.form1.submit() ; return true; } function OnClickLogoff() { if (confirm("<%= s_ConfirmLogoffCurrent %>")){ document.form1.LogoffButton.value = "Logoff"; document.form1.submit() ; return true; } Else { return false; }; } function OnClickDisconnect() { if (confirm("<%= s_ConfirmDiscCurrent %>")){ document.form1.DisconnectButton.value = "Disconnect"; document.form1.submit() ; return true; } Else { return false; }; } function OnClickShadow() { document.form1.Caller.value = "SessionClientModules.asp" document.form1.ShadowButton.value = "Shadow"; document.form1.submit() ; return true; } function OnClickSendMessage() { document.form1.SendMessageButton.value = "SendMessage"; document.form1.submit() ; return true; } //--> </SCRIPT> </body> </html>

Finally some Java functions are defined for the buttons displayed in the page. 66

Scripting MetaFrame Everything appears to be correct for the information we have seen in the web browser. To make sure this is really the source code for the page, insert some debug strings at some places. If upon refreshing the page you see your debug string, then this is definitely the code you are looking for. The next step is to further digest the code and cut-n-paste the portions that are useful to you and integrate them into your own ASP code. If everything were this easy then there would be far more users to create their own pages. Sometimes more techniques are needed to find the source code. One of the ways is to search some text. Since words like session, application, and/or user are used almost everywhere, searching these words is probably not very helpful. Instead, the uncommonly used words should be searched. Also, note that the strings displayed as data should not be searched. The left side pane is most probably not updated with the right side pane; therefore the search should be concentrated on the strings displayed in the right side pane. If you still cant find the string in the ASP source files, then you may want to try the defs.asp file located under the sub-directories like en, de, etc. These directories contain translated versions of the strings for different languages. Since very few of the strings are translated, sometimes it may be helpful if you install one of the non-English versions of the CWC. If you see that a translated string, search it in the defs.asp file. After finding the string, get the variable that contain the translated string and go back to the ASP source files and search the variable, this process should yield better results. The defs.asp file is particularly useful to locate error messages.

16

Gathering and Reporting Data

The Resource Manager (RM) is a MetaFrame XP module that collects and stores statistical data about the commonly used resources (application, server, session, etc.) in a server farm. The data is stored in databases which can be accessed using the database querying languages. Since MFCOM offers a scripting interface, some of the data stored in the RM database can also be retrieved using MFCOM-based scripts. Only a small portion of the information stored in the RM database can be alternatively retrieved this way. Generally, if what you need includes data about statistical data about session and application, then you may consider using MFCOM-based scripts as an alternative to RM. In this section, we discuss an embedded script that automatically collects data and present the data in a Microsoft Excel spreadsheet22. Included in the MFCOM SDKs scripts sub-directory there is a userload.xls file, which contains a macro listed as follow. To view the macro, open the file and click the ToolsMacroVisual Basic Editor menus. Since most users have macros disabled, to run the script, macro execution must be enabled in Excel. To enable macro, click the ToolsSecurityMacro menus.

22

Another great piece of work from Jeff Reed.

67

Scripting MetaFrame
Option Explicit Private Sub Workbook_Open() Dim Dim Dim Dim Dim Dim Dim Dim Dim Dim theFarm As MetaFrameFarm aSession As MetaFrameSession SessionState(10) As String intResult, intActiveSessions, intDisconnSessions As Integer intUniqueUsers, intSessions As Integer strTime As String timeNow As Date WB As Workbook WSAll, WSSessions, WSActive, WSDisconn, WSUsers As Worksheet intRowNum As Integer

' Get current date and time and store in "file name friendly" format. timeNow = Now() strTime = Month(timeNow) & "-" & Day(timeNow) & "-" & Year(timeNow) & "-" & _ Hour(timeNow) & "-" & Minute(timeNow)

Since this is an Excel macro, the Workbook_Open function is called every time the file is opened.
' Create MetaFrameFarm object Set theFarm = CreateObject("MetaFrameCOM.MetaFrameFarm") If Err.Number <> 0 Then MsgBox "Can't create MetaFrameFarm object" & "(" & Err.Number & ") " & _ Err.Description End End If ' Initialize the farm object. theFarm.Initialize (MetaFrameWinFarmObject) If Err.Number <> 0 Then MsgBox "Can't Initialize MetaFrameFarm object" & "(" & Err.Number & ") " & _ Err.Description End End If ' Are you Citrix Administrator? If theFarm.WinFarmObject.IsCitrixAdministrator = 0 Then MsgBox "You must be a Citrix administrator to run this application" End End If

Like in many other scripts, the farm object is created and initialized. We also check to make sure the user is a Citrix administrator.

68

Scripting MetaFrame
SessionState(0) = "Unknown" SessionState(1) = "Connected" SessionState(2) = "Active" SessionState(3) = "Connecting" SessionState(4) = "Shadowing" SessionState(5) = "Disconnected" SessionState(6) = "Idle" SessionState(7) = "Listening" SessionState(8) = "Resetting" SessionState(9) = "Down" SessionState(10) = "Init" ' We want 5 worksheets in a new workbook Application.SheetsInNewWorkbook = 5 Set WB = Application.Workbooks.Add ' Rename first Worksheet to All Set WSAll = WB.Worksheets(1) WSAll.Name = "All" ' Rename second worksheet to Sessions for displaying unique sessions only. Set WSSessions = WB.Worksheets(2) WSSessions.Name = "Sessions" ' Rename third worksheet to Active for displaying active sessions only. Set WSActive = WB.Worksheets(3) WSActive.Name = "Active" ' Rename fourth worksheet Disconn for displaying disconnected sessions only. Set WSDisconn = WB.Worksheets(4) WSDisconn.Name = "Disconn" ' Rename fifth worksheet Users for displaying distinct users only. Set WSUsers = WB.Worksheets(5) WSUsers.Name = "Users" 'Application.Visible = True ' Write Header WSAll.Cells(1, WSAll.Cells(1, WSAll.Cells(1, WSAll.Cells(1, WSAll.Cells(1, WSAll.Cells(1, WSAll.Cells(1, to Excel 1).Value 2).Value 3).Value 4).Value 5).Value 6).Value 7).Value Worksheet = "User" = "ServerName" = "SessionID" = "SessionName" = "ClientName" = "AppName" = "SessionState"

' Set current row to header row intRowNum = 1

Five worksheets are created and initialized.


For Each aSession In theFarm.Sessions If Err.Number <> 0 Then MsgBox "Can't enumerate sessions" & vbCrLf & "(" & Err.Number & ") " & _ Err.Description End End If intRowNum = intRowNum + 1 WSAll.Cells(intRowNum, 1).Value = aSession.UserName WSAll.Cells(intRowNum, 2).Value = aSession.ServerName WSAll.Cells(intRowNum, 3).Value = aSession.SessionID WSAll.Cells(intRowNum, 4).Value = aSession.SessionName WSAll.Cells(intRowNum, 5).Value = aSession.ClientName WSAll.Cells(intRowNum, 6).Value = aSession.AppName WSAll.Cells(intRowNum, 7).Value = SessionState(aSession.SessionState) Next

69

Scripting MetaFrame All the sessions in the farm are enumerated and the cells of the worksheets are initialized using the session properties.
' Sort worksheet WSAll.Columns("A:G").Sort WSAll.Columns("A"), xlAscending, WSAll.Columns("B"), , _ xlAscending, WSAll.Columns("C"), xlAscending, xlYes ' Autoformat to change column widths WSAll.Columns("A:G").AutoFit ' Change header to bold font WSAll.Range("A1:G1").Font.Bold = True ' Filter out duplicate records ' expression.AdvancedFilter(Action, CriteriaRange, CopyToRange, Unique) WSAll.Columns("A:G").AdvancedFilter xlFilterInPlace, , , True ' Copy WSAll to WSSessions WSAll.Columns("A:G").Copy (WSSessions.Cells(1, 1)) WSSessions.Columns("A:G").AutoFit ' Get number of Sessions intSessions = Application.CountA(WSSessions.Range("A:A")) - 1 ' Filter Sessions Worksheet for Active sessions only. WSSessions.Range("A1").AutoFilter 7, "Active" ' Copy WSSessions to WSActive WSSessions.Columns("A:G").Copy (WSActive.Cells(1, 1)) WSActive.Columns("A:G").AutoFit ' Get number of Active Sessions intActiveSessions = Application.CountA(WSActive.Range("A:A")) - 1 ' Show all data in WSSessions WSSessions.ShowAllData WSSessions.AutoFilterMode = False ' Filter Sessions Worksheet for Disconnected sessions only. WSSessions.Range("A1").AutoFilter 7, "Disconnected" ' Copy WSSessions to WSDisconn WSSessions.Columns("A:G").Copy (WSDisconn.Cells(1, 1)) WSDisconn.Columns("A:G").AutoFit ' Get number of Disconnected Sessions intDisconnSessions = Application.CountA(WSDisconn.Range("A:A")) - 1 ' Show all data in WSSessions WSSessions.ShowAllData WSSessions.AutoFilterMode = False ' Filter WSActive so only unique users are shown WSActive.Columns("A").AdvancedFilter xlFilterInPlace, WSActive.Columns("A"), , True ' Copy unique users from WSActive to WSUsers ' We only copy first row WSActive.Columns("A").Copy (WSUsers.Cells(1, 1)) WSUsers.Columns("A").AutoFit ' Get number of unique users intUniqueUsers = Application.CountA(WSUsers.Range("A:A")) - 1

70

Scripting MetaFrame
WB.SaveAs "UserLoad-" & theFarm.FarmName & "-" & strTime & ".xls" MsgBox theFarm.FarmName & vbCrLf & _ timeNow & vbCrLf & vbCrLf & _ "Total Sessions: " & vbTab & intSessions & vbCrLf & _ "Active: " & vbTab & vbTab & intActiveSessions & vbCrLf & _ "Disconnected: " & vbTab & intDisconnSessions & vbCrLf & _ "Users: " & vbTab & vbTab & intUniqueUsers End Sub

The rest is more code to properly fill out the cells using the values obtained from the result of the enumeration. As we can see that the portion of the code that uses MFCOM is very simple. If the reader has read all the previous sections there should be no difficult in understanding the MFCOM related code. The purpose of this example is to show that there are many ways to use MFCOM. If it is used creatively, you can create many tools that may surprise many of your peers. Data gathering and reporting about session information has been made very easy with this example. By simply opening the document, you immediately get a spreadsheet that contains the latest session statistics with the data nicely formatted. In addition, the data contains real time information, something not easily available from the RM database. Extending the idea, other object enabled documents can also be created in a similar way and presented in formats suitable for the embedding document. For example, pie charts or bar charts may be created in a PowerPoint presentation using real time data. If the script is created to run standalone, it can also be run regularly as a daemon process to periodically collect information. This gives administrators the power to gather information about the farm in many different ways, simply by creating scripts that collect only data that are of interest.

17

Work Around Bugs

Wed like to discuss another use of MFCOM unlike all the things we have discussed before. Since MFCOM is an administrative interface besides the CMC, problems or bugs exist in the CMC may be worked around using MFCOM. The topic itself is some what difficult to discuss in depth in a document focused on finding ideas for more creative use of MFCOM in the future. Nonetheless its our duty to find and introduce all the potential uses of MFCOM whenever and wherever it fits. Although working around bugs is not generally designed as a goal in any products, MFCOM as an SDK offers this capability, so it should be mentioned. So its clear that the purpose of this section is to raise the readers awareness of the one more potential use of MFCOM in managing a MetaFrame server farm. Bugs are unavoidable in any large and complex software. When a bug is discovered, it should be reported and depending on the nature of the problem, it may take time for a fix to become available. During the period when a fix is being developed, any work around is invaluable to the customers who must keep their servers running all the time.

71

Scripting MetaFrame Unlike the previous sections in which we present some examples that may be useful with or without modification in the readers environment, we cant describe some code that works around some future or probable bugs. These types of bugs dont exist, if we knew any problems, we would have fixed them before releasing the product. So, theres no way for us to describe some code here that will be useful. Instead, we can only take this opportunity to remind the readers that just in case there is a problem in the future, MFCOM should be included as one of the attempts in fixing or working around a bug. The use of MFCOM may or may not solve any specific future problem. But at least the reader should have one additional weapon in attacking the problems.

18

Scripting MetaFrame 1.8

Scripting MetaFrame 1.8 management functions is possible, although limited to certain operations. Since MetaFrame 1.8 does not have a COM interface, scripting is not possible by using VBScript. For some tasks, scripting is possible by using some SDK tools. In this section, well see how some application management functions may be scripted using the tools available from the Application Publishing SDK (APSDK). The APSDK is supported by MetaFrame 1.8. Except some slight differences among the calls supported by MetaFrame XP and MetaFrame 1.8, most APSDK calls work on both MetaFrame 1.8 and MetaFrame XP. One of the frequently performed tasks in managing a MetaFrame 1.8 server is publishing applications. This task can be greatly simplified by using the newapp tool. The full source code of newapp is included in the SDK. Well take a look how the tool may be used in more detail in the rest of the section. Some of the contents have been discussed in a previous section on managing applications. More detailed information about the newapp tool is presented here since for MetaFrame 1.8 users, APSDK is the only interface available for managing published applications. Newapp requires an .app file. The formal syntax of an .app file is described in a Microsoft Word file found in the same directory as newapp.cpp. A sample .app file is also included in the document. Both the source code and rigorous language definitions are long and tedious; we are not describing them in detail here in this section. The source code is merely a parser of the defined syntax. More adventurous readers may be interested in the syntax description in BNF (Backus Naur Form). However, numerous people who have used this tool have had difficulties in following the strange and intolerable grammars required by an .app file. By no means, however, these problems have anything to do with the SDK itself. The language is defined with simplicity as one of the most important priorities. As a result, the syntax checking is primitive23. Lets take a look at the sample .app file here.
# # This is a sample application publishing script file. # notepad.exe. #
23

It publishes

People may sense some influence of YACC and LEX, two of the very useful UNIX tools for such a task, in the definition of the language. The goal was to design a language that requires one-pass, no trace-back, and one token look ahead parsing.

72

Scripting MetaFrame Lines that start with the # are comments and ignored by the parser. In fact anything after a # are considered comments and ignored by the parser to the end of the line.
BeginApplication

BeginApplication is a keyword. It tells the parser that the application description begins. This may not be redundant but this makes the parser code a lot simpler and easier to implement (see the previous footnote). An alias for this keyword is Begin.
Program = "notepad.exe" # Must be in string becuase . is keyword. BrowserName = Notepad # The most popular application. Description = "The good old notepad" ProgramNeighborhoodFolder = "Put client folder here" Parent = "The parent folder name" FriendlyName = "My notepad" Directory = "c:\temp" # Must be in string because : and \. Encryption = "128 bit" WindowType = "800x600" WindowWidth = 800 WindowHeight = 600 WindowScale = 0 # We use absolute window size. WindowColor = 256 Sound = None Video = None DesktopIntegrate = StartMenu, Desktop. PNAttributes = "Sound minimum", "Video minimum". MetaFrameAttributes = "Hide titlebar". PublishingFlags = "No PN Enum", "No folder enum".

The body of application description contains lines that assign value to the properties of published applications. Not every property of a published application needs to be specifically assigned since some of them have default values. But its a good practice to specify everything. Properties that require a single value is defined by the following syntax:
Keyword = Value

The lists of keyword can be found in the file newapp.cpp in the structure KeywordTable, which is listed below. Many keywords have aliases, which are not listed in the table. This means different names may be used for the same keyword.
KeywordEntry_t KeywordTable[] = { {"Begin", {"End", {"BrowserName", {"ApplicationID", {APP_KEYWORD_DESC, {APP_KEYWORD_PNFOLDER, {"Folder", {APP_KEYWORD_PARENT, {APP_KEYWORD_FNNAME, {APP_KEYWORD_INITPROG, {APP_KEYWORD_WORKDIR, {APP_KEYWORD_ENCRYPT, {APP_KEYWORD_WINTYPE, {APP_KEYWORD_WINWIDTH, {APP_KEYWORD_WINHEIGHT,

KEYWORD_BEGIN}, KEYWORD_END}, KEYWORD_BROWSERNAME}, KEYWORD_BROWSERNAME}, KEYWORD_DESCRIPTION}, KEYWORD_PNFOLDER}, KEYWORD_PNFOLDER}, KEYWORD_PARENTFOLDER}, KEYWORD_FRIENDLYNAME}, KEYWORD_INITPROG}, KEYWORD_WORKDIR}, KEYWORD_ENCRYPTION}, KEYWORD_WINDOWTYPE}, KEYWORD_WINDOWWIDTH}, KEYWORD_WINDOWHEIGHT},

73

Scripting MetaFrame
{APP_KEYWORD_WINSCALE, {"WindowScale", {APP_KEYWORD_WINCOLOR, {APP_KEYWORD_SOUND, {APP_KEYWORD_DTFLAGS, {APP_KEYWORD_PNATTR, {APP_KEYWORD_MFATTR, {APP_KEYWORD_PUBFLAGS, {"Server", {"User", {APP_KEYWORD_ICONDATA, {APP_KEYWORD_ICONMASK, {APP_KEYWORD_ICONFILE, {APP_KEYWORD_ICONINDEX, {NULL, }; KEYWORD_WINDOWSCALE}, KEYWORD_WINDOWSCALE}, KEYWORD_WINDOWCOLOR}, KEYWORD_SOUNDTYPE}, KEYWORD_DESKTOPFLAGS}, KEYWORD_PNATTRIBUTES}, KEYWORD_MFATTRIBUTES}, KEYWORD_PUBLISHINGFLAGS}, KEYWORD_SERVER}, KEYWORD_USER}, KEYWORD_ICONDATA}, KEYWORD_ICONMASK}, KEYWORD_ICONFILE}, KEYWORD_ICONINDEX}, 0},

An equal sign must follow the keyword and a value must follow the equal sign. If the property takes one single value, the value itself completes the sentence. If the property takes more than one values, each value must be followed by either a comma or a period. The commas separate the values and the period ends the sentence. If the value is a number, the number can be directly used. If a value is a string, it must be enclosed using double quotes. Strings that dont contain spaces and reserved characters may not need to be in double quotes. For example, the values for DesktopIntegrate dont need double quotes. But the value for Directory needs to be quoted since it contains : and \, which are reserve red characters. The parser does a decent job in reporting syntax errors. Usually it is able to report the line and the column of the error. The syntax must be strictly followed. When an error is encountered, the parser exits. Now lets look at the two more complex and important properties of a published application, the server list and user list.
ServerList = SUN; MERCURY, c:\wtsrv\notepad.exe, f:\temp; VENUS, , g:\temp; EARTH; MARS, c:\wtsrv\system32\winword.exe; JUPITER, sol.exe; SATURN.

If you pay attention, you can probably write down the syntax for the server list yourself. Every entry ends with the semicolon and the last entry and the whole list ends with the period. For every entry, the items are separated using commas. There can be up to 3 items in an entry. The first item is the server name, which is required24. The second item is the command line and the third item is the working directory. Since most of these values are strings, they need to be quoted unless a single plain word is used. In most cases, these are server names. As a rule of thumb, unless you are absolutely sure, put everything is double quotes.

24

The server name should be all in uppercase. This is not apparent in this example. The code should convert the server name to upper case.

74

Scripting MetaFrame
UserList = Domain/Jones; Domain/Smith; Somewhere/Abe; Anywhere/Bob; Nowhere/Chuck; whois/David; Domain/Edward; Domain/Frank; Local/George; Local/Henry; Global/Ian; Global/John; Global/Kevin; Global/Larry; Global/Michael.

The user list follows a similar syntax as that of the server list. Every entry ends with a semicolon and the whole list ends with a period, which also ends the last entry. Each entry consists of a domain name, a forward slash, and a user or group name.
# # # IconFile = "notepad.ico" IconData = 0x5, 0x0, 0x8, 0x9, 0x0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc. IconMask = 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc.

Application icon setting can be specified using either a file or a bitmap defined by the IconData and IconMask properties. Very few people probably would like to specify an icon using the icon data and icon mask. Therefore these two properties are commented out in this example. The icon file can be any binary that contains the desired icon as a resource at index 0.
EndApplication

The last keyword ends the application description. Multiple application description blocks can appear in the same file with each block enclosed by the BeginApplication and EndApplication keywords. Multiple applications may be published this way using one single file. Once the file is prepared, it is given to newapp as the only command line argument. Newapp reads the file, checks for syntax errors, and uses the data to publish the application. There is no guarantee that an application can be published even if there is no syntax error found in the data file. The parser implemented in newapp checks only syntax errors. Semantic errors are checked by the API function FarmObject::CreateMFWinApp. The most common errors include invalid user and server list entries. No local users should be used in the user list since they will not be trusted by all the servers in the server list. For MetaFrame 1.8, the servers must trust all the users defined in the user list and they must allow remote registry operations by the user who is publishing the applications. Finally, dont forget that the user must be a MetaFrame administrator for MetaFrame XP and a system administrator on MetaFrame 1.8. The best way to create an .app file is not from scratch, but using a combination of all the existing tools and SDK utilities. The following scenario is the recommended way of using newapp to publish applications. 75

Scripting MetaFrame 1. Create a template application using the GUI tools available from MetaFrame (CMC or PAM) or an SDK utility (CINCOM, CSCMC, etc.). For example, an application foo is published this way. 2. Create an .app file for foo using the APSDK export utility. Run export \Applications\foo /ea foo.app. Please note that the applications distinguished name must be used for export. An applications distinguished name always starts with 25 \Applications and in most cases followed by the application name . 3. Copy foo.app to foo1.app, which can then be modified using a text editor for publishing another application foo1. Then run newapp foo1.app to get the application published. Steps 2 and 3 may be repeated to create more published applications. Using a template .app file saves time and allows common syntax errors be avoided.

19

Frequently Asked Questions

In this section we attempt to document problems frequently seen during the configuration and use of MFCOM. This list is by no means complete. But it should help new users to avoid some of the mistakes others have made and finding solutions to the problems seen before by the other MFCOM users. The following list is compiled based the questions asked by customers and Citrix engineers who have used MFCOM in developing applications. The questions are listed in no particular order and are not categorized. Questions have been rephrased and similar questions are consolidated. Problems that were later found out to be bugs are not included. The latest MetaFrame XP release should have all the known bugs fixed. Questions about new features are not listed. Some of the features requested have been implemented in Feature Release 3 and some others are being planned for the next MetaFrame XP release. Many questions have been asked not only by VB programmers but also C++ programmers. So I have included some questions and answers for C++ programmers here. Hopefully the answers will also be useful for VB programmers. 1. I keep getting the you are not a Citrix administrator message when I run some of the example programs
included in the CSSDK. I know I am an administrator since I can use the same account to logon to the Management Console.

The most probable cause of this problem is the DCOM setting for the impersonation level. The impersonation level must be set to Impersonate. See the previous sections on DCOM configuration for detailed steps on setting this parameter. If DCOM is used, both the MetaFrame XP server and the DCOM client must be set to Impersonate.
25

On MetaFrame XP applications are organized using folders. An applications distinguished name includes all the parent folders from the root. Refer to the MFCOM users manual for more detailed information about application folders and distinguished names. Although folders are not defined for MetaFrame 1.8, we still use the distinguished name concept to make the naming consistent with MetaFrame XP.

76

Scripting MetaFrame 2. I use the application object to access application data, but the calls dont work. Application data must be loaded by a call to the LoadData method of the application object. Before this call, very few calls will work. Some of the calls may appear to be working but returning invalid data and some other calls may fail. A SaveData call must also be made to save the changes you just have made to the application object. Until this call returns successfully, nothing is saved to the IMA persistent data store. For application objects that are returned from an enumeration, data may or may not be loaded for an application. So, its always a good practice to call LoadData, which takes a Boolean to indicate if the data should be read mandatory. Specifying FALSE to let MFCOM decide if the data needs to be updated. The only time when TRUE should be given to the LoadData call is that you know changes have been made to the IMA application object since you created the MFCOM application object. 3. The AppName property of the session object is returning an empty string. The AppName property doesnt always contain an application name. Apparently for a desktop session, there is no application associated with it and this property must return an empty string. For a seamless session in which more than one published applications sharing the session, the AppName property contains valid application names for each published application only if the session object has been returned in a session collection object from an enumeration. If the session object is created by the user, even for seamless sessions, the AppName property doesnt return an application name. 4. I am trying to publish an application but having a hard time to get it working. The SaveData and Validate
calls keep failing.

Publishing a new application is a tricky process because there are so many parameters to initialize. The IMA application publishing subsystem checks the consistency of those parameters and will not publish an application unless all the checks are passed. There are some trivial checks such as making sure the application name length is less than 256 characters. There are some other more complicated checks such as making sure that all the users specified in the applications user list have the logon permission to all the servers in the applications server list. Many times a publishing attempt fails because invalid user or server information is provided. Unfortunately the MFCOM call Validate and SaveData dont provide much help more than returning an error code. In many cases the error code is simply E_FAIL. To debug and fix problems in publishing an application, a simple one that doesnt change many default parameters should be published first. The gradually the other parameters can be changed one by one. The user and server list should be initialized using the same process, starting with one or two entries for each list and gradually add more entries to the lists. 5. Why does the AppName property of an application object returns only the display name of an application? The AppName property of an application object returns the display name of the published application. The Application Name property displayed in the application properties page of the 77

Scripting MetaFrame CMC is returned by the BrowserName property of the application object. This BrowserName is not available for MetaFrame XP releases prior to Feature Release 3. See the section on Application Names for more detailed explanation of all the application names. 6. I get permission denied error when running the MFCOM examples. This is one of the most common errors seen by the users of MFCOM, particularly users new to MFCOM and COM in general. Most likely the error is caused by the incorrect DCOM configuration. See the section titled DCOM Caveats on detailed information about configuring MFCOM. If all the steps have been correctly followed, the problem should go away. Otherwise, the most apparent reason is that the user is not a MetaFrame administrator. 7. Is it possible to use the credentials of another user to connect to MFCOM? The short answer is YES! But there are many restrictions. First of all, there is no easy way to do this in VBScript or any other language other than the powerful C++. The CSSDK version 2.3 has a new example IsAdmin that shows how this can be done. Before we dive into this example, lets make sure we understand the question first. We can rephrase the question as follow:
I logon to a Windows system as user user1 of domain D. But this user is not a MetaFrame administrator. Another user user2 from domain E is a MetaFrame administrator. While logging on as user1 of domain D, can I access MFCOM as user2 of domain E?

Now lets take a look at the readme.txt file in the IsAdmin example. This file contains important information about the restrictions in implementing a solution for the above problem. Since all the restrictions must be met, we attached the readme file below for a closer look.
MFCOM uses RPC based COM impersonation to implement secure access to its information. Basic knowledge about RPC/COM security is necessary to understand the concepts used in this note. When using MFCOM and particularly this example, the following items should be noted. 1. CoInitializeSecurity can be used to set the user credential used to access all the MFCOM calls. But since QueryInterface calls seem to use a different set of credentials, it may be impossible to use one set of user credential to call CoInitializeSecurity to access all MFCOM calls, including QueryInterface. Because of the above reason, the CoSetProxyBlanket call should be used to specifically indicate the context under which a particular call is made. Both the client system and the MetaFrame server should trust the same domain that has the user's account information stored. Impersonation doesn't work if one single MetaFrame server is used to run this code. The current logon user's credential is always used by COM when MFCOM impersonates the user. The code must run only on a system that connects to a MFCOM server remotely using DCOM.

2. 3. 4.

78

Scripting MetaFrame
5. The user must be a domain user that really exist in the domain. Otherwise any call may fail with an error code of 0x80070005 (E_ACCESSDENIED). This error code is returned by COM and in most cases the MFCOM call is not involved. There must NOT be a user of the same name defined on the remote MFCOM server, even if the password is different. When COM finds a local user of the same name, it'll use the local user's credentials to access MFCOM. When MFCOM impersonates the user, it's actually impersonating the local user. Only NTLM has been tested with this example. Kerberos should work in an Active Directory environment but that scenario has not been tested. Default principal name should be used, letting COM decide the principal name. The impersonation level should always be set to "IMPERSONATE". No other impersonation level will work with MFCOM.

6.

7. 8.

Although this is way beyond the scope of this document, which is on scripting, I want to at least introduce to the reader the IsAdmin example since we are already on this subject. The abbreviated source code is listed below.
{ SEC_WINNT_AUTH_IDENTITY_W AuthID;

The variable is singled out because it will be used later.


// // Initialize authentication information. // ZeroMemory(&AuthID, sizeof(AuthID)); AuthID.User = UserName; AuthID.UserLength = wcslen(UserName); AuthID.Domain = Domain; AuthID.DomainLength = wcslen(Domain); AuthID.Password = Password; AuthID.PasswordLength = wcslen(Password); AuthID.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;

The SEC_WINNT_AUTH_IDENTITY_W structure is initialized. Following this is other regular COM client code.
// // Set user identity used in accessing the object. // hr = CoSetProxyBlanket(pFarm, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, COLE_DEFAULT_PRINCIPAL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, &AuthID, EOAC_NONE); if (FAILED(hr)) { printf("%ls: failed to set pFarm proxy 0x%08X\n", argv[0], hr); goto Done; }

The CoSetProxyBlanket call is a COM library function. Since the first argument is a pointer to an object, we can guess that it sets the security and authentication on the particular object. The rest of the arguments take a lot more to discuss and interested readers should read the MSDN reference on this call. The sole purpose of introducing this example here is to show this call to the reader. 79

Scripting MetaFrame Continuing reading the code, careful readers will notice the following commented code with an interesting comment. Why is this left here? Was the author too sloppy to forget removing test code? Ill leave this as a quiz to the reader. A clue, the answer can be found in this document.
#if 0 // // Don't use QueryInterface to get the pointer to a WinFarm object. // hr = pFarm->QueryInterface(IID_IMetaFrameWinFarm4, (PVOID*)&pWinFarm); if (FAILED(hr)) { printf("%ls: failed to query interface 0x%08X\n", argv[0], hr); goto Done; } #endif

8. How do I add a server to a published application? To add a server to a published application, you need to have 3 pieces of information ready, the server name, the initial program, and the working directory. Once you know them, you can create a MetaFrameAppSrvBinding object and initialize it using the 3 items. Then you need to call AddServer method of the MetaFrameApplication object to add the server. Finally, dont forget the SaveData call. A detailed example is found in the section on application-server binding object. You can leave either or both of the initial program and working directory empty. An empty string indicates that the applications default initial program or working directory will be used. As a bonus, use the RemoveServer call with the server name to remove a server from the application. 9. Why some of the printer preference settings, such as paper width, dont work? There are different types of printers in a MetaFrame XP farm. There are network printers that have been imported from a remote network print server. There are also auto-created client printers, which must be specified using the CMC. The printer preference settings can only be queried for the auto-created printers. 10. What is the value of the session object property ClientHardwareID, can I use it to identify an ICA client? This property cannot be used to uniquely identify a client. Instead, use the ClientID property. There is a long history behind the ClientHardwareID property and it would take too much to explain it here. Basically it is a hashed value calculated based on some of the client characteristics. It is not uniquely calculated and for many clients, its value is 0. Since it cannot be used to identify a client, a new ClientID property is introduced in the Feature Release 3 version of MFCOM. This property is almost guaranteed to be unique for all the clients in a farm. The algorithm for generating this value is as follow: 1. If the client has a hardware ID, the hardware ID is returned. 2. The client name and the IP address is then used to generate the 32-bit ID. 80

Scripting MetaFrame 11. How do I specify the application icon in publishing an application? Since icons are bitmap data, it is not easily manipulated using VBScript. In MFCOM, there is no call available for the bitmap data to be set using the data in memory. Instead, the ReadIconFromFile method of the application object should be used to set the icon of an application. This call takes a file name and a resource index, which specifies the icon resource contained in the file. Use APSDK to initialize a published application icon with data in memory. 12. I know that the user who accesses MFCOM must be a MetaFrame administrator, but I would like to allow
some of my users to use some of the MFCOM calls, is this possible?

I assume that these users should not be configured to be MetaFrame administrators. Otherwise the delegated administration feature of MetaFrame XP can be used to allow these users to be partial administrators with limited capabilities. With the above assumption, additional C++ code needs to be written to allow a regular user to access certain MFCOM calls. The C++ code wraps the MFCOM calls in the context of a predefined administrator account. See the answer for a previous question on using the CoSetProxyBlanket call to make MFCOM calls in a different user context. Then you can implement your own security checking in your module to allow only those users with authorization to go through. Apparently this solution is subject to the same restrictions applicable to the previous answer. 13. The C# examples dont work. The C# examples require the Microsoft .NET Framework. If you are running your binaries from a remote shared drive, the examples dont work. I guess this is a .NET Framework security feature. If you copy the binaries to a local drive, the examples should run fine. 14. I get E_INVALIDARG returned for calls that take strings as arguments. All MFCOM calls take only strings of BSTR type. For VB and VBScript programmers, you dont need to worry about this type since the VB runtime handles the string type. For C++ programmers, you cannot pass a UNICODE string to a MFCOM without first allocating a BSTR using one of the COM library calls, e.g., SysAllocString(). An E_INVALIDARG error code will be returned if a string is given to a call where BSTR is required. 15. How do I get the session idle time? Two time values returned by the session properties can be used to calculate the session idle time. The CurrentTime and LastInputTime. Subtract the LastInputTime from CurrentTime, you get the idle time. 81

Scripting MetaFrame Please be aware that the CurrentTime is not updated. Rather it is the time when the session object is created. So to get the updated current time, a new session object should be created. The following is an example of the VBScript code to calculate session idle time.
aSession.CurrentTime(TRUE).Minute aSession.LastInputTime(TRUE).Minute

Here we assume that the session has been idle in the same hour. This is rarely the case, so at least the Hour property of the time object should be used. To be more precise, the HighPart and LowPart properties can be used. But the calculated result is in 100 nanosecond. So some conversion may be needed to convert the result into seconds or minutes. 16. The Count property for a collection object returns zero. This is not a bug but a flawed design. Prior to Feature Release 3, all collections contain zero objects before a get__NewEnum call (in C++) or a For Each statement (in VBScript) is used. This behavior has been changed in Feature Release 3. Now a collection returns the number of objects in it. For Feature Release 2 users, the work around is to call get__NewEnum or quit from a For Each statement and then access the Count property of the collection. 17. How do I know if my application is running in an ICA session? The session ID of an ICA session is always a non-zero value. The console session always has session ID 0.

82

Anda mungkin juga menyukai