Posts Tagged ‘XenDesktop’

I don’t have a lot of time for another post right now, but I found this one particularly interesting.

I was finding a lot of warning errors on my PVS servers.  There were numerous errors, but this was the most common.
The Citrix Broker Service failed to contact virtual machine ‘<machinename>’ (IP address ).
Check that the virtual machine can be contacted from the controller and that any firewall on the virtual machine allows connections from the controller. See Citrix Knowledge Base article CTX126992.
Error details:
Exception ‘Client is unable to finish the security negotiation within the configured timeout (00:00:05). The current negotiation leg is 1 (00:00:05). ‘ of type ‘System.TimeoutException’.

There were several articles on the subject, most of them talking about the VM’s registering, and deregistering, etc. but I didn’t have that problem. I ran down the articles, and none of the problems fit my situation.  Since I wasn’t really having a problem, I dropped the subject for a while.  I had some time recently to start looking back into the subject.

I had followed the best practices for the VM’s (or thought I had).  I ended up calling Citrix support, and went through everything.  We were going through all the same articles again, and not finding anything.  But, the Citrix tech noticed one thing.. I had the firewall turned off for the domain profile, but not for the private or public profiles.  We began discussing the subject, and why it was set that way.  Best practices say that the firewall be turned off for the PVS machines.  Since the machines already belonged to the domain, I turned off the domain profile by GPO.  (To me, this is a pretty typical security view – only enable/disable what you have to, and lock the rest).

It turns out that even though PVS is booting from the network, and that network connection is the very first thing established, as that streamed copy of Windows boots up, it does not recognize the network connection profile as the domain profile until well after the machine is up, and it has already tried to register.   Once it gets up far enough, the GPO takes effect, and the firewall comes down, the re-register works, and everything is good.   I turned around and adjusted the GPO to have the the firewall always off.  Success!  90%+ errors of the errors were gone.  I still get the occasional warning errors, but now they are unusual.

So, the next step will be to check the actual firewall configurations, and forcibly allow all the ports through the firewall in all profiles.

David F.

In my opinion, one of the great missteps that Citrix has made (and I pray they will fix it!) is that by default, XenDesktop does not set the CLIENTNAME variable.  Now that Citrix has become common place, a lot of software vendors are beginning to take advantage of the CLIENTNAME variable in their programs.  I work in the financial industry, and many of the products are not only Citrix aware, they also assign licensing on a per-physical seat basis by using CLIENTNAME in a Citrix environment. But, since XenDesktop doesn’t provide this, it’s a challenge to make this work properly.

