In this blog post, we’re going to explore an easy yet incredibly powerful detection script for applications on Windows devices. If you’ve ever tried to detect whether an application is installed on a Windows machine—whether you’re deploying a new app, troubleshooting an update, or just auditing your environment—you’ve likely faced some challenges. Finding a reliable, efficient, and repeatable method to check for an application’s existence (and even its version) can feel like searching for a needle in a haystack.
You may have experimented with tools like Get-WmiObject
, Get-CimInstance
, or even dabbled in registry lookups, only to discover that none of these approaches quite hit the mark in every scenario. They often require extra customization, and let’s face it—sometimes they’re not as intuitive as we’d like.
That’s where PowerShell comes in. In this post, we’ll break down a script designed to simplify and streamline the detection process. Whether you’re an IT admin, a developer, or simply someone who loves automating tasks, this script will help you quickly determine if an application is installed on a Windows device—and even check its version if needed.
Ready to dive in? Let’s get started!
Table of Contents
How to get installed apps?
When it comes to detecting installed applications on Windows devices, there are several approaches you can take, each with its own strengths and use cases. One of the most common methods is leveraging PowerShell cmdlets like Get-WmiObject
or Get-CimInstance
, which allow you to query the system for details about installed software. These cmdlets can pull data from the Win32_Product class or other system management resources, giving you insights into application names, publishers, and sometimes even version numbers. While these are powerful tools, they can be a bit slow and may not always return the full picture, especially for applications that don’t register themselves using standard Windows Installer processes.
Another effective approach involves querying the Windows registry. The registry holds critical information about installed applications, typically under locations like HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
and HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall
. By navigating these registry paths, you can uncover a wealth of details about both 32-bit and 64-bit applications installed on the device. However, working with the registry requires precision—mistakes can lead to unintended consequences—and it might not capture apps installed through modern package managers like Winget or the Microsoft Store.
Speaking of modern package managers, tools like Winget are increasingly becoming a go-to option for discovering and managing installed applications. Winget’s list
command allows you to quickly see what’s installed and even compare versions for updates. Similarly, the Microsoft Store and Universal Windows Platform (UWP) apps can be queried using dedicated PowerShell modules or APIs, offering a more streamlined approach for modern app management.
Each of these methods has its pros and cons, and the right one often depends on your specific scenario. Whether you’re scripting for automation, troubleshooting an issue, or building a detection mechanism, understanding the tools available is the first step in crafting the ultimate solution. In the next sections, we’ll explore how to combine these approaches into a custom PowerShell script that’s both versatile and efficient.
Why use PowerShell and the Registry?
After exploring the various methods to retrieve installed applications, one key question remains: What is the best way to do it? The answer largely depends on the type of application you’re dealing with. For example, detecting traditional Win32 applications often involves querying the registry, while modern apps, such as Microsoft Store or AppX applications, may require a different approach. However, one thing is certain: using PowerShell, coupled with the right commands, provides a versatile and powerful way to achieve excellent results regardless of the application type.
When it comes to traditional applications, the Windows registry is an invaluable resource. By reading data stored under the following two registry keys, you can access comprehensive details about installed software:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
These keys contain entries for both 64-bit and 32-bit applications, respectively. From these locations, you can extract critical details such as the application’s display name, version, publisher, installation location, and even uninstall commands. This makes the registry a one-stop shop for gathering information about most installed software on a device. PowerShell makes this process seamless, allowing you to efficiently query these keys, filter the results, and use the data in other automation tasks.
But what about newer application types like Microsoft Store or AppX apps? These modern applications are not typically registered in the same way as traditional Win32 programs, so the registry won’t provide all the details you need. Fortunately, PowerShell comes to the rescue with commands like Get-AppxPackage
. This cmdlet allows you to retrieve information about all installed AppX and Microsoft Store apps on the system, including their package name, publisher, version, and install location.
Using PowerShell to query both traditional registry-based applications and modern AppX applications provides a unified and powerful detection mechanism. You can combine the results from both approaches to create a comprehensive inventory of installed software on a device. Additionally, PowerShell’s scripting capabilities enable you to automate this process, tailor the output to your needs, and integrate it into broader management workflows.
By leveraging the strengths of PowerShell and the registry, you can ensure no application—whether classic or modern—escapes detection. This dual approach not only simplifies the process but also provides the flexibility needed to handle various application types with confidence.
The script that does everything – THE application detection script
You can find the script in my GitHub repo too.
# Initialize a variable for the log file location
$LogFilePath = "C:\Logs\ApplicationDetection.log"
# Check if the log file exists, and if so, delete it
if (Test-Path -Path $LogFilePath) {
try {
Remove-Item -Path $LogFilePath -Force -ErrorAction Stop
} catch {
Write-Error "Failed to delete existing log file. Error: $_"
exit 1
}
}
# Start transcribing to log the process
try {
Start-Transcript -Path $LogFilePath -Append -ErrorAction Stop
} catch {
Write-Error "Failed to start logging. Error: $_"
exit 1
}
# Initialize the variable for the application's display name
$AppDisplayName = "your app name goes here"
# Boolean variable for version check
$CheckVersion = $false # Set to $false if version check is not required
$TargetVersion = [System.Version]"1.0.0.0" # Define the target version as a System.Version object
# Function to parse registry for application details
function Get-InstalledAppDetails {
param (
[string]$DisplayName,
[bool]$CheckVersion = $false,
[System.Version]$TargetVersion = $null
)
# Define registry paths for both 32-bit and 64-bit applications
$RegistryPaths = @(
"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall",
"HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall"
)
$AppDetails = @()
foreach ($Path in $RegistryPaths) {
try {
# Get all subkeys under the registry path
$SubKeys = Get-ChildItem -Path $Path -ErrorAction Stop
foreach ($Key in $SubKeys) {
$AppInfo = Get-ItemProperty -Path $Key.PSPath -ErrorAction Stop
if ($AppInfo.DisplayName -like "*$DisplayName*") {
$AppDetails += [PSCustomObject]@{
DisplayName = $AppInfo.DisplayName
Version = $AppInfo.DisplayVersion
UninstallCmd = $AppInfo.UninstallString
Publisher = $AppInfo.Publisher
MSIProductCode = $AppInfo.PSChildName
}
}
}
} catch {
Write-Warning "Failed to query registry path: $Path. Error: $_"
}
}
return $AppDetails
}
# Function to check MSIX and AppX packages
function Get-AppXPackageDetails {
param (
[string]$DisplayName
)
$AppXDetails = @()
try {
$Packages = Get-AppxPackage -AllUsers -ErrorAction Stop | Where-Object { $_.Name -like "*$DisplayName*" }
foreach ($Package in $Packages) {
$AppXDetails += [PSCustomObject]@{
DisplayName = $Package.Name
Version = $Package.Version
Publisher = $Package.Publisher
InstallPath = $Package.InstallLocation
}
}
} catch {
Write-Warning "Failed to query AppX packages. Error: $_"
}
return $AppXDetails
}
# Main logic for detection
try {
$AppFound = $false
$RegistryApps = Get-InstalledAppDetails -DisplayName $AppDisplayName -CheckVersion $CheckVersion -TargetVersion $TargetVersion
$AppXApps = Get-AppXPackageDetails -DisplayName $AppDisplayName
if ($RegistryApps -or $AppXApps) {
$AppFound = $true
Write-Output "Application found. Details:"
$RegistryApps | ForEach-Object { Write-Output $_ }
$AppXApps | ForEach-Object { Write-Output $_ }
if ($CheckVersion) {
foreach ($App in $RegistryApps) {
# Cast the version to System.Version for accurate comparison
$InstalledVersion = [System.Version]$App.Version
if ($InstalledVersion -eq $TargetVersion) {
Write-Output "Version check passed for Registry App: $($App.DisplayName)"
} else {
Write-Output "Version mismatch for Registry App: $($App.DisplayName). Expected: $TargetVersion, Found: $InstalledVersion"
}
}
foreach ($App in $AppXApps) {
# Cast the version to System.Version for accurate comparison
$InstalledVersion = [System.Version]$App.Version
if ($InstalledVersion -eq $TargetVersion) {
Write-Output "Version check passed for AppX App: $($App.DisplayName)"
} else {
Write-Output "Version mismatch for AppX App: $($App.DisplayName). Expected: $TargetVersion, Found: $InstalledVersion"
}
}
}
} else {
Write-Output "Application not found."
}
} catch {
Write-Error "An unexpected error occurred during the detection process. Error: $_"
} finally {
# Stop the transcription/logging process
try {
Stop-Transcript -ErrorAction Stop
} catch {
Write-Error "Failed to stop logging. Error: $_"
}
}
# Return the detection result
return $AppFound
Script explanation and how to use
Overview of the Script
The script is designed to detect the presence of an application on a Windows device. It uses two primary methods:
- Registry Queries:
It scans both 32-bit and 64-bit registry paths to find details about traditional Win32 applications. This includes information like the application’s display name, version, publisher, and uninstall command. - MSIX and AppX Detection:
It leverages theGet-AppxPackage
PowerShell cmdlet to detect modern Microsoft Store or AppX-based applications.
Additionally, the script includes:
- Logging to track the process step by step.
- Optional version checks to validate whether the installed application’s version matches a defined target version.
- Comprehensive error handling to ensure stability during execution.
How the Script Works
Here’s a detailed explanation of each major section:
- Log Initialization:
- The script begins by defining a file path for the log file (
$LogFilePath
). - If a previous log file exists, it deletes it to ensure that the new log starts fresh.
- A transcription process is started to capture all script activities into the log file.
- The script begins by defining a file path for the log file (
- Application Display Name and Version Check Variables:
- The
$AppDisplayName
variable is used to define the name of the application you want to detect. Replace"Your Application Name Here"
with the actual display name of the app. - The
$CheckVersion
variable is a Boolean value ($true
or$false
) that determines whether the script will check the installed application’s version. - If
$CheckVersion
istrue
, the script compares the installed application’s version against the$TargetVersion
variable, which you can customize.
- The
- Registry Queries:
- The script scans two registry paths for installed applications:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
(64-bit apps)HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall
(32-bit apps)
- It looks for entries where the display name matches or partially matches
$AppDisplayName
. - For each match, it extracts details like display name, version, publisher, uninstall command, and MSI product code.
- The script scans two registry paths for installed applications:
- MSIX and AppX Detection:
- For modern Microsoft Store or AppX-based applications, the script uses
Get-AppxPackage -AllUsers
. - It searches for applications whose package name matches
$AppDisplayName
. - The script captures details like the package name, version, publisher, and installation path.
- For modern Microsoft Store or AppX-based applications, the script uses
- Version Comparison:
- If
$CheckVersion
is set totrue
, the script casts both the installed version and the target version toSystem.Version
objects for accurate comparison. - It checks if the installed version matches
$TargetVersion
. If there’s a mismatch, the script logs the discrepancy.
- If
- Error Handling:
- Each major section of the script is wrapped in
try-catch
blocks with-ErrorAction Stop
. This ensures that any unexpected issues during execution are handled gracefully and logged.
- Each major section of the script is wrapped in
- Return Value:
- If the application is found, the script outputs the application details and a
true
result. - If the application is not found, it outputs a
false
result.
- If the application is found, the script outputs the application details and a
How to Use the Script
- Define the Application Details:
- Set the
$AppDisplayName
variable to the name of the application you want to detect. For example:powershellCopy code$AppDisplayName = "Google Chrome"
- If you want to perform a version check, set
$CheckVersion
to$true
and define the target version in$TargetVersion
using semantic versioning. For example:
- Set the
$CheckVersion = $true
$TargetVersion = [System.Version]"114.0.0"
- Run the Script:
- Save the script as a
.ps1
file, such asDetectApp.ps1
. - Open PowerShell as an administrator.
- Execute the script by running:powershellCopy code
.\DetectApp.ps1
- Save the script as a
- Check the Log File:
- The script generates a detailed log of the process at the location specified by
$LogFilePath
. By default, this is set toC:\Logs\ApplicationDetection.log
. - Review the log file for insights into what was detected, any errors encountered, and other useful details.
- The script generates a detailed log of the process at the location specified by
- Interpret the Results:
- If the application is found, the script will display its details (name, version, publisher, etc.) in the PowerShell console.
- If the application matches the specified target version (when
$CheckVersion
is enabled), a success message is logged and displayed. - If the application is not found or the version doesn’t match, the script will indicate this in both the console and the log file.
- Customize as Needed:
- You can modify the script to add additional registry paths, filter specific app types, or integrate it with larger automation workflows.
How to customize? My suggestions
As presented, this script is a foundational yet highly flexible tool for detecting applications on Windows devices. Its straightforward design makes it easy to adapt for a wide range of use cases. Whether you’re using it in Microsoft Intune Remediations, as a custom detection script for application deployments, or in other automation workflows, you can easily modify it to suit your specific requirements.
Below, I’ll share some suggestions for customization and enhancement, highlighting key areas where adjustments can make the script even more tailored and effective.
1. Strictly Define the Application Name
By default, the script uses the -like
operator to perform a partial match when searching for the application’s display name. While this is helpful for detecting applications with inconsistent naming conventions, it may result in unintended matches. For example, searching for “Chrome” might return multiple results like “Google Chrome” and “Chrome Remote Desktop.”
To make the script more precise:
- Replace the
-like
operator with the-eq
operator for an exact match. - Update the
$AppDisplayName
variable to exactly match the application’s display name as it appears in the registry or AppX package list.
Here’s how you can modify the relevant line:
if ($AppInfo.DisplayName -eq $DisplayName) {
# Strict match for the application name
}
This ensures that the script targets only the intended application, reducing false positives and making it more reliable in production environments.
2. Customize Exit Codes for Intune Detection Scripts
When using this script in Intune’s custom detection scripts, the exit codes are critical. Intune relies on specific return codes to determine the result of the detection:
- Exit Code 0: The application is detected (success).
- Exit Code 1: The application is not detected (failure).
Currently, the script outputs true
or false
based on whether the application is found. To align it with Intune’s requirements, you can modify the script to return the appropriate exit codes:
# Exit codes for Intune detection
if ($AppFound) {
exit 0 # Application detected
} else {
exit 1 # Application not detected
}
3. Add Parameters for Flexibility
To make the script more dynamic and reusable, consider adding input parameters. This allows you to define application details and detection preferences directly when running the script, rather than hardcoding them.
param (
[string]$AppDisplayName,
[System.Version]$TargetVersion = $null,
[bool]$CheckVersion = $false
)
# Run the script with parameters
.\DetectApp.ps1 -AppDisplayName "Google Chrome" -TargetVersion "114.0.0" -CheckVersion $true
Closing Remarks
In this blog, we explored the art of crafting a powerful custom detection script using PowerShell. From understanding the methods to retrieve installed applications to leveraging the registry and modern PowerShell cmdlets, we walked through how to detect applications reliably, check their versions, and log the entire process effectively. We also discussed how to tailor the script for specific use cases, such as Microsoft Intune or custom workflows, highlighting the immense flexibility and potential of PowerShell in system management.
With this script as a foundation, you can easily customize it to meet the unique requirements of your environment, whether you’re managing legacy apps, modern MSIX packages, or enterprise-grade automation. The possibilities are only limited by your creativity and the needs of your systems.
As we conclude, remember:
“Automation is not about replacing effort, but amplifying it. The more you empower your tools, the more they empower you.”
Happy scripting!
References
Other interesting topics to check