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
                        }
                    }
        }
    }
}

No comments:

Post a Comment