Citrix PVS Image Preparation Script for XenApp 7.x Workloads

With this post I share a Powershell script that prepares the master installation of a XenApp 7.x Worker for imaging with Citix Provisioning Services, Prepare-XenApp7.ps1.

Due to fact that Citrix has ported its flagship XenApp to the architecture that was introduced with XenDesktop 5, there’s strictly speaking no need to generalize the PVS vDisk that provides the workload of a XenApp Worker because it doesn’t contain IMA-related stuff anymore. On the other hand there’s still room for some optimization steps before putting a XenApp vDisk into production/standard mode. The script automates the following steps:

Investigate the PVS’ Personality.ini in the root of the system drive in order to determine the disk mode that is read-write, read-only, or started from local HD

Clear Citrix User Profile Manager’s cache

Resync time

Update GPO settings

Clear network related caches (DNS and ARP)

Clear WSUS Client related settings

Clear event logs

Based on the findings in Step 1, suggest a convenient main action, that is either “Exit” (if we’re in maintenance/private w/ read-write vdisk access), or “Invoke ImagingWizard” (if we started from local HD), or “Invoke XenConvert” (reverse imaging scenario w/ read-only vdisk access)

BTW, the script should work for desktop workloads as well but I haven’t tested it so far.

The other evening, I’ve updated the Sync-PvsLocalStore.ps1 script in order to support multiple Citrix Provisioning Services (PVS) vDisks due to customer request.

The purpose of this script is to copy or rather sync changed and new Versions of one or more given vDisks between the local Stores within a Farm of two or more PVS servers. Basically, you can think of the Sync-PvsLocalStore.ps1 as a wrapper for Robocopy.exe /MIR with some extra brains on top. That is because it is able to detect and exclude a Maintenance Version of a vDisk from the copy process, meaning that the script only spreads out the latest Production and Test versions of a vDisk while it doesn’t bloat the stores with Maintenance versions that is work in progress typically.

You need to specify one ‘MasterServer’, one or more ‘MemberServer’, the path of the Store (needs to be the same on each server), one or more vDisk names, the name of the corresponding Site and Store. The two latter help the script to identify any Maintenance Version.

Sync-PvsLocalStore.ps1 needs to be run on a system where the PVS Console or rather its command-line interface MCLI.EXE is installed. (The script utilizes MCLI.EXE because to date there’s no advantage in using the PVS PowerShell Snapin.)

A Microsoft Windows installation that is properly optimized to run from a Citrix PVS vDisk in standard mode won’t start the Windows Update Service and a couple of other background services that alter the system. It makes no sense to start these services because PVS redirects any write access to the write cache that is known to be volatile. When it comes to maintenance the system needs to be started from the vDisk in a writeable state (that is nowadays a maintenance version/vhd snapshot of the vDisk). In order to let the system pull updates from Microsoft WSUS the Windows Update service needs to be configured to start. Following the maintenance tasks, as part of re-sealing the vDisk, the service will be disabled again through the PVS TargetOSOptimizer.exe utility.

How about a startup script that configures and starts the Windows Update service automatically dependent on the vDisk mode? Here we go:

So, what happens here? Basically, the batch file uses the so-called personality data. In course of booting from vDisk PVS injects these data to a file called Personality.ini in the root directory of the vDisk file system. The script leverages a command line tool called GetPersonality.exe to retrieve the value for $WriteCacheType. A value of 0 indicates that the vDisk is writeable (private mode or maintenance version) and, thus, the script configures the Windows Update service to start automatically and starts it. Additional read: Managing Target Device Personality.

How to use? Save the script as a batchfile on the vDisk, for example in a scripts folder. Configure it to run on system startup (Windows task scheduler, LGPO, whatever).

Disclaimer: I hope that the information in this post is valuable to you. Your use of the information contained in this post, however, is at your sole risk. All information on this post is provided “as is”, without any warranty, whether express or implied, of its accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the third-party products or information mentioned in the work are authored, recommended, supported or guaranteed by me. Further, I shall not be liable for any damages you may sustain by using this information, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages.

Back in late 2009, I wrote a series of posts about the Citrix Provisioning Services’ PowerShell Snapin. 3,5 years later, even within the latest version of PVS the cmdlets return structured text output instead of “real” objects. I’m still hoping that Citrix will provide us a PVS Module/Snapin that follows the common PowerShell standards.

This function takes the output of a given Mcli-Get command and turns it into

"PVS objects" with properties.

.PARAMETER InputObject

The output of a Mcli-Get command

.EXAMPLE

Mcli-Get DiskInfo | ConvertTo-PvsObject

.EXAMPLE

$diskinfo = Mcli-Get DiskInfo

ConvertTo-PvsObject $diskinfo

#>

[cmdletBinding(SupportsShouldProcess=$False)]

param(

[Parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true)]

$InputObject

)

process{

switch-regex($InputObject){

'^Record\s#\d+$'{

if($record.Count){

New-ObjectPSObject-Property$record

}

$record=@{}

}

'^\s{2}(?<Name>\w+):\s(?<Value>.*)'{

$record.Add($Matches.Name,$Matches.Value)

}

}

}

end{

if($record.Count){

New-ObjectPSObject-Property$record

}

}

}

Disclaimer: I hope that the information in this post is valuable to you. Your use of the information contained in this post, however, is at your sole risk. All information on this post is provided “as is”, without any warranty, whether express or implied, of its accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the third-party products or information mentioned in the work are authored, recommended, supported or guaranteed by me. Further, I shall not be liable for any damages you may sustain by using this information, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages.

This post outlines the steps to build a Reverse Image of a Citrix Provisioning Services vDisk with XenApp 6.5 Server to a VM with locally attached hard disk.

Whenever you have to install or upgrade software on a vDisk that interferes directly with the Citrix Provisioning Services infrastructure you need to choose the Reverse Imaging approach. The installation of such software would fail within a system that was booted either from the vDisk in Private mode or from a Maintenance version of the vDisk.

Reverse Imaging means booting from vDisk in order to capture the vDisk’s contents to a local hard drive. Afterwards you can boot from the local hard drive an make the changes without any of those issues that would arise if you’d have tried to change the vDisk directly.

Reverse Imaging is a time-consuming task. Normally you’ll capture a new vDisk following the Reverse Imaging process. In total you should plan a man-day including preparation, performing the Reverse Imaging, applying the updates, and capturing a new vDisk. Therefore, I recommend to start with it just at the beginning of the work day.

Dedicate or create a VM and PVS target device for the Reverse Imaging process. (For istance, an already existing update VM/target device for vDisk maintenance purposes would be a perfectly possible candidate for Reverse Imaging.)

Create and attach a new local hard disk to the Reverse Imaging VM that matches at least the size of the vDisk. (The HD size can be greater though, meaning that with Reverse Imaging you can’t only update software like VMware Tools but also increase the vDisk size as a side benefit.)

Prepare the source vDisk using one of this options:

Create a new version of the vDisk and keep the predefined mode, that is “Maintenance” (PVS 6 style)

Copy the vDisk and put it in Private mode (PVS 5 style)

Check or set the following target device properties:

Boot from: vDisk

Type: Maintenance

Double-check the target’s vDisk assignment. (Use the vDisk prepared in the third step)

Boot the target VM from vDisk and log on

Launch the disk management mmc snap-in.

Initialize the new local hard disk. (Keep default settings.)

Partition and format the hard disk. Keep the default disk type (Basic). The partition needs to be either a simple volume or at least a primary partition. (PVS reverse imaging doesn’t support both extended partitions and dynamic disks.)

Once again, the Image Builder will automatically reboot the target. Log on and confirm a message box saying “The device image build is complete”

Launch the disk management mmc snap-in and mark the partition of the new local hard disk as active.

In the PVS console, change the target’s “Boot from” setting to “Hard Disk”

Reboot the target and apply the required changes, updates, whatever..

Prepare capturing of a new vDisk with PVS Imaging Wizard, reboot, and log on to let XenConvert building the vDisk.

Close XenConvert to let Windows finalize the logon process.

In the PVS console, change the target’s “Boot from” setting to “vDisk”. Leave the vDisk in Private mode

Shutdown the target device VM.

Detach the local hard disk that was used to hold the Reverse Image.

Boot the target device VM and log on.

Launch the XenApp Server Role manager to prepare this server for imaging. (As the server already left the farm in step 8 you need to deselect the corresponding option.)

Shutdown

In the PVS console, change the new vDisk’s mode to Standard and configure the relevant targets to boot from this vDisk.

Clean up. For example you won’t ever use again both, the local hard disk created in step 2 and the Private mode vDisk or vDisk Maintenance version prepared in step 3.

Disclaimer: I hope that the information in this post is valuable to you. Your use of the information contained in this post, however, is at your sole risk. All information on this post is provided “as is”, without any warranty, whether express or implied, of its accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the third-party products or information mentioned in the work are authored, recommended, supported or guaranteed by me. Further, I shall not be liable for any damages you may sustain by using this information, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages.

Back in the days of Citrix Provisioning Services 5.x it was common practice to copy an existing vDisk mostly due to maintenance reasons in order to apply updates, install new software and so on and so forth. On the contrary, sometimes the copy of a vDisk was needed as a starting point to create a new vDisk for other purposes (introduction of a new XenApp application silo/worker group for example).

Aside from the fact that Citrix introduced with PVS 6.0 a more flexible and robust vDisk updating approach based on VHD chaining, the good old copy approach still works. In the case of an unversioned vDisk the procedure is as straightforward as in PVS 5.x (copy/label the according *.vhd and *.pvp files, then choose ‘Add or Import Existing vDisk’ in the context menu of the vDisk Store).

If you go the same path with a versioned vDisk (copy the complete VHD cain) you’ll get an error message. Is it impossible to copy a versioned vDisk after all? No, you have two options: merge and export/import

Merging the VHD chain of the source vDisk seems to be the most obvious preparation step in order to copy the vDisk since it results in a single set of VHD/PVP files, meaning that afterwards you’re able to copy that vDisk as simple as ever. But to merge means to commit oneself to a single source vDisk version. You’ll lose flexibility

If you need to keep the source vDisk with all its versions as it is and want to get a copy anyway you should choose the export/import option. This approach is actually supposed to export and import both versioned and unversioned vDisks from an existing store to a store in a different PVS farm. However you can leverage it to get a copy in one and the same vDisk store as follows:

Open the PVS console, right click the source vDisk, and choose ‘Export vDisk…’. A dialog appears.

In the export dialog, choose the the latest version in the drop-down menu named ‘Export versions starting at’, then click OK. After a short delay the dialog closes. In the source vDisk’s store you’ll find a manifest file containing the entire information about the versions of that vDisk. The manifest file name matches with the name of the vDisk and it has a .xml suffix

Rename the manifest file using the name of the copied vDisk.

Open the manifest file in a text editor and accurately replace all references to the name of the source vDisk with the name of the copied vDisk. Double-check the changes, then save the file.

Now, you should have a set of VHD/AVHD/PVP file and an XML file with the same base file name. And the manifest/XML refers to the new VHD and AVHD files.

In the PVS console, right click the vDisk store, and choose ‘Add or Import Existing vDisk…’. A dialog appears.

In the import dialog, check the settings for Site, Store, and Server, then click Search. After a short delay the copied vDisk will be displayed.

Check the vDisk, check/uncheck the load balancing option as wanted, then click Add. After a short delay a popup appears saying that the import was successful. Click OK, then click Close in the import dialog.

Disclaimer: I hope that the information in this post is valuable to you. Your use of the information contained in this post, however, is at your sole risk. All information on this post is provided “as is”, without any warranty, whether express or implied, of its accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the third-party products or information mentioned in the work are authored, recommended, supported or guaranteed by me. Further, I shall not be liable for any damages you may sustain by using this information, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages.

With this post I share a small batch file that leverages the devcon utility from Microsoft to remove hidden network interfaces.

Recently Citrix identified an issue with those “Ghost NICs” when trying to image a target machine that resides on a virtualization platform like VMware (see CTX133188). NICs can be hidden of leftover from when the machine was built or when the virtualization tools were installed. If the PVS target software binds itself to one of these hidden NICs it won’t allow bnistack to load a vDisk properly.

Next question is how to get devcon because there’s no official download page. Check out this blog post

Disclaimer: I hope that the information in this post is valuable to you. Your use of the information contained in this post, however, is at your sole risk. All information on this post is provided “as is”, without any warranty, whether express or implied, of its accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the third-party products or information mentioned in the work are authored, recommended, supported or guaranteed by me. Further, I shall not be liable for any damages you may sustain by using this information, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages.

Run XenAppConfigConsole to prepare the server for imaging. (Based on the script user’s choice locally stored XenApp database information will be either kept or cleared from mf20.dsn and LGPO. Default is keep db information. If you choose to clear them you need to provide DB information through GPO.)

Clear XenApp related caches (LHC and RADE)

Clear Citrix User Profile Manager’s cache

Resync time

Update GPO settings

Clear network related caches (DNS and ARP)

Clear WSUS Client related settings

Clear event logs

Based on the findings in Step 1, suggest a convenient main action, that is either “Exit” (if we’re in maintenance/private w/ read-write vdisk access), or “Invoke ImagingWizard” (if we started from local HD), or “Invoke XenConvert” (reverse imaging scenario w/ read-only vdisk access)

The script raises no claim to completeness. For example, you should consider running chkdsk C: as consistency check and Mark Russinovich’s SDelete to reduce storage needs.

Disclaimer: I hope that the information in this post is valuable to you. Your use of the information contained in this post, however, is at your sole risk. All information on this post is provided “as is”, without any warranty, whether express or implied, of its accuracy, completeness, fitness for a particular purpose, title or non-infringement, and none of the third-party products or information mentioned in the work are authored, recommended, supported or guaranteed by me. Further, I shall not be liable for any damages you may sustain by using this information, whether direct, indirect, special, incidental or consequential, even if it has been advised of the possibility of such damages.

I wrote the PowerShell function below, Register-PSSnapin, to facilitate the usage of the InstallUtil.exe program in order to register a series of PowerShell snap-ins. Since this utility isn’t located in the “normal” command path, you have to find it in the .NET Framework’s directory. Or, with my function you just don’t care 😉

Side note: in PowerShell 2.0 the concept of snap-ins is substantially replaced by binary modules. Apart from the fact that the core of PowerShell 2.0 is delivered as snap-ins, many software vendors still provide a PowerShell snap-in to allow for command-line based administration and automation. (For example the latest release of Citrix Provisioning Services 6.0 ships a PowerShell Snap-in.)

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

<#

.SYNOPSIS

Registers PowerShell Snap-ins.

.DESCRIPTION

Uses the InstallUtil tool in the .NET Framework to register one ore more snap-ins

.PARAMETER Path

Specifies the path to the file name or &quot;module name&quot; of the snap-in

Hello again and welcome to the third part of my blog post series dealing with McliPSSnapIn – the PowerShell SnapIn to automate the Citrix Provisioning Services (PVS). Hope you enjoyed the first two parts and played with the McliPSSnapIn cmdlets meanwhile. This time you’ll learn how to handle the output of the Mcli-Get cmdlet meaning how to convert its structured text to an object (or an array of objects).

For example, suppose you want to display all vDisks sorted by size in descending order. By intuition, Mcli-Get DiskInfo and Sort-Object seem well-suited for this job get done.

PowerShell

1

PSC:\>Mcli-GetDiskInfo|Sort-ObjectdiskSize-Descending

Worse luck! This will end up in a mess (which I don’t want to show here). As formerly mentioned, McliPSSnapIn‘s cmdlets don’t deliver objects but rather text information just in the fashion of a legacy console utility. This is not much fun.

Since PowerShell supports the creation of custom objects you can convert Mcli-Get‘s output to your own “homebrew” object, strictly speaking to a Device, Disk, Site, Store, Farm, whatever PVS object. Look at this working solution:

Compared to the task to create an object and to assign property names with values to that object it is actually more challenging to parse the text-based information in order to identify the information related to each single object.

You’re not scared now, are you? 😉 Let’s take one thing at a time – according to Louis X divide et impera strategy.

First, let’s analyze the text returned by Mcli-Get DiskInfo on my test system:

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

PSC:\>Mcli-GetDiskInfo

Executing:Get DiskInfo

Get succeeded.3record(s)returned.

Record#1

diskLocatorId:5c3e5124-158b-44e7-891a-f600d1ebd072

diskLocatorName:VDISK

...

diskSize:41940702720

...

deviceCount:0

locked:0

Record#2

...

Record#3

...

PSC:\>

The cmdlet returned the “properties” of three vDisks. The text is structured nicely in order to support human readability:

The output begins with a short summary followed by an information block for each returned record (object).

Each information block is introduced with “Record #nnn” where nnn stands for a counter

Furthermore empty lines separate the information blocks from each other.

The lines that contain the actual data for the records (objects) are indented “name: value” pairs like “diskSize: 41940702720”

Based on this research findings you’re able design an algorithm as stated above to convert the cmdlet’s output to an array of objects.

The Power Of “switch -regex” Unveiled

Naturally you need to process the returned text data in a loop. At first glance it seems perfectly possible to use a foreach loop as follows:

PowerShell

1

2

3

4

foreach($linein$(Mcli-GetDiskInfo))

{

...

}

Inside each run it is important to distinguish between three cases:

A new information block begins (initiated by “Record #nnn“)

Information for the current record (object) in the form of “name: value“

Superfluous information (as for instance the summary)

I consider this a classic case for a switch statement with the -regex option in order to identify a match with “Record #nnn” and a match with “name: value“.

PowerShell

1

2

3

4

5

6

7

foreach($linein$(Mcli-GetDiskInfo))

{

switch-regex($line)

{

...

}

}

You know what? switch is able to process a pipeline (an array of elements) itself, meaning you can forget foreach and let switch process Mcli-Get‘s output directly:

PowerShell

1

2

3

4

switch-regex(Mcli-GetDiskInfo)

{

...

}

According PowerShell’s help, the switch statement’s -regex option “indicates that the match clause, if a string, is treated as a regex string.” So, you need two regex strings, one to identify a “Record #nnn” line and one to identify a “name: value” pair. I will explain my regex statements below.

This regex string matches “Record #nnn“: ^Record\s#\d+$

Element

Description

^

Matches the beginning characters

Record

Matches “Record”

\s

Matches any single white-space character

#

Matches “#”

\d

Matches any decimal digit

+

Matches repeating instances of the preceding characters

$

Matches the end characters

This regex string matches “name: value“: ^\s{2}(?\w+):\s(?.*)

Element

Description

^

Matches the beginning characters

\s{2}

Matches exactly two white-space characters

(?<Name>\w+)

Matches any word character until the next non-word character and assign the result to $Matches.Value

:

Matches “:”

\s

Matches any single white-space characters

(?<Value>.*)

Matches the rest of the line and assign the result to $Matches.Value

And what is $Matches? PowerShell places resulting matches automatically in the $Matches hashtable where $Matches[0] always holds the entire match. Furthermore, PowerShell allows to specify (optionally named) capture groups inside a regex string. These captures will be assigned to separate elements of the $Matches hashtable, either numbered or named. The example below shows what I’m trying to explain:

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

PSC:\># Unnamed capture groups in a regex string

PSC:\>' theName: the Value'-match'^\s{2}(\w+):\s(.*)'

True

PSC:\>$Matches

Name Value

---------

2the Value

1theName

0theName:the Value

PSC:\># Named capture groups in a regex string

PSC:\>' theName: the Value'-match'^\s{2}(?<Name>\w+):\s(?<Value>.*)'

True

PSC:\>$Matches

Name Value

---------

Value the Value

Name theName

0theName:the Value

PSC:\>$Matches.Name

theName

PCC:\>

This is so cool! Did I already mention that I love PowerShell?

How To Avoid Flattening Of Arrays?

I need to tell you that the return value of the above-stated Get-DiskInfo function messed me around a while.

The function should always return an array regardless of the number of objects found by Mcli-Get. The problem I was running into was that the function didn’t returned an array when the object count was 1. Instead it returned the object directly.

In order to avoid that PowerShell unrolls or flattens the array when it would contain only one element I used the comma operator to force the function to return the array as is:

PowerShell

1

return,$diskInfo

Ah, I’ve missed to show the Get-DiskInfo function in action:

PowerShell

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

PSC:\># Is the function loaded?

PSC:\>test-pathfunction:pvs\get-diskinfo

True

PSC:\># assign the returning objects to $vdisks

PSC:\>$vdisks=pvs\get-diskinfo

PSC:\># show disk sizes in gb

PSC:\>$vdisks|%{

>>'Disk Name: {0}'-f$_.diskLocatorName

>>'Disk Size: {0:N2}'-f($_.diskSize/1gb)

>>}

>>

Disk Name:VDISK

Disk Size:39.06

Disk Name:TestDisk

Disk Size:10.00

Disk Name:VDISK2

Disk Size:39.06

PSC:\>

The Get-DiskInfo function is a very basic example for creating custom objects in PowerShell and therefore should considered to be a kick-start.