Application detection script-A powerful way

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-WmiObjectGet-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!

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:

  1. 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.
  2. MSIX and AppX Detection:
    It leverages the Get-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:

  1. 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.
  2. 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 is true, the script compares the installed application’s version against the $TargetVersion variable, which you can customize.
  3. 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.
  4. 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.
  5. Version Comparison:
    • If $CheckVersion is set to true, the script casts both the installed version and the target version to System.Version objects for accurate comparison.
    • It checks if the installed version matches $TargetVersion. If there’s a mismatch, the script logs the discrepancy.
  6. 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.
  7. 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.
How to Use the Script
  1. 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:
$CheckVersion = $true 

$TargetVersion = [System.Version]"114.0.0"
  1. Run the Script:
    • Save the script as a .ps1 file, such as DetectApp.ps1.
    • Open PowerShell as an administrator.
    • Execute the script by running:powershellCopy code.\DetectApp.ps1
  2. Check the Log File:
    • The script generates a detailed log of the process at the location specified by $LogFilePath. By default, this is set to C:\Logs\ApplicationDetection.log.
    • Review the log file for insights into what was detected, any errors encountered, and other useful details.
  3. 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.
  4. 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

Leave a Reply

Your email address will not be published. Required fields are marked *