Public/Get-EFInstalledSoftware.ps1

function Get-EFInstalledSoftware {
    <#
    .SYNOPSIS
    Gets installed software from Windows uninstall registry entries.
 
    .DESCRIPTION
    Reads machine and/or current-user uninstall keys. It deliberately avoids the
    Win32_Product class, which can be slow and trigger Windows Installer consistency
    checks. Duplicate entries are normalized and removed.
 
    .PARAMETER Scope
    Selects Machine, CurrentUser, or All registry scopes. Under Intune, scheduled tasks,
    or another SYSTEM host, CurrentUser means the SYSTEM profile rather than the signed-in
    interactive user.
 
    .PARAMETER IncludeSystemComponents
    Includes entries marked as system components by their installer.
 
    .PARAMETER Name
    Filters display names with PowerShell wildcard syntax.
 
    .PARAMETER Publisher
    Filters publishers with PowerShell wildcard syntax.
 
    .PARAMETER Architecture
    Filters x64, x86, or per-user entries.
 
    .PARAMETER IncludeUninstallCommand
    Includes uninstall command lines. They are omitted by default because they can
    contain organization-specific paths or installer tokens.
 
    .EXAMPLE
    Get-EFInstalledSoftware | Where-Object Name -like '*7-Zip*'
 
    .EXAMPLE
    Get-EFInstalledSoftware -Name '*Microsoft*' -Architecture x64
 
    .OUTPUTS
    EndpointForge.InstalledSoftware
    #>

    [CmdletBinding()]
    param(
        [ValidateSet('Machine', 'CurrentUser', 'All')]
        [string]$Scope = 'All',

        [switch]$IncludeSystemComponents,

        [ValidateNotNullOrEmpty()]
        [string]$Name = '*',

        [ValidateNotNullOrEmpty()]
        [string]$Publisher = '*',

        [ValidateSet('All', 'x64', 'x86', 'User')]
        [string]$Architecture = 'All',

        [switch]$IncludeUninstallCommand
    )

    $null = Test-EFWindows -Throw
    $locations = @()
    if ($Scope -in @('Machine', 'All')) {
        $locations += [pscustomobject]@{ Path = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'; Scope = 'Machine'; Architecture = 'x64' }
        $locations += [pscustomobject]@{ Path = 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'; Scope = 'Machine'; Architecture = 'x86' }
    }
    if ($Scope -in @('CurrentUser', 'All')) {
        $locations += [pscustomobject]@{ Path = 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*'; Scope = 'CurrentUser'; Architecture = 'User' }
    }

    $items = foreach ($location in $locations) {
        foreach ($entry in @(Get-ItemProperty -Path $location.Path -ErrorAction SilentlyContinue)) {
            $displayName = [string](Get-EFPropertyValue -InputObject $entry -Name 'DisplayName')
            if ([string]::IsNullOrWhiteSpace($displayName)) {
                continue
            }
            $entryPublisher = [string](Get-EFPropertyValue -InputObject $entry -Name 'Publisher')
            if ($displayName -notlike $Name -or $entryPublisher -notlike $Publisher) {
                continue
            }
            if ($Architecture -ne 'All' -and $location.Architecture -ne $Architecture) {
                continue
            }
            if (-not $IncludeSystemComponents -and [int](Get-EFPropertyValue -InputObject $entry -Name 'SystemComponent' -Default 0) -eq 1) {
                continue
            }

            $installDate = $null
            $rawInstallDate = [string](Get-EFPropertyValue -InputObject $entry -Name 'InstallDate')
            if ($rawInstallDate -match '^\d{8}$') {
                try { $installDate = [DateTime]::ParseExact($rawInstallDate, 'yyyyMMdd', [Globalization.CultureInfo]::InvariantCulture) }
                catch { $installDate = $null }
            }
            $estimatedSize = Get-EFPropertyValue -InputObject $entry -Name 'EstimatedSize'

            [pscustomobject]@{
                PSTypeName       = 'EndpointForge.InstalledSoftware'
                Name             = $displayName
                Version          = [string](Get-EFPropertyValue -InputObject $entry -Name 'DisplayVersion')
                Publisher        = $entryPublisher
                InstallDate      = $installDate
                InstallLocation  = [string](Get-EFPropertyValue -InputObject $entry -Name 'InstallLocation')
                Scope            = $location.Scope
                Architecture     = $location.Architecture
                ProductCode      = [string](Get-EFPropertyValue -InputObject $entry -Name 'PSChildName')
                EstimatedSizeMB  = if ($null -ne $estimatedSize) { [math]::Round([double]$estimatedSize / 1024, 2) } else { $null }
                UninstallString  = if ($IncludeUninstallCommand) { [string](Get-EFPropertyValue -InputObject $entry -Name 'UninstallString') } else { $null }
            }
        }
    }

    $seen = @{}
    @($items | Sort-Object Name, Version, Scope, Architecture) | Where-Object {
        $key = '{0}|{1}|{2}|{3}|{4}' -f $_.Name, $_.Version, $_.Publisher, $_.Scope, $_.Architecture
        if ($seen.ContainsKey($key)) { return $false }
        $seen[$key] = $true
        return $true
    }
}