• PVS V4.1 Documentation Script Has Been Updated 2-FEB-2014

    February 2, 2014

    PowerShell, PVS

    I received a message on Twitter from the very smart and always helpful Shane Kleinert (@ShaneKleinert you should follow him) asking if my Provisioning Services (PVS) documentation script had the vDisk Versions and Audit Trail information. I was sure it did until I checked and saw that I had never added that information to my script and no one, until now, had noticed or asked about it. Of course, I had to add those two items to my script. This article describes the process.

    I downloaded the latest PVS 7.1 PowerShell Programmers Guide and started looking for where to find the proper MCLI-GET command to use. I finally found it in MCLI-Get DiskVersion. The only problem was that it requires three parameters and my PVSObject function can only handle one parameter. Parameters passed to the MCLI-Get wrapper are comma delimited (with no spaces allowed) and PowerShell then treats that as an array. So I had to put the code to handle getting DiskVersion directly into the script since I couldn’t find a way around the comma delimited parameters treated as an array issue.

    There are two items I have yet to find in the PVS PowerShell Programmers Guide: Boot production devices from version and which version is the current booting version (the one with the green checkmark).

    vDisk Versions
    vDisk Versions

    If you know how to get those two pieces of information, please let me know and I will add them to the script and give you credit.

    The code to handle vDisk Versions:

    #process Versions menu
    #get versions info
    Write-Verbose "$(Get-Date): `t`t`tProcessing vDisk Versions"
    $VersionsObjects = @()
    $error.Clear()
    $MCLIGetResult = Mcli-Get DiskVersion -p diskLocatorName="$($Disk.diskLocatorName)",storeName="$($disk.storeName)",siteName="$($disk.siteName)"
    If($error.Count -eq 0)
    {
    	#build versions object
    	$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
    	$DiskVersions = $PluralObject
    	
    	If($DiskVersions -ne $Null)
    	{
    		WriteWordLine 0 1 "vDisk Versions"
    		ForEach($DiskVersion in $DiskVersions)
    		{
    			Write-Verbose "$(Get-Date): `t`t`t`tProcessing vDisk Version $($DiskVersion.version)"
    			WriteWordLine 0 2 "Version`t`t`t`t`t: " $DiskVersion.version
    			WriteWordLine 0 2 "Created`t`t`t`t`t: " $DiskVersion.createDate
    			If(![String]::IsNullOrEmpty($DiskVersion.scheduledDate))
    			{
    				WriteWordLine 0 2 "Released`t`t`t`t: " $DiskVersion.scheduledDate
    			}
    			WriteWordLine 0 2 "Devices`t`t`t`t`t: " $DiskVersion.deviceCount
    			WriteWordLine 0 2 "Access`t`t`t`t`t: " -NoNewLine
    			Switch ($DiskVersion.access)
    			{
    				0 {WriteWordLine 0 0 "Production"}
    				1 {WriteWordLine 0 0 "Maintenance"}
    				2 {WriteWordLine 0 0 "Maintenance Highest Version"}
    				3 {WriteWordLine 0 0 "Override"}
    				4 {WriteWordLine 0 0 "Merge"}
    				5 {WriteWordLine 0 0 "Merge Maintenance"}
    				6 {WriteWordLine 0 0 "Merge Test"}
    				7 {WriteWordLine 0 0 "Test"}
    				Default {WriteWordLine 0 0 "Access could not be determined: $($DiskVersion.access)"}
    			}
    			WriteWordLine 0 2 "Type`t`t`t`t`t: " -NoNewLine
    			Switch ($DiskVersion.type)
    			{
    				0 {WriteWordLine 0 0 "Base"}
    				1 {WriteWordLine 0 0 "Manual"}
    				2 {WriteWordLine 0 0 "Automatic"}
    				3 {WriteWordLine 0 0 "Merge"}
    				4 {WriteWordLine 0 0 "Merge Base"}
    				Default {WriteWordLine 0 0 "Type could not be determined: $($DiskVersion.type)"}
    			}
    			If(![String]::IsNullOrEmpty($DiskVersion.description))
    			{
    				WriteWordLine 0 2 "Properties`t`t`t`t: " $DiskVersion.description
    			}
    			WriteWordLine 0 2 "Can Delete`t`t`t`t: "  -NoNewLine
    			Switch ($DiskVersion.canDelete)
    			{
    				0 {WriteWordLine 0 0 "No"}
    				1 {WriteWordLine 0 0 "Yes"}
    			}
    			WriteWordLine 0 2 "Can Merge`t`t`t`t: "  -NoNewLine
    			Switch ($DiskVersion.canMerge)
    			{
    				0 {WriteWordLine 0 0 "No"}
    				1 {WriteWordLine 0 0 "Yes"}
    			}
    			WriteWordLine 0 2 "Can Merge Base`t`t`t: "  -NoNewLine
    			Switch ($DiskVersion.canMergeBase)
    			{
    				0 {WriteWordLine 0 0 "No"}
    				1 {WriteWordLine 0 0 "Yes"}
    			}
    			WriteWordLine 0 2 "Can Promote`t`t`t`t: "  -NoNewLine
    			Switch ($DiskVersion.canPromote)
    			{
    				0 {WriteWordLine 0 0 "No"}
    				1 {WriteWordLine 0 0 "Yes"}
    			}
    			WriteWordLine 0 2 "Can Revert back to Test`t`t`t: "  -NoNewLine
    			Switch ($DiskVersion.canRevertTest)
    			{
    				0 {WriteWordLine 0 0 "No"}
    				1 {WriteWordLine 0 0 "Yes"}
    			}
    			WriteWordLine 0 2 "Can Revert back to Maintenance`t: "  -NoNewLine
    			Switch ($DiskVersion.canRevertMaintenance)
    			{
    				0 {WriteWordLine 0 0 "No"}
    				1 {WriteWordLine 0 0 "Yes"}
    			}
    			WriteWordLine 0 2 "Can Set Scheduled Date`t`t`t: "  -NoNewLine
    			Switch ($DiskVersion.canSetScheduledDate)
    			{
    				0 {WriteWordLine 0 0 "No"}
    				1 {WriteWordLine 0 0 "Yes"}
    			}
    			WriteWordLine 0 2 "Can Override`t`t`t`t: "  -NoNewLine
    			Switch ($DiskVersion.canOverride)
    			{
    				0 {WriteWordLine 0 0 "No"}
    				1 {WriteWordLine 0 0 "Yes"}
    			}
    			WriteWordLine 0 2 "Is Pending`t`t`t`t: "  -NoNewLine
    			Switch ($DiskVersion.isPending)
    			{
    				0 {WriteWordLine 0 0 "No, version Scheduled Date has occurred"}
    				1 {WriteWordLine 0 0 "Yes, version Scheduled Date has not occurred"}
    			}
    			WriteWordLine 0 2 "Replication Status`t`t`t: " -NoNewLine
    			Switch ($DiskVersion.goodInventoryStatus)
    			{
    				0 {WriteWordLine 0 0 "Not available on all servers"}
    				1 {WriteWordLine 0 0 "Available on all servers"}
    				Default {WriteWordLine 0 0 "Replication status could not be determined: $($DiskVersion.goodInventoryStatus)"}
    			}
    			WriteWordLine 0 2 "Disk Filename`t`t`t`t: " $DiskVersion.diskFileName
    			WriteWordLine 0 0 ""
    		}
    	}
    }
    Else
    {
    	WriteWordLine 0 0 "Disk Version information could not be retrieved"
    	WriteWordLine 0 0 "Error returned is " $error[0].FullyQualifiedErrorId.Split(',')[0].Trim()
    }
    

    Here is what the vDisk Versions part of the report looks like for my lab.

    vDisk Versions Report
    vDisk Versions Report

    Next up was getting the Audit Trail data.

    Audit Trail
    Audit Trail

    This is retrieved using MCLI-GET AuditTrail. This also required three parameters with two of them being Start and End dates. That required me adding two more parameters to my script: StartDate and EndDate. The dates are entered in MM/DD/YYYY format but MCLI-GET AuditTrail requires the dates in YYYY/MM/DD format. The default, by both MCLI-GET AuditTrail and the script, is to include only the previous seven days of audit trail data.

    The audit trail data is put into a Word table to get more data per page. In order for the table to fit in the width of the page I removed the Number and Domain columns and changed the font size to 9 points.

    In the PVS console, you get to the Audit Trail report at the Farm level but MCLI-GET AuditTrail does not have a way to retrieve the data for the Farm??? I decided to put it as the last item, on a new page, in the Site section of the report.

    The new parameters:

    .PARAMETER StartDate
    	Start date, in MM/DD/YYYY format, for the Audit Trail report.
    	Default is today's date minus seven days.
    .PARAMETER EndDate
    	End date, in MM/DD/YYYY format, for the Audit Trail report.
    	Default is today's date.
    
    
    .EXAMPLE
    	PS C:\PSScript > .\PVS_Inventory_V41.ps1 -StartDate "01/01/2014" -EndDate "01/31/2014" -verbose
    	
    	Will use all Default values.
    	HKEY_CURRENT_USER\Software\Microsoft\Office\Common\UserInfo\Company="Carl Webster"
    	$env:username = Administrator
    	AdminAddress = LocalHost
    
    	Carl Webster for the Company Name.
    	Sideline for the Cover Page format.
    	Administrator for the User Name.
    	LocalHost for AdminAddress.
    	Will display verbose messages as the script is running.
    	Will return all Audit Trail entries from "01/01/2014" through "01/31/2014".
    
    
    	[parameter(
    	Position = 9, 
    	Mandatory=$False)
    	] 
    	[Datetime]$StartDate = ((Get-Date -displayhint date).AddDays(-7)),
    
    	[parameter(
    	Position = 10, 
    	Mandatory=$False)
    	] 
    	[Datetime]$EndDate = (Get-Date -displayhint date)
    

    The code to handle the audit trail data is 300 lines so I will snip out some lines for this article.

    #add Audit Trail
    Write-Verbose "$(Get-Date): `t`t`tProcessing Audit Trail"
    $AuditTrailObjects = @()
    $error.Clear()
    
    #the audittrail call requires the dates in YYYY/MM/DD format
    $Sdate = '{0:yyyy/MM/dd}' -f $StartDate
    $Edate = '{0:yyyy/MM/dd}' -f $EndDate
    $MCLIGetResult = Mcli-Get AuditTrail -p siteName="$($PVSSite.siteName)",beginDate="$($SDate)",endDate="$($EDate)"
    If($error.Count -eq 0)
    {
    	#build audit trail object
    	$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
    	$Audits = $PluralObject
    	
    	If($Audits -ne $Null)
    	{
    		$selection.InsertNewPage()
    		WriteWordLine 2 0 "Audit Trail"
    		WriteWordLine 0 0 "Audit Trail for dates $($StartDate) through $($EndDate)"
    		$TableRange   = $doc.Application.Selection.Range
    		[int]$Columns = 6
    		If($Audits -is [array])
    		{
    			[int]$Rows = $Audits.Count +1
    		}
    		Else
    		{
    			[int]$Rows = 2
    		}
    		Write-Verbose "$(Get-Date): `t`t`t`tAdd Audit Trail table to doc"
    		$Table = $doc.Tables.Add($TableRange, $Rows, $Columns)
    		$table.Style = "Table Grid"
    		$table.Borders.InsideLineStyle = 0
    		$table.Borders.OutsideLineStyle = 1
    		$Table.Cell(1,1).Shading.BackgroundPatternColor = $wdColorGray15
    		$Table.Cell(1,1).Range.Font.Bold = $True
    		$Table.Cell(1,1).Range.Font.size = 9
    		$Table.Cell(1,1).Range.Text = "Date/Time"
    		$Table.Cell(1,2).Shading.BackgroundPatternColor = $wdColorGray15
    		$Table.Cell(1,2).Range.Font.Bold = $True
    		$Table.Cell(1,2).Range.Font.size = 9
    		$Table.Cell(1,2).Range.Text = "Action"
    		$Table.Cell(1,3).Shading.BackgroundPatternColor = $wdColorGray15
    		$Table.Cell(1,3).Range.Font.Bold = $True
    		$Table.Cell(1,3).Range.Font.size = 9
    		$Table.Cell(1,3).Range.Text = "Type"
    		$Table.Cell(1,4).Shading.BackgroundPatternColor = $wdColorGray15
    		$Table.Cell(1,4).Range.Font.Bold = $True
    		$Table.Cell(1,4).Range.Font.size = 9
    		$Table.Cell(1,4).Range.Text = "Name"
    		$Table.Cell(1,5).Shading.BackgroundPatternColor = $wdColorGray15
    		$Table.Cell(1,5).Range.Font.Bold = $True
    		$Table.Cell(1,5).Range.Font.size = 9
    		$Table.Cell(1,5).Range.Text = "User"
    		$Table.Cell(1,6).Shading.BackgroundPatternColor = $wdColorGray15
    		$Table.Cell(1,6).Range.Font.Bold = $True
    		$Table.Cell(1,6).Range.Font.size = 9
    		$Table.Cell(1,6).Range.Text = "Path"
    		[int]$xRow = 1
    		[int]$Cnt = 0
    		ForEach($Audit in $Audits)
    		{
    			$xRow++
    			$Cnt++
    			Write-Verbose "$(Get-Date): `t`t`tAdding row for audit trail item # $Cnt"
    			If($xRow % 2 -eq 0)
    			{
    				$Table.Cell($xRow,1).Shading.BackgroundPatternColor = $wdColorGray05
    				$Table.Cell($xRow,2).Shading.BackgroundPatternColor = $wdColorGray05
    				$Table.Cell($xRow,3).Shading.BackgroundPatternColor = $wdColorGray05
    				$Table.Cell($xRow,4).Shading.BackgroundPatternColor = $wdColorGray05
    				$Table.Cell($xRow,5).Shading.BackgroundPatternColor = $wdColorGray05
    				$Table.Cell($xRow,6).Shading.BackgroundPatternColor = $wdColorGray05
    			}
    			$Table.Cell($xRow,1).Range.Font.size = 9
    			$Table.Cell($xRow,1).Range.Text = $Audit.time
    			$Tmp = ""
    			Switch([int]$Audit.action)
    			{
    				1 { $Tmp = "AddAuthGroup"}
    				2 { $Tmp = "AddCollection"}
    				3 { $Tmp = "AddDevice"}
    				4 { $Tmp = "AddDiskLocator"}
    				5 { $Tmp = "AddFarmView"}
    				6 { $Tmp = "AddServer"}
    				7 { $Tmp = "AddSite"}
    				8 { $Tmp = "AddSiteView"}
    				9 { $Tmp = "AddStore"}
    				10 { $Tmp = "AddUserGroup"}
    				# SNIP 140 lines of code
    				7033 { $Tmp = "SetListUserGroupCustomPropertyAdd"}				
    			}
    			$Table.Cell($xRow,2).Range.Font.size = 9
    			$Table.Cell($xRow,2).Range.Text = $Tmp
    			$Tmp = ""
    			Switch ($Audit.type)
    			{
    				0 {$Tmp = "Many"}
    				1 {$Tmp = "AuthGroup"}
    				2 {$Tmp = "Collection"}
    				3 {$Tmp = "Device"}
    				4 {$Tmp = "Disk"}
    				5 {$Tmp = "DeskLocator"}
    				6 {$Tmp = "Farm"}
    				7 {$Tmp = "FarmView"}
    				8 {$Tmp = "Server"}
    				9 {$Tmp = "Site"}
    				10 {$Tmp = "SiteView"}
    				11 {$Tmp = "Store"}
    				12 {$Tmp = "System"}
    				13 {$Tmp = "UserGroup"}
    				Default { {$Tmp = "Undefined"}}
    			}
    			$Table.Cell($xRow,3).Range.Font.size = 9
    			$Table.Cell($xRow,3).Range.Text = $Tmp
    			$Table.Cell($xRow,4).Range.Font.size = 9
    			$Table.Cell($xRow,4).Range.Text = $Audit.objectName
    			$Table.Cell($xRow,5).Range.Font.size = 9
    			$Table.Cell($xRow,5).Range.Text = $Audit.userName
    			$Table.Cell($xRow,6).Range.Font.size = 9
    			$Table.Cell($xRow,6).Range.Text = $Audit.path
    		}
    		$table.AutoFitBehavior(1)
    
    		#return focus back to document
    		Write-Verbose "$(Get-Date): `t`tReturn focus back to document"
    		$doc.ActiveWindow.ActivePane.view.SeekView=$wdSeekMainDocument
    
    		#move to the end of the current document
    		Write-Verbose "$(Get-Date): `tMove to the end of the current document"
    		$selection.EndKey($wdStory,$wdMove) | Out-Null
    		Write-Verbose "$(Get-Date):"
    	}
    }
    

    The Audit Trail report for my lab.

    Audit Trail Report
    Audit Trail Report

    Script version is now 4.12. And thanks to Shane, the script is now 500 lines longer! Man that was a lot of late night typing. 🙂

    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/

    Thanks

    Webster

    , , ,

    About Carl Webster

    Webster is a Sr. Solutions Architect for Choice Solutions, LLC and specializes in Citrix, Active Directory and Technical Documentation. Webster has been working with Citrix products for many years starting with Multi-User OS/2 in 1990.

    View all posts by Carl Webster

    No comments yet.

    Leave a Reply