MSIX – A Unified Application Packaging Format

You’d be forgiven for being confused when it comes to Microsoft application packaging and installation frameworks.  It’s a constantly evolving landscape of emerging technologies, with various name changes along the way!

This blog provides a brief history of application packaging and installation frameworks, taking us from setup.exe to the unified application packaging format that is MSIX.

InstallShield Setup.exe

Back in the days of Windows 95, Microsoft endorsed InstallShield as an installation framework for applications.  However these setup.exe installation packages weren’t very intelligent.  They had no real knowledge of other applications that were currently installed and often resulted in conflicts such as DLL hell and, over time, the degradation of the underlying operating system commonly referred to as ‘winrot’.

The total cost of ownership (TCO) for each device was relatively high, since these conflicts frequently required debugging or ultimately desktop rebuilds.

Windows Installer (MSI)

This led to the introduction of Microsoft Installer (MSI – now known as Windows Installer), which provided more resiliency in the form of rollback, self healing, self repair, dynamic link library (DLL) reference counting and much more.

If authored by a competent application packager, MSI provided a highly reliable installation framework and still does.  The big problem was that many application packagers weren’t (and still aren’t) competent.  They don’t understand how each table in the MSI relational database works, or about COM registration, or about the plethora of Custom Actions available, and a host of other things.  The complexity of authoring bullet proof MSI packages is a steep learning curve for most.

Microsoft ClickOnce

Microsoft introduced ClickOnce as part of the .Net 2 framework, aimed at reducing application conflicts by installing self-contained and self-updating applications.  These  deployments enabled non-administrative users to install them and granted only those users code access security (CAS) permissions necessary for the application.  But there were limitations which prevented ClickOnce applications from installing new fonts, installing files to the Global Assembly Cache (GAC) and writing registry changes.

Microsoft App-V 4.x

Microsoft App-V 4, formerly Softricity Softgrid, overcame ClickOnce technological limitations and eliminated conflict issues associated with setup.exe and MSI by providing isolation against other applications and the underlying operating system.  This meant that junk cleaning and conflict checking was no longer as important.  But due to this level of isolation and the new file format (which streamed in blocks rather than files), there were inherent technological limitations such as the inability to natively package device drivers and a package size limitation of 4gb.

Microsoft App-V 5.x

Microsoft then developed App-V 5.  This was a complete rewrite of the App-V 4 technology by doing away with the SFT file system and introducing an NTFS file system and ‘real’ registry.  The package format also adopted principles of the Open Package Specification (OPC), which is essentially a set of standards to store binary data and XML metadata in a single entity leveraging Open XML and ZIP formats.

App-V 5 had (and still has) a very high success rate when packaging Win32 applications (your common desktop applications) and resolved some of the issues associated with App-V 4 such as the 4gb package size limitation.  However once again, due to being a virtualised package format it still inherited some of the same limitations associated App-V 4.6 such as an inability to package kernel-mode device drivers.

Universal Windows Platform (UWP)

Meanwhile user requirements were beginning to change.  Users needed to access applications on mobile devices in a touch-friendly way.  So Microsoft introduced Universal Windows Platform (UWP) applications (formerly ‘Windows Store’ app or ‘Metro’ app) based on the Appx file format (which also adopts principles of the OPC).  These applications are delivered via the Windows Store (which is now called the Microsoft Store).  But alas, the UWP packaging format didn’t place nicely with traditional Win32 applications and developers were reluctant to rewrite their software just to accommodate UWP.

Microsoft MSIX

In 2018 Microsoft announced a new package format called MSIX – an attempt to create a unified packaging format by combining the best features of MSI, Appx (UWP apps), App-V, and ClickOnce.

Unlike UWP, MSIX supports the packaging of Win32 applications as well as WPF and Windows Forms applications.  And similarly to Appx and App-V, the .MSIX file format is also based on the OPC specification (as such the file extension can be renamed to .zip and extracted using your zip compression tool of choice).

As an added layer of security, it is now a requirement to sign every MSIX package with a valid code-signing certificate.  This means that since the applications are trusted they can be installed from the web, from the Microsoft Store, via SCCM or otherwise.

Microsoft also claims that MSIX provides very reliable installations, clean uninstalls, seamless updates, with optimised network bandwidth and disk space usage.  They also promote a simple transition from your existing application packaging format to the new MSIX format.

There are also some nice new features such as exposure to a wealth of APIs to accomplish things like sending push notifications, and the emergence of MSIX app attach which utilises the VHD file format to perform application layering!

However in its current state, and even though App-V 5 is no longer being actively developed,  MSIX is still not ready for enterprise production environments.

There is still no support for device driver installations.  There is no way for multiple separate MSIX packages to interact with each other like we saw previously with dynamic suite composition (DSC) and connection groups (CG).  There is no native support for MSIX shortcuts that pass command line arguments to the executable.  And no doubt there will be a plethora of other bugs on the horizon that require remediation.

We will keep a close eye on the evolution of MSIX technology.  But in the mean time please get in touch should you have any application packaging enquiries.

App-V 5 Connection Group Manager

 

I’ve been testing a new tool developed by Howard over at HRP Consultancy called App-V 5 Connection Group Manager.  It’s very handy indeed.  There are dozens of scripts out there which do a similar job (keyword – ‘similar’), but most importantly they rely on adding each package to a machine and using Powershell CMDLets to query the package cache and generate the connection group XML file.  Considering an example where we may want to connect half a dozen apps (of varying file sizes!), this in itself may take me 20-30 minutes or more to prepare my virtual machine!

Contrast the aforementioned cumbersome approach with this tool, whereby all we have to do is simply point to the .appv file itself (stored on the file system in an unpublished state) and bang….it extracts the relevant DisplayName, PackageId and VersionId’s to add to the connection group.  What’s more is that this tool provides a GUI which enables us to order and prioritise our connected packages, specify connection group names and GUIDs whilst providing support for the more recent schema’s VersionId and isOptional attributes.

Virtualisr – FREE App-V 4.6, App-V 5 and App-V 5.1 Automated Sequencing

Virtualisr is a tool used for App-V 4.6, App-V 5 and App-V 5.1 automated sequencing/virtualisation.  It can convert scripted installs (VBS, BAT, CMD) to App-V, convert MSI to App-V and convert executables/legacy installers to App-V.  It can rapidly accelerate application migrations and save your company hundreds of man-hours and thousands of pounds.  Other virtualisation technologies can be supported upon request.

PLEASE NOTE: There seems to be a bug with the New-AppvSequencerPackage cmdlet in the Windows ADK version of the sequencer.  PSR mode may not work with this version.

PLEASE ALSO NOTE: I generally only use Virtualisr when I have a batch of pre-configured (or default) apps that I need to quickly convert, or if I’m performing a  migration for a client.  In reality this is probably every few months since most of the time I will package ad-hoc requests.  During this period Oracle tend to update VirtualBox and as part of these updates they usually alter the command line syntax!  This may stop Virtualisr from functioning correctly.  If this is the case please contact me and I will attempt to resolve any issues as soon as possible.

Download:

Pricing:

  • FREE!  (for a limited period – licenses will be provided in batches of 10).  License key must be obtained from us first.  Contact us here

Overview:

  • App-V 4.6, App-V 5 and App-V 5.1 Automated Sequencing/VirtualisationOracle VirtualBox
  • Sequence on your own custom virtual machines
  • Keep your installation files local and secure
  • Utilise industry leading Oracle virtualisation software
  • Bulk import multiple applications to convert, and perform batch conversion whilst you make a brew!
  • Perform batch automated* conversions of .EXE (legacy installers), .MSI, .VBS, .BAT and .CMD to App-V 4.6 and App-V 5 formats
  • Supply command-line arguments for your installation target
  • Apply one or many transforms (MSTs) to MSI installations
  • Manually specify package names (auto-generated from imported MSI/MST name as default)
  • Perform MNT/VFS installations (App-V 4.6)
  • Manually specify PVAD, or automatically set PVAD to actual installation directory (App-V 5/MSI only)
  • Specify App-V templates, Full Load, Mount Drives etc
  • Record Problem Step Recorder screenshots to use as part of your discovery inventory
  • Output conversions to a logical folder structure
  • Compatible with Oracle VirtualBox and VMWare Workstation** virtual machines
  • License not used for failed conversions***
  • Unlimited remote support

