Fixes, Windows Update (step 12), auto-reboot, PS window hide
- Write-Log creates C:\Windows\Setup\Scripts\ automatically (was failing on fresh install) - Step 12: PSWindowsUpdate first pass + X9-WindowsUpdate scheduled task for post-reboot rounds (handles typical 2-3 reboot cycles on fresh Windows, task self-deletes when up to date) - GUI summary: 60s countdown auto-reboot with "Restartovat ted" / "Zrusit restart" buttons - runner: HideWindow=true prevents PS console from appearing over GUI - runner: skipPSNoiseLine filters PS error metadata (CategoryInfo, FullyQualifiedErrorId etc.) - web: fix curl command to include https:// prefix
This commit is contained in:
parent
e62cbaaec3
commit
7e6095d1bd
20 changed files with 247 additions and 5 deletions
|
|
@ -77,6 +77,7 @@ func DefaultConfig() Config {
|
||||||
"backinfo": true,
|
"backinfo": true,
|
||||||
"activation": true,
|
"activation": true,
|
||||||
"dellUpdate": true,
|
"dellUpdate": true,
|
||||||
|
"windowsUpdate": true,
|
||||||
"network": true,
|
"network": true,
|
||||||
"pcIdentity": true,
|
"pcIdentity": true,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -324,11 +325,16 @@ func runPhase(runCfg runner.RunConfig, steps []runner.Step) []runner.Result {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Phase 3 – Summary
|
// Phase 3 – Summary with auto-reboot countdown
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
const rebootCountdown = 60 // seconds before automatic reboot
|
||||||
|
|
||||||
func donePhase(results []runner.Result) {
|
func donePhase(results []runner.Result) {
|
||||||
var mw *walk.MainWindow
|
var (
|
||||||
|
mw *walk.MainWindow
|
||||||
|
countdownLbl *walk.Label
|
||||||
|
)
|
||||||
|
|
||||||
ok, errs, skipped := 0, 0, 0
|
ok, errs, skipped := 0, 0, 0
|
||||||
rows := make([]Widget, 0, len(results))
|
rows := make([]Widget, 0, len(results))
|
||||||
|
|
@ -358,6 +364,9 @@ func donePhase(results []runner.Result) {
|
||||||
|
|
||||||
summaryText := fmt.Sprintf("OK: %d CHYBY: %d PRESKOCENO: %d", ok, errs, skipped)
|
summaryText := fmt.Sprintf("OK: %d CHYBY: %d PRESKOCENO: %d", ok, errs, skipped)
|
||||||
|
|
||||||
|
// cancelled by "Zrusit restart" button
|
||||||
|
cancelReboot := make(chan struct{})
|
||||||
|
|
||||||
if err := (MainWindow{
|
if err := (MainWindow{
|
||||||
AssignTo: &mw,
|
AssignTo: &mw,
|
||||||
Title: "xetup \u2014 hotovo",
|
Title: "xetup \u2014 hotovo",
|
||||||
|
|
@ -371,7 +380,7 @@ func donePhase(results []runner.Result) {
|
||||||
},
|
},
|
||||||
HSeparator{},
|
HSeparator{},
|
||||||
ScrollView{
|
ScrollView{
|
||||||
MinSize: Size{Height: 560},
|
MinSize: Size{Height: 510},
|
||||||
Layout: VBox{MarginsZero: true},
|
Layout: VBox{MarginsZero: true},
|
||||||
Children: rows,
|
Children: rows,
|
||||||
},
|
},
|
||||||
|
|
@ -381,16 +390,34 @@ func donePhase(results []runner.Result) {
|
||||||
Alignment: AlignHCenterVNear,
|
Alignment: AlignHCenterVNear,
|
||||||
Font: Font{Bold: true},
|
Font: Font{Bold: true},
|
||||||
},
|
},
|
||||||
|
Label{
|
||||||
|
AssignTo: &countdownLbl,
|
||||||
|
Text: fmt.Sprintf("Restart za %ds...", rebootCountdown),
|
||||||
|
Alignment: AlignHCenterVNear,
|
||||||
|
},
|
||||||
Composite{
|
Composite{
|
||||||
Layout: HBox{MarginsZero: true},
|
Layout: HBox{MarginsZero: true},
|
||||||
Children: []Widget{
|
Children: []Widget{
|
||||||
HSpacer{},
|
HSpacer{},
|
||||||
PushButton{
|
PushButton{
|
||||||
Text: " ZAVRIT ",
|
Text: " Restartovat ted ",
|
||||||
OnClicked: func() {
|
OnClicked: func() {
|
||||||
|
close(cancelReboot) // signal goroutine to stop ticker
|
||||||
|
reboot()
|
||||||
mw.Close()
|
mw.Close()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
PushButton{
|
||||||
|
Text: " Zrusit restart ",
|
||||||
|
OnClicked: func() {
|
||||||
|
select {
|
||||||
|
case <-cancelReboot: // already closed
|
||||||
|
default:
|
||||||
|
close(cancelReboot)
|
||||||
|
}
|
||||||
|
countdownLbl.SetText("Restart zrusen.")
|
||||||
|
},
|
||||||
|
},
|
||||||
HSpacer{},
|
HSpacer{},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -399,9 +426,42 @@ func donePhase(results []runner.Result) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Countdown goroutine
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
remaining := rebootCountdown
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-cancelReboot:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
remaining--
|
||||||
|
r := remaining
|
||||||
|
mw.Synchronize(func() {
|
||||||
|
if r > 0 {
|
||||||
|
countdownLbl.SetText(fmt.Sprintf("Restart za %ds...", r))
|
||||||
|
} else {
|
||||||
|
countdownLbl.SetText("Restartuji...")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if remaining <= 0 {
|
||||||
|
reboot()
|
||||||
|
mw.Synchronize(func() { mw.Close() })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
mw.Run()
|
mw.Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// reboot issues an immediate Windows restart.
|
||||||
|
func reboot() {
|
||||||
|
exec.Command("shutdown", "/r", "/t", "0").Run() //nolint:errcheck
|
||||||
|
}
|
||||||
|
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
// Helpers
|
// Helpers
|
||||||
// --------------------------------------------------------------------------
|
// --------------------------------------------------------------------------
|
||||||
|
|
|
||||||
7
internal/runner/hidecmd_other.go
Normal file
7
internal/runner/hidecmd_other.go
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import "os/exec"
|
||||||
|
|
||||||
|
func hideWindow(cmd *exec.Cmd) {}
|
||||||
14
internal/runner/hidecmd_windows.go
Normal file
14
internal/runner/hidecmd_windows.go
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package runner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// hideWindow prevents the child process from creating a visible console window.
|
||||||
|
// Without this, powershell.exe opens a full-size window on top of the GUI.
|
||||||
|
func hideWindow(cmd *exec.Cmd) {
|
||||||
|
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||||
|
}
|
||||||
|
|
@ -36,6 +36,7 @@ func AllSteps() []Step {
|
||||||
{ID: "backinfo", Num: "07", Name: "BackInfo", ScriptName: "07-backinfo.ps1"},
|
{ID: "backinfo", Num: "07", Name: "BackInfo", ScriptName: "07-backinfo.ps1"},
|
||||||
{ID: "activation", Num: "08", Name: "Windows aktivace", ScriptName: "08-activation.ps1"},
|
{ID: "activation", Num: "08", Name: "Windows aktivace", ScriptName: "08-activation.ps1"},
|
||||||
{ID: "dellUpdate", Num: "11", Name: "Dell Command | Update", ScriptName: "11-dell-update.ps1"},
|
{ID: "dellUpdate", Num: "11", Name: "Dell Command | Update", ScriptName: "11-dell-update.ps1"},
|
||||||
|
{ID: "windowsUpdate", Num: "12", Name: "Windows Update", ScriptName: "12-windows-update.ps1"},
|
||||||
{ID: "network", Num: "09", Name: "Network discovery", ScriptName: "10-network.ps1"},
|
{ID: "network", Num: "09", Name: "Network discovery", ScriptName: "10-network.ps1"},
|
||||||
{ID: "pcIdentity", Num: "10", Name: "PC identita", ScriptName: "09-pc-identity.ps1"},
|
{ID: "pcIdentity", Num: "10", Name: "PC identita", ScriptName: "09-pc-identity.ps1"},
|
||||||
}
|
}
|
||||||
|
|
@ -222,6 +223,7 @@ func (r *Runner) runScript(ctx context.Context, step Step, cfgArg string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "powershell.exe", args...)
|
cmd := exec.CommandContext(ctx, "powershell.exe", args...)
|
||||||
|
hideWindow(cmd) // prevent PS console window from appearing over the GUI
|
||||||
|
|
||||||
stdout, err := cmd.StdoutPipe()
|
stdout, err := cmd.StdoutPipe()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -236,6 +238,9 @@ func (r *Runner) runScript(ctx context.Context, step Step, cfgArg string) error
|
||||||
scanner := bufio.NewScanner(stdout)
|
scanner := bufio.NewScanner(stdout)
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
|
if skipPSNoiseLine(line) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
r.onLog(LogLine{
|
r.onLog(LogLine{
|
||||||
StepID: step.ID,
|
StepID: step.ID,
|
||||||
Text: line,
|
Text: line,
|
||||||
|
|
@ -246,6 +251,33 @@ func (r *Runner) runScript(ctx context.Context, step Step, cfgArg string) error
|
||||||
return cmd.Wait()
|
return cmd.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skipPSNoiseLine returns true for PowerShell stderr noise that clutters the log:
|
||||||
|
// multi-line error blocks (At line:N, CategoryInfo, FullyQualifiedErrorId, etc.),
|
||||||
|
// blank lines, and VERBOSE: prefix lines already handled by Write-Log.
|
||||||
|
func skipPSNoiseLine(line string) bool {
|
||||||
|
trimmed := strings.TrimSpace(line)
|
||||||
|
if trimmed == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, prefix := range []string{
|
||||||
|
"At line:",
|
||||||
|
"+ CategoryInfo",
|
||||||
|
"+ FullyQualifiedErrorId",
|
||||||
|
"+ PositionMessage",
|
||||||
|
"VERBOSE:",
|
||||||
|
"DEBUG:",
|
||||||
|
} {
|
||||||
|
if strings.HasPrefix(trimmed, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// PS error continuation lines start with spaces + "+" or "~"
|
||||||
|
if len(trimmed) > 0 && (trimmed[0] == '+' || strings.HasPrefix(trimmed, "~")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// parseLevel extracts the log level from lines formatted as "[HH:mm:ss] [LEVEL] message".
|
// parseLevel extracts the log level from lines formatted as "[HH:mm:ss] [LEVEL] message".
|
||||||
func parseLevel(line string) string {
|
func parseLevel(line string) string {
|
||||||
if strings.Contains(line, "] [OK]") {
|
if strings.Contains(line, "] [OK]") {
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ $ErrorActionPreference = "Continue"
|
||||||
function Write-Log {
|
function Write-Log {
|
||||||
param([string]$Message, [string]$Level = "INFO")
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
switch ($Level) {
|
switch ($Level) {
|
||||||
"OK" { Write-Host $line -ForegroundColor Green }
|
"OK" { Write-Host $line -ForegroundColor Green }
|
||||||
|
|
|
||||||
115
scripts/12-windows-update.ps1
Normal file
115
scripts/12-windows-update.ps1
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Installs all available Windows Updates via PSWindowsUpdate module.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
First pass: installs all currently available updates without rebooting.
|
||||||
|
Then registers a scheduled task "X9-WindowsUpdate" that runs on every
|
||||||
|
logon (as SYSTEM) until no more updates are found - handles the typical
|
||||||
|
2-3 reboot cycles required on a fresh Windows installation.
|
||||||
|
|
||||||
|
Operator workflow:
|
||||||
|
1. xetup completes all steps
|
||||||
|
2. Operator reboots manually
|
||||||
|
3. On each subsequent logon the scheduled task runs another update pass
|
||||||
|
4. Task removes itself automatically when system is fully up to date
|
||||||
|
|
||||||
|
.ITEMS
|
||||||
|
nainstalovat-pswindowsupdate-modul: Installs NuGet provider and PSWindowsUpdate module from PSGallery.
|
||||||
|
spustit-prvni-kolo-windows-update: First update pass without reboot - installs all currently available updates.
|
||||||
|
registrovat-scheduled-task-pro-dalsi-kola: Registers X9-WindowsUpdate scheduled task that runs on logon, handles post-reboot update rounds, and self-deletes when no more updates are found.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[object]$Config,
|
||||||
|
[string]$LogFile
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
param([string]$Message, [string]$Level = "INFO")
|
||||||
|
$line = "[$(Get-Date -Format 'HH:mm:ss')] [$Level] $Message"
|
||||||
|
$null = New-Item -ItemType Directory -Force -Path (Split-Path $LogFile -Parent) -ErrorAction SilentlyContinue
|
||||||
|
Add-Content -Path $LogFile -Value $line -Encoding UTF8
|
||||||
|
Write-Output $line
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "=== Step 12 - Windows Update ===" -Level STEP
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 1. NuGet provider + PSWindowsUpdate module
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Setting up PSWindowsUpdate module..." -Level INFO
|
||||||
|
try {
|
||||||
|
Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope AllUsers | Out-Null
|
||||||
|
$existing = Get-Module -ListAvailable -Name PSWindowsUpdate | Select-Object -First 1
|
||||||
|
if ($existing) {
|
||||||
|
Write-Log " PSWindowsUpdate $($existing.Version) already installed" -Level INFO
|
||||||
|
} else {
|
||||||
|
Install-Module -Name PSWindowsUpdate -Force -Scope AllUsers -AllowClobber | Out-Null
|
||||||
|
Write-Log " PSWindowsUpdate installed" -Level OK
|
||||||
|
}
|
||||||
|
Import-Module PSWindowsUpdate -Force
|
||||||
|
} catch {
|
||||||
|
Write-Log " Module setup failed: $_" -Level ERROR
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 2. First update pass (no reboot)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Running first Windows Update pass..." -Level INFO
|
||||||
|
try {
|
||||||
|
$result = Install-WindowsUpdate -AcceptAll -IgnoreReboot -Verbose 2>&1
|
||||||
|
$installed = @($result | Where-Object { $_ -match 'KB\d+|Downloaded|Installed' })
|
||||||
|
if ($installed.Count -gt 0) {
|
||||||
|
$result | Where-Object { "$_" -match '\S' } | ForEach-Object { Write-Log " $_" -Level INFO }
|
||||||
|
Write-Log " First pass complete - reboot required for remaining rounds" -Level OK
|
||||||
|
} else {
|
||||||
|
Write-Log " System already up to date" -Level OK
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
Write-Log " First pass failed: $_" -Level ERROR
|
||||||
|
}
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
# 3. Scheduled task for post-reboot update rounds (self-deleting)
|
||||||
|
# -----------------------------------------------------------------------
|
||||||
|
Write-Log "Registering post-reboot update task..." -Level INFO
|
||||||
|
|
||||||
|
$taskName = "X9-WindowsUpdate"
|
||||||
|
|
||||||
|
# PowerShell block that runs on each logon until no more updates found
|
||||||
|
$updateScript = @'
|
||||||
|
Import-Module PSWindowsUpdate -Force -ErrorAction Stop
|
||||||
|
$updates = Get-WindowsUpdate -AcceptAll -IgnoreReboot
|
||||||
|
if ($updates) {
|
||||||
|
Install-WindowsUpdate -AcceptAll -IgnoreReboot | Out-File "C:\Windows\Setup\Scripts\wu-pass-$(Get-Date -Format 'yyyyMMdd-HHmmss').log" -Encoding UTF8
|
||||||
|
} else {
|
||||||
|
# No more updates - remove this task
|
||||||
|
Unregister-ScheduledTask -TaskName "X9-WindowsUpdate" -Confirm:$false
|
||||||
|
}
|
||||||
|
'@
|
||||||
|
|
||||||
|
try {
|
||||||
|
$action = New-ScheduledTaskAction -Execute "powershell.exe" `
|
||||||
|
-Argument "-NonInteractive -WindowStyle Hidden -ExecutionPolicy Bypass -Command `"$updateScript`""
|
||||||
|
$trigger = New-ScheduledTaskTrigger -AtLogOn
|
||||||
|
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit (New-TimeSpan -Hours 2) `
|
||||||
|
-MultipleInstances IgnoreNew
|
||||||
|
$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -RunLevel Highest
|
||||||
|
|
||||||
|
# Remove existing task first (idempotent)
|
||||||
|
Unregister-ScheduledTask -TaskName $taskName -Confirm:$false -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger `
|
||||||
|
-Settings $settings -Principal $principal -Force | Out-Null
|
||||||
|
|
||||||
|
Write-Log " Task '$taskName' registered - runs on each logon until fully updated" -Level OK
|
||||||
|
} catch {
|
||||||
|
Write-Log " Failed to register scheduled task: $_" -Level WARN
|
||||||
|
Write-Log " Manual Windows Update rounds will be needed after reboot" -Level WARN
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Step 12 - Windows Update complete" -Level OK
|
||||||
|
Write-Log " ACTION REQUIRED: Reboot the machine to complete remaining update rounds" -Level WARN
|
||||||
|
|
@ -178,7 +178,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="install-box">
|
<div class="install-box">
|
||||||
<code id="install-cmd">curl -Lo xetup.exe xetup.x9.cz/dl</code>
|
<code id="install-cmd">curl -Lo xetup.exe https://xetup.x9.cz/dl</code>
|
||||||
<button id="copy-btn" onclick="copyCmd()">Kopirovat</button>
|
<button id="copy-btn" onclick="copyCmd()">Kopirovat</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue