Thursday, January 21, 2021

Stupid Citrix Trick #10: Smart Card Confusion

Problem: When using smart cards to log in, some users get an error when launching apps.

Solution: Configure Citrix Workspace to prompt for credentials.

No, Smart Card Confusion is not the hottest nerdcore band in Portland.  It's a problem that Citrix may encounter when multiple smart cards are present in a user's PC.  Before we get to the solution, let's talk about smart cards a bit.

Smart cards have become much more common in corporate and government environments over the past few years, as they are more secure than logging in with a username and password.  The user credential is on a card that looks much like a bank card, and is inserted into a card reader - either built in to the computer, or external, typically a USB device.

When logging in, the user will be prompted for the PIN number, just like a bank card.  Crucially, the user's PIN is not stored in a network database like Active Directory.  Instead, it's kept on the smart card itself.  This is more secure, since there's no password on the server that can be stolen. An intruder would need the card itself and the PIN.

More recently, virtual smart cards have been introduced, which eliminate the need for a physical card.  These smart cards reside on the user's PC in a piece of hardware called a Trusted Computing Module, which is just as secure as a physical card, and also uses a PIN.

Smart card confusion occurs when a user needs to be able to log on with more than one account at different times.  For example, a user may have a virtual smart card for their user account, but a physical smart card to log on as an administrator.  If there are two smart cards present in the system, a user may get this error when launching apps:


If you can see an app in Workspace, you've been granted access, so what does this message mean?  It means that the wrong smart card is being used when logging in, so that the wrong credentials are used to log in to Citrix.

There are only two options for picking the smart card to use with Workspace - always use the default card, or always make the user choose.  Unfortunately this is not configurable using the GUI, but it's very easy to fix it in the registry.  First, navigate to this registry key:

HKLM:\Software\WOW6432Node\Citrix\ICA Client\SSON

SSON stands for Single Sign-on, and it determines how smart cards behave. If the string value "Enable" is set to the test "true", then Workspace will always use the default smart card.  If it's set to "false", as in the picture below, it will always prompt the user for their credentials.

Unfortunately it requires administrator access to change that value.  But it's easily done and will resolve the problem... at least until the next version of Workspace is installed.




Sunday, January 10, 2021

Stupid Citrix Trick #9: Deploying A Delivery Group With Powershell

Problem: You're deploying a Delivery Group to a XenApp farm using Powershell.

Solution: NOT AS SIMPLE as you'd expect!

Citrix had done a really good job of integrating Powershell automation into their system.  Anything you can do in the GUI, you can do in Powershell, and I have heard that the system itself uses Powershell to make its changes.  Still, there are a few gotchas here and there that you have to look out for.

One gotcha in particular nearly made me lose my mind.

All I wanted to do was to create a delivery group and publish some applications to it.  Pretty simple, right?  There's a straightforward Powershell function that does just this:

New-BrokerDesktopGroup
-Name $GroupName
-Description $GroupDescription
-DeliveryType DesktopsAndApps
-DesktopKind Shared
-IsRemotePC $false
-SecureIcaRequired $false
-SessionSupport MultiSession
-TimeZone $TimeZone

As you can see from the options we're applying, this group is delivering both desktops and applications and its servers are used by multiple simultaneous sessions - basically an old-school Citrix server.  The information specified is pretty much what you'd need to supply when using the GUI, so you should be able to add some servers and start firing up your apps, right?

Not so fast, buckaroo.  If you published applications to that delivery group, no one would ever see them, and that's because you have to create another object, a Broker Entitlement Policy Rule.  Here's what the code looks like:

New-BrokerAppEntitlementPolicyRule
-Name $GroupName
-DesktopGroupUid $GroupUid
-Enabled $true
-ExcludedUserFilterEnabled $false
-IncludedUserFilterEnabled $false
-LeasingBehavior Allowed
-SessionReconnection Always

The $GroupName and $GroupUid are for the group you just created, natch. We're naming the rule the same as the group; you can name it something else if you prefer.

