In my blog series so far, I’ve focused on the nuts and bolts of X1 Customization.

This post is an exception. I’m going to walk through a complex “problem” and explain how X1 customization can be used to address it.

The problem I’ve chosen is not a simple one by any means, but it is something I’ve been asked about on a regular basis since the first “Dazzle” prototype more than five years ago. We have even shipped a solution prevoiusly, but use of that feature was very low, and it has since been deprecated. Understanding why we failed in the past is key to making this attempt successful.

The problem I’m going to look at is “workflow” or, more specifically, how to build a system so that users can request applications and have some back-end system or human decided if they are worthy before they are given access. Workflow is not in itself complex, but every customer wants something slightly different. That is why we failed in the past, and why it is an ideal topic for a customization blog.

Note that this post will cover both X1 customizations and storefront customizations for the front-end and back-end parts of the problem.

Here is what we will cover:

  • How to decide which applications a user can see.
  • How to mark which apps require approval
  • How to ensure that those applications are put into a ‘pending’ state when requested
  • How to show apps as ‘requiring approval’ or ‘pending’ as appropriate
  • How to detect requests and call out to your own systems to fulfil them
  • How to mark a request as approved or denied

When I set out to prepare this blog I also wanted to add the following:

  • How to ask the user workflow related questions such as ‘why do you want this app’
  • How to inform the user of feedback from the approver

Those can be addressed, but they need a couple of APIs that aren’t in the X1 tech preview (but will be there by release). I’ll aim to cover them in a later blog.

How to decide which applications a user can see:

Store Front has a very simple built-in policy. Users can (only) see applications published to them from a XenApp or XenDesktop server, and they can only launch applications that they can both see, and have a suitable status in the subscription store.

The simplest approach to supporting workflow is, therefore, to simply publish applications that require workflow along with applications that don’t and to use the subscription store to control access.

However, this is where the first customer “gotcha” tends to hit. Many customers want an alternative. They want to use one AD group for “visibility” and another AD group for “permissions”. For example I might want to say “Anyone in sales can request SalesForce,” but only people in “SalesForceUsers” can run it.

With X1, we have an answer to this, but it does require some additional setup.

For these use cases, we would recommend two application entries: a dummy Salesforce published to the “Sales” group and the real Salesforce published to SalesForceUsers. We can use customizations to ensure that the user only ever sees one of these. For our purposes here, I’m going to say nothing more about the ‘dummy app’ approach and focus on the basic scheme.

How to mark which apps require approval:

Marking apps for approval is simply the case of adding a keyword. I’m going to use the keyword “WFS” (workflow supported), as this was used historically and some of the older (pre-X1) receivers still support it.

To add a keyword, simply enter KEYWORDS:WFS in the description field for each app you want to have workflow. This is done when publishing the app on XenApp or XenDesktop.

Adding the keyword won’t do anything (yet). We need to add some server side customization to detect and act on this keyword.

How to ensure that those applications are put into a ‘pending’ state when requested:

This is where it starts to get more complicated. In the early days of Receiver and StoreFront, we supported a connector technology that leveraged Microsoft Workflow Studio. That technology is long dead, but we have a simple alternative.

I’m making available a simple StoreFront customization in the form of a DLL you can drop into your StoreFront which will automatically ensure that when a user subscribes to an application that has the WFS keyword, that app is put into the “pending” state rather than the “subscribed” state. Once the app is marked in this way, StoreFront will ensure if cannot be launched until workflow completes (more of which later).

This is a DLL built for StoreFront 2.7 (the Tech Preview) (see below for a version for 3.0). It is distributed under the normal terms of the Citrix Developer Network, included below:

/*************************************************************************
*
* Copyright (c) 2015 Citrix Systems, Inc. All Rights Reserved.
* You may only reproduce, distribute, perform, display, or prepare derivative
* works of this file pursuant to a valid license from Citrix.
*
* THIS SAMPLE CODE IS PROVIDED BY CITRIX "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
*
*************************************************************************/

Here are the steps for installing it:

  1. Find the store directory for the store you want to enable workflow for. For example C:\inetpub\wwwroot\Purple
  2. Edit web.config in this directory and add the following to the end of the <components> section
    &lt;component id="SimpleWorkflowHandler"
     type="Citrix.DeveloperNetwork.StoreFront.SimpleWorkflowHandler, SimpleWorkflowHandler"
     service="Citrix.DeliveryServices.DazzleResources.Workflow.IWorkflowAdaptor,
              Citrix.DeliveryServices.DazzleResources.Workflow" /&gt;
  3. Download the DLL from this link (SF 2.7) and save it in the bin directory (e.g. C:\inetpub\wwroot\Purple\bin)
[EDIT. For StoreFront 3.0 (or 3.0 Tech Preview), you need a different set of binaries. Download from here. The binary for this part is called SimpleWorkflowHandler.dll]

There is no need to restart IIS (or the machine) the change will be applied automatically.

To test it out, run the X1 UI (browse to a web site) and take a look at a workflow app.

[EDIT. The following section only applies to the 2.7 tech preview. For 3.0 / 3.0 Tech Preview skip to  ‘How to detect requests and call out to your own systems to fulfil them’]

The UI won’t look any different (!) – but if you try and click on the ‘Add’ button you will find it throws you to the “App Details” page. Clearly something has happened – just not what we wanted!How to show apps as ‘requiring approval’ or ‘pending’ as appropriateThe X1 UI doesn’t automatically show the app differently because of workflow (at least not in the Tech Preview), and as you have just seen, it actually breaks them a little. This is because we have some unfinished code – but you can write a customization to get round this.Workflow apps have a property requiresworkflow:true which can be used to detected them. Unfortunately the tech preview disables subscription for apps with this property, so you do need a tweak to prevent this. Add this to custom\script.js
CTXS.Extensions.preProcessAppData = function(store, appData) {
 for (var i = 0, max = appData.resources.length; i &lt; max; i++) {
   var resource = appData.resources[i];
   if(resource.requiresworkflow)
   {
     delete resource.requiresworkflow; // stop blocking subscriptions
     resource.cssClass="requiresworkflow"; // mark with a css class
   }
 }
};

You can detect the css class and use it in your customization. For example the following customization will show ‘Request’ rather than ‘Add’ for each app that needs workflow. (Note this customization may not survive across versions as it relies on some deeper knowledge of the UI structure – but we plan to add built in support for this case, so that isn’t a huge issue)

Add this to custom\style.css

/* Hide the 'Add' text */
.requiresworkflow.available .storeapp-action-link span
{
 display:none;
}
/* Show 'Request' instead */
.requiresworkflow.available .storeapp-action-link:before
{
 content:"Request";
}

This should get you

If you click on ‘Request’ the resource will move to the pending state. This is a little ugly at present:

Again, we can add some customization to tidy this up by choosing a shorter message than “Pending Approval” to avoid the ugly wrapping.

Here is the right way to do it [spoiler – this won’t work yet]. In the custom directory you will find a string database for each language. You can use this to add your own strings – or to change any of the standard ones. So in principle you should edit custom\strings.en.js to this:

(function ($) {
 $.localization.customStringBundle('en', {
 PendingApproval: 'Pending'
 });
})(jQuery);

<span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 19px;"> </span>

Unfortunately, when writing this post, I discovered a bug. In the tech preview, we don’t actually load the strings.en.js file – though we do load the equivalent files for non-English locales. This will be fixed , but in the meantime, here is an equivalent fix using CSS> Add to styles.css as usual:
.pending .storeapp-action-link span
 {
 display:none;
 }
.pending .storeapp-action-link:before
 {
 content:"Pending";
 }

One last client side customization – in the ‘Favourites’ screen there is no indication that an app is in the pending state. This is easy to fix. Again in custom\styles.css add
.myapps-view .pending
 {
 opacity:0.5;
 }

Here is a canned demo site that shows these visual effects. (You can’t actually subscribe in this UI – it is only a dummy site).

In the demo site:

  • One app has been denied – you will see a popup notification when you open the UI
  • One app (ADP iPay) is pending – you will see it grayed out in Favourites, and with a ‘Pending’ icon in ‘Apps’
  • One app (Adobe Reader) requires approval – you will see a ‘Request’ label in ‘Apps’
For reference the two files we have modified are here:

How to detect requests and call out to your own systems to fulfil them

This is the meat of the problem, and the bad news is that I’m only going to give a skeleton answer. Subscriptions are stored on the StoreFront server (and if you have a cluster they are replicated to every StoreFront server). Here is the approach:

  • We use a new utility library to provide access to the subscription store from .Net code
  • (and a PowerShell friendly Command Line utility for those that are less familiar with .Net)
  • You provide the “business logic” that is informed of each requested app and ultimately moves it to a “subscribed” or “denied” state.

First the library. This is provided under the same license as the previous download.

  • Download the library from here and download a the command line utility from here (SF2.7)
  • Create a directory on your storefront server to run these from (e.g. C:\test) and put both files in it.
  • Add the user(s) who will use the library\utility to the ‘CitrixSubscriptionsStoreServiceUsers’ group.
[EDIT. For StoreFront 3.0 / 3.0 Tech Preview, you need a different set of binaries. Download from here.]
Before you can use this new utility library, you need to make sure the caller has permission to view/edit the subscription store. To do this, go to the control panel, search for “local user” and click on ‘Edit local users and groups’. You then need to add yourself (or the appropriate service account) to the CitrixSubscriptionsStoreServiceUsers group.

A quick test.

Open a command prompt and try the following: You will need to supply the name of your own store, and it should output a list of all subscriptions in the subscription store.

C:\test&gt;dir
03/24/2015 06:27 AM 16,384 SSClient.exe
03/24/2015 06:27 AM 25,600 SubscriptionStoreHelper.dll

C:\test&gt;SSClient.exe -store Purple -dump
user:WWCO\helenj resource:SampleFarm.Access 2013 status:subscribed
user:WWCO\helenj resource:SampleFarm.Adobe Reader XI status:subscribed

user:WWCO\richardh resource:Controller.Word 2013 status:subscribed
user:WWCO\richardh resource:SampleFarm.VLC media player status:pending
C:\test&gt;

You can change the status of the pending subscription (or any of the others). Experiment with the following (after requesting a workflow app).

Note you will need to replace the store/user and app with your own values.

SSClient.exe -store Purple -update -user WWCO\richardh -app " SampleFarm.VLC media player " -status denied

Or

SClient.exe -store Purple -update -user WWCO\richardh -app ” SampleFarm.VLC media player ” -status subscribed

Under the hood:

Let’s look into what this tool is actually doing … and how to write your own.

The key is the ‘SubscriptionStoreHelper’ dll. This provides a simple interface to the subscription store. Here is the primary interface it exposes (If you plan to use this I’d recommend also downloading the associated XML file to give Visual Studio some tooltips for the DLL)