My solution was to create a launcher executable that would get the actual clientname out of the registry, set it the variable and execute the application.  (I’ve had mixed results with the Virtual Channel Driver).  My language of choice for this is Winbatch.  (A trial version can be downloaded from http://www.winbatch.com.)  [The normal version of Winbatch lists for $99, and the compiler to create redistributable executables is $499.  However, the compiled exe’s can be distributed without limit].

There are 2 parts to my solution – the actual script file, and the INI file. I use an INI file for the configuration data, simply because the INI functions in Winbatch are extremely easy to use, and Winbatch’s ability to process command line parameters is not very good.. it’s not terrible, but it could be a lot better.

The INI File:
As is my pattern with most of my winbatch scripts, the INI file has the same root name as the script itself. The INI file has one section called Configuration, and only two entries.  To execute an external program, Winbatch needs the executable name, and the parameters as a separate item.  So, the CLAppLauncher.INI file is very simple:

[Configuration]
EXEName=<full path of executable>
EXEParams=<command line parameters>

The script itself is fairly simple, and includes some of my “boilerplate” Winbatch code.  But, it basically extracts the clientname from the registry, adds it to the environment, and then executes the program.  Part of the design was to make it very flexible so that it can be used with any executable file (hence the INI file).

Boilerplate Code:

WinHide("")
IntControl(12,5,0,0,0) ;Allow clean exit
;IntControl(38, 1, 0, 0, 0) ;Hide all errors
IntControl (28, 2, 0, 0, 0) ;Set the controls to use the system GUI font

WinHide(“”) – By default, Winbatch displays a large white dialog box while it is processing the script.  For me, this is virtually always undesirable, so as part of my boilerplate, I use WinHide(“”) to suppress this.

IntControl(12,5,0,0,0) – this is one of the internal control calls that Winbatch provides. This particular call lets Winbatch exit cleanly when closed, and to acknowledge a Windows shutdown in order to exit.  As an aside, the IntControl functions all have a numerical code, followed by up to 4 built-in parameters for the call.

IntControl(38,1,0,0,0) – this call suppresses all of the errors in the script.  As long as the error is not a terminating error, it prevents the script from exiting unnecessarily.  I typically leave it commented out (;), because I want to know when an error happens.

IntControl (28, 2, 0, 0, 0) – this call tells the Winbatch dialogs to use the system font by default.  Out of the box, Winbatch uses a much older “crude” looking font that is not visually appealing.  By using this call, I have the script look exactly like the default dialogs & menus etc.

The next step is to load the Extenders.  Extenders are DLL’s that can be downloaded freely from Winbatch’s website.  These DLL’s provide ‘wrapper’ functions for Windows API calls.  This allows you to perform programming functions without actually knowing how to do the programming API calls.  This allows scripts to really run like a program.  (I’ve actually downloaded programs online that were actually Winbatch scripts..)  In this case, I’m using the WILX extender which provides several “basic” functions such as MessageBox.

AddExtender("WILX44i.DLL")

Because I will be using some of the registry call functions from Winbatch, I like setting some default constants so that I can use plain english when making calls, and I don’t need to remember or lookup the numerical values.

REG_SZ = 1
REG_EXPAND_SZ = 2
REG_BINARY = 3
REG_DWORD = 4
REG_MULTI_SZ = 7
REG_QWORD = 11
The next line isn’t *critical* per-se.. but I like to keep my functions at the bottom of the script, so I jump to the Functions section to read them in and then return.  Processing wise it really doesn’t cost any processing power, but it makes it more legible in general.  (Most Winbatch scripts look like regular batch files, and I try to format all of my scripts in a much more “formal” looking manner.)  This particular script does not have any user-defined functions (UDF’s).
GoSub Functions
Further down in the script is the Functions section, along with the one function in this script and the Return statement.
:Functions

#DEFINEFUNCTION GetINIFile(inifile)
    mbOKOnly               = 0 ;Display OK button only.
    mbOKCancel             = 1 ;Display OK and Cancel buttons.
    mbAbortRetryIgnore     = 2 ;Display Abort, Retry, and Ignore buttons.
    mbYesNoCancel          = 3 ;Display Yes, No, and Cancel buttons.
    mbYesNo                = 4 ;Display Yes and No buttons.
    mbRetryCancel          = 5 ;Display Retry and Cancel buttons.
    mbCritical             = 16 ;Display Critical Message icon.
    mbQuestion             = 32 ;Display Warning Query icon.
    mbExclamation          = 48 ;Display Warning Message icon.
    mbInformation          = 64 ;Display Information Message icon.
    mbDefaultButton1       = 0 ;First button is the default.
    mbDefaultButton2       = 256 ;Second button is the default.
    mbDefaultButton3       = 512 ;Third button is the default.
    mbDefaultButton4       = 768 ;Fourth button is the default.
    mbApplicationModal     = 0 ;Application modal. 
                               ;The user must respond to the message box before continuing
                               ;work in the current application.
    mbSystemModal          = 4096 ;System modal. On Win16 systems, all applications are suspended 
                               ;until the user responds to the message box. On Win32 systems, 
                               ;this constant provides an application modal message box that always
                               ;remains on top of any other programs you may have running.

    mbOK                   = 1 ;OK button was clicked.
    mbCancel               = 2 ;Cancel button was clicked.
    mbAbort                = 3 ;Abort button was clicked.
    mbRetry                = 4 ;Retry button was clicked.
    mbIgnore               = 5 ;Ignore button was clicked.
    mbYes                  = 6 ;Yes button was clicked.
    mbNo                   = 7 ;No button was clicked.
    sINIFile = FileLocate(inifile)
    if sINIFile == ""
        sMessage = strcat("Could not locate - ", inifile, @CRLF, @CRLF, "Please contact IT")
        xMessageBox("Missing INI File", sMessage, mbCritical)
        exit
    endif 
 return sINIFile
#ENDFUNCTION
return
#DEFINEFUNCTION Name(Parameters)  – this defines a function, along with parameters.  A function call is limited to 5 parameters.  One major limitation of the Winbatch functions is that the variables are 100% isolated to the scope of the functions.  Any variables set outside of the function itself are NOT accessible.  To that end, the variables sometimes have to be defined a second time inside of the function to work properly.
[sINIFile = FileLocate(infile)]
The next several lines define the constants for xMessageBox.  xMessageBox is a wrapper for the standard MessageBox function. The next line after that uses the FileLocate command.  This is a default command for Winbatch (it does not need an extender).  FileLocate actually searches the local directory, followed by the Windows path.  If the file is located, the full path to the file (including the file name) is returned.  If not, a blank string is returned.  inifile is a variable that represents the file we are looking for.
[if sINIFile == “”]
In the next line, I check to see if a blank string is returned.  Notice the 2 = signs (==).  In Winbatch, assignments use a single =, while a comparison uses ==.  If you use an single = in a comparison, it will generate an error.  You’ll notice that the “feel” of the script is similar to vbscript.

[sMessage = strcat(“Could not locate – “, inifile, @CRLF, @CRLF, “Please contact IT”)]
In this line, I’m creating a message prompt for the Messagbox.  Using strcat(), I am concatenating several string fragments.  You can basically concatenate an unlimited number of strings together with strcat.  The limitation is that a single line in a Winbatch script is limited to 2048 characters. That’s a lot of data, but for legibility, it’s easier to concatenate it first, and then use the variable.  You’ll also notice the internal macro.  The macro starts with @, and there are a number of them that get used.  Most of them are pretty obvious what the represent (@CRLF, @CR, @LF, @REGMACHINE, @REGCURRENT, @TAB, etc.)

[xMessageBox(“Missing INI File”, sMessage, mbCritical)]
This pops up the actual messagbox and notifies the user that the file can’t be located.  The first parameter is the Title of the message box, the second parameter is the prompt (body) of the message box, and the last one indicates the settings for the buttons, icons etc.  Because it is a numerical value, it’s easy to add them together.  For example, you could do mbCritical + mbYesNo. and that will show the stopsign icon of a critical error and the Yes and No buttons. If you want to use the results of the message box, then you would set a variable equal to the xMessageBox call.  So, continuing this example, you could do sResult = xMessageBox(“Test dialog”, “Do you like the test dialog?”, mbCritical + mbYesNo).  And then you can test the results with the other variables that are defined (mbYes or mbNo).

[Exit]
The next line is a simple Exit which just exits the script.  This prevents the script from trying to continue if it can’t find the INI file.  It’s a graceful way to keep it from erroring out if the INI file is not available.

[EndIf]
Every IF call must be terminated with an EndIf call.  This tells Winbatch where the IF test is completed.  (And just like vbscript, you can add in ElseIf and/or Else clauses.

[return sINIFile]
In this function, we are returning the full path of the INI file.

[#ENDFUNCTION]
Just like the EndIf, we need to tell Winbatch where the end boundary of the function is.  (Also notice that definefunction and endfunction begin with # signs.  This is critical!

[sINIFile = FileLocate(“CLAppLauncher.ini”)]
Now, our function is defined.. and we’re able call it.  I set a variable equal to the function to get the path.  As mentioned above, if the file isn’t found, the script exits, so we don’t have to worry about anything.

[sRunString = IniReadPvt(“Configuration”, “ExeName”, “”, sINIFile)]
In this line, I’m getting the executable name to run.  Again, if the executable is not in the Windows path, then you need to specify the full path in the INI file.  But, the syntax is pretty self-explanatory (with one minor exception).  IniReadPvt – reads a “private” ini file which is basically anything that is not the win.ini file. The first parameter is the section name, the second value is the value name, the 3rd value is a default value that is used if the value is not set in the INI file, and the last parameter is the name of the INI file.  Because the 3rd parameter is set to blank “”, nothing is passed as the default.  If we specified something like Notepad.exe, then it would run notepad.exe if ExeName was not set.

[sRunParams = IniReadPvt(“Configuration”, “ExeParams”, “”, sINIFile)]
This is virtually identical to the line above.. we’re just getting the parameters of the command line.  Anything you would use in a command line would be put here.  As an example, Outlook has a command line switch /cleanfreebusy.  So, the parameters for the command line would be /cleanfreebusy.  The INI file line would show ExeParams=/cleanfreebusy.

[sClientName = RegQueryEx(@REGMACHINE, “Software\Citrix\ICA\Session[ClientName]”,””,REG_SZ)]
Now, we go extract the client name from the registry.  I am using the RegQueryEx function (Registry Query Extended) to read the value.  RegQueryEx requires an open registry handle – We’re using the macro @REGMACHINE to represent HKLM.  (By default, Windows always has handles open to the root keys like HKLM, HKCU, HKU, etc.).  The next parameter is the string that represents the path in the registry.  It’s important to note that the actual VALUE name is surrounded by [ ] marks in the string.  The next parameter is blank “”.  This is the delimiter used in REG_MULTI_SZ values, and unless the value is a REG_MULTI_SZ it is ignored.  The last parameter is the type of registry vale.  Here I am using the constant that I had previously defined for legibility. I could just as easily used RegQueryEx(@REGMACHINE, “Software\Citrix\ICA\Session[ClientName]”, “”, 1), but someone following up behind me would not necessarily know what the 1 meant.

[EnvironSet(“ClientName”, sClientName)]
This sets the environment variable. It is important to note that EnvironSet only affects the current environment for the script.. it does *not* affect the environment outside of the script execution itself.

[If sRunParams == “”]
I am validating if the sRunParams is blank or not.  I am checking this to make sure that I don’t generate an error on the next call.

[RunEnviron(sRunString, “”, @NORMAL, @WAIT)]
The RunEnviron command actually runs the executable using the environment that exists inside of the script.  By combining this statement with the previous EnvironSet, we’re able to modify the environment as we want and executing the specified program in that context.  It runs the executable string without any parameters (1st and 2nd parameters).  The 3rd parameter indicates the Window style.  Again, Winbatch provides several macros so that you do not need to remember what the actual numeric values are.  In this circumstance, there are options like @HIDDEN, @MAXIMIZED, etc.  The last parameter indicates if the script should wait for the program to exit and return.

[Else]
Pretty self-explanatory.

[RunEnviron(sRunString, sRunParams, @NORMAL, @WAIT)]
This is identical to the RunEnviron above, other than that I’m passing the runtime parameters to the RunEnviron command.

[EndIf]
This ends the IF construct.

That’s the sum of the file.. it simply reads the configuration file for the executable information, reads the ClientName, sets the variable, and then runs the program.  By providing the INI file it is very flexible, and using this setup, it is easy to get the needed ClientName variable.

Here is the code in it’s entirety

; ======================================================================
; 
; WinBatch Source File -- Created with SAPIEN Technologies PrimalScript 2012
; 
; NAME: CLAppLauncher.wil
; 
; AUTHOR: David Figueroa
; 
; COMMENT: Script to set the ClientName variable and launch the specified application
; 
; ========================================================================
WinHide("")
; ========================================================================
; Add Extenders
; ========================================================================
AddExtender("WILX44i.DLL")
; ========================================================================
; Set internal controls
; ========================================================================
IntControl(12,5,0,0,0) ;Allow clean exit
;IntControl(38, 1, 0, 0, 0) ;Hide all errors
IntControl (28, 2, 0, 0, 0) ;Set the controls to use the system GUI font

; ========================================================================
; Constants
; ========================================================================
REG_SZ = 1
REG_EXPAND_SZ = 2
REG_BINARY = 3
REG_DWORD = 4
REG_MULTI_SZ = 7
REG_QWORD = 11

; ========================================================================
; Main Routine
; ========================================================================

GoSub Function
sINIFile = FileLocate("CLAppLauncher.ini")
sRunString = IniReadPvt("Configuration", "ExeName", "", sINIFile)
sRunParams = IniReadPvt("Configuration", "ExeParams", "", sINIFile)
sClientName = RegQueryEx(@REGMACHINE, "Software\Citrix\ICA\Session[ClientName]","",REG_SZ)
EnvironSet("ClientName", sClientName)
if sRunParams == ""
    RunEnviron(sRunString,"", @Normal, @WAIT)
else
    RunEnviron(sRunString, sRunParams, @Normal, @Wait)
endif

:FUNCTIONS
#DEFINEFUNCTION GetINIFile(inifile)
    mbOKOnly = 0 ;Display OK button only.
    mbOKCancel = 1 ;Display OK and Cancel buttons.
    mbAbortRetryIgnore = 2 ;Display Abort, Retry, and Ignore buttons.
    mbYesNoCancel = 3 ;Display Yes, No, and Cancel buttons.
    mbYesNo = 4 ;Display Yes and No buttons.
    mbRetryCancel = 5 ;Display Retry and Cancel buttons.
    mbCritical = 16 ;Display Critical Message icon.
    mbQuestion = 32 ;Display Warning Query icon.
    mbExclamation = 48 ;Display Warning Message icon.
    mbInformation = 64 ;Display Information Message icon.
    mbDefaultButton1 = 0 ;First button is the default.
    mbDefaultButton2 = 256 ;Second button is the default.
    mbDefaultButton3 = 512 ;Third button is the default.
    mbDefaultButton4 = 768 ;Fourth button is the default.
    mbApplicationModal = 0 ;Application modal. 
                           ;The user must respond to the message box before continuing
                           ;work in the current application.
    mbSystemModal = 4096 ;System modal. On Win16 systems, all applications are suspended 
                         ;until the user responds to the message box. On Win32 systems, 
                         ;this constant provides an application modal message box that always
                         ;remains on top of any other programs you may have running.
    mbOK = 1 ;OK button was clicked.
    mbCancel = 2 ;Cancel button was clicked.
    mbAbort = 3 ;Abort button was clicked.
    mbRetry = 4 ;Retry button was clicked.
    mbIgnore = 5 ;Ignore button was clicked.
    mbYes = 6 ;Yes button was clicked.
    mbNo = 7 ;No button was clicked.

    sINIFile = FileLocate(inifile)
    if sINIFile == ""
        sMessage = strcat("Could not locate - ", inifile, @CRLF, @CRLF, "Please contact IT")
        xMessageBox("Missing INI File", sMessage, mbCritical)
        exit
    endif 
    return sINIFile
#ENDFUNCTION
return

That’s it till next time 🙂