Softwareinventar mit Bordmitteln: winget, PowerShell, dpkg — und die eine Spalte, die keins füllen kann
Eine Abfrage auf Win32_Product liefert nur einen Teil der installierten Programme — und stößt dabei über die installierten Pakete eine Konsistenzprüfung samt Reparatur an. Microsoft dokumentiert das in der eigenen Klassenreferenz. Für ein Windows-Softwareinventar gibt es vier brauchbare Wege; dieser gehört nicht dazu.
Weg 1: die Registry — das, was „Programme und Features“ liest
Die Systemsteuerung hat keine Datenbank. Sie liest die Uninstall-Schlüssel, und davon gibt es drei: den maschinenweiten 64-Bit-Schlüssel, die 32-Bit-Sicht, die WOW64 unter Wow6432Node automatisch anlegt, und den Benutzerschlüssel für Installationen, die nur für ein Profil gelten.
$pfade = @( 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*' ) Get-ItemProperty $pfade -ErrorAction SilentlyContinue | Where-Object DisplayName | Select-Object DisplayName, DisplayVersion, Publisher, InstallDate | Sort-Object DisplayName
Ausgabe: ein PowerShell-Objekt je Uninstall-Eintrag, mit genau den Werten, die der Windows Installer dort hinterlässt — DisplayName, DisplayVersion, Publisher und die übrigen. Zwei Fallstricke. DisplayVersion ist ein freier String — wer danach wie nach einer Versionsnummer sortiert, bekommt Unsinn. Und InstallDate ist nicht das Installationsdatum: Microsoft dokumentiert den Wert als den Zeitpunkt, an dem das Produkt zuletzt gewartet wurde; jeder eingespielte oder entfernte Patch überschreibt ihn. Der HKCU-Zweig gilt außerdem nur für das Konto, unter dem das Skript läuft — als geplante Aufgabe unter SYSTEM erfasst er die Profile der Anwender nicht.
Weg 2: winget — die Liste, die auch nach draußen schaut
winget list zeigt Programme, die über den Windows Package Manager installiert wurden, ebenso wie solche, die auf anderem Weg kamen — und ergänzt eine Spalte, die die Registry nicht hat: ob ein Update verfügbar ist und aus welcher Quelle es käme. Gibt es nirgends ein Update, wird die Spalte gar nicht erst angezeigt.
winget list --accept-source-agreements winget list --upgrade-available winget export -o inventar.json --include-versions
Ausgabe: eine Tabelle im Terminal oder JSON aus winget export mit einer Paketkennung je Eintrag. Den Haken dokumentiert Microsoft selbst: Der Export versucht es mit allen installierten Programmen, aber wo er eines keinem Paket aus einer konfigurierten Quelle zuordnen kann, gibt es eine Warnung. In der JSON-Datei stehen nur Pakete mit Kennung — was sich nicht zuordnen lässt, fehlt also darin. Die Zuordnung hängt an den Metadaten des Manifests und an dem, was der Installer in „Programme und Features“ hinterlassen hat. winget ist ein Paketmanager mit Inventar-Nebenwirkung, kein Inventarwerkzeug.
Weg 3: Store- und MSIX-Apps stehen ganz woanders
Paketierte Apps verwaltet Windows getrennt von den Uninstall-Schlüsseln — dort stehen sie in aller Regel nicht. Get-AppxPackage listet die Pakete eines Benutzerprofils; -AllUsers nimmt alle Konten des Rechners und braucht Administratorrechte.
Get-AppxPackage -AllUsers | Select-Object Name, Version, PackageFullName
Weg 4: unter Linux sind dpkg und rpm die ehrlichen Quellen
Beide nehmen eine Formatangabe entgegen — dpkg-query(1) und rpm(8). Damit bekommst du eine maschinenlesbare Datei statt einer Tabelle, die du hinterher wieder auseinandernehmen musst.
# Debian, Ubuntu — der Statusfilter ist Pflicht
dpkg-query -W -f='${db:Status-Abbrev}\t${binary:Package}\t${Version}\n' | grep '^ii'
# RHEL, Rocky, SUSE
rpm -qa --qf '%{NAME}\t%{VERSION}-%{RELEASE}\n'Ausgabe: tabulatorgetrennte Zeilen, ein Paket je Zeile. Der Filter ist keine Kosmetik — dpkg führt auch Pakete, die nicht mehr installiert sind: Status rc heißt entfernt, Konfiguration liegt noch auf der Platte. Ohne den ii-Filter stehen die in deinem Inventar, als liefe die Software.
Die teure Falle: Win32_Product
Microsoft schreibt die Warnung in die eigene Klassendokumentation: Win32_Product ist nicht abfrageoptimiert. Jede Abfrage zwingt WMI dazu, über den MSI-Provider sämtliche installierten Produkte aufzuzählen und die Liste sequenziell zu durchlaufen — und dieser Vorgang stößt zusätzlich eine Konsistenzprüfung der installierten Pakete an, die den Installationszustand prüft und repariert. Unter einem Konto ohne ausreichende Rechte kann das den Start von Anwendungen verzögern und ein Ereignis 11708 mit einem Installationsfehler erzeugen.
Das zweite Problem ist die Abdeckung. Win32_Product kennt nur, was der Windows Installer installiert hat; alles, was als Inno-Setup, NSIS-Paket oder eigenes EXE-Setup kam, fehlt schlicht. Du bezahlst einen Reparaturlauf über den ganzen Bestand und bekommst dafür eine unvollständige Liste. Weg 1 liest dieselben Werte aus der Registry, ohne jede Nebenwirkung.
Was jeder Weg systematisch übersieht
| Weg | sieht nicht |
|---|---|
| Registry | ausgepackte Programme ohne Setup, Installationen in fremden Profilen, Store-Apps |
| winget | alles ohne Treffer in einer konfigurierten Quelle |
| Get-AppxPackage | jedes klassische Win32-Programm |
| Win32_Product | alles, was nicht der Windows Installer installiert hat |
| dpkg, rpm | Snap, Flatpak, pip, npm, Tarballs unter /opt, Container-Images |
| alle zusammen | Geräte ohne Shell: Firewalls, Switches, Hypervisoren, NAS-Systeme, Drucker |
Die letzte Zeile entscheidet, wie viel die ganze Übung wert ist. Die Geräte, die du nicht abfragen kannst, stehen oft am Internetrand.
Zusammenführen: eine CSV, fünf Spalten
Host, Produkt, Version, Quelle, Stand. Die Spalte „Quelle“ sieht nach Buchhaltung aus und ist die Mühe trotzdem wert — ein halbes Jahr später ist sie das Einzige, was erklärt, warum ein Eintrag fehlt.
Get-ItemProperty $pfade -ErrorAction SilentlyContinue |
Where-Object DisplayName |
Select-Object @{n='Host';e={$env:COMPUTERNAME}},
@{n='Produkt';e={$_.DisplayName}},
@{n='Version';e={$_.DisplayVersion}},
@{n='Quelle';e={'registry'}},
@{n='Stand';e={Get-Date -Format 'yyyy-MM-dd'}} |
Export-Csv -Path .\inventar.csv -NoTypeInformation -Encoding UTF8Auf der Linux-Seite schickst du dpkg-query durch awk in dieselben fünf Felder und hängst an dieselbe Datei an. Mehr ist der Trick nicht: Der Wert steckt nicht im Befehl, sondern darin, dass jeder Host dieselbe Form schreibt.
Die ehrliche Einschränkung
Namen sind nicht normiert, und daran scheitert die Zusammenführung leise. Mozilla Firefox (x64 de) aus der Registry, Mozilla.Firefox aus winget und firefox-esr aus dpkg sind dasselbe Produkt unter drei verschiedenen Schlüsseln; wer über den Anzeigenamen zusammenführt, bekommt Dubletten, die wie zusätzliche Abdeckung aussehen. Sauber geht das nur mit einer eigenen Zuordnungstabelle — und die pflegt niemand nebenbei. Dazu kommt: Eine CSV vom Dienstag beschreibt den Dienstag. Bordmittel beantworten „was liegt auf diesem Rechner“, nie „ist es noch aktuell“.
Die Spalte, die auf keinem deiner Rechner steht
Vier Wege, fünf Spalten — und die sechste bleibt leer: die aktuelle Version beim Hersteller. Kein Befehl auf dem eigenen Rechner kann sie füllen, weil die Antwort dort nicht liegt.
Zwei Wege scheinen das zu widerlegen. winget list zeigt eine Update-Spalte, apt list --upgradeable ebenso. Beide fragen aber nicht den Rechner, sondern einen Katalog außerhalb: winget die konfigurierte Quelle, apt die Paketquellen der Distribution. Wo dieser Katalog das Produkt nicht kennt, ist die Spalte wieder leer — und genau das ist der Teil des Bestands, auf den es ankommt: das Backup-Werkzeug, die Firewall-Firmware, das Java, das jemand nach C:\tools ausgepackt hat, das Confluence auf der VM.
Für diesen Teil brauchst du einen Katalog, der nicht an einen Paketmanager gebunden ist. patchletter führt einen: je verfolgtem Produkt die aktuellste erfasste Version mit Herstellerdatum. Für winget selbst ist das 1.29.280 vom 24.06.2026 (Stand: 19.08.2026), Chocolatey steht bei 2.7.3 vom 10.06.2026. Hak die Produkte aus deiner CSV im Katalog an, und du bekommst eine Mail, sobald für eines davon eine neue Version erscheint — kostenlos, ohne Konto, Abmeldung mit einem Klick. Die Spalte, die dein Inventar nicht füllen kann, steht dann daneben statt darin.
Befehle und Verhalten geprüft an der Herstellerdokumentation, abgerufen am 19.08.2026: winget list und export, die Klasse Win32_Product, der Uninstall-Registry-Schlüssel, die WOW6432Node-Umleitung und Get-AppxPackage auf Microsoft Learn; dpkg-query(1) und apt(8) in den Debian-Manpages; rpm(8) auf man7.org. Die beiden patchletter-Versionsstände stammen aus dem eigenen Bestand, Stand: 19.08.2026, und bleiben bewusst datiert stehen; die laufenden Werte stehen auf den Produktseiten.