Perusing the ICO Documentation , there are a bunch of diagrams and techie-talk about “embedding the ICA Client Object into a container” for proper usage. Some of the documentation goes on to define such containers as Internet Explorer, Netscape Navigator, Delphi application, MFC applications, etc. (holy dated material!?!). But who wants to write some web page with embedded scripting to launch a simple session? Furthermore, who wants to try and debug that? There has to be an easier/friendlier way … right?

In an attempt to go from “0 to … Hey, I think I know what I’m doing now,” I want to provide a step-by-step “getting started” tutorial for consuming ICO and the ICA Client Simulation APIs. I’ll do so, by leverage one of my favorite, simple, and commonly used “containers” … a .Net Console Application. I’ll also provide insight on some of the pitfalls and oddities that occur when trying consuming ICO in such a manner.

Downloadable Examples of what’s covered below

Getting Started

One of the best parts of the Ica Client Object (ICO) SDK is that is ships right along side your favorite version of the Citrix Online plug-in (formerly called ICA client). That is, there is no need to download any external binaries to reference as they all reside on your system, post plug-in install. So, with plug-in installed on your local development machine, start off by creating a Console Application in your favorite flavor of Visual Studio (these example uses VS2008).

Add a reference, within your project to the following file %ProgramFiles%\Citrix\ICA Client\Wfica.ocx (or on x64 development machines %ProgramFiles(x86)%). Doing so, will generate an interop reference for the ICO ActiveX control within your project. Pretty simple and friendly so far … right?

Be Careful

At this time, I think it’s important to point out the Citrix Online plug-in is a 32-bit compiled application. As such, when the Citrix Online plug-in is installed the ICO ActiveX (COM) control is registered on the system. The registration occurs within the 32-bit hive of the registry (which is taken for granted until you’re testing/developing on a 64-bit machine). So, in order to have your console application run properly on both x86 and x64 architectures, you must specifically compile it to target an “x86” processor architecture within Visual Studio. Compiling “AnyCPU” will cause your application to look for the ICO COM class registration in the native registry hive on a 64-bit machine (not the Wow6432Node where it was registered). Subsequently, at runtime, you will see COM exceptions/errors thrown as seen (in the thubmnail) below on my 64-bit development machine.

Make a quick change to your target platform as seen here within Visual Studio:

Now, that we’ve got that small tidbit squared away. Let’s get on to the launch …

Launching Your First Session

The main class that’s used to launch and interact with an ICA session is the ICAClientClass. Ensure that you add the appropriate using statement to your program.cs file in order to access the namespace of the ICAClientClass class (yeah … that’s just weird sounding).

The various methods and properties of the class can be found outlined in the ICA Client Object Guide (here). For simplicity sake (and to build on this example further) the following example leverages only a very few properties and shows how we can launch our first application session, to Notepad, hosted on a XenApp server (read the code comments for extended detail).

Handy Hint

In the example, I’m targeting a server that has a Notepad published for my user, however you can easily launch an ICA desktop session by removing InitialApplication=”#Notepad”; … and setting the Application=””;

Simple ICO Launch – Program.cs
<span class="code-comment">/// &lt;summary&gt;
</span><span class="code-comment">/// This program demo's the basic of launching and ICO session from within an application.
</span><span class="code-comment">/// &lt;/summary&gt;
</span>class Program
{
    <span class="code-keyword">static</span> void Main(string[] args)
    {
        ICAClientClass ica = <span class="code-keyword">new</span> ICAClientClass();

        <span class="code-comment">// Launch published Notepad <span class="code-keyword">if</span> you comment <span class="code-keyword">this</span> line, and uncommented
</span>        <span class="code-comment">// the one above you will launch a desktop session
</span>        <span class="code-comment">// ica.Application = "";
</span>        ica.InitialProgram = <span class="code-quote">"#Notepad"</span>;

        <span class="code-comment">// Launch a <span class="code-keyword">new</span> session
</span>        ica.Launch = <span class="code-keyword">true</span>;

        <span class="code-comment">// Set Server address
</span>        ica.Address = <span class="code-quote">"10.8.X.X"</span>;

        <span class="code-comment">// No Password property is exposed (<span class="code-keyword">for</span> security)
</span>        <span class="code-comment">// but can explicitly specify it by using the ICA File <span class="code-quote">"password"</span> property
</span>        ica.Username = <span class="code-quote">"johncat"</span>;
        ica.Domain = <span class="code-quote">"xxxx"</span>;
        ica.SetProp(<span class="code-quote">"Password"</span>, <span class="code-quote">"xxxx"</span>);

        <span class="code-comment">// Let's have a <span class="code-quote">"pretty"</span> session
</span>        ica.DesiredColor = ICAColorDepth.Color24Bit;

        <span class="code-comment">// Reseach the output mode you want, depending on what your trying
</span>        <span class="code-comment">// to attempt to automate. The <span class="code-quote">"Client Simulation APIs"</span> are only available under certain modes
</span>        <span class="code-comment">// (i.e. things like sending keys to the session, enumerating windows, etc.)
</span>        ica.OutputMode = OutputMode.OutputModeNormal;

        <span class="code-comment">// Height and Width
</span>        ica.DesiredHRes = 1024;
        ica.DesiredVRes = 786;

        <span class="code-comment">// Launch/Connect to the session
</span>        ica.Connect();

        Console.WriteLine(<span class="code-quote">"\nPress any key to log off"</span>);
        Console.Read();

        <span class="code-comment">// Logoff our session
</span>        Console.WriteLine(<span class="code-quote">"Logging off Session"</span>);
        ica.Logoff();
    }
}

If you’ve made it this far, and your session is launching, your probably feeling pretty good. However, let’s delay the pre-emptive celebration, and try our hand at more advanced functionality …

Handling Events

With our first session launch behind us, let’s start asking the fun questions. “Well, how does my automation/script know when I’ve successfully logged on? And Can I figure out when Notepad is available in the session?” Questions like these start to unveil the power and granularity that the object can provide.

Let’s add some event handlers to our application so we can try and figure out when the client has actually logged on successfully. In the following example, you can see that I’ve added an explicit handler to the application to handle the OnLogon event from the Ica Client Object. Within the main thread of the program I use an AutoResetEvent as a control mechanism to block program execution until our session is logged on. You can see in my event handler, I trigger the AutoResetEvent explicitly, so that the program execution will continue within our “Main()” method as soon as ICO tells us the session has logged on. If we don’t receive the OnLogon event within a few minutes we’ll time out waiting and attempt to log off the session anyways.

Knowledge Drop

If you’re using any version of the client, earlier than 11.2, and experimenting with this example, you’ll never receive the OnLogon event. Previous clients had to explicitly start a Windows application pump to handle various ICO events from the ICO object. This was not intuitive by any means, and many users may have thought ICO events and/or the client simulation APIs were even broken. However, this is not the case, and I’ve submitted a code example (along with the ones outlined in this blog) that illustrates a work around for previous clients. Talking to some of the developers, there were design changes introduced to the 11.2 Ica Client Object that switched ICO use the Common Connection Manager (CCM) and COM based events. This architectural change freed the ICO object from having to have an explicit windows message pump, and allows events to be raised/handled within the main thread of our single-threaded console application. A lot of info, but let’s just say “thank you fine developers for making life easier” and be on our way …

Simple ICO Launch With Events – Program.cs
<span class="code-comment">/// &lt;summary&gt;
</span><span class="code-comment">/// This program demo's the basic of launching and ICO session from within an application.
</span><span class="code-comment">/// &lt;/summary&gt;
</span>class Program
{
    <span class="code-keyword">public</span> <span class="code-keyword">static</span> AutoResetEvent onLogonResetEvent = <span class="code-keyword">null</span>;

    <span class="code-keyword">static</span> void Main(string[] args)
    {
        ICAClientClass ica = <span class="code-keyword">new</span> ICAClientClass();
        onLogonResetEvent = <span class="code-keyword">new</span> AutoResetEvent(<span class="code-keyword">false</span>);

        <span class="code-comment">// Launch published Notepad <span class="code-keyword">if</span> you comment <span class="code-keyword">this</span> line, and uncommented
</span>        <span class="code-comment">// the one above you will launch a desktop session
</span>        <span class="code-comment">// ica.Application = "";
</span>        ica.InitialProgram = <span class="code-quote">"#Notepad"</span>;

        <span class="code-comment">// Launch a <span class="code-keyword">new</span> session
</span>        ica.Launch = <span class="code-keyword">true</span>;

        <span class="code-comment">// Set Server address
</span>        ica.Address = <span class="code-quote">"10.8.X.X"</span>;

        <span class="code-comment">// No Password property is exposed (<span class="code-keyword">for</span> security)
</span>        <span class="code-comment">// but can explicitly specify it by using the ICA File <span class="code-quote">"password"</span> property
</span>        ica.Username = <span class="code-quote">"johncat"</span>;
        ica.Domain = <span class="code-quote">"xxxx"</span>;
        ica.SetProp(<span class="code-quote">"Password"</span>, <span class="code-quote">"xxxx"</span>);

        <span class="code-comment">// Let's have a <span class="code-quote">"pretty"</span> session
</span>        ica.DesiredColor = ICAColorDepth.Color24Bit;

        <span class="code-comment">// Reseach the output mode you want, depending on what your trying
</span>        <span class="code-comment">// to attempt to automate. The <span class="code-quote">"Client Simulation APIs"</span> are only available under certain modes
</span>        <span class="code-comment">// (i.e. things like sending keys to the session, enumerating windows, etc.)
</span>        ica.OutputMode = OutputMode.OutputModeNormal;

        <span class="code-comment">// Height and Width
</span>        ica.DesiredHRes = 1024;
        ica.DesiredVRes = 786;

        <span class="code-comment">// Register <span class="code-keyword">for</span> the OnLogon event
</span>        ica.OnLogon += <span class="code-keyword">new</span> _IICAClientEvents_OnLogonEventHandler(ica_OnLogon);

        <span class="code-comment">// Launch/Connect to the session
</span>        ica.Connect();

        <span class="code-keyword">if</span>(onLogonResetEvent.WaitOne(<span class="code-keyword">new</span> TimeSpan(0,2,0)))
            Console.WriteLine(<span class="code-quote">"Session Logged on sucessfully! And OnLogon Event was Fired!"</span>);
        <span class="code-keyword">else</span>
            Console.WriteLine(<span class="code-quote">"OnLogon event was NOT Fired! Logging off ..."</span>);

        <span class="code-comment">// Do we have access to the client simulation APIs?
</span>        <span class="code-keyword">if</span> (ica.Session == <span class="code-keyword">null</span>)
            Console.WriteLine(<span class="code-quote">"ICA.Session object is NULL! :("</span>);

        Console.WriteLine(<span class="code-quote">"\nPress any key to log off"</span>);
        Console.Read();

        <span class="code-comment">// Logoff our session
</span>        Console.WriteLine(<span class="code-quote">"Logging off Session"</span>);
        ica.Logoff();
    }

    <span class="code-comment">/// &lt;summary&gt;
</span>    <span class="code-comment">/// OnLogon event handler
</span>    <span class="code-comment">/// &lt;/summary&gt;
</span>    <span class="code-keyword">static</span> void ica_OnLogon()
    {
        Console.WriteLine(<span class="code-quote">"OnLogon event was Handled!"</span>);
        onLogonResetEvent.Set();
    }
}

Great! Now we know when our session is logged on. What else can we do? Well, how about we get our feet wet with something fun! Let’s shift gears and try a stab at interacting with our session by consuming, what are termed, the “Client Simulation APIs” within the Ica Client Object.

Be Careful

Before attempting to consume the Client Simulation APIs (or more directly, the “Session interface object”) you must be informed about another change that was introduced within the 11.2 client. There was added security to restrict access to the session object. Access is controlled via the client’s registry. The following HKLM\Software\Citrix\ICA Client\CCM key must be created (it’s not there by default). Under that key a DWORD value of 0 or 1 disables and enables access to the Session object. So make sure this is set properly, or you might risk destroying your monitor out of frustration, after launching multiple sessions that return a NULL Session object. Wait what was the definition of insanity again?

KB Article 123616 ICO Changes in 11.2 XenApp plug-in

The following code example illustrates the use of Client Simulation Keyboard APIs to type within our Notepad application session, take a screen shot and save it to the %temp% directory, and finally enumerate and output the top level windows within the session. Finally, you can take this opportunity to print out a crisp copy of your “screenshoted” session, hand it to your boss (or hang in your office/room), and declare that … indeed … “Greatness has been achieved!”

Simple ICO Launch With Events & Client Simulation – Program.cs
<span class="code-comment">/// &lt;summary&gt;
</span><span class="code-comment">/// This program demo's the basic of launching and ICO session from within an application.
</span><span class="code-comment">/// &lt;/summary&gt;
</span>class Program
{
    <span class="code-comment">/// &lt;summary&gt;
</span>    <span class="code-comment">/// Auto Reset Event used to block execution until we receive the OnLogon event
</span>    <span class="code-comment">/// &lt;/summary&gt;
</span>    <span class="code-keyword">public</span> <span class="code-keyword">static</span> AutoResetEvent onLogonResetEvent = <span class="code-keyword">null</span>;

    <span class="code-keyword">static</span> void Main(string[] args)
    {
        ICAClientClass ica = <span class="code-keyword">new</span> ICAClientClass();
        onLogonResetEvent = <span class="code-keyword">new</span> AutoResetEvent(<span class="code-keyword">false</span>);

        <span class="code-comment">// Launch published Notepad <span class="code-keyword">if</span> you comment <span class="code-keyword">this</span> line, and uncommented
</span>        <span class="code-comment">// the one above you will launch a desktop session
</span>        <span class="code-comment">// ica.Application = "";
</span>        ica.InitialProgram = <span class="code-quote">"#Notepad"</span>;

        <span class="code-comment">// Launch a <span class="code-keyword">new</span> session
</span>        ica.Launch = <span class="code-keyword">true</span>;

        <span class="code-comment">// Set Server address
</span>        ica.Address = <span class="code-quote">"10.8.X.X"</span>;

        <span class="code-comment">// No Password property is exposed (<span class="code-keyword">for</span> security)
</span>        <span class="code-comment">// but can explicitly specify it by using the ICA File <span class="code-quote">"password"</span> property
</span>        ica.Username = <span class="code-quote">"johncat"</span>;
        ica.Domain = <span class="code-quote">"xxxx"</span>;
        ica.SetProp(<span class="code-quote">"Password"</span>, <span class="code-quote">"xxxx"</span>);

        <span class="code-comment">// Let's have a <span class="code-quote">"pretty"</span> session
</span>        ica.DesiredColor = ICAColorDepth.Color24Bit;

        <span class="code-comment">// Reseach the output mode you want, depending on what your trying
</span>        <span class="code-comment">// to attempt to automate. The <span class="code-quote">"Client Simulation APIs"</span> are only available under certain modes
</span>        <span class="code-comment">// (i.e. things like sending keys to the session, enumerating windows, etc.)
</span>        ica.OutputMode = OutputMode.OutputModeNormal;

        <span class="code-comment">// Height and Width
</span>        ica.DesiredHRes = 1024;
        ica.DesiredVRes = 786;

        <span class="code-comment">// Register <span class="code-keyword">for</span> logon event
</span>        ica.OnLogon += <span class="code-keyword">new</span> _IICAClientEvents_OnLogonEventHandler(ica_OnLogon);

        <span class="code-comment">// Launch/Connect to the session
</span>        ica.Connect();

        <span class="code-keyword">if</span> (onLogonResetEvent.WaitOne(<span class="code-keyword">new</span> TimeSpan(0, 2, 0)))
            Console.WriteLine(<span class="code-quote">"Session Logged on sucessfully! And OnLogon Event was Fired!"</span>);
        <span class="code-keyword">else</span>
            Console.WriteLine(<span class="code-quote">"OnLogon event was NOT Fired! Logging off ..."</span>);

        <span class="code-comment">// Do we have access to the client simulation APIs?
</span>        <span class="code-keyword">if</span> (ica.Session == <span class="code-keyword">null</span>)
            Console.WriteLine(<span class="code-quote">"ICA.Session object is NULL! :("</span>);

        <span class="code-comment">// Give notepad a few seconds to setup
</span>        <span class="code-comment">// (or detect the window as seen below)
</span>        <span class="code-comment">// Obvious Tips: Sleep is usually bad in automation, race conditions ... now you know
</span>        <span class="code-object">Thread</span>.Sleep(5000);

        <span class="code-comment">////////////////////////////////////////////////////////////////////
</span>        <span class="code-comment">// Keyboard Simulation
</span>        <span class="code-comment">// Let's script some key input with the Keyboard <span class="code-keyword">interface</span>
</span>        Keyboard kbrd = ica.Session.Keyboard;

        <span class="code-comment">// <span class="code-quote">"Greatness has been achieved!"</span> - Indeed!
</span>        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.G);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.R);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.E);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.A);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.T);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.N);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.E);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.S);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.S);

        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.Space);

        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.H);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.A);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.S);

        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.Space);

        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.B);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.E);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.E);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.N);

        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.Space);

        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.A);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.C);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.H);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.I);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.E);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.V);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.E);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.D);

        <span class="code-comment">// Exclamation !
