diff --git a/Cargo.lock b/Cargo.lock index 44dae2a..d735ef2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3197,7 +3197,7 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hello-agent" -version = "0.1.2" +version = "0.1.3" dependencies = [ "anyhow", "env_logger 0.10.2", diff --git a/Cargo.toml b/Cargo.toml index 46627c2..ba41507 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hello-agent" -version = "0.1.2" +version = "0.1.3" edition = "2021" rust-version = "1.75" description = "Headless RustDesk-protocol-compatible support agent for Windows" diff --git a/src/inventory.rs b/src/inventory.rs index ba839c5..d6d91a7 100644 --- a/src/inventory.rs +++ b/src/inventory.rs @@ -105,6 +105,53 @@ try { $public_ip = (Invoke-RestMethod -Uri 'https://api.ipify.org' -TimeoutSec 5 -ErrorAction Stop).ToString().Trim() } catch {} +# Installed software (the "Add/Remove Programs" / "Apps & features" list). +# We enumerate the Uninstall registry keys directly — the same source +# Settings reads — rather than `Get-CimInstance Win32_Product`, which is +# notoriously slow (triggers MSI self-repair on every entry) and only +# covers MSI-installed software, missing everything from per-user +# installers, Chocolatey/Scoop/Inno-Setup, etc. +# +# We read both HKLM hives (64-bit + WOW6432Node 32-bit) so apps installed +# under either bitness show up. HKCU is skipped on purpose: the agent +# runs as LocalSystem (or LocalService), whose HKCU hive has nothing the +# logged-in user installed under their own profile — that data would +# require running per-user, which is out of scope for v1. +# +# Filter rules: +# * `DisplayName` must be set — empty-DisplayName entries are uninstall +# stubs for individual update KBs and not user-facing apps. +# * Skip `SystemComponent = 1` — internal Windows components hidden +# from Settings (DirectX shims, VC++ private redists, …). +# * Skip entries with a `ParentKeyName` — those are subcomponents of a +# parent application (e.g. Office's per-language packs); the parent +# row already covers the user-facing app. +# +# Bitness-tagged so the admin UI can distinguish a 64-bit vs 32-bit +# install of the same product (common for runtimes like VC++). +$installed_software = @() +foreach ($scope in @( + @{ path = 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*'; bitness = '64' }, + @{ path = 'HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*'; bitness = '32' } +)) { + try { + $entries = Get-ItemProperty -Path $scope.path -ErrorAction SilentlyContinue + } catch { continue } + foreach ($e in $entries) { + if (-not $e.DisplayName) { continue } + if ($e.SystemComponent -eq 1) { continue } + if ($e.ParentKeyName) { continue } + $installed_software += [pscustomobject]@{ + name = "$($e.DisplayName)" + version = if ($e.DisplayVersion) { "$($e.DisplayVersion)" } else { '' } + publisher = if ($e.Publisher) { "$($e.Publisher)" } else { '' } + install_date = if ($e.InstallDate) { "$($e.InstallDate)" } else { '' } + bitness = $scope.bitness + } + } +} +$installed_software = @($installed_software | Sort-Object name, version) + $os_release = "$($os.Version)" if ($displayVersion) { $os_release = "$($os.Version) $displayVersion" } $result = [pscustomobject]@{ @@ -123,6 +170,7 @@ $result = [pscustomobject]@{ bitlocker_recovery_key = $bl_key network_interfaces = $nics public_ip = $public_ip + installed_software = $installed_software } $result | ConvertTo-Json -Compress -Depth 6 "#;