Created
August 12, 2025 08:15
-
-
Save Sid110307/7c17053be108620ca83b7ef734781139 to your computer and use it in GitHub Desktop.
List all installed apps, programs, features, etc.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [CmdletBinding()] | |
| param() | |
| function Test-Cmd { param([string]$Name) [bool](Get-Command $Name -ErrorAction SilentlyContinue) } | |
| function Test-Try { param([scriptblock]$Block) try { & $Block } catch { } } | |
| $results = New-Object System.Collections.Generic.List[object] | |
| $seenKeys = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase) | |
| $dupeCount = 0 | |
| function New-DupeKey { | |
| param([string]$Source, [string]$Name, [string]$Version, [string]$Identifier) | |
| if (-not [string]::IsNullOrWhiteSpace($Identifier)) { return "id::$Identifier" } | |
| if (-not [string]::IsNullOrWhiteSpace($Version)) { return "nv::$Name|$Version" } | |
| if (-not [string]::IsNullOrWhiteSpace($Name)) { return "n::$Name" } | |
| return "src::$Source::(blank)" | |
| } | |
| function Add-Item { | |
| param( | |
| [string]$Source, [string]$Name, [string]$Version, [string]$Publisher, | |
| [string]$InstallDate, [string]$Location, [string]$Identifier, [hashtable]$Extra | |
| ) | |
| $key = New-DupeKey -Source $Source -Name $Name -Version $Version -Identifier $Identifier | |
| if (-not $seenKeys.Add($key)) { | |
| $script:dupeCount++ | |
| return | |
| } | |
| $obj = [PSCustomObject]@{ | |
| Source = $Source | |
| Name = $Name | |
| Version = $Version | |
| Publisher = $Publisher | |
| InstallDate = $InstallDate | |
| Location = $Location | |
| Identifier = $Identifier | |
| Extra = if ($Extra) { $Extra } else { $null } | |
| } | |
| [void]$results.Add($obj) | |
| } | |
| Write-Output "=== Inventory started $(Get-Date) ===" | |
| Write-Output "PS Version: $($PSVersionTable.PSVersion)" | |
| # -------- 1) Win32 apps via Registry -------- | |
| function Get-UninstallKeyItems { | |
| param([string[]]$Roots) | |
| foreach ($root in $Roots) { | |
| if (Test-Path $root) { | |
| Get-ChildItem $root -ErrorAction SilentlyContinue | ForEach-Object { | |
| $p = Get-ItemProperty $_.PsPath -ErrorAction SilentlyContinue | |
| if ($p.DisplayName -and ($p.SystemComponent -ne 1) -and ($p.ReleaseType -ne 'Update') -and ($p.ParentKeyName -ne 'OperatingSystem')) { | |
| $arguments = @{ | |
| Source = "Win32-Registry" | |
| Name = $p.DisplayName | |
| Version = $p.DisplayVersion | |
| Publisher = $p.Publisher | |
| InstallDate = $p.InstallDate | |
| Location = $p.InstallLocation | |
| Identifier = $p.PSChildName | |
| Extra = @{ | |
| UninstallString = $p.UninstallString | |
| InstallSource = $p.InstallSource | |
| URLInfoAbout = $p.URLInfoAbout | |
| } | |
| } | |
| Add-Item @arguments | |
| } | |
| } | |
| } | |
| } | |
| } | |
| Write-Output "[1/6] Registry..." | |
| Get-UninstallKeyItems -Roots @( | |
| 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall', | |
| 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall', | |
| 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall' | |
| ) | |
| # -------- 2) Appx / Microsoft Store apps -------- | |
| Write-Output "[2/6] Appx..." | |
| Test-Try { Get-AppxPackage -AllUsers -ErrorAction SilentlyContinue } | ForEach-Object { | |
| Add-Item -Source "Appx" -Name $_.Name -Version $_.Version -Publisher $_.Publisher ` | |
| -InstallDate $null -Location $_.InstallLocation -Identifier $_.PackageFullName ` | |
| -Extra @{ Architecture = $_.Architecture; IsFramework = $_.IsFramework } | |
| } | |
| # -------- 3) Windows Optional Features -------- | |
| Write-Output "[3/6] Windows Optional Features..." | |
| Test-Try { Get-WindowsOptionalFeature -Online } | | |
| Where-Object State -eq 'Enabled' | | |
| ForEach-Object { | |
| Add-Item -Source "WindowsFeature" -Name $_.FeatureName -Version $null -Publisher $null ` | |
| -InstallDate $null -Location $null -Identifier $_.FeatureName -Extra @{ State = $_.State } | |
| } | |
| # -------- 4) Windows Capabilities -------- | |
| Write-Output "[4/6] Windows Capabilities..." | |
| Test-Try { Get-WindowsCapability -Online } | | |
| Where-Object State -eq 'Installed' | | |
| ForEach-Object { | |
| Add-Item -Source "WindowsCapability" -Name $_.Name -Version $_.Version -Publisher $null ` | |
| -InstallDate $null -Location $null -Identifier $_.Name -Extra @{ State = $_.State } | |
| } | |
| # -------- 5) WinGet packages -------- | |
| Write-Output "[5/6] WinGet..." | |
| if (Test-Cmd "winget") { | |
| $wg = Test-Try { winget list --accept-source-agreements --output json 2>$null | ConvertFrom-Json } | |
| if ($wg) { | |
| foreach ($p in $wg) { | |
| Add-Item -Source "WinGet" -Name $p.Name -Version $p.Version -Publisher $p.Source ` | |
| -InstallDate $null -Location $null -Identifier $p.Id ` | |
| -Extra @{ Available = $p.Available; Scope = $p.Scope } | |
| } | |
| } | |
| else { | |
| winget list --accept-source-agreements 2>$null | | |
| Select-Object -Skip 2 | ForEach-Object { | |
| $line = ($_ -split '\s{2,}', 4) | |
| if ($line.Length -ge 2) { | |
| Add-Item -Source "WinGet" -Name $line[0] -Version $line[1] -Publisher $null ` | |
| -InstallDate $null -Location $null -Identifier $line[2] -Extra @{} | |
| } | |
| } | |
| } | |
| } | |
| else { Write-Output " WinGet not found." } | |
| # -------- 6) Chocolatey packages -------- | |
| Write-Output "[6/6] Chocolatey..." | |
| if (Test-Cmd "choco") { | |
| Test-Try { choco list -lo --limit-output --no-color --source="'Chocolatey'" --format=json 2>$null | ConvertFrom-Json } | | |
| ForEach-Object { | |
| foreach ($p in $_.packages) { | |
| Add-Item -Source "Chocolatey" -Name $p.title -Version $p.version -Publisher "Chocolatey" ` | |
| -InstallDate $null -Location $null -Identifier $p.id -Extra @{} | |
| } | |
| } | |
| } | |
| else { Write-Output " Chocolatey not found." } | |
| Write-Output "[OUTPUT] Writing Excel workbook..." | |
| if (-not (Get-Module -ListAvailable -Name ImportExcel)) { | |
| try { Import-Module ImportExcel -ErrorAction Stop } | |
| catch { | |
| Write-Warning "✖ The 'ImportExcel' module is required. Install with: Install-Module ImportExcel -Scope CurrentUser" | |
| return | |
| } | |
| } | |
| $stamp = Get-Date -Format "yyyyMMdd_HHmmss" | |
| $baseXlsx = Join-Path (Get-Location) "inventory_$stamp.xlsx" | |
| function ConvertTo-Row { | |
| param($x) | |
| [PSCustomObject]@{ | |
| Source = $x.Source | |
| Name = $x.Name | |
| Version = $x.Version | |
| Publisher = $x.Publisher | |
| InstallDate = $x.InstallDate | |
| Location = $x.Location | |
| Identifier = $x.Identifier | |
| ExtraJson = if ($x.Extra) { $x.Extra | ConvertTo-Json -Depth 6 -Compress } else { $null } | |
| } | |
| } | |
| $allRows = $results | ForEach-Object { ConvertTo-Row $_ } | |
| $xl = $allRows | Export-Excel -Path $baseXlsx -WorksheetName 'All' -TableName 'All' ` | |
| -AutoSize -AutoFilter -FreezeTopRow -BoldTopRow -ClearSheet -PassThru | |
| $wsAll = $xl.Workbook.Worksheets["All"] | |
| Set-ExcelRange -Worksheet $wsAll -Range $wsAll.Dimension.Address -HorizontalAlignment Left | |
| $allRows | Group-Object Source | ForEach-Object { | |
| $sheetName = ($_.Name -replace '[^\w\.-]', '_') | |
| if ($sheetName.Length -gt 31) { $sheetName = $sheetName.Substring(0, 31) } | |
| $tableName = 'T_' + ($sheetName -replace '[^\w]', '_') | |
| if ($tableName.Length -gt 31) { $tableName = $tableName.Substring(0, 31) } | |
| $_.Group | Export-Excel -ExcelPackage $xl -WorksheetName $sheetName -TableName $tableName ` | |
| -AutoSize -AutoFilter -FreezeTopRow -BoldTopRow -ClearSheet -PassThru | Out-Null | |
| $ws = $xl.Workbook.Worksheets[$sheetName] | |
| Set-ExcelRange -Worksheet $ws -Range $ws.Dimension.Address -HorizontalAlignment Left | |
| } | |
| Close-ExcelPackage -ExcelPackage $xl | |
| $results | Group-Object Source | Sort-Object Name | Format-Table Name, Count -AutoSize | |
| Write-Output "Done. Workbook created at: $baseXlsx" | |
| Write-Output "=== Inventory completed $(Get-Date) ===" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment