Why I love the PowerShell AppDeployment Toolkit

Today I want to share some thoughts and insights about a very useful toolkit that is available free for everybody – The PowerShell App Deployment Toolkit. I will describe what features it has, how the architecture looks like and how you can create and customize packages. At the end I will provide you with some packages you can start to play with.

First I want to make a short excurse about wrappers.

About Wrappers…

A wrapper is basically a script that runs certain functions around an installation package. This scripts could for example run copy jobs before or after the installation routine of an application, clean up files or provide extended logging functionality that some electronic software delivery products are missing today. In short: It provides functionality that is not available in an ESD product from scratch.

About wrapper languages…

Most of the wrappers in the field are still written in VBScript or as a Batch and although VBS is very fast and efficient I personally don´t like the fact that is not as easy to understand as a PowerShell script. Therefore the PSADK is IMHO the better wrapper.

About The PowerShell App Deployment Toolkit

The PowerShell App Deployment Toolkit was launched in August 2013 and is a joint collaboration between Seán Lillis and Dan Cunningham. Muhammad Mashwani and Aman Motazedian. You can use it for whatever deployment mechanism or ESD you are using in your infrastructure. It is written completely in PowerShell and has a lot of functions around deploying applications to endpoints and users. It is licensed under the Microsoft Public License (Ms-PL). See some of the features in the following list:

Main Features of the PowerShell App Deployment Toolkit

  • Easy To Use
  • Consistent
  • Powerful
  • User Interface
  • Localized
  • Integration with SCCM 2012 R2
  • Updatable
  • Extensible
  • Helpful

Functions/Logic (besides others)

  • Provides extensive logging of both the Toolkit functions and any MSI installation / uninstallation
  • Provides the ability to execute any type of setup (MSI or EXEs) and handle / translate the return codes
  • Mass remove MSI applications with a partial match (e.g. remove all versions of all MSI applications which match “Office”)
  • Check for in-progress MSI installations and wait for MSI Mutex to become available
  • Send a sequence of keys to an application window
  • Perform SCCM actions such as Machine and User Policy Refresh, Inventory Update and Software Update
  • Supports installation of applications on Citrix XenApp/Remote Desktop Session Host Servers
  • Check File versions
  • Pin or Unpin applications to the Start Menu or Task Bar
  • Create Start Menu Shortcuts
  • Register / Unregister DLL files
  • Refresh desktop icons
  • Test network connectivity
  • Test power connectivity
  • Check whether a PowerPoint slideshow is running

How does it look like?

The GUI is basically what the user sees during a deployment of a package if you want him to so something. Because the PSADK is very customizable you can change things to whatever you like. You can create full interactive packages or silent one´s.

This is what you see when you run “Deploy-Application.exe” without further parameters:

psadk

On the first screen that comes up I changed the banner to something that fits more likely from my perspective ;-). And that is the first example of how customizable it is.

You can configure defer settings or check the disk space before deploying a application – if you like. Let´s Continue… Installation in progress…

psadk

After the successfull installation the summary window comes up. You can see the standard message in the next window.

psadk

I will show you in a few moments how to change this. First let´s have a look at the package structure…

Deep Dive into the package structure

Every PSADK consists of 2 folders and three files in the root directory. The AppDeployToolkit folder contains the configuration files and function files. The Files folder is used for the source files. There you copy all the files that are needed in order to install the package. Alternatively you can create a variable for all your scripts that points to a central file share. That way you can skip putting the source files there. Personally I like the idea of having a “closed” package with the installation logic and the files in it.

Because you can insert version numbers into the script header it is easy to version packages.

psadk

If we switch to the AppDeploymentToolkit folder you will find the following files:

  • AppDeployToolkitBanner.png (the banner you see during the installation process and that I changed already)
  • AppDeployToolkitConfig.xml (configuration file with language settings etc.)
  • AppDeployToolkitExtensions.ps1 (script with your extensions for the framework)
  • AppDeployToolkitHelp.ps1 (a good helpfile explaining the functions)
  • AppDeployToolkitLogo.ico (the icon file)
  • AppDeployToolkitMain.cs (some further functions)
  • AppDeployToolkitMain.ps1 (main script with all functions)

psadk

The default log path is set to C:\Windows\Logs. After the installation of a package you can check for issues or errors at this place.

psadk

You can open the log files for example with Notepad or with CMTrace.exe. And that is again a point I really like. Looking at log files with CMTrace.exe is very straight forward and makes the process of troubleshooting application installation issues easy.

psadk

You can change default settings in the AppDepleyToolkitConfig.xml file. Be sure to change it once in a template and to copy this template for every package you want to create.

psadk

