Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PSAppDeployToolkit 4.x Application Deployment Template
A reusable, self-contained application packaging template built on PSAppDeployToolkit 4.x.
Copy this repository, edit one region, drop the installation media into Files, and the package is ready to deploy.
What it adds over the stock v4 template
| Addition | Detail |
|---|---|
| Elevation wrapper | Test-ProcessElevationStatus gates the entire script. When not elevated it relaunches itself elevated through the first available interpreter (powershell.exe, then pwsh.exe) and forwards every supplied parameter. |
| Deployment type switch | A single Switch ($adtSession.DeploymentType) with Install / Uninstall / Repair branches instead of three separate dispatch functions. |
| Automatic MSI/EXE installation | Installation media is located across the architecture aware content directories and executed through Start-ADTMsiProcess / Start-ADTProcess using splatted parameters, with transform and patch detection. |
| Self contained | The PSAppDeployToolkit module ships inside the package and is imported without a version constraint, so the package runs on any endpoint and the module is updated by replacing the directory. |
Everything the toolkit already provides is used as-is rather than being mirrored into a parallel configuration structure:
| Concern | Where it lives |
|---|---|
| Application identity, install titles, exit codes, processes to close, content directories, log paths | $adtSession (the deployment session) |
| OS architecture, culture, well known paths, task sequence detection | $adtEnv (Get-ADTEnvironmentTable) |
| Logging, dialog style, timeouts, cache and temp paths | Config\config.psd1 |
| Media discovery, executable arguments, software removal criteria, dialog behavior | $PackageOptions (the only custom structure) |
Layout
Everything that gets deployed to an endpoint lives under src/. That directory is the package — copy its contents (not the repository root) into your MECM/Intune source folder.
src/ <- the deployable package
Invoke-AppDeployToolkit.ps1 Deployment script (edit the "Package Configuration" region)
Invoke-AppDeployToolkit.exe Launcher used by MECM/Intune
AppDeployToolkitExtensions.ps1 Dot sourced; loads the bundled dependencies below
PSAppDeployToolkit/ The toolkit module that ships with the package
Assets/ Package branding (icon, banner)
Config/ Toolkit configuration overrides
Files/
X64/ 64-bit installation media
X86/ 32-bit installation media
All/ Architecture neutral installation media
SupportFiles/
Functions/ .ps1 files, dot sourced automatically
Modules/ Module folders, imported automatically
Libraries/
X64/ X86/ All/ .dll assemblies, loaded automatically
Icons/ Shortcut icons
Templates/ Copy-ready authoring material (function skeleton)
docs/ In-depth documentation
Scratch/ Working/reference material (git ignored)
README.md
Bundled dependencies
AppDeployToolkitExtensions.ps1 is dot sourced right after the session opens — so $adtSession, $adtEnv, and toolkit logging are all live — and loads whatever the package carries, in this order:
- Modules — every module folder under
SupportFiles\Modules. The newest version of each wins, and any prerequisite declared throughRequiredModulesthat is also bundled is imported first, so load order does not matter. - Assemblies — every
.dllunderSupportFiles\Libraries\<OS Architecture>andSupportFiles\Libraries\All, loaded from its bytes so the file is not locked on disk. - Functions — every
.ps1underSupportFiles\Functions, dot sourced so its functions are available to all deployment phases.
Each stage is skipped when its directory is empty or absent, and a failure to load one dependency is logged at severity 3 and the deployment continues. Every step is written to the toolkit log, so you can see exactly what loaded and in what order.
Quick start
- Copy the contents of
src\to a new package folder. - Stage the installation media under
Files\X64,Files\X86, and/orFiles\All. - Open
Invoke-AppDeployToolkit.ps1and edit the Package Configuration region:$adtSession— vendor, name, version, revision, processes to close.$PackageOptions— executable arguments, MSI properties, software removal filter, dialog behavior.
- Test:
powershell.exe -ExecutionPolicy Bypass -NoProfile -NoLogo -File ".\Invoke-AppDeployToolkit.ps1" -DeploymentType 'Install' -DeployMode 'Silent'
Leave InstallName and InstallTitle empty and the toolkit derives them from the identity (MyApplicationVendor_MyApplicationName_1.0.0.0_EN_01).
Updating the bundled toolkit module
No version is pinned. The script prefers .\PSAppDeployToolkit\PSAppDeployToolkit.psd1 and falls back to the PowerShell module path when the directory is absent. To update the bundled module, replace the directory:
Copy-Item -Path (Get-Module -ListAvailable PSAppDeployToolkit | Sort-Object Version -Descending | Select-Object -First 1).ModuleBase -Destination '.\src\PSAppDeployToolkit' -Recurse -Force
The version that was actually loaded is written to the deployment log on every run.
How the automatic installation works
Discovery and execution live in AppDeployToolkitExtensions.ps1 as Get-ADTDeploymentMedia and Invoke-ADTDeploymentMedia, so the Installation phase of the deployment script is a single splatted call rather than a hundred lines of loop. The behavior below is what those functions do.
The content directories are searched in this order, which establishes execution precedence:
Files\<OS Architecture>— for exampleFiles\X64, from$adtEnv.envOSArchitectureFiles\AllFiles
Within each directory the search patterns are applied in the order they are listed (*.msi, then *.exe), and files are ordered largest first so the primary installer runs before its companions.
- Windows installer packages are executed with
Start-ADTMsiProcess. A.mstin the same directory is applied when its base name matches the package base name, the culture name (en-US), or the culture LCID (1033). Every.mspin the same directory is applied as a patch. - Executables are executed with
Start-ADTProcess. Arguments come from$PackageOptions.ExecutableArgumentDictionary— each key is a regular expression matched against the file name, and the first key that matches wins, so the most specific pattern must be listed first. An executable that matches no entry uses$PackageOptions.ExecutableDefaultArgumentList(/Sby default).
Success and reboot exit codes are inherited from $adtSession.AppSuccessExitCodes / AppRebootExitCodes, so they are not repeated on the individual calls.
Uninstallation uses Uninstall-ADTApplication driven by $PackageOptions.SoftwareRemovalFilterScript, which replaces the dynamic software removal function used in the 3.x template.
Zero-Config MSI: the toolkit's built-in Zero-Config MSI support only activates when
AppNameis empty, and it only looks in the root ofFiles. This template setsAppName, so discovery is owned by the loop above. If you deliberately blankAppNameto use Zero-Config, also set$PackageOptions.PerformAutomaticInstallation = $Falseso the same MSI is not executed twice.
Deployment types and modes
-DeploymentType is still a parameter in 4.x and still drives the flow. The session resolves the final value, so the script switches on $adtSession.DeploymentType rather than the raw parameter.
-DeployMode gained an Auto value in 4.x (the default). Auto resolves to interactive when a user is logged on, the device has completed the OOBE, and there are no processes to close, and to silent otherwise. The resolved value is available as $adtSession.DeployMode.
Each of those three checks can be bypassed individually. All three are exposed in the package configuration region and default to $False:
| Setting | Set $True to |
|---|---|
$adtSession.NoOobeDetection |
Show the UI during the OOBE / Autopilot ESP phase |
$adtSession.NoSessionDetection |
Show the UI when no user is logged on and the SYSTEM process is interactive |
$adtSession.NoProcessDetection |
Show the UI even when there are no processes to close |
They only take effect when DeployMode is Auto; an explicit -DeployMode always wins.
Exit codes
| Range | Meaning |
|---|---|
| 0 | Success |
| 1641 / 3010 | Success, restart required |
| 60001 | Unhandled error in Invoke-AppDeployToolkit.ps1 |
| 60008 | The PSAppDeployToolkit module failed to load |
| 69000 - 69999 | Available for package specific exit codes |
Further documentation
- docs/Template-Reference.md — full
$PackageOptionsreference, custom parameter handling, and 3.x migration notes. - Templates/README.md — the function skeleton and what is in scope inside a package function.