* scripted installations can only be automatically converted if the scripts themselves are automated with no human interaction required.

** VMWare Virtual Machine needs to be hosted inside Oracle VirtualBox

*** sufficient error handling required in scripts

Missing any functionality?  Contact us here to request it.

virtualisr

Why Virtualisr:

There are products such as Autonoware ConversionBox and Flexera AdminStudio Virtualization Pack which already provide automated App-V virtualisation and do a decent job of it.  However these are costly (certainly when we’re talking about multiple application portfolios containing several hundred applications), cumbersome installations with often complex licensing agreements.  Virtualisr is a lightweight (under 500kb) executable used alongside Oracle VirtualBox and is completely free.

System Requirements:

  • 6GB Memory (minimum)
  • 2.5 GHz dual-core Intel or AMD processor
  • Powershell 2
  • Microsoft .Net 3.5
  • Oracle VirtualBox (https://www.virtualbox.org/wiki/Downloads)

Setup:

The Virtualisr executable does not install anything.  The program is run directly.  It works by launching virtual machines that are hosted in Oracle VirtualBox (since VirtualBox is free).  You can also mount your VMWare virtual machines in VirtualBox in order to use Virtualisr.

Step 1 – Install Oracle VirtualBox

You can download it from this location: https://www.virtualbox.org/wiki/Downloads

Step 2 – Create your Sequencing Virtual Machine base image

NOTE: If you already have VMWare virtual machines set up you can use these by importing them into Oracle VirtualBox (Just select ‘Use an existing virtual hard drive file’ and then specify the VMDK/VDI when asked to configure the hard drive).  If you already have VirtualBox virtual machines configured you can use those too.  In both cases, just ensure you have the latest Guest Additions (see below) and you can then ignore this step and step 3.

Get hold of an ISO of the Windows operating system that you want to sequence applications on.  Open Oracle VirtualBox.  Create a new Virtual Machine, specifying memory amount and hard disk configurations. Be wary that there are some large applications out there. I’ve created mine as the default 25GB.

Once you’ve created it, start the VM and point to your Windows ISO when it prompts to select a start-up disk. Follow the usual instructions to install your copy of Windows. Ensure that you give the admin account a password and don’t just leave it blank.

Once this is complete and you’re logged in to the operating system, install the latest Guest Additions (I installed 4.3.12 at the time of writing this).  You can obtain it from this location:

VirtualBox Guest Additions (choose version and downoad the .iso, for example VBoxGuestAdditions_4.3.8.iso)

and instructions are here:

https://www.virtualbox.org/manual/ch04.html#additions-windows

Reboot your VM. Configure the base image as required (service packs, windows updates etc).  Once done, disable Windows Updates, stop the Windows Defender and Windows Search services.  Disable Action Center messages. And disable User Account Control.

Create a snapshot.  I called mine ‘base’

Step 3 – Create your App-V 4.6 and App-V 5.0 snapshots

With our base snapshot running, install the App-V 4.6 sequencer with all the relevant service pack and hotfixes. Once done, create a snapshot called ‘App-V 4.6 <Service Pack/Hotfix etc>’.  For example, mine is called ‘App-V 4.6 SP3’.

Now revert back to the base snapshot. Install the App-V 5 sequencer, again with any service packs and hotfixes. You may also need to install the relevant prerequisites for the App-V 5 Sequencer. Once done create another snapshot.  I called mine ‘App-V 5.0 SP2 HF5’.

You should now be ready to sequence!  It’s pretty self explanatory (i think!), but just in case it’s not, here are a few instructions:

How it Works:

Before you start, create a folder and dump all of your installation source in there.  I created a folder on the root of C: called ‘ToConvert’.  I also organised each application into their own folder for clarity.

Step 1 – Launch Virtualisr.exe

Step 2 – Import Packages to Virtualise

Specify your license key.  You can check how many licenses you have available by clicking ‘Get Status’.

Click ‘Import Source Files’ and point to your source dump location.  In my case, as mentioned above, my source dump is C:\ToConvert.  Click Ok and all of the .MSI, .MST, .CMD, .BAT and .VBS files will populate in the DataGrid.  You can filter which file type you want to view.  From the screenshot above, you can see there are 11 columns.  They should all be self explanatory but you can hover over the header for more information.

  • Dark grey denotes a disabled cell
  • Select zero to many transforms.  If you need them to apply in a specific order I suggest you name them alphabetically (or prefix with a number) to order them as they will install in the order shown in the GUI
  • The Package Name column is editable.  By default it will give this the name of the MSI/First MST/Script provided as install source.
  • The PVAD columns are also editable
  • The MNT checkbox will (in the case of and MSI) attempt to install the MSI to the PVAD location.  Otherwise it will perform a VFS installation.
  • The O/R (Override) checkbox will attempt to get the INSTALLDIR of an MSI and set the App-V 5 PVAD to this location
  • Select the ‘AV4.6’ or ‘AV5’ check box to convert the installer to the relevant format
  • Configure Feature Block 1 with FullLoad
  • Specify the Virtual Machine to use, and select the appropriate snapshots for sequencing App-V 4.6 and App-V 5 packages.  You must also specify the login credentials for the admin user.

Step 3 – Click Virtualise!

Output:

Two folders will be created in your source dump folder called ‘Virtualisr-AV5’ and ‘Virtualisr-AV46’.  Your packages will be output to these locations, and named according to the package name you specified in the DataGrid.

A log file will also be created in the source dump folder called virtualisr.log.  This is appended to each time you run Virtualisr, and is opened at the end of each session.  It should contain installer exit codes (in case the installer fails), sequencer exit codes (in case the sequencer fails) and VBoxManage.exe exit codes (in case the VirtualBox element fails).

Virtualisr log

A HTML report will be generated called Virtualisr_Report.html.  This will contain information from the sequencing session such as success rate, time elapsed, configurations, and package success etc.  Remember that ‘green’ means it has been successfully virtualised, but this does not imply that it will work in the App-V technology.  You will need to use a compatibility toolset to find this out before passing the application through Virtualisr.

Virtualisr report

Under the hood:

When the conversion process starts, a shared folder is created on the guest virtual machine and is mapped to the source dump folder on the host (in our example this is C:\ToConvert).  This is the only location on the host that the VirtualBox session has access to.  Hence any installation source, App-V templates, log files, reports and App-V output is located here.  See the screenshot below for an example of the files required for input, and the files which are output:

Virtualisr Input Output

Finally…:

I’m aware that once you kick off the conversion process that the GUI becomes unresponsive.  However the status in the bottom left will update, the log file will update and also the datagrid cells will be highlighted red (failure)/green (success) after each application has been processed.

Multi-threading in Powershell is not trivial, and one day I may look into using the Start-Job cmdlet and timers, as referenced to here: http://www.sapien.com/blog/2012/05/16/powershell-studio-creating-responsive-forms/.  But until that day, click ‘Virtualise’ and go and make yourself a brew (or a few brews, depending upon how many apps you choose to virtualise).

Publishing App-V 5 packages with SCCM 2007

Introduction

At the time of writing this article App-V 5 is not natively supported by SCCM 2007.  Publishing App-V 5 packages with SCCM 2007 requires either deploying the MSI which gets created by the sequencer, or create a custom Powershell script to perform the publishing tasks manually.

The limitation with deploying the MSI is that you cannot handle connection groups, and so you’ll end up with a hybrid approach of deploying MSIs for packages and powershell scripts for connection groups.  On top of this, I don’t believe there is a way (using the MSI) to apply DeploymentConfig and UserConfig files at add/publish time respectively.

To circumvent these issues I’ve created an App-V 5 administration script which can be used with SCCM 2007 programs.

Command Line Parameters are:

Parameter: -av5Verb                        Possible Value(s): “add” “remove”

Parameter: -av5Object                     Possible Value(s): “package” “connectionGroup”

Parameter: -av5PackageName        Possible Value(s): {Package Name – for example “Adobe-Reader-X”}

Parameter: -deploymentConfig         Possible Value(s): “true” “false”

Parameter: -userConfig                     Possible Value(s): “true” “false”

Usage

1.  Put the Powershell script inside the installation source folder, with any associated .appv/.xml files.  In our example we’ll call the script AV5Admin.ps1:

App-v 5 and SCCM 2007

2.  Command lines for the SCCM program are as follows (be careful of the quotes in this blog post!!  See ‘Things to Note’ below for more info!):

Add package (Publishes globally by default)

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass .\AV5Admin.ps1 -av5Verb "add" -av5Object "package" -av5PackageName "Adobe-Reader-X"

Add package and apply deployment config xml

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass .\AV5Admin.ps1 -av5Verb "add" -av5Object "package" -av5PackageName "Adobe-Reader-X" -deploymentConfig "true"

Add package and apply user config xml

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass .\AV5Admin.ps1 -av5Verb "add" -av5Object "package" -av5PackageName "Adobe-Reader-X" -userConfig "true"

Remove package

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass .\AV5Admin.ps1 -av5Verb "remove" -av5Object "package" -av5PackageName "Adobe-Reader-X"

Add connection group (connecting globally published packages)

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass .\AV5Admin.ps1 -av5Verb "add" -av5Object "connectionGroup" -av5PackageName "Adobe-Reader-X"

Add connection group (connecting user published packages)

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass .\AV5Admin.ps1 -av5Verb "add" -av5Object "connectionGroup" -av5PackageName "Adobe-Reader-X" -userConfig "true"

Remove connection group

powershell.exe -WindowStyle Hidden -ExecutionPolicy Bypass .\AV5Admin.ps1 -av5Verb "remove" -av5Object "connectionGroup" -av5PackageName "Adobe-Reader-X"

Things to Note

  • When creating your connection group XML file, the DisplayName attribute of the AppConnectionGroup tag should be set to the av5PackageName value. In the case of the example above, it should be set to “Adobe-Reader-X”.
  • Be careful of dodgy quotes when copying and pasting. For example:
    " - correct 
    “ - incorrect 
    ” - incorrect
  • By default, no XML (deployment or user) files are applied. If you specify a “true” value for -deploymentConfig the xx_deploymentConfig.xml file will be applied and the package will be published globally. If you specify a “true” value for -userConfig the xx_UserConfig.xml file will be applied and the package will NOT be published globally.
  • If any XML files are applied, scripting is enabled on the App-V client.
  • *UPDATE – Apparently Configuration Manager 2012 uses -ExecutionPolicy Bypass, as shown here: http://blogs.technet.com/b/virtualvibes/archive/2013/10/02/configuration-manager-2012-sp1-and-app-v-5-0-integration-more-than-meets-the-eye.aspx We have now used this in our command line, whilst also hiding the Powerhell console using -WindowStyle Hidden.
  • Log files get written to C:\Windows\Security\Logs. For example, if you try to publish a connection group and some of the connected packages are not published on the machine, the appropriate error message will be written to a log file called {packagename}_add.log.
  • Checks to see if the AppvClient module exists and is imported.
  • Returns a code of 0 for success, and 1 for an error.
  • **Important**  If running a 32-bit SCCM agent on a 64-bit platform see this post, which explains why we MUST use the Sysnative alias when pointing to the powershell.exe.

The Script

Param(
  [string]$av5Verb,
  [string]$av5Object,
  [string]$av5PackageName,
  [string]$deploymentConfig,
  [string]$userConfig
)
#possible values are:
#
#av5Verb - "add", "remove"
#av5Object - "package", "connectionGroup"
#av5PackageName - "{package brick name}"
#deploymentConfig - "true", "false"
#userConfig - "true", "false"

#Kae Travis - Alkane Solutions Ltd
#15/11/2013
#Version 1.0 - Initial Release
#29/11/2013
#Version 2.0 - Added more error handling
#02/12/2013
#Version 3.0 - Bug fix when using deploymentConfig param
#29/01/2013
#Version 4.0 - Now mounts the packages, changed import-module message since it only imports for current session, cannot specify deploymentConfig and userConfig both as true

#declare variables for use throughout script

$scriptPath = Split-Path -parent $MyInvocation.MyCommand.Definition;
$avFile = $scriptPath + "\" + $av5PackageName + ".appv";
$cgfile = $scriptPath + "\" + $av5PackageName + ".xml";
$logfile = $env:windir + "\security\logs\" + $av5PackageName + "_" + $av5verb + ".log";

$appvmodule = "AppvClient";

#Import-Module imports a module only into the current session. 
#To import the module into all sessions, add an Import-Module command to your Windows PowerShell profile. 
#For more information about profiles, see about_Profiles (http://go.microsoft.com/fwlink/?LinkID=113729).

function detectAppvClientModule
{

	#detect if appvclient module is imported

	if(-not(Get-Module -name $appvmodule)) 
	{ 
		if(Get-Module -ListAvailable | Where-Object { $_.name -eq $appvmodule }) 
		{ 
			try
			{
				#module available, so import 
				Import-Module -Name $appvmodule -ErrorAction Stop | Out-Null;				
				Add-Content $logfile -value "'$appvmodule' module has been imported for this session.`r`n`r`n";
				return $true;
			}
			catch
			{
				Add-Content $logfile -value "Error: Unable to import '$appvmodule'.`r`n`r`n$($_.Exception.Message)";
				return $false;
			}
		} 
		else 
		{ 
			#module not available, so we write to log and exit script
			Add-Content $logfile -value "Error: Powershell module '$appvmodule' does not exist.  Is the App-V 5 Client installed?";
			return $false;
		} 
	}
	else
	{
		return $true;
	}
}

function targetFileExists
{
	#detect if target file (.app package, .xml connection group exists)
	Param(
	  [string]$targetFile
	)

	if (Test-Path $targetfile)
	{
		return $true;
	}
	else
	{
		Add-Content $logfile -value "Error: '$targetfile' does not exist.";
		return $false;
	}	

}

function addPackage
{

	#attempt to add/publish application

	try {
		#if success we write results to log
		if (targetFileExists($avfile) -eq $true)
		{

			if ($deploymentConfig -eq "true")
			{
				$deploymentFile = $scriptPath + "\" + $av5PackageName + "_DeploymentConfig.xml";

				if (targetFileExists($deploymentFile))
				{
					Set-AppvClientConfiguration -EnablePackageScripts $true -ErrorAction Stop | Out-Null;
				} else {
					#otherwise we write exception to log
					Add-Content $logfile -value "Error: Cannot add/publish package.`r`n`r`nDeployment config file does not exist";
					return $false;
				}
			}
			else
			{
				$deploymentFile = "";
			}

			if ($userConfig -eq "true")
			{
				$userFile = $scriptPath + "\" + $av5PackageName + "_UserConfig.xml";

				if (targetFileExists($userFile))
				{
					Set-AppvClientConfiguration -EnablePackageScripts $true -ErrorAction Stop | Out-Null;
				} else {
					#otherwise we write exception to log
					Add-Content $logfile -value "Error: Cannot add/publish package.`r`n`r`nUser config file does not exist";
					return $false;
				}
			}
			else
			{
				$userFile = "";
			}

			if (($userConfig -eq "true") -And ($deploymentConfig -eq "true"))
			{
				Add-Content $logfile -value "Error: Cannot add/publish a package in both user and machine contexts.  userConfig and deploymentConfig cannot both be set to true.";
				return $false;
			}
			elseif (($userConfig -eq "true") -And ($deploymentConfig -ne "true"))
			{
				Add-AppvClientPackage -Path $avfile -ErrorAction Stop | Publish-AppvClientPackage -DynamicUserConfigurationPath $userFile -ErrorAction Stop | Mount-AppvClientPackage | Out-File -FilePath $Logfile | Out-Null;
			}
			elseif (($userConfig -ne "true") -And ($deploymentConfig -eq "true"))
			{
				Add-AppvClientPackage -Path $avfile -DynamicDeploymentConfiguration $deploymentFile -ErrorAction Stop | Publish-AppvClientPackage -Global -ErrorAction Stop | Mount-AppvClientPackage | Out-File -FilePath $Logfile | Out-Null;
			}
			elseif (($userConfig -ne "true") -And ($deploymentConfig -ne "true"))
			{
				Add-AppvClientPackage -Path $avfile -ErrorAction Stop | Publish-AppvClientPackage -Global -ErrorAction Stop | Mount-AppvClientPackage | Out-File -FilePath $Logfile | Out-Null;
			}

			return $true;
		}
	} 
	catch 
	{
		#otherwise we write exception to log
		Add-Content $logfile -value "Error: Cannot add/publish package.`r`n`r`n$($_.Exception.Message)";
		return $false;
	}

}

function removePackage
{
	#attempt to add/publish application

	try {

		if(Get-AppvClientPackage -Name $av5PackageName) 
		{ 
			#if success we write results to log
			if ($userConfig -eq "true")
			{
				Unpublish-AppvClientPackage -name $av5PackageName -ErrorAction Stop | Remove-AppvClientPackage -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;
			}
			else
			{		
				if((Get-AppvClientPackage -Name $av5PackageName).IsPublishedGlobally) 
				{ 
					Unpublish-AppvClientPackage -name $av5PackageName -Global -ErrorAction Stop | Remove-AppvClientPackage -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;
				}
				else
				{
					Unpublish-AppvClientPackage -name $av5PackageName -ErrorAction Stop | Remove-AppvClientPackage -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;
				}	
			}
			return $true;
		}
		else
		{
			#otherwise we write exception to log
			Add-Content $logfile -value "Error: Cannot unpublish/remove package.  Package does not exist.";
			return $false;
		}
	} 
	catch 
	{
		#otherwise we write exception to log
		Add-Content $logfile -value "Error: Cannot unpublish/remove package.`r`n`r`n$($_.Exception.Message)";
		return $false;
	}

}

function addConnectionGroup
{
	#attempt to add/enable connection group

	try {
		#if success we write results to log
		if (targetFileExists($cgfile) -eq $true)
		{		

			#we check that all the apps in the cg are published, and that they are all published in the same context (user/global)

			[xml]$av5Xml = Get-Content $cgfile;

			$av5Namespace = new-object Xml.XmlNamespaceManager $av5Xml.NameTable
			$av5Namespace.AddNamespace("appv", "http://schemas.microsoft.com/appv/2010/virtualapplicationconnectiongroup")

			$cgUnpublished = "";
			$cgGlobalCount = 0;
			$cgUserCount = 0;
			$cgUnpublishedApp = $false;

			$av5packages = $av5Xml.SelectNodes("/appv:AppConnectionGroup/appv:Packages/appv:Package",$av5Namespace)
			foreach ($av5package in $av5packages) {

			 	if(Get-AppvClientPackage -PackageId $av5package.PackageId -VersionId $av5package.VersionId)
				{
					if((Get-AppvClientPackage -PackageId $av5package.PackageId -VersionId $av5package.VersionId).IsPublishedGlobally)
					{
						$cgGlobalCount = $cgGlobalCount + 1;
					}
					else
					{
						$cgUserCount = $cgUserCount + 1;
					}
				}
				else
				{
					$cgUnpublished = "PackageId: " + $av5package.PackageId + " VersionId: " + $av5package.VersionId + "`r`n"
					$cgUnpublishedApp = $true;
				}
			}

			#if we get to this point, all packages in cg are published.  we should check they are published in same context now.

			if ($cgGlobalCount -gt 0 -And $cgUserCount -eq 0 -And $cgUnpublishedApp -eq $false)
			{
				#all pubbed globally

				if ($userConfig -eq "true")
				{
					Add-Content $logfile -value "Error: Connection Group should be published in a global context!";
					return $false;
				}
			}

			if ($cgGlobalCount -eq 0 -And $cgUserCount -gt 0 -And $cgUnpublishedApp -eq $false)
			{
				#all pubbed to user
				if ($userConfig -ne "true")
				{
					Add-Content $logfile -value "Error: Connection Group should be published in a user context!";
					return $false;
				}
			}

			if ($cgGlobalCount -gt 0 -And $cgUserCount -gt 0 -And $cgUnpublishedApp -eq $false)
			{
				#mixture of global and user packages
				Add-Content $logfile -value "Error: Packages in this Connection Group are published in both global and user contexts.  They should all be published in the same context!";
				return $false;
			}

			if ($cgUnpublishedApp)
			{
				#unpublished apps				
				Add-Content $logfile -value "Error: The following packages in this Connection Group have not been published:`r`n`r`n$cgUnpublished";
				return $false;
			}

			#add and publish the connection group

			if ($userConfig -eq "true")
			{
				Add-AppvClientConnectionGroup -path $cgfile -ErrorAction Stop | Enable-AppvClientConnectionGroup -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;
			}
			else
			{
				Add-AppvClientConnectionGroup -path $cgfile -ErrorAction Stop | Enable-AppvClientConnectionGroup -Global -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;
			}
			return $true;
		}
	} 
	catch 
	{
		#otherwise we write exception to log
		Add-Content $logfile -value "Error: Cannot add/enable connection group.  Are all packages in the connection group published to the same context?  And are you enabling this connection group in the same context as the packages? (Global or User)`r`n`r`n$($_.Exception.Message)";
		return $false;
	}
}

function removeConnectionGroup
{

	#attempt to disable/remove connection group

	try {

		if(Get-AppvClientConnectionGroup -Name $av5PackageName) 
		{
			#if success we write results to log
			if((Get-AppvClientConnectionGroup -Name $av5PackageName).IsEnabledGlobally) 
			{
				Disable-AppvClientConnectionGroup -name $av5PackageName -Global -ErrorAction Stop | Remove-AppvClientConnectionGroup -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;
			}
			elseif((Get-AppvClientConnectionGroup -Name $av5PackageName).IsEnabledToUser) 
			{
				Disable-AppvClientConnectionGroup -name $av5PackageName -ErrorAction Stop | Remove-AppvClientConnectionGroup -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;
			}
			else
			{
				#if not enabled at all!
				Remove-AppvClientConnectionGroup -name $av5PackageName -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;			
			}

			return $true;
		}
		else
		{
			#seems to be a bug whereby if a cg is added but not enabled, Get-AppvClientConnectionGroup -Name $av5PackageName doesnt find it!
			#if this is the case, we will try to just remove (and not disable) the connection group here
			try
			{
				Remove-AppvClientConnectionGroup -name $av5PackageName -ErrorAction Stop | Out-File -FilePath $Logfile | Out-Null;
				return $true;				
			} catch {
				#otherwise we write exception to log
				Add-Content $logfile -value "Error: Cannot unpublish/remove connection group.  Connection group does not exist.";
				return $false;
			}			
		}
	} 
	catch 
	{
		#otherwise we write exception to log
		Add-Content $logfile -value "Error: Cannot disable/remove connection group.`r`n`r`n$($_.Exception.Message)";
		return $false;
	}
}

#main program

#assign a default value
$av5Result = $false;

#create blank log file
Set-Content $logfile -value "";

#validate arguments

if ($av5Verb -ne "add" -and $av5Verb -ne "remove")
{
	Add-Content $logfile -value "Error: av5Verb parameter should be ""Add"" or ""Remove""";
	Exit 1
}

if ($av5Object -ne "package" -and $av5Object -ne "connectiongroup")
{
	Add-Content $logfile -value "Error: av5Object parameter should be ""package"" or ""connectiongroup""";
	Exit 1
}

if ($av5PackageName -eq "")
{
	Add-Content $logfile -value "Error: av5PackageName parameter must be provided";
	Exit 1
}

if ($deploymentConfig -ne "")
{
	if ($deploymentConfig -ne "true" -and $deploymentConfig -ne "false")
	{
		Add-Content $logfile -value "Error: deploymentConfig parameter should be ""true"" or ""false""";
		Exit 1
	}
}

if ($userConfig -ne "")
{
	if ($userConfig -ne "true" -and $userConfig -ne "false")
	{
		Add-Content $logfile -value "Error: userConfig parameter should be ""true"" or ""false""";
		Exit 1
	}
}

if (detectAppvClientModule -eq $true)
{
	if ($av5Verb -eq "add")
	{
		if ($av5Object -eq "package")
		{
			$av5Result = addPackage;
		}
		elseif ($av5Object -eq "connectiongroup")
		{
			$av5Result = addConnectionGroup;
		}
	}
	elseif ($av5Verb -eq "remove")
	{	
		if ($av5Object -eq "package")
		{
			$av5Result = removePackage;
		}
		elseif ($av5Object -eq "connectiongroup")
		{
			$av5Result = removeConnectionGroup;
		}
	}
}
else
{
	#exit with error
	Exit 1
}

if ($av5Result -eq $true)
{
	#exit with no error
	Exit 0
}
else
{
	#exit with error
	Exit 1
}