Archive for the ‘Applications’ Category

StoreFront & 1030 Errors

Posted: January 13, 2014 in Applications, Citrix

I’m in the process of building my StoreFront environment in preparation to migrate to XenDesktop 7. However, during some initial testing, we had several issues trying to launch applications, most of them resulting in 1030 errors. There are several links & pages around 1030 errors when trying to launch an application through StoreFront. Well, during my testing, these all ended up being relatively useless.

The layout:
2 Web Interface 5.4 servers, load balanced through a Netscaler
2 StoreFront 2.1 servers, not load balanced yet.
1 XD 5.6 farm
1 XA 6.5 farm

The internal clients currently use the WI VIP (virtual ip address) to launch published XD desktops, and the desktops launch some published apps from the XA farm. The Netscaler is also an Access Gateway used for external access.

During the initial SF build, we configured and tested the Receiver for Web on the server, configured the needed VIP for the SF (not in use yet), and generally got the environment prepared for more testing. Other projects reared their heads temporarily, but I picked the testing back up. I modified the URL for my Receiver 3.4 CU2 Enterprise to point at the SF for testing. The SF icons would not appear, and nothing was working. A quick call to Citrix, and I found out that you need to put in a full path to a URL that does not exist directly in the IIS path on the SF server. It turns out that enabling Legacy support creates a virtual path that is embedded in the compiled code for all of the stores. By taking the store path, and tacking on /pnagent/config.xml will provide the needed configuration file.

Now, the icons appeared, but the apps simply would not launch. The connections kept failing with 1030 errors. I ran through several testing scenarios, and a very long call with Citrix. I came up with some important facts.

1. Receiver 4.1 would not launch the applications correctly from StoreFront, but launched perfectly from Web Interface.
2. Receiver 3.4 would not launch the applications correctly from StoreFront, but launched perfectly from Web Interface.
3. Receiver 3.4 Enterprise would not launch the applications correctly from StoreFront, but launched perfectly from Web Interface.

Ultimately, we turned on ICA logging* (see below) and compared the ICA files being generated. At first glance, the ICA files were identical except for items that should be different (like Session keys, session reference ID’s etc.) — all of the “key” items were correct. We ran the tests multiple times, but on a line by line comparison, one thing really stood out. There was a reference to the Proxy server being set to Automatic with the Receiver 4.1. That flagged a major note for me.. We have an authenticated proxy that in our WI environment requires us to set it for proxy as Client-defined, instead of the default automatic. I mentioned this to the folks at Citrix, and it turns out there is *not* a GUI entry for the proxy settings in SF 2.1 (and prior). Citrix was able to turn up an article () that specifically mentions setting the proxy in the default.ica file in a SF 1.2 server for a completely unrelated situation. However, on a guess, we tried setting it, and there it was… the applications launched correctly from SF with the 3.4 Receiver Enterprise. I will not be surprised if Citrix generates another article based on this.

Settings:
The default.ica file lives in c:\inetpub\wwwroot\citrix\{storename}\app_data\ directory.
The line you would add is ProxyType=None in both the [WFClient] and [Application] sections. (Also, be aware, there are also directories for c:\inetpub\wwwroot\citrix\{storenameweb}\. There is no default.ica directory in the location, and there should not be.

*For ICA logging, there are a couple of registry entries to set:

(x86)
HKLM\SOFTWARE\Citrix\ICA Client\Engine\Configuration\Advanced\Modules\Logging
(x64)
HKLM\SOFTWARE\Wow6432Node\Citrix\ICA Client\Engine\Configuration\Advanced\Modules\Logging
LogFile=c:\temp\ica.log
LogICAFile=true

Be sure to precreate the directory for the log path. No reboot it is required.. the logging takes effect immediately.

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 🙂

This is a recent problem I ran across.  One of my users was trying to create a hyperlink in Microsoft Publisher 2010.   However, during that process, she received an Out of Memory error.
OutOfMemory

However, like many long term Citrix admins will realize, an Out of Memory error is much more frequently an issue of access to the file system.  This was my first suspicion to the problem.

My first step was to check the various registry paths in HKEY_CURRENT_USER for that user.  As I went through, I checked multiple locations that could have contained a clue to the problem.  I went through the various keys under HKEY_LOCAL_MACHINE\Software\Microsoft\Office\14.0\Publisher.  There were no values or keys that contained any paths that might have been an issue.  In fact, there were no file paths that I could find, other than the typical MRU (Most Recently Used) entries.  Those file paths were all files that she had access to.

I asked more questions about her activities, and what was happening.  She indicated that she was also having trouble saving some files, unless she went through a very specific set of gyrations to open the file, and even that sequence was not foolproof.

That’s when I pulled out one of my favorite tools, SysInternal’s Process Monitor.  (For people unfamiliar with this tool, it is a utility developed by Mark Russoinovich before he joined Microsoft, and it has been updated many times since he joined Microsoft. The tool monitors and captures all disk activity, network activity, registry activity, etc. taken by Windows.  Even a few seconds capture will show thousands of entries.)

I configured Process Monitor to only watch the activity from Publisher’s executable – mspub.exe.  In order to minimize the captured data, I turned on the capturing, tried to create a link, and turned off the capture.  I used the Search tool, and looked for ‘ACCESS DENIED’).  I found a few file system entries for this. The ones that caught my eye however, were these:

AccessDenied

What really caught my attention was the operation and the associated directory – Create File and \\server\users.  This particular directory is the user’s home directory, and I know that several folders are redirected by policy to the user’s home directory.

Back to the user’s registry!  For those unfamiliar with the spot in the registry for Folder Redirection, it is located at HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders.  The various values control the various special folders’ locations.  So, with this information, I changed the various paths in her registry from their redirected locations, to local paths on her system.  In order to make sure that Windows ‘saw’ the correct location was to force it to reload it.  Logging her off and back on would ruin my changes, and given the amount of work it would take to get her out of those policies, that did not make sense.  The quick and dirty method to do this is to force Windows Explorer (the actual shell) to restart.

Method 1:
Press Ctrl-Alt-Del and start Task Manager
Find the explorer.exe process, and select End Task.
Method 2:
Open an elevated cmd prompt
Enter in the command ‘tskill explorer’ or ‘taskkill /im explorer.exe’ (note that tskill requires that you not put the extension in the file name, while taskkill requires that you do put the extension name in).

Explorer will automatically restart, regenerate its environment, including the user’s folders. Knowing that I was close to an answer, and being impatient, I changed all the paths, restarted explorer (method 2 with tskill).  I relaunched Publisher 2010, and tried to create a hyperlink — Success!.  Now, it was just a matter of changing the paths back, and then changing them one at a time to locate which folder was causing the problem.

It turned out to be the Desktop folder.  It looked like Publisher was trying to create a file on the root of the redirected desktop folder.   I changed her desktop folder to her mapped home directory (e.g. u:\desktop) and Publisher continued to work properly.  On a hunch, I tried this same tactic without restarting explorer.  Again, success!  So, restarting explorer was *not* necessary, and Publisher had to have the “correct” value to work correctly.  Further testing, revealed that

  • She had to have permissions to create a folder at the root of the home directory path. Giving her that permission temporarily worked, but was not a good long term solution.
  • Changing the folder redirection to a mapped drive worked, but was really not a workable solution – I didn’t want to have to revisit this architecture.

The next challenge was to figure out how to correct this situation without changing her Desktop redirection.  (Re-architecting the entire structure (directories & policies) for an occasionally used application was out of the question). Like many other times, I figured a good script should handle the problem.

The script below does exactly this.  It reads in the current setting, changes the Desktop redirection to the mapped drive so that the user is unaffected and launches Publisher.  This worked also.  But, leaving the redirection changed is risky.  So, I set the path back to the original location that I had read in originally.  On first iteration, this script failed.  But, this had worked earlier by hand.. the conclusion was that the script was changing the value back to the original path was happening before Publisher had a chance to read it in.  I added the sleep delay at 5 seconds, and that failed.  I tried it at 30 and it succeeded. It was just a matter of tuning the time.   The final script also launches Publisher itself.

Here is the script:

Option Explicit
Dim wshShell
Set wshShell = CreateObject ("WScript.Shell")
Dim sDesktopPath, sHomeSharePath, sHomeDrive, sHomePath, sMsg, sTempDesktopPath, sPublisherPath, sRegDesktopPath, vbQuote
'Initialize vbQuote for adding " marks to strings
vbQuote = chr(34)
'Get the original desktop path
sRegDesktopPath = "HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders\Desktop"
sDesktopPath = wshShell.RegRead(sRegDesktopPath)
'Get the home directory information from Environment variables
sHomeDrive = wshShell.ExpandEnvironmentStrings("%HOMEDRIVE%")
sHomePath = wshShell.ExpandEnvironmentStrings("%HOMEPATH%")
'Locate the install for Publisher
sPublisherPath = wshShell.RegRead("HKLM\Software\Wow6432Node\Microsoft\Office\14.0\Publisher\InstallRoot\Path")
sPublisherPath = sPublisherPath & "mspub.exe"
'Surround the publisher path with " marks to allow it to launch through vbscript
sPublisherPath = vbQuote & sPublisherPath & vbQuote
'Check if the Desktop directory is a UNC path
If Left(sDesktopPath, 2) = "\\" and lcase(right(sDesktopPath, 7)) = "desktop" Then 
 'It is a UNC path, so put in the new home value path
 sTempDesktopPath = sHomeDrive & sHomePath & "Desktop"
 wshShell.RegWrite sRegDesktopPath, sTempDesktopPath, "REG_EXPAND_SZ"
'Launch Publisher with the new value in place
 wshShell.Run sPublisherPath, 1, vbFalse 'Wait for 15 seconds
 WScript.Sleep 15000 'Put the Desktop path back into place to avoid other potential issues. 
 wshShell.RegWrite sRegDesktopPath, sDesktopPath
Else
 'Doesn't look like a redirected path. Just launch publisher
 wshShell.Run sPublisherPath, 1, vbFalse
End If
Set wshShell = Nothing