I'm not really sure why this rule is required.  It's created silently when using the GUI, so why couldn't it be automatically configured when the DeliveryType is "DesktopsAndApps" or "AppsOnly"?  I suspect it's because that XenApp 7 grew out of the XenDesktop product, not from XenApp 6.5 (at least that's my understanding).  Support for publishing apps was more or less bolted on, and some of the joints are still visible.

In addition, and more aggravating, the documentation for New-BrokerDesktopGroup does not tell you that there's another step before you can publish apps.  If you don't create it, everything will look as though it's configured properly but the apps will not be visible.

Let me know in the comments if this helped you. 'Til next time...

Tuesday, August 4, 2020

Stupid Citrix Trick #8: Forcing A Powershell Function To Return An Array

Problem: You want to return an array object from a Powershell function as an array under all circumstances.

Solution: No one's a bigger fan of Powershell than me. It's not perfect but it's powerful, flexible, and can use lots of other programming environments natively, like WMI objects and the .Net framework.  But today I found one super-duper stupid thing it does that made my blood boil with the heat of a thousand suns.

Specifically if you return an array from a function, and the array only has one element, than just that variable is returned.  So if you have an array of strings containing only a single value, that value will be returned as a string.

Why is this a big deal?  Because ArrayC = ArrayA + ArrayB behaves differently if you are adding two arrays than if you're adding a string object to an array.  Let's run through an example.

Let's pretend we have a couple of functions, GetArrayA and GetArrayB.  These functions just return an array containing three values:

function GetArrayA
{
   $Array = @("1", "2", "3")
   Return  $Array
}

function GetArrayB
{
   $Array = @("40", "50", "60")
   Return  $Array
}

$A = GetArrayA
$B = GetArrayB

$C = $A + $B

So what is the expected value of $C?  Here's what you'd expect:

1
2
3
40
50
60

And you'd be right, that is the value of $C.  Now let's make one small change:

function GetArrayA
{
   $Array = @("1")
   Return  $Array
}

function GetArrayB
{
   $Array = @("40", "50", "60")
   Return  $Array
}

$A = GetArrayA
$B = GetArrayB

$C = $A + $B


Now the array $A only contains a single entry.  You would expect the contents of $C to look like this:

1
40
50
60

But you would be wrong.  Here's what you would actually get:

140
50
60

WHYYYY would it do such a thing?  Because the function GetArrayA did not return an array.  Since the array held only a single entry, it returned that object, a string, instead of the array.  And you can confirm that with the .GetType() function.  It then appended that string object to the first entry in array $B and went on its merry way.

You'd think when I realized this, realized that my code was correct but Powershell was sabotaging me, that I would've been relieved, but instead my wrath burned with the fire of a thousand suns. I trusted Powershell, Powershell was my friend, and suddenly it turned around and stabbed me in the back.

However there's a simple solution.  You just have to use a type declaration on the variables that will hold the array values, like this:

[array]$A = GetArrayA
[array]$B = GetArrayB

Despite the title of this post you're not actually forcing the function to return an array, you're forcing the variable to receive an array.  Which means you use that function frequently, you'll have to update a lot of code! But at least it WILL work.

I found that solution here:

https://stackoverflow.com/questions/11107428/how-can-i-force-powershell-to-return-an-array-when-a-call-only-returns-one-objec

If this helps even one of you avoid the painful consequences, then my suffering was worthy it.

Thursday, July 30, 2020

Stupid Citrix Trick #7: Getting User Profiles Right

Problem: User profiles, man.

Solution: So who doesn't love user profiles?  Well, the users aren't too fond of them when their profile gets corrupted, and as a systems engineer who works on Citrix I can say that I get awfully tired of resetting them.  It's a rare instance when users and sysadmins are in full agreement.

But like many crummy things, profiles are the best of a bad lot.

This post won't delve into quite as many technical details as some of my others, but I thought I'd pass along the best practices that I've learned, most of which won't be new if you've worked on profiles before.  But it may help you avoid some of the pitfalls I've tumbled headlong into.

Profile Location.  A profile is just a folder with a bunch of files in it, so it can go on pretty much any shared folder.  I always like to name this folder "CitrixProfiles" so other sysadmins will leave it alone so they don't get Citrix all over themselves. You can just put it at the root of a share, but I normally do it like this:


You'll see that this path includes a template for what each user's profile will be.  I highly encourage you to use %username%.%userdomain% as in the picture, even if your environment only has a single domain.  Someday that might change and it's very difficult to go back and reorganize profiles.

I'd really prefer if Citrix allowed us to use the environment variable %userdnsdomain% but if you can read the text near the bottom of the profile policy image, that's not allowed.

Profile Permissions When setting profile permissions, you'll have two goals:  1) Users who are logging on for the first time must be able to create a profile folder, and 2) users should not have access to other people's folders.  To implement that, we'll use two different sets of permissions.

(As for share permissions, users will need modify rights and administrators will need full control.)

The first set of permissions will be for "Authenticated Users" - anyone who can log on to a Citrix server - and will apply to "This folder only":


It's not obvious what those permissions mean, but what it amounts to is that all authenticated users can create a subfolder in this folder, CitrixProfiles, but they cannot see into other user's folders. When a user creates a folder, they become the Creator Owner of that folder.  Creator Owner is an object that can be assigned permissions.  In fact that's how we'll grant the user rights.  Here is the second set of permissions we'll use:


As you can see, setting rights for the Creator Owner object, but not for the CitrixProfiles folder - for subfolders and files only (subfolders of the user's own profile folder, not CitrixProfiles itself).  By the way, I have seen some documents that suggest giving users the "Full control" permission on their profile folder.  DO NOT DO THIS!! I cannot emphasize that enough.  Users only need rights to modify the contents of their folder, they do not need to be able to set permissions on their folder as well.  Of course no user would ever do that but the process that saves their settings runs as the user.  Sometimes things go wrong, and if the account has the ability to change permissions then the permissions will get bunged up from time to time.

You should also apply permissions for whatever administrators' group you're using.  This group will need full control, and it will be applied to "This folder, subfolders, and files" so it will have permission to everything.

One other thing - if your organization has an IT Security team, and if that team ever has to update permissions on your profile folder, they'll probably take ownership of the file system, which will make all the users' profiles unavailable since they rely on the user being the owner.  I do have a Powershell script that can get a list of all the profiles, look up the user with the profile name (which is the username) and profile extension (the domain), and then grant the user explicit rights to the folder.  I'll post it in a few days, so watch this space.

Tuesday, July 28, 2020

Stupid Citrix Trick #6: Showing the Web Server Name in StoreFront

Problem: Sometimes when users are logging on through the StoreFront web page, you need to know what web server they are reaching.

Solution:  This is a pretty common thing, mostly for troubleshooting or finding other inconsistencies between how two StoreFront web servers are behaving.  When I started out a co-worker would just hard code the store name in the StoreFront HTML.  He couldn't figure out why, after he replicated from one server to another, only one server name showed up!

The solutions I found online typically involved setting a value in Internet Information Services that's unique to that server and the retrieving it, but I believe I've found a better way.

First you're going to want to create a file called GetServerName.aspx, and if you want the domain too then you may want to create GetServerDomain.aspx.  You can do that all in one file, but having separate ones gives you a little flexibility in how you display it.

Since these files will only be used on the web page and not the Citrix client, we'll put them in the customweb folder.

What I realized was that the server name and domain are available in environment variables on the server, and we can use very simple ASP.NET code to query and return them.  Here's GetServerName.aspx:

<%@ Page Language="C#"%>
<%=Environment.GetEnvironmentVariable("COMPUTERNAME")%>

That's the whole file.  GetServerDomain.aspx is almost as simple:

<%@ Page Language="C#"%>
<%=System.Net.NetworkInformation.IPGlobalProperties.GetIPGlobalProperties().DomainName%>

As I said, you drop those two files in the customweb folder, and then you go to the custom folder and edit script.js.

I am not a Javascript expert. I know just enough endanger my livelihood, but I always like to make sure my Javascript variables have some value, because if they don't it could cause issues in the code. So let's set some variables to an empty string:

var ServerName = ""; // The name of this StoreFront server.
var ServerDomain = ""; // The DNS name of the StoreFront server's domain.

Next we're going to jump down to a portion of script.js where the function CTXS.Extensions.afterDisplayHomeScreen gets called.  This is when the home screen has already been loaded, and that's when we want to make the changes.  Here's the code to insert the variable:

CTXS.Extensions.afterDisplayHomeScreen = function (callback)
{
    // Get the server name.
    $.ajax(
    {
        async: false,
url: "customweb/GetServerName.aspx",
success: function(Name)
{
ServerName = Name;
}
    });

    // -----------------------------------------------------------

    // Get the server domain.
    $.ajax(
    {
async: false,
url: "customweb/GetServerDomain.aspx",
success: function(Domain)
{
ServerDomain = Domain;
}
    });
};
 
Again, my Javascript skills are somewhat rudimentary.  In particular this is considered poor practice because it's a synchronous operation, and if there are any delays getting the data back it could cause problems.  Use at your own risk, and your mileage may very, but this has worked in our environment.

(Also, if someone has a simple method for doing an asynchronous callback, please drop it in the comments to I can be "with it" like all the cool kids.)

Saturday, July 25, 2020

Stupid Citrix Joke #1

I had a Citrix joke but everyone else is using it.

Thanks, I'm here all week.

Tuesday, July 21, 2020

Stupid Citrix Trick #5: Updating INI files

Problem: To configure some older programs we have to manipulate INI files. There's not a built-in way to do this in our preferred scripting language, Powershell.

Solution: Like so many things in IT, we must begin with a story about Lotus Notes.

For those not familiar with Notes, it's an email and workflow program, and its workflow capabilities are especially useful.  You can use it to set up a change request system, for example, with tickets being routed to various users for their approval.  For the time it was considered advanced.

So about ten years ago we had just upgraded Notes from version 7 to version 8.  The new version came with a redesigned graphical user interface which was much easier to use, but it still included the old GUI for users who preferred it.  Crucially, each interface had a different set of licenses, so if your account was licensed for the old interface, you needed to run that interface to stay in compliance.  Which interface the application launched with was configured by a command-line parameter, and since we had separate Active Directory groups for the users of each interface, it was simple to set up two Citrix published apps, each one assigned to the appropriate group and with the proper command-line settings.  And all was right with the world.

Then we changed our email system to Microsoft Exchange.  Users would not launch Notes to get their email as before; instead their workflow notifications would go to their Outlook inbox.  When they received one they'd just click a link in the message and it would launch Notes and take them directly to the right document.

And this was a huge problem! No longer could I use command-line parameters to configure the Notes interface.  Due to the limited number of licenses I couldn't just always use one or the other.  I had to find another way.

Oh, and just to add a dash of urgency, our senior executives were going on a retreat in a couple of weeks where they would only be able to access Outlook and Notes via Citrix, so I had to get this working ASAP.

But then, hallelujah! I found that you could use an INI setting in the Notes configuration file for each user that would configure the interface.  This INI file was stored in a known location in the user's home folder, so we could use a launch script for Outlook that would make sure that the INI file had the correct settings before launching, and when the user clicked a link to a Notes document, Notes would already be set up.

Too bad there's no way to change INI settings in Powershell.

Well, no native way.

Fortunately Powershell is the Swiss army knife of programming languages, and if something can be done, it can be done in Powershell, and in this case what we need to do is to use some functions from kernel.dll.

(Quick note: I am not an expert at this, and likely found it on the Internet. I'd like to give credit but this was at least eight years ago.)

For our purposes we'll pretend we're working with a file called "sample.ini", which contains this text:

[Section1]
Key1=FirstKey
Key2=SecondKey

[Section2]
Key3=ThirdKey

Here's the first function, Get-IniSetting, which reads from an INI file.  You can see the parameters are all required, and used to specify the file name, the section we want to work with, and the key we want to fetch.  The first and last are simple but for the section you'll want to use the name without the brackets. To get a key from [Section2] you'd just pass "Section2" to this function.


Function Get-IniSetting
{

[CmdletBinding()]
Param
(
    [Parameter(Mandatory = $True)]
    [string]$File,

    [Parameter(Mandatory = $True)]
    [string]$Section,

    [Parameter(Mandatory = $True)]
    [string]$Key
)

$Signature = @’
[DllImport("kernel32.dll")]
public static extern uint GetPrivateProfileString(
    string lpAppName,
    string lpKeyName,
    string lpDefault,
    StringBuilder lpReturnedString,
    uint nSize,
    string lpFileName);
‘@

    $type = Add-Type -MemberDefinition $Signature -Name Win32Utils -Namespace GetPrivateProfileString -Using System.Text -PassThru

    $builder = New-Object System.Text.StringBuilder 1024

    $type::GetPrivateProfileString($Section, $Key, "", $builder, $builder.Capacity, $File) | Out-Null
   
    $builder.ToString()

}


Here's what a call to that function would look like. In this case the result would be the string value "SecondKey":

Get-IniSetting -File "C:\Users\Graham\Documents\sample.ini" -Section "Section1" -Key "Key2"

Next up is the function to write settings to an INI file.  If you look at the parameters you'll see that it has a parameter set, so based on the parameters passed it can do a couple of different things.


Function Write-IniSetting
{

[CmdletBinding()]
Param
(
    [Parameter(Mandatory = $True)]
    [string]$File,

    [Parameter(Mandatory = $True)]
    [string]$Section,

    [Parameter(ParameterSetName = "SetKey")]
    [string]$Key,

    [Parameter(ParameterSetName = "SetKey")]
    [string]$Value
)

$Signature = @’
[DllImport("kernel32.dll")]
public static extern bool WritePrivateProfileString(
    string lpAppName,
    string lpKeyName,
    string lpString,
    string lpFileName);
‘@


    Switch ($PSCmdlet.ParameterSetName)
    {
        "SetKey"
            {
                If ($Value -ne (Get-IniSetting -File $File -Section $Section -Key $Key))
                {
                    $type = Add-Type -MemberDefinition $Signature -Name Win32Utils -Namespace WritePrivateProfileString -Using System.Text -PassThru

                    $type::WritePrivateProfileString($Section, $Key, $Value, $File) | Out-Null
                }
            }
        default
            {
                $type = Add-Type -MemberDefinition $Signature -Name Win32Utils -Namespace WritePrivateProfileString -Using System.Text -PassThru

                $type::WritePrivateProfileString($Section, [NullString]::Value, [NullString]::Value, $File) | Out-Null
            }
    }
}

This function does two different things based on what parameters are passed.  The first two, $File and $Section, are exactly the same as before, but the $Key and $Value parameters are optional.  If all four are specified then the function works as you'd expect, setting the value of the specified section and key, and creating it if it doesn't already exist.  Here's a what that call would look like:

Write-IniSetting -File "C:\Users\Graham\Documents\sample.ini" -Section "Section2" -Key "Key2" -Value "AnotherValue" 


But if you only include the $File and $Section parameters, it does something much different: it deletes the entire section. This can come in handy if you need to make wholesale changes.  This function call would delete Section2 and all the data it contains:

Write-IniSetting -File "C:\Users\Graham\Documents\sample.ini" -Section "Section2"

This is really a Stupid Powershell Trick more than a Stupid Citrix Trick - something that will come up many times - but it's extremely useful when dealing with older applications, all of which seem to live out their golden years on the Citrix farm.