-
Documenting a Citrix Provisioning Services Farm with Microsoft PowerShell
April 13, 2012
A customer site I was at recently needed their Provisioning Services (PVS) farm documented. Since I had already created PowerShell scripts to document the various versions of XenApp, I figured a PVS script should be easy to create. I could not have been more wrong in my thinking! This article and script were written on and tested against PVS 5.6 SP2, 6.0 and 6.1 all running on Microsoft Windows Server 2008 R2 SP1.
NOTE: This script is continually updated. You can always find the most current version by going to https://carlwebster.com/where-to-get-copies-of-the-documentation-scripts/
I would recommend downloading the Citrix PVS PowerShell Programmer’s Guide for the version of PVS for your Farm. While most of each guide is the same, each guide is specific to each version of PVS. For example, the PVS 6.1 guide has information on the new Personal vDisk that is only supported in PVS 6.1.
Citrix Provisioning Services 5.6 SP1 PowerShell Programmer’s Guide
Citrix Provisioning Services 6.0 PowerShell Programmer’s Guide
Citrix Provisioning Services 6.1 PowerShell Programmer’s Guide
My initial goal was to see if I could walk down the nodes in the Provisioning Services Console (PSC) and see if I could document every nook and cranny. PVS 5.6 SP2 is shown in Figure 1 and PVS 6.1 is shown in Figure 2.
Figure 1 Figure 2 Before we can start using PowerShell to document anything in the PVS farm we first need to register the PVS Console PowerShell snap-in. Even if you are running PVS on a 64-bit Windows Server Operating System you must still register the snap-in with the 32-bit .Net Framework.
Change to the folder where the PSC is installed. By default, this is C:\Program Files\Citrix\Provisioning Services Console.
To register with the 32-bit .Net Framework, enter the following command (Figure 3):
%systemroot%\Microsoft.NET\Framework\v2.0.50727\installutil.exe McliPSSnapIn.dll
Figure 3 To register with the 64-bit .Net Framework, enter the following command (Figure 4):
%systemroot%\Microsoft.NET\Framework64\v2.0.50727\installutil.exe McliPSSnapIn.dll
Figure 4 Click Start, Administrative Tools, Windows PowerShell Modules. A PowerShell session starts. Enter the following command:
Add-PSSnapin –Name McliPSSnapIn
Everything is now set up for us to get started.
PowerShell commands are supposed to be done in a Verb-Noun format. Get-Something, New-Something, Copy-Something, etc. That is the way Citrix did the PowerShell commands for XenApp 5, XenApp 6.x, and XenDesktop 4. But that is not what Citrix did for PVS.
In XenApp or XenDesktop 4, if I wanted to retrieve farm information I would use Get-XAFarm or Get-XDFarm. I thought the obvious thing to do was to use Get-PVSFarm but that doesn’t work. For some bizarre reason, Citrix decided to wrap PowerShell around the PVS MCLI command-line utilities.
I could write an article on all the things Citrix did wrong with their PVS PowerShell implementation but that would not serve any useful purpose. This article will do its best to stay focused on what is needed to use what Citrix gave us to document a PVS farm.
To get a list of the MCLI commands, showing just the Name, where the “verb” starts with “mcli”, type the following in the PowerShell session (results are shown in Figure 11):
Get-Command -Verb mcli | Select-Object Name
Figure 5 The main problem with the Citrix PowerShell PVS implementation is that the cmdlets do not return objects. Rather they return an array of strings as shown in Figure 6.
Figure 6 In order to work around this issue, I had to get help from my PowerShell mentor and Exchange MVP Michael B. Smith (MBS). He pointed me in the right direction and I was able to write a function that turns what I need from the array of strings into objects.
An object has one or more properties and the property has a name and a value. Looking at the array of strings, I can see that almost everything that has a colon “:” is useful data. The text before the “:” is the property and the text after the “:” is the value. For example,
licenseServer: labxd55 licenseServerPort: 27000
licenseServer is the property name with a value of labxd55 and licenseServerPort is another property with a value of 27000.
Anything in the array of strings that do not contain a “:” is not needed. There is one line that contains a “:” that is not needed. “Executing: Get farm”, in this example, is not needed.
In the case where multiple records are returned, an object needs to be created for each record. Otherwise, an error is returned saying the object already has a property named, for example, licenseServer.
The function that MBS and I created is named BuildPVSObject. The function needs three parameters: What to get, what parameters to use, if any, and what text to use for the error message.
Function BuildPVSObject
{
Param( [string]$MCLIGetWhat = ”, [string]$MCLIGetParameters = ”, [string]$TextForErrorMsg = ” )$error.Clear()
If($MCLIGetParameters -ne ”)
{
$MCLIGetResult = Mcli-Get “$($MCLIGetWhat)” -p “$($MCLIGetParameters)”
}
Else
{
$MCLIGetResult = Mcli-Get “$($MCLIGetWhat)”
}If( $error.Count -eq 0 )
{
$PluralObject = @()
$SingleObject = $null
foreach( $record in $MCLIGetResult )
{
If($record.length -gt 5 -and $record.substring(0,6) -eq “Record”)
{
If($SingleObject -ne $null)
{
$PluralObject += $SingleObject
}
$SingleObject = new-object System.Object
}$index = $record.IndexOf( ‘:’ )
if( $index –gt 0 )
{
$property = $record.SubString( 0, $index )
$value = $record.SubString( $index + 2 )
If($property -ne “Executing”)
{
Add-Member –inputObject $SingleObject –MemberType NoteProperty –Name $property –Value $value
}
}
}
$PluralObject += $SingleObject
Return $PluralObject
}
Else
{
line 0 “$($TextForErrorMsg) could not be retrieved”
line 0 “Error returned is ” $error[0].FullyQualifiedErrorId.Split(‘,’)[0].Trim()
}
}
[/code]My goal is to use the same wording as what is seen in the PSC for headings, captions, and text. In order to do that, I needed a way to format the output text. MBS developed a function for me to use called Line.
Function line #function created by Michael B. Smith, Exchange MVP #@essentialexchange on Twitter #http://Essential.Exchange/Blog { Param( [int]$tabs = 0, [string]$name = ’’, [string]$value = ’’, [string]$newline = “`n”, [switch]$nonewline ) While( $tabs –gt 0 ) { $global:output += “`t”; $tabs--; } If( $nonewline ) { $global:output += $name + $value } Else { $global:output += $name + $value + $newline } }
In writing this script, the information about the status of a Target Device was needed more than once. To keep from duplicating the code, I turned that section of code into a Function named DeviceStatus.
Let’s get started. We will build the script node by node. Some of the lines may wrap.
The beginning of the script:
#Carl Webster, CTP and independent consultant #webster@carlwebster.com #@carlwebster on Twitter #http://www.CarlWebster.com #This script written for "Benji", March 19, 2012 #Thanks to Michael B. Smith, Joe Shonk, and Stephane Thirion #for testing and fine-tuning tips Function line #function created by Michael B. Smith, Exchange MVP #@essentialexchange on Twitter #http://Essential.Exchange/Blog { Param( [int]$tabs = 0, [string]$name = ’’, [string]$value = ’’, [string]$newline = “`n”, [switch]$nonewline ) While( $tabs –gt 0 ) { $global:output += “`t”; $tabs--; } If( $nonewline ) { $global:output += $name + $value } Else { $global:output += $name + $value + $newline } } Function BuildPVSObject { Param( [string]$MCLIGetWhat = '', [string]$MCLIGetParameters = '', [string]$TextForErrorMsg = '' ) $error.Clear() If($MCLIGetParameters -ne '') { $MCLIGetResult = Mcli-Get "$($MCLIGetWhat)" -p "$($MCLIGetParameters)" } Else { $MCLIGetResult = Mcli-Get "$($MCLIGetWhat)" } If( $error.Count -eq 0 ) { $PluralObject = @() $SingleObject = $null foreach( $record in $MCLIGetResult ) { If($record.length -gt 5 -and $record.substring(0,6) -eq "Record") { If($SingleObject -ne $null) { $PluralObject += $SingleObject } $SingleObject = new-object System.Object } $index = $record.IndexOf( ‘:’ ) if( $index –gt 0 ) { $property = $record.SubString( 0, $index ) $value = $record.SubString( $index + 2 ) If($property -ne "Executing") { Add-Member –inputObject $SingleObject –MemberType NoteProperty –Name $property –Value $value } } } $PluralObject += $SingleObject Return $PluralObject } Else { line 0 "$($TextForErrorMsg) could not be retrieved" line 0 "Error returned is " $error[0].FullyQualifiedErrorId.Split(',')[0].Trim() } } Function DeviceStatus { Param( $xDevice ) If($xDevice.active -eq "0" -or $xDevice -eq $null) { line 2 "Target device inactive" } Else { line 2 "Target device active" line 2 "IP Address: " $xDevice.ip line 2 "Server: " -nonewline line 0 "$($xDevice.serverName) `($($xDevice.serverIpConnection)`: $($xDevice.serverPortConnection)`)" line 2 "Retries: " $xDevice.status line 2 "vDisk: " $xDevice.diskLocatorName line 2 "vDisk version: " $xDevice.diskVersion line 2 "vDisk full name: " $xDevice.diskFileName line 2 "vDisk access: " -nonewline switch ($xDevice.diskVersionAccess) { 0 {line 0 "Production"} 1 {line 0 "Test"} 2 {line 0 "Maintenance"} 3 {line 0 "Personal vDisk"} Default {line 0 "vDisk access type could not be determined: $($xDevice.diskVersionAccess)"} } switch($xDevice.licenseType) { 0 {line 2 "No License"} 1 {line 2 "Desktop License"} 2 {line 2 "Server License"} 5 {line 2 "OEM SmartClient License"} 6 {line 2 "XenApp License"} 7 {line 2 "XenDesktop License"} Default {line 2 "Device license type could not be determined: $($xDevice.licenseType)"} } line 1 " Logging tab" line 2 "Logging level: " -nonewline switch ($xDevice.logLevel) { 0 {line 0 "Off" } 1 {line 0 "Fatal" } 2 {line 0 "Error" } 3 {line 0 "Warning"} 4 {line 0 "Info" } 5 {line 0 "Debug" } 6 {line 0 "Trace" } default {line 0 "Logging level could not be determined: $($xDevice.logLevel)"} } line 2 "" } } $global:output = ""
This script is designed to work with PVS 5.6 and 6.x. Some sections of the PSC are version-specific. The first thing that needs to be done is to get the PVS version.
#get PVS major version $PVSVersion = "" $error.Clear() $tempversion = mcli-info version If( $error.Count -eq 0 ) { #build PVS version values $version = new-object System.Object foreach( $record in $tempversion ) { $index = $record.IndexOf( ‘:’ ) if( $index –gt 0 ) { $property = $record.SubString( 0, $index) $value = $record.SubString( $index + 2 ) Add-Member –inputObject $version –MemberType NoteProperty –Name $property –Value $value } } } Else { line 0 "PVS version information could not be retrieved" line 0 "Error returned is " $error[0].FullyQualifiedErrorId.Split(',')[0].Trim() line 0 "Script is terminating" #without version info, script should not proceed Break } $PVSVersion = $Version.mapiVersion.SubString(0,1) $tempversion = $null $version = $null
The first node in the PSC is the Farm itself.
$FarmAutoAddEnabled = $false #build PVS farm values #there can only be one farm $GetWhat = "Farm" $GetParam = "" $ErrorTxt = "PVS Farm information" $farm = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($Farm -eq $null) { #without farm info, script should not proceed Break } line 0 "PVS Farm Information" #general tab line 1 "General tab" line 2 "Name: " $farm.farmName line 2 "Description: " $farm.description #security tab line 1 "Security tab" line 2 "Groups with Farm Administrator access:" #build security tab values $GetWhat = "authgroup" $GetParam = "farm=1" $ErrorTxt = "Groups with Farm Administrator access" $authgroups = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($AuthGroups -ne $null) { ForEach($Group in $authgroups) { If($Group.authGroupName) { line 2 $Group.authGroupName } } } #groups tab line 1 "Groups tab" line 2 "All the Security Groups that can be assigned acess rights:" $GetWhat = "authgroup" $GetParam = "" $ErrorTxt = "Security Groups information" $authgroups = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($AuthGroups -ne $null) { ForEach($Group in $authgroups) { If($Group.authGroupName) { line 3 $Group.authGroupName } } } #licensing tab line 1 "Licensing tab" line 2 "License server name: " $farm.licenseServer line 2 "License server port: " $farm.licenseServerPort If($PVSVersion -eq "5") { line 2 "User Datacenter licenses for desktops if no Desktop licenses are available: " -nonewline If($farm.licenseTradeUp -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } } #options tab line 1 "Options tab" line 2 "Auto-Add" line 3 "Enable auto-add: " -nonewline If($farm.autoAddEnabled -eq "1") { line 0 "Enabled" line 4 "Add new devices to this site: " $farm.defaultSiteName $FarmAutoAddEnabled = $true } Else { line 0 "Disabled" $FarmAutoAddEnabled = $false } line 2 "Auditing" line 3 "Enable auditing: " -nonewline If($farm.auditingEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Offline database support" line 3 "Enable offline database support: " -nonewline If($farm.offlineDatabaseSupportEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } If($PVSVersion -eq "6") { #vDisk Version tab line 1 "vDisk Version tab" line 2 "Alert if number of version from base image exceeds: " $farm.maxVersions line 2 "Merge after automated vDisk update, if over alert threshold: " -nonewline If($farm.automaticMergeEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Default access mode for new merge versions: " -nonewline switch ($Farm.mergeMode) { 0 {line 0 "Production" } 1 {line 0 "Test" } 2 {line 0 "Maintenance"} default {line 0 "Default access mode could not be determined: $($Farm.mergeMode)"} } } #status tab line 1 "Status tab" line 2 "Current status of the farm:" line 3 "Database server: " $farm.databaseServerName line 3 "Database instance: " $farm.databaseInstanceName line 3 "Database: " $farm.databaseName line 3 "Failover Partner Server: " $farm.failoverPartnerServerName line 3 "Failover Partner Instance: " $farm.failoverPartnerInstanceName If($Farm.adGroupsEnabled -eq "1") { line 3 "Active Directory groups are used for access rights" } Else { line 3 "Active Directory groups are not used for access rights" } write-output $global:output $global:output = "" $farm = $null $authgroups = $null
Sample script output from PVS 6.1: PVS Farm Information General tab Name: LabPVSFarm Description: Information in this document is subject to change without notice. Companies, names, and data used in Security tab Groups with Farm Administrator access: websterslab.biz/Users/PVS Farm Admins Groups tab All the Security Groups that can be assigned acess rights: websterslab.biz/Users/PVS Collection Admins websterslab.biz/Users/PVS Collection Operators websterslab.biz/Users/PVS Farm Admins websterslab.biz/Users/PVS Site Admins Licensing tab License server name: labxd55 License server port: 27000 Options tab Auto-Add Enable auto-add: Disabled Auditing Enable auditing: Disabled Offline database support Enable offline database support: Disabled vDisk Version tab Alert if number of version from base image exceeds: 5 Merge after automated vDisk update, if over alert threshold: Enabled Default access mode for new merge versions: Maintenance Status tab Current status of the farm: Database server: labsql Database instance: Database: ProvisioningServices Failover Partner Server: Failover Partner Instance: Active Directory groups are used for access rights Sample script output from PVS 5.6 SP2: PVS Farm Information General tab Name: PVS56Farm Description: PVS 5.6 SP2 lab server Security tab Groups with Farm Administrator access: websterslab.biz/Builtin/Administrators websterslab.biz/Users/PVS Farm Admins Groups tab All the Security Groups that can be assigned acess rights: websterslab.biz/Builtin/Administrators websterslab.biz/Users/PVS Collection Admins websterslab.biz/Users/PVS Collection Operators websterslab.biz/Users/PVS Farm Admins websterslab.biz/Users/PVS Site Admins Licensing tab License server name: LabXD55 License server port: 27000 User Datacenter licenses for desktops if no Desktop licenses are available: Enabled Options tab Auto-Add Enable auto-add: Disabled Auditing Enable auditing: Disabled Offline database support Enable offline database support: Disabled Status tab Current status of the farm: Database server: labsql Database instance: Database: pvs56 Failover Partner Server: Failover Partner Instance: Active Directory groups are used for access rights
The next main node is Sites. A Site can contain Servers, vDisks, vDisk Update Management for PVS 6.x, Device Collections, User Groups in PVS 5.6, and Site Views. To keep this article from getting very cluttered, very fast I will cover each one separately.
First, Site information.
#build site values $GetWhat = "site" $GetParam = "" $ErrorTxt = "PVS Site information" $PVSSites = BuildPVSObject $GetWhat $GetParam $ErrorTxt ForEach($PVSSite in $PVSSites) { line 0 "Site properties" #general tab line 1 "General tab" line 2 "Name: " $PVSSite.siteName line 2 "Description: " $PVSSite.description #security tab $temp = $PVSSite.SiteName $GetWhat = "authgroup" $GetParam = "sitename=$temp" $ErrorTxt = "Groups with Site Administrator access" $authgroups = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 1 "Security tab" line 2 "Groups with Site Administrator access:" If($authGroups -ne $null) { ForEach($Group in $authgroups) { line 3 $Group.authGroupName } } Else { line 3 "There are no Site Administrators defined" } If($PVSVersion -eq "5") { #MAK tab line 1 "MAK tab" line 2 "MAK User: " $PVSSite.makUser line 2 "Password: " $PVSSite.makPassword } #options tab line 1 "Options tab" line 2 "Auto-Add" If($PVSVersion -eq "5" -or ($PVSVersion -eq "6" -and $FarmAutoAddEnabled)) { line 3 "Add new devices to this collection: " -nonewline If($PVSSite.defaultCollectionName) { line 0 $PVSSite.defaultCollectionName } Else { line 0 "" } } If($PVSVersion -eq "6") { line 3 "Seconds between vDisk inventory scans: " $PVSSite.inventoryFilePollingInterval #vDisk Update tab line 1 "vDisk Update tab" line 2 "Enable automatic vDisk updates on this site: " -nonewline If($PVSSite.enableDiskUpdate -eq "1") { line 0 "Enabled" line 2 "Select the server to run vDisk updates for this site: " $PVSSite.diskUpdateServerName } Else { line 0 "Disabled" } } }
Sample script output from PVS 6.1: Site properties General tab Name: WebstersLab Description: Information in this document is subject to change without notice. Companies, names, and data used in Security tab Groups with Site Administrator access: websterslab.biz/Users/PVS Site Admins Options tab Auto-Add Seconds between vDisk inventory scans: 60 vDisk Update tab Enable automatic vDisk updates on this site: Disabled Sample script output from PVS 5.6 SP2: Site properties General tab Name: WebstersLab Description: Security tab Groups with Site Administrator access: websterslab.biz/Users/PVS Site Admins MAK tab MAK User: Password: Options tab Auto-Add Add new devices to this collection:
To keep this article from being over 100 pages long, I will no longer show sample output for the remaining sub-nodes and main nodes. I will include full sample output from my PVS 5.6 SP2 Farm and my 6.1 Farm in the ShareFile download folder.
The first sub-node in Sites is Servers.
#process all servers in site $temp = $PVSSite.SiteName $GetWhat = "server" $GetParam = "sitename=$temp" $ErrorTxt = "Servers for Site $temp" $servers = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 1 "Servers" ForEach($Server in $Servers) { #general tab line 1 " Server Properties" line 1 " General tab" line 2 "Name: " $Server.serverName line 2 "Description: " $Server.description line 2 "Power Rating: " $Server.powerRating line 2 "Log events to the server's Windows Event Log: " -nonewline IF($Server.eventLoggingEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 1 " Network tab" line 2 "IP addresses:" $test = $Server.ip.ToString() $test1 = $test.replace(",","`n`t`t`t") line 3 $test1 line 2 "Ports" line 3 "First port: " $Server.firstPort line 3 "Last port: " $Server.lastPort line 1 " Stores tab" #process all stores for this server $temp = $Server.serverName $GetWhat = "serverstore" $GetParam = "servername=$temp" $ErrorTxt = "Store information for server $temp" $stores = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 2 "Stores that this server supports:" If($Stores -ne $null) { ForEach($store in $stores) { line 3 "Store: " $store.storename line 3 "Path: " -nonewline If($store.path.length -gt 0) { line 0 $store.path } Else { line 0 "" } line 3 "Write cache paths: " -nonewline If($store.cachePath.length -gt 0) { line 0 $store.cachePath } Else { line 0 "" } line 4 "" } } line 1 " Options tab" If($PVSVersion -eq "5") { line 2 "Enable automatic vDisk updates" line 3 "Check for new versions of a vDisk: " -nonewline If($Server.autoUpdateEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 3 "Check for incremental updates to a vDisk: " -nonewline If($Server.incrementalUpdateEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } $AMorPM = "AM" $tempHour = $Server.autoUpdateHour If($Server.autoUpdateHour -ge "1" -and $Server.autoUpdateHour -le "13") { $AMorPM = "AM" } Else { $AMorPM = "PM" If($Server.autoUpdateHour -eq "0") { $tempHour = $tempHour + 12 } Else { $tempHour = $tempHour - 12 } } $tempMinute = "" If($Server.autoUpdateMinute.length -lt 2) { $tempMinute = "0" + $Server.autoUpdateMinute } line 3 "Check for updates daily at: $($tempHour)`:$($tempMinute) $($AMorPM)" } line 2 "Active directory" If($PVSVersion -eq "5") { line 3 "Enable automatic password support: " -nonewline If($Server.adMaxPasswordAgeEnabled -eq "1") { line 0 "Enabled" line 3 "Change computer account password every this number of days: " $Server.adMaxPasswordAge } Else { line 0 "Disabled" } } Else { line 3 "Automate computer account password updates: " -nonewline If($Server.adMaxPasswordAgeEnabled -eq "1") { line 0 "Enabled" line 3 "Days between password updates: " $Server.adMaxPasswordAge } Else { line 0 "Disabled" } } line 1 " Logging tab" line 2 "Logging level: " -nonewline switch ($Server.logLevel) { 0 {line 0 "Off" } 1 {line 0 "Fatal" } 2 {line 0 "Error" } 3 {line 0 "Warning"} 4 {line 0 "Info" } 5 {line 0 "Debug" } 6 {line 0 "Trace" } default {line 0 "Logging level could not be determined: $($Server.logLevel)"} } line 2 "File size maximum (MB): " $Server.logFileSizeMax line 2 "Backup files maximum: " $Server.logFileBackupCopiesMax line 2 "" #advanced button at the bottom line 1 " Advanced button" line 2 " Server tab" line 3 "Threads per port: " $Server.threadsPerPort line 3 "Buffers per thread: " $Server.buffersPerThread line 3 "Server cache timeout (seconds): " $Server.serverCacheTimeout line 3 "Local concurrent I/O limit (transactions): " $Server.localConcurrentIoLimit line 3 "Remote concurrent I/O limit (transactions): " $Server.remoteConcurrentIoLimit line 2 " Network tab" line 3 "Ethernet MTU (bytes): " $Server.maxTransmissionUnits line 3 "I/O burst size (KB): " $Server.ioBurstSize line 3 "Socket communications" line 4 "Enable non-blocking I/O for network communications: " -nonewline If($Server.nonBlockingIoEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 " Pacing tab" line 3 "Boot pause seconds: " $Server.bootPauseSeconds line 3 "Maximum boot time (seconds): " $Server.maxBootSeconds line 3 "Maximum devices booting: " $Server.maxBootDevicesAllowed line 3 "vDisk Creation pacing: " $Server.vDiskCreatePacing line 2 " Device tab" line 3 "License timeout (seconds): " $Server.licenseTimeout line 1 "" }
For each server, now process the right-click options.
#the properties for the servers have been processed. #now to process the stuff available via a right-click on each server #Configure Bootstrap is first ForEach($Server in $Servers) { #first get all bootstrap files for the server $temp = $server.serverName $GetWhat = "ServerBootstrapNames" $GetParam = "serverName=$temp" $ErrorTxt = "Server Bootstrap Name information" $BootstrapNames = BuildPVSObject $GetWhat $GetParam $ErrorTxt #Now that the list of bootstrap names has been gathered #We have the mandatory parameter to get the bootstrap info #there should be at least one bootstrap filename line 1 "" line 1 " Configure Bootstrap settings for server " $Server.serverName If($Bootstrapnames -ne $null) { #cannot use the BuildPVSObject function here $serverbootstraps=@() ForEach($Bootstrapname in $Bootstrapnames) { #get serverbootstrap info $error.Clear() $tempserverbootstrap = Mcli-Get ServerBootstrap -p name="$($Bootstrapname.name)",servername="$($server.serverName)" If( $error.Count -eq 0 ) { $serverbootstrap = $null foreach( $record in $tempserverbootstrap ) { If($record.length -gt 5 -and $record.substring(0,6) -eq "Record") { If($serverbootstrap -ne $null) { $serverbootstraps += $serverbootstrap } $serverbootstrap = new-object System.Object #add the bootstrapname name value to the serverbootstrap object $property = "BootstrapName" $value = $Bootstrapname.name Add-Member –inputObject $serverbootstrap –MemberType NoteProperty –Name $property –Value $value } $index = $record.IndexOf( ‘:’ ) if( $index –gt 0 ) { $property = $record.SubString( 0, $index) $value = $record.SubString( $index + 2 ) If($property -ne "Executing") { Add-Member –inputObject $serverbootstrap –MemberType NoteProperty –Name $property –Value $value } } } $serverbootstraps += $serverbootstrap } Else { line 0 "Server Bootstrap information could not be retrieved" line 0 "Error returned is " $error[0].FullyQualifiedErrorId.Split(',')[0].Trim() } } If($ServerBootstraps -ne $null) { ForEach($ServerBootstrap in $ServerBootstraps) { line 1 " General tab" line 2 "Bootstrap file: " $ServerBootstrap.Bootstrapname If($ServerBootstrap.bootserver1_Ip -ne "0.0.0.0") { line 2 "IP Address: " $ServerBootstrap.bootserver1_Ip line 2 "Subnet Mask: " $ServerBootstrap.bootserver1_Netmask line 2 "Gateway: " $ServerBootstrap.bootserver1_Gateway line 2 "Port: " $ServerBootstrap.bootserver1_Port } If($ServerBootstrap.bootserver2_Ip -ne "0.0.0.0") { line 2 "IP Address: " $ServerBootstrap.bootserver2_Ip line 2 "Subnet Mask: " $ServerBootstrap.bootserver2_Netmask line 2 "Gateway: " $ServerBootstrap.bootserver2_Gateway line 2 "Port: " $ServerBootstrap.bootserver2_Port } If($ServerBootstrap.bootserver3_Ip -ne "0.0.0.0") { line 2 "IP Address: " $ServerBootstrap.bootserver3_Ip line 2 "Subnet Mask: " $ServerBootstrap.bootserver3_Netmask line 2 "Gateway: " $ServerBootstrap.bootserver3_Gateway line 2 "Port: " $ServerBootstrap.bootserver3_Port } If($ServerBootstrap.bootserver4_Ip -ne "0.0.0.0") { line 2 "IP Address: " $ServerBootstrap.bootserver4_Ip line 2 "Subnet Mask: " $ServerBootstrap.bootserver4_Netmask line 2 "Gateway: " $ServerBootstrap.bootserver4_Gateway line 2 "Port: " $ServerBootstrap.bootserver4_Port } line 1 " Options tab" line 2 "Verbose mode: " -nonewline If($ServerBootstrap.verboseMode -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Interrupt safe mode: " -nonewline If($ServerBootstrap.interruptSafeMode -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Advanced Memory Support: " -nonewline If($ServerBootstrap.paeMode -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Network recovery method" If($ServerBootstrap.bootFromHdOnFail -eq "0") { line 3 "Restore network connection" } Else { line 3 "Reboot to Hard Drive after " -nonewline line 0 $ServerBootstrap.recoveryTime -nonewline line 0 " seconds" } line 2 "Timeouts" line 3 "Login polling timeout (milliseconds): " -nonewline If($ServerBootstrap.pollingTimeout -eq "") { line 0 "5000" } Else { line 0 $ServerBootstrap.pollingTimeout } line 3 "Login general timeout (milliseconds): " -nonewline If($ServerBootstrap.generalTimeout -eq "") { line 0 "5000" } Else { line 0 $ServerBootstrap.generalTimeout } } } } Else { line 2 "No Bootstrap names available" } }
The next right-click option is Configure BIOS Bootstrap. When I was writing and testing this script at the customer site, they used physical servers and I had no issues. When I test this script on Virtual Machines, I would get a bunch of fatal errors. If you are running physical servers, you can safely uncomment this section of code.
ForEach($Server in $Servers) { $temp = $server.serverName $GetWhat = "ServerBiosBootstrap" $GetParam = "serverName=$Temp" $ErrorTxt = "Server Bios Bootstrap information" $BiosBootstraps = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 1 "" line 1 " Configure BIOS Bootstrap settings for server " $Server.serverName If($BiosBootstraps -ne $null) ForEach($BiosBootstrap in $BiosBootstraps) { line 1 " General tab" line 2 "Automatically update the BIOS on the target device with these settings: " -nonewline If($BiosBootstraps.enabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 1 " Target Device IP tab" If($BiosBootstraps.dhcpEnabled -eq "1") { line 2 "Use DHCP to retrieve target device IP" } Else { line 2 "Use static target device IP" line 3 "Primary DNS: " $BiosBootstraps.dnsIpAddress1 line 3 "Secondary DNS: " $BiosBootstraps.dnsIpAddress2 line 3 "Domain name: " $BiosBootstraps.domain } line 1 " Server Lookup tab" If($BiosBootstraps.lookup -eq "1") { line 2 "Use DNS to find server" line 3 "Host name: " $BiosBootstraps.serverName } Else { line 2 "Use specific servers" If($BiosBootstraps.bootserver1_Ip -ne "0.0.0.0") { line 3 "IP Address: " $BiosBootstraps.bootserver1_Ip line 3 "Port: " $BiosBootstraps.bootserver1_Port } If($BiosBootstraps.bootserver2_Ip -ne "0.0.0.0") { line 3 "IP Address: " $BiosBootstraps.bootserver2_Ip line 3 "Port: " $BiosBootstraps.bootserver2_Port } If($BiosBootstraps.bootserver3_Ip -ne "0.0.0.0") { line 3 "IP Address: " $BiosBootstraps.bootserver3_Ip line 3 "Port: " $BiosBootstraps.bootserver3_Port } If($BiosBootstraps.bootserver4_Ip -ne "0.0.0.0") { line 3 "IP Address: " $BiosBootstraps.bootserver4_Ip line 3 "Port: " $BiosBootstraps.bootserver4_Port } } line 1 " Options tab" line 2 "Verbose mode: " -nonewline If($BiosBootstraps.verboseMode -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Interrupt safe mode: " -nonewline If($BiosBootstraps.interruptSafeMode -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Advanced Memory Support: " -nonewline If($BiosBootstraps.paeMode -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Network recovery method" If($BiosBootstraps.bootFromHdOnFail -eq "0") { line 3 "Restore network connection" } Else { line 3 "Reboot to Hard Drive after " -nonewline line 0 $BiosBootstraps.recoveryTime -nonewline line 0 " seconds" } line 2 "Timeouts" line 3 "Login polling timeout (milliseconds): " -nonewline If($BiosBootstraps.pollingTimeout -eq "") { line 0 "5000" } Else { line 0 $BiosBootstraps.pollingTimeout } line 3 "Login general timeout (milliseconds): " -nonewline If($BiosBootstraps.generalTimeout -eq "") { line 0 "5000" } Else { line 0 $BiosBootstraps.generalTimeout } } } }
Now process all the vDisks for the Site.
#process all vDisks in site $Temp = $PVSSite.SiteName $GetWhat = "DiskInfo" $GetParam = "siteName=$Temp" $ErrorTxt = "Disk information" $Disks = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 1 "vDisk Pool" line 1 " vDisk Properties" If($Disks -ne $null) { ForEach($Disk in $Disks) { line 1 " General tab" line 2 "Site: " $Disk.siteName line 2 "Store: " $Disk.storeName line 2 "Filename: " $Disk.diskLocatorName line 2 "Size: " (($Disk.diskSize/1024)/1024)/1024 -nonewline line 0 " MB" line 2 "VHD block size: " $Disk.vhdBlockSize -nonewline line 0 " KB" line 2 "Access mode" line 3 "Access mode: " -nonewline If($Disk.writeCacheType -eq "0") { line 0 "Private Image (single device, read/write access)" } Else { line 0 "Standard Image (multi-device, read-only access)" } line 3 "Cache type: " -nonewline If($PVSVersion -eq "6") { switch ($Disk.writeCacheType) { 0 {line 0 "Private Image" } 1 {line 0 "Cache on server" } 3 {line 0 "Cache in device RAM" } 4 {line 0 "Cache on device hard disk" } 7 {line 0 "Cache on server persisted" } 8 {line 0 "Cache on device hard drive persisted (NT 6.1 and later)"} default {line 0 "Cache type could not be determined: $($Disk.writeCacheType)"} } } Else { switch ($Disk.writeCacheType) { 0 {line 0 "Private Image" } 1 {line 0 "Cache on server" } 2 {line 0 "Cache on server encrypted" } 3 {line 0 "RAM" } 4 {line 0 "Hard Disk" } 5 {line 0 "Hard Disk Encrypted" } 6 {line 0 "RAM Disk" } 7 {line 0 "Difference Disk" } default {line 0 "Cache type could not be determined: $($Disk.writeCacheType)"} } } line 2 "BIOS boot menu text: " $Disk.menuText line 2 "Enable Active Directory machine account password management: " -nonewline If($Disk.adPasswordEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Enable printer management: " -nonewline If($Disk.printerManagementEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Enable streaming of this vDisk: " -nonewline If($Disk.Enabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 1 " Identification tab" line 2 "Description: " $Disk.longDescription line 2 "Date: " $Disk.date line 2 "Author: " $Disk.author line 2 "Title: " $Disk.title line 2 "Company: " $Disk.company line 2 "Internal name: " $Disk.internalName line 2 "Original file: " $Disk.originalFile line 2 "Hardware target: " $Disk.hardwareTarget line 1 " Microsoft Volume Licensing tab" line 2 "Microsoft license type: " -nonewline switch ($Disk.licenseMode) { 0 {line 0 "None" } 1 {line 0 "Multiple Activation Key (MAK)" } 2 {line 0 "Key Management Service (KMS)" } default {line 0 "License Mode could not be determined: $($Disk.licenseMode)"} } line 1 " Auto Update tab" line 2 "Enable automatic updates for the vDisk: " -nonewline If($Disk.autoUpdateEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Apply vDisk updates as soon as they are detected by the server: " -nonewline If($Disk.activationDateEnabled -eq "0") { line 0 "Enabled" } Else { line 0 "Disabled" } line 2 "Schedule the next vDisk update to occur on: " -nonewline If($Disk.activationDateEnabled -eq "1") { line 0 $Disk.activeDate } Else { line 0 "N/A" } line 2 "Class: " $Disk.class line 2 "Type: " $Disk.imageType line 2 "Major #: " $Disk.majorRelease line 2 "Minor #: " $Disk.minorRelease line 2 "Build #: " $Disk.build line 2 "Serial #: " $Disk.serialNumber } }
For PVS 6.x, process all the vDisk Update Management information. This is where I came across one of the bugs in the PVS 6.x PowerShell Programmers Guide. For UpdateTask, the Guide says the dayMask value of 1 = Monday and 64 = Sunday. That is incorrect. 1 = Sunday, 2 = Monday and 64 = Saturday.
From the Guide:
dayMask Days selected values. 1 = Monday, 2 = Tuesday, 4 = Wednesday, 8 = Thursday, 16 = Friday, 32 = Saturday, 64 = Sunday, 128 = Day. Default=0. This is used with Weekly and Monthly Type recurrence.
Min=1, Max=255, Default=4
It should be:
dayMask Days selected values. 1 = Sunday, 2 = Monday, 4 = Tuesday, 8 = Wednesday, 16 = Thursday, 32 = Friday, 64 = Saturday, 128 = Day. Default=0. This is used with Weekly and Monthly Type recurrence.
Min=1, Max=255, Default=4
#process all vDisk Update Management in site (PVS 6.x only) If($PVSVersion -eq "6") { $Temp = $PVSSite.SiteName $GetWhat = "UpdateTask" $GetParam = "siteName=$Temp" $ErrorTxt = "vDisk Update Management information" $Tasks = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 1 " vDisk Update Management" If($Tasks -ne $null) { ForEach($Task in $Tasks) { line 1 " Hosts" #process all virtual hosts for this site $Temp = $PVSSite.SiteName $GetWhat = "VirtualHostingPool" $GetParam = "siteName=$Temp" $ErrorTxt = "Virtual Hosting Pool information" $vHosts = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($vHosts -ne $null) { ForEach($vHost in $vHosts) { line 1 " General tab" line 2 "Type: " -nonewline switch ($vHost.type) { 0 {line 0 "Citrix XenServer"} 1 {line 0 "Microsoft SCVMM/Hyper-V"} 2 {line 0 "VMware vSphere/ESX"} Default {line 0 "Virtualization Host type could not be determined: $($vHost.type)"} } line 2 "Name: " $vHost.virtualHostingPoolName line 2 "Description: " $vHost.description line 2 "Host: " $vHost.server line 1 " Credentials tab" line 2 "Enter the credentials for connecting to the host:" line 3 "Username: " $vHost.userName line 3 "Password: " $vHost.password line 1 " Advanced tab" line 2 "Update limit: " $vHost.updateLimit line 2 "Update timeout: " $vHost.updateTimeout line 2 "Shutdown timeout: " $vHost.shutdownTimeout } } line 1 " vDisks" line 1 " Managed vDisk Properties" #process all the vDisks for this task ID $Temp = $Task.updateTaskId $GetWhat = "diskUpdateDevice" $GetParam = "updateTaskId=$Temp" $ErrorTxt = "vDisk information" $ManagedvDisks = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($ManagedvDisks -ne $null) { ForEach($ManagedvDisk in $ManagedvDisks) { line 1 " General tab" line 2 "ManagedvDisk: " "$($ManagedvDisk.store.name)`\$($ManagedvDisk.disklocatorName)" line 2 "Virtual Host Connection: " $ManagedvDisk.virtualHostingPoolName line 2 "VM Name: " $ManagedvDisk.deviceName line 2 "VM MAC: " $ManagedvDisk.deviceMac line 2 "VM Port: " $ManagedvDisk.port line 1 " Personality tab" #process all personality strings for this device $Temp = $ManagedvDisk.deviceName $GetWhat = "DevicePersonality" $GetParam = "deviceName=$Temp" $ErrorTxt = "Device Personality Strings information" $PersonalityStrings = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($PersonalityStrings -ne $null) { ForEach($PersonalityString in $PersonalityStrings) { line 2 "Name: " $PersonalityString.Name line 2 "String: " $PersonalityString.Value } } line 1 " Status tab" $Temp = $ManagedvDisk.deviceId $GetWhat = "deviceInfo" $GetParam = "deviceId=$Temp" $ErrorTxt = "Device Info information" $Device = BuildPVSObject $GetWhat $GetParam $ErrorTxt DeviceStatus $Device } } line 1 " Tasks" line 1 " Update Task Properties" line 1 " General tab" line 2 "Name: " $Task.updateTaskName line 2 "Description: " $Task.description line 2 "Disable this task: " -nonewline If($Task.enabled -eq "1") { line 0 "Not checked" } Else { line 0 "Checked" } line 1 " Schedule tab" line 2 "Recurrence: " -nonewline switch ($Task.recurrence) { 0 {line 0 "None"} 1 {line 0 "Daily Everyday"} 2 {line 0 "Daily Weekdays only"} 3 {line 0 "Weekly"} 4 {line 0 "Monthly Date"} 5 {line 0 "Monthly Type"} Default {line 0 "Recurrence type could not be determined: $($Task.recurrence)"} } If($Task.recurrence -ne "0") { $AMorPM = "AM" $tempHour = $Task.Hour If($Task.Hour -ge "1" -and $Task.Hour -le "13") { $AMorPM = "AM" } Else { $AMorPM = "PM" If($Task.Hour -eq "0") { $tempHour = $tempHour + 12 } Else { $tempHour = $tempHour - 12 } } $tempMinute = "" If($Task.Minute.length -lt 2) { $tempMinute = "0" + $Task.Minute } line 3 "Run the update at $($tempHour)`:$($tempMinute) $($AMorPM)" } If($Task.recurrence -eq "3") { $dayMask = @{ 1 = "Sunday" 2 = "Monday"; 4 = "Tuesday"; 8 = "Wednesday"; 16 = "Thursday"; 32 = "Friday"; 64 = "Saturday"} For( $i = 1; $i –le 128; $i = $i * 2 ) { If( ( $Task.dayMask –band $i ) –ne 0 ) { line 3 $dayMask.$i } } } If($Task.recurrence -eq "4") { line 3 "On Date " $Task.date } If($Task.recurrence -eq "5") { line 3 "On " -nonewline switch($Task.monthlyOffset) { 1 {line 0 "First"} 2 {line 0 "Second"} 3 {line 0 "Third"} 4 {line 0 "Fourth"} 5 {line 0 "Last"} Default {line 0 "Monthly Offset could not be determined: $($Task.monthlyOffset)"} } $dayMask = @{ 1 = "Sunday" 2 = "Monday"; 4 = "Tuesday"; 8 = "Wednesday"; 16 = "Thursday"; 32 = "Friday"; 64 = "Saturday"; 128 = "Weekday"} For( $i = 1; $i –le 128; $i = $i * 2 ) { If( ( $Task.dayMask –band $i ) –ne 0 ) { line 3 $dayMask.$i } } } line 1 " vDisks tab" line 2 "Select the vDisks to be updated by this task:" $Temp = $ManagedvDisk.deviceId $GetWhat = "diskUpdateDevice" $GetParam = "deviceId=$Temp" $ErrorTxt = "Device Info information" $vDisks = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($vDisks -ne $null) { ForEach($vDisk in $vDisks) { line 3 "vDisk: " -nonewline line 0 "$($vDisk.storeName)`\$($vDisk.diskLocatorName)" line 3 "Host: " $vDisk.virtualHostingPoolName line 3 "VM: " $vDisk.deviceName } } line 1 " ESD tab" line 2 "Select ESD client to use: " -nonewline switch($Task.esdType) { "" {line 0 "None (runs a custom script on the client)"} "WSUS" {line 0 "Microsoft Windows Update Service (WSUS)"} "SCCM" {line 0 "Microsoft System Center Configuration Manager (SCCM)"} Default {line 0 "ESD Client could not be determined: $($Task.esdType)"} } line 1 " Scripts tab" line 2 "Scripts that should execute with the vDisk update processing:" line 3 "Pre-update script: " $Task.preUpdateScript line 3 "Pre-startup script: " $Task.preVmScript line 3 "Post-shutdown script: " $Task.postVmScript line 3 "Post-update script: " $Task.postUpdateScript line 1 " Access tab" line 2 "Upon successful completion of the update, select the access to asign to the vDisk: " -nonewline switch($Task.postUpdateApprove) { 0 {line 0 "Production"} 1 {line 0 "Test"} 2 {line 0 "Maintenance"} Default {line 0 "Access method for vDisk could not be determined: $($Task.postUpdateApprove)"} } } } }
Next are the Device Collections.
#process all device collections in site $Temp = $PVSSite.SiteName $GetWhat = "Collection" $GetParam = "siteName=$Temp" $ErrorTxt = "Device Collection information" $Collections = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($Collections -ne $null) { ForEach($Collection in $Collections) { line 1 " Device Collection Properties" line 1 " General tab" line 2 "Name: " $Collection.collectionName line 2 "Description: " $Collection.description line 1 " Security tab" line 2 "Groups with 'Device Administrator' access:" $Temp = $Collection.collectionId $GetWhat = "authGroup" $GetParam = "collectionId=$Temp" $ErrorTxt = "Device Collection information" $AuthGroups = BuildPVSObject $GetWhat $GetParam $ErrorTxt $DeviceAdmins = $False If($AuthGroups -ne $null) { ForEach($AuthGroup in $AuthGroups) { $Temp = $authgroup.authGroupName $GetWhat = "authgroupusage" $GetParam = "authgroupname=$Temp" $ErrorTxt = "Device Collection Administrator usage information" $AuthGroupUsages = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($AuthGroupUsages -ne $null) { ForEach($AuthGroupUsage in $AuthGroupUsages) { If($AuthGroupUsage.role -eq "300") { $DeviceAdmins = $True line 3 $authgroup.authGroupName } } } } } If(!$DeviceAdmins) { line 3 "There are no device collection administrators defined" } line 2 "Groups with 'Device Operator' access:" $DeviceOperators = $False If($AuthGroups -ne $null) { ForEach($AuthGroup in $AuthGroups) { $Temp = $authgroup.authGroupName $GetWhat = "authgroupusage" $GetParam = "authgroupname=$Temp" $ErrorTxt = "Device Collection Operator usage information" $AuthGroupUsages = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($AuthGroupUsages -ne $null) { ForEach($AuthGroupUsage in $AuthGroupUsages) { If($AuthGroupUsage.role -eq "400") { $DeviceOperators = $True line 3 $authgroup.authGroupName } } } } } If(!$DeviceOperators) { line 3 "There are no device collection operators defined" } line 1 " Auto-Add tab" If($FarmAutoAddEnabled) { line 2 "Template target device: " $Collection.templateDeviceName line 2 "Device Name" line 3 "Prefix: " $Collection.autoAddPrefix line 3 "Length: " $Collection.autoAddNumberLength line 3 "Zero fill: " -nonewline If($Collection.autoAddZeroFill -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } line 3 "Suffix: " $Collection.autoAddSuffix line 3 "Last incremental number: " $Collection.lastAutoAddDeviceNumber } Else { line 2 "The auto-add feature is not enabled at the PVS Farm level" } #for each collection process each device $Temp = $Collection.collectionId $GetWhat = "deviceInfo" $GetParam = "collectionId=$Temp" $ErrorTxt = "Device Info information" $Devices = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($Devices -ne $null) { ForEach($Device in $Devices) { If($Device.type -eq "3") { line 1 " Device with Personal vDisk Properties" } Else { line 1 " Target Device Properties" } line 1 " General tab" line 2 "Name: " $Device.deviceName line 2 "Description: " $Device.description If($PVSVersion -eq "6" -and $Device.type -ne "3") { line 2 "Type: " -nonewline switch ($Device.type) { 0 {line 0 "Production"} 1 {line 0 "Test"} 2 {line 0 "Maintenance"} 3 {line 0 "Personal vDisk"} Default {line 0 "Device type could not be determined: $($Device.type)"} } } If($Device.type -ne "3") { line 2 "Boot from: " -nonewline switch ($Device.bootFrom) { 1 {line 0 "vDisk"} 2 {line 0 "Hard Disk"} 3 {line 0 "Floppy Disk"} Default {line 0 "Boot from could not be determined: $($Device.bootFrom)"} } } line 2 "MAC: " $Device.deviceMac line 2 "Port: " $Device.port If($Device.type -ne "3") { line 2 "Class: " $Device.className line 2 "Disable this device: " -nonewline If($Device.enabled -eq "1") { line 0 "Unchecked" } Else { line 0 "Checked" } } Else { line 2 "vDisk: " $Device.diskLocatorName line 2 "Personal vDisk Drive: " $Device.pvdDriveLetter } line 1 " vDisks tab" line 2 "vDisks for this Device:" #process all vdisks for this device $Temp = $Device.deviceName $GetWhat = "DiskInfo" $GetParam = "deviceName=$Temp" $ErrorTxt = "Device vDisk information" $vDisks = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($vDisks -ne $null) { ForEach($vDisk in $vDisks) { line 3 "Name: " -nonewline line 0 "$($vDisk.storeName)`\$($vDisk.diskLocatorName)" } } line 2 "Options" line 3 "List local hard drive in boot menu: " -nonewline If($Device.localDiskEnabled -eq "1") { line 0 "Enabled" } Else { line 0 "Disabled" } #process all bootstrap files for this device $Temp = $Device.deviceName $GetWhat = "DeviceBootstraps" $GetParam = "deviceName=$Temp" $ErrorTxt = "Device Bootstrap information" $Bootstraps = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($Bootstraps -ne $null) { ForEach($Bootstrap in $Bootstraps) { line 3 "Custom bootstrap file: " -nonewline line 0 "$($Bootstrap.bootstrap) `($($Bootstrap.menuText)`)" } } line 1 " Authentication tab" line 2 "Select the type of authentication to use for this device" line 3 "Authentication: " -nonewline switch($Device.authentication) { 0 {line 0 "None"} 1 {line 0 "Username and password"; line 3 "Username: " $Device.user; line 3 "Password: " $Device.password} 2 {line 0 "External verification (User supplied method)"} Default {line 0 "Authentication type could not be determined: $($Device.authentication)"} } line 1 " Personality tab" #process all personality strings for this device $Temp = $Device.deviceName $GetWhat = "DevicePersonality" $GetParam = "deviceName=$Temp" $ErrorTxt = "Device Personality Strings information" $PersonalityStrings = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($PersonalityStrings -ne $null) { ForEach($PersonalityString in $PersonalityStrings) { line 2 "Name: " $PersonalityString.Name line 2 "String: " $PersonalityString.Value } } line 1 " Status tab" DeviceStatus $Device } } } }
For PVS 5.6, process all user groups.
#process all user groups in site (PVS 5.6 only) If($PVSVersion -eq "5") { $Temp = $PVSSite.siteName $GetWhat = "UserGroup" $GetParam = "siteName=$Temp" $ErrorTxt = "User Group information" $UserGroups = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 1 "User Group Properties" If($UserGroups -ne $null) { ForEach($UserGroup in $UserGroups) { line 1 " General tab" line 2 "Name: " $UserGroup.userGroupName line 2 "Description: " $UserGroup.description line 2 "Class: " $UserGroup.className line 2 "Disable this user group: " -nonewline If($UserGroup.enabled -eq "1") { line 0 "Not Checked" } Else { line 0 "Checked" } #process all vDisks for usergroup $Temp = $UserGroup.userGroupId $GetWhat = "DiskInfo" $GetParam = "userGroupId=$Temp" $ErrorTxt = "User Group Disk information" $vDisks = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 1 " vDisk tab" line 2 "vDisks for this user group:" If($vDisks -ne $null) { ForEach($vDisk in $vDisks) { line 3 "$($vDisk.storeName)`\$($vDisk.diskLocatorName)" } } } } }
The last item for Sites is Site Views.
#process all site views in site $Temp = $PVSSite.siteName $GetWhat = "SiteView" $GetParam = "siteName=$Temp" $ErrorTxt = "Site View information" $SiteViews = BuildPVSObject $GetWhat $GetParam $ErrorTxt line 1 " View Properties" If($SiteViews -ne $null) { ForEach($SiteView in $SiteViews) { line 1 " General tab" line 2 "Name: " $SiteView.siteViewName line 2 "Description: " $SiteView.description line 1 " Members tab" #process each target device contained in the site view $Temp = $SiteView.siteViewId $GetWhat = "Device" $GetParam = "siteViewId=$Temp" $ErrorTxt = "Site View Device Members information" $Members = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($Members -ne $null) { ForEach($Member in $Members) { line 2 $Member.deviceName } } } } Else { line 2 "There are no Site Views configured" }
After processing everything for Sites, time to clean up.
write-output $global:output $global:output = "" $PVSSites = $null $authgroups = $null $servers = $null $stores = $null $bootstrapnames = $null $tempserverbootstrap = $null $serverbootstraps = $null $UserGroups = $null $Disks = $null $vDisks = $null $Members = $null $SiteViews = $null
Back to the Farm level in the PSC, next up is Farm Views.
#process the farm views now line 0 "" line 0 "Farm View Properties" $Temp = $PVSSite.siteName $GetWhat = "FarmView" $GetParam = "" $ErrorTxt = "Farm View information" $FarmViews = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($FarmViews -ne $null) { ForEach($FarmView in $FarmViews) { line 1 "General tab" line 2 "Name: " $FarmView.farmViewName line 2 "Description: " $FarmView.description line 1 "Members tab" #process each target device contained in the farm view $Temp = $FarmView.farmViewId $GetWhat = "Device" $GetParam = "farmViewId=$Temp" $ErrorTxt = "Farm View Device Members information" $Members = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($Members -ne $null) { ForEach($Member in $Members) { line 2 $Member.deviceName } } } } Else { line 1 "There are no Farm Views configured" } write-output $global:output $global:output = "" $FarmViews = $null $Members = $null
Finally, Stores.
#process the stores now line 0 "" line 0 "Stores Properties" $GetWhat = "Store" $GetParam = "" $ErrorTxt = "Farm Store information" $Stores = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($Stores -ne $null) { ForEach($Store in $Stores) { line 1 "General tab" line 2 "Name: " $Store.StoreName line 2 "Description: " $Store.description line 2 "Site that acts as the owner of this store: " -nonewline If($Store.siteName -eq $null -or $Store.siteName -eq "") { line 0 "" } Else { line 0 $Store.siteName } line 1 "Servers tab" #find the servers (and the site) that serve this store $GetWhat = "Server" $GetParam = "" $ErrorTxt = "Server information" $Servers = BuildPVSObject $GetWhat $GetParam $ErrorTxt $StoreSite = "" $StoreServers = @() If($Servers -ne $null) { ForEach($Server in $Servers) { $Temp = $Server.serverName $GetWhat = "ServerStore" $GetParam = "serverName=$Temp" $ErrorTxt = "Server Store information" $ServerStore = BuildPVSObject $GetWhat $GetParam $ErrorTxt If($ServerStore -ne $null -and $ServerStore.storeName -eq $Store.StoreName) { $StoreSite = $Server.siteName $StoreServers += $Server.serverName } } } line 2 "Site: " $StoreSite line 2 "Servers that provide this store:" ForEach($StoreServer in $StoreServers) { line 3 $StoreServer } line 1 "Paths tab" line 2 "Default store path: " $Store.path line 2 "Default write-cache paths: " If($Store.cachePath -ne $null) { $WCPaths = $Store.cachePath.replace(",","`n`t`t`t") line 3 $WCPaths } } } Else { line 1 "There are no Stores configured" } write-output $global:output $global:output = "" $Stores = $null $Servers = $null $StoreSite = $null $StoreServers = $null $ServerStore = $null
How to use this script?
I saved the script as PVS_Inventory.ps1 in theC:\Scripts folder. From the PowerShell prompt, change to the C:\Scripts folder, or the folder where you saved the script. From the PowerShell prompt, type in:
.\PVS_Inventory.ps1 |out-file .\PVSFarm.txt and press Enter.
Open PVSFarm.txt in either WordPad or Microsoft Word (Figure 7).
Figure 7 If you have any suggestions for the script, please let me know. Send an e-mail to webster@carlwebster.com.
NOTE: This script is continually updated. You can always find the most current version by going to https://carlwebster.com/where-to-get-copies-of-the-documentation-scripts/
8 Responses to “Documenting a Citrix Provisioning Services Farm with Microsoft PowerShell”
Leave a Reply to Carl Webster
August 28, 2017 at 1:10 pm
Carl,
First of all, thanks for making available such useful information. I am looking for a copy of the ‘Citrix Provisioning Services 6.1 PowerShell Programmer’s Guide’. It is nowhere to be found. Would you happen to have a copy laying around? Thanks
August 28, 2017 at 2:33 pm
Sent
Webster
September 30, 2015 at 8:33 am
Hi Carl
I’m using the function BuildPVSObject, but i get some strange results:
$GetWhat = “collection”
$GetParam = “siteName=MySite,collectionName=MyCollection”
$ErrorTxt = “PVS collection information”
$Result = BuildPVSObject $GetWhat $GetParam $ErrorTxt
then i get the following error:
Mcli-Get : The specified Site does not exist.
$GetParam = “siteName=MySite” works korrectly…
Seems to be a strange behaviour, when i print out the full mcli command:
If($MCLIGetParameters -ne ”)
{
$MCLIGetResult = Mcli-Get “$($MCLIGetWhat)” -p “$($MCLIGetParameters)”
Write-Host “Mcli-Get $($MCLIGetWhat) -p $($MCLIGetParameters)”
}
…and run the printed code in a powershell window, it works.
Have you any idea, what the cause of this error is?
Thomas
October 3, 2015 at 5:15 pm
This is one of the stupid things with the current PVS PoSH implementation. You are passing two parameters in a string which is interpreted as an array. When I needed to pass two, or more, parameters, I could not use the BuildPVSObject function.
For example:
#cannot use the BuildPVSObject Function here
$serverbootstraps = @()
ForEach($Bootstrapname in $Bootstrapnames)
{
#get serverbootstrap info
$error.Clear()
$tempserverbootstrap = Mcli-Get ServerBootstrap -p name=”$($Bootstrapname.name)”,servername=”$($server.serverName)”
If($error.Count -eq 0)
{
$serverbootstrap = $Null
ForEach($record in $tempserverbootstrap)
{
If($record.length -gt 5 -and $record.substring(0,6) -eq “Record”)
{
If($serverbootstrap -ne $Null)
{
$serverbootstraps += $serverbootstrap
}
$serverbootstrap = new-object System.Object
#add the bootstrapname name value to the serverbootstrap object
$property = “BootstrapName”
$value = $Bootstrapname.name
Add-Member -inputObject $serverbootstrap -MemberType NoteProperty -Name $property -Value $value
}
$index = $record.IndexOf(‘:’)
If($index -gt 0)
{
$property = $record.SubString(0, $index)
$value = $record.SubString($index + 2)
If($property -ne “Executing”)
{
Add-Member -inputObject $serverbootstrap -MemberType NoteProperty -Name $property -Value $value
}
}
}
$serverbootstraps += $serverbootstrap
}
Else
{
WriteWordLine 0 0 “Server Bootstrap information could not be retrieved”
WriteWordLine 0 0 “Error returned is ” $error[0].FullyQualifiedErrorId.Split(‘,’)[0].Trim()
}
}
Hope this helps and I really can’t wait for PVS 7.7 and the new REAL PowerShell implementation.
Webster
October 15, 2015 at 3:50 pm
Thanks for these great scripts! I was able to modify the BuildPVSObject function to take multiple parameters using this code:
If($MCLIGetParameters -ne ”)
{
# split multiple params into an array
$ArrGetParams=$MCLIGetParameters.Split(‘,’)
# pass the array to Mcli-get
$MCLIGetResult = Mcli-Get “$($MCLIGetWhat)” -p ($ArrGetParams)
}
Else
{
$MCLIGetResult = Mcli-Get “$($MCLIGetWhat)”
}
It’s working so far in my limited testing. The remainder of the function code is unaffected.
February 25, 2015 at 9:37 am
Good Morning Carl. I’m running the PVS script on my PVS server. Is there a way to run the script with out installing word on my PVS server? The following is the message I receive.
PS C:\scripts> .\PVS_Inventory.ps1 |out-file .\PVSFarm.txt
VERBOSE: 02/25/2015 07:32:27: Testing output parameters
VERBOSE: 02/25/2015 07:32:27: MSWord is set
VERBOSE: 02/25/2015 07:32:27: CoName is
VERBOSE: 02/25/2015 07:32:27: Checking for McliPSSnapin
VERBOSE: 02/25/2015 07:32:28: Verifying PVS SOAP and Stream Services are running
VERBOSE: 02/25/2015 07:32:28: Getting PVS version info
VERBOSE: 02/25/2015 07:32:29: Build PVS farm values
This script directly outputs to Microsoft Word, please install Microsoft Word
PS C:\scripts>
February 25, 2015 at 9:39 am
Yes, there is. It is documented in the ReadMe file.
https://carlwebster.sharefile.com/d-s0efb89e2de64b8da
Webster
February 26, 2015 at 8:00 pm
Thank you Carl!