<span style="color: blue;">public interface</span> ISubscriptionStore
{
 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Automatically flush any changes to store (true by default)</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: blue;">bool</span> AutoSave { <span style="color: blue;">get; set;</span> } 

 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Return all subscriptions for a given user</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: gray;">/// &lt;returns&gt;</span><span style="color: green;">Stream of subscriptions</span><span style="color: gray;">&lt;/returns&gt;</span>
 <span style="color: #2b91af;">IEnumerable&lt;SubInfo&gt;</span> GetSubscriptionsForUser(<span style="color: blue;">string</span> usernameOrSid);

 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Return all subscriptions added/changed in the store since the last</span>
 <span style="color: gray;">///</span> <span style="color: green;">'get subscriptions' call.</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: gray;">/// &lt;returns&gt;</span><span style="color: green;">Stream of subscriptions</span><span style="color: gray;">&lt;/returns&gt;</span>
 <span style="color: #2b91af;">IEnumerable&lt;SubInfo&gt;</span> GetNewSubscriptions();

 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Get all subscriptions added/modified since the given date</span>
 <span style="color: gray;">///</span> <span style="color: green;">Note the date is low fidelity and only gives an approximation.</span>
 <span style="color: gray;">///</span> <span style="color: green;">Some subscriptions prior to the date may also be returned.</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: gray;">/// &lt;param name="from"&gt;</span><span style="color: green;">Approximate date</span><span style="color: gray;">&lt;/param&gt;</span>
 <span style="color: gray;">/// &lt;returns&gt;</span><span style="color: green;">Stream of subscriptions</span><span style="color: gray;">&lt;/returns&gt;</span>
 <span style="color: #2b91af;">IEnumerable&lt;SubInfo&gt;</span> GetSubscriptionsSince(<span style="color: #2b91af;">DateTime</span> from);

 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Get all subscription entries in the database</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: gray;">/// &lt;returns&gt;</span><span style="color: green;">Stream of subscriptions</span><span style="color: gray;">&lt;/returns&gt;</span>
 <span style="color: #2b91af;">IEnumerable&lt;SubInfo&gt;</span> GetAllSubscriptions();

 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Remove a single subscription based on user/sid and resource id.</span>
 <span style="color: gray;">///</span> <span style="color: green;">Note. Not currently supported by StoreFront</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: gray;">/// &lt;param name="usernameOrSid"&gt;</span><span style="color: green;">User to remove subscription from.</span><span style="color: gray;">&lt;/param&gt;</span>
 <span style="color: gray;">/// &lt;param name="resource"&gt;</span><span style="color: green;">Resource to remove subscription info about.</span><span style="color: gray;">&lt;/param&gt;</span>
 <span style="color: gray;">/// &lt;returns&gt;&lt;/returns&gt;</span>
 <span style="color: blue;">bool</span> RemoveSubscriptionInfo(<span style="color: blue;">string</span> usernameOrSid, <span style="color: blue;">string</span> resource);

 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Update or create a subscription entry.</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: gray;">/// &lt;param name="info"&gt;</span><span style="color: green;">The subscription info to save</span><span style="color: gray;">&lt;/param&gt;</span>
 <span style="color: gray;">/// &lt;param name="mergeProperties"&gt;</span><span style="color: green;">Merge properties with existing properties (defaults to false)</span><span style="color: gray;">&lt;/param&gt;</span>
 <span style="color: gray;">/// &lt;returns&gt;</span><span style="color: green;">True on success</span><span style="color: gray;">&lt;/returns&gt;</span>
 <span style="color: blue;">bool</span> SetSubscriptionInfo(SubInfo info, <span style="color: blue;">bool</span> mergeProperties = <span style="color: blue;">false</span>);

 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Get a single subscription based on the user/sid and resource in the passed subscription info</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: gray;">/// &lt;param name="user"&gt;</span><span style="color: green;">User to lookup subscription from</span><span style="color: gray;">&lt;/param&gt;</span>
 <span style="color: gray;">/// &lt;param name="resource"&gt;</span><span style="color: green;">Resource to lookup</span><span style="color: gray;">&lt;/param&gt;</span>
 <span style="color: gray;">/// &lt;returns&gt;</span><span style="color: green;">The matching record, or null if none is found</span><span style="color: gray;">&lt;/returns&gt;</span>
 <span style="color: #2b91af;">SubInfo</span> GetSubscriptionInfo(<span style="color: blue;">string</span> usernameOrSid, <span style="color: blue;">string</span> resource);

 <span style="color: gray;">/// &lt;summary&gt;</span>
 <span style="color: gray;">///</span> <span style="color: green;">Flush any changes to the store. This is only required if AutoSave is set to false</span>
 <span style="color: gray;">/// &lt;/summary&gt;</span>
 <span style="color: blue;">void</span> SaveChanges();
}

 