</span>        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.ShiftKey);
        kbrd.SendKeyDown((<span class="code-object">int</span>)Keys.D1);

        <span class="code-comment">////////////////////////////////////////////////////////////////////
</span>        <span class="code-comment">// ScreenShots
</span>        <span class="code-comment">// Let's take a screen shot of our session
</span>        ScreenShot ss = ica.Session.CreateFullScreenShot();
        ss.Filename = Path.GetTempPath() + Path.GetRandomFileName() + <span class="code-quote">".bmp"</span>;
        Console.WriteLine(<span class="code-quote">"Saving Screen Shot to: "</span> + ss.Filename);
        ss.Save();

        <span class="code-comment">///////////////////////////////////////////////////////////////////
</span>        <span class="code-comment">// Session Windows Enumeration
</span>        <span class="code-comment">// Enumerate top level windows and output them
</span>        Windows allTopLevelWindows = ica.Session.TopLevelWindows;
        <span class="code-keyword">if</span> (allTopLevelWindows.Count &gt; 0)
        {
            Console.WriteLine(<span class="code-quote">"\nDisplaying all Top Level Windows:"</span>);

            foreach (window w in allTopLevelWindows)
            {
                Console.Write(<span class="code-quote">"\n"</span>);
                Console.WriteLine(<span class="code-quote">"Window ID: "</span> + w.WindowID);
                Console.WriteLine(<span class="code-quote">"Window Caption: "</span> + w.Caption);
                Console.WriteLine(<span class="code-quote">"Window Position X: "</span> + w.PositionX);
                Console.WriteLine(<span class="code-quote">"Window Position Y: "</span> + w.PositionY);
            }
        }

        Console.WriteLine(<span class="code-quote">"\nPress any key to log off"</span>);
        Console.Read();

        <span class="code-comment">// Logoff our session
</span>        Console.WriteLine(<span class="code-quote">"Logging off Session"</span>);
        ica.Logoff();
    }

    <span class="code-comment">/// &lt;summary&gt;
</span>    <span class="code-comment">/// OnLogon Event Handler
</span>    <span class="code-comment">/// &lt;/summary&gt;
</span>    <span class="code-keyword">static</span> void ica_OnLogon()
    {
        Console.WriteLine(<span class="code-quote">"OnLogon event was Handled!"</span>);
        onLogonResetEvent.Set();
    }
}

I’ve uploaded working examples to the code share which can be found here

Happy Coding!

  • JohnCat