background
Tuesday, 25 December 2012 15:22

How to query a Citrix Farm in C#

This basic tutorial explains how to create a simple C# application that can query a Citrix Farm.

In our example our Farm is a logical group of Citrix server with Citrix Presentation server 4.5. With this tutorial you will be able to retrieve information about, for example, the name of each server in the farm, the users that are using a published application, the client name (and client version) etc.

For this tutorial I assume that you have the following knowledge/requirements:

  1. Basic knowledge of C# and Visual Studio.
  2. Good knowledge of Citrix Presentation server 4.5.

 

Our example environment

With an example you can better understand the argument. Obviously I cannot refer to the production environment where I work. So for this tutorial I will use fictitious names. Don't worry if your environment is different from the example. If you read all this guide then you will be able to adapt what I have written to your environment.


Our farm consists of four Citrix servers: srvctx001, srvctx002, srvctx003 and srvctx004. All of them are 32 bit, with Windows server 2003 and Citrix Presentation server 4.5.
All of them are joined in the domain: domain.local. My development machine, called wsdev010 (joined in the domain: domain.local) is a Windows 7 32 bit with visual studio 2010 (but other versions should work fine). I use a domain account called usr003 on my machine. I'm an Administrator only on my computer wsdev010.

 

Let's start

First of all we have to download Citrix Presentation Server SDK (MPSSDK) 4.5 from this link: http://support.citrix.com/article/CTX114194. Then we have to install it on our development machine (in our example wsdev010) following below steps:

  1. Run MPSSDK.msi. Don't warry about the message "The system does not have a supported C++ compiler installed, you may not be able to compile the exaple program", go ahead.
  2. The installation program starts. The following tasks are completed during installation:
    1. Accepting the license agreement (if you agree).
    2. Specifying a destination folder for the MPSSDK; the default installation directory is %SystemDrive%\Program Files\Citrix\MPSSDK.
    3. Register MFCOM for remote access and insert the name of one of your server member of the Citrix Farm. For our example, we have insert srvctx001. You can change it later.
  3. Ensure that you have a DCOM called "MetaFrame COM Server".
    Go to Control Panel -> System and Security -> Administrative Tools -> Component Services (or run the command DCOMCNFG), go to "DCOM Config" and check if you can see the "MetaFrame COM Server" icon as illustrated into below image. If you cannot see that dcom then you have to run these commands (if you have Windows 7 and the UAC on then you have to use the option "Run as administrator"):
    1. "mpssdk home directory\utils\mfreg.exe /u".
    2. "mpssdk home directory\utils\mfreg.exe remote_citrix_server_name". For our example, we have insert srvctx001. If you want you can later change the remote server name in the "MetaFrame COM Server" (our Dcom) properties.

dcom

If you look in the installation folder of the MPSSDK you will find many examples written in different programming languages.

Note: The programs developed using MPSSDK functions will work only on servers running Presentation Server, or on a system that has MFCOM registered for remote access. However, in this tutorial I will explain everything you need step by step. A good guide about mfcom is THE ULTIMATE GUIDE TO MFCOM. If you want you can read it to know more about how mfcom works and other useful details.

 

Adjust permissions

The code interacts with the Citrix Farm with the credential of the user (or more properly the security context of the user) that runs the code, in our example usr003. So make sure that user has the correct permissions to query the Citrix Farm; in our example I asked to our Citrix Administrators to give to the users usr003 the rights to read the configurations of the citrix farm. In addition, that user must be a member of the "Distributed COM Users" group of the the server that you entered into the dcom "MetaFrame COM Server" of your development machine. So, I also asked to our Citrix Administrators to insert the users usr003 into the local group "Distributed COM Users" of the server srvctx001. Below you can find the most common exceptions regarding permissions:

  1. If the user is not a member of the "Distributed COM Users" group of the remote server (in our case srvctx001) you will receive this error: System.UnauthorizedAccessException: Retrieving the COM class factory for component with CLSID {ED62F4E2-63C2-11D4-94D8-00C04FB0F326} failed due to the following error: 80070005 Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED)).
  2. If the user cannot read the configuration of the Citrix Farm you will receive this error: System.UnauthorizedAccessException: Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))
  3. If the remote server (in our example srvctx001) is unavailable you will receive this error: System.Runtime.InteropServices.COMException (0x800706BA): Retrieving the COM class factory for component with CLSID {ED62F4E2-63C2-11D4-94D8-00C04FB0F326} failed due to the following error: 800706ba The RPC server is unavailable. (Exception from HRESULT: 0x800706BA).

So, before you write the code make sure you have the correct permissions. If you need to read data from the farm you must have rights to read. But if you write code that changes the properties of the Farm, then you will need write permission.

 

First example: print the name of all Citrix servers

First of all make sure your permissions are configured correctly (as described in the previously). Now, on your development machine (in our example wsdev010) open Visual Studio and create a new visual C# project (a console application) called, for example, listCtxServer. I used Framework 4.0 .NET, but you can use other versions. Now You have to add a new reference to the project, that allows us to call the classes and objects of the mfcom. So, you have to add this reference: "your mpssdk home directory\Bin.Net\mfcom.dll", as shown into below image:



The comments in the code allow you to see how the code works:

//The following four namespaces are added by default
//when you create a C# Console application (with .NET 4.0)
//if you use other version of .NET Framework namespaces will
//probably be different. Don't worry about that. We don't need all of them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//the below namespace allows us to use classes and objects of the mfcom.
//REMEMBER: you have to add mfcom.dll as reference
using MetaFrameCOM;


namespace listCtxServer
{
    class Program
    {
        static void Main(string[] args)
        {
            try //with a try catch block we can We can handle exceptions
            {
                //create and initialize the Citrix MetaFrame Farm object
                MetaFrameFarm Farm = new MetaFrameFarm();
                Farm.Initialize(MetaFrameObjectType.MetaFrameWinFarmObject);

                //loop trhow each server of the citrix farm
                foreach (IMetaFrameServer6 ctxServer in Farm.Servers)
                {
                    //print the server name
                    Console.WriteLine(ctxServer.ServerName);
                }

                //wait until the user presses any key on the keyboard
                Console.WriteLine("Press any key to continue . . .");
                Console.ReadKey(); 
            }
            catch (Exception ex)
            {
                //if an error has occured then print the error message
                Console.WriteLine("AN ERROR HAS OCCURED:\n");
                Console.WriteLine(ex.ToString());
                //wait until the user presses any key on the keyboard
                Console.WriteLine("Press any key to continue . . .");
                Console.ReadKey(); 
            }

        }
    }
}

As you can see the code is very very simple. But its simplicity allows you to understand well how to connect to the farm.

 

Second example: print the client version of the users

This basic example shows you how to retrieve the version of the user's client (Citrix client). The code also print the user name and the computer name of each user connected to the farm. So, open Visual Studio and create a new visual c# project (a console application) called, for example, listCtxClients. You have to add the mfcom.dll as new reference.

Unfortunately, when you query the farm you don't get the version of the client but you receive a number (Client Build) which you must then decode. This topic in the citrix forum helped me a lot.

Here the code:

//The following four namespaces are added by default
//when you create a C# Console application (with .NET 4.0)
//if you use other version of .NET Framework namespaces will 
//probably be different. Don't worry about that. We don't need all of them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//the below namespace allows us to use classes and objects of the mfcom.
//REMEMBER: first of all, you have to add mfcom.dll as reference
//then you can use MetaFrameCOM
using MetaFrameCOM;

namespace listCtxClients
{
    class Program
    {
        static void Main(string[] args)
        {
            try //with a try catch block we can We can handle exceptions
            {
                //into this variable will save the client version
                int clientVersion = 0;
                string MainVer = "";

                //create and initialize the Citrix MetaFrame Farm object
                MetaFrameFarm Farm = new MetaFrameFarm();
                Farm.Initialize(MetaFrameObjectType.MetaFrameWinFarmObject);

                //for every session of every user on all servers in the Citrix Farm
                foreach (IMetaFrameSession6 UserSession in Farm.Sessions)
                {

                    /*rarely the method UserSession.ClientBuild jumps to COM Exception:
                      "System.Runtime.InteropServices.COMException (0x80004005): Error HRESULT E_FAIL has been returned 
                      from a call to a COM component" ,This is because, probably, the session has been 
                      broken down/no longer exists or the client has some problems.
                      I don't want exit from the loop for this Exception. So I put here another try catch
                     */

                    try
                    {
                        //if we succeed to get ClientBuild than save it
                        clientVersion = UserSession.ClientBuild;
                    }
                    catch
                    {
                        //else, save the error code "-555"
                        //later, with that code, we will print the message:
                        //"Unknown build code (code is: number of client build)"
                        clientVersion = -555;
                    }

                    //get the client version
                    //this helped me to decode the client version: http://forums.citrix.com/message.jspa?messageID=633558
                    switch (clientVersion)
                    {

                        case 581:
                            MainVer = "4.00";
                            break;
                        case 686:
                            MainVer = "4.00";
                            break;
                        case 715:
                            MainVer = "4.20";
                            break;
                        case 727:
                            MainVer = "4.20";
                            break;
                        case 741:
                            MainVer = "4.20";
                            break;
                        case 779:
                            MainVer = "4.21";
                            break;
                        case 910:
                            MainVer = "6.00";
                            break;
                        case 963:
                            MainVer = "6.01";
                            break;
                        case 964:
                            MainVer = "6.01";
                            break;
                        case 967:
                            MainVer = "6.01";
                            break;
                        case 985:
                            MainVer = "6.20";
                            break;
                        case 1050:
                            MainVer = "6.30";
                            break;
                        case 1051:
                            MainVer = "6.31";
                            break;
                        case 17534:
                            MainVer = "7.00";
                            break;
                        case 20497:
                            MainVer = "7.01";
                            break;
                        case 21845:
                            MainVer = "7.10";
                            break;
                        case 22650:
                            MainVer = "7.10";
                            break;
                        case 24737:
                            MainVer = "8.00";
                            break;
                        case 29670:
                            MainVer = "8.10";
                            break;
                        case 32649:
                            MainVer = "9.00";
                            break;
                        case 36280:
                            MainVer = "9.10";
                            break;
                        case 39151:
                            MainVer = "9.15";
                            break;
                        case 44376:
                            MainVer = "9.20";
                            break;
                        case 50211:
                            MainVer = "9.23";
                            break;
                        case 45418:
                            MainVer = "10.00";
                            break;
                        case 49686:
                            MainVer = "10.00";
                            break;
                        case 52110:
                            MainVer = "10.00";
                            break;
                        case 55836:
                            MainVer = "10.100";
                            break;
                        case 58643:
                            MainVer = "10.150";
                            break;
                        case 2650:
                            MainVer = "10.20";
                            break;
                        case 5284:
                            MainVer = "11.00 (beta)";
                            break;
                        case 5357:
                            MainVer = "11.00";
                            break;
                        case 27334:
                            MainVer = "11.1 (Linux)";
                            break;
                        case 31560:
                            MainVer = "11.2.0";
                            break;
                        case 3:
                            MainVer = "11.2.2";
                            break;
                        case 6410:
                            MainVer = "12.0.0";
                            break;
                        case 6:
                            MainVer = "12.0.3";
                            break;
                        case 13:
                            MainVer = "13.0.0 (beta)";
                            break;
                        case -555:
                            MainVer = "Error to get client version";
                            break;
                        default:
                            //Since I didn't want to decode them all (they are are so many)
                            //I use a default message for those not encoded:
                            MainVer = "Unknown build code (code is: " + UserSession.ClientBuild.ToString() + ")";
                            break;
                    }

                    //print the client version
                    Console.WriteLine("User: {0}, Client Name: {1}, Client Version: {2}", UserSession.UserName, UserSession.ClientName, MainVer);
                }

                //wait until the user presses any key on the keyboard
                Console.WriteLine("Press any key to continue . . .");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                //if an error has occured then print the error message
                Console.WriteLine("AN ERROR HAS OCCURED:\n");
                Console.WriteLine(ex.ToString());
                //wait until the user presses any key on the keyboard
                Console.WriteLine("Press any key to continue . . .");
                Console.ReadKey();
            }
        }
    }
}

 