Let’s write a simple client to use this. (For those of you unfamiliar with C# you can skip forward a page or so)

In Visual Studio, create a new command line project, include a reference to the SubscriptionStoreHelper.dll and replace the default program with the following text:

<span style="color: blue;">using</span> System;

<span style="color: blue;">namespace</span> Citrix.DeveloperNetwork.StoreFront
{
  <span style="color: blue;">public class</span> <span style="color: #2b91af;">SSDump</span>
  {
    <span style="color: blue;">public static int</span> Main(<span style="color: blue;">string</span>[] args)
    {
      <span style="color: blue;">string</span> url = <span style="color: #2b91af;">String</span>.Format(
      "net.pipe://localhost/Citrix/Subscriptions/1__Citrix_{0}", args[0]);

      <span style="color: blue;">var</span> client = new <span style="color: #2b91af;">SubscriptionStore</span>(url);

      <span style="color: blue;">var</span> subscriptions = client.GetAllSubscriptions();

      <span style="color: blue;">foreach</span> (<span style="color: blue;">var</span> info <span style="color: blue;">in</span> subscriptions)
      {
        <span style="color: #2b91af;">Console</span>.Out.WriteLine("{0} {1} {2}", info.User,
                                             info.Resource, info.Status);
      }
    }
  }
}

Remember you need to copy this to your storefront server to run it!

This program is very simple. It does the following:

  • Construct a URL referencing the store specified on the command line
  • Creates a SubscriptionStore helper object
  • Calls that object to list all subscriptions
  • Dumps the subscriptions to the command line

You can use the same pattern to update subscriptions or–in theory–delete them. I say “in theory” because delete is not currently implemented on StoreFront. It is part of the API, but it won’t work yet.

Rather than spell out all the operations and flows in detail, I’m just going to point out some key features, give you access to the source to the command line tool, which exercises them all.

  • You can ask for all subscriptions, subscriptions since a given time, or subscriptions for a given user
  • You can then ask for any new/updated subscriptions (so can monitor for changes)
  • You can set subscription state, or subscription properties. Properties are general purpose name/value pairs associated with each subscription. Today, these are used to remember the order that apps appear on the “favorites” screen, but you can invent your own uses.

I’ve written a general purpose command line client that uses this library (this is the SSClient you downloaded earlier). Type SSClient.exe without any arguments to see a full list of options. This will give:

C:\test&gt;SSClient.exe
Usage SubscriptionStoreClient -url &lt;url&gt; &lt;op&gt; &lt;opargs&gt;
or SubscriptionStoreClient -store &lt;store&gt; [-siteid &lt;siteid&gt;] &lt;op&gt; &lt;opargs&gt;
&lt;url&gt; : WCF endpoint for subscription store. E.g. net.pipe://localhost/Citrix/Subscriptions/1__Citrix_Store
&lt;store&gt;: Store name. E.g. Store
&lt;siteid&gt;: IIS Site id (default is 1)
&lt;op&gt;: set | update | delete | dump

set/update/delete
=================
Set creates a new entry. Update merges with a current one. Delete removes
-set [-sid &lt;sid&gt; | -user &lt;user&gt;] -app &lt;app&gt; -status &lt;status&gt; [-properties &lt;props&gt;]
-update [-sid &lt;sid&gt; | -user &lt;user&gt;] -app &lt;app&gt; -status &lt;status&gt; [-properties &lt;props&gt;]
-delete [-sid &lt;sid&gt; | -user &lt;user&gt;] -app &lt;app&gt; [NB currently implemented in StoreFront]
&lt;sid&gt; : User sid for subscrption to update
&lt;user&gt; : User for subscrption to update (AD format). (Sid or User must be specified)
&lt;app&gt; : Resource ID of subscription to update
&lt;status&gt;: Status to store in DB (e.g. subscribed/unsubscribed/pending/denied
&lt;props&gt; : Semicolon seperated list of name=value pairs

dump
====
Dump contents of database. Can loop and display any changes
-dump [-csv] [-user &lt;user] [-sid &lt;sid&gt;] [-app &lt;app&gt;] [-props [names]] [-start &lt;date&gt;]
      [[-stream [-delay timespan]]
-csv : Output in CSV format to ease post-processing
-props : Include subscription properties. Names can be optionally specified (required for CSV)
-start &lt;date&gt; : Only return entries after specified date/time (approx)
-stream [&lt;time&gt;]: Keep running and stream any changes to database. Default pause is 1min
-sid &lt;sid&gt; : Only include subscriptions for this user (-start is ignored)
-user &lt;user&gt; : Only include subscriptions for this user (-start is ignored)
-app &lt;app&gt; : Only include subscriptions for this app
-status &lt;status&gt;: Only include subscriptions with this status

 

You can also access the <source code>, and tweak it in any way you need, bearing in mind the standard Citrix Developer Network license (as included above, and in the source file).

Back to Workflow

Let’s look at how to use these tools for a simple workflow. As I said the details are up to you, but here is an example.

  1. Monitor the subscription store for any app entering the ‘pending’ state.
    (Note the –csv option to SSClient outputs data in a format easy to consume elsewhere, for example in powershell).
  2. SSClient.exe  –store Purple –dump –csv –status pending –stream
    For each change fire off the appropriate workflow.
  3. When a workflow completes, move the app into the ‘denied’ or ‘subscribed’ state

SSClient.exe –store Purple –user "WWCO\Richard" –app "SampleFarm.VLC media player" –update –status subscribed

Summary and Next Steps

Workflow in X1 is not yet baked. There are a number of places in this post where we have had to tweak the default behaviour to get it to do what we need. However, support for displaying and processing apps that have workflow is coming. That said, while XenMobile has an email based approval system baked in, there are no immediate plans to add something similar to StoreFront. If you are a partner wishing to build such a solution, or a customer wanting to integrate into an existing system, then I hope this blog provides the tools you need. Please let me know if there are any gaps.

In the next post I’m going to return to the “dummy apps” approach, and show how you can link to a system that uses active directory groups to remember subscriptions. I’ll also show how you can use the subscriptions properties mentioned earlier to pass useful state between the X1 client and the back end systems. That can be used to prompt the user for details or their request, or inform them of reasons for a denial.

Blogs in this series