Softwareinventur, einfaches PowerShell inkl. GUI (ohne Adminrechte)

🧾 PowerShell-GUI zur Software-Inventar (Versions-Informationen)

Zweck

Das Skript dient der benutzerfreundlichen Erfassung und dem Export installierter Software-Stände auf einem Windows-System – ohne administrative Rechte. Es bietet eine grafische Oberfläche (GUI) zur Steuerung der Ausgabe und ermöglicht gezielte Filterung sowie den Export als CSV-Datei für die Weiterverarbeitung in Excel.


Funktionsumfang

  • GUI mit Checkbox-Filter für Browser-Anwendungen (Chrome, Firefox, Edge, Opera, Brave)
  • Auswertung der Registry-Zweige:
    • HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
    • HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*
    • HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*
  • Export als UTF-8-kodierte CSV-Datei mit dynamischem Trennzeichen:
    • ; für deutschsprachige Systeme
    • , für andere Sprachregionen
  • Installationsdatum:
    • Primär aus Registry (InstallDate)
    • Sekundär über Erstellungsdatum der zugehörigen .exe-Datei (Fallback)
    • Format: yyyyMMdd (nur Tag)
  • Dateiname mit Zeitstempel: InstalledSoftware_YYYYMMDD_HHmmss.csv
  • ein Logfile %Temp%  SoftwareInventory_Log_Datum_UhrZeit.txt
  • Automatisches Öffnen der CSV-Datei nach Export

Systemvoraussetzungen

  • Windows-Betriebssystem mit PowerShell 5.x oder höher
  • Schreibzugriff auf %TEMP%-Verzeichnis
  • Keine Administratorrechte erforderlich
  • .NET Framework für Windows Forms (standardmäßig vorhanden)

Einschränkungen

  • Installationsdatum ist nicht bei allen Anwendungen zuverlässig über die Registry verfügbar.
  • Fallback über .exe-Dateien kann fehlschlagen, wenn Pfade nicht standardisiert sind.
  • Portable oder benutzerdefinierte Installationen (z. B. Opera) liefern ggf. keine verwertbaren Daten.

Empfohlene Anwendung

  • Lokale Softwareinventarisierung durch Endanwender
  • Vorbereitung für Audits oder interne Dokumentation
  • Filterung nach bestimmten Anwendungstypen (z. B. Browser)

Ist eine Frage aufgetaucht? Dann erstellen Sie bitte direkt hier ein Ticket – wir kümmern uns darum!
Je genauer Sie Ihr Anliegen beschreiben, desto schneller können wir Ihnen weiterhelfen.


Script-Inhalt Stand 8.10.2025 v3 (in Freizeit entstanden mit Unterstützung Chat-GPT optimized by Grok)
# Erforderliche .NET-Assemblies laden
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing

# Execution Policy prüfen
$currentPolicy = Get-ExecutionPolicy
if ($currentPolicy -eq 'Restricted' -or $currentPolicy -eq 'AllSigned') {
    [System.Windows.Forms.MessageBox]::Show(
        "Die aktuelle PowerShell-Ausführungsrichtlinie ($currentPolicy) verhindert möglicherweise die Ausführung dieses Skripts.`nBitte wenden Sie sich an den IT-Support oder passen Sie die Richtlinie an (z. B. mit 'Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned').",
        "Ausführungsrichtlinie",
        [System.Windows.Forms.MessageBoxButtons]::OK,
        [System.Windows.Forms.MessageBoxIcon]::Warning
    )
    return
}

# Hostname inkl. Domain, Punkte durch Bindestriche ersetzen
$hostname = [System.Net.Dns]::GetHostName().Replace(".", "-")

# Log-Datei für Debugging
$logPath = [System.IO.Path]::Combine($env:TEMP, "SoftwareInventory_Log_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt")
function Write-Log {
    param ($Message)
    $logMessage = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message"
    Write-Output $logMessage | Out-File -FilePath $logPath -Append -Encoding UTF8
}

# Fenster erstellen
$form = New-Object System.Windows.Forms.Form
$form.Text = "Softwareliste Export"
$form.Size = New-Object System.Drawing.Size(450, 220)
$form.Topmost = $true # Im Vordergrund
$form.StartPosition = "Manual"
$form.Location = New-Object System.Drawing.Point(100, 100) # Feste Position: 100px von links, 100px von oben
$form.Add_Shown({ $form.Activate(); $form.BringToFront() }) # Sicherstellen, dass das Fenster im Fokus ist
Write-Log "GUI wird an Position (100,100) auf primärem Monitor angezeigt"

# Label für Execution Policy
$lblPolicy = New-Object System.Windows.Forms.Label
$lblPolicy.Text = "Aktuelle Execution Policy: $currentPolicy"
$lblPolicy.Location = New-Object System.Drawing.Point(20, 10)
$lblPolicy.Size = New-Object System.Drawing.Size(400, 20)
$form.Controls.Add($lblPolicy)

# Checkbox für Browser-Filter
$chkBrowserOnly = New-Object System.Windows.Forms.CheckBox
$chkBrowserOnly.Text = "Nur Browser-Anwendungen anzeigen"
$chkBrowserOnly.Location = New-Object System.Drawing.Point(20, 40)
$chkBrowserOnly.Size = New-Object System.Drawing.Size(300, 20)
$form.Controls.Add($chkBrowserOnly)

# Button zum Exportieren
$btnExport = New-Object System.Windows.Forms.Button
$btnExport.Text = "Exportieren"
$btnExport.Location = New-Object System.Drawing.Point(20, 70)
$btnExport.Size = New-Object System.Drawing.Size(128, 30)
$form.Controls.Add($btnExport)

# Ereignis beim Klick auf Exportieren
$btnExport.Add_Click({
    $timestamp = Get-Date -Format "yyyyMMdd_HHmmss"
    $csvFileName = "${hostname}_InstalledSoftware_$timestamp.csv"
    $csvPath = [System.IO.Path]::Combine($env:TEMP, $csvFileName)
    $culture = Get-Culture
    $delimiter = if ($culture.Name -like "de-*") { ";" } else { "," }

    Write-Log "Starte Software-Inventur für $hostname"

    $registryPaths = @(
        'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
        'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*',
        'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'
    )

    $apps = @()
    foreach ($path in $registryPaths) {
        try {
            $items = Get-ItemProperty -Path $path -ErrorAction SilentlyContinue | Where-Object { $_.DisplayName }
            foreach ($item in $items) {
                $regKey = Get-Item -Path "$($item.PSPath)" -ErrorAction SilentlyContinue
                $installDate = if ($item.InstallDate) { $item.InstallDate } else { if ($regKey) { ($regKey.LastWriteTime | Get-Date -Format 'yyyyMMdd' -ErrorAction SilentlyContinue) } }
                $apps += [PSCustomObject]@{
                    DisplayName       = $item.DisplayName
                    DisplayVersion    = $item.DisplayVersion
                    Publisher         = $item.Publisher
                    InstallDate       = $installDate
                    InstallLocation   = $item.InstallLocation
                    ManualCheckNeeded = "No"
                }
                Write-Log "Gefunden: $($item.DisplayName), InstallDate: $installDate"
            }
        } catch {
            $errorMsg = "Fehler beim Auslesen der Registry ($path): $_"
            Write-Log $errorMsg
            [System.Windows.Forms.MessageBox]::Show($errorMsg, "Fehler", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
        }
    }

    if ($chkBrowserOnly.Checked) {
        Write-Log "Filtere nur Browser-Anwendungen"
        $apps = $apps | Where-Object { $_.DisplayName -match '(?i)chrome|firefox|edge|opera|brave' }
    }

    if ($apps.Count -gt 0) {
        # Sortiere nach DisplayName
        $apps = $apps | Sort-Object DisplayName
        Write-Log "Sortiere $($apps.Count) Einträge nach DisplayName"

        # Verbesserter Fallback für fehlende InstallDates
        foreach ($app in $apps | Where-Object { -not $_.InstallDate }) {
            try {
                if ($app.InstallLocation -and (Test-Path $app.InstallLocation)) {
                    $exeFiles = Get-ChildItem -Path $app.InstallLocation -Filter "*.exe" -Recurse -File -ErrorAction SilentlyContinue |
                                Sort-Object CreationTime | Select-Object -First 1
                    if ($exeFiles) {
                        $app.InstallDate = Get-Date $exeFiles.CreationTime -Format 'yyyyMMdd'
                        Write-Log "Fallback für $($app.DisplayName): InstallDate aus Datei: ${app.InstallDate}"
                    } else {
                        $app.InstallDate = "Unbekannt"
                        $app.ManualCheckNeeded = "Yes"
                        Write-Log "Fallback für $($app.DisplayName): Keine .exe-Dateien gefunden"
                    }
                } else {
                    # Erweiterte Suche in Program Files, wenn InstallLocation fehlt
                    $searchPaths = @("C:\Program Files", "C:\Program Files (x86)")
                    foreach ($searchPath in $searchPaths) {
                        if (Test-Path $searchPath) {
                            $exeFiles = Get-ChildItem -Path $searchPath -Filter "*$($app.DisplayName.Replace(' ', ''))*" -Directory -ErrorAction SilentlyContinue | 
                                        Where-Object { Test-Path (Join-Path $_.FullName "*.exe") } | 
                                        ForEach-Object { Get-ChildItem -Path $_.FullName -Filter "*.exe" -File -ErrorAction SilentlyContinue } | 
                                        Sort-Object CreationTime | Select-Object -First 1
                            if ($exeFiles) {
                                $app.InstallDate = Get-Date $exeFiles.CreationTime -Format 'yyyyMMdd'
                                $app.ManualCheckNeeded = "No"
                                Write-Log "Erweiterter Fallback für $($app.DisplayName): InstallDate aus $searchPath - Datum: ${app.InstallDate}"
                                break
                            }
                        }
                    }
                    if (-not $app.InstallDate) {
                        $app.InstallDate = "Unbekannt"
                        $app.ManualCheckNeeded = "Yes"
                        Write-Log "Fallback für $($app.DisplayName): Kein gültiger InstallLocation und keine .exe in Program Files"
                    }
                }
            } catch {
                $app.InstallDate = "Unbekannt"
                $app.ManualCheckNeeded = "Yes"
                Write-Log "Fehler bei Dateisuche für $($app.DisplayName): $_"
            }
        }

        # NEU: Spezifische Fallback-Pfade für die 20 problematischen Anwendungen (angepasst)
        $knownApps = @{
            "7-Zip 25.01 (x64)" = "C:\Program Files\7-Zip"
            "BMEcatConverter" = "C:\Program Files\BMEcatConverter" # Falls vorhanden, sonst anpassen
            "Citrix Workspace 2503" = "C:\Program Files (x86)\Citrix\ICA Client"
            "Dell SupportAssist OS Recovery Plugin for Dell Update" = "C:\Program Files\Dell\SupportAssist\plugins\OSRecovery"
            "Dell SupportAssist Remediation" = "C:\Program Files\Dell\SupportAssist\Remediation"
            "ELCOM DCTERM" = "C:\Program Files (x86)\ELCOM\DC-TERM"
            "Intel(R) Arc Software & Drivers" = "C:\Program Files\Intel\Intel(R) Arc(TM) Graphics"
            "Logitech Solar App 1.20" = "C:\Program Files (x86)\Logitech\Solar App"
            "Microsoft 365 - de-de" = "C:\Program Files\Microsoft Office\root\Office16"
            "Microsoft 365 - en-us" = "C:\Program Files\Microsoft Office\root\Office16"
            "Microsoft 365 - fr-fr" = "C:\Program Files\Microsoft Office\root\Office16"
            "Microsoft 365 - it-it" = "C:\Program Files\Microsoft Office\root\Office16"
            "Microsoft 365 Apps for Enterprise - de-de" = "C:\Program Files\Microsoft Office\root\Office16"
            "Microsoft 365 Apps for enterprise - en-us" = "C:\Program Files\Microsoft Office\root\Office16"
            "Microsoft 365 Apps for enterprise - fr-fr" = "C:\Program Files\Microsoft Office\root\Office16"
            "Microsoft 365 Apps for enterprise - it-it" = "C:\Program Files\Microsoft Office\root\Office16"
            "Microsoft ASP.NET Core 8.0.20 - Shared Framework (x64)" = "C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\8.0.20"
            "Microsoft OneDrive" = "C:\Users\$env:USERNAME\AppData\Local\Microsoft\OneDrive"
            "Microsoft Visual C++ 2015-2022 Redistributable (x64) - 14.44.35208" = "C:\Windows\System32" # Fallback für WinSxS
            "Microsoft Visual C++ 2015-2022 Redistributable (x64) - 14.44.35211" = "C:\Windows\System32"
            "Microsoft Visual C++ 2015-2022 Redistributable (x86) - 14.44.35211" = "C:\Windows\SysWOW64"
            "Microsoft Windows Desktop Runtime - 6.0.36 (x64)" = "C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\6.0.36"
            "Microsoft Windows Desktop Runtime - 8.0.20 (x64)" = "C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\8.0.20"
            "Microsoft Windows Desktop Runtime - 8.0.20 (x86)" = "C:\Program Files (x86)\dotnet\shared\Microsoft.WindowsDesktop.App\8.0.20"
            "Microsoft Windows Desktop Runtime - 9.0.9 (x64)" = "C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App\9.0.9"
            "Notepad++ (64-bit x64)" = "C:\Program Files\Notepad++"
            "Omnissa Horizon Client" = "C:\Program Files (x86)\Omnissa\Horizon Client"
            "Opera Stable 122.0.5643.92" = "C:\Program Files\Opera"
            "PowerShell 7.5.3.0-x64" = "C:\Program Files\PowerShell\7"
            "Webex" = "C:\Program Files (x86)\Webex\Webex Meetings"
            "Windows Software Development Kit - Windows 10.0.26100.4948" = "C:\Program Files (x86)\Windows Kits\10"
            "Windows 11-Installationsassistent" = "C:\Windows\SoftwareDistribution\Download\Install"
        }

        foreach ($app in $apps | Where-Object { -not $_.InstallDate -and $knownApps.ContainsKey($app.DisplayName) }) {
            try {
                $knownPath = $knownApps[$app.DisplayName]
                if (Test-Path $knownPath) {
                    $folder = Get-Item -Path $knownPath -ErrorAction SilentlyContinue
                    if ($folder) {
                        $app.InstallDate = Get-Date $folder.CreationTime -Format 'yyyyMMdd'
                        $app.ManualCheckNeeded = "No"
                        Write-Log "Spezifischer Fallback für $($app.DisplayName): InstallDate aus Pfad $knownPath - Datum: ${app.InstallDate}"
                    } else {
                        # Innerhalb des Pfads nach .exe suchen
                        $exeFiles = Get-ChildItem -Path $knownPath -Filter "*.exe" -Recurse -File -ErrorAction SilentlyContinue | Sort-Object CreationTime | Select-Object -First 1
                        if ($exeFiles) {
                            $app.InstallDate = Get-Date $exeFiles.CreationTime -Format 'yyyyMMdd'
                            $app.ManualCheckNeeded = "No"
                            Write-Log "Spezifischer Fallback für $($app.DisplayName): InstallDate aus .exe in $knownPath - Datum: ${app.InstallDate}"
                        }
                    }
                } else {
                    Write-Log "Spezifischer Fallback für $($app.DisplayName): Pfad $knownPath nicht gefunden"
                }
            } catch {
                Write-Log "Fehler bei spezifischem Fallback für $($app.DisplayName): $_"
            }
        }

        # NEU: Optionaler Fallback mit Event-Logs (erweiterte Suche)
        foreach ($app in $apps | Where-Object { -not $_.InstallDate }) {
            try {
                $events = Get-WinEvent -LogName "Application", "System" -MaxEvents 1000 -ErrorAction SilentlyContinue | 
                Where-Object { ($_.ProviderName -like "*Installer*" -or $_.ProviderName -like "*Application*") -and 
                              ($_.Message -like "*$($app.DisplayName)*" -or $_.Message -like "*$($app.Publisher)*") } | 
                Sort-Object TimeCreated | Select-Object -First 1
                if ($events) {
                    $app.InstallDate = Get-Date $events.TimeCreated -Format 'yyyyMMdd'
                    $app.ManualCheckNeeded = "No"
                    Write-Log "Event-Log-Fallback für $($app.DisplayName): InstallDate: ${app.InstallDate}"
                }
            } catch {
                Write-Log "Fehler bei Event-Log für $($app.DisplayName): $_"
            }
        }

        # Export als CSV mit korrektem Delimiter
        Write-Log "Exportiere $($apps.Count) Einträge nach $csvPath"
        $apps | Export-Csv -Path $csvPath -NoTypeInformation -Delimiter $delimiter -Encoding UTF8
        try {
            Start-Process $csvPath
            Write-Log "CSV erfolgreich geöffnet: $csvPath"
            [System.Windows.Forms.MessageBox]::Show("Inventur abgeschlossen. CSV gespeichert unter: $csvPath", "Erfolg", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
        } catch {
            $errorMsg = "Fehler beim Öffnen der CSV-Datei: $_"
            Write-Log $errorMsg
            [System.Windows.Forms.MessageBox]::Show($errorMsg, "Fehler", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
        }
    } else {
        Write-Log "Keine passenden Einträge gefunden"
        [System.Windows.Forms.MessageBox]::Show("Keine passenden Einträge gefunden.", "Hinweis", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Information)
    }
})

# GUI anzeigen
try {
    Write-Log "Starte GUI"
    $form.ShowDialog()
    Write-Log "GUI geschlossen"
} catch {
    $errorMsg = "Fehler beim Anzeigen der GUI: $_"
    Write-Log $errorMsg
    [System.Windows.Forms.MessageBox]::Show($errorMsg, "Fehler", [System.Windows.Forms.MessageBoxButtons]::OK, [System.Windows.Forms.MessageBoxIcon]::Error)
}
anhängende Datei(en)
BG-RP-WS3_InstalledSoftware_20251008_165120.csv
404kb
BG-RP-WS3_InstalledSoftware_20251008_165120.xlsx
404kb
SoftwareInventory_Log_20251008_165118.txt
404kb
SW-Inventar-v3.ps1
404kb
Tags