Scripting Examples for App-V 5

Below are a few scripting examples for App-V 5 configuration files (DeploymentConfig, UserConfig and AppxManifest files – the XML syntax varies slightly between these files but the same principle applies).  They’re purely for illustrative purposes and aim to show how we can string together the executable and their arguments to achieve different things.

**UPDATED WORKAROUND FOR MSIEXEC – Scroll down…**

Installing a shim

<MachineScripts>              
    <AddPackage>        
        <Path>sdbinst.exe</Path>        
        <Arguments>/q "[{AppVPackageRoot}]\..\Scripts\ExampleFile.sdb"</Arguments>      
        <Wait RollbackOnError="true" Timeout="30"/>      
    </AddPackage>   
</MachineScripts>

Running a Powershell script

<MachineScripts>
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -WindowStyle Hidden -File "[{AppVPackageRoot}]\..\Scripts\ExampleFile.ps1"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Running a Powershell command

<MachineScripts>
<AddPackage>
<Path>powershell.exe</Path>
<Arguments>-ExecutionPolicy ByPass -WindowStyle Hidden -Command  "&amp; { Get-EventLog -LogName security }"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Running an EXE (For example, Powershell) without waiting for execution to complete

<MachineScripts>
<AddPackage>
<Path>cmd.exe</Path>
<Arguments>/c START "" "powershell.exe" -ExecutionPolicy ByPass -WindowStyle Hidden -File "[{AppVPackageRoot}]\..\Scripts\runProcess.ps1"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Running a VBScript (via the CScript engine)

<MachineScripts>
<AddPackage>
<Path>cscript.exe</Path>
<Arguments>"[{AppVPackageRoot}]\..\Scripts\ExampleFile.vbs"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Running a VBScript (via the WScript engine)

<MachineScripts>
<AddPackage>
<Path>wscript.exe</Path>
<Arguments>"[{AppVPackageRoot}]\..\Scripts\ExampleFile.vbs"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Installing an MSI (if this doesn’t work see workaround below!)

<MachineScripts>
<AddPackage>
<Path>msiexec.exe</Path>
<Arguments>/i "[{AppVPackageRoot}]\..\Scripts\ExampleFile.msi" /qb</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Installing a Driver

<MachineScripts>
<AddPackage>
<Path>pnputil.exe</Path>
<Arguments>/i /a "[{AppVPackageRoot}]\..\Scripts\ExampleFile.inf"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Adding a Registry String Value

<MachineScripts>
<AddPackage>
<Path>reg.exe</Path>
<Arguments>ADD HKLM\SOFTWARE\AlkaneTestKey /f /v AlkaneTestValue /t REG_SZ /d AlkaneTestData</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Giving Modify Permission to a Directory

<MachineScripts>
<AddPackage>
<Path>icacls.exe</Path>
<Arguments>"C:\AlkaneTest" /grant Users:(OI)(CI)(M) /C /Q</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

Creating a Symbolic Link (Running a windows command shell internal command – see here)

<MachineScripts>
<AddPackage>
<Path>cmd.exe</Path>
<Arguments>/C MKlink /D C:\AlkaneSolutions "C:\Program Files\AlkaneSolutions"</Arguments>
<Wait RollbackOnError="true" Timeout="30"/>
</AddPackage>
</MachineScripts>

 

**UPDATED WORKAROUND FOR MSIEXEC**

I referred a colleague of mine to this blog today.  He was trying to install an MSI from within the Scripts folder of his App-V 5 package.  But the example above didn’t work for him!!?  Event Viewer stated that MSIEXEC could not locate the MSI!?  I ran Process Monitor to confirm this.  And then I ran a quick local test.

I created two folders in the root of the C drive – C:\Scripts and C:\Root.  As you can probably tell, I was recreating the folder structure of an App-V 5 package to try and recreate the issue.

Essentially when we construct a path in an App-V 5 scripting node such as:

“[{AppVPackageRoot}]\..\Scripts\ExampleFile.msi”

When the package is added to a machine it will resolve to something like:

“C:\ProgramData\App-V\{PackageGUID}\{VersionGUID}\Root\..\Scripts\ExampleFile.msi”

Note how the path starts in the “Root” folder (“[{AppVPackageRoot}]”), goes up a level (“\..\”) and then goes into the Scripts folder.

To replicate this behaviour I placed a sample MSI in C:\Scripts called ExampleFile.msi and then opened up a command prompt and ran:

notepad.exe "c:\Root\..\Scripts\ExampleFile.msi"

The MSI opened in Notepad with no issues (full of gobbledygook as expected). I then ran:

msiexec.exe "c:\Root\..\Scripts\ExampleFile.msi"

Ooops!  The exact same path could not be resolved by the MSIEXEC engine!  So I assume MSIEXEC cannot handle the double-dot notation to go up one level in the directory structure?  As a workaround we had to run the MSI with a slightly different approach:

<MachineScripts>
    <AddPackage>
        <Path>cmd.exe</Path>
        <Arguments>/c cd "[{AppVPackageRoot}]\..\Scripts" &amp; msiexec.exe /i "ExampleFile.msi" /qn</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
    </AddPackage>
    <RemovePackage>
        <Path>cmd.exe</Path>
        <Arguments>/c cd "[{AppVPackageRoot}]\..\Scripts" &amp; msiexec.exe /x "ExampleFile.msi" /qn</Arguments>
        <Wait RollbackOnError="true" Timeout="30"/>
    </RemovePackage>
</MachineScripts>

As ever, make a note of the timeout period (in seconds) if your MSI is likely to take longer to (un)install!  Also it might be worth setting RollbackOnError to “false” if your MSI is likely to request a (soft) reboot and return a 3010 (non-zero) error code!

AppxManifest.xml in place of DeploymentConfig.xml and UserConfig.xml

There’s a new feature in the App-V 5.1 Sequencer which enables us to export and import a file called AppxManifest.xml:

App-V 5.1 Sequencer Import/Export ManifestI’ve been using this approach recently to add custom scripting logic to my packages (I used ACE to make my life slightly easier).  What it means is that I can import my App-V package straight into the App-V Management Server and it works straight away with my custom changes – that is, I do not have to manually specify any Deployment/User config files using the approach below:

Appv-5 Overwrite ConfigIt also means that when I’m testing my application in standalone mode I do not have to specify DynamicUserConfigurationPath or DynamicDeploymentConfigurationPath parameters to apply the configurations to my package on the Powershell command line.

 

Common App-V 5 Powershell Commands

The following posts contains common App-V 5 Powershell commands.  Assuming a package consisting of the following:

Package Name: AlkaneSolutions
AlkaneSolutions.appv
AlkaneSolutions_DeploymentConfig.xml
AlkaneSolutions_UserConfig.xml

and a connection group consisting of:

Package Name: AlkaneSolutions_ConnectionGroup
AlkaneSolutions_ConnectionGroup.xml

General Commands

Import the App-V 5 module (so we can use the cmdlets below)

Import-Module AppvClient

Set execution policy to unrestricted (used during testing – should really be set by policy in a live environment)

Set-ExecutionPolicy unrestricted

Enable Package Scripting (can also be done via policy):

Set-AppvClientConfiguration -EnablePackageScripts 1

Package Commands

Add Package

Add-AppvClientPackage "<Path to package>\AlkaneSolutions.appv"

Add Package with a Deployment Config

Add-AppvClientPackage "<Path to package>\AlkaneSolutions.appv" -DynamicDeploymentConfiguration "<Path to package>\AlkaneSolutions_DeploymentConfig.xml"

Publish Package (-Global will publish globally.  Omitting this will publish to the user)

Publish-AppvClientPackage -Name "AlkaneSolutions" -Global

Mount the package at 100%

Mount-AppvClientPackage -Name "AlkaneSolutions"

Add, Publish and Mount Package on One Line

Add-AppvClientPackage "<Path to package>\AlkaneSolutions.appv" | Publish-AppvClientPackage -Global | Mount-AppvClientPackage

Add and publish App-V package along with a deployment xml

Add-AppvClientPackage "<Path to package>\AlkaneSolutions.appv" -DynamicDeploymentConfiguration "<Path to package>\AlkaneSolutions_DeploymentConfig.xml" | Publish-AppvClientPackage -Global

Add and publish App-V package along with a User Config xml (note how -Global is omitted from this command)

Add-AppvClientPackage "<Path to package>\AlkaneSolutions.appv" | Publish-AppvClientPackage -DynamicUserConfigurationPath "<Path to package>\AlkaneSolutions_UserConfig.xml"

Unpublish package

Unpublish-AppvClientPackage -name "AlkaneSolutions"

Delete App-V Package from cache

Remove-AppvClientPackage -name "AlkaneSolutions"

Connection Group Commands

Add Connection Group

Add-AppvClientConnectionGroup -path "<Path to package>\AlkaneSolutions_ConnectionGroup.xml"

Enable Connection Group (-Global will publish globally. Omitting this will publish to the user)

Enable-AppvClientConnectionGroup -name "AlkaneSolutions_ConnectionGroup" -Global

Add and Enable a Connection Group on One Line

Add-AppvClientConnectionGroup -path "<Path to package>\AlkaneSolutions_ConnectionGroup.xml" | Enable-AppvClientConnectionGroup -Global

Disable Connection Group

Disable-AppvClientConnectionGroup -name "AlkaneSolutions_ConnectionGroup"

Remove Connection Group

Remove-AppvClientConnectionGroup -name "AlkaneSolutions_ConnectionGroup"

Disable and Remove Connection Group on One Line

Disable-AppvClientConnectionGroup -name "AlkaneSolutions_ConnectionGroup" | Remove-AppvClientConnectionGroup

 Verification Commands

Get App-V Client Configuration Settings

Get-AppvClientConfiguration

View all added (not neccessarily published) packages (useful for retrieving names, GUIDs, publishing status etc)

Get-AppvClientPackage -all

View all added (not neccessarily published) connection groups  (useful for retrieving namesm GUIDs, publishing status etc)

Get-AppvClientConnectionGroup -all

Using Shims with App-V 5

I was working on a package recently called Mead Co ScriptX 7.0.2, and was trying to make it work on Windows 7 x64.  By default when you clicked the shortcut on the Start Menu it did nothing.  However if I ran it in XPSP3 Compatibility Mode it launched.  So I decided to create a compatibility shim.  This post describes using shims with App-V 5:

Step 1 – Install the Windows Assessment Deployment Kit (ADK):

In this instance we’ll install the ADK on our vitual machine (VM).

Go to the Windows Assessment Deployment Kit (ADK) web page – http://www.microsoft.com/en-us/download/details.aspx?id=30652

Click ‘Download’.  The download should commence – run adksetup.exe.  You only need to select the Application Compatibility Toolkit (ACT) feature.

Step 2 – Install the application with the executable you want to shim onto the VM.

In our case, we’ll install Mead Co ScriptX 7.0.2.

Step 3 – Launch the Compatibility Administrator:

From the Start Menu run the Compatibilty Administrator.  If the .exe you want to shim is 32-bit, run the 32-bit version of the Compatibility Administrator.  If the .exe you want to shim is 64-bit, run the 64-bit version of the Compatibility Administrator.

Step 4 – Create the Shim:

Right-click where it says ‘New Database(1) [Untitled 1]’, Click ‘Create New’ and ‘Application Fix’.

Shim Compatibility

Fill in the Name and Vendor for your shim appropriately.  Then navigate to the exe you want to shim.  Click Next.

Shim New Fix

Select the checkbox to run the executable in compatibility mode, and select ‘Windows XP (Service Pack 3)’ from the dropdown list.  Click Next.

XPSP3 Shim

You will notice that because you selected the XPSP3 compatibility option, that 19 of the 367 fixes are automatically applied.  Click Next.

XPSP3 Shim Fixes

Finally we need to select the matchin file attributes.  Thi should be automatically populated from the executable you selected earlier, so just click Finish.

XPSP3 Shim Match File

Now click ‘Save’.  Give the database a name, and then save it.  For example, I called mine MeadCo_ScriptX_703_XPSP3.sdb.

Step 5 – Add the Shim to the App-V 5 package

Open your package in the App-V 5 sequencer.  Go to the ‘Package Files’ tab.  Right-click the ‘Scripts’ folder (it’s on the same level as the ‘Root’ folder) and add your shim file.  Save the package.

Shim Sequencer

Step 6 – Update _DeploymentConfig.xml with a script to install and remove the shim

Amend the package _DeploymentConfig.xml file to install (on AddPackage) and remove (on RemovePackage) the shim using the following commands:

<MachineScripts>              
    <AddPackage>         
        <Path>sdbinst.exe</Path>         
        <Arguments>/q "[{AppVPackageRoot}]\..\Scripts\MeadCo_ScriptX_703_XPSP3.sdb"</Arguments>         
        <Wait RollbackOnError="true" Timeout="30"/>       
    </AddPackage>       
    <RemovePackage>         
        <Path>sdbinst.exe</Path>         
        <Arguments>/q /u "[{AppVPackageRoot}]\..\Scripts\MeadCo_ScriptX_703_XPSP3.sdb"</Arguments>
        <Wait RollbackOnError="false" Timeout="60"/>       
    </RemovePackage>
</MachineScripts>

Done!  Add/Publish your package, ensuring that you specify the DynamicDeploymentConfiguration parameter:

Add-AppVClientPackage –Path MeadCo_ScriptX_703.appv -DynamicDeploymentConfiguration MeadCo_ScriptX_703_DeploymentConfig.xml | Publish-AppVClientPackage

To check that your shim is installed you should be able to see it in Programs and Features (and your app should launch!)

A few points to remember:

Be careful if you re-save your sequenced package, since it will overwrite your customized DeploymentConfig.xml file!

Also when you test your package, remember to ensure that scripting is enabled using the following command:

Set-AppVClientConfiguration –EnablePackageScripts 1

 

Running the x64 Powershell from x86 SCCM ConfigMgr

This post describes running the x64 Powershell from x86 SCCM ConfigMgr.  I stumbled upon an issue when I’d created a powershell script to manage App-V 5 packages.  On an x64 platform, we tried to launch the powershell script with the x64 version of Powershell (i.e, the version in %windir%\system32) via an SCCM 2007 program like so:

%windir%\System32\WindowsPowerShell\v1.0\PowerShell.exe .\AV5Admin.ps1 {parameters}

Upon running this program we received the following Powershell error:

The current processor architecture is: x86. The module ‘C:\Program Files\Microsoft Application Virtualization\Client\AppvClient\AppvClient.psd1’ requires the following architecture: Amd64.

This immediately made me think that SCCM was launching the x86 Powershell console as opposed to the x64 one.  But strangely enough, the execmgr log file still said it was running the x64 Powershell at %windir%\System32\WindowsPowerShell\v1.0\PowerShell.exe.  This log file entry was incorrect, and was just spitting out the exact command line in the SCCM program as opposed to what was actually running.

It turns out that the program execution environment in the SCCM 2007 ConfigMgr is 32-bit, and hence the x86 version of Powershell was being launched.  You can find out more information on the redirection process here.  To circumvent this issue, the command line needed to be tweaked to use the SysNative alias like so:

%windir%\Sysnative\windowsPowershell\V1.0\PowerShell.exe .\AV5Admin.ps1 {parameters}

WOW64 recognizes Sysnative as a special alias used to indicate that the file system should not redirect the access, and should instead use the native system folder. This alias was introduced in Windows Vista and can not be used with x64 applications.  Note that you can ONLY use this alias from 32-bit applications, and it would not be recongnised if, for example, you used it from a 64-bit instance of cmd.exe.

As an example, if you launch cmd.exe on an x86 platform, and run the following command:

%windir%\sysnative\notepad.exe

You will get the error:

The system cannot find the path specified.

Because the Sysnative alias is not supported on x86 platforms.  Also, if you launch the same command on an x64 platform, using the native x64 cmd.exe, you will get the same error because the Sysnative alias is not supported on x64 applications.

However, if you launch an x86 version of cmd.exe on an x64 platform and run the same command, it will launch the native (x64) version of notepad.exe.

Out of interest if you just run the command:

notepad.exe

from the x86 version of cmd.exe on an x64 platform, it will launch the x86  version of notepad.exe.

 

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
}