diff --git a/ee/debug/checkups/checkups.go b/ee/debug/checkups/checkups.go index 71c3886c0..374ca539f 100644 --- a/ee/debug/checkups/checkups.go +++ b/ee/debug/checkups/checkups.go @@ -108,7 +108,7 @@ func checkupsFor(k types.Knapsack, target targetBits) []checkupInt { {&enrollSecretCheckup{k: k}, doctorSupported | flareSupported}, {&bboltdbCheckup{k: k}, flareSupported}, {&networkCheckup{}, doctorSupported | flareSupported}, - {&installCheckup{}, flareSupported}, + {&installCheckup{k: k}, flareSupported}, {&servicesCheckup{}, doctorSupported | flareSupported}, {&powerCheckup{}, flareSupported}, {&osqueryCheckup{k: k}, doctorSupported | flareSupported}, diff --git a/ee/debug/checkups/install.go b/ee/debug/checkups/install.go index 3c5ed90bd..01b1837d4 100644 --- a/ee/debug/checkups/install.go +++ b/ee/debug/checkups/install.go @@ -7,10 +7,11 @@ import ( "io" "runtime" - "github.com/kolide/launcher/pkg/launcher" + "github.com/kolide/launcher/ee/agent/types" ) type installCheckup struct { + k types.Knapsack } func (i *installCheckup) Name() string { @@ -25,7 +26,7 @@ func (i *installCheckup) Run(ctx context.Context, extraWriter io.Writer) error { return fmt.Errorf("gathering installation logs: %w", err) } - if err := gatherInstallerInfo(extraZip); err != nil { + if err := gatherInstallerInfo(extraZip, i.k.Identifier()); err != nil { return fmt.Errorf("gathering installer info: %w", err) } @@ -56,14 +57,3 @@ func gatherInstallationLogs(z *zip.Writer) error { return addFileToZip(z, "/var/log/install.log") } - -func gatherInstallerInfo(z *zip.Writer) error { - if runtime.GOOS == "windows" { - return nil - } - - configDir := launcher.DefaultPath(launcher.EtcDirectory) - installerInfoPath := fmt.Sprintf("%s/installer-info.json", configDir) - - return addFileToZip(z, installerInfoPath) -} diff --git a/ee/debug/checkups/install_other.go b/ee/debug/checkups/install_other.go new file mode 100644 index 000000000..ad86f05b8 --- /dev/null +++ b/ee/debug/checkups/install_other.go @@ -0,0 +1,18 @@ +//go:build !windows +// +build !windows + +package checkups + +import ( + "archive/zip" + "fmt" + + "github.com/kolide/launcher/pkg/launcher" +) + +func gatherInstallerInfo(z *zip.Writer, _ string) error { + configDir := launcher.DefaultPath(launcher.EtcDirectory) + installerInfoPath := fmt.Sprintf("%s/installer-info.json", configDir) + + return addFileToZip(z, installerInfoPath) +} diff --git a/ee/debug/checkups/install_windows.go b/ee/debug/checkups/install_windows.go new file mode 100644 index 000000000..286ce29fe --- /dev/null +++ b/ee/debug/checkups/install_windows.go @@ -0,0 +1,110 @@ +//go:build windows +// +build windows + +package checkups + +import ( + "archive/zip" + "bytes" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/kolide/launcher/pkg/launcher" + "golang.org/x/sys/windows/registry" +) + +const ( + installerInfoRegistryKeyFmt = `Software\Kolide\Launcher\%s\%s` + currentVersionKeyName = `CurrentVersionNum` + installedVersionKeyName = `InstalledVersionNum` + downloadPathKeyName = `DownloadPath` + identifierKeyName = `Identifier` + userKeyName = `User` + versionKeyName = `Version` +) + +type installerInfo struct { + // CurrentVersionNum is the numeric representation of our semver version, added at startup + CurrentVersionNum uint64 `json:"current_version_num"` + // InstalledVersionNum is the numeric representation of our semver version, added at install time + InstalledVersionNum uint64 `json:"installed_version_num"` + // Version is our semver string version representation, added at install time + Version string `json:"installed_version"` + // DownloadPath is the original location of the MSI used to install launcher + DownloadPath string `json:"download_path"` + Identifier string `json:"identifier"` + User string `json:"user"` +} + +func gatherInstallerInfo(z *zip.Writer, identifier string) error { + if strings.TrimSpace(identifier) == "" { + identifier = launcher.DefaultLauncherIdentifier + } + + info := installerInfo{ + CurrentVersionNum: getDefaultRegistryIntValue( + fmt.Sprintf(installerInfoRegistryKeyFmt, identifier, currentVersionKeyName), + ), + InstalledVersionNum: getDefaultRegistryIntValue( + fmt.Sprintf(installerInfoRegistryKeyFmt, identifier, installedVersionKeyName), + ), + Version: getDefaultRegistryStringValue( + fmt.Sprintf(installerInfoRegistryKeyFmt, identifier, versionKeyName), + ), + DownloadPath: getDefaultRegistryStringValue( + fmt.Sprintf(installerInfoRegistryKeyFmt, identifier, downloadPathKeyName), + ), + Identifier: getDefaultRegistryStringValue( + fmt.Sprintf(installerInfoRegistryKeyFmt, identifier, identifierKeyName), + ), + User: getDefaultRegistryStringValue( + fmt.Sprintf(installerInfoRegistryKeyFmt, identifier, userKeyName), + ), + } + + infoJson, err := json.MarshalIndent(info, "", " ") + if err != nil { + return err + } + + return addStreamToZip(z, "installer-info.json", time.Now(), bytes.NewReader(infoJson)) +} + +// getDefaultRegistryStringValue queries for the default registry value set at the provided path +// on the local machine. this is a best effort approach, grabbing whatever info we can. not all +// devices will have all paths/values present, errors are ignored +func getDefaultRegistryStringValue(path string) string { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.QUERY_VALUE) + if err != nil { + return "" + } + + defer key.Close() + + val, _, err := key.GetStringValue("") + if err != nil { + return "" + } + + return val +} + +// getDefaultRegistryIntValue performs the same function as getDefaultRegistryStringValue but +// is intended for integer (REG_DWORD) values +func getDefaultRegistryIntValue(path string) uint64 { + key, err := registry.OpenKey(registry.LOCAL_MACHINE, path, registry.QUERY_VALUE) + if err != nil { + return 0 + } + + defer key.Close() + + val, _, err := key.GetIntegerValue("") + if err != nil { + return 0 + } + + return val +} diff --git a/ee/debug/checkups/power_windows.go b/ee/debug/checkups/power_windows.go index 6fd2eda86..6c988ed4f 100644 --- a/ee/debug/checkups/power_windows.go +++ b/ee/debug/checkups/power_windows.go @@ -54,12 +54,12 @@ func (p *powerCheckup) Run(ctx context.Context, extraWriter io.Writer) error { hideWindow(powerCfgSleepStatesCmd) availableSleepStatesOutput, err := powerCfgSleepStatesCmd.CombinedOutput() if err != nil { - return fmt.Errorf("running powercfg.exe for sleep states: error %w", err) + return fmt.Errorf("running powercfg.exe for sleep states: error %w, output %s", err, string(availableSleepStatesOutput)) } // Add sleep states using addStreamToZip if err := addStreamToZip(extraZip, "available_sleep_states.txt", time.Now(), bytes.NewReader(availableSleepStatesOutput)); err != nil { - return fmt.Errorf("running powercfg.exe for sleep states: error %w, output %s", err, string(availableSleepStatesOutput)) + return fmt.Errorf("adding sleep states stream to zip: %w", err) } // Get power events