Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
feat(windows): CU-17tkuw63weu report install metadata in the programs…
… table

Adds system_component, installer_type, install_scope and install_arch, so
a backend can apply winget's rules when it matches installed programs.
Winget skips SystemComponent entries, refuses an upgrade across installer
technologies and requires the installer scope to match.

The flags are collected in locals and set only on rows that already have
data, so a subkey holding nothing but a flag stays filtered out. MSIX rows
carry only installer_type.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
  • Loading branch information
denys-gif and claude committed Nov 5, 2026
commit 08cf5592e2bc95180e43294a6b9d71084c788d18
25 changes: 25 additions & 0 deletions osquery/tables/system/windows/programs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -220,90 +220,114 @@
return productCodeUpgradeCodeMap;
}

void keyEnumPrograms(const std::string& key,
std::set<std::string>& processed,
std::map<std::string, std::string> upgradeCodeMap,
QueryData& results) {
QueryData regResults;
queryKey(key, regResults);
for (const auto& rKey : regResults) {
// Each subkey represents a program, skip if not a subkey
if (rKey.at("type") != "subkey") {
continue;
}

// Ensure we only process each program one time
const auto& fullProgramName = rKey.at("path");
if (processed.find(fullProgramName) != processed.end()) {
continue;
}
processed.insert(fullProgramName);

// Query additional information about the program
QueryData appResults;
queryKey(fullProgramName, appResults);
Row r;

// Attempt to derive the program identifying GUID
std::string identifyingNumber;
std::smatch matches;
std::regex expression(
"(\\{[a-fA-F0-9]+-[a-fA-F0-9]+-[a-fA-F0-9]+-[a-fA-F0-9]+-[a-fA-F0-9]+"
"\\})$");
if (std::regex_search(fullProgramName, matches, expression)) {
identifyingNumber = matches[0];
r["identifying_number"] = identifyingNumber;
}

if (!identifyingNumber.empty()) {
std::string identifyingNumberUpper = identifyingNumber;
std::transform(identifyingNumberUpper.begin(),
identifyingNumberUpper.end(),
identifyingNumberUpper.begin(),
::toupper);

const auto& upgradeCode = upgradeCodeMap[identifyingNumberUpper];
if (!upgradeCode.empty()) {
r["upgrade_code"] = upgradeCode;
}
}

bool systemComponent = false;
bool windowsInstaller = false;
bool portable = false;
for (const auto& aKey : appResults) {
auto name = aKey.find("name");
if (identifyingNumber.empty() && name->second == "BundleIdentifier") {
r["identifying_number"] = aKey.at("data");
}
if (name->second == "DisplayName") {
r["name"] = aKey.at("data");
}
if (name->second == "DisplayVersion") {
r["version"] = aKey.at("data");
}
if (name->second == "InstallLocation") {
r["install_location"] = aKey.at("data");
}
if (name->second == "InstallSource") {
r["install_source"] = aKey.at("data");
}
if (name->second == "Language") {
r["language"] = aKey.at("data");
}
if (name->second == "Publisher") {
r["publisher"] = aKey.at("data");
}
if (name->second == "UninstallString") {
r["uninstall_string"] = aKey.at("data");
}
if (name->second == "InstallDate") {
r["install_date"] = aKey.at("data");
}
if (name->second == "SystemComponent") {
systemComponent =
aKey.at("type") == "REG_DWORD" && aKey.at("data") != "0";
}
if (name->second == "WindowsInstaller") {
windowsInstaller =
aKey.at("type") == "REG_DWORD" && aKey.at("data") != "0";
}
if (name->second == "WinGetInstallerType") {
portable = aKey.at("data") == "portable";
}
}
if (!r.empty()) {
r["arp_key"] = rKey.at("name");
r["system_component"] = systemComponent ? "1" : "0";
r["installer_type"] =
portable ? "portable" : (windowsInstaller ? "msi" : "exe");
if (key.rfind("HKEY_USERS", 0) == 0) {
r["install_scope"] = "user";
} else {
r["install_scope"] = "machine";
r["install_arch"] =
key.find("WOW6432Node") != std::string::npos ? "32" : "64";
}
results.push_back(r);
}
}
}

Check notice on line 330 in osquery/tables/system/windows/programs.cpp

View check run for this annotation

Flamingo / Flamingo Code Review

2 new or changed definitions have no test referencing them

2 new or changed definitions have no test referencing them No test file in this checkout mentions these definitions. Add or extend tests for the ones that carry logic. - `osquery::tables::keyEnumPrograms` (osquery/tables/system/windows/programs.cpp:223) - `osquery::tables::genMsixPrograms` (osquery/tables/system/windows/programs.cpp:332)

void genMsixPrograms(const std::string& key,
std::set<std::string>& packageFamilyNameProcessed,
Expand All @@ -326,6 +350,7 @@
result["identifying_name"] = regPackageFullName;
result["package_family_name"] =
packageFamilyNameFromPackageFullName(regPackageFullName);
result["installer_type"] = "msix";

for (const auto& aKey : appResults) {
auto name = aKey.find("name");
Expand Down
6 changes: 5 additions & 1 deletion specs/windows/programs.table
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ schema([
Column("identifying_number", TEXT, "Product identification such as a serial number on software, or a die number on a hardware chip."),
Column("package_family_name", TEXT, "A combination of PackageName and PublisherHash that is used to uniquely identify applications across versions and architectures."),
Column("upgrade_code", TEXT, "Specific to MSI applications, a GUID used to identify a product suite across multiple versions."),
Column("arp_key", TEXT, "Name of the program's Uninstall registry subkey. Empty for MSIX packages.")
Column("arp_key", TEXT, "Name of the program's Uninstall registry subkey. Empty for MSIX packages."),
Column("system_component", TEXT, "1 if the Uninstall entry is flagged SystemComponent and hidden from Programs and Features, 0 if not. Empty for MSIX packages."),
Column("installer_type", TEXT, "Installer technology of the program: msi, exe, portable or msix."),
Column("install_scope", TEXT, "Scope of the Uninstall entry: machine or user. Empty for MSIX packages."),
Column("install_arch", TEXT, "Registry view of a machine scope Uninstall entry: 64 or 32. Empty for user scope entries and MSIX packages.")
])
implementation("programs@genPrograms")
examples([
Expand Down
6 changes: 5 additions & 1 deletion tests/integration/tables/programs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,11 @@
{"identifying_number", NormalType},
{"package_family_name", NormalType},
{"upgrade_code", NormalType},
{"arp_key", NormalType}};
{"arp_key", NormalType},
{"system_component", NormalType},
{"installer_type", NormalType},
{"install_scope", NormalType},
{"install_arch", NormalType}};

Check warning on line 43 in tests/integration/tables/programs.cpp

View check run for this annotation

Flamingo / Flamingo Code Review / tests and mutation

The new classification columns are validated as any text, so the integration test checks none of their values

The four new columns use NormalType, which accepts any value, and no other test covers the new logic in keyEnumPrograms. A wrong or missing value in system_component, installer_type, install_scope or install_arch passes test_sanity. Risk: The backend uses these values to decide whether winget can manage a program. A regression in the classification would silently change that decision and no test would fail. Fix: Constrain the columns with SpecificValuesCheck, including the empty value where the spec allows it. installer_type is set on every row, so it takes no empty value.
Comment thread
denys-gif marked this conversation as resolved.
Resolved
validate_rows(data, row_map);
}

Expand Down
Loading