You can start AppDeployToolkitHelp.ps1 to check the available help for all functions.

psadk

Now let´s change the text at the end of the installation. We need to open the Deploy-Application.ps1 file and change the following line in the code to a text you like or remove it completely:

psadk

Why I love it!

I love standardization as well as packages and scripts that follow guidelines. And that´s what makes the PSADK so special. It enhances – if you like – your SCCM environment with functions you don´t get with the standard SCCM function set. Even if you don´t use SCCM or another ESD solution you can benefit from standardized installation and configuration packages. Again, if you use a standard like PowerShell you can reuse all of your software packages, even if you switch from SCCM to Empirum, Heat or want to deploy your applications through PowerShell DSC. No other language offers so many options.

Why I hate it!

Complex installations can be hard to read if they extend to a certain level. All phases are integrated in one main script and even if you use tools like the PowerShell ISE or Visual Studio Code it can sometimes be hard to edit. Especially if you use pre- and post-installation and uninstallation areas and have a huge amount of variables and functions.

Where can You use it?

Like I said before, nearly everywhere! If you created a package you can use it stand alone, in a script that runs the packages, you can integrate and deploy it via Group Policy or – and that´s the best thing in larger environments – with your ESD (e.g. SCCM).

Installation Scripts

Here you can see an example script file from the toolkit. It is very simple and I haven´t done any parameter initialization or inserted special variables for the installation parameters.

.SYNOPSIS This script performs the installation or uninstallation of an application(s). 
.DESCRIPTION The script is provided as a template to perform an install or uninstall of an application(s). The script either performs an "Install" deployment type or an "Uninstall" deployment type. The install deployment type is broken down into 3 main sections/phases: Pre-Install, Install, and Post-Install. The script dot-sources the AppDeployToolkitMain.ps1 script which contains the logic and functions required to install or uninstall an application. .PARAMETER DeploymentType The type of deployment to perform. Default is: Install. 
.PARAMETER DeployMode Specifies whether the installation should be run in Interactive, Silent, or NonInteractive mode. Default is: Interactive. Options: Interactive = Shows dialogs, Silent = No dialogs, NonInteractive = Very silent, i.e. no blocking apps. NonInteractive mode is automatically set if it is detected that the process is not user interactive. 
.PARAMETER AllowRebootPassThru Allows the 3010 return code (requires restart) to be passed back to the parent process (e.g. SCCM) if detected from an installation. If 3010 is passed back to SCCM, a reboot prompt will be triggered. 
.PARAMETER TerminalServerMode Changes to "user install mode" and back to "user execute mode" for installing/uninstalling applications for Remote Destkop Session Hosts/Citrix servers. 
.PARAMETER DisableLogging Disables logging to file for the script. Default is: $false. 
.EXAMPLE Deploy-Application.ps1 .EXAMPLE Deploy-Application.ps1 -DeployMode 'Silent' 
.EXAMPLE Deploy-Application.ps1 -AllowRebootPassThru -AllowDefer 
.EXAMPLE Deploy-Application.ps1 -DeploymentType Uninstall .NOTES Toolkit Exit Code Ranges: 60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1 69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1 70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1 
.LINK  http://psappdeploytoolkit.com #>[/fusion_text][/fusion_builder_column][/fusion_builder_row][/fusion_builder_container][fusion_builder_container hundred_percent="yes" overflow="visible"][fusion_builder_row][fusion_builder_column row_column_index="1_1" type="1_1" layout="1_1" background_position="left top" background_color="" border_size="" border_color="" border_style="solid" spacing="yes" background_image="" background_repeat="no-repeat" padding="" margin_top="0px" margin_bottom="0px" class="" id="" animation_type="" animation_speed="0.3" animation_direction="left" hide_on_mobile="no" center_content="no" min_height="none" last="no" hover_type="none" link="" border_position="all"][fusion_text][CmdletBinding()]
Param (
[Parameter(Mandatory=$false)]
[ValidateSet('Install','Uninstall')]
[string]$DeploymentType = 'Install',
[Parameter(Mandatory=$false)]
[ValidateSet('Interactive','Silent','NonInteractive')]
[string]$DeployMode = 'Interactive',
[Parameter(Mandatory=$false)]
[switch]$AllowRebootPassThru = $false,
[Parameter(Mandatory=$false)]
[switch]$TerminalServerMode = $false,
[Parameter(Mandatory=$false)]
[switch]$DisableLogging = $false
)

