I’ve been asked on more than a few occasions whether it is possible to deploy Citrix policies through AppSense DesktopNow Environment Manager, and I’ve been trying to find information on this for some time (just ask Carl Webster, who I’ve bugged more than a few times for facts on this subject). I can see where the motivation for this comes from – it’s the “everything under one roof” drive that I touched on yesterday when we discussed Group Policy Actions in EM. If you can apply your Citrix policies through Environment Manager, again, it is one less console to check for your support staff. You can also leverage the power of EM to deploy Citrix policies based on the broad spectrum of Conditions and Triggers, which dwarf those available through the Citrix native method.
Where are policies stored?
Citrix policies, since the advent of XenApp 6.x, can be deployed “old-style” through the IMA, or alternatively via Active Directory Group Policy if you’ve loaded the Citrix Group Policy Management add-in. However, rather disappointingly, they can’t be managed via ADM or ADMX files. To quote Citrix directly – “Citrix policies are associated with Group Policy Objects (GPOs) using console extensions. Citrix policies are stored in Sysvol together with the ADM files, but they are not ADM templates. There is no Active Directory schema extension.”
They may not be manageable by ADM files, but they certainly are written to the Registry, which allows us to manage them through AppSense Environment Manager Registry Actions. Strangely enough, both Citrix User and Machine policies write to the HKEY_LOCAL_MACHINE area of the Registry. The areas they write to are
x86
MACHINE – HKLM\Software\Policies\Citrix
USER – HKLM\Software\Policies\Citrix\<SessionID>
x64
MACHINE – HKLM\Software\Wow6432Node\Policies\Citrix
USER – HKLM\Software\Wow6432Node\Policies\Citrix\<SessionID>
where <SessionID> is the Session ID of the user.
Finding the relevant Registry values
So, if we want to manipulate Citrix policy settings through Environment Manager, firstly, we need to work out which Registry values under these keys correspond to each setting. Thanks to Tom over on the Citrix forums, I was pointed in the direction of this article – http://support.citrix.com/article/CTX135039 – which links to a spreadsheet containing the Registry keys for all of the available Citrix XenApp 6.x policies.
Update – the mighty Carl Webster has put together a custom Excel file that details all of the settings for 7.x as well, check it out at http://carlwebster.com/group-policy-settings-reference-citrix-xenapp-xendesktop/
In the interests of keeping this blog article reasonably short, we will simply elect to use AppSense Environment Manager to set a couple of Citrix policies – one machine, and one user. We will set the User policy to set up shadowing, and the Machine setting for licensing (server name and port).
Now, you may be thinking that first we may need to run a check for the CPU architecture type, as there are different Registry keys for both x86 and x64 platforms. However – as we are aiming this article at XenApp 6.x, you may recall that this version of XenApp only runs on x64 platforms. If you were using the same process to deploy XenDesktop settings to x86-based endpoints, you would need to put in a check for the architecture type, but for the purposes of this article, it’s not necessary.
The logical first step, then, is to find a way to extract the session ID of the user, otherwise we won’t be able to work with the Registry keys for the user policy items. Now, in Citrix terms, this doesn’t apply to the RDP session id, but the actual %sessionname% variable which is usually something along the lines of ICA-TCP#x. We will have to strip away the protocol and the hash to leave us with the SessionID, and then write this somewhere. The most straightforward way to do this would be, I believe, to write this to a new environment variable, so we will create a Reusable Node to do this and reference it during the Logon process. We will add a Custom (scripted) Action with the following options set
And then add the following code to it…please, please, please forgive my ultra-rudimentary PowerShell and the overload of variables – all suggestions about making it better are welcomed.
$session = $env:sessionname
$split = $session.split(“#”)
$split1 = $split[1]
[Environment]::SetEnvironmentVariable(“SessionID”, $split1, “User”)
It’s interesting to note that we’ve had to use the .NET Framework and its SetEnvironmentVariable method to make this variable permanent for the user, otherwise the environment variable would have been discarded as soon as the PowerShell process exited.
Now, we can reference this in the Logon trigger somewhere so that it is set before we get down to the nitty-gritty of setting the Citrix policies in our configuration. In this example, we’ve called the Reusable Node from a Node Group quite high up in the configuration, and henceforth early in the logon process.
Note – a lot of you are probably aware that you are supposed to declare environment variables in the root of the Trigger to get them to function optimally. In this case, as we’re not doing it native to EM, we can’t really do it in the root. I will look at a way to tidy this up in future – for the moment, we’ve had to sit it as high as possible. The screenshot below shows it added to a Node Group along with some other settings we want to apply fairly early.
So now we’ve captured our Citrix Session ID into a variable which will allow us to do the User side of this without error, we can go to work on setting our Citrix policy items by writing to the Registry. What we need now is a way to ensure that we only write these settings into a session that requires them. In this case, we are working with XenApp 6.x sessions, so we will need to write a Reusable Condition that identifies a XenApp 6.x system. How can we do that?
Filtering the XenApp 6.x systems
Well, XenApp 6.x only runs on Windows 2008 R2, which is a start. Don’t forget we need to write a Condition to identify this on a machine and a user level, as we need to apply policies to both. So for the machine Condition, we will create a Reusable Condition and populate it with these two dependent Conditions (in that they are nested inside each other to create an AND)
These Conditions check for a Windows 2008 R2 TS-enabled system on which a file called ImaSrv.exe is present in the specified folder. This should pretty much ensure that the system is running XenApp 6.x
For the user Conditions, we can again create a Reusable Condition but use a slightly different pair of nested Conditions
What we have now is a Condition that the OS must be 2008 R2 TS-enabled, and the user must be using the ICA protocol to connect – again, filtering out anything but XenApp 6.x sessions.
Setting the Machine policies
So, in the Computer Startup trigger we can now reference the first Reusable Condition we just created, preferably in a logical position in the configuration.
Now we need to create the Registry Actions to enforce our Citrix policies for the machine. If you can recall that far back, we were going to set the Licensing, so we will reference the Citrix policies spreadsheet we linked to earlier and create the required Registry Actions as children of the parent Reusable Condition.
This value will enforce the License Server host name (change the value as required) |
This value will enforce the License Server port (normally 27000, but change as required) |
Note that the example above shows the value in hexadecimal, but it is in fact set to the default of 27000 for the license server port.
What might also be prudent is to enter a description for these Actions that tells you (or anyone following your work) what the Actions do, as shown below
So once you’ve done all the Citrix policy settings you require for the Machine, we can then move on to the user part.
Setting the User policies
We can now reference the second Reusable Condition we created at an appropriate level in the Logon trigger.
And inside this Condition we will add the appropriate Actions. We were intending to create the user policies for shadowing at this point. Note that we will now start to leverage the %SessionID% variable that we created. Also note that these are HKLM entries, not HKCU like you’d expect.
This sets the setting for Allowing Shadow Input |
This sets the setting for turning on Shadow logging |
And this turns on the setting for notifying the user about Shadowing |
One thing you may have to do is (testing should indicate whether it is necessary or not), given that these are all HKLM entries, is change the permissions on the required Registry keys using something like subinacl.exe or a bit of scripting so that the users can write to them properly – running as System will introduce problems with the SessionID variable.
This will ensure that the entries are written correctly.
Tidy-up
Finally, to avoid problems with the %SessionID% variable being maintained across sessions, it would be sensible to configure another Reusable Node to be called in the Logoff trigger. This Action will remove the environment variable for SessionID, ensuring that if you are using the sort of profile management that could persist this, it will not be saved.
Summary
So, now, should you wish to do it, you can move the enforcement of your Citrix Policies out of the Citrix console or ADUC, and into the AppSense Environment Manager console.
I’m sure plenty of you are saying “why?” I would only do this if there is something specific to gain from the change – whether it is using the breadth of triggers and Conditions available from the EM console, or simply allowing people access who otherwise wouldn’t be able to administer the Citrix policies because of political or organizational issues. I’ve certainly been asked if it’s possible a few times, although I concede that most people will probably opt to manage user policies through this rather than machine ones. But it’s another good demonstration of what you can achieve with a piece of software like AppSense DesktopNow, if you put some time and effort into achieving it.