Wednesday, July 8, 2020

Stupid Citrix Trick #4: Profile Bloat and INetCache

Problem: User profiles are unusually large.  Logons and logoffs are taking an extremely long time, and disk space is running low in the network share where profiles are saved.

Solution: Almost every user with a Citrix profile will have a folder in that profile called INETCACHE.  Based on the name alone, what would you expect that folder to do?  Cache internet files, right?  And that's exactly what it does, but unfortunately there's more to the story.

First of all, that's not the folder where Internet Explorer files are cached.  They are actually cached in a subfolder, INetCache\IE.  It's the IE folder that gets bloated and cause issues. There's another good reason for putting in in its own subfolder: the INetCache folder contains quite a bit of stuff besides cached Internet Explorer files.

I don't know if it's this was for all Office versions, but I do know that Office 2010 and Office 2013 also store cached resources in the INetCache folder, each in as separate subfolder.  Not only that but Office does not like it one bit if you delete those folders.  It will complain about it without doing anything useful, such as automatically recreating them at application launch.  If they do get deleted you'll have to recreate them manually or reset the user's profile.

So how to limit bloat without affecting Office or other apps that use that folder.  Naturally, Citrix has you covered.

In the Profile section of XenApp 7 policies there are a lot of settings for user profiles, from the most basic, such as the path to the profile share and the names of the user folders, to such things as... folder inclusion and exclusion policies.

First, here is the exclusion policy to make sure that INetCache is included in the user's profile:


(Profile paths are always relative to the root of the user's profile, natch.)

So that setting will include the entire INetCache folder.  Great!  Well, sorta.  We don't want to include the IE folder.  Remember the catchphrase "There's an app for that!"  You guessed it: there's a setting for that!  And here it is:


Yes it's JUST THAT SIMPLE.  You can explicitly exclude subfolder of folders you have already explicitly included.  Apply these two policies and your profile woes are over.

But Graham (you say), we still have all these bloated folders on disk.  Is there any way we can clean out the IE folder that's already there?  I'm glad you asked!  This is Citrix, of course there's a way.

May I present to you the Logon Exclusion Policy.


As you can see we have selected to delete excluded files and folders from the user's profile.  Once we apply the two policies above, this policy will delete the files in the IE folder (and any other excluded folders).  The other values for this setting are to synchronize the excluded files and folders with the user's profile, or to ignore the files.

Disinfecting your profiles this way reduce disk usage and improve logon performance, which should keep both IT and your end users happy.

Sunday, July 5, 2020

Stupid Citrix Trick #3: Fixing Provisioning Services Client Drive Letters

Problem: Citrix virtual machines using Provisioning Services are getting wrong drive letters.

Solution: Provisioning Services (hereafter "PVS") is a Citrix method of deploying and updating virtual machines using a standard image (or several different images).  I believe it predates Machine Creation Services ("MCS"), which is simpler to implement, but PVS is still very useful as the server image is stored as a file completely independent of any particular virtual machine; in MCS, a VM disk or an snapshot of one is replicated.

On a server running on PVS, the C: drive is virtual. It physically exists only as a file on the PVS server; each VM running the image has a local hard drive to cache the image file. But if you are not using the default Windows drive letters, this can cause problems if two circumstances are met: 1) not all of your VMs are deployed from the same template, and 2) you are not using the default drive letters.

First lets talk about the drive letters.  In PVS the virtual disk running from an image will always be C:.  By default, Windows will name the physical drive with the next drive letter, D:.  If you want to change that drive letter to anything else, then Windows needs to be able to remember your settings, and it does this by saving the selected drive letter and the unique ID of the drive in the registry.

So at your first site, the artfully named Site A, everything is fine and dandy.  You deploy a bunch of virtual machines, all from the same VM template, and they load the image. Raises and promotions for everyone!

But there's a problem.  When you create some VMs at the notoriously backwards and incompetent Site B, the drive letters are not correct.  In fact they revert to the defaults. All published applications that point to drive J: (an extremely cromulent drive letter) are failing because what should be J: is actually D:. What possibly could have gone wrong?

Remember when I said Windows saves a non-default drive letter in the registry, along with the drive's unique ID?  Different drives have different signatures, and if the ID doesn't match what's in the registry, default drive letters are used.

Happily there's a simple solution.  First, you'll need to create a scheduled task that runs at startup, or better yet, startup plus two minutes.  This task will first examine what's called a personality file, which PVS manages.  From this file you can tell if the disk is in private (editing) mode, or standard (running) mode.

If the disk is in private mode, the task saves the disk ID to a folder on the C: drive.  If it's in standard mode, the disk compares the saved ID to the local disk.  If they are not the same then it updates the disk ID and reboots. This works because disk IDs don't have to be globally unique, unlike a MAC address for example; they only have to be unique within the same computer.

Here's a sample script in Powershell:

# This program is used to ensure that all Citrix PVS session hosts
# have the same disk UniqueID value as the editing server where the
# image was last editied.
#
# To do this, it first looks in C:\Personality.ini and checks to see
# if the disk is in Private (_DiskMode=P) or Standard (_DiskMode=S)
# mode.  If Private mode, it SAVES the UniqueID to
# C:\SetDiskID\DiskID.txt.
#
# If Standard mode, it checks to see if the UniqueID matches the
# contents of C:\SetDiskID\DiskID.txt, and if it does not match,
# it SETS the ID using Diskpart.

# Set some variables.
$PvsDiskCaption = "Citrix Virtual Disk SCSI Disk Device"

$LaunchDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$LaunchDir = $LaunchDir.TrimEnd("\")

$DiskIDPath = $LaunchDir + "\DiskID.txt"
$DPInputPath = $LaunchDir + "\DpInput.txt"


# First, check to see what mode we are in.

$PersonalityFile = "c:\Personality.ini"

If (Test-Path -Path $PersonalityFile)
{

    # Read in the file.
    $Personality = Get-Content -Path $PersonalityFile

    # Now examine each line until you find the _DiskMode field.

    ForEach ($PrsLine in $Personality)
    {
        $Matched = $PrsLine -match "(.*)\="

        # If the first match group equals _DiskMode, save the value.
        If ($Matched -and ($Matches[1] -eq "_DiskMode"))
        {
            $PrsLine -match "\=(.*)"

            $DiskMode = $Matches[1]
        }
    }


    # Identify which disk is the non-PVS disk.

    # Get the disks in this server.  There will be at least two, a fixed disk and a PVS disk.
    $Disks = Get-Disk

    # Find the first disk that does NOT have type "Citrix Virtual Disk SCSI Disk Device".
    $TargetDisk = $null
    $DiskCount = $Disks.Count

    For ($CurrDisk = 0$CurrDisk -lt $DiskCount$CurrDisk++)
    {
        $Disk = $Disks.Item($CurrDisk)
                       
        If (-not ($Disk.Friendlyname -eq $PvsDiskCaption))
        {
            $TargetDisk = $Disk
            $TargetDiskIndex = $CurrDisk     # Disk index is important if we have to use Diskpart below.
            Break     # Exit once the disk is found.
        }
    }

    # If we found a disk to use...
    If (-not ($TargetDisk -eq $null))
    {
        # Get the UniqueID of the non-PVS disk in hexadecimal format.
        $UniqueID = $TargetDisk.Signature.ToString("X")

        # Now check to see what the mode is.
        switch ($DiskMode)
        {
            "P"     {
                        # Private mode. We need to capture the disk's UniqueID and save it to a file.
                        Set-Content -Value $UniqueID -Path $DiskIDPath
                    }


            "S"     {
                        # Standard mode.  We must check to see if the Unique ID matches what's in the file
                        # If not, add the correct value and then reboot.

                        # Read in the target UniqueID from file.
                        $TargetID = Get-Content -Path $DiskIDPath

                        If (-not ($TargetID -eq $UniqueID))
                        {
                            # Unique ID does not match, set it using Diskpart.

                            # Write Diskpart commands out to a text file, DpInput.txt.
                            Remove-Item -Path $DPInputPath -Force
                            Add-Content -Value ("Select Disk " + $TargetDiskIndex-Path $DPInputPath
                            Add-Content -Value ("UniqueID Disk ID=" + $TargetID-Path $DPInputPath
                            Add-Content -Value ("Exit"-Path $DPInputPath

                            # Run Diskpart to update the ID.
                            Start-Process -FilePath "C:\Windows\System32\Diskpart.exe" -ArgumentList ("/s " + $DPInputPath-Wait

                            Start-Sleep -Seconds 5

                            # Reboot to fix the drive letters.
                            Restart-Computer -Force
                        }
                    }
        }
    }
}

Thursday, July 2, 2020

Stupid Citrix Trick #2: Mapping Drives With Powershell

Problem: Many systems need network drives mapped dynamically depending on group memberships, location, etc.  Powershell options are limited.

Solution: Me and mapped drives have a lot in common: we've both been around a long time, we both can be mighty useful, and we both can sometimes be a little awkward to deal with. Powershell, on the other hand, is a lot newer, is extremely useful, but unfortunately it ain't perfect, and when you're using it as a logon script you'll quickly discover its flaws.

There are two common ways to use this, which I'll refer to as "One Night Stand" and "Love You Forever".

One Night Stand

The usual method of mapping drives in Powershll is New-PSDrive.  A call to this function looks like this:

New-PSDrive -Name "F" -Root "\\Server\Share\Folder"

Very straightforward, you just provide the drive letter and the path that you want to assign to it.  However, this drive mapping only as long as the Powershell session is open.  If you're running Citrix virtual apps, the Powershell session will end around the time your app is opening.  So won't get us where we want to go. We need a long term commitment. We need...

Love You Forever

So you're tired of love that doesn't last, of fleeting kisses that feel like rain, of summer days that turn to autumn rains.  You're looking for a truly everlasting love.  Here you go:

New-PSDrive -Name "F" -Root "\\Server\Share\Folder" -Persist

But like the Gothic romance of a cursed vampire, this love will last forever.  Powershell sticks that mapped drive in your user profile, if you need to reuse it you'll need to explicitly delete it.  Why would you need to reuse it?  There are 26 letters after all!

Well, you may not believe this, but large organizations have an insatiable thirst for mapped drives, and since A through E are used for local drives, you're really only got 21.  If only there were some middle ground!

I don't guess it's a spoiler to say that there is.  Powershell is useful partly because you can use so many functions from other environments.  In this case we're going to use COM to create a VBScript Network object to do what we want.  Here's the code to get the object:

$VBSnet = New-Object -ComObject "WScript.Network"

Once this has been created you can call the MapNetworkDrive function like so:

$VBSnet.MapNetworkDrive("F:", "\\Otherserver\Thatshare\Thispath")

Et voila!

Thursday, June 11, 2020

Stupid Citrix Trick #1: Dynamically Configuring Which Controllers a Citrix Server Connects To

Problem: The Citrix team where I work was tasked with setting up a large number of sites, connected by WAN links. Initially we tried using a single hub and zones, but in the current version of XenDesktop at the time (7.9), performance of Studio with remote sites over slow links was very poor, unusably so.

We needed a way to configure the VDAs at each site to connect to the local controllers. The session hosts were all running Provisioning Services images, and we didn't want to have to edit the image at each site to use the correct controllers.

Solution: The best solution would have been to use Group Policies to specify the controllers we wanted, but this was not practical 1) the computer accounts were all kept in a single AD container, and 2) the administrative overhead in creating GPOs meant that they would not have been ready anyway. So I turned the expert I trust most - Dr. Google.

I found that there is a way to configure the Citrix VDA from the command line, though it's not well documented. So I created a small batch file, stuck it on the session hosts' persistent drive, and created a scheduled task in the server image to run it at startup (actually startup + 2 minutes). Here it is. (It's difficult to read, but the list of controllers is space-delimited):

REM Set the list of controllers.
SET Controllers="controller1.siteA.company.com controller2.siteA.company.com"

REM Configure the Citrix Virtual Delivery Agent.
Start "Configuring Citrix VDA" /wait "%ProgramFiles%\Citrix\Virtual Desktop Agent\Agent Configuration\AgentConfig.exe" /ExecutionMode:Reconfigure /Controllers:%Controllers% /Enable_Firewall_Port:true /PortNumber:80 /Log:"C:\Temp\agent.log"

REM Restart Citrix Virtual Desktop Service.
Start "Stopping Citrix VDA Service" /wait net stop BrokerAgent
Start "Starting Citrix VDA Service" /wait net start BrokerAgent

Presto! All we had to do then was make sure the each server had the batch file with the list of controllers that corresponded to its site.