Tuesday, October 10, 2023

Citrix Releases StoreFront 2308

On September 14, 2023, Citrix released StoreFront 2308. The following features were added to this version:

App Protection through a web browser

App Protection provides an additional level of security by blocking keyloggers and screen capture. Previously, this functionality was only available when accessing a store through Citrix Workspace apps for Windows, Mac and Linux. When viewing a store through a web browser, protected apps were not displayed. With this release it is now possible to configure a store website to display apps requiring App Protection when viewed through a browser, as long as StoreFront has detected that the user has a sufficiently new version of Citrix Workspace app for Windows, Mac or Linux installed that will be used to launch the app.


Advanced Health Check is now enabled by default

StoreFront runs periodic health checks on each Citrix Virtual Apps and Desktops server and Cloud Connector to reduce the impact of intermittent server availability. With Advanced heath check StoreFront performs a more in-depth check that is more likely to detect any issues.

From this release onward, the advance health check feature is enabled by default. Previously it had to be enabled manually.


The following StoreFront features have been deprecated in this release:

XenApp Services

From this release onward, support for XenApp Services (also known as PNAgent) is deprecated. It will be removed in a future release.

XenApp 6.5

It is no longer possible to add new XenApp 6.5 resource feeds using the StoreFront admin console. It is still possible to add them using PowerShell Add-STFStoreFarm specifying the FarmType as XenApp.

There were also a number of fixed issues in the release.

Full documentation on this release.

Sunday, September 10, 2023

Custom Timeouts for Citrix StoreFront - Revisited

In my last blog post I showed you how to create custom StoreFront timeouts based on a user's AD group membership. Once the custom timer expires, the user is logged out of StoreFront. Under certain circumstances this might cause unexpected behavior. 

For example, the default action when the user clicks the logoff button in StoreFront is to disconnect any Citrix sessions launched from that PC. This may be changed under Workspace Control:


You probably don't want your Citrix sessions to disconnect when StoreFront times out (what if you were in the middle of an important document in your Citrix session?). You would need to sign on to StoreFront again to continue working. It would be a lot worse if you had the logout action set to Terminate (you would lose all your work!).

To get around this "gotcha", we need to take advantage of a StoreFront "extension" that allows us to dynamically change the logout action on the fly. We define the new action at the top of the file:
var ICAaction    = "none";

"None" means to leave any running Citrix sessions alone - do not disconnect or terminate them. We then call the beforeWebLogoffIca StoreFront extension, and tell it to swap our new logout action for the default one:

CTXS.Extensions.beforeWebLogoffIca = function(defaultAction) {
log("About to log off ... default ICA action: " + defaultAction + " ... returning: " + ICAaction);
return ICAaction;
}; 

If you activate the console in the browser's Developer Tools, you should see something like:










You can download the updated script.js here.

Sunday, July 16, 2023

Custom Timeouts for Citrix StoreFront

One of the features that I have found missing in StoreFront is the ability to set different timeouts for different categories of users. For example, you may wish to have a shorter timeout for users in a sensitive department (e.g. Finance). If you feel the same way, read on ...

Let's assume that you would like the Finance department to have a shorter timeout period (e.g. 5 minutes) rather than the default value of 20 minutes. First, you would need a way to determine if the user logging on to StoreFront is in the Finance department. You would then need to set the appropriate timeout based on the user's membership.

The first requirement may be accomplished by adding the following .Net .aspx file (inGroup.aspx) to the custom directory. You pass it the user's ID and the department (OU) you wish to check for membership. and it will return true if the user is a member of that OU or false if the user is not. It is beyond the scope of this post to explain each of the routines in the file, but two points need to be mentioned. 

1) You must edit the file at line #11 to reflect the user root OU and your company's domain:

string rootOU = "OU=Accounts,DC=domain,DC=com";

2) You need to add an assembly to the file web.config in your store's web directory (e.g. /Citrix/StoreWeb). To do this:

- make a backup of the web.config file.

- open the file and search for <assemblies>

      <assemblies>
        <add assembly="System.Web.Mvc, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
      </assemblies>

- insert the following line right before the closing </assemblies> tag

        <add assembly="System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />

- which should then look like

      <assemblies>
        <add assembly="System.Web.Mvc, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Abstractions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.Web.Routing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
        <add assembly="System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
      </assemblies>

- save the file

You also need to copy the contents of this script file (script.js) into the script file currently in the custom directory (after backing it up, of course). At the very top of the file you will see:

// group to get a special timeout
var specialGroup       = "Finance";
var specialTimeoutMins = 5;

You will need to modify the above for the AD group to check for and the special timeout (in minutes) for that group.

Note: You need to make sure that the timeout specified in your StoreFront configuration is greater than your special timeout, or StoreFront will log you out before you reach your custom timeout.

The JavaScript code sets a timer based on the timeout value specified and simulates a click on the logoff link when the timer expires.

function logout() {
log("Session timeout has expired ... logging out ... ");
$('#dropdownLogOffBtn').click();
}

Logging code has been added to the above which you can view by activating the console in your browser's Developer Tools. Here is a sample of what you would see if the user is in the special group:


Here is a sample of what you would see if the user is NOT in the special group:




Sunday, May 28, 2023

QoS Indicator for Citrix StoreFront

After the successful release of Setting user Expectations, which showed you how to add a QoS (Quality of Service) indicator to the NetScaler logon screen, I've had requests for a similar indicator for StoreFront. 


As a bonus, we will have the display auto-refresh at a custom-defined interval. We will set it to every 15 seconds, but you can very easily modify that.

Note: The code in this post has been written for StoreFront version 2203. The code may need to be tweaked for other versions of StoreFront.

All of the modifications may be done via updates within the custom directory of the site, which means that they will be replicated to all servers in the StoreFront group when you propagate. 

The following code should be added to script.js (in the custom directory):

  // QoS refresh interval
  var refreshIntervalSecs = 15;

   // QoS latency ranges - max value for range, class name for graphic, legend
   var aLatencyRanges = 
[[20,"qos-excellent","Excellent"],
[50,"qos-verygood","Very Good"],
[75,"qos-good","Good"],
[150,"qos-fair","Fair"],
[99999,"qos-poor","Poor"]];

    // image to download to calculate Time To First Byte (TTFB) 
    var testImgUrl = "custom/images/FourBars.png";

//This method calculates the time it takes for the image to load 
function checkLatency(url, callback) {
var t=[], n=2, tcp, rtt;
var ld = function() {
   t.push(+new Date);
   if(t.length > n) {
rtt=t[2]-t[1];
callback(rtt);
   }
   else {
var img = new Image;
img.onload = ld;
img.src=url+"?" + Math.random() + '=' + new Date;
   }
};
ld();
}

function displayQoS(latency) {

   // for refresh, we need to remove the current image class
   var currClass = $("#QoSimage").attr('class');
   $("#QoSimage").removeClass(currClass);

   for (i=0; i<aLatencyRanges.length;i++) {
if(latency<=aLatencyRanges[i][0]) {
var QoSclass = aLatencyRanges[i][1];
var QoSinfo = aLatencyRanges[i][2] + ' (' + latency + ' ms.)';
$("#QoSval").html(QoSinfo);
$("#QoSimage").addClass(QoSclass);
break;
}
   }
}

function RunQoS() {
  // file to use for testing, and callback routine
  checkLatency(testImgUrl, displayQoS);
}

// wait until StoreFront has been loaded
$(document).ready(function() {

   // add the QoS DIVs
   var $qos = $("<div id='QoSinfo'><a href='custom/ConnectionQuality.html'><div id='QoSimage'></div><div id='QoSRight'><div id='QoStext'>Connection Quality:</div><div id='QoSval'></div></div></a></div>");

   // Important: This selector is for StoreFront version 2203
   //                  The selector may be different for other versions of StoreFront
   $qos.insertBefore("#searchBtn");

   // run QoS ... run twice the first time to remove handshake overhead
   RunQoS(); RunQoS();

   // now set up auto refresh
   var refreshIntervalms = refreshIntervalSecs * 1000;
   window.setInterval(RunQoS, refreshIntervalms); 
});


Let's style the above (size, color, font, etc.) by adding the following to style.css (in the custom directory):

#QoSinfo {
margin-right: 100px;
width: 200px;
margin-top: 20px;
height: 40px;
color: white;
}

#QoSimage {
height: 40px;
width: 70px;
margin-top: -7px;
position: absolute;
margin-right: -50px;
}

#QoSRight {
display: inline-block;
font-size: 12px;
height: 40px;
margin-left: 77px;
}

#QoSval,#QoStext {
font-style: italic;
color:white;
font-weight: bold;
}

.qos-excellent {
background-image : url('../custom/images/FourBars.png'); 
background-repeat: no-repeat;
background-position: center;
}

.qos-verygood {
background-image : url('../custom/images/ThreeBars.png'); 
background-repeat: no-repeat;
background-position: center;
}

.qos-good {
background-image : url('../custom/images/TwoBars.png'); 
background-repeat: no-repeat;
background-position: center;
}

.qos-fair {
background-image : url('../custom/images/OneBar.png'); 
background-repeat: no-repeat;
background-position: center;
}

.qos-poor {
background-image : url('../custom/images/NoBars.png'); 
background-repeat: no-repeat;
background-position: center;
}

Notice that we added a hyperlink in the code above, so clicking on the QoS description brings up a legend of the QoS ranges. You can download that file here: ConnectionQuality.html.




Sunday, April 16, 2023

Setting User Expectations - Part 3

In Part 1 of this series, we discussed how to create a QoS indicator to inform the user of their expected experience before they even logon by making a few simple changes to the NetScaler. In Part 2 of the series, we added a graphical representation of the results. In this final installment, we will add a reference page that will display the various ranges of the QoS indicator to the user.



All of the graphics needed have already been created and uploaded in Part 2. We simply need to create a page that references the various ranges, and change the display into a hyperlink that when clicked, opens up the reference page.

You can download the above page here: 

https://samjacobs.sharefile.com/d-s4eaf8e876fe449afada749110b5ae48c

Let's modify the original display by adding a hyperlink to the above page. 

Make a backup of our original file: /var/netscaler/logon/LogonPoint/tmindex.html

Open up the file and search for:

QoStext.innerHTML = 'Connection Strength:<br>'+QoSrange;

Add a hyperlink to our reference page by changing the above to:

QoStext.innerHTML = '<a href="QoS.html">Connection Strength:<br>'+QoSrange+'</a>';

Then save the file. Now when you click on the text of the QoS indicator, the reference page will be displayed.

 

Complete Code Solution

You can find a complete copy of the code here.

Sunday, February 26, 2023

Setting User Expectations - Part 2

 In Part 1 of this series, we discussed how to create a QoS indicator to inform the user of their expected experience before they even logon by making a few simple changes to the NetScaler. In part 2 of the series, we will add a graphical representation of the results. The strength of the connection will be represented by the number of bars in the graphic - the more bars, the stronger the connection.




The first step will be to upload the bar images which may be found here:
https://samjacobs.sharefile.com/d-s15afc8842d044e8cb6f0da39dc6e0381

into the following directory:

/var/netscaler/logon/themes/QoS/custom_media.

Next, we need to add some css to the file: /var/netscaler/logon/themes/QoS/css/theme.css.

Insert the following:

#QoSbars {
   display:table-cell;   
   min-width:70px;
   height:40px;
}
.FourBars, .ThreeBars, .TwoBars, .OneBar, .NoBars {
   min-width:70px;
   height:40px;
   background-repeat: no-repeat;
}
.FourBars {
   background-image: url("../custom_media/FourBars.png");
}
.ThreeBars {
   background-image: url("../custom_media/ThreeBars.png");
}
.TwoBars {
   background-image: url("../custom_media/TwoBars.png");
}
.OneBar {
   background-image: url("../custom_media/OneBar.png");
}
.NoBars {
   background-image: url("../custom_media/NoBars.png");
}

The above changes need to be made to the primary NetScaler node and will be replicated to the secondary node.

The final changes need to be made to the file: /var/logon/LogonPoint/tmindex.html.

After making a backup, open the file and look for the following code (that we added in Part 1):

<div id="QoSinfo">
   <div id="QoStext">&nbsp;</div>
</div>

Add the following line after the QoSinfo <div>:

<div id="QoSbars" class="ThreeBars">&nbsp;</div>

Then, scroll to the bottom of the file to find where you added the JavaScript code, and replace the entire <script> section that you added with the below. You can customize your ranges by simply changing the values in the aLatencyRanges array below. The default below says that anything 50 ms. or below is considered Excellent, 51-100 ms. is Very Good, 101-130 ms is Good, 131-200 is Fair, and 200+ ms. is Poor.

<script>
    // QoS latency ranges - max value for range, class name for graphic, legend
    var aLatencyRanges = 
        [[50,"FourBars","Excellent"],
         [100,"ThreeBars","Very Good"],
         [130,"TwoBars","Good"],
         [200,"OneBar","Fair"],
         [99999,"NoBars","Poor"]];
    
    var testImgUrl = "https://" + location.hostname + "/vpn/images/Tick32.gif";
    
    //alert ("Using image: " + testImgUrl);
    //This method calculates the time it takes for the image to load 
    function checkLatency(url, callback) {
        //alert("checkLatency()...");
        var t=[], n=2, tcp, rtt;
        var ld = function() {
           t.push(+new Date);
           if(t.length > n) {
             rtt=t[2]-t[1];
             callback(rtt);
           }
           else {
             var img = new Image;
             img.onload = ld;
             img.src=url+"?" + Math.random() + '=' + new Date;
           }
        };
            
        ld();
    }
    
    function displayQoS(latency) {
        var QoSbars = document.getElementById("QoSbars");
        var QoStext = document.getElementById("QoStext");
    
        for (i=0; i<aLatencyRanges.length;i++) {
            if(latency<=aLatencyRanges[i][0]) {
                var QoSclass = aLatencyRanges[i][1];
                var QoSrange = aLatencyRanges[i][2] + ' (' + latency + ' ms.)';
    
                QoSbars.className = QoSclass;
                QoStext.innerHTML = 'Connection Strength:<br>'+QoSrange; 
    
                break;
            }
        }
    }
        
    function runQoS() {
       checkLatency(testImgUrl, displayQoS);
    }
    runQoS();

    // end of QoS modifications
    </script>

