# Copyright (c) 2026, R. W. Rodolico # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # windows_software.ps1 # Installed software from the registry Uninstall keys (never Win32_Product). # Sensor contract: unix/sensors/Documentation/README.md; keys: Documentation/Spec.md 7.6 # Must stay Windows PowerShell 5.1 compatible and pure ASCII. # # Version 1.0.0 20261018 Claude # Initial version (bug 211, stage W2) Import-Module ([IO.Path]::Combine($PSScriptRoot, '..', 'lib', 'SysinfoSensor.psm1')) Test-SysinfoWindows Invoke-SysinfoSensor { $uninstall = 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' $seen = @{} foreach ($view in [Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32) { $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, $view) try { $root = $base.OpenSubKey($uninstall) if ($null -eq $root) { continue } try { foreach ($subName in $root.GetSubKeyNames()) { $sub = $root.OpenSubKey($subName) if ($null -eq $sub) { continue } try { $display = ([string]$sub.GetValue('DisplayName', '')).Trim() if ($display -eq '') { continue } if ([string]$sub.GetValue('SystemComponent', '0') -eq '1') { continue } if (([string]$sub.GetValue('ParentKeyName', '')) -ne '') { continue } if (@('Update', 'Hotfix', 'Security Update') -contains ([string]$sub.GetValue('ReleaseType', ''))) { continue } if ($seen.ContainsKey($display)) { continue } $seen[$display] = $true Write-SysinfoFact -Path @('software', $display, 'version') -Value $sub.GetValue('DisplayVersion', '') Write-SysinfoFact -Path @('software', $display, 'description') -Value $sub.GetValue('Publisher', '') } finally { $sub.Close() } } } finally { $root.Close() } } finally { $base.Close() } } }