Information on and Troubleshooting Detection Scripts
What is a Detection Script?
A detection script determines whether an application is already installed on a target machine. It runs before install/uninstall actions and after the actions to verify success. The UAM engine uses the script's return value to decide whether to install, skip, or upgrade an app.
Detection scripts are used by:
- Shell Apps — always required
- Winget Apps — optional custom detection (overrides default winget query)
Return Value Rules
Your detection script must return one of the following:
| Return | Meaning |
|---|---|
A version string (e.g. "1.2.3") |
App is installed at this version |
$true |
App is installed (version unknown — upgrade decisions cannot be made) |
$false |
App is NOT installed |
$null |
App is NOT installed |
Any other return type will cause an immediate failure for that action.
Troubleshooting: Symptoms and Causes
Symptom: Entire deployment fails with no results reported
Likely cause: Using exit in your detection script
# WRONG — causes total deployment failure
if (-not (Test-Path "C:\Program Files\MyApp")) {
exit 1
}The exit command terminates the entire deployment process — not just your script. No results are reported back to the portal, and the deployment appears to hang or timeout.
This also applies to:
exit 0exit 1[System.Environment]::Exit(0)
Fix: Use return instead:
if (-not (Test-Path "C:\Program Files\MyApp")) {
return $null # Not installed
}
return $trueSymptom: Action fails with "unexpected type" error
Likely cause: Returning a complex object or an array
# WRONG — returns a registry object, not a string
return Get-ItemProperty "HKLM:\SOFTWARE\MyApp"
# WRONG — returns an array
return @("1.0", "2.0")
# WRONG — returns an integer
return 1Fix: Always cast your return value explicitly:
$app = Get-ItemProperty "HKLM:\SOFTWARE\MyApp" -ErrorAction SilentlyContinue
if ($app) {
return [string]$app.DisplayVersion
}
return $nullSymptom: Action fails with "unexpected type" even though you return a string
Likely cause: Using Write-Output in your script
# WRONG — Write-Output adds to the return value
Write-Output "Checking for MyApp..."
$version = (Get-ItemProperty "HKLM:\SOFTWARE\MyApp").Version
return $versionAny Write-Output (or bare string expressions) adds data to the script's output. The final return becomes a mixed array instead of a clean string.
Fix: Use $context.Log() for diagnostic messages:
$context.Log("Checking for MyApp...")
$version = (Get-ItemProperty "HKLM:\SOFTWARE\MyApp" -ErrorAction SilentlyContinue).Version
if ($version) {
$context.Log("Found: $version")
return [string]$version
}
return $nullSymptom: Action reports failure even though the app isn't installed
Likely cause: Using throw to signal "not found"
# WRONG — throw means "error", not "not installed"
$app = Get-Package -Name "MyApp" -ErrorAction SilentlyContinue
if (-not $app) {
throw "Application not found"
}An exception in a detection script is treated as a script error, not as "app not installed." The action fails rather than proceeding with installation.
Fix: Return $null or $false to signal the app is absent:
$app = Get-Package -Name "MyApp" -ErrorAction SilentlyContinue
if ($app) {
return [string]$app.Version
}
return $nullSymptom: Deployment hangs indefinitely or times out
Likely cause: Unbounded loop or long-running operation in detection script
# WRONG — may never exit
while ((Get-Service "MyAppService").Status -ne "Running") {
Start-Sleep -Seconds 5
}
return $trueDetection scripts have no individual timeout. A script that never finishes blocks the entire deployment until the platform-level timeout is reached (which can take over an hour).
Fix: Always use bounded checks:
$maxAttempts = 3
for ($i = 0; $i -lt $maxAttempts; $i++) {
$svc = Get-Service "MyAppService" -ErrorAction SilentlyContinue
if ($svc -and $svc.Status -eq "Running") {
return $true
}
Start-Sleep -Seconds 2
}
return $nullSymptom: Detection is unreliable — sometimes reports "not installed" for apps that are present
Likely cause: Detection depends on network or external services
# WRONG — network may be unavailable
$response = Invoke-RestMethod "https://api.myapp.com/status"
if ($response.installed) { return $response.version }
return $nullNetwork connectivity is not guaranteed during deployment (especially on newly provisioned VMs). DNS failures or API timeouts will produce false "not installed" results, triggering unnecessary reinstalls.
Fix: Always detect locally using registry, file system, or services:
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\MyApp"
$app = Get-ItemProperty $regPath -ErrorAction SilentlyContinue
if ($app) {
return [string]$app.DisplayVersion
}
return $nullSymptom: App is detected but triggers an unexpected upgrade (or skip)
Likely cause: Returned version doesn't match the version names defined in your app
For Shell Apps, the returned version string should match one of the version names configured in your app definition. If you return "1.0.0.1234" but your version is named "1.0.0", the system may make incorrect upgrade or skip decisions.
Fix: Return the exact version name that matches your app's version definitions:
$installedBuild = (Get-ItemProperty "HKLM:\SOFTWARE\MyApp").DisplayVersion
# Map to the version names defined in your shell app
$knownVersion = $context.Versions | Where-Object { $installedBuild -like "$($_.Name)*" } | Select-Object -First 1
if ($knownVersion) {
return $knownVersion.Name
}
return $nullSymptom: Detection works in testing but fails during deployment
Likely cause: Cmdlet throws a terminating error you're not catching
# Fails if the registry path doesn't exist
$version = (Get-ItemProperty "HKLM:\SOFTWARE\NonExistent\MyApp" -ErrorAction Stop).VersionWhen run on a machine where the app isn't installed, this throws an error that is treated as a detection failure (not "app not installed").
Fix: Use -ErrorAction SilentlyContinue for all probing operations:
$app = Get-ItemProperty "HKLM:\SOFTWARE\MyApp" -ErrorAction SilentlyContinue
if ($app -and $app.Version) {
return [string]$app.Version
}
return $nullQuick Reference: Do's and Don'ts
DO
- Use
returnto provide your result - Return a
[string]version,$true/$false, or$null - Use
$context.Log()for diagnostic messages - Use
-ErrorAction SilentlyContinuewhen probing for app presence - Wrap your entire script in
try/catchfor safety - Keep detection fast (under 5 seconds)
- Detect locally (registry, files, services)
DON'T
- Never use
exit(in any form) - Never use
Write-Output(corrupts return value) - Never use
throwto mean "not installed" - Never rely on network/API calls for detection
- Never use unbounded loops
- Never modify system state (env vars, registry writes, stopping services)
- Never return objects, arrays, or integers
Recommended Template
Shell App
try {
$context.Log("Detecting MyApp")
# Primary: Registry check
$regPath = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{GUID}"
$app = Get-ItemProperty $regPath -ErrorAction SilentlyContinue
if ($app -and $app.DisplayVersion) {
$context.Log("Found version: $($app.DisplayVersion)")
return [string]$app.DisplayVersion
}
# Fallback: File check
$exePath = "C:\Program Files\MyApp\myapp.exe"
if (Test-Path $exePath) {
$version = (Get-Item $exePath).VersionInfo.ProductVersion
$context.Log("Found binary version: $version")
return [string]$version
}
$context.Log("MyApp not found")
return $null
}
catch {
$context.Log("Detection error: $($_.Exception.Message)")
return $null
}Winget App (Custom Detection)
try {
$context.Log("Detecting app (target: $($context.TargetVersion))")
$regPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{GUID}",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{GUID}"
)
foreach ($path in $regPaths) {
$app = Get-ItemProperty $path -ErrorAction SilentlyContinue
if ($app -and $app.DisplayVersion) {
$context.Log("Detected: $($app.DisplayVersion)")
return [string]$app.DisplayVersion
}
}
$context.Log("Not detected")
return $null
}
catch {
$context.Log("Error: $($_.Exception.Message)")
return $null
}Available Context
| Property/Method | Shell Apps | Winget Apps | Description |
|---|---|---|---|
$context.TargetVersion |
Yes | Yes | The version being installed (or "not_specified") |
$context.DetectedVersion |
Yes | No | Previously detected version (if known) |
$context.Versions |
Yes | No | List of all defined version names |
$context.Log("message") |
Yes | Yes | Write to deployment logs |
$context.GetAttachedBinary() |
Yes | No | Download the app's binary package |
Need help?
Raise a support ticket for this item.
Comments (0 comments)