The PowerShell App Deployment Toolkit — PSADT — is the de facto standard for wrapping Windows application installers in enterprise environments. It provides a reliable pattern for pre-install checks, user notification, process handling, the actual installation call, and post-install verification, all wrapped in a scriptable framework that plays well with the deployment engines that consume it. Configuration Manager is the most common such engine, and ConfigMgr's Application model and PSADT's wrapper pattern are a natural fit.
Version 4 of PSADT introduced a module-based architecture that departs significantly from the dot-sourced layout of version 3. Function names were systematically renamed with an ADT prefix, configuration moved from XML to PowerShell data files, and the package layout was tightened. For ConfigMgr integrators, the practical consequences are narrow but important: the entry-point command that the ConfigMgr Application calls is different from version 3, and the logging paths changed.
The following article focuses on the ConfigMgr side of the equation — how a PSADT 4 package becomes a well-behaved ConfigMgr Application, which detection methods hold up in practice, which user experience settings matter, and how to read the relevant logs when something misbehaves. The internals of PSADT itself — writing the wrapper, handling dialogs, localisation — are covered in the PSADT documentation and are deliberately not duplicated here.
Why PSADT in the First Place
Bare vendor installers vary wildly in quality. Some accept silent parameters cleanly; others embed MSI payloads in outer bootstrappers that reject /quiet. Some exit with meaningful codes; others always return zero. Some check for running processes; others happily overwrite in-use binaries. Some localise their own user prompts; most do not.
A deployment toolkit solves these problems uniformly. PSADT wraps the vendor installer with:
- Pre-install checks — disk space, architecture, prerequisites, running processes
- User notification — close these applications, defer, please save your work
- Controlled execution — of the vendor installer with logged output and known exit-code handling
- Post-install actions — registry adjustments, shortcuts, file permissions, user profile seeding
- Consistent return codes — translated for the consuming deployment engine
For a ConfigMgr Administrator, this translates into higher success rates, clearer failure reporting, and a uniform user experience across the application catalogue.
PSADT 4 in One Page
PSADT 4 is a PowerShell module rather than a dot-sourced script library. The functions live in PSAppDeployToolkit, which is loaded at the start of the wrapper script. All exported functions carry the ADT prefix — Show-ADTInstallationWelcome, Start-ADTProcess, Write-ADTLogEntry, and so on — which eliminates the naming collisions that could arise when PSADT was sourced into a session with other modules loaded.
The package layout that a packager hands off to ConfigMgr contains, at a minimum:
- A wrapper script that the deployment engine calls
- A signed executable entry point used to elevate and invoke the wrapper
- A
Filesfolder holding the vendor installer payload - A
SupportFilesfolder for ancillary resources (icons, XML configs, reference data) - The PSADT toolkit folder containing the module, configuration, and localised strings
The specific filenames and folder names are documented in the official PSADT repository and should be taken as authoritative. The ConfigMgr-facing surface — what matters for deployment — is the entry-point executable that the Application's install and uninstall commands call, and the log location PSADT writes to during execution.
From Package to Application
A finalised PSADT 4 package is a folder tree on a packaging share — typically \\packaging\Packages\<Vendor>\<Product>\<Version>\. This folder is the content source for the ConfigMgr Application.
Creating the Application
In the ConfigMgr console, under Software Library → Application Management → Applications, Create Application launches the wizard. Two paths exist:
- Automatically detect information from an installation file — works when the source is an MSI with readable metadata. Not the right choice for a PSADT wrapper, which is a PowerShell-driven container around whatever installer type lies beneath.
- Manually specify the application information — the path used for PSADT packages. All metadata is entered by the packager.
The metadata that matters later for reporting and user visibility:
- Name — displayed in Software Center and in the console
- Publisher — displayed in Software Center
- Version — version string, used in detection and display
- Language — the primary UI language of the application, for filtering
- Icon — 250 × 250 pixel PNG recommended; shown in Software Center tiles
- Localized descriptions — displayed during the user-facing prompts
For enterprise environments, a naming convention that surfaces vendor, product, and version in the Name field (for example, 7-Zip 24.09 x64) is strongly advised. The same string should appear in detection methods where applicable.
Adding a Deployment Type
An Application is a logical wrapper. The actual install and uninstall commands live in one or more Deployment Types. For PSADT packages, the type is Script Installer.
Deployment Types → Add → Script Installer launches a sub-wizard. The relevant settings:
Content tab
- Content location — UNC path to the package folder. The path is what ConfigMgr hashes and distributes; content distribution runs from this path to every selected DP.
- Persist content in the client cache — enables this when the application is large and may be referenced by superseding deployments later. Disabled by default to conserve cache space.
- Allow clients to use distribution points from the default site boundary group — standard choice; relevant for boundary-group fallback behaviour.
Programs tab
This is where the PSADT entry point is wired up. Install and uninstall commands call the PSADT wrapper with a deployment mode parameter:
Install program:
Invoke-AppDeployToolkit.exe -DeploymentType Install -DeployMode Interactive
Uninstall program:
Invoke-AppDeployToolkit.exe -DeploymentType Uninstall -DeployMode Silent
Repair program (optional):
Invoke-AppDeployToolkit.exe -DeploymentType Repair -DeployMode Silent
The executable entry point, rather than the .ps1 wrapper, is called directly. This ensures elevation, PowerShell execution policy bypass, and signing of the entry point itself — all handled inside the toolkit.
Deploy mode values are interpreted by PSADT:
- Interactive — shows the user-facing dialogs (close-applications prompt, installation progress, completion banner)
- NonInteractive — shows minimal UI; suitable for session-zero execution with a passive logged-on user
- Silent — no UI at all; the default for uninstall and for unattended reinstalls
Detection Method tab
Detection is the single most important correctness element in a ConfigMgr Application. It is what ConfigMgr uses to determine whether the application is already installed, whether the last install succeeded, and whether a subsequent enforcement cycle needs to run. A broken detection method is the root cause of most phantom "install failed" statuses.
Four detection primitives are supported, combined by AND/OR:
- MSI product code — when the underlying installer is an MSI and the product code is stable across builds. Simple, fast, and reliable.
- File system — a file exists at a given path, optionally with a specific version or modification date. Useful for non-MSI products that install versioned binaries.
- Registry — a registry key exists, a value exists, or a value equals a specific content. The most flexible of the primitives.
- Custom script — a PowerShell, VBScript, or JScript snippet that evaluates detection and returns a specific marker. Used when no single primitive captures the installation state.
Reliable detection patterns for PSADT-wrapped packages in the real world:
- MSI-based vendor installers — MSI product code is the correct choice; the PSADT wrapper does not change it.
- EXE-based vendor installers — file-based detection against the installed binary's version, or registry-based detection against an
Uninstallkey, both work well. - Compound installs (several vendor components) — a custom PowerShell detection script that asserts each component individually and returns success only if all are present.
A minimal custom detection script:
# Detection: application present if the expected version is recorded in the registry
$key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{PRODUCT-GUID}"
$expected = "24.09"
if (Test-Path $key) {
$installed = (Get-ItemProperty -Path $key).DisplayVersion
if ($installed -eq $expected) {
Write-Output "Installed" # non-empty stdout = detected
exit 0
}
}
# Fall through: no output, application not detected
exit 0
User Experience tab
This is where the semantics of interactive versus session-zero are tuned:
- Installation behaviour — Install for system, Install for user, or Install for system if resource is device; otherwise install for user. For PSADT wrappers running as SYSTEM, Install for system is the correct choice.
- Logon requirement — Only when a user is logged on, Whether or not a user is logged on, Only when no user is logged on. The logged-on requirement matters when the PSADT wrapper shows dialogs; it must match the deploy mode.
- Installation program visibility — Normal, Minimized, Maximized, Hidden. For Interactive mode, Normal allows the PSADT dialogs to appear.
- Allow users to view and interact with the installation — must be checked when a user-facing PSADT wrapper is deployed as required with Whether or not a user is logged on.
- Maximum allowed run time — a hard cap after which ConfigMgr terminates the process. The default of 120 minutes is generous; some installers need adjustment upward, and most packages tolerate a downward trim to 30 or 45 minutes.
Requirements tab
Optional conditions that must evaluate true before the install runs. Common requirements:
- Operating system version (
Windows 11 24H2 or higher) - Architecture (
64-bit) - Primary device (deploy only on devices the user uses regularly)
- Disk space
Requirements are evaluated on the client; a requirement that fails silently suppresses the install with a specific status in reporting.
Dependencies tab
Used when the application needs another application installed first. Dependencies are enforced automatically — installing the dependent application triggers the dependency install before the wrapper runs. For PSADT packages that depend on, say, a Visual C++ Redistributable, the redistributable is packaged as a separate Application and referenced here.
Content Distribution
Once the Application and Deployment Type are defined, content must be distributed to every DP that will serve the install.
Distribute Content launches a wizard that lists referenced content (the deployment type's source folder) and lets the DP or DP Group be chosen. For a lab with a single DP, the choice is trivial; in production, content is typically distributed to a DP Group that matches the collection's boundary group.
Monitoring the distribution lives under Monitoring → Distribution Status → Content Status. A distribution in progress is normal; a distribution in Failed state points at a content library or network issue on the DP. The log to consult is distmgr.log on the site server, which records the state transitions and provider errors.
Targeting and Deployment
With content on the DP, Deploy creates a deployment against a Collection. The fundamental choices are the same as for any ConfigMgr deployment — User Collection vs Device Collection, Available vs Required, deadline, notification behaviour.
A few PSADT-specific notes:
- User collection vs device collection — PSADT wrappers that handle user-visible dialogs are typically deployed as Available to a user collection, allowing the user to trigger the install through Software Center at a convenient time. Device collection deployments are appropriate for required silent installs at device-wide scope.
- Pre-deployment notification — ConfigMgr's own notifications (Show notifications for new deployments, Show all notifications) interact with PSADT's installation dialogs. For a clean user experience, one of the two is usually suppressed — either ConfigMgr's toast is disabled, or PSADT's pre-install banner is.
- Maintenance windows — on device collections with a maintenance window, ConfigMgr defers execution to the window. PSADT wrappers that require a user context do not run during unattended windows when no user is logged on unless the logon requirement is set accordingly.
The Enforcement Flow
Understanding what happens when ConfigMgr decides a PSADT package must be installed clarifies both the success path and the failure modes.
- Policy evaluation — the ConfigMgr client pulls the application policy from the MP and registers a pending application install.
- Requirement evaluation — the client evaluates requirements. A failing requirement ends enforcement with a Requirements not met status and does not escalate to content download.
- Content download — the client locates a DP through its boundary group, downloads the content to the local cache (
C:\Windows\ccmcache), and verifies the hash. - Detection pre-check — the client runs the detection method first. If the application is already detected, the enforcement is marked as successful immediately without running the install command.
- Install execution — the install command (
Invoke-AppDeployToolkit.exe -DeploymentType Install ...) is launched in the configured context. Output is captured; exit codes are evaluated against the success/failure/reboot mapping. - Post-enforcement detection — the client runs the detection method a second time, after the install command returns. A detection that reports not installed after a zero exit code is the source of the infamous Past due - will be retried status.
- State reporting — the client reports the result to the MP, where the console surfaces it under Monitoring → Deployments.
The enforcement flow matters because different failure modes manifest at different steps. A content hash mismatch fails at step 3. An install error fails at step 5. A detection mismatch fails at step 6. The failure message, and the relevant log, depend on the step.
Logs and Verification
Two sets of logs cover the PSADT deployment cycle: the ConfigMgr client logs and the PSADT-written logs.
Client-Side ConfigMgr Logs
All under C:\Windows\CCM\Logs\:
AppEnforce.log— the primary log for application enforcement. Records the content cache path, the install command line as executed, the exit code, and the post-enforcement detection result. ReadingAppEnforce.logis the first step for any application deployment failure.AppDiscovery.log— detection method evaluation. Surfaces why a detection thinks an application is or is not present.AppIntentEval.log— intent evaluation; records why a deployment is assigned to the client and whether it is required or available.CAS.log— content access service; tracks content download and DP selection.CcmExec.log— the ConfigMgr agent's main loop; useful for timing and state correlation.
PSADT Logs
PSADT writes its own logs to a deterministic location so that package authors can attach them to incidents. The default path in recent versions is C:\Windows\Logs\Software\ with a file named after the application and the deployment type, for example MyApp_PSADT_Install.log. The exact filename convention is defined in the toolkit's configuration.
The PSADT log captures:
- The invocation parameters (deployment type, deploy mode)
- Pre-install checks and their results
- The user notification interaction (accept, defer, timeout)
- The vendor installer command line as PSADT executed it, including all arguments
- The vendor installer's own output and exit code
- Post-install actions
For troubleshooting an install that returns non-zero, AppEnforce.log identifies the failure moment and the returned code; the PSADT log explains why the vendor installer returned it.
AppEnforce.log and the matching PSADT log — is the irreducible minimum for any packaging incident ticket. Missing either one forces the next analyst to reproduce the issue.
Verifying in Software Center
On the client, Software Center exposes deployment status directly. Installation Status tab lists every application deployment by state. A failed install shows the error code and a truncated message; expanding the entry reveals the full error record. For user-facing diagnostics, this is the end-user-safe interface; for deep analysis, the logs remain primary.
Common Pitfalls
Detection evaluates to not installed after a successful install
The install command returned zero but the detection method did not find the expected artifact. Causes range from a mistyped registry key to a version string that differs from the expected value by whitespace. Reading AppDiscovery.log after the enforcement clarifies what the detection actually saw.
Past due - will be retried repeats indefinitely
A classic symptom of a mismatched detection method. Every retry installs, detects nothing, and retries again. The fix is always in the detection method, not in the install command.
PSADT dialogs do not appear when expected
The Deployment Type's User Experience settings override PSADT's own -DeployMode Interactive argument. If the deployment type is set to Install for system with Whether or not a user is logged on but without Allow users to view and interact, the dialogs are suppressed. Both settings must align.
Install succeeds manually but fails through ConfigMgr Almost always a context difference. Running the install manually typically uses the logged-on user's context; ConfigMgr runs as SYSTEM. Path resolution, HKCU access, and per-user software components behave differently under SYSTEM. The PSADT log, running under SYSTEM, captures what actually happened.
Uninstall never runs The Uninstall command is only executed when the deployment purpose is Uninstall or when the application is superseded by another with Uninstall chosen. An Available deployment that is later removed from the collection does not trigger uninstall.
Dependencies install in the wrong order
ConfigMgr resolves dependencies at enforcement time, not at creation time. A dependency chain that worked yesterday can be broken by a superseding deployment elsewhere in the chain. The AppIntentEval.log surfaces the resolution order.
Content not available on the DP at the time of enforcement The Application was deployed before content distribution completed. ConfigMgr dutifully attempts the install, finds no content, and logs a failure. The fix is procedural — distribute first, then deploy.
Vendor installer prompts for UAC even under SYSTEM
Some legacy installers request UAC even when not running interactively; the request is auto-denied by the session-zero isolation, and the installer then silently fails. The remedy is an installer-specific silent flag, usually discoverable through the vendor's documentation or by running the installer with /?. PSADT's Start-ADTProcess exposes the arguments cleanly.
Maximum allowed run time expires mid-install A large suite with multiple vendor installers chained in the wrapper can exceed the default 120-minute cap. ConfigMgr terminates the process, and PSADT's log ends abruptly mid-step. Raising the Maximum allowed run time on the deployment type is the correct fix; masking the problem by increasing ConfigMgr's global timeout affects every deployment.
Takeaways
PSADT 4 and ConfigMgr's Application model are a tested pair. PSADT encapsulates the messiness of vendor installers and produces a clean wrapper; ConfigMgr's Application model provides the metadata, targeting, detection, and state reporting that enterprise deployment requires. The integration surface between the two is small — the entry-point executable, its arguments, and the detection method — but getting each of those three right is what separates a reliable deployment from a chronic source of incident tickets.
Detection is the most important of the three and the most frequently misconfigured. Every hour spent refining the detection method saves days of investigating Past due - will be retried statuses in production. MSI product codes remain the gold standard when available; registry-based detections are a strong second; custom PowerShell detections fill the gaps but require careful output handling.
Logs are the second most important discipline. AppEnforce.log and the PSADT log together cover every scenario; reading them in order — ConfigMgr first to identify the moment of failure, PSADT second to understand the vendor installer's behaviour — is a repeatable diagnostic pattern that scales across any packaged product.
Rule of thumb: Detection first, deploy mode second, run-time cap third. When a PSADT 4 application misbehaves in ConfigMgr, these three settings are the root cause in the overwhelming majority of real-world incidents.
Tested with: Configuration Manager Current Branch 2503 on Windows Server 2025, PSADT 4 on Windows 11 24H2 clients, mixed MSI and EXE vendor installers.