Extract SequencerVersion from .appv file

For a Windows XP migration project to Windows 7, we were migrating VMWare Thinapp applications to Microsoft App-V applications. Most of the time, we’d use App-V 5.0 SP1 to build the sequence. However, there were some applications that made the App-V Sequencer become unresponsive during the monitoring process, as seen below.

appv-crash

To solve this, we used the App-V 5.0 SP2 Sequencer for those applications.

After the project was over, the customer obviously wanted to know which applications were done with SP2. I’ll be really honest here and say that we forgot to keep track of that. I took the issue upon myself and said I’d find out, while thinking “It should somehow be possible with PowerShell.”

PowerShell

I hit Google with the search term “powershell extract app-v”. Within the results was a link to the Virtual Engine website (link). Iain Brighton has written a complete module for all sorts of fun with App-V 5.0 .appv files. Reading the documentation, 2 functions caught my eye: Get-AppV5FileXmlPackage and Save-AppV5File.

Get-AppV5FileXmlPackage Retrieve details of the AppxManifest, AppxBlockMap, FileSystemMetadata, PackageHistory and StreamMap XML files within an .APPV file package as a single XmlDocument object
Save-AppV5File Extract and save any file within an .APPV file package

Yes, I could use both of these functions. There are a lot of possibilities with these two alone, and the module even has more functions.

However, I only used them for inspiration. With all due respect, I didn’t want to use an extra module. Elsewhere on the Virtual Engine site, I found a blog by Iain about extracting files from an .appv with powershell.

To sum up what I wanted to accomplish:

  • Use and modify the above example code provided by Virtual Engine to extract the PackageHistory.xml from the .appv file;
  • Use a parameter to specify the path of the root folder holding all the .appv files;
  • Extract the SequencerVersion of the PackageHistory.xml;
  • Lookup the version number on a site that holds a list of current App-V 5.0 file versions.

Since the Virtual Engine blog is clear, I won’t be providing information already mentioned in that post. This means that I will not explain about the first and third bullet listed in my summary above.

The Path parameter

[CmdletBinding()]
Param(

# Create a path parameter and check whether the supplied path is valid.

[Parameter(Mandatory=$True,HelpMessage="Enter the root path where all .AppV files are saved.")]
[ValidateScript({
if(!(Test-Path $_ -PathType Container)) { throw "$_ does not exist." }
else { return $true }
})]
[String]$path
)

Above is the code I used to create my path parameter. There were 2 things new to me in Iain’s VirtualEngine-AppV5 module, one being the HelpMessage and the other being the ValidateScript.

HelpMessage provides the user to get a message when no parameter has been supplied upon execution of the script. If a parameter has not been provided and it is mandatory, PowerShell will ask the user to supply the parameter.

helpmessage1

When a HelpMessage has been defined, the user sees the message “(Type !? for Help.)”. When a user does that, the script shows the value of HelpMessage.

helpmessage2

ValidateScript does exactly what one might think. It validates the parameter value the user provided and – in this script – uses Test-Path to see if the supplied path exists.

Lookup App-V version

This was slightly new to me aswell. I found a Microsoft Support site listing all of the currently available App-V versions (link). When grabbing the SequencerVersion from the PackageHistory.xml, I noticed it only provides the Build version. The user would than have to go to the Support site and manually check for the App-V version. I wanted to save the users the trouble and have the script provide that information.

I initially started with the following code:

# Retrieve a list of App-V versions

$url = "http://support.microsoft.com/kb/2940578"
$res = Invoke-WebRequest $url
$html = $res.AllElements | Where Class -eq "table" | Select -ExpandProperty outerHTML

This worked for me, but seemed rather slow. Using Measure-Command, I got the following results:

measure-command1

Almost 4.7 seconds?! Why does it take that long? Because I’m acquiring all HTML elements, that’s why. Is there a faster way to accomplish the same? Yes, fortunately there is. By changing the line which went into the Measure-Command to the following:

$html = $res.ParsedHtml.body.GetElementsByClassName("table") | Select -ExpandProperty outerHTML

Measure-Command now showed me the following:

measure-command2

I won 4 seconds! Now I can safely – and quickly – get the SequencerVersion.

# There might be multiple PackageHistoryItems and we want to see them all, including their information

$xml.PackageHistory.PackageHistoryItem | % {

# Save the SequencerVersion and the Time (timestamp the file was saved) of the PackageHistoryItem

$version = $_.SequencerVersion
$timestamp = $_.Time

# Use a regular expression to get the corresponding App-V version

Write-Output ([regex]::Matches($html, '<td>(App-V.*?)</td><td>(\d{1}.*?)</') | `
? { $_.Captures[0].Groups[2].value -eq $version } | `
% { $_.Captures[0].Groups[1].value + " - " + $timestamp })
}

I had to save the SequencerVersion to a variable, because Write-Output would have put “$_.SequencerVersion” as plain text instead of its actual value. Using a regular expression, the script searches the HTML code stored in the variable $html for matches. To understand a bit more about regular expressions and how to use them, have a look at this website. The Write-Output basically says this: “For each App-V version found, check if the build version of the corresponding App-V version matches SequencerVersion and write the App-V version with the time the package was saved.” It does however provide a nanosecond output, which apparently is saved in the PackageHistory.xml.

endresult1

To solve this, add

[datetime]

in front of $_.Time. So it would look like this:

$timestamp = [datetime]$_.Time

The end result should look this:

endresult2

You can download the script by clicking here.