Third example: print all applications

This basic example shows you how to retrieve a list of all Published Applications with the users and groups assigned to them. So, open Visual Studio and create a new visual c# project (a console application) called, for example, listCtxApps. As usual You have to add the mfcom.dll as new reference.

Into the code is present an important method: LoadData(1), without it you cannot get some information from the application, like the application name, some informations about the users and groups assigned to them etc.

Here the code:

//The following four namespaces are added by default
//when you create a C# Console application (with .NET 4.0)
//if you use other version of .NET Framework namespaces will 
//probably be different. Don't worry about that. We don't need all of them.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

//the below namespace allows us to use classes and objects of the mfcom.
//REMEMBER: first of all, you have to add mfcom.dll as reference
//then you can use MetaFrameCOM
using MetaFrameCOM;

namespace listCtxApps
{
    class Program
    {
        static void Main(string[] args)
        {
            try //with a try catch block we can We can handle exceptions
            {

                //create and initialize the Citrix MetaFrame Farm object
                MetaFrameFarm Farm = new MetaFrameFarm();
                Farm.Initialize(MetaFrameObjectType.MetaFrameWinFarmObject);

                foreach (IMetaFrameApplication app in Farm.Applications)
                {
                    //Application's data must be loaded first before you can use it...
                    app.LoadData(1); //however this option will slow down a little bit all the code

                    //Now (after loaded Application's data) you cant print all application's properties
                    
                    Console.WriteLine("Application Name = {0}",app.AppName); //print the application name
                    
                    //print all the users who are enabled to the current application
                    foreach (IMetaFrameUser user in app.Users)
                    {
                        Console.WriteLine("   |-User = {0}", user.UserName);
                    }

                    //print all the groups that are enabled to the current application
                    foreach (IMetaFrameGroup6 group in app.Groups)
                    {
                        Console.WriteLine("   |-Group = {0}", group.GroupName);
                    }

                    //separate each application by a blank line
                    Console.WriteLine();
                }

                //wait until the user presses any key on the keyboard
                Console.WriteLine("Press any key to continue . . .");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                //if an error has occured then print the error message
                Console.WriteLine("AN ERROR HAS OCCURED:\n");
                Console.WriteLine(ex.ToString());
                //wait until the user presses any key on the keyboard
                Console.WriteLine("Press any key to continue . . .");
                Console.ReadKey();
            }
        }
    }
}

 

Multiple Farms

Suppose you have more than one Farm, for example a test Farm and a production Farm. The right thing to do is try our code on the test Farm first and then on the production environment (especially if We develop code that changes the Farm properties). To do this you just need to change the remote server of the dcom "MetaFrame COM Server" (installed on your development PC, in our example wsdev010) by inserting a server that belongs to the test Farm. The below image shows how to do it:

remote server

 

Conclusion

What I showed you is just a taste of what you can do with the MFCOM. There are many examples available on the Internet (just search on Google) that show you how to use MFCOM. In fact you can even change the properties of the Farm. MFCOM provides methods and properties that are common to almost all programming languages. In this way You can look for examples in other programming languages (There are many examples on the Internet written in VB Script) and then adapt them to what you prefer.

You must keep in mind that during the testing of your applications, unfortunately, You may receive many COM Exceptions, like those I wrote on page 3, the one described on page 5 and many others that you will receive. To understand the reason You must be extremely patient, You have to search on Google or on the Citrix forum. Do not forget to always use the Try-Catch blocks properly.

Well, That's all. I hope you enjoyed this tutorial. Bye.

Alan Pipitone

I am the owner.

Website: www.alan-pipitone.com
Latest from Alan Pipitone
  • Popular
Use Kinect with Mac OSX

In this article I will show how you can use Microsoft Kinect for xBox ... Read more

How to query a Citrix Farm in C#

This basic tutorial explains how to create a simple C# application tha... Read more

« December 2017 »
Mon Tue Wed Thu Fri Sat Sun
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31