- Update go.mod files to use github.com/Fimeg/RedFlag module path - Fix all import statements across server and agent code - Resolves build errors when cloning from GitHub - Utils package (version comparison) is actually needed and working
662 lines
20 KiB
Go
662 lines
20 KiB
Go
package scanner
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os/exec"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Fimeg/RedFlag/aggregator-agent/internal/client"
|
|
)
|
|
|
|
// WingetPackage represents a single package from winget output
|
|
type WingetPackage struct {
|
|
Name string `json:"Name"`
|
|
ID string `json:"Id"`
|
|
Version string `json:"Version"`
|
|
Available string `json:"Available"`
|
|
Source string `json:"Source"`
|
|
IsPinned bool `json:"IsPinned"`
|
|
PinReason string `json:"PinReason,omitempty"`
|
|
}
|
|
|
|
// WingetScanner scans for Windows package updates using winget
|
|
type WingetScanner struct{}
|
|
|
|
// NewWingetScanner creates a new Winget scanner
|
|
func NewWingetScanner() *WingetScanner {
|
|
return &WingetScanner{}
|
|
}
|
|
|
|
// IsAvailable checks if winget is available on this system
|
|
func (s *WingetScanner) IsAvailable() bool {
|
|
// Only available on Windows
|
|
if runtime.GOOS != "windows" {
|
|
return false
|
|
}
|
|
|
|
// Check if winget command exists
|
|
_, err := exec.LookPath("winget")
|
|
return err == nil
|
|
}
|
|
|
|
// Scan scans for available winget package updates
|
|
func (s *WingetScanner) Scan() ([]client.UpdateReportItem, error) {
|
|
if !s.IsAvailable() {
|
|
return nil, fmt.Errorf("winget is not available on this system")
|
|
}
|
|
|
|
// Try multiple approaches with proper error handling
|
|
var lastErr error
|
|
|
|
// Method 1: Standard winget list with JSON output
|
|
if updates, err := s.scanWithJSON(); err == nil {
|
|
return updates, nil
|
|
} else {
|
|
lastErr = err
|
|
fmt.Printf("Winget JSON scan failed: %v\n", err)
|
|
}
|
|
|
|
// Method 2: Fallback to basic winget list without JSON
|
|
if updates, err := s.scanWithBasicOutput(); err == nil {
|
|
return updates, nil
|
|
} else {
|
|
lastErr = fmt.Errorf("both winget scan methods failed: %v (last error)", err)
|
|
fmt.Printf("Winget basic scan failed: %v\n", err)
|
|
}
|
|
|
|
// Method 3: Attempt automatic recovery for known issues
|
|
if isKnownWingetError(lastErr) {
|
|
fmt.Printf("Attempting automatic winget recovery...\n")
|
|
if updates, err := s.attemptWingetRecovery(); err == nil {
|
|
fmt.Printf("Winget recovery successful, found %d updates\n", len(updates))
|
|
return updates, nil
|
|
} else {
|
|
fmt.Printf("Winget recovery failed: %v\n", err)
|
|
}
|
|
|
|
return nil, fmt.Errorf("winget encountered a known issue (exit code %s). This may be due to Windows Update service or system configuration. Automatic recovery was attempted but failed", getExitCode(lastErr))
|
|
}
|
|
|
|
return nil, lastErr
|
|
}
|
|
|
|
// scanWithJSON attempts to scan using JSON output (most reliable)
|
|
func (s *WingetScanner) scanWithJSON() ([]client.UpdateReportItem, error) {
|
|
// Run winget list command to get outdated packages
|
|
// Using --output json for structured output
|
|
cmd := exec.Command("winget", "list", "--outdated", "--accept-source-agreements", "--output", "json")
|
|
|
|
// Use CombinedOutput to capture both stdout and stderr for better error handling
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
// Check for specific exit codes that might be transient
|
|
if isTransientError(err) {
|
|
return nil, fmt.Errorf("winget temporary failure: %w", err)
|
|
}
|
|
return nil, fmt.Errorf("failed to run winget list: %w (output: %s)", err, string(output))
|
|
}
|
|
|
|
// Parse JSON output
|
|
var packages []WingetPackage
|
|
if err := json.Unmarshal(output, &packages); err != nil {
|
|
return nil, fmt.Errorf("failed to parse winget JSON output: %w (output: %s)", err, string(output))
|
|
}
|
|
|
|
var updates []client.UpdateReportItem
|
|
|
|
// Convert each package to our UpdateReportItem format
|
|
for _, pkg := range packages {
|
|
// Skip if no available update
|
|
if pkg.Available == "" || pkg.Available == pkg.Version {
|
|
continue
|
|
}
|
|
|
|
updateItem := s.parseWingetPackage(pkg)
|
|
updates = append(updates, *updateItem)
|
|
}
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
// scanWithBasicOutput falls back to parsing text output
|
|
func (s *WingetScanner) scanWithBasicOutput() ([]client.UpdateReportItem, error) {
|
|
cmd := exec.Command("winget", "list", "--outdated", "--accept-source-agreements")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to run winget list basic: %w", err)
|
|
}
|
|
|
|
// Simple text parsing fallback
|
|
return s.parseWingetTextOutput(string(output))
|
|
}
|
|
|
|
// parseWingetTextOutput parses winget text output as fallback
|
|
func (s *WingetScanner) parseWingetTextOutput(output string) ([]client.UpdateReportItem, error) {
|
|
var updates []client.UpdateReportItem
|
|
lines := strings.Split(output, "\n")
|
|
|
|
for _, line := range lines {
|
|
line = strings.TrimSpace(line)
|
|
// Skip header lines and empty lines
|
|
if strings.HasPrefix(line, "Name") || strings.HasPrefix(line, "-") || line == "" {
|
|
continue
|
|
}
|
|
|
|
// Simple parsing for tab or space-separated values
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 3 {
|
|
pkgName := fields[0]
|
|
currentVersion := fields[1]
|
|
availableVersion := fields[2]
|
|
|
|
// Skip if no update available
|
|
if availableVersion == currentVersion || availableVersion == "Unknown" {
|
|
continue
|
|
}
|
|
|
|
update := client.UpdateReportItem{
|
|
PackageType: "winget",
|
|
PackageName: pkgName,
|
|
CurrentVersion: currentVersion,
|
|
AvailableVersion: availableVersion,
|
|
Severity: s.determineSeverityFromName(pkgName),
|
|
RepositorySource: "winget",
|
|
PackageDescription: fmt.Sprintf("Update available for %s", pkgName),
|
|
Metadata: map[string]interface{}{
|
|
"package_manager": "winget",
|
|
"detected_via": "text_parser",
|
|
},
|
|
}
|
|
updates = append(updates, update)
|
|
}
|
|
}
|
|
|
|
return updates, nil
|
|
}
|
|
|
|
// isTransientError checks if the error might be temporary
|
|
func isTransientError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
errStr := err.Error()
|
|
// Common transient error patterns
|
|
transientPatterns := []string{
|
|
"network error",
|
|
"timeout",
|
|
"connection refused",
|
|
"temporary failure",
|
|
"service unavailable",
|
|
}
|
|
|
|
for _, pattern := range transientPatterns {
|
|
if strings.Contains(strings.ToLower(errStr), pattern) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// isKnownWingetError checks for known Winget issues
|
|
func isKnownWingetError(err error) bool {
|
|
if err == nil {
|
|
return false
|
|
}
|
|
|
|
errStr := err.Error()
|
|
// Check for the specific exit code 0x8a150002
|
|
if strings.Contains(errStr, "2316632066") || strings.Contains(errStr, "0x8a150002") {
|
|
return true
|
|
}
|
|
|
|
// Other known Winget issues
|
|
knownPatterns := []string{
|
|
"winget is not recognized",
|
|
"windows package manager",
|
|
"windows app installer",
|
|
"restarting your computer",
|
|
}
|
|
|
|
for _, pattern := range knownPatterns {
|
|
if strings.Contains(strings.ToLower(errStr), pattern) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// getExitCode extracts exit code from error if available
|
|
func getExitCode(err error) string {
|
|
if err == nil {
|
|
return "unknown"
|
|
}
|
|
|
|
// Try to extract exit code from error message
|
|
errStr := err.Error()
|
|
if strings.Contains(errStr, "exit status") {
|
|
// Extract exit status number
|
|
parts := strings.Fields(errStr)
|
|
for i, part := range parts {
|
|
if part == "status" && i+1 < len(parts) {
|
|
return parts[i+1]
|
|
}
|
|
}
|
|
}
|
|
|
|
return "unknown"
|
|
}
|
|
|
|
// determineSeverityFromName provides basic severity detection for fallback
|
|
func (s *WingetScanner) determineSeverityFromName(name string) string {
|
|
lowerName := strings.ToLower(name)
|
|
|
|
// Security tools get higher priority
|
|
if strings.Contains(lowerName, "antivirus") ||
|
|
strings.Contains(lowerName, "security") ||
|
|
strings.Contains(lowerName, "defender") ||
|
|
strings.Contains(lowerName, "firewall") {
|
|
return "critical"
|
|
}
|
|
|
|
// Browsers and communication tools get high priority
|
|
if strings.Contains(lowerName, "firefox") ||
|
|
strings.Contains(lowerName, "chrome") ||
|
|
strings.Contains(lowerName, "edge") ||
|
|
strings.Contains(lowerName, "browser") {
|
|
return "high"
|
|
}
|
|
|
|
return "moderate"
|
|
}
|
|
|
|
// parseWingetPackage converts a WingetPackage to our UpdateReportItem format
|
|
func (s *WingetScanner) parseWingetPackage(pkg WingetPackage) *client.UpdateReportItem {
|
|
// Determine severity based on package type and source
|
|
severity := s.determineSeverity(pkg)
|
|
|
|
// Categorize the package type
|
|
packageCategory := s.categorizePackage(pkg.Name, pkg.Source)
|
|
|
|
// Create metadata with winget-specific information
|
|
metadata := map[string]interface{}{
|
|
"package_id": pkg.ID,
|
|
"source": pkg.Source,
|
|
"category": packageCategory,
|
|
"is_pinned": pkg.IsPinned,
|
|
"pin_reason": pkg.PinReason,
|
|
"package_manager": "winget",
|
|
}
|
|
|
|
// Add additional metadata based on package source
|
|
if pkg.Source == "winget" {
|
|
metadata["repository_type"] = "community"
|
|
} else if pkg.Source == "msstore" {
|
|
metadata["repository_type"] = "microsoft_store"
|
|
} else {
|
|
metadata["repository_type"] = "custom"
|
|
}
|
|
|
|
// Create the update report item
|
|
updateItem := &client.UpdateReportItem{
|
|
PackageType: "winget",
|
|
PackageName: pkg.Name,
|
|
CurrentVersion: pkg.Version,
|
|
AvailableVersion: pkg.Available,
|
|
Severity: severity,
|
|
RepositorySource: pkg.Source,
|
|
Metadata: metadata,
|
|
}
|
|
|
|
// Add description if available (would need additional winget calls)
|
|
// For now, we'll use the package name as description
|
|
updateItem.PackageDescription = fmt.Sprintf("Update available for %s from %s", pkg.Name, pkg.Source)
|
|
|
|
return updateItem
|
|
}
|
|
|
|
// determineSeverity determines the severity of a package update based on various factors
|
|
func (s *WingetScanner) determineSeverity(pkg WingetPackage) string {
|
|
name := strings.ToLower(pkg.Name)
|
|
source := strings.ToLower(pkg.Source)
|
|
|
|
// Security tools get higher priority
|
|
if strings.Contains(name, "antivirus") ||
|
|
strings.Contains(name, "security") ||
|
|
strings.Contains(name, "firewall") ||
|
|
strings.Contains(name, "malware") ||
|
|
strings.Contains(name, "defender") ||
|
|
strings.Contains(name, "crowdstrike") ||
|
|
strings.Contains(name, "sophos") ||
|
|
strings.Contains(name, "symantec") {
|
|
return "critical"
|
|
}
|
|
|
|
// Browsers and communication tools get high priority
|
|
if strings.Contains(name, "firefox") ||
|
|
strings.Contains(name, "chrome") ||
|
|
strings.Contains(name, "edge") ||
|
|
strings.Contains(name, "browser") ||
|
|
strings.Contains(name, "zoom") ||
|
|
strings.Contains(name, "teams") ||
|
|
strings.Contains(name, "slack") ||
|
|
strings.Contains(name, "discord") {
|
|
return "high"
|
|
}
|
|
|
|
// Development tools
|
|
if strings.Contains(name, "visual studio") ||
|
|
strings.Contains(name, "vscode") ||
|
|
strings.Contains(name, "git") ||
|
|
strings.Contains(name, "docker") ||
|
|
strings.Contains(name, "nodejs") ||
|
|
strings.Contains(name, "python") ||
|
|
strings.Contains(name, "java") ||
|
|
strings.Contains(name, "powershell") {
|
|
return "moderate"
|
|
}
|
|
|
|
// Microsoft Store apps might be less critical
|
|
if source == "msstore" {
|
|
return "low"
|
|
}
|
|
|
|
// Default severity
|
|
return "moderate"
|
|
}
|
|
|
|
// categorizePackage categorizes the package based on name and source
|
|
func (s *WingetScanner) categorizePackage(name, source string) string {
|
|
lowerName := strings.ToLower(name)
|
|
|
|
// Development tools
|
|
if strings.Contains(lowerName, "visual studio") ||
|
|
strings.Contains(lowerName, "vscode") ||
|
|
strings.Contains(lowerName, "intellij") ||
|
|
strings.Contains(lowerName, "sublime") ||
|
|
strings.Contains(lowerName, "notepad++") ||
|
|
strings.Contains(lowerName, "git") ||
|
|
strings.Contains(lowerName, "docker") ||
|
|
strings.Contains(lowerName, "nodejs") ||
|
|
strings.Contains(lowerName, "python") ||
|
|
strings.Contains(lowerName, "java") ||
|
|
strings.Contains(lowerName, "rust") ||
|
|
strings.Contains(lowerName, "go") ||
|
|
strings.Contains(lowerName, "github") ||
|
|
strings.Contains(lowerName, "postman") ||
|
|
strings.Contains(lowerName, "wireshark") {
|
|
return "development"
|
|
}
|
|
|
|
// Security tools
|
|
if strings.Contains(lowerName, "antivirus") ||
|
|
strings.Contains(lowerName, "security") ||
|
|
strings.Contains(lowerName, "firewall") ||
|
|
strings.Contains(lowerName, "malware") ||
|
|
strings.Contains(lowerName, "defender") ||
|
|
strings.Contains(lowerName, "crowdstrike") ||
|
|
strings.Contains(lowerName, "sophos") ||
|
|
strings.Contains(lowerName, "symantec") ||
|
|
strings.Contains(lowerName, "vpn") ||
|
|
strings.Contains(lowerName, "1password") ||
|
|
strings.Contains(lowerName, "bitwarden") ||
|
|
strings.Contains(lowerName, "lastpass") {
|
|
return "security"
|
|
}
|
|
|
|
// Browsers
|
|
if strings.Contains(lowerName, "firefox") ||
|
|
strings.Contains(lowerName, "chrome") ||
|
|
strings.Contains(lowerName, "edge") ||
|
|
strings.Contains(lowerName, "opera") ||
|
|
strings.Contains(lowerName, "brave") ||
|
|
strings.Contains(lowerName, "vivaldi") ||
|
|
strings.Contains(lowerName, "browser") {
|
|
return "browser"
|
|
}
|
|
|
|
// Communication tools
|
|
if strings.Contains(lowerName, "zoom") ||
|
|
strings.Contains(lowerName, "teams") ||
|
|
strings.Contains(lowerName, "slack") ||
|
|
strings.Contains(lowerName, "discord") ||
|
|
strings.Contains(lowerName, "telegram") ||
|
|
strings.Contains(lowerName, "whatsapp") ||
|
|
strings.Contains(lowerName, "skype") ||
|
|
strings.Contains(lowerName, "outlook") {
|
|
return "communication"
|
|
}
|
|
|
|
// Media and entertainment
|
|
if strings.Contains(lowerName, "vlc") ||
|
|
strings.Contains(lowerName, "spotify") ||
|
|
strings.Contains(lowerName, "itunes") ||
|
|
strings.Contains(lowerName, "plex") ||
|
|
strings.Contains(lowerName, "kodi") ||
|
|
strings.Contains(lowerName, "obs") ||
|
|
strings.Contains(lowerName, "streamlabs") {
|
|
return "media"
|
|
}
|
|
|
|
// Productivity tools
|
|
if strings.Contains(lowerName, "microsoft office") ||
|
|
strings.Contains(lowerName, "word") ||
|
|
strings.Contains(lowerName, "excel") ||
|
|
strings.Contains(lowerName, "powerpoint") ||
|
|
strings.Contains(lowerName, "adobe") ||
|
|
strings.Contains(lowerName, "photoshop") ||
|
|
strings.Contains(lowerName, "acrobat") ||
|
|
strings.Contains(lowerName, "notion") ||
|
|
strings.Contains(lowerName, "obsidian") ||
|
|
strings.Contains(lowerName, "typora") {
|
|
return "productivity"
|
|
}
|
|
|
|
// System utilities
|
|
if strings.Contains(lowerName, "7-zip") ||
|
|
strings.Contains(lowerName, "winrar") ||
|
|
strings.Contains(lowerName, "ccleaner") ||
|
|
strings.Contains(lowerName, "process") ||
|
|
strings.Contains(lowerName, "task manager") ||
|
|
strings.Contains(lowerName, "cpu-z") ||
|
|
strings.Contains(lowerName, "gpu-z") ||
|
|
strings.Contains(lowerName, "hwmonitor") {
|
|
return "utility"
|
|
}
|
|
|
|
// Gaming
|
|
if strings.Contains(lowerName, "steam") ||
|
|
strings.Contains(lowerName, "epic") ||
|
|
strings.Contains(lowerName, "origin") ||
|
|
strings.Contains(lowerName, "uplay") ||
|
|
strings.Contains(lowerName, "gog") ||
|
|
strings.Contains(lowerName, "discord") { // Discord is also gaming
|
|
return "gaming"
|
|
}
|
|
|
|
// Default category
|
|
return "application"
|
|
}
|
|
|
|
// GetPackageDetails retrieves detailed information about a specific winget package
|
|
func (s *WingetScanner) GetPackageDetails(packageID string) (*client.UpdateReportItem, error) {
|
|
if !s.IsAvailable() {
|
|
return nil, fmt.Errorf("winget is not available on this system")
|
|
}
|
|
|
|
// Run winget show command to get detailed package information
|
|
cmd := exec.Command("winget", "show", "--id", packageID, "--output", "json")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to run winget show: %w", err)
|
|
}
|
|
|
|
// Parse JSON output (winget show outputs a single package object)
|
|
var pkg WingetPackage
|
|
if err := json.Unmarshal(output, &pkg); err != nil {
|
|
return nil, fmt.Errorf("failed to parse winget show output: %w", err)
|
|
}
|
|
|
|
// Convert to UpdateReportItem format
|
|
updateItem := s.parseWingetPackage(pkg)
|
|
return updateItem, nil
|
|
}
|
|
|
|
// GetInstalledPackages retrieves all installed packages via winget
|
|
func (s *WingetScanner) GetInstalledPackages() ([]WingetPackage, error) {
|
|
if !s.IsAvailable() {
|
|
return nil, fmt.Errorf("winget is not available on this system")
|
|
}
|
|
|
|
// Run winget list command to get all installed packages
|
|
cmd := exec.Command("winget", "list", "--output", "json")
|
|
output, err := cmd.Output()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to run winget list: %w", err)
|
|
}
|
|
|
|
// Parse JSON output
|
|
var packages []WingetPackage
|
|
if err := json.Unmarshal(output, &packages); err != nil {
|
|
return nil, fmt.Errorf("failed to parse winget JSON output: %w", err)
|
|
}
|
|
|
|
return packages, nil
|
|
}
|
|
|
|
// attemptWingetRecovery tries to fix common winget issues automatically
|
|
func (s *WingetScanner) attemptWingetRecovery() ([]client.UpdateReportItem, error) {
|
|
fmt.Printf("Starting winget recovery process...\n")
|
|
|
|
// Recovery Method 1: Reset winget sources (common fix)
|
|
fmt.Printf("Attempting to reset winget sources...\n")
|
|
if err := s.resetWingetSources(); err == nil {
|
|
if updates, scanErr := s.scanWithJSON(); scanErr == nil {
|
|
fmt.Printf("Recovery successful after source reset\n")
|
|
return updates, nil
|
|
}
|
|
}
|
|
|
|
// Recovery Method 2: Update winget itself (silent)
|
|
fmt.Printf("Attempting to update winget itself...\n")
|
|
if err := s.updateWingetSilent(); err == nil {
|
|
// Wait a moment for winget to stabilize
|
|
time.Sleep(2 * time.Second)
|
|
if updates, scanErr := s.scanWithJSON(); scanErr == nil {
|
|
fmt.Printf("Recovery successful after winget update\n")
|
|
return updates, nil
|
|
}
|
|
}
|
|
|
|
// Recovery Method 3: Repair Windows App Installer (winget backend)
|
|
fmt.Printf("Attempting to repair Windows App Installer...\n")
|
|
if err := s.repairWindowsAppInstaller(); err == nil {
|
|
// Wait longer for system repairs
|
|
time.Sleep(5 * time.Second)
|
|
if updates, scanErr := s.scanWithJSON(); scanErr == nil {
|
|
fmt.Printf("Recovery successful after Windows App Installer repair\n")
|
|
return updates, nil
|
|
}
|
|
}
|
|
|
|
// Recovery Method 4: Force refresh with admin privileges
|
|
fmt.Printf("Attempting admin refresh...\n")
|
|
if updates, err := s.scanWithAdminPrivileges(); err == nil {
|
|
fmt.Printf("Recovery successful with admin privileges\n")
|
|
return updates, nil
|
|
}
|
|
|
|
// If all recovery attempts failed, return the original error
|
|
return nil, fmt.Errorf("all winget recovery attempts failed")
|
|
}
|
|
|
|
// resetWingetSources resets winget package sources
|
|
func (s *WingetScanner) resetWingetSources() error {
|
|
// Reset winget sources silently
|
|
cmd := exec.Command("winget", "source", "reset", "--force")
|
|
_, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Printf("Failed to reset winget sources: %v\n", err)
|
|
return err
|
|
}
|
|
|
|
// Add default sources back
|
|
cmd = exec.Command("winget", "source", "add", "winget", "--accept-package-agreements", "--accept-source-agreements")
|
|
_, err = cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Printf("Failed to add winget source: %v\n", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// updateWingetSilent updates winget itself silently
|
|
func (s *WingetScanner) updateWingetSilent() error {
|
|
// Update winget silently with no interaction
|
|
cmd := exec.Command("winget", "upgrade", "--id", "Microsoft.AppInstaller", "--silent", "--accept-package-agreements", "--accept-source-agreements")
|
|
_, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Printf("Failed to update winget: %v\n", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// repairWindowsAppInstaller attempts to repair the Windows App Installer
|
|
func (s *WingetScanner) repairWindowsAppInstaller() error {
|
|
// Try to repair using PowerShell
|
|
psCmd := `Get-AppxPackage -Name "Microsoft.DesktopAppInstaller" | Repair-AppxPackage -ForceUpdateFromAnyVersion`
|
|
cmd := exec.Command("powershell", "-ExecutionPolicy", "Bypass", "-Command", psCmd)
|
|
_, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Printf("Failed to repair Windows App Installer: %v\n", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// scanWithAdminPrivileges attempts to scan with elevated privileges if available
|
|
func (s *WingetScanner) scanWithAdminPrivileges() ([]client.UpdateReportItem, error) {
|
|
// Try running with elevated privileges using PowerShell
|
|
psCmd := `Start-Process winget -ArgumentList "list","--outdated","--accept-source-agreements" -Verb RunAs -Wait`
|
|
cmd := exec.Command("powershell", "-ExecutionPolicy", "Bypass", "-Command", psCmd)
|
|
|
|
// This will likely fail without actual admin privileges, but we try anyway
|
|
_, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
// Fallback to regular scan with different flags
|
|
return s.scanWithDifferentFlags()
|
|
}
|
|
|
|
// If admin scan succeeded, try to get the results
|
|
return s.scanWithBasicOutput()
|
|
}
|
|
|
|
// scanWithDifferentFlags tries alternative winget flags
|
|
func (s *WingetScanner) scanWithDifferentFlags() ([]client.UpdateReportItem, error) {
|
|
// Try different combination of flags
|
|
flagVariations := [][]string{
|
|
{"list", "--outdated", "--accept-source-agreements"},
|
|
{"list", "--outdated", "--include-unknown"},
|
|
{"list", "--outdated"},
|
|
}
|
|
|
|
for _, flags := range flagVariations {
|
|
cmd := exec.Command("winget", flags...)
|
|
output, err := cmd.CombinedOutput()
|
|
if err == nil {
|
|
// Try to parse the output
|
|
if updates, parseErr := s.parseWingetTextOutput(string(output)); parseErr == nil {
|
|
return updates, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("all flag variations failed")
|
|
} |