Now when you browse to your logon page, in addition to having the connection strength is milliseconds, you will also have the strength displayed graphically.

Sunday, January 8, 2023

Setting User Expectations - Part 1

One of the main gripes from Citrix administrators are clients complaining that Citrix is slow when the issue is not anything in the Citrix infrastructure, but rather at the user's endpoint. This can include issues such as the user's PC (e.g. slow or pegged CPU), or a weak Wi-Fi connection. 

In this 3-part series, I will show you how to set the user's expectations before they even log on by placing what I call a QoS (Quality of Service) indicator on your NetScaler's logon page. This is accomplished by measuring what is known as Time To First Byte (TTFB). TTFB is a metric that measures the time between the request for a resource and when the first byte of a response begins to arrive by downloading a very small file from the NetScaler.

For this series, we will be using firmware version 13.1 of NetScaler, and we will be using the RfWebUI (Receiver for Web UI) theme. The tool we will use for most of our work will be WinSCP - a free secure copy utility (https://winscp.net/eng/download.php).

In Part 1, we will get the basics of the QoS indicator done, and when finished, it should look something like this:



We begin by creating a NetScaler theme which will contain the look and feel of our customizations. The easier way to do this is by opening up a PuTTY (terminal emulation software) session to the primary node and entering:

add vpn portaltheme QoS -basetheme RfWebUI

These changes will be replicated from the primary node to the secondary node. Make sure you make these changes to the primary, or they will be removed the next time the config is synched from the primary node.

The above command will create a new theme called QoS in the following directory: /var/netscaler/logon/themes. 

Open the file /var/netscaler/logon/themes/QoS/css/theme.css and insert the following:

#QoSinfo {
   display:table-row;
   height:125px;
   width:250px;
   float:right;
   color: #fff;
}
#QoStext {
   display:table-cell;
   font-family:'Open Sans', sans-serif;
   font-size:12px;
   width:170px;
   min-height:30px;
   margin-left:10px;
   vertical-align:middle;
   padding-top:7px;
   text-align:left;
}
#QoStext a {
   color:white;
}

Then save the file.

We then need to make a backup up of the following file on the NetScaler: /var/logon/LogonPoint/tmindex.html.  Open the file. Around line 386, you should see:

<div class="logon-spacer"></div>

Add the following lines between <div class="logon-spacer"> and </div>:

<div id="QoSinfo">
  <div id="QoStext">&nbsp;</div>
</div>

This is where the QoS indicator will be inserted on the screen.

When done, it should look like this:




The final step will be to insert the JavaScript code for the Time To First Byte calculation and to insert the result into the QoStext <div> above.

Scroll to the bottom of the file and locate the following two lines:

<script src="receiver/js/ctxs.core.min.js"></script>
<script src="receiver/js/ctxs.webui.min.js"></script>

Insert the following:

    <script>
     var testImgUrl = "https://" + location.hostname + "/vpn/images/Tick32.gif";
     function checkLatency(url, callback) {
        //alert("checkLatency()...");
        var t=[], n=2, tcp, rtt;
        var ld = function() {
           t.push(+new Date);
           if(t.length > n) {
             rtt=t[2]-t[1];
             callback(rtt);
           }
           else {
             var img = new Image;
             img.onload = ld;
             img.src=url+"?" + Math.random() + '=' + new Date;
           }
        };
        ld();
    }
    function displayQoS(latency) {
         var QoStext = document.getElementById("QoStext");
         for (i=0; i<aLatencyRanges.length;i++) {
            if(latency<=aLatencyRanges[i][0]) {
                var QoSclass = aLatencyRanges[i][1];
                var QoSrange = aLatencyRanges[i][2] + ' (' + latency + ' ms.)';
                 QoStext.innerHTML = 'Connection Strength:<br>'+QoSrange; 
                 break;
            }
        }
    }  
    function runQoS() {
       checkLatency(testImgUrl, displayQoS);
    }

    runQoS();
    </script>

Then save the file. Note: if you have an HA pair, any changes to this file must be done to both NetScalers - source code changes are not replicated between the nodes.

That's it! In Part 2, we will add a graphical representation of the QoS indicator.