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:
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
}