Files
driver-booster/internal/winupdate/winupdate.go
taqin 83d7a24d1a Redesign UI like Driver Navigator + fix CMD window
UI changes:
- Top title bar with logo, app name, and system status LED
- Toolbar with 4 icon tabs: Scan, Drivers, Download, System
- Scan page with computer SVG illustration, stats, and big SCAN NOW button
- Step indicator bar: Scan Devices > Review Drivers > Install Updates
- Driver list with per-row progress bars, Download + Install buttons
- "Update All Drivers" CTA bar at bottom when outdated drivers found
- Windows Update tab with pending/installed sections
- System Info tab with resource bars
- Footer with About/Help buttons

Backend fixes:
- Add cmdutil.HiddenCommand() using CREATE_NO_WINDOW (0x08000000)
- All PowerShell subprocesses now run without visible console window
- Build with -ldflags "-H windowsgui" to hide main CMD window

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 20:54:41 +07:00

210 lines
5.8 KiB
Go

package winupdate
import (
"encoding/json"
"time"
"github.com/mumur/driver-booster/internal/cmdutil"
)
type Update struct {
Title string `json:"title"`
Description string `json:"description"`
KBArticle string `json:"kbArticle"`
Category string `json:"category"`
Size string `json:"size"`
IsInstalled bool `json:"isInstalled"`
IsDownloaded bool `json:"isDownloaded"`
IsMandatory bool `json:"isMandatory"`
Severity string `json:"severity"`
}
type CheckResult struct {
Updates []Update `json:"updates"`
PendingCount int `json:"pendingCount"`
InstalledCount int `json:"installedCount"`
CheckTime string `json:"checkTime"`
CheckedAt time.Time `json:"checkedAt"`
Error string `json:"error,omitempty"`
}
type InstallResult struct {
Success bool `json:"success"`
Message string `json:"message"`
}
type Checker struct{}
func NewChecker() *Checker {
return &Checker{}
}
func (c *Checker) Check() CheckResult {
start := time.Now()
result := CheckResult{
Updates: []Update{},
}
// Use PowerShell COM object for Windows Update Agent
psScript := `
$ErrorActionPreference = 'SilentlyContinue'
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
# Get installed update history (recent 50)
$HistoryCount = $Searcher.GetTotalHistoryCount()
$History = $Searcher.QueryHistory(0, [Math]::Min($HistoryCount, 50))
$installed = @()
foreach ($entry in $History) {
if ($entry.Title) {
$installed += @{
Title = $entry.Title
Description = $entry.Description
Date = $entry.Date.ToString("yyyy-MM-dd")
ResultCode = $entry.ResultCode
}
}
}
# Search for pending updates
try {
$SearchResult = $Searcher.Search("IsInstalled=0")
$pending = @()
foreach ($update in $SearchResult.Updates) {
$kb = ""
if ($update.KBArticleIDs.Count -gt 0) { $kb = "KB" + $update.KBArticleIDs.Item(0) }
$cat = ""
if ($update.Categories.Count -gt 0) { $cat = $update.Categories.Item(0).Name }
$sev = if ($update.MsrcSeverity) { $update.MsrcSeverity } else { "Unspecified" }
$sz = ""
if ($update.MaxDownloadSize -gt 0) {
$mb = [Math]::Round($update.MaxDownloadSize / 1MB, 1)
$sz = "$mb MB"
}
$pending += @{
Title = $update.Title
Description = $update.Description
KBArticle = $kb
Category = $cat
Size = $sz
IsDownloaded = $update.IsDownloaded
IsMandatory = $update.IsMandatory
Severity = $sev
}
}
} catch {
$pending = @()
}
@{
Installed = $installed
Pending = $pending
} | ConvertTo-Json -Depth 3 -Compress
`
out, err := cmdutil.HiddenCommand("powershell", "-NoProfile", "-Command", psScript).Output()
if err != nil {
result.Error = "Failed to check updates: " + err.Error()
result.CheckTime = time.Since(start).Round(time.Millisecond).String()
result.CheckedAt = time.Now()
return result
}
var raw struct {
Installed []struct {
Title string `json:"Title"`
Description string `json:"Description"`
Date string `json:"Date"`
ResultCode int `json:"ResultCode"`
} `json:"Installed"`
Pending []struct {
Title string `json:"Title"`
Description string `json:"Description"`
KBArticle string `json:"KBArticle"`
Category string `json:"Category"`
Size string `json:"Size"`
IsDownloaded bool `json:"IsDownloaded"`
IsMandatory bool `json:"IsMandatory"`
Severity string `json:"Severity"`
} `json:"Pending"`
}
if err := json.Unmarshal(out, &raw); err != nil {
result.Error = "Failed to parse update data"
result.CheckTime = time.Since(start).Round(time.Millisecond).String()
result.CheckedAt = time.Now()
return result
}
for _, p := range raw.Pending {
result.Updates = append(result.Updates, Update{
Title: p.Title,
Description: p.Description,
KBArticle: p.KBArticle,
Category: p.Category,
Size: p.Size,
IsInstalled: false,
IsDownloaded: p.IsDownloaded,
IsMandatory: p.IsMandatory,
Severity: p.Severity,
})
result.PendingCount++
}
for _, i := range raw.Installed {
result.Updates = append(result.Updates, Update{
Title: i.Title,
Description: i.Description,
IsInstalled: true,
Category: "Installed",
})
result.InstalledCount++
}
result.CheckTime = time.Since(start).Round(time.Millisecond).String()
result.CheckedAt = time.Now()
return result
}
func (c *Checker) Install(titles []string) InstallResult {
// Trigger Windows Update installation via PowerShell
psScript := `
$Session = New-Object -ComObject Microsoft.Update.Session
$Searcher = $Session.CreateUpdateSearcher()
$SearchResult = $Searcher.Search("IsInstalled=0")
$ToInstall = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($update in $SearchResult.Updates) {
if ($update.IsDownloaded -eq $false) {
$Downloader = $Session.CreateUpdateDownloader()
$dl = New-Object -ComObject Microsoft.Update.UpdateColl
$dl.Add($update) | Out-Null
$Downloader.Updates = $dl
$Downloader.Download() | Out-Null
}
$ToInstall.Add($update) | Out-Null
}
if ($ToInstall.Count -gt 0) {
$Installer = $Session.CreateUpdateInstaller()
$Installer.Updates = $ToInstall
$Result = $Installer.Install()
Write-Output "Installed $($ToInstall.Count) updates. Result: $($Result.ResultCode)"
} else {
Write-Output "No updates to install."
}
`
out, err := cmdutil.HiddenCommand("powershell", "-NoProfile", "-Command", psScript).Output()
if err != nil {
return InstallResult{
Success: false,
Message: "Installation failed: " + err.Error(),
}
}
return InstallResult{
Success: true,
Message: string(out),
}
}