Try {
## Set the script execution policy for this process
Try { Set-ExecutionPolicy -ExecutionPolicy 'ByPass' -Scope 'Process' -Force -ErrorAction 'Stop' } Catch {}
##*===============================================
##* VARIABLE DECLARATION
##*===============================================
## Variables: Application

[string]$appVendor = 'Citrix'
[string]$appName = 'XenDesktop Controller'
[string]$appVersion = '7.6.300'
[string]$appArch = 'x64'
[string]$appLang = 'EN'
[string]$appRevision = '02'
[string]$appScriptVersion = '1.0.0'
[string]$appScriptDate = '03/30/2016'
[string]$appScriptAuthor = 'Sinisa Sokolic'
#
$labsources = "\\FILESERVER\lab_sources"

#*===============================================
##* Do not modify section below
#region DoNotModify
## Variables: Exit Code
[int32]$mainExitCode = 0
## Variables: Script
[string]$deployAppScriptFriendlyName = 'Deploy Application'
[version]$deployAppScriptVersion = [version]'3.6.5'
[string]$deployAppScriptDate = '08/17/2015'
[hashtable]$deployAppScriptParameters = $psBoundParameters
## Variables: Environment
If (Test-Path -LiteralPath 'variable:HostInvocation') { $InvocationInfo = $HostInvocation } Else { $InvocationInfo = $MyInvocation }
[string]$scriptDirectory = Split-Path -Path $InvocationInfo.MyCommand.Definition -Parent
## Dot source the required App Deploy Toolkit Functions
Try {
[string]$moduleAppDeployToolkitMain = "$scriptDirectory\AppDeployToolkit\AppDeployToolkitMain.ps1"
If (-not (Test-Path -LiteralPath $moduleAppDeployToolkitMain -PathType 'Leaf')) { Throw "Module does not exist at the specified location [$moduleAppDeployToolkitMain]." }
If ($DisableLogging) { . $moduleAppDeployToolkitMain -DisableLogging } Else { . $moduleAppDeployToolkitMain }
}
Catch {
If ($mainExitCode -eq 0){ [int32]$mainExitCode = 60008 }
Write-Error -Message "Module [$moduleAppDeployToolkitMain] failed to load: `n$($_.Exception.Message)`n `n$($_.InvocationInfo.PositionMessage)" -ErrorAction 'Continue'
## Exit the script, returning the exit code to SCCM
If (Test-Path -LiteralPath 'variable:HostInvocation') { $script:ExitCode = $mainExitCode; Exit } Else { Exit $mainExitCode }
}
#endregion
##* Do not modify section above
##*===============================================
##* END VARIABLE DECLARATION
##*===============================================
If ($deploymentType -ine 'Uninstall') {
##*===============================================
##* PRE-INSTALLATION
##*===============================================

[string]$installPhase = 'Pre-Installation'
## Show Welcome Message, close Internet Explorer if required, allow up to 3 deferrals, verify there is enough disk space to complete the install, and persist the prompt
Show-InstallationWelcome -CloseApps 'iexplore' -AllowDefer -DeferTimes 3 -CheckDiskSpace -PersistPrompt
## Show Progress Message (with the default message)
Show-InstallationProgress
##

##*===============================================
##* INSTALLATION
##*===============================================

[string]$installPhase = 'Installation'
## Handle Zero-Config MSI Installations
If ($useDefaultMsi) {
[hashtable]$ExecuteDefaultMSISplat =  @{ Action = 'Install'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) }
Execute-MSI @ExecuteDefaultMSISplat; If ($defaultMspFiles) { $defaultMspFiles [/fusion_text][fusion_text] ForEach-Object { Execute-MSI -Action 'Patch' -Path $_ } }
}

Execute-Process  -Path "$labsources\02_Citrix\XenDesktop_76\media\x64\XenDesktop Setup\Xendesktopserversetup.exe" -Parameters "/quiet /Components CONTROLLER /nosql /noreboot"

##*===============================================
##* POST-INSTALLATION
##*===============================================

[string]$installPhase = 'Post-Installation'
## ## Display a message at the end of the install
If (-not $useDefaultMsi) { Show-InstallationPrompt -Message 'You can customize text to appear at the end of an install or remove it completely for unattended installations.' -ButtonRightText 'OK' -Icon Information -NoWait }

}
ElseIf ($deploymentType -ieq 'Uninstall')
{
##*===============================================
##* PRE-UNINSTALLATION
##*===============================================

[string]$installPhase = 'Pre-Uninstallation'
## Show Welcome Message, close Internet Explorer with a 60 second countdown before automatically closing
Show-InstallationWelcome -CloseApps 'iexplore' -CloseAppsCountdown 60
## Show Progress Message (with the default message)
Show-InstallationProgress
##

##*===============================================
##* UNINSTALLATION
##*===============================================

[string]$installPhase = 'Uninstallation'
## Handle Zero-Config MSI Uninstallations
If ($useDefaultMsi) {
[hashtable]$ExecuteDefaultMSISplat =  @{ Action = 'Uninstall'; Path = $defaultMsiFile }; If ($defaultMstFile) { $ExecuteDefaultMSISplat.Add('Transform', $defaultMstFile) }
Execute-MSI @ExecuteDefaultMSISplat
}
#

##*===============================================
##* POST-UNINSTALLATION
##*===============================================

[string]$installPhase = 'Post-Uninstallation'
##

}
##*===============================================
##* END SCRIPT BODY
##*===============================================
## Call the Exit-Script function to perform final cleanup operations
Exit-Script -ExitCode $mainExitCode
}
Catch {
[int32]$mainExitCode = 60001
[string]$mainErrorMessage = "$(Resolve-Error)"
Write-Log -Message $mainErrorMessage -Severity 3 -Source $deployAppScriptFriendlyName
Show-DialogBox -Text $mainErrorMessage -Icon 'Stop'
Exit-Script -ExitCode $mainExitCode
}

How to add functionality?

<# .SYNOPSIS This script is a template that allows you to extend the toolkit with your own custom functions. 
.DESCRIPTION The script is automatically dot-sourced by the AppDeployToolkitMain.ps1 script. 
.NOTES     Toolkit Exit Code Ranges:     60000 - 68999: Reserved for built-in exit codes in Deploy-Application.ps1, Deploy-Application.exe, and AppDeployToolkitMain.ps1     69000 - 69999: Recommended for user customized exit codes in Deploy-Application.ps1     70000 - 79999: Recommended for user customized exit codes in AppDeployToolkitExtensions.ps1 
.LINK  http://psappdeploytoolkit.com #>

[CmdletBinding()]
Param (
)

##*===============================================
##* VARIABLE DECLARATION
##*===============================================

# Variables: Script
[string]$appDeployToolkitExtName = 'PSAppDeployToolkitExt'
[string]$appDeployExtScriptFriendlyName = 'App Deploy Toolkit Extensions'
[version]$appDeployExtScriptVersion = [version]'1.5.0'
[string]$appDeployExtScriptDate = '06/11/2015'
[hashtable]$appDeployExtScriptParameters = $PSBoundParameters

##*===============================================
##* FUNCTION LISTINGS
##*===============================================

#

##*===============================================
##* END FUNCTION LISTINGS
##*===============================================

##*===============================================
##* SCRIPT BODY
##*===============================================

If ($scriptParentPath) {
Write-Log -Message "Script [$($MyInvocation.MyCommand.Definition)] dot-source invoked by [$(((Get-Variable -Name MyInvocation).Value).ScriptName)]" -Source $appDeployToolkitExtName
}
Else {
Write-Log -Message "Script [$($MyInvocation.MyCommand.Definition)] invoked directly" -Source $appDeployToolkitExtName
}

##*===============================================
##* END SCRIPT BODY
##*===============================================

How to install a package?

There are two ways to install an application:

First:

Launch the "Deploy-Application.ps1" PowerShell script as administrator and use a parameter like -Deploymode "Silent"

Second:

Launch the "Deploy-Application.exe" and use parameters like /32 or -AllowRebootPassThru"

If you start the ps1 script or the exe file without any parameter it will always use “Install” as default.

How to uninstall a package?

This process is also very straight forward:

First:

Launch the "Deploy-Application.ps1" PowerShell script as administrator and use the parameter "Uninstall"

Second:

Launch the "Deploy-Application.exe" and use the parameter -DeploymentType "Uninstall"

How to start?

Well, I prepared something for you ;-)… In the downloads section of my blog you can find some preconfigured packages. The only thing you need to do is to add the source files (if they are needed) to the packages and you´re good to go.

Why should you use automated and standardized installation Packages?

In my opinion there is a very simple explanation for that. Every application that needs to be installed more than once profits from automation and it brings more than just the value in saving time. It also leads to:

  • Reproducibility
  • Quality assurance
  • Documentation

Further information

The toolkit itself has a very good documentation. Just download it and you have it. Find the download on GitHub. Version 3.6.8 is the current available version.

I really hope this was interesting for you and you can use something from here for yourself. All available PSADK packages on my blog come without warranty, please check them before installing them in your test lab or development environment.

By | 2017-03-04T20:21:45+00:00 May 12th, 2016|Microsoft, PowerShell, Technical Stuff|1 Comment

About the Author:

One Comment

  1. […] about 152 MB for the Citrix XenApp / XenDesktop 7.6 Controller. I suggest you use the PSADK and my blog article about it to create a package that will extend the logging of the […]

%d bloggers like this: