A prog2-höz tartozó friss repo anyagok itt elérhetőek: https://git.iit.bme.hu/prog2

Commit d99e366f by Szeberényi Imre

win_exe + *.ps1

parent fe93383e
......@@ -7,6 +7,8 @@ version.txt
.coverage
*~
*.log
build
dist
build/
dist/
*.spec
.venv/
.venv64/
\ No newline at end of file
# WinVM-IOPS-Apply.ps1 (v1.5)
# Applies low-IOPS template settings on Windows 10/11
# Updates:
# - SCM recovery policy for circle-agent + circle-watchdog (reset=86400, restart=180000 x3, failureflag=1)
# - Replace Startup shortcut autostart with Task Scheduler for circle-notify.exe (logon, single instance, restart on failure)
# - Simplify hive handling: assume script is run by cloud user in elevated PS; use HKCU only (plus HKU\.DEFAULT)
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Apply.ps1
# ASCII-only comments
param(
# Pagefile size: you can pass 4096 (MB) or "4G" / "4096M"
[string]$Pagefile = "4G",
# Event log max sizes in MB
[int]$AppLogMB = 4,
[int]$SysLogMB = 4,
[int]$SecLogMB = 20,
[switch]$WhatIf
)
# --- Parameter validation / help ---
# If any unnamed or unknown arguments are passed, show help and exit.
if ($args.Count -gt 0) {
Write-Host ""
Write-Host "ERROR: Unknown parameter(s): $($args -join ' ')" -ForegroundColor Red
Write-Host ""
Write-Host "Usage:"
Write-Host " powershell -ExecutionPolicy Bypass -File WinVM-IOPS-Apply.ps1 [-Pagefile <size>] [-WhatIf]"
Write-Host ""
Write-Host "Options:"
Write-Host " -Pagefile <size> Pagefile size (default: 4G)"
Write-Host " Examples: 4096 | 4096M | 4G | 6G"
Write-Host " -WhatIf Show what would be changed, do not apply"
Write-Host ""
exit 1
}
$ErrorActionPreference = "Inquire"
# include WU core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
function Say($msg) { Write-Host $msg }
function Require-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "Run this script as Administrator."
}
}
function Run($desc, [ScriptBlock]$action) {
if ($WhatIf) {
Say ("[WHATIF] {0}" -f $desc)
} else {
Say ("[DO] {0}" -f $desc)
& $action
}
}
function Parse-SizeToMB([string]$s) {
if ([string]::IsNullOrWhiteSpace($s)) { return 4096 }
$t = $s.Trim().ToUpperInvariant()
# If it's pure number, treat as MB
if ($t -match '^\d+$') { return [int]$t }
# Accept forms like 4G, 4096M
if ($t -match '^(\d+)\s*G$') { return [int]$Matches[1] * 1024 }
if ($t -match '^(\d+)\s*M$') { return [int]$Matches[1] }
throw "Invalid -Pagefile value '$s'. Use e.g. 4096, 4096M, or 4G."
}
function Set-ServiceDisabled($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Stop service $name" { Stop-Service -Name $name -Force -ErrorAction SilentlyContinue }
Run "Disable service $name" { Set-Service -Name $name -StartupType Disabled -ErrorAction SilentlyContinue }
}
function Set-ServiceManual($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Stop service $name" { Stop-Service -Name $name -Force -ErrorAction SilentlyContinue }
Run "Set service $name to Manual" { Set-Service -Name $name -StartupType Manual -ErrorAction SilentlyContinue }
}
function Clear-EventLogBestEffort($logName) {
Run "Clear EventLog '$logName' (best effort)" {
& wevtutil cl $logName 2>$null | Out-Null
}
}
function Set-EventLogMaxMB($logName, $mb) {
# wevtutil expects bytes
$bytes = [int64]$mb * 1024 * 1024
Run "Set EventLog '$logName' max size to ${mb}MB" {
& wevtutil sl $logName /ms:$bytes 2>$null | Out-Null
}
}
function Disable-ScheduledTaskSafe($taskPath) {
Run "Disable scheduled task $taskPath" {
$tp = ([IO.Path]::GetDirectoryName($taskPath) + "\")
$tn = ([IO.Path]::GetFileName($taskPath))
Disable-ScheduledTask -TaskPath $tp -TaskName $tn -ErrorAction SilentlyContinue | Out-Null
}
}
# Helper: set Desktop shutdown behavior values under a registry root
function Set-DesktopShutdownTweaks($root) {
Run "Set AutoEndTasks/timeout tweaks in $root" {
& reg add "$root\Control Panel\Desktop" /v AutoEndTasks /t REG_SZ /d 1 /f | Out-Null
& reg add "$root\Control Panel\Desktop" /v HungAppTimeout /t REG_SZ /d 2000 /f | Out-Null
& reg add "$root\Control Panel\Desktop" /v WaitToKillAppTimeout /t REG_SZ /d 5000 /f | Out-Null
}
}
# Helper: set user env vars (TEMP/TMP) under a registry root
function Set-UserEnvTempTmp($root, [string]$tempPath) {
Run "Set TEMP/TMP (User) in $root to $tempPath" {
& reg add "$root\Environment" /v TEMP /t REG_SZ /d $tempPath /f | Out-Null
& reg add "$root\Environment" /v TMP /t REG_SZ /d $tempPath /f | Out-Null
}
}
# --- SCM recovery helpers (services) ---
function Set-ServiceRecoveryPolicy($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Set SCM recovery policy for service '$name' (reset=86400, restart=180000 x3)" {
& sc.exe failure "$name" reset= 86400 actions= restart/180000/restart/180000/restart/180000 | Out-Null
& sc.exe failureflag "$name" 1 | Out-Null
}
}
# --- Task Scheduler helper (circle-notify.exe) ---
function Ensure-CircleNotifyLogonTask(
[string]$taskName,
[string]$exePath
) {
if (-not (Test-Path $exePath)) {
Say (" WARN: circle-notify exe not found: {0} (task not created)" -f $exePath)
return
}
Run "Create/Update Scheduled Task '$taskName' (At logon, single instance, restart on failure)" {
$action = New-ScheduledTaskAction -Execute $exePath
# Trigger at logon for current user
$trigger = New-ScheduledTaskTrigger -AtLogOn
# Run only when user is logged on (no password prompt)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
-RestartCount 5 `
-RestartInterval (New-TimeSpan -Minutes 3)
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
}
}
# --- Remove legacy client from Startup
function Remove-LegacyStartupClientArtifacts {
# Remove legacy Startup items for old client.exe autostart (best effort)
# Kill running legacy process only if legacy startup artifact exists.
# Robust against weird object[] values; uses Path.Combine.
$appData = $env:APPDATA
if ($appData -is [System.Array]) { $appData = $appData[0] }
$appData = [string]$appData
if ([string]::IsNullOrWhiteSpace($appData)) { return }
$startup = [System.IO.Path]::Combine($appData, "Microsoft\Windows\Start Menu\Programs\Startup")
if (-not (Test-Path $startup)) { return }
$names = @(
"client.exe",
"client.lnk"
)
$candidatePaths = foreach ($n in $names) { [System.IO.Path]::Combine($startup, $n) }
# Gather .lnk files once
$lnkFiles = @()
try { $lnkFiles = Get-ChildItem -Path $startup -Filter "*.lnk" -ErrorAction SilentlyContinue } catch {}
# Detect whether any legacy startup artifact exists (file or lnk->client.exe)
$hasLegacy = $false
foreach ($p in $candidatePaths) {
if (Test-Path $p) { $hasLegacy = $true; break }
}
if (-not $hasLegacy -and $lnkFiles.Count -gt 0) {
foreach ($lnk in $lnkFiles) {
try {
$wsh = New-Object -ComObject WScript.Shell
$sc = $wsh.CreateShortcut($lnk.FullName)
$tp = $sc.TargetPath
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($sc) | Out-Null } catch {}
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($wsh) | Out-Null } catch {}
if ($tp -and ($tp.ToLowerInvariant().EndsWith("\client.exe"))) {
$hasLegacy = $true
break
}
} catch {
# ignore
}
}
}
if (-not $hasLegacy) {
Say " (skip) no legacy client.exe Startup artifact detected."
return
}
# If we get here, legacy startup exists -> kill legacy client.exe first (often 2 procs due to PyInstaller)
Run "Stop legacy client.exe processes (best effort)" {
$legacyPaths = @(
"c:\circle\client.exe",
($startup.ToLowerInvariant() + "\client.exe")
)
try {
$procs = Get-Process -Name "client" -ErrorAction SilentlyContinue
if ($procs) {
foreach ($p in $procs) {
# Prefer safe path match if we can read it
try {
if ($p.Path) {
$pp = $p.Path.ToLowerInvariant()
if ($legacyPaths -contains $pp) {
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
}
} else {
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
}
} catch {
# Fall back to killing by Id if anything weird happens
try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {}
}
}
}
} catch {}
}
# Now remove legacy startup items
Run "Remove legacy Startup artifacts (client.exe / old shortcuts) from $startup" {
foreach ($p in $candidatePaths) {
if (Test-Path $p) {
Remove-Item -LiteralPath $p -Force -ErrorAction SilentlyContinue
}
}
# Remove any .lnk that points to *\client.exe
foreach ($lnk in $lnkFiles) {
try {
$wsh = New-Object -ComObject WScript.Shell
$sc = $wsh.CreateShortcut($lnk.FullName)
$tp = $sc.TargetPath
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($sc) | Out-Null } catch {}
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($wsh) | Out-Null } catch {}
if ($tp -and ($tp.ToLowerInvariant().EndsWith("\client.exe"))) {
Remove-Item -LiteralPath $lnk.FullName -Force -ErrorAction SilentlyContinue
}
} catch {
# ignore
}
}
}
}
Require-Admin
# Cache CS once (avoid repeated CIM)
$cs = $null
try { $cs = Get-CimInstance Win32_ComputerSystem } catch {}
# Normalize pagefile MB
$pagefileMB = Parse-SizeToMB $Pagefile
Say "=== Apply low-IOPS template settings (v1.5) ==="
Say ("Computer: {0}" -f $env:COMPUTERNAME)
Say ("Pagefile target: {0} MB" -f $pagefileMB)
Say ""
# 1) Pagefile fixed size (robust: set via registry)
Run "Set fixed pagefile ${pagefileMB}/${pagefileMB} MB on C:\" {
# Disable automatic pagefile management (CIM)
if ($cs) {
Set-CimInstance -InputObject $cs -Property @{ AutomaticManagedPagefile = $false } -Confirm:$false | Out-Null
}
# Set PagingFiles (REG_MULTI_SZ): "path initialMB maximumMB"
$pf = "C:\pagefile.sys $pagefileMB $pagefileMB"
& reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" `
/v PagingFiles /t REG_MULTI_SZ /d $pf /f | Out-Null
# Optional: clear temp pagefile flag if present
& reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" `
/v TempPageFile /t REG_DWORD /d 0 /f | Out-Null
}
# 2) Disable noisy services
Say ""
Say "Services:"
Set-ServiceDisabled "WSearch" # Windows Search
Set-ServiceDisabled "SysMain" # Superfetch/SysMain
# Windows Update related (lab template style)
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode Disable
# 3) Defender scheduled scan -> Never (Day=8)
Say ""
Say "Defender:"
Run "Set Defender scheduled scan day to Never (8)" {
Set-MpPreference -ScanScheduleDay 8 -ErrorAction SilentlyContinue | Out-Null
}
# 3.1) Defender exclusions (low-IOPS friendly)
Say ""
Say "Defender Exclusions:"
$DefenderExclusions = @(
"C:\Windows\Temp",
"C:\Windows\SoftwareDistribution",
"C:\ProgramData\Microsoft\Windows Defender",
"C:\pagefile.sys"
)
foreach ($path in $DefenderExclusions) {
Run "Add Defender exclusion $path" {
Add-MpPreference -ExclusionPath $path -ErrorAction SilentlyContinue | Out-Null
}
}
# 4) NTFS LastAccess disable
Say ""
Say "NTFS:"
Run "Disable NTFS LastAccess updates (DisableLastAccess=1)" {
& fsutil behavior set disablelastaccess 1 | Out-Null
}
# 5) Event log sizes (clear then set)
Say ""
Say "Event Logs:"
Clear-EventLogBestEffort "Application"
Clear-EventLogBestEffort "System"
Clear-EventLogBestEffort "Security"
Set-EventLogMaxMB "Application" $AppLogMB
Set-EventLogMaxMB "System" $SysLogMB
Set-EventLogMaxMB "Security" $SecLogMB
# 6) (Optional-ish) Try to disable common Update scheduled tasks
# moved to WU core
# 7) ACPI shutdown reliability (RDP-friendly)
Say ""
Say "ACPI Shutdown reliability:"
# 7.1) Ensure Power Button action = Shutdown (AC and DC)
Run "Set power button action to Shutdown (AC/DC)" {
& powercfg /setacvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3 | Out-Null
& powercfg /setdcvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3 | Out-Null
& powercfg /setactive SCHEME_CURRENT | Out-Null
}
# 7.2) Apply to the default (logon screen / disconnected situations)
Set-DesktopShutdownTweaks "HKU\.DEFAULT"
# 7.3) Apply to current user (cloud) - simplified
Say " Apply Desktop tweaks to HKCU (current user)"
Set-DesktopShutdownTweaks "HKCU"
# 7.4) Service shutdown timeout (system-wide)
Run "Set WaitToKillServiceTimeout=5000ms" {
& reg add "HKLM\SYSTEM\CurrentControlSet\Control" /v WaitToKillServiceTimeout /t REG_SZ /d 5000 /f | Out-Null
}
# 7.5) TEMP/TMP (User-level) for current user (cloud) - simplified
Say ""
Say "Environment variables (current user TEMP/TMP):"
$fallbackTemp = Join-Path $env:USERPROFILE "AppData\Local\Temp"
Set-UserEnvTempTmp "HKCU" $fallbackTemp
# 8) CIRCLE services (recovery hardening)
Say ""
Say "CIRCLE services (recovery hardening):"
Set-ServiceRecoveryPolicy "circle-agent"
Set-ServiceRecoveryPolicy "circle-watchdog"
# 9) CIRCLE notify autostart via Task Scheduler (logon)
Say ""
Say "CIRCLE notify autostart (Scheduled Task):"
# Remove legacy Startup autostart entries for old client.exe
Remove-LegacyStartupClientArtifacts
$notifyExe = "C:\circle\circle-notify.exe"
$taskName = "CIRCLE circle-notify (Logon)"
Ensure-CircleNotifyLogonTask -taskName $taskName -exePath $notifyExe
Say ""
Say "Done."
Say "NOTE: Reboot recommended (pagefile + services)."
# WinVM-IOPS-Check.ps1 (v1.7)
# Checks Windows VM settings for low-IOPS template readiness
# Updates:
# - Fix recommendations collection (no [ref] += issues)
# - CIRCLE service names: circle-agent, circle-watchdog
# - CIRCLE notify autostart via Task Scheduler (no Startup shortcut)
# - Simplified user/hive assumptions: run by cloud user in elevated PS; use HKCU only (plus HKU\.DEFAULT)
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Check.ps1
# ASCII-only comments
# ASCII-only comments
param(
# Expected fixed pagefile size (MB). Default 4096 (4G).
[int]$ExpectedPagefileMB = 4096,
# Expected EventLog max sizes (MB)
[int]$ExpectedAppLogMB = 4,
[int]$ExpectedSysLogMB = 4,
[int]$ExpectedSecLogMB = 20
)
$ErrorActionPreference = "Continue"
function Say($msg="") { Write-Host $msg }
function ToGB([double]$bytes) {
[Math]::Round($bytes / 1GB, 2)
}
# Recommendations bucket (ArrayList: robust Add)
$recs = New-Object System.Collections.ArrayList
function Add-Rec([string]$level, [string]$msg) {
[void]$recs.Add((" {0}: {1}" -f $level, $msg))
}
function Get-ServiceStartType($name) {
try {
$svc = Get-CimInstance Win32_Service -Filter ("Name='{0}'" -f $name) -ErrorAction Stop
return $svc.StartMode
} catch {
return "Unknown"
}
}
function Format-ScanDay($day) {
switch ($day) {
0 { "Everyday" }
1 { "Sunday" }
2 { "Monday" }
3 { "Tuesday" }
4 { "Wednesday" }
5 { "Thursday" }
6 { "Friday" }
7 { "Saturday" }
8 { "Never" }
default { "$day" }
}
}
# Expectations helper (services)
function Expect-ServiceInline($svcState, [string]$name, $wantStartTypes, $wantStatus = $null) {
if (-not $svcState.ContainsKey($name)) {
Add-Rec "WARN" ("Service {0} not checked." -f $name)
return
}
$st = $svcState[$name]
if ($wantStartTypes -isnot [System.Array]) { $wantStartTypes = @($wantStartTypes) }
if ($wantStartTypes -notcontains $st.StartType) {
Add-Rec "WARN" ("Service {0} StartType is {1} (expected one of: {2})." -f $name, $st.StartType, ($wantStartTypes -join ", "))
}
if ($wantStatus -and ($st.Status -ne $wantStatus)) {
Add-Rec "WARN" ("Service {0} Status is {1} (expected {2})." -f $name, $st.Status, $wantStatus)
}
}
# Small env getter (no duplicates)
function Get-Env([string]$name, [string]$scope) {
try { return [Environment]::GetEnvironmentVariable($name, $scope) } catch { return $null }
}
function Get-RegStr($path, $name) {
try { (Get-ItemProperty $path -Name $name -ErrorAction Stop).$name } catch { $null }
}
# Parse sc.exe qfailure output (best effort)
function Get-ScFailureSummary([string]$svcName) {
try {
$lines = & sc.exe qfailure $svcName 2>$null
if (-not $lines) { return $null }
$txt = ($lines -join "`n")
$reset = $null
if ($txt -match 'RESET_PERIOD.*:\s*(\d+)') { $reset = [int]$Matches[1] }
$hasRestart = ($txt -match 'RESTART')
return @{
Text = $txt
Reset = $reset
HasRestart = $hasRestart
}
} catch {
return $null
}
}
# Compare recovery policy to expected: reset=86400, restart/180000 x3, failureflag=1
function Validate-CircleServiceRecovery([string]$svcName, [string]$displayName) {
$sum = Get-ScFailureSummary $svcName
if (-not $sum) {
Add-Rec "INFO" ("{0} recovery info unavailable (sc qfailure failed)." -f $displayName)
return
}
if (-not $sum.HasRestart) {
Add-Rec "WARN" ("{0} recovery actions do not include RESTART." -f $displayName)
}
if ($sum.Reset -ne $null -and $sum.Reset -ne 86400) {
Add-Rec "WARN" ("{0} recovery reset period is {1}s (expected 86400)." -f $displayName, $sum.Reset)
} elseif ($sum.Reset -eq $null) {
Add-Rec "INFO" ("{0} recovery reset period not parsed (verify via sc qfailure)." -f $displayName)
}
# Validate restart delay(s) 180000 (best effort)
if ($sum.Text -notmatch '180000') {
Add-Rec "WARN" ("{0} recovery does not mention 180000ms delay (expected restart/180000 x3)." -f $displayName)
}
# failureflag (non-crash) check (best effort parse)
try {
$ff = & sc.exe qfailureflag $svcName 2>$null
if ($ff) {
$t = ($ff -join "`n")
if ($t -match 'FAILURE_ACTIONS_ON_NONCRASH_FAILURES\s*:\s*(TRUE|FALSE|[01])') {
$raw = $Matches[1].ToUpperInvariant()
$ok = ($raw -eq "TRUE" -or $raw -eq "1")
if (-not $ok) {
Add-Rec "WARN" ("{0} failureflag is {1} (expected TRUE/1)." -f $displayName, $Matches[1])
}
} else {
Add-Rec "INFO" ("{0} failureflag output not parsed; verify manually if needed." -f $displayName)
}
} else {
Add-Rec "INFO" ("{0} failureflag info unavailable (sc qfailureflag empty)." -f $displayName)
}
} catch {
Add-Rec "INFO" ("{0} failureflag query failed." -f $displayName)
}
}
Say "=== Windows VM low-IOPS template check (v1.7) ==="
Say ("Computer: {0}" -f $env:COMPUTERNAME)
Say ("User: {0}" -f $env:USERNAME)
# One-time queries
$os = $null
$cs = $null
try { $os = Get-CimInstance Win32_OperatingSystem } catch {}
try { $cs = Get-CimInstance Win32_ComputerSystem } catch {}
if ($os -and $os.Caption) { Say ("OS: {0}" -f $os.Caption) }
else { Say "OS: (unknown)" }
Say ""
# -----------------------
# Memory / Commit (uses $os/$cs once)
# -----------------------
Say "Memory:"
try {
if ($os -and $cs) {
$commitUsedGB = ToGB([double]$os.TotalVirtualMemorySize * 1KB - [double]$os.FreeVirtualMemory * 1KB)
$commitLimitGB = ToGB([double]$os.TotalVirtualMemorySize * 1KB)
$ramGB = [Math]::Round(([double]$cs.TotalPhysicalMemory / 1GB), 2)
Say (" RAM (GB): {0}" -f $ramGB)
Say (" Commit used (GB): {0}" -f $commitUsedGB)
Say (" Commit limit (GB): {0}" -f $commitLimitGB)
} else {
Say " (unable to query)"
}
} catch {
Say " (unable to query)"
}
Say ""
# -----------------------
# Pagefile
# -----------------------
Say "Pagefile:"
$pfReg = $null
try {
$pfReg = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" -ErrorAction Stop).PagingFiles
Say (" Registry PagingFiles: {0}" -f ($pfReg -join " | "))
} catch {
Say " Registry PagingFiles: (unable to read)"
Add-Rec "WARN" "Registry PagingFiles could not be read."
}
try {
if ($cs -and ($cs.AutomaticManagedPagefile -ne $false)) {
Add-Rec "WARN" "AutomaticManagedPagefile is not False (pagefile may be auto-managed)."
}
} catch {}
try {
if ($pfReg) {
$want = $ExpectedPagefileMB
$hit = $false
foreach ($line in $pfReg) {
if ($line -match '^[Cc]:\\pagefile\.sys\s+(\d+)\s+(\d+)$') {
$i = [int]$Matches[1]; $m = [int]$Matches[2]
if ($i -eq $want -and $m -eq $want) {
$hit = $true
} else {
Add-Rec "WARN" ("Registry PagingFiles is '{0}' (expected {1}/{1} MB)." -f $line, $want)
}
}
}
if (-not $hit) {
Add-Rec "WARN" ("Registry PagingFiles does not contain C:\pagefile.sys {0} {0}." -f $ExpectedPagefileMB)
}
}
} catch {}
$pfs = $null
try { $pfs = Get-CimInstance Win32_PageFileSetting -ErrorAction SilentlyContinue } catch {}
if ($pfs) {
foreach ($p in $pfs) {
Say (" {0} InitialMB={1} MaximumMB={2}" -f $p.Name, $p.InitialSize, $p.MaximumSize)
if ($p.Name -match 'pagefile\.sys') {
if (($p.InitialSize -ne $ExpectedPagefileMB) -or ($p.MaximumSize -ne $ExpectedPagefileMB)) {
Add-Rec "WARN" ("WMI PageFileSetting {0} is {1}/{2} MB (expected {3}/{3} MB)." -f $p.Name, $p.InitialSize, $p.MaximumSize, $ExpectedPagefileMB)
}
}
}
} else {
Say " Win32_PageFileSetting: (none)"
Add-Rec "WARN" "Win32_PageFileSetting is empty/unavailable (cannot confirm fixed pagefile via WMI)."
}
$pfu = $null
try { $pfu = Get-CimInstance Win32_PageFileUsage -ErrorAction SilentlyContinue } catch {}
if ($pfu) {
foreach ($u in $pfu) {
Say (" Usage: {0} AllocatedMB={1} CurrentUsedMB={2} PeakUsedMB={3}" -f $u.Name, $u.AllocatedBaseSize, $u.CurrentUsage, $u.PeakUsage)
}
} else {
Say " Win32_PageFileUsage: (none)"
}
Say ""
# -----------------------
# Disk (basic)
# -----------------------
try {
$disk = Get-CimInstance Win32_DiskDrive | Select-Object -First 1
if ($disk) {
$sizeGB = [Math]::Round($disk.Size / 1GB, 0)
Say "Disk (basic):"
Say (" Model={0} Interface={1} SizeGB={2}" -f $disk.Model, $disk.InterfaceType, $sizeGB)
Say ""
}
} catch {}
# -----------------------
# TEMP/TMP
# -----------------------
Say "TEMP/TMP (environment):"
$procTEMP = Get-Env "TEMP" "Process"
$procTMP = Get-Env "TMP" "Process"
$userTEMP = Get-Env "TEMP" "User"
$userTMP = Get-Env "TMP" "User"
Say (" Process TEMP={0}" -f $(if ($procTEMP) { $procTEMP } else { "(missing)" }))
Say (" Process TMP ={0}" -f $(if ($procTMP) { $procTMP } else { "(missing)" }))
Say (" User TEMP={0}" -f $(if ($userTEMP) { $userTEMP } else { "(missing)" }))
Say (" User TMP ={0}" -f $(if ($userTMP) { $userTMP } else { "(missing)" }))
$wantTemp = Join-Path $env:USERPROFILE "AppData\Local\Temp"
Say (" Expected (baseline) ~ {0}" -f $wantTemp)
if (-not $userTEMP) {
Add-Rec "WARN" "User-level TEMP is missing (HKCU\\Environment)."
} elseif ($userTEMP -ne $wantTemp) {
Add-Rec "INFO" ("User-level TEMP is '{0}' (expected '{1}')." -f $userTEMP, $wantTemp)
}
if (-not $userTMP) {
Add-Rec "WARN" "User-level TMP is missing (HKCU\\Environment)."
} elseif ($userTMP -ne $wantTemp) {
Add-Rec "INFO" ("User-level TMP is '{0}' (expected '{1}')." -f $userTMP, $wantTemp)
}
Say ""
# -----------------------
# Defender
# -----------------------
Say "Defender:"
try {
$st = Get-MpComputerStatus
Say (" AMServiceEnabled: {0}" -f $st.AMServiceEnabled)
Say (" RealTimeProtectionEnabled: {0}" -f $st.RealTimeProtectionEnabled)
Say (" OnAccessProtectionEnabled: {0}" -f $st.OnAccessProtectionEnabled)
Say (" AntivirusEnabled: {0}" -f $st.AntivirusEnabled)
Say (" NISEnabled: {0}" -f $st.NISEnabled)
$mp = Get-MpPreference
$day = $mp.ScanScheduleDay
Say (" ScanScheduleDay: {0}" -f (Format-ScanDay $day))
try {
$t = $mp.ScanScheduleTime
if ($t) { Say (" ScanScheduleTime: {0}" -f ($t.ToString("HH:mm:ss"))) }
else { Say " ScanScheduleTime: (not set)" }
} catch {
Say " ScanScheduleTime: (unavailable)"
}
$ex = @($mp.ExclusionPath)
Say (" ExclusionPath count: {0}" -f $ex.Count)
$show = $ex | Select-Object -First 10
if ($show.Count -gt 0) {
Say " ExclusionPath (first 10):"
foreach ($p in $show) { Say (" {0}" -f $p) }
}
if ($mp.ScanScheduleDay -ne 8) {
Add-Rec "WARN" ("Defender ScanScheduleDay is {0} (expected Never)." -f (Format-ScanDay $mp.ScanScheduleDay))
}
$expectedEx = @(
"C:\Windows\Temp",
"C:\Windows\SoftwareDistribution",
"C:\ProgramData\Microsoft\Windows Defender",
"C:\pagefile.sys"
)
foreach ($e in $expectedEx) {
if ($ex -notcontains $e) { Add-Rec "WARN" ("Defender exclusion missing: {0}" -f $e) }
}
} catch {
Say " WARN: Defender cmdlets not available or access denied."
Add-Rec "WARN" "Defender preferences unavailable (cannot validate schedule/exclusions)."
}
Say ""
# -----------------------
# Services
# -----------------------
Say "Services:"
$svcNames = @("WSearch","SysMain","wuauserv","BITS","UsoSvc","WaaSMedicSvc")
$svcState = @{}
foreach ($n in $svcNames) {
$s = Get-Service -Name $n -ErrorAction SilentlyContinue
if ($s) {
$start = Get-ServiceStartType $n
$svcState[$n] = @{ Status = $s.Status; StartType = $start }
Say (" {0} Status={1} StartType={2}" -f $n, $s.Status, $start)
} else {
$svcState[$n] = @{ Status = "NotFound"; StartType = "NotFound" }
Say (" {0} (not found)" -f $n)
}
}
Expect-ServiceInline $svcState "WSearch" "Disabled"
Expect-ServiceInline $svcState "SysMain" "Disabled"
Expect-ServiceInline $svcState "wuauserv" @("Disabled", "Manual")
Expect-ServiceInline $svcState "UsoSvc" "Disabled"
Expect-ServiceInline $svcState "WaaSMedicSvc" @("Disabled", "Manual")
Expect-ServiceInline $svcState "BITS" "Manual"
Say ""
# -----------------------
# Scheduled Tasks (Windows Update)
# -----------------------
Say "Scheduled Tasks:"
$taskPaths = @(
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($tp in $taskPaths) {
try {
$folder = ($tp.Substring(0, $tp.LastIndexOf("\") + 1))
$name = ($tp.Substring($tp.LastIndexOf("\") + 1))
$t = Get-ScheduledTask -TaskPath $folder -TaskName $name -ErrorAction Stop
$ti = Get-ScheduledTaskInfo -TaskPath $folder -TaskName $name -ErrorAction SilentlyContinue
$state = if ($t.State) { "$($t.State)" } else { "Unknown" }
$enabled = $true
try { $enabled = [bool]$t.Settings.Enabled } catch { $enabled = $true }
if (-not $enabled) { $state = "Disabled" }
if ($ti) { Say (" {0} State={1} LastRun={2}" -f $tp, $state, $ti.LastRunTime) }
else { Say (" {0} State={1}" -f $tp, $state) }
if ($state -ne "Disabled") {
Add-Rec "INFO" ("Scheduled task not Disabled: {0} (State={1})" -f $tp, $state)
}
} catch {
Say (" {0} State=NotFound" -f $tp)
}
}
Say ""
# -----------------------
# Event Logs (max size)
# -----------------------
Say "Event Logs (max size):"
function Get-EventLogMaxMB($logName) {
try {
$out = & wevtutil gl $logName 2>$null
if (-not $out) { return $null }
$line = $out | Where-Object { $_ -match '^\s*maxSize:\s*\d+' } | Select-Object -First 1
if (-not $line) { return $null }
$bytes = [int64]($line -replace '^\s*maxSize:\s*','')
return [Math]::Round($bytes / 1MB, 0)
} catch {
return $null
}
}
$logMax = @{}
foreach ($ln in @("Application","System","Security")) {
$mb = Get-EventLogMaxMB $ln
$logMax[$ln] = $mb
if ($mb -ne $null) { Say (" {0,-12} MaxSizeMB={1}" -f $ln, $mb) }
else { Say (" {0,-12} MaxSizeMB=(unknown)" -f $ln) }
}
if ($logMax["Application"] -ne $null -and [int]$logMax["Application"] -ne $ExpectedAppLogMB) {
Add-Rec "WARN" ("EventLog Application max is {0} MB (expected {1} MB)." -f $logMax["Application"], $ExpectedAppLogMB)
}
if ($logMax["System"] -ne $null -and [int]$logMax["System"] -ne $ExpectedSysLogMB) {
Add-Rec "WARN" ("EventLog System max is {0} MB (expected {1} MB)." -f $logMax["System"], $ExpectedSysLogMB)
}
if ($logMax["Security"] -ne $null -and [int]$logMax["Security"] -ne $ExpectedSecLogMB) {
Add-Rec "INFO" ("EventLog Security max is {0} MB (expected {1} MB (some builds restrict values))." -f $logMax["Security"], $ExpectedSecLogMB)
}
Say ""
# -----------------------
# NTFS LastAccess
# -----------------------
Say "NTFS LastAccess:"
try {
$o = & fsutil behavior query disablelastaccess 2>$null
$lastAccessVal = ($o | Select-String -Pattern '\d+' | Select-Object -First 1).Matches.Value
if ($lastAccessVal) {
$meaning = switch ($lastAccessVal) {
"0" { "Enabled" }
"1" { "Disabled (recommended for low IO)" }
"2" { "System Managed" }
"3" { "Enabled (user-managed)" }
default { "Unknown" }
}
Say (" DisableLastAccess={0} ({1})" -f $lastAccessVal, $meaning)
if ($lastAccessVal -ne "1") {
Add-Rec "WARN" ("DisableLastAccess is {0} (expected 1 = Disabled)." -f $lastAccessVal)
}
} else {
Say " DisableLastAccess=(unknown)"
Add-Rec "WARN" "DisableLastAccess could not be determined."
}
} catch {
Say " DisableLastAccess=(unable to query)"
Add-Rec "WARN" "DisableLastAccess could not be determined."
}
Say ""
# -----------------------
# ACPI Shutdown reliability
# -----------------------
Say "ACPI Shutdown reliability:"
function Get-PowerButtonActionACDC_FromRegistry() {
$base = "HKLM:\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes"
$schemeGuid = $null
try {
$schemeGuid = (Get-ItemProperty -Path $base -Name "ActivePowerScheme" -ErrorAction Stop).ActivePowerScheme
} catch {
return @{ AC=$null; DC=$null; Note="ActivePowerScheme not readable" }
}
if ([string]::IsNullOrWhiteSpace($schemeGuid)) {
return @{ AC=$null; DC=$null; Note="ActivePowerScheme empty" }
}
$SUB_BUTTONS = "4f971e89-eebd-4455-a8de-9e59040e7347"
$PBUTTONACTION = "7648efa3-dd9c-4e3e-b566-50f929386280"
$p = Join-Path $base ($schemeGuid + "\" + $SUB_BUTTONS + "\" + $PBUTTONACTION)
$ac = $null
$dc = $null
try { $ac = (Get-ItemProperty -Path $p -Name "ACSettingIndex" -ErrorAction Stop).ACSettingIndex } catch {}
try { $dc = (Get-ItemProperty -Path $p -Name "DCSettingIndex" -ErrorAction Stop).DCSettingIndex } catch {}
return @{ AC=$ac; DC=$dc; Note=("Scheme={0}" -f $schemeGuid) }
}
$pb = Get-PowerButtonActionACDC_FromRegistry
Say (" Power button action (AC/DC): {0}/{1} (expected 3/3 = Shutdown)" -f $pb.AC, $pb.DC)
if ($pb.AC -ne $null -and $pb.AC -ne 3) { Add-Rec "WARN" ("Power button AC action is {0} (expected 3=Shutdown)." -f $pb.AC) }
if ($pb.DC -ne $null -and $pb.DC -ne 3) { Add-Rec "WARN" ("Power button DC action is {0} (expected 3=Shutdown)." -f $pb.DC) }
if ($pb.AC -eq $null -or $pb.DC -eq $null) {
Add-Rec "INFO" ("Power button AC/DC action could not be parsed (powercfg format/build). {0}" -f $pb.Note)
}
$wtkst = Get-RegStr "HKLM:\SYSTEM\CurrentControlSet\Control" "WaitToKillServiceTimeout"
Say (" WaitToKillServiceTimeout: {0} (expected 5000)" -f $wtkst)
if ($wtkst -and ($wtkst -ne "5000")) { Add-Rec "INFO" ("WaitToKillServiceTimeout is {0} (expected 5000)." -f $wtkst) }
$defAuto = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "AutoEndTasks"
$defHung = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "HungAppTimeout"
$defWait = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "WaitToKillAppTimeout"
Say (" HKU\.DEFAULT AutoEndTasks/Hung/Wait: {0}/{1}/{2} (expected 1/2000/5000)" -f $defAuto, $defHung, $defWait)
if ($defAuto -and $defAuto -ne "1") { Add-Rec "WARN" ("HKU\.DEFAULT AutoEndTasks is {0} (expected 1)." -f $defAuto) }
if ($defHung -and $defHung -ne "2000") { Add-Rec "INFO" ("HKU\.DEFAULT HungAppTimeout is {0} (expected 2000)." -f $defHung) }
if ($defWait -and $defWait -ne "5000") { Add-Rec "INFO" ("HKU\.DEFAULT WaitToKillAppTimeout is {0} (expected 5000)." -f $defWait) }
$cuAuto = Get-RegStr "HKCU:\Control Panel\Desktop" "AutoEndTasks"
$cuHung = Get-RegStr "HKCU:\Control Panel\Desktop" "HungAppTimeout"
$cuWait = Get-RegStr "HKCU:\Control Panel\Desktop" "WaitToKillAppTimeout"
Say (" HKCU AutoEndTasks/Hung/Wait: {0}/{1}/{2} (expected 1/2000/5000)" -f $cuAuto, $cuHung, $cuWait)
if ($cuAuto -and $cuAuto -ne "1") { Add-Rec "WARN" ("HKCU AutoEndTasks is {0} (expected 1)." -f $cuAuto) }
if ($cuHung -and $cuHung -ne "2000") { Add-Rec "INFO" ("HKCU HungAppTimeout is {0} (expected 2000)." -f $cuHung) }
if ($cuWait -and $cuWait -ne "5000") { Add-Rec "INFO" ("HKCU WaitToKillAppTimeout is {0} (expected 5000)." -f $cuWait) }
Say ""
# -----------------------
# Reboot hint
# -----------------------
Say "Reboot hint:"
$rebootPending = $false
try {
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { $rebootPending = $true }
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { $rebootPending = $true }
$pfr = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name "PendingFileRenameOperations" -ErrorAction SilentlyContinue
if ($pfr) { $rebootPending = $true }
if ($rebootPending) { Say " WARN: Reboot appears pending." }
else { Say " OK: No reboot pending flags detected." }
} catch {
Say " (unable to determine reboot pending state)"
}
Say ""
# -----------------------
# CIRCLE Services
# -----------------------
Say "CIRCLE Services:"
$circleServices = @("circle-agent","circle-watchdog")
foreach ($name in $circleServices) {
$s = $null
try { $s = Get-CimInstance Win32_Service -Filter ("Name='{0}'" -f $name) -ErrorAction SilentlyContinue } catch {}
if ($s) {
Say (" {0} Name={1} State={2} StartMode={3}" -f $s.DisplayName, $s.Name, $s.State, $s.StartMode)
if ($s.StartMode -ne "Auto") {
Add-Rec "WARN" ("{0} StartMode is {1} (expected Auto)." -f $s.DisplayName, $s.StartMode)
}
if ($s.State -ne "Running") {
Add-Rec "WARN" ("{0} is not Running (State={1})." -f $s.DisplayName, $s.State)
}
Validate-CircleServiceRecovery $name $s.DisplayName
} else {
Say (" {0} NOT FOUND" -f $name)
Add-Rec "WARN" ("CIRCLE service not found: {0}" -f $name)
}
}
Say ""
# -----------------------
# CIRCLE notify (Task Scheduler)
# -----------------------
Say "CIRCLE notify (Task Scheduler):"
$notifyExe = "C:\circle\circle-notify.exe"
$notifyTaskName = "CIRCLE circle-notify (Logon)"
$notifyProcName = "circle-notify"
if (Test-Path $notifyExe) {
Say (" circle-notify.exe: present ({0})" -f $notifyExe)
} else {
Say (" circle-notify.exe: MISSING ({0})" -f $notifyExe)
Add-Rec "WARN" ("circle-notify.exe missing: {0}" -f $notifyExe)
}
try {
$t = Get-ScheduledTask -TaskName $notifyTaskName -ErrorAction Stop
$ti = Get-ScheduledTaskInfo -TaskName $notifyTaskName -ErrorAction SilentlyContinue
$enabled = $true
try { $enabled = [bool]$t.Settings.Enabled } catch { $enabled = $true }
$state = if ($t.State) { "$($t.State)" } else { "Unknown" }
if (-not $enabled) { $state = "Disabled" }
Say (" Task: present Name='{0}' State={1}" -f $notifyTaskName, $state)
if ($ti) { Say (" Task LastRun: {0}" -f $ti.LastRunTime) }
if ($state -eq "Disabled") {
Add-Rec "WARN" ("Task '{0}' is Disabled." -f $notifyTaskName)
}
try {
$mi = "$($t.Settings.MultipleInstances)"
Say (" Task MultipleInstances: {0} (expected IgnoreNew)" -f $mi)
if ($mi -ne "IgnoreNew") {
Add-Rec "WARN" ("Task '{0}' MultipleInstances is {1} (expected IgnoreNew)." -f $notifyTaskName, $mi)
}
} catch {}
try {
$rc = [int]$t.Settings.RestartCount
$ri = "$($t.Settings.RestartInterval)"
Say (" Task RestartCount/Interval: {0} / {1} (expected 5 / PT3M)" -f $rc, $ri)
if ($rc -ne 5) {
Add-Rec "WARN" ("Task '{0}' RestartCount is {1} (expected 5)." -f $notifyTaskName, $rc)
}
if ($ri -ne "PT3M") {
Add-Rec "INFO" ("Task '{0}' RestartInterval is {1} (expected PT3M)." -f $notifyTaskName, $ri)
}
} catch {}
try {
$etl = "$($t.Settings.ExecutionTimeLimit)"
Say (" Task ExecutionTimeLimit: {0} (expected PT0S)" -f $etl)
if ($etl -ne "PT0S") {
Add-Rec "INFO" ("Task '{0}' ExecutionTimeLimit is {1} (expected PT0S)." -f $notifyTaskName, $etl)
}
} catch {}
try {
$a = $t.Actions | Select-Object -First 1
if ($a -and $a.Execute) {
Say (" Task Action Execute: {0}" -f $a.Execute)
if ($a.Execute -notmatch 'circle-notify\.exe$') {
Add-Rec "WARN" ("Task '{0}' action is '{1}' (expected circle-notify.exe)." -f $notifyTaskName, $a.Execute)
}
}
} catch {}
} catch {
Say (" Task: MISSING Name='{0}'" -f $notifyTaskName)
Add-Rec "WARN" ("Scheduled Task missing: {0}" -f $notifyTaskName)
}
try {
$p = Get-Process -Name $notifyProcName -ErrorAction SilentlyContinue
if ($p) {
Say (" Process: running (count={0})" -f $p.Count)
} else {
Say " Process: not running (may be OK if user not logged on yet)"
}
} catch {}
Say ""
# -----------------------
# Recommendations + summary
# -----------------------
Say "Recommendations:"
if ($recs.Count -eq 0) {
Say " (none) All checked settings match the expected low-IOPS profile."
} else {
foreach ($r in $recs) { Say $r }
}
Say ""
$infoCount = ($recs | Where-Object { $_ -match '^ INFO:' }).Count
$warnCount = ($recs | Where-Object { $_ -match '^ WARN:' }).Count
if ($warnCount -eq 0 -and $infoCount -eq 0) {
Say "TEMPLATE READY: PASS"
} elseif ($warnCount -eq 0) {
Say ("TEMPLATE READY: PASS ({0} info)" -f $infoCount)
} else {
Say ("TEMPLATE READY: FAIL ({0} warn, {1} info)" -f $warnCount, $infoCount)
}
Say @"
Recommended general steps for making a new template:
====================================================
1) Windows update
2) Disable Windows Update (noVirusThanks / your method)
3) Apply baseline:
powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Apply.ps1
4) Verify baseline:
powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Check.ps1
5) Clean up
DISM /Online /Cleanup-Image /StartComponentCleanup
cleanmgr
compact.exe /compactOS:always
shutdown /r /t 0
defrag C: /X
sdelete -z C:
shutdown /s /t 0
6) Save as template (dashboard)
"@
Say ""
Say "Done."
# WinVM-WindowsUpdate-Enable.ps1
# Re-enables Windows Update services and core scheduled tasks
# Run as Administrator
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
$ErrorActionPreference = "Continue"
function Say($m) { Write-Host $m }
function Enable-ServiceSafe($name, $startType) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) {
Say " (skip) Service not found: $name"
return
}
try {
Say " Set service $name StartType=$startType"
sc.exe config $name start= $startType | Out-Null
} catch {}
try {
Say " Start service $name"
Start-Service -Name $name -ErrorAction SilentlyContinue
} catch {}
}
function Enable-TaskSafe($taskPath) {
Say " Enable scheduled task $taskPath"
try {
& schtasks.exe /Change /TN $taskPath /Enable | Out-Null
} catch {}
}
Say "=== Re-enable Windows Update ==="
Say ""
# --- Services ---
Say "Services:"
Enable-ServiceSafe "wuauserv" auto
Enable-ServiceSafe "UsoSvc" auto
Enable-ServiceSafe "BITS" delayed-auto
Enable-ServiceSafe "WaaSMedicSvc" demand
Say ""
# --- Scheduled Tasks ---
Say "Scheduled Tasks:"
$tasks = @(
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($t in $tasks) {
Enable-TaskSafe $t
}
Say ""
# --- Optional: clear Windows Update policy keys (best effort) ---
Say "Registry policy cleanup (best effort):"
$wuPolicy = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
if (Test-Path $wuPolicy) {
try {
Remove-Item -Path $wuPolicy -Recurse -Force
Say " Removed $wuPolicy"
} catch {
Say " WARN: Could not remove $wuPolicy (may be protected)"
}
} else {
Say " (none)"
}
Say ""
Say "Windows Update re-enabled."
Say "NOTE: A reboot is recommended."
# WinVM-WindowsUpdate-Toggle.ps1 (v1.0)
# Soft toggle for Windows Update (no ACL/SDDL hardening).
# -Mode Disable : disable wuauserv + disable WindowsUpdate tasks (Scheduled Start, sih, sihboot)
# -Mode Enable : enable/start services + enable those tasks
# Compatible: Windows 10/11 (incl. LTSC). Run as Administrator.
# RunCmd:
# single command:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
# multiple commans:
# Admin PowerShell
# Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Disable
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Status
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Enable
# ASCII-only comments
param(
[Parameter(Mandatory=$true)]
[ValidateSet("Enable","Disable","Status")]
[string]$Mode
)
$ErrorActionPreference = "Continue"
# include core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode $Mode
\ No newline at end of file
......@@ -3,20 +3,31 @@
# Should be in autostart and run by the user logged in
import logging
import sys
from os.path import join
from os import environ
from utils import setup_logging
from windows.winutils import update_component, create_autostart_task
from notify import run_client, get_temp_dir
logger = logging.getLogger()
logfile = join(get_temp_dir(), "agent-client.log")
fh = logging.FileHandler(logfile)
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
workdir = r"C:\circle"
comp_name = "circle-notify"
if getattr(sys, "frozen", False):
file = os.path.join(workdir, comp_name)
logger = setup_logging(logfile=file + ".log")
else:
logger = logging.getLogger()
level = environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level)
logger.info("Update check: %s in %s", comp_name + ".exe", workdir)
update = update_component(comp_name, workdir, service_fn=create_autostart_task)
logger.info("Update status: %s", update)
if update == "exit":
sys.exit(1)
if __name__ == '__main__':
run_client()
#
import logging
from logging.handlers import NTEventLogHandler
from time import sleep
......@@ -7,38 +8,45 @@ import servicemanager
import socket
import sys
import winerror
import win32api
import win32event
import win32service
import win32serviceutil
from utils import setup_logging
from windows.winutils import getRegistryVal, get_windows_version, update_component
from windows.winutils import(
getRegistryVal, get_windows_version,
update_component, update_service_binpath
)
workdir = r"C:\circle"
comp_name = "circle-watchdog"
if getattr(sys, "frozen", False):
logger = setup_logging(logfile=workdir + r"\watchdog.log")
file = join(workdir, comp_name)
logger = setup_logging(logfile=file + ".log")
else:
logger = setup_logging()
fh = NTEventLogHandler("CIRCLE Watchdog")
fh = NTEventLogHandler(comp_name)
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
level = getRegistryVal(
r"SYSTEM\\CurrentControlSet\\Services\\CIRCLE-Watchdog\\Parameters",
fr"SYSTEM\\CurrentControlSet\\Services\\{comp_name}\\Parameters",
"LogLevel",
"INFO"
)
logger.setLevel(level)
logger.info("%s loaded", __file__)
logger.info("%s loaded, level: %s", __file__, level)
class AppServerSvc (win32serviceutil.ServiceFramework):
_svc_name_ = "circle-watchdog"
_svc_name_ = comp_name
_svc_display_name_ = "CIRCLE Watchdog"
_svc_description_ = "Watchdog for CIRCLE Agent"
def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
......@@ -50,7 +58,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
def check_service(checked_service):
return win32serviceutil.QueryServiceStatus(checked_service)[1] == 4
def start_service():
def start_service(checked_service):
win32serviceutil.StartService(checked_service)
timo_base = 20
......@@ -61,7 +69,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
if not check_service(checked_service):
logger.info("Service %s is not running.", checked_service)
try:
start_service()
start_service(checked_service)
timo = timo_base
logger.info("Service %s restarted.", checked_service)
except Exception:
......@@ -80,11 +88,12 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, ''))
logger.info("%s starting", __file__)
working_dir = r"C:\circle"
exe = "circle-watchdog.exe"
exe_path = join(working_dir, exe)
logger.debug("Update check: %s %s", self._svc_name_, exe_path)
update = update_component(self._svc_name_ + ".state", workdir)
u = win32api.GetUserName()
logger.info("Running as user: %s", u)
logger.info("Update check: %s in %s", comp_name + ".exe", workdir)
update = update_component(comp_name, workdir, service_fn=update_service_binpath)
logger.info("Update status: %s", update)
if update == "exit":
# Service updated, Restart needed
......
......@@ -9,24 +9,37 @@ import win32service
import win32serviceutil
import logging
from logging.handlers import NTEventLogHandler
from twisted.internet import reactor
#import agent, reactor
from agent import main as agent_main
from twisted.internet import reactor
from windows.winutils import getRegistryVal
workdir = r"C:\circle"
comp_name = "circle-agent"
#
# angent inported and it initilized the logger
logger = logging.getLogger(__name__)
logger = logging.getLogger()
fh = NTEventLogHandler("CIRCLE Agent")
fh = NTEventLogHandler(comp_name)
formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter)
logger.addHandler(fh)
level = getRegistryVal(
fr"SYSTEM\\CurrentControlSet\\Services\\{comp_name}\\Parameters",
"LogLevel",
"INFO"
)
#logger.propagate = False
#logger.setLevel('INFO')
logger.info("%s loaded", __file__)
logger.setLevel(level)
logger.info("%s loaded, level: %s", __file__, level)
class AppServerSvc (win32serviceutil.ServiceFramework):
_svc_name_ = "circle-agent"
_svc_name_ = comp_name
_svc_display_name_ = "CIRCLE Agent"
_svc_description_ = "CIRCLE cloud contextualization agent"
......
......@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*-
from os import environ, chdir
from os.path import join
import logging
import platform
import subprocess
......@@ -12,8 +13,12 @@ from logging.handlers import TimedRotatingFileHandler
from pathlib import Path
from utils import setup_logging
workdir = r"C:\circle"
comp_name = "circle-agent"
if getattr(sys, "frozen", False):
logger = setup_logging(logfile=r"C:\Circle\agent.log")
file = join(workdir, comp_name)
logger = setup_logging(logfile=file + ".log")
else:
logger = setup_logging()
......@@ -53,7 +58,7 @@ if win:
)
logger.setLevel(level)
system = get_windows_version()
system = "DEBUG"
# system = "DEBUG"
from context import get_context, get_serial # noqa
......
# circle-agent-install.ps1 (v1.1)
# Installs and starts CIRCLE agent + watchdog, and registers circle-notify.exe as logon task
# Run from elevated PowerShell as cloud user
# ASCII-only comments
param(
[switch]$WhatIf
)
$ErrorActionPreference = "Stop"
function Say($msg="") { Write-Host $msg }
function Fail($msg) {
Write-Host "ERROR: $msg" -ForegroundColor Red
exit 1
}
function Require-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Fail "Run this script as Administrator."
}
}
function Run-Step($desc, [ScriptBlock]$action) {
if ($WhatIf) {
Say ("[WHATIF] {0}" -f $desc)
return
}
Say ("[DO] {0}" -f $desc)
try {
& $action
} catch {
Fail ("{0} failed: {1}" -f $desc, $_.Exception.Message)
}
}
function Check-File($path) {
if (-not (Test-Path $path)) { Fail "Missing file: $path" }
}
function Check-Dir($path) {
if (-not (Test-Path $path)) { Fail "Directory not found: $path" }
}
function Check-ServiceRunning($name) {
$s = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $s) { Fail "Service not found: $name" }
if ($s.Status -ne "Running") {
Fail "Service $name is not running (state=$($s.Status))"
}
}
Require-Admin
$baseDir = "C:\circle"
$agentExe = Join-Path $baseDir "circle-agent.exe"
$watchdogExe = Join-Path $baseDir "circle-watchdog.exe"
$notifyExe = Join-Path $baseDir "circle-notify.exe"
$taskName = "CIRCLE circle-notify (Logon)"
Say "=== CIRCLE Agent install ==="
Say ("User: {0}" -f $env:USERNAME)
Say ("Working dir: {0}" -f $baseDir)
Say ("Mode: {0}" -f $(if ($WhatIf) { "WhatIf (dry-run)" } else { "Apply" }))
Say ""
# --- Preconditions ---
Check-Dir $baseDir
Check-File $agentExe
Check-File $watchdogExe
Check-File $notifyExe
# Show what would run (useful in WhatIf)
Say "Plan:"
Say (" - cd {0}" -f $baseDir)
Say (" - {0} --startup auto install" -f $agentExe)
Say (" - {0} --startup auto install" -f $watchdogExe)
Say (" - {0} start" -f $agentExe)
Say (" - {0} start" -f $watchdogExe)
Say (" - Register Scheduled Task: {0} -> {1}" -f $taskName, $notifyExe)
Say ""
Run-Step "Set location to $baseDir" { Set-Location $baseDir }
# --- Install services ---
Run-Step "Install circle-agent service (startup auto)" {
& $agentExe --startup auto install | Out-Null
}
Run-Step "Install circle-watchdog service (startup auto)" {
& $watchdogExe --startup auto install | Out-Null
}
# --- Start services ---
Run-Step "Start circle-agent service" {
& $agentExe start | Out-Null
}
Run-Step "Start circle-watchdog service" {
& $watchdogExe start | Out-Null
}
# --- Verify services (skip on WhatIf) ---
if (-not $WhatIf) {
Check-ServiceRunning "circle-agent"
Check-ServiceRunning "circle-watchdog"
} else {
Say "[WHATIF] Skip service running checks (no changes applied)."
}
# --- Register circle-notify as logon task ---
Run-Step "Register Scheduled Task '$taskName' for circle-notify.exe" {
$action = New-ScheduledTaskAction -Execute $notifyExe
$trigger = New-ScheduledTaskTrigger -AtLogOn
# Run only when user is logged on (no password prompt)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
-RestartCount 5 `
-RestartInterval (New-TimeSpan -Minutes 3)
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
}
# --- Verify task (skip on WhatIf) ---
if (-not $WhatIf) {
$task = Get-ScheduledTask -TaskName $taskName -ErrorAction SilentlyContinue
if (-not $task) { Fail "Scheduled Task '$taskName' not found after registration" }
} else {
Say "[WHATIF] Skip task verification (no changes applied)."
}
Say ""
if ($WhatIf) {
Say "DRY-RUN OK: Preconditions passed; no changes were made."
} else {
Say "SUCCESS:"
Say " - circle-agent service installed and running"
Say " - circle-watchdog service installed and running"
Say " - circle-notify scheduled task registered (logon, restart-on-failure)"
Say ""
Say "NOTE: User logoff/logon required for circle-notify.exe to start."
}
......@@ -299,7 +299,7 @@ if win:
message="This VM expiring soon",
yes_label="Renew",
no_label="Cancel",
timeout_seconds=60, )
timeout_seconds=120, )
if ans == "yes":
ret = accept(line)
prompt_yes_no(
......@@ -307,7 +307,7 @@ if win:
message=ret['msg'],
yes_label="OK",
no_label="",
timeout_seconds=10 if ret['ret'] else 60)
timeout_seconds=30 if ret['ret'] else 60)
class SubFactory(protocol.ReconnectingClientFactory):
......
pyinstaller --clean -F --path . --hidden-import pkg_resources --hidden-import infi --hidden-import win32timezone --hidden-import win32traceutil --exclude-module tkinter --exclude-module _tkinter -F agent-wdog-winservice.py
pyinstaller --clean -F --path . --hidden-import pkg_resources --hidden-import infi --hidden-import win32timezone --hidden-import win32traceutil --exclude-module tkinter --exclude-module _tkinter -F agent-winservice.py
pyinstaller --clean -F --path . --hidden-import pkg_resources --hidden-import infi --hidden-import win32timezone --hidden-import win32traceutil -F agent-notify.pyw
\ No newline at end of file
pyinstaller --clean -F --path . --hidden-import pkg_resources --hidden-import infi --hidden-import win32timezone --hidden-import win32traceutil --hidden-import winotify -F agent-notify.pyw
\ No newline at end of file
# WinVM-IOPS-Apply.ps1 (v1.5)
# Applies low-IOPS template settings on Windows 10/11
# Updates:
# - SCM recovery policy for circle-agent + circle-watchdog (reset=86400, restart=180000 x3, failureflag=1)
# - Replace Startup shortcut autostart with Task Scheduler for circle-notify.exe (logon, single instance, restart on failure)
# - Simplify hive handling: assume script is run by cloud user in elevated PS; use HKCU only (plus HKU\.DEFAULT)
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Apply.ps1
# ASCII-only comments
param(
# Pagefile size: you can pass 4096 (MB) or "4G" / "4096M"
[string]$Pagefile = "4G",
# Event log max sizes in MB
[int]$AppLogMB = 4,
[int]$SysLogMB = 4,
[int]$SecLogMB = 20,
[switch]$WhatIf
)
# --- Parameter validation / help ---
# If any unnamed or unknown arguments are passed, show help and exit.
if ($args.Count -gt 0) {
Write-Host ""
Write-Host "ERROR: Unknown parameter(s): $($args -join ' ')" -ForegroundColor Red
Write-Host ""
Write-Host "Usage:"
Write-Host " powershell -ExecutionPolicy Bypass -File WinVM-IOPS-Apply.ps1 [-Pagefile <size>] [-WhatIf]"
Write-Host ""
Write-Host "Options:"
Write-Host " -Pagefile <size> Pagefile size (default: 4G)"
Write-Host " Examples: 4096 | 4096M | 4G | 6G"
Write-Host " -WhatIf Show what would be changed, do not apply"
Write-Host ""
exit 1
}
$ErrorActionPreference = "Inquire"
# include WU core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
function Say($msg) { Write-Host $msg }
function Require-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "Run this script as Administrator."
}
}
function Run($desc, [ScriptBlock]$action) {
if ($WhatIf) {
Say ("[WHATIF] {0}" -f $desc)
} else {
Say ("[DO] {0}" -f $desc)
& $action
}
}
function Parse-SizeToMB([string]$s) {
if ([string]::IsNullOrWhiteSpace($s)) { return 4096 }
$t = $s.Trim().ToUpperInvariant()
# If it's pure number, treat as MB
if ($t -match '^\d+$') { return [int]$t }
# Accept forms like 4G, 4096M
if ($t -match '^(\d+)\s*G$') { return [int]$Matches[1] * 1024 }
if ($t -match '^(\d+)\s*M$') { return [int]$Matches[1] }
throw "Invalid -Pagefile value '$s'. Use e.g. 4096, 4096M, or 4G."
}
function Set-ServiceDisabled($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Stop service $name" { Stop-Service -Name $name -Force -ErrorAction SilentlyContinue }
Run "Disable service $name" { Set-Service -Name $name -StartupType Disabled -ErrorAction SilentlyContinue }
}
function Set-ServiceManual($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Stop service $name" { Stop-Service -Name $name -Force -ErrorAction SilentlyContinue }
Run "Set service $name to Manual" { Set-Service -Name $name -StartupType Manual -ErrorAction SilentlyContinue }
}
function Clear-EventLogBestEffort($logName) {
Run "Clear EventLog '$logName' (best effort)" {
& wevtutil cl $logName 2>$null | Out-Null
}
}
function Set-EventLogMaxMB($logName, $mb) {
# wevtutil expects bytes
$bytes = [int64]$mb * 1024 * 1024
Run "Set EventLog '$logName' max size to ${mb}MB" {
& wevtutil sl $logName /ms:$bytes 2>$null | Out-Null
}
}
function Disable-ScheduledTaskSafe($taskPath) {
Run "Disable scheduled task $taskPath" {
$tp = ([IO.Path]::GetDirectoryName($taskPath) + "\")
$tn = ([IO.Path]::GetFileName($taskPath))
Disable-ScheduledTask -TaskPath $tp -TaskName $tn -ErrorAction SilentlyContinue | Out-Null
}
}
# Helper: set Desktop shutdown behavior values under a registry root
function Set-DesktopShutdownTweaks($root) {
Run "Set AutoEndTasks/timeout tweaks in $root" {
& reg add "$root\Control Panel\Desktop" /v AutoEndTasks /t REG_SZ /d 1 /f | Out-Null
& reg add "$root\Control Panel\Desktop" /v HungAppTimeout /t REG_SZ /d 2000 /f | Out-Null
& reg add "$root\Control Panel\Desktop" /v WaitToKillAppTimeout /t REG_SZ /d 5000 /f | Out-Null
}
}
# Helper: set user env vars (TEMP/TMP) under a registry root
function Set-UserEnvTempTmp($root, [string]$tempPath) {
Run "Set TEMP/TMP (User) in $root to $tempPath" {
& reg add "$root\Environment" /v TEMP /t REG_SZ /d $tempPath /f | Out-Null
& reg add "$root\Environment" /v TMP /t REG_SZ /d $tempPath /f | Out-Null
}
}
# --- SCM recovery helpers (services) ---
function Set-ServiceRecoveryPolicy($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Set SCM recovery policy for service '$name' (reset=86400, restart=180000 x3)" {
& sc.exe failure "$name" reset= 86400 actions= restart/180000/restart/180000/restart/180000 | Out-Null
& sc.exe failureflag "$name" 1 | Out-Null
}
}
# --- Task Scheduler helper (circle-notify.exe) ---
function Ensure-CircleNotifyLogonTask(
[string]$taskName,
[string]$exePath
) {
if (-not (Test-Path $exePath)) {
Say (" WARN: circle-notify exe not found: {0} (task not created)" -f $exePath)
return
}
Run "Create/Update Scheduled Task '$taskName' (At logon, single instance, restart on failure)" {
$action = New-ScheduledTaskAction -Execute $exePath
# Trigger at logon for current user
$trigger = New-ScheduledTaskTrigger -AtLogOn
# Run only when user is logged on (no password prompt)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
-RestartCount 5 `
-RestartInterval (New-TimeSpan -Minutes 3)
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
}
}
# --- Remove legacy client from Startup
function Remove-LegacyStartupClientArtifacts {
# Remove legacy Startup items for old client.exe autostart (best effort)
# Kill running legacy process only if legacy startup artifact exists.
# Robust against weird object[] values; uses Path.Combine.
$appData = $env:APPDATA
if ($appData -is [System.Array]) { $appData = $appData[0] }
$appData = [string]$appData
if ([string]::IsNullOrWhiteSpace($appData)) { return }
$startup = [System.IO.Path]::Combine($appData, "Microsoft\Windows\Start Menu\Programs\Startup")
if (-not (Test-Path $startup)) { return }
$names = @(
"client.exe",
"client.lnk"
)
$candidatePaths = foreach ($n in $names) { [System.IO.Path]::Combine($startup, $n) }
# Gather .lnk files once
$lnkFiles = @()
try { $lnkFiles = Get-ChildItem -Path $startup -Filter "*.lnk" -ErrorAction SilentlyContinue } catch {}
# Detect whether any legacy startup artifact exists (file or lnk->client.exe)
$hasLegacy = $false
foreach ($p in $candidatePaths) {
if (Test-Path $p) { $hasLegacy = $true; break }
}
if (-not $hasLegacy -and $lnkFiles.Count -gt 0) {
foreach ($lnk in $lnkFiles) {
try {
$wsh = New-Object -ComObject WScript.Shell
$sc = $wsh.CreateShortcut($lnk.FullName)
$tp = $sc.TargetPath
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($sc) | Out-Null } catch {}
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($wsh) | Out-Null } catch {}
if ($tp -and ($tp.ToLowerInvariant().EndsWith("\client.exe"))) {
$hasLegacy = $true
break
}
} catch {
# ignore
}
}
}
if (-not $hasLegacy) {
Say " (skip) no legacy client.exe Startup artifact detected."
return
}
# If we get here, legacy startup exists -> kill legacy client.exe first (often 2 procs due to PyInstaller)
Run "Stop legacy client.exe processes (best effort)" {
$legacyPaths = @(
"c:\circle\client.exe",
($startup.ToLowerInvariant() + "\client.exe")
)
try {
$procs = Get-Process -Name "client" -ErrorAction SilentlyContinue
if ($procs) {
foreach ($p in $procs) {
# Prefer safe path match if we can read it
try {
if ($p.Path) {
$pp = $p.Path.ToLowerInvariant()
if ($legacyPaths -contains $pp) {
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
}
} else {
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
}
} catch {
# Fall back to killing by Id if anything weird happens
try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {}
}
}
}
} catch {}
}
# Now remove legacy startup items
Run "Remove legacy Startup artifacts (client.exe / old shortcuts) from $startup" {
foreach ($p in $candidatePaths) {
if (Test-Path $p) {
Remove-Item -LiteralPath $p -Force -ErrorAction SilentlyContinue
}
}
# Remove any .lnk that points to *\client.exe
foreach ($lnk in $lnkFiles) {
try {
$wsh = New-Object -ComObject WScript.Shell
$sc = $wsh.CreateShortcut($lnk.FullName)
$tp = $sc.TargetPath
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($sc) | Out-Null } catch {}
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($wsh) | Out-Null } catch {}
if ($tp -and ($tp.ToLowerInvariant().EndsWith("\client.exe"))) {
Remove-Item -LiteralPath $lnk.FullName -Force -ErrorAction SilentlyContinue
}
} catch {
# ignore
}
}
}
}
Require-Admin
# Cache CS once (avoid repeated CIM)
$cs = $null
try { $cs = Get-CimInstance Win32_ComputerSystem } catch {}
# Normalize pagefile MB
$pagefileMB = Parse-SizeToMB $Pagefile
Say "=== Apply low-IOPS template settings (v1.5) ==="
Say ("Computer: {0}" -f $env:COMPUTERNAME)
Say ("Pagefile target: {0} MB" -f $pagefileMB)
Say ""
# 1) Pagefile fixed size (robust: set via registry)
Run "Set fixed pagefile ${pagefileMB}/${pagefileMB} MB on C:\" {
# Disable automatic pagefile management (CIM)
if ($cs) {
Set-CimInstance -InputObject $cs -Property @{ AutomaticManagedPagefile = $false } -Confirm:$false | Out-Null
}
# Set PagingFiles (REG_MULTI_SZ): "path initialMB maximumMB"
$pf = "C:\pagefile.sys $pagefileMB $pagefileMB"
& reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" `
/v PagingFiles /t REG_MULTI_SZ /d $pf /f | Out-Null
# Optional: clear temp pagefile flag if present
& reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" `
/v TempPageFile /t REG_DWORD /d 0 /f | Out-Null
}
# 2) Disable noisy services
Say ""
Say "Services:"
Set-ServiceDisabled "WSearch" # Windows Search
Set-ServiceDisabled "SysMain" # Superfetch/SysMain
# Windows Update related (lab template style)
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode Disable
# 3) Defender scheduled scan -> Never (Day=8)
Say ""
Say "Defender:"
Run "Set Defender scheduled scan day to Never (8)" {
Set-MpPreference -ScanScheduleDay 8 -ErrorAction SilentlyContinue | Out-Null
}
# 3.1) Defender exclusions (low-IOPS friendly)
Say ""
Say "Defender Exclusions:"
$DefenderExclusions = @(
"C:\Windows\Temp",
"C:\Windows\SoftwareDistribution",
"C:\ProgramData\Microsoft\Windows Defender",
"C:\pagefile.sys"
)
foreach ($path in $DefenderExclusions) {
Run "Add Defender exclusion $path" {
Add-MpPreference -ExclusionPath $path -ErrorAction SilentlyContinue | Out-Null
}
}
# 4) NTFS LastAccess disable
Say ""
Say "NTFS:"
Run "Disable NTFS LastAccess updates (DisableLastAccess=1)" {
& fsutil behavior set disablelastaccess 1 | Out-Null
}
# 5) Event log sizes (clear then set)
Say ""
Say "Event Logs:"
Clear-EventLogBestEffort "Application"
Clear-EventLogBestEffort "System"
Clear-EventLogBestEffort "Security"
Set-EventLogMaxMB "Application" $AppLogMB
Set-EventLogMaxMB "System" $SysLogMB
Set-EventLogMaxMB "Security" $SecLogMB
# 6) (Optional-ish) Try to disable common Update scheduled tasks
# moved to WU core
# 7) ACPI shutdown reliability (RDP-friendly)
Say ""
Say "ACPI Shutdown reliability:"
# 7.1) Ensure Power Button action = Shutdown (AC and DC)
Run "Set power button action to Shutdown (AC/DC)" {
& powercfg /setacvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3 | Out-Null
& powercfg /setdcvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3 | Out-Null
& powercfg /setactive SCHEME_CURRENT | Out-Null
}
# 7.2) Apply to the default (logon screen / disconnected situations)
Set-DesktopShutdownTweaks "HKU\.DEFAULT"
# 7.3) Apply to current user (cloud) - simplified
Say " Apply Desktop tweaks to HKCU (current user)"
Set-DesktopShutdownTweaks "HKCU"
# 7.4) Service shutdown timeout (system-wide)
Run "Set WaitToKillServiceTimeout=5000ms" {
& reg add "HKLM\SYSTEM\CurrentControlSet\Control" /v WaitToKillServiceTimeout /t REG_SZ /d 5000 /f | Out-Null
}
# 7.5) TEMP/TMP (User-level) for current user (cloud) - simplified
Say ""
Say "Environment variables (current user TEMP/TMP):"
$fallbackTemp = Join-Path $env:USERPROFILE "AppData\Local\Temp"
Set-UserEnvTempTmp "HKCU" $fallbackTemp
# 8) CIRCLE services (recovery hardening)
Say ""
Say "CIRCLE services (recovery hardening):"
Set-ServiceRecoveryPolicy "circle-agent"
Set-ServiceRecoveryPolicy "circle-watchdog"
# 9) CIRCLE notify autostart via Task Scheduler (logon)
Say ""
Say "CIRCLE notify autostart (Scheduled Task):"
# Remove legacy Startup autostart entries for old client.exe
Remove-LegacyStartupClientArtifacts
$notifyExe = "C:\circle\circle-notify.exe"
$taskName = "CIRCLE circle-notify (Logon)"
Ensure-CircleNotifyLogonTask -taskName $taskName -exePath $notifyExe
Say ""
Say "Done."
Say "NOTE: Reboot recommended (pagefile + services)."
# WinVM-IOPS-Check.ps1 (v1.7)
# Checks Windows VM settings for low-IOPS template readiness
# Updates:
# - Fix recommendations collection (no [ref] += issues)
# - CIRCLE service names: circle-agent, circle-watchdog
# - CIRCLE notify autostart via Task Scheduler (no Startup shortcut)
# - Simplified user/hive assumptions: run by cloud user in elevated PS; use HKCU only (plus HKU\.DEFAULT)
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Check.ps1
# ASCII-only comments
# ASCII-only comments
param(
# Expected fixed pagefile size (MB). Default 4096 (4G).
[int]$ExpectedPagefileMB = 4096,
# Expected EventLog max sizes (MB)
[int]$ExpectedAppLogMB = 4,
[int]$ExpectedSysLogMB = 4,
[int]$ExpectedSecLogMB = 20
)
$ErrorActionPreference = "Continue"
function Say($msg="") { Write-Host $msg }
function ToGB([double]$bytes) {
[Math]::Round($bytes / 1GB, 2)
}
# Recommendations bucket (ArrayList: robust Add)
$recs = New-Object System.Collections.ArrayList
function Add-Rec([string]$level, [string]$msg) {
[void]$recs.Add((" {0}: {1}" -f $level, $msg))
}
function Get-ServiceStartType($name) {
try {
$svc = Get-CimInstance Win32_Service -Filter ("Name='{0}'" -f $name) -ErrorAction Stop
return $svc.StartMode
} catch {
return "Unknown"
}
}
function Format-ScanDay($day) {
switch ($day) {
0 { "Everyday" }
1 { "Sunday" }
2 { "Monday" }
3 { "Tuesday" }
4 { "Wednesday" }
5 { "Thursday" }
6 { "Friday" }
7 { "Saturday" }
8 { "Never" }
default { "$day" }
}
}
# Expectations helper (services)
function Expect-ServiceInline($svcState, [string]$name, $wantStartTypes, $wantStatus = $null) {
if (-not $svcState.ContainsKey($name)) {
Add-Rec "WARN" ("Service {0} not checked." -f $name)
return
}
$st = $svcState[$name]
if ($wantStartTypes -isnot [System.Array]) { $wantStartTypes = @($wantStartTypes) }
if ($wantStartTypes -notcontains $st.StartType) {
Add-Rec "WARN" ("Service {0} StartType is {1} (expected one of: {2})." -f $name, $st.StartType, ($wantStartTypes -join ", "))
}
if ($wantStatus -and ($st.Status -ne $wantStatus)) {
Add-Rec "WARN" ("Service {0} Status is {1} (expected {2})." -f $name, $st.Status, $wantStatus)
}
}
# Small env getter (no duplicates)
function Get-Env([string]$name, [string]$scope) {
try { return [Environment]::GetEnvironmentVariable($name, $scope) } catch { return $null }
}
function Get-RegStr($path, $name) {
try { (Get-ItemProperty $path -Name $name -ErrorAction Stop).$name } catch { $null }
}
# Parse sc.exe qfailure output (best effort)
function Get-ScFailureSummary([string]$svcName) {
try {
$lines = & sc.exe qfailure $svcName 2>$null
if (-not $lines) { return $null }
$txt = ($lines -join "`n")
$reset = $null
if ($txt -match 'RESET_PERIOD.*:\s*(\d+)') { $reset = [int]$Matches[1] }
$hasRestart = ($txt -match 'RESTART')
return @{
Text = $txt
Reset = $reset
HasRestart = $hasRestart
}
} catch {
return $null
}
}
# Compare recovery policy to expected: reset=86400, restart/180000 x3, failureflag=1
function Validate-CircleServiceRecovery([string]$svcName, [string]$displayName) {
$sum = Get-ScFailureSummary $svcName
if (-not $sum) {
Add-Rec "INFO" ("{0} recovery info unavailable (sc qfailure failed)." -f $displayName)
return
}
if (-not $sum.HasRestart) {
Add-Rec "WARN" ("{0} recovery actions do not include RESTART." -f $displayName)
}
if ($sum.Reset -ne $null -and $sum.Reset -ne 86400) {
Add-Rec "WARN" ("{0} recovery reset period is {1}s (expected 86400)." -f $displayName, $sum.Reset)
} elseif ($sum.Reset -eq $null) {
Add-Rec "INFO" ("{0} recovery reset period not parsed (verify via sc qfailure)." -f $displayName)
}
# Validate restart delay(s) 180000 (best effort)
if ($sum.Text -notmatch '180000') {
Add-Rec "WARN" ("{0} recovery does not mention 180000ms delay (expected restart/180000 x3)." -f $displayName)
}
# failureflag (non-crash) check (best effort parse)
try {
$ff = & sc.exe qfailureflag $svcName 2>$null
if ($ff) {
$t = ($ff -join "`n")
if ($t -match 'FAILURE_ACTIONS_ON_NONCRASH_FAILURES\s*:\s*(TRUE|FALSE|[01])') {
$raw = $Matches[1].ToUpperInvariant()
$ok = ($raw -eq "TRUE" -or $raw -eq "1")
if (-not $ok) {
Add-Rec "WARN" ("{0} failureflag is {1} (expected TRUE/1)." -f $displayName, $Matches[1])
}
} else {
Add-Rec "INFO" ("{0} failureflag output not parsed; verify manually if needed." -f $displayName)
}
} else {
Add-Rec "INFO" ("{0} failureflag info unavailable (sc qfailureflag empty)." -f $displayName)
}
} catch {
Add-Rec "INFO" ("{0} failureflag query failed." -f $displayName)
}
}
Say "=== Windows VM low-IOPS template check (v1.7) ==="
Say ("Computer: {0}" -f $env:COMPUTERNAME)
Say ("User: {0}" -f $env:USERNAME)
# One-time queries
$os = $null
$cs = $null
try { $os = Get-CimInstance Win32_OperatingSystem } catch {}
try { $cs = Get-CimInstance Win32_ComputerSystem } catch {}
if ($os -and $os.Caption) { Say ("OS: {0}" -f $os.Caption) }
else { Say "OS: (unknown)" }
Say ""
# -----------------------
# Memory / Commit (uses $os/$cs once)
# -----------------------
Say "Memory:"
try {
if ($os -and $cs) {
$commitUsedGB = ToGB([double]$os.TotalVirtualMemorySize * 1KB - [double]$os.FreeVirtualMemory * 1KB)
$commitLimitGB = ToGB([double]$os.TotalVirtualMemorySize * 1KB)
$ramGB = [Math]::Round(([double]$cs.TotalPhysicalMemory / 1GB), 2)
Say (" RAM (GB): {0}" -f $ramGB)
Say (" Commit used (GB): {0}" -f $commitUsedGB)
Say (" Commit limit (GB): {0}" -f $commitLimitGB)
} else {
Say " (unable to query)"
}
} catch {
Say " (unable to query)"
}
Say ""
# -----------------------
# Pagefile
# -----------------------
Say "Pagefile:"
$pfReg = $null
try {
$pfReg = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" -ErrorAction Stop).PagingFiles
Say (" Registry PagingFiles: {0}" -f ($pfReg -join " | "))
} catch {
Say " Registry PagingFiles: (unable to read)"
Add-Rec "WARN" "Registry PagingFiles could not be read."
}
try {
if ($cs -and ($cs.AutomaticManagedPagefile -ne $false)) {
Add-Rec "WARN" "AutomaticManagedPagefile is not False (pagefile may be auto-managed)."
}
} catch {}
try {
if ($pfReg) {
$want = $ExpectedPagefileMB
$hit = $false
foreach ($line in $pfReg) {
if ($line -match '^[Cc]:\\pagefile\.sys\s+(\d+)\s+(\d+)$') {
$i = [int]$Matches[1]; $m = [int]$Matches[2]
if ($i -eq $want -and $m -eq $want) {
$hit = $true
} else {
Add-Rec "WARN" ("Registry PagingFiles is '{0}' (expected {1}/{1} MB)." -f $line, $want)
}
}
}
if (-not $hit) {
Add-Rec "WARN" ("Registry PagingFiles does not contain C:\pagefile.sys {0} {0}." -f $ExpectedPagefileMB)
}
}
} catch {}
$pfs = $null
try { $pfs = Get-CimInstance Win32_PageFileSetting -ErrorAction SilentlyContinue } catch {}
if ($pfs) {
foreach ($p in $pfs) {
Say (" {0} InitialMB={1} MaximumMB={2}" -f $p.Name, $p.InitialSize, $p.MaximumSize)
if ($p.Name -match 'pagefile\.sys') {
if (($p.InitialSize -ne $ExpectedPagefileMB) -or ($p.MaximumSize -ne $ExpectedPagefileMB)) {
Add-Rec "WARN" ("WMI PageFileSetting {0} is {1}/{2} MB (expected {3}/{3} MB)." -f $p.Name, $p.InitialSize, $p.MaximumSize, $ExpectedPagefileMB)
}
}
}
} else {
Say " Win32_PageFileSetting: (none)"
Add-Rec "WARN" "Win32_PageFileSetting is empty/unavailable (cannot confirm fixed pagefile via WMI)."
}
$pfu = $null
try { $pfu = Get-CimInstance Win32_PageFileUsage -ErrorAction SilentlyContinue } catch {}
if ($pfu) {
foreach ($u in $pfu) {
Say (" Usage: {0} AllocatedMB={1} CurrentUsedMB={2} PeakUsedMB={3}" -f $u.Name, $u.AllocatedBaseSize, $u.CurrentUsage, $u.PeakUsage)
}
} else {
Say " Win32_PageFileUsage: (none)"
}
Say ""
# -----------------------
# Disk (basic)
# -----------------------
try {
$disk = Get-CimInstance Win32_DiskDrive | Select-Object -First 1
if ($disk) {
$sizeGB = [Math]::Round($disk.Size / 1GB, 0)
Say "Disk (basic):"
Say (" Model={0} Interface={1} SizeGB={2}" -f $disk.Model, $disk.InterfaceType, $sizeGB)
Say ""
}
} catch {}
# -----------------------
# TEMP/TMP
# -----------------------
Say "TEMP/TMP (environment):"
$procTEMP = Get-Env "TEMP" "Process"
$procTMP = Get-Env "TMP" "Process"
$userTEMP = Get-Env "TEMP" "User"
$userTMP = Get-Env "TMP" "User"
Say (" Process TEMP={0}" -f $(if ($procTEMP) { $procTEMP } else { "(missing)" }))
Say (" Process TMP ={0}" -f $(if ($procTMP) { $procTMP } else { "(missing)" }))
Say (" User TEMP={0}" -f $(if ($userTEMP) { $userTEMP } else { "(missing)" }))
Say (" User TMP ={0}" -f $(if ($userTMP) { $userTMP } else { "(missing)" }))
$wantTemp = Join-Path $env:USERPROFILE "AppData\Local\Temp"
Say (" Expected (baseline) ~ {0}" -f $wantTemp)
if (-not $userTEMP) {
Add-Rec "WARN" "User-level TEMP is missing (HKCU\\Environment)."
} elseif ($userTEMP -ne $wantTemp) {
Add-Rec "INFO" ("User-level TEMP is '{0}' (expected '{1}')." -f $userTEMP, $wantTemp)
}
if (-not $userTMP) {
Add-Rec "WARN" "User-level TMP is missing (HKCU\\Environment)."
} elseif ($userTMP -ne $wantTemp) {
Add-Rec "INFO" ("User-level TMP is '{0}' (expected '{1}')." -f $userTMP, $wantTemp)
}
Say ""
# -----------------------
# Defender
# -----------------------
Say "Defender:"
try {
$st = Get-MpComputerStatus
Say (" AMServiceEnabled: {0}" -f $st.AMServiceEnabled)
Say (" RealTimeProtectionEnabled: {0}" -f $st.RealTimeProtectionEnabled)
Say (" OnAccessProtectionEnabled: {0}" -f $st.OnAccessProtectionEnabled)
Say (" AntivirusEnabled: {0}" -f $st.AntivirusEnabled)
Say (" NISEnabled: {0}" -f $st.NISEnabled)
$mp = Get-MpPreference
$day = $mp.ScanScheduleDay
Say (" ScanScheduleDay: {0}" -f (Format-ScanDay $day))
try {
$t = $mp.ScanScheduleTime
if ($t) { Say (" ScanScheduleTime: {0}" -f ($t.ToString("HH:mm:ss"))) }
else { Say " ScanScheduleTime: (not set)" }
} catch {
Say " ScanScheduleTime: (unavailable)"
}
$ex = @($mp.ExclusionPath)
Say (" ExclusionPath count: {0}" -f $ex.Count)
$show = $ex | Select-Object -First 10
if ($show.Count -gt 0) {
Say " ExclusionPath (first 10):"
foreach ($p in $show) { Say (" {0}" -f $p) }
}
if ($mp.ScanScheduleDay -ne 8) {
Add-Rec "WARN" ("Defender ScanScheduleDay is {0} (expected Never)." -f (Format-ScanDay $mp.ScanScheduleDay))
}
$expectedEx = @(
"C:\Windows\Temp",
"C:\Windows\SoftwareDistribution",
"C:\ProgramData\Microsoft\Windows Defender",
"C:\pagefile.sys"
)
foreach ($e in $expectedEx) {
if ($ex -notcontains $e) { Add-Rec "WARN" ("Defender exclusion missing: {0}" -f $e) }
}
} catch {
Say " WARN: Defender cmdlets not available or access denied."
Add-Rec "WARN" "Defender preferences unavailable (cannot validate schedule/exclusions)."
}
Say ""
# -----------------------
# Services
# -----------------------
Say "Services:"
$svcNames = @("WSearch","SysMain","wuauserv","BITS","UsoSvc","WaaSMedicSvc")
$svcState = @{}
foreach ($n in $svcNames) {
$s = Get-Service -Name $n -ErrorAction SilentlyContinue
if ($s) {
$start = Get-ServiceStartType $n
$svcState[$n] = @{ Status = $s.Status; StartType = $start }
Say (" {0} Status={1} StartType={2}" -f $n, $s.Status, $start)
} else {
$svcState[$n] = @{ Status = "NotFound"; StartType = "NotFound" }
Say (" {0} (not found)" -f $n)
}
}
Expect-ServiceInline $svcState "WSearch" "Disabled"
Expect-ServiceInline $svcState "SysMain" "Disabled"
Expect-ServiceInline $svcState "wuauserv" @("Disabled", "Manual")
Expect-ServiceInline $svcState "UsoSvc" "Disabled"
Expect-ServiceInline $svcState "WaaSMedicSvc" @("Disabled", "Manual")
Expect-ServiceInline $svcState "BITS" "Manual"
Say ""
# -----------------------
# Scheduled Tasks (Windows Update)
# -----------------------
Say "Scheduled Tasks:"
$taskPaths = @(
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($tp in $taskPaths) {
try {
$folder = ($tp.Substring(0, $tp.LastIndexOf("\") + 1))
$name = ($tp.Substring($tp.LastIndexOf("\") + 1))
$t = Get-ScheduledTask -TaskPath $folder -TaskName $name -ErrorAction Stop
$ti = Get-ScheduledTaskInfo -TaskPath $folder -TaskName $name -ErrorAction SilentlyContinue
$state = if ($t.State) { "$($t.State)" } else { "Unknown" }
$enabled = $true
try { $enabled = [bool]$t.Settings.Enabled } catch { $enabled = $true }
if (-not $enabled) { $state = "Disabled" }
if ($ti) { Say (" {0} State={1} LastRun={2}" -f $tp, $state, $ti.LastRunTime) }
else { Say (" {0} State={1}" -f $tp, $state) }
if ($state -ne "Disabled") {
Add-Rec "INFO" ("Scheduled task not Disabled: {0} (State={1})" -f $tp, $state)
}
} catch {
Say (" {0} State=NotFound" -f $tp)
}
}
Say ""
# -----------------------
# Event Logs (max size)
# -----------------------
Say "Event Logs (max size):"
function Get-EventLogMaxMB($logName) {
try {
$out = & wevtutil gl $logName 2>$null
if (-not $out) { return $null }
$line = $out | Where-Object { $_ -match '^\s*maxSize:\s*\d+' } | Select-Object -First 1
if (-not $line) { return $null }
$bytes = [int64]($line -replace '^\s*maxSize:\s*','')
return [Math]::Round($bytes / 1MB, 0)
} catch {
return $null
}
}
$logMax = @{}
foreach ($ln in @("Application","System","Security")) {
$mb = Get-EventLogMaxMB $ln
$logMax[$ln] = $mb
if ($mb -ne $null) { Say (" {0,-12} MaxSizeMB={1}" -f $ln, $mb) }
else { Say (" {0,-12} MaxSizeMB=(unknown)" -f $ln) }
}
if ($logMax["Application"] -ne $null -and [int]$logMax["Application"] -ne $ExpectedAppLogMB) {
Add-Rec "WARN" ("EventLog Application max is {0} MB (expected {1} MB)." -f $logMax["Application"], $ExpectedAppLogMB)
}
if ($logMax["System"] -ne $null -and [int]$logMax["System"] -ne $ExpectedSysLogMB) {
Add-Rec "WARN" ("EventLog System max is {0} MB (expected {1} MB)." -f $logMax["System"], $ExpectedSysLogMB)
}
if ($logMax["Security"] -ne $null -and [int]$logMax["Security"] -ne $ExpectedSecLogMB) {
Add-Rec "INFO" ("EventLog Security max is {0} MB (expected {1} MB (some builds restrict values))." -f $logMax["Security"], $ExpectedSecLogMB)
}
Say ""
# -----------------------
# NTFS LastAccess
# -----------------------
Say "NTFS LastAccess:"
try {
$o = & fsutil behavior query disablelastaccess 2>$null
$lastAccessVal = ($o | Select-String -Pattern '\d+' | Select-Object -First 1).Matches.Value
if ($lastAccessVal) {
$meaning = switch ($lastAccessVal) {
"0" { "Enabled" }
"1" { "Disabled (recommended for low IO)" }
"2" { "System Managed" }
"3" { "Enabled (user-managed)" }
default { "Unknown" }
}
Say (" DisableLastAccess={0} ({1})" -f $lastAccessVal, $meaning)
if ($lastAccessVal -ne "1") {
Add-Rec "WARN" ("DisableLastAccess is {0} (expected 1 = Disabled)." -f $lastAccessVal)
}
} else {
Say " DisableLastAccess=(unknown)"
Add-Rec "WARN" "DisableLastAccess could not be determined."
}
} catch {
Say " DisableLastAccess=(unable to query)"
Add-Rec "WARN" "DisableLastAccess could not be determined."
}
Say ""
# -----------------------
# ACPI Shutdown reliability
# -----------------------
Say "ACPI Shutdown reliability:"
function Get-PowerButtonActionACDC_FromRegistry() {
$base = "HKLM:\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes"
$schemeGuid = $null
try {
$schemeGuid = (Get-ItemProperty -Path $base -Name "ActivePowerScheme" -ErrorAction Stop).ActivePowerScheme
} catch {
return @{ AC=$null; DC=$null; Note="ActivePowerScheme not readable" }
}
if ([string]::IsNullOrWhiteSpace($schemeGuid)) {
return @{ AC=$null; DC=$null; Note="ActivePowerScheme empty" }
}
$SUB_BUTTONS = "4f971e89-eebd-4455-a8de-9e59040e7347"
$PBUTTONACTION = "7648efa3-dd9c-4e3e-b566-50f929386280"
$p = Join-Path $base ($schemeGuid + "\" + $SUB_BUTTONS + "\" + $PBUTTONACTION)
$ac = $null
$dc = $null
try { $ac = (Get-ItemProperty -Path $p -Name "ACSettingIndex" -ErrorAction Stop).ACSettingIndex } catch {}
try { $dc = (Get-ItemProperty -Path $p -Name "DCSettingIndex" -ErrorAction Stop).DCSettingIndex } catch {}
return @{ AC=$ac; DC=$dc; Note=("Scheme={0}" -f $schemeGuid) }
}
$pb = Get-PowerButtonActionACDC_FromRegistry
Say (" Power button action (AC/DC): {0}/{1} (expected 3/3 = Shutdown)" -f $pb.AC, $pb.DC)
if ($pb.AC -ne $null -and $pb.AC -ne 3) { Add-Rec "WARN" ("Power button AC action is {0} (expected 3=Shutdown)." -f $pb.AC) }
if ($pb.DC -ne $null -and $pb.DC -ne 3) { Add-Rec "WARN" ("Power button DC action is {0} (expected 3=Shutdown)." -f $pb.DC) }
if ($pb.AC -eq $null -or $pb.DC -eq $null) {
Add-Rec "INFO" ("Power button AC/DC action could not be parsed (powercfg format/build). {0}" -f $pb.Note)
}
$wtkst = Get-RegStr "HKLM:\SYSTEM\CurrentControlSet\Control" "WaitToKillServiceTimeout"
Say (" WaitToKillServiceTimeout: {0} (expected 5000)" -f $wtkst)
if ($wtkst -and ($wtkst -ne "5000")) { Add-Rec "INFO" ("WaitToKillServiceTimeout is {0} (expected 5000)." -f $wtkst) }
$defAuto = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "AutoEndTasks"
$defHung = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "HungAppTimeout"
$defWait = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "WaitToKillAppTimeout"
Say (" HKU\.DEFAULT AutoEndTasks/Hung/Wait: {0}/{1}/{2} (expected 1/2000/5000)" -f $defAuto, $defHung, $defWait)
if ($defAuto -and $defAuto -ne "1") { Add-Rec "WARN" ("HKU\.DEFAULT AutoEndTasks is {0} (expected 1)." -f $defAuto) }
if ($defHung -and $defHung -ne "2000") { Add-Rec "INFO" ("HKU\.DEFAULT HungAppTimeout is {0} (expected 2000)." -f $defHung) }
if ($defWait -and $defWait -ne "5000") { Add-Rec "INFO" ("HKU\.DEFAULT WaitToKillAppTimeout is {0} (expected 5000)." -f $defWait) }
$cuAuto = Get-RegStr "HKCU:\Control Panel\Desktop" "AutoEndTasks"
$cuHung = Get-RegStr "HKCU:\Control Panel\Desktop" "HungAppTimeout"
$cuWait = Get-RegStr "HKCU:\Control Panel\Desktop" "WaitToKillAppTimeout"
Say (" HKCU AutoEndTasks/Hung/Wait: {0}/{1}/{2} (expected 1/2000/5000)" -f $cuAuto, $cuHung, $cuWait)
if ($cuAuto -and $cuAuto -ne "1") { Add-Rec "WARN" ("HKCU AutoEndTasks is {0} (expected 1)." -f $cuAuto) }
if ($cuHung -and $cuHung -ne "2000") { Add-Rec "INFO" ("HKCU HungAppTimeout is {0} (expected 2000)." -f $cuHung) }
if ($cuWait -and $cuWait -ne "5000") { Add-Rec "INFO" ("HKCU WaitToKillAppTimeout is {0} (expected 5000)." -f $cuWait) }
Say ""
# -----------------------
# Reboot hint
# -----------------------
Say "Reboot hint:"
$rebootPending = $false
try {
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { $rebootPending = $true }
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { $rebootPending = $true }
$pfr = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name "PendingFileRenameOperations" -ErrorAction SilentlyContinue
if ($pfr) { $rebootPending = $true }
if ($rebootPending) { Say " WARN: Reboot appears pending." }
else { Say " OK: No reboot pending flags detected." }
} catch {
Say " (unable to determine reboot pending state)"
}
Say ""
# -----------------------
# CIRCLE Services
# -----------------------
Say "CIRCLE Services:"
$circleServices = @("circle-agent","circle-watchdog")
foreach ($name in $circleServices) {
$s = $null
try { $s = Get-CimInstance Win32_Service -Filter ("Name='{0}'" -f $name) -ErrorAction SilentlyContinue } catch {}
if ($s) {
Say (" {0} Name={1} State={2} StartMode={3}" -f $s.DisplayName, $s.Name, $s.State, $s.StartMode)
if ($s.StartMode -ne "Auto") {
Add-Rec "WARN" ("{0} StartMode is {1} (expected Auto)." -f $s.DisplayName, $s.StartMode)
}
if ($s.State -ne "Running") {
Add-Rec "WARN" ("{0} is not Running (State={1})." -f $s.DisplayName, $s.State)
}
Validate-CircleServiceRecovery $name $s.DisplayName
} else {
Say (" {0} NOT FOUND" -f $name)
Add-Rec "WARN" ("CIRCLE service not found: {0}" -f $name)
}
}
Say ""
# -----------------------
# CIRCLE notify (Task Scheduler)
# -----------------------
Say "CIRCLE notify (Task Scheduler):"
$notifyExe = "C:\circle\circle-notify.exe"
$notifyTaskName = "CIRCLE circle-notify (Logon)"
$notifyProcName = "circle-notify"
if (Test-Path $notifyExe) {
Say (" circle-notify.exe: present ({0})" -f $notifyExe)
} else {
Say (" circle-notify.exe: MISSING ({0})" -f $notifyExe)
Add-Rec "WARN" ("circle-notify.exe missing: {0}" -f $notifyExe)
}
try {
$t = Get-ScheduledTask -TaskName $notifyTaskName -ErrorAction Stop
$ti = Get-ScheduledTaskInfo -TaskName $notifyTaskName -ErrorAction SilentlyContinue
$enabled = $true
try { $enabled = [bool]$t.Settings.Enabled } catch { $enabled = $true }
$state = if ($t.State) { "$($t.State)" } else { "Unknown" }
if (-not $enabled) { $state = "Disabled" }
Say (" Task: present Name='{0}' State={1}" -f $notifyTaskName, $state)
if ($ti) { Say (" Task LastRun: {0}" -f $ti.LastRunTime) }
if ($state -eq "Disabled") {
Add-Rec "WARN" ("Task '{0}' is Disabled." -f $notifyTaskName)
}
try {
$mi = "$($t.Settings.MultipleInstances)"
Say (" Task MultipleInstances: {0} (expected IgnoreNew)" -f $mi)
if ($mi -ne "IgnoreNew") {
Add-Rec "WARN" ("Task '{0}' MultipleInstances is {1} (expected IgnoreNew)." -f $notifyTaskName, $mi)
}
} catch {}
try {
$rc = [int]$t.Settings.RestartCount
$ri = "$($t.Settings.RestartInterval)"
Say (" Task RestartCount/Interval: {0} / {1} (expected 5 / PT3M)" -f $rc, $ri)
if ($rc -ne 5) {
Add-Rec "WARN" ("Task '{0}' RestartCount is {1} (expected 5)." -f $notifyTaskName, $rc)
}
if ($ri -ne "PT3M") {
Add-Rec "INFO" ("Task '{0}' RestartInterval is {1} (expected PT3M)." -f $notifyTaskName, $ri)
}
} catch {}
try {
$etl = "$($t.Settings.ExecutionTimeLimit)"
Say (" Task ExecutionTimeLimit: {0} (expected PT0S)" -f $etl)
if ($etl -ne "PT0S") {
Add-Rec "INFO" ("Task '{0}' ExecutionTimeLimit is {1} (expected PT0S)." -f $notifyTaskName, $etl)
}
} catch {}
try {
$a = $t.Actions | Select-Object -First 1
if ($a -and $a.Execute) {
Say (" Task Action Execute: {0}" -f $a.Execute)
if ($a.Execute -notmatch 'circle-notify\.exe$') {
Add-Rec "WARN" ("Task '{0}' action is '{1}' (expected circle-notify.exe)." -f $notifyTaskName, $a.Execute)
}
}
} catch {}
} catch {
Say (" Task: MISSING Name='{0}'" -f $notifyTaskName)
Add-Rec "WARN" ("Scheduled Task missing: {0}" -f $notifyTaskName)
}
try {
$p = Get-Process -Name $notifyProcName -ErrorAction SilentlyContinue
if ($p) {
Say (" Process: running (count={0})" -f $p.Count)
} else {
Say " Process: not running (may be OK if user not logged on yet)"
}
} catch {}
Say ""
# -----------------------
# Recommendations + summary
# -----------------------
Say "Recommendations:"
if ($recs.Count -eq 0) {
Say " (none) All checked settings match the expected low-IOPS profile."
} else {
foreach ($r in $recs) { Say $r }
}
Say ""
$infoCount = ($recs | Where-Object { $_ -match '^ INFO:' }).Count
$warnCount = ($recs | Where-Object { $_ -match '^ WARN:' }).Count
if ($warnCount -eq 0 -and $infoCount -eq 0) {
Say "TEMPLATE READY: PASS"
} elseif ($warnCount -eq 0) {
Say ("TEMPLATE READY: PASS ({0} info)" -f $infoCount)
} else {
Say ("TEMPLATE READY: FAIL ({0} warn, {1} info)" -f $warnCount, $infoCount)
}
Say @"
Recommended general steps for making a new template:
====================================================
1) Windows update
2) Disable Windows Update (noVirusThanks / your method)
3) Apply baseline:
powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Apply.ps1
4) Verify baseline:
powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Check.ps1
5) Clean up
DISM /Online /Cleanup-Image /StartComponentCleanup
cleanmgr
compact.exe /compactOS:always
shutdown /r /t 0
defrag C: /X
sdelete -z C:
shutdown /s /t 0
6) Save as template (dashboard)
"@
Say ""
Say "Done."
# WinVM-WindowsUpdate-Enable.ps1
# Re-enables Windows Update services and core scheduled tasks
# Run as Administrator
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
$ErrorActionPreference = "Continue"
function Say($m) { Write-Host $m }
function Enable-ServiceSafe($name, $startType) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) {
Say " (skip) Service not found: $name"
return
}
try {
Say " Set service $name StartType=$startType"
sc.exe config $name start= $startType | Out-Null
} catch {}
try {
Say " Start service $name"
Start-Service -Name $name -ErrorAction SilentlyContinue
} catch {}
}
function Enable-TaskSafe($taskPath) {
Say " Enable scheduled task $taskPath"
try {
& schtasks.exe /Change /TN $taskPath /Enable | Out-Null
} catch {}
}
Say "=== Re-enable Windows Update ==="
Say ""
# --- Services ---
Say "Services:"
Enable-ServiceSafe "wuauserv" auto
Enable-ServiceSafe "UsoSvc" auto
Enable-ServiceSafe "BITS" delayed-auto
Enable-ServiceSafe "WaaSMedicSvc" demand
Say ""
# --- Scheduled Tasks ---
Say "Scheduled Tasks:"
$tasks = @(
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($t in $tasks) {
Enable-TaskSafe $t
}
Say ""
# --- Optional: clear Windows Update policy keys (best effort) ---
Say "Registry policy cleanup (best effort):"
$wuPolicy = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
if (Test-Path $wuPolicy) {
try {
Remove-Item -Path $wuPolicy -Recurse -Force
Say " Removed $wuPolicy"
} catch {
Say " WARN: Could not remove $wuPolicy (may be protected)"
}
} else {
Say " (none)"
}
Say ""
Say "Windows Update re-enabled."
Say "NOTE: A reboot is recommended."
# WinVM-WindowsUpdate-Toggle.ps1 (v1.0)
# Soft toggle for Windows Update (no ACL/SDDL hardening).
# -Mode Disable : disable wuauserv + disable WindowsUpdate tasks (Scheduled Start, sih, sihboot)
# -Mode Enable : enable/start services + enable those tasks
# Compatible: Windows 10/11 (incl. LTSC). Run as Administrator.
# RunCmd:
# single command:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
# multiple commans:
# Admin PowerShell
# Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Disable
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Status
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Enable
# ASCII-only comments
param(
[Parameter(Mandatory=$true)]
[ValidateSet("Enable","Disable","Status")]
[string]$Mode
)
$ErrorActionPreference = "Continue"
# include core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode $Mode
\ No newline at end of file
# WinVM-IOPS-Apply.ps1 (v1.5)
# Applies low-IOPS template settings on Windows 10/11
# Updates:
# - SCM recovery policy for circle-agent + circle-watchdog (reset=86400, restart=180000 x3, failureflag=1)
# - Replace Startup shortcut autostart with Task Scheduler for circle-notify.exe (logon, single instance, restart on failure)
# - Simplify hive handling: assume script is run by cloud user in elevated PS; use HKCU only (plus HKU\.DEFAULT)
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Apply.ps1
# ASCII-only comments
param(
# Pagefile size: you can pass 4096 (MB) or "4G" / "4096M"
[string]$Pagefile = "4G",
# Event log max sizes in MB
[int]$AppLogMB = 4,
[int]$SysLogMB = 4,
[int]$SecLogMB = 20,
[switch]$WhatIf
)
# --- Parameter validation / help ---
# If any unnamed or unknown arguments are passed, show help and exit.
if ($args.Count -gt 0) {
Write-Host ""
Write-Host "ERROR: Unknown parameter(s): $($args -join ' ')" -ForegroundColor Red
Write-Host ""
Write-Host "Usage:"
Write-Host " powershell -ExecutionPolicy Bypass -File WinVM-IOPS-Apply.ps1 [-Pagefile <size>] [-WhatIf]"
Write-Host ""
Write-Host "Options:"
Write-Host " -Pagefile <size> Pagefile size (default: 4G)"
Write-Host " Examples: 4096 | 4096M | 4G | 6G"
Write-Host " -WhatIf Show what would be changed, do not apply"
Write-Host ""
exit 1
}
$ErrorActionPreference = "Inquire"
# include WU core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
function Say($msg) { Write-Host $msg }
function Require-Admin {
$id = [Security.Principal.WindowsIdentity]::GetCurrent()
$p = New-Object Security.Principal.WindowsPrincipal($id)
if (-not $p.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
throw "Run this script as Administrator."
}
}
function Run($desc, [ScriptBlock]$action) {
if ($WhatIf) {
Say ("[WHATIF] {0}" -f $desc)
} else {
Say ("[DO] {0}" -f $desc)
& $action
}
}
function Parse-SizeToMB([string]$s) {
if ([string]::IsNullOrWhiteSpace($s)) { return 4096 }
$t = $s.Trim().ToUpperInvariant()
# If it's pure number, treat as MB
if ($t -match '^\d+$') { return [int]$t }
# Accept forms like 4G, 4096M
if ($t -match '^(\d+)\s*G$') { return [int]$Matches[1] * 1024 }
if ($t -match '^(\d+)\s*M$') { return [int]$Matches[1] }
throw "Invalid -Pagefile value '$s'. Use e.g. 4096, 4096M, or 4G."
}
function Set-ServiceDisabled($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Stop service $name" { Stop-Service -Name $name -Force -ErrorAction SilentlyContinue }
Run "Disable service $name" { Set-Service -Name $name -StartupType Disabled -ErrorAction SilentlyContinue }
}
function Set-ServiceManual($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Stop service $name" { Stop-Service -Name $name -Force -ErrorAction SilentlyContinue }
Run "Set service $name to Manual" { Set-Service -Name $name -StartupType Manual -ErrorAction SilentlyContinue }
}
function Clear-EventLogBestEffort($logName) {
Run "Clear EventLog '$logName' (best effort)" {
& wevtutil cl $logName 2>$null | Out-Null
}
}
function Set-EventLogMaxMB($logName, $mb) {
# wevtutil expects bytes
$bytes = [int64]$mb * 1024 * 1024
Run "Set EventLog '$logName' max size to ${mb}MB" {
& wevtutil sl $logName /ms:$bytes 2>$null | Out-Null
}
}
function Disable-ScheduledTaskSafe($taskPath) {
Run "Disable scheduled task $taskPath" {
$tp = ([IO.Path]::GetDirectoryName($taskPath) + "\")
$tn = ([IO.Path]::GetFileName($taskPath))
Disable-ScheduledTask -TaskPath $tp -TaskName $tn -ErrorAction SilentlyContinue | Out-Null
}
}
# Helper: set Desktop shutdown behavior values under a registry root
function Set-DesktopShutdownTweaks($root) {
Run "Set AutoEndTasks/timeout tweaks in $root" {
& reg add "$root\Control Panel\Desktop" /v AutoEndTasks /t REG_SZ /d 1 /f | Out-Null
& reg add "$root\Control Panel\Desktop" /v HungAppTimeout /t REG_SZ /d 2000 /f | Out-Null
& reg add "$root\Control Panel\Desktop" /v WaitToKillAppTimeout /t REG_SZ /d 5000 /f | Out-Null
}
}
# Helper: set user env vars (TEMP/TMP) under a registry root
function Set-UserEnvTempTmp($root, [string]$tempPath) {
Run "Set TEMP/TMP (User) in $root to $tempPath" {
& reg add "$root\Environment" /v TEMP /t REG_SZ /d $tempPath /f | Out-Null
& reg add "$root\Environment" /v TMP /t REG_SZ /d $tempPath /f | Out-Null
}
}
# --- SCM recovery helpers (services) ---
function Set-ServiceRecoveryPolicy($name) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) { Say (" (skip) service not found: {0}" -f $name); return }
Run "Set SCM recovery policy for service '$name' (reset=86400, restart=180000 x3)" {
& sc.exe failure "$name" reset= 86400 actions= restart/180000/restart/180000/restart/180000 | Out-Null
& sc.exe failureflag "$name" 1 | Out-Null
}
}
# --- Task Scheduler helper (circle-notify.exe) ---
function Ensure-CircleNotifyLogonTask(
[string]$taskName,
[string]$exePath
) {
if (-not (Test-Path $exePath)) {
Say (" WARN: circle-notify exe not found: {0} (task not created)" -f $exePath)
return
}
Run "Create/Update Scheduled Task '$taskName' (At logon, single instance, restart on failure)" {
$action = New-ScheduledTaskAction -Execute $exePath
# Trigger at logon for current user
$trigger = New-ScheduledTaskTrigger -AtLogOn
# Run only when user is logged on (no password prompt)
$principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType Interactive -RunLevel Highest
$settings = New-ScheduledTaskSettingsSet `
-MultipleInstances IgnoreNew `
-ExecutionTimeLimit (New-TimeSpan -Seconds 0) `
-RestartCount 5 `
-RestartInterval (New-TimeSpan -Minutes 3)
$task = New-ScheduledTask -Action $action -Trigger $trigger -Principal $principal -Settings $settings
Register-ScheduledTask -TaskName $taskName -InputObject $task -Force | Out-Null
}
}
# --- Remove legacy client from Startup
function Remove-LegacyStartupClientArtifacts {
# Remove legacy Startup items for old client.exe autostart (best effort)
# Kill running legacy process only if legacy startup artifact exists.
# Robust against weird object[] values; uses Path.Combine.
$appData = $env:APPDATA
if ($appData -is [System.Array]) { $appData = $appData[0] }
$appData = [string]$appData
if ([string]::IsNullOrWhiteSpace($appData)) { return }
$startup = [System.IO.Path]::Combine($appData, "Microsoft\Windows\Start Menu\Programs\Startup")
if (-not (Test-Path $startup)) { return }
$names = @(
"client.exe",
"client.lnk"
)
$candidatePaths = foreach ($n in $names) { [System.IO.Path]::Combine($startup, $n) }
# Gather .lnk files once
$lnkFiles = @()
try { $lnkFiles = Get-ChildItem -Path $startup -Filter "*.lnk" -ErrorAction SilentlyContinue } catch {}
# Detect whether any legacy startup artifact exists (file or lnk->client.exe)
$hasLegacy = $false
foreach ($p in $candidatePaths) {
if (Test-Path $p) { $hasLegacy = $true; break }
}
if (-not $hasLegacy -and $lnkFiles.Count -gt 0) {
foreach ($lnk in $lnkFiles) {
try {
$wsh = New-Object -ComObject WScript.Shell
$sc = $wsh.CreateShortcut($lnk.FullName)
$tp = $sc.TargetPath
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($sc) | Out-Null } catch {}
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($wsh) | Out-Null } catch {}
if ($tp -and ($tp.ToLowerInvariant().EndsWith("\client.exe"))) {
$hasLegacy = $true
break
}
} catch {
# ignore
}
}
}
if (-not $hasLegacy) {
Say " (skip) no legacy client.exe Startup artifact detected."
return
}
# If we get here, legacy startup exists -> kill legacy client.exe first (often 2 procs due to PyInstaller)
Run "Stop legacy client.exe processes (best effort)" {
$legacyPaths = @(
"c:\circle\client.exe",
($startup.ToLowerInvariant() + "\client.exe")
)
try {
$procs = Get-Process -Name "client" -ErrorAction SilentlyContinue
if ($procs) {
foreach ($p in $procs) {
# Prefer safe path match if we can read it
try {
if ($p.Path) {
$pp = $p.Path.ToLowerInvariant()
if ($legacyPaths -contains $pp) {
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
}
} else {
Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue
}
} catch {
# Fall back to killing by Id if anything weird happens
try { Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue } catch {}
}
}
}
} catch {}
}
# Now remove legacy startup items
Run "Remove legacy Startup artifacts (client.exe / old shortcuts) from $startup" {
foreach ($p in $candidatePaths) {
if (Test-Path $p) {
Remove-Item -LiteralPath $p -Force -ErrorAction SilentlyContinue
}
}
# Remove any .lnk that points to *\client.exe
foreach ($lnk in $lnkFiles) {
try {
$wsh = New-Object -ComObject WScript.Shell
$sc = $wsh.CreateShortcut($lnk.FullName)
$tp = $sc.TargetPath
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($sc) | Out-Null } catch {}
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($wsh) | Out-Null } catch {}
if ($tp -and ($tp.ToLowerInvariant().EndsWith("\client.exe"))) {
Remove-Item -LiteralPath $lnk.FullName -Force -ErrorAction SilentlyContinue
}
} catch {
# ignore
}
}
}
}
Require-Admin
# Cache CS once (avoid repeated CIM)
$cs = $null
try { $cs = Get-CimInstance Win32_ComputerSystem } catch {}
# Normalize pagefile MB
$pagefileMB = Parse-SizeToMB $Pagefile
Say "=== Apply low-IOPS template settings (v1.5) ==="
Say ("Computer: {0}" -f $env:COMPUTERNAME)
Say ("Pagefile target: {0} MB" -f $pagefileMB)
Say ""
# 1) Pagefile fixed size (robust: set via registry)
Run "Set fixed pagefile ${pagefileMB}/${pagefileMB} MB on C:\" {
# Disable automatic pagefile management (CIM)
if ($cs) {
Set-CimInstance -InputObject $cs -Property @{ AutomaticManagedPagefile = $false } -Confirm:$false | Out-Null
}
# Set PagingFiles (REG_MULTI_SZ): "path initialMB maximumMB"
$pf = "C:\pagefile.sys $pagefileMB $pagefileMB"
& reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" `
/v PagingFiles /t REG_MULTI_SZ /d $pf /f | Out-Null
# Optional: clear temp pagefile flag if present
& reg add "HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" `
/v TempPageFile /t REG_DWORD /d 0 /f | Out-Null
}
# 2) Disable noisy services
Say ""
Say "Services:"
Set-ServiceDisabled "WSearch" # Windows Search
Set-ServiceDisabled "SysMain" # Superfetch/SysMain
# Windows Update related (lab template style)
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode Disable
# 3) Defender scheduled scan -> Never (Day=8)
Say ""
Say "Defender:"
Run "Set Defender scheduled scan day to Never (8)" {
Set-MpPreference -ScanScheduleDay 8 -ErrorAction SilentlyContinue | Out-Null
}
# 3.1) Defender exclusions (low-IOPS friendly)
Say ""
Say "Defender Exclusions:"
$DefenderExclusions = @(
"C:\Windows\Temp",
"C:\Windows\SoftwareDistribution",
"C:\ProgramData\Microsoft\Windows Defender",
"C:\pagefile.sys"
)
foreach ($path in $DefenderExclusions) {
Run "Add Defender exclusion $path" {
Add-MpPreference -ExclusionPath $path -ErrorAction SilentlyContinue | Out-Null
}
}
# 4) NTFS LastAccess disable
Say ""
Say "NTFS:"
Run "Disable NTFS LastAccess updates (DisableLastAccess=1)" {
& fsutil behavior set disablelastaccess 1 | Out-Null
}
# 5) Event log sizes (clear then set)
Say ""
Say "Event Logs:"
Clear-EventLogBestEffort "Application"
Clear-EventLogBestEffort "System"
Clear-EventLogBestEffort "Security"
Set-EventLogMaxMB "Application" $AppLogMB
Set-EventLogMaxMB "System" $SysLogMB
Set-EventLogMaxMB "Security" $SecLogMB
# 6) (Optional-ish) Try to disable common Update scheduled tasks
# moved to WU core
# 7) ACPI shutdown reliability (RDP-friendly)
Say ""
Say "ACPI Shutdown reliability:"
# 7.1) Ensure Power Button action = Shutdown (AC and DC)
Run "Set power button action to Shutdown (AC/DC)" {
& powercfg /setacvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3 | Out-Null
& powercfg /setdcvalueindex SCHEME_CURRENT SUB_BUTTONS PBUTTONACTION 3 | Out-Null
& powercfg /setactive SCHEME_CURRENT | Out-Null
}
# 7.2) Apply to the default (logon screen / disconnected situations)
Set-DesktopShutdownTweaks "HKU\.DEFAULT"
# 7.3) Apply to current user (cloud) - simplified
Say " Apply Desktop tweaks to HKCU (current user)"
Set-DesktopShutdownTweaks "HKCU"
# 7.4) Service shutdown timeout (system-wide)
Run "Set WaitToKillServiceTimeout=5000ms" {
& reg add "HKLM\SYSTEM\CurrentControlSet\Control" /v WaitToKillServiceTimeout /t REG_SZ /d 5000 /f | Out-Null
}
# 7.5) TEMP/TMP (User-level) for current user (cloud) - simplified
Say ""
Say "Environment variables (current user TEMP/TMP):"
$fallbackTemp = Join-Path $env:USERPROFILE "AppData\Local\Temp"
Set-UserEnvTempTmp "HKCU" $fallbackTemp
# 8) CIRCLE services (recovery hardening)
Say ""
Say "CIRCLE services (recovery hardening):"
Set-ServiceRecoveryPolicy "circle-agent"
Set-ServiceRecoveryPolicy "circle-watchdog"
# 9) CIRCLE notify autostart via Task Scheduler (logon)
Say ""
Say "CIRCLE notify autostart (Scheduled Task):"
# Remove legacy Startup autostart entries for old client.exe
Remove-LegacyStartupClientArtifacts
$notifyExe = "C:\circle\circle-notify.exe"
$taskName = "CIRCLE circle-notify (Logon)"
Ensure-CircleNotifyLogonTask -taskName $taskName -exePath $notifyExe
Say ""
Say "Done."
Say "NOTE: Reboot recommended (pagefile + services)."
# WinVM-IOPS-Check.ps1 (v1.7)
# Checks Windows VM settings for low-IOPS template readiness
# Updates:
# - Fix recommendations collection (no [ref] += issues)
# - CIRCLE service names: circle-agent, circle-watchdog
# - CIRCLE notify autostart via Task Scheduler (no Startup shortcut)
# - Simplified user/hive assumptions: run by cloud user in elevated PS; use HKCU only (plus HKU\.DEFAULT)
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Check.ps1
# ASCII-only comments
# ASCII-only comments
param(
# Expected fixed pagefile size (MB). Default 4096 (4G).
[int]$ExpectedPagefileMB = 4096,
# Expected EventLog max sizes (MB)
[int]$ExpectedAppLogMB = 4,
[int]$ExpectedSysLogMB = 4,
[int]$ExpectedSecLogMB = 20
)
$ErrorActionPreference = "Continue"
function Say($msg="") { Write-Host $msg }
function ToGB([double]$bytes) {
[Math]::Round($bytes / 1GB, 2)
}
# Recommendations bucket (ArrayList: robust Add)
$recs = New-Object System.Collections.ArrayList
function Add-Rec([string]$level, [string]$msg) {
[void]$recs.Add((" {0}: {1}" -f $level, $msg))
}
function Get-ServiceStartType($name) {
try {
$svc = Get-CimInstance Win32_Service -Filter ("Name='{0}'" -f $name) -ErrorAction Stop
return $svc.StartMode
} catch {
return "Unknown"
}
}
function Format-ScanDay($day) {
switch ($day) {
0 { "Everyday" }
1 { "Sunday" }
2 { "Monday" }
3 { "Tuesday" }
4 { "Wednesday" }
5 { "Thursday" }
6 { "Friday" }
7 { "Saturday" }
8 { "Never" }
default { "$day" }
}
}
# Expectations helper (services)
function Expect-ServiceInline($svcState, [string]$name, $wantStartTypes, $wantStatus = $null) {
if (-not $svcState.ContainsKey($name)) {
Add-Rec "WARN" ("Service {0} not checked." -f $name)
return
}
$st = $svcState[$name]
if ($wantStartTypes -isnot [System.Array]) { $wantStartTypes = @($wantStartTypes) }
if ($wantStartTypes -notcontains $st.StartType) {
Add-Rec "WARN" ("Service {0} StartType is {1} (expected one of: {2})." -f $name, $st.StartType, ($wantStartTypes -join ", "))
}
if ($wantStatus -and ($st.Status -ne $wantStatus)) {
Add-Rec "WARN" ("Service {0} Status is {1} (expected {2})." -f $name, $st.Status, $wantStatus)
}
}
# Small env getter (no duplicates)
function Get-Env([string]$name, [string]$scope) {
try { return [Environment]::GetEnvironmentVariable($name, $scope) } catch { return $null }
}
function Get-RegStr($path, $name) {
try { (Get-ItemProperty $path -Name $name -ErrorAction Stop).$name } catch { $null }
}
# Parse sc.exe qfailure output (best effort)
function Get-ScFailureSummary([string]$svcName) {
try {
$lines = & sc.exe qfailure $svcName 2>$null
if (-not $lines) { return $null }
$txt = ($lines -join "`n")
$reset = $null
if ($txt -match 'RESET_PERIOD.*:\s*(\d+)') { $reset = [int]$Matches[1] }
$hasRestart = ($txt -match 'RESTART')
return @{
Text = $txt
Reset = $reset
HasRestart = $hasRestart
}
} catch {
return $null
}
}
# Compare recovery policy to expected: reset=86400, restart/180000 x3, failureflag=1
function Validate-CircleServiceRecovery([string]$svcName, [string]$displayName) {
$sum = Get-ScFailureSummary $svcName
if (-not $sum) {
Add-Rec "INFO" ("{0} recovery info unavailable (sc qfailure failed)." -f $displayName)
return
}
if (-not $sum.HasRestart) {
Add-Rec "WARN" ("{0} recovery actions do not include RESTART." -f $displayName)
}
if ($sum.Reset -ne $null -and $sum.Reset -ne 86400) {
Add-Rec "WARN" ("{0} recovery reset period is {1}s (expected 86400)." -f $displayName, $sum.Reset)
} elseif ($sum.Reset -eq $null) {
Add-Rec "INFO" ("{0} recovery reset period not parsed (verify via sc qfailure)." -f $displayName)
}
# Validate restart delay(s) 180000 (best effort)
if ($sum.Text -notmatch '180000') {
Add-Rec "WARN" ("{0} recovery does not mention 180000ms delay (expected restart/180000 x3)." -f $displayName)
}
# failureflag (non-crash) check (best effort parse)
try {
$ff = & sc.exe qfailureflag $svcName 2>$null
if ($ff) {
$t = ($ff -join "`n")
if ($t -match 'FAILURE_ACTIONS_ON_NONCRASH_FAILURES\s*:\s*(TRUE|FALSE|[01])') {
$raw = $Matches[1].ToUpperInvariant()
$ok = ($raw -eq "TRUE" -or $raw -eq "1")
if (-not $ok) {
Add-Rec "WARN" ("{0} failureflag is {1} (expected TRUE/1)." -f $displayName, $Matches[1])
}
} else {
Add-Rec "INFO" ("{0} failureflag output not parsed; verify manually if needed." -f $displayName)
}
} else {
Add-Rec "INFO" ("{0} failureflag info unavailable (sc qfailureflag empty)." -f $displayName)
}
} catch {
Add-Rec "INFO" ("{0} failureflag query failed." -f $displayName)
}
}
Say "=== Windows VM low-IOPS template check (v1.7) ==="
Say ("Computer: {0}" -f $env:COMPUTERNAME)
Say ("User: {0}" -f $env:USERNAME)
# One-time queries
$os = $null
$cs = $null
try { $os = Get-CimInstance Win32_OperatingSystem } catch {}
try { $cs = Get-CimInstance Win32_ComputerSystem } catch {}
if ($os -and $os.Caption) { Say ("OS: {0}" -f $os.Caption) }
else { Say "OS: (unknown)" }
Say ""
# -----------------------
# Memory / Commit (uses $os/$cs once)
# -----------------------
Say "Memory:"
try {
if ($os -and $cs) {
$commitUsedGB = ToGB([double]$os.TotalVirtualMemorySize * 1KB - [double]$os.FreeVirtualMemory * 1KB)
$commitLimitGB = ToGB([double]$os.TotalVirtualMemorySize * 1KB)
$ramGB = [Math]::Round(([double]$cs.TotalPhysicalMemory / 1GB), 2)
Say (" RAM (GB): {0}" -f $ramGB)
Say (" Commit used (GB): {0}" -f $commitUsedGB)
Say (" Commit limit (GB): {0}" -f $commitLimitGB)
} else {
Say " (unable to query)"
}
} catch {
Say " (unable to query)"
}
Say ""
# -----------------------
# Pagefile
# -----------------------
Say "Pagefile:"
$pfReg = $null
try {
$pfReg = (Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management" -ErrorAction Stop).PagingFiles
Say (" Registry PagingFiles: {0}" -f ($pfReg -join " | "))
} catch {
Say " Registry PagingFiles: (unable to read)"
Add-Rec "WARN" "Registry PagingFiles could not be read."
}
try {
if ($cs -and ($cs.AutomaticManagedPagefile -ne $false)) {
Add-Rec "WARN" "AutomaticManagedPagefile is not False (pagefile may be auto-managed)."
}
} catch {}
try {
if ($pfReg) {
$want = $ExpectedPagefileMB
$hit = $false
foreach ($line in $pfReg) {
if ($line -match '^[Cc]:\\pagefile\.sys\s+(\d+)\s+(\d+)$') {
$i = [int]$Matches[1]; $m = [int]$Matches[2]
if ($i -eq $want -and $m -eq $want) {
$hit = $true
} else {
Add-Rec "WARN" ("Registry PagingFiles is '{0}' (expected {1}/{1} MB)." -f $line, $want)
}
}
}
if (-not $hit) {
Add-Rec "WARN" ("Registry PagingFiles does not contain C:\pagefile.sys {0} {0}." -f $ExpectedPagefileMB)
}
}
} catch {}
$pfs = $null
try { $pfs = Get-CimInstance Win32_PageFileSetting -ErrorAction SilentlyContinue } catch {}
if ($pfs) {
foreach ($p in $pfs) {
Say (" {0} InitialMB={1} MaximumMB={2}" -f $p.Name, $p.InitialSize, $p.MaximumSize)
if ($p.Name -match 'pagefile\.sys') {
if (($p.InitialSize -ne $ExpectedPagefileMB) -or ($p.MaximumSize -ne $ExpectedPagefileMB)) {
Add-Rec "WARN" ("WMI PageFileSetting {0} is {1}/{2} MB (expected {3}/{3} MB)." -f $p.Name, $p.InitialSize, $p.MaximumSize, $ExpectedPagefileMB)
}
}
}
} else {
Say " Win32_PageFileSetting: (none)"
Add-Rec "WARN" "Win32_PageFileSetting is empty/unavailable (cannot confirm fixed pagefile via WMI)."
}
$pfu = $null
try { $pfu = Get-CimInstance Win32_PageFileUsage -ErrorAction SilentlyContinue } catch {}
if ($pfu) {
foreach ($u in $pfu) {
Say (" Usage: {0} AllocatedMB={1} CurrentUsedMB={2} PeakUsedMB={3}" -f $u.Name, $u.AllocatedBaseSize, $u.CurrentUsage, $u.PeakUsage)
}
} else {
Say " Win32_PageFileUsage: (none)"
}
Say ""
# -----------------------
# Disk (basic)
# -----------------------
try {
$disk = Get-CimInstance Win32_DiskDrive | Select-Object -First 1
if ($disk) {
$sizeGB = [Math]::Round($disk.Size / 1GB, 0)
Say "Disk (basic):"
Say (" Model={0} Interface={1} SizeGB={2}" -f $disk.Model, $disk.InterfaceType, $sizeGB)
Say ""
}
} catch {}
# -----------------------
# TEMP/TMP
# -----------------------
Say "TEMP/TMP (environment):"
$procTEMP = Get-Env "TEMP" "Process"
$procTMP = Get-Env "TMP" "Process"
$userTEMP = Get-Env "TEMP" "User"
$userTMP = Get-Env "TMP" "User"
Say (" Process TEMP={0}" -f $(if ($procTEMP) { $procTEMP } else { "(missing)" }))
Say (" Process TMP ={0}" -f $(if ($procTMP) { $procTMP } else { "(missing)" }))
Say (" User TEMP={0}" -f $(if ($userTEMP) { $userTEMP } else { "(missing)" }))
Say (" User TMP ={0}" -f $(if ($userTMP) { $userTMP } else { "(missing)" }))
$wantTemp = Join-Path $env:USERPROFILE "AppData\Local\Temp"
Say (" Expected (baseline) ~ {0}" -f $wantTemp)
if (-not $userTEMP) {
Add-Rec "WARN" "User-level TEMP is missing (HKCU\\Environment)."
} elseif ($userTEMP -ne $wantTemp) {
Add-Rec "INFO" ("User-level TEMP is '{0}' (expected '{1}')." -f $userTEMP, $wantTemp)
}
if (-not $userTMP) {
Add-Rec "WARN" "User-level TMP is missing (HKCU\\Environment)."
} elseif ($userTMP -ne $wantTemp) {
Add-Rec "INFO" ("User-level TMP is '{0}' (expected '{1}')." -f $userTMP, $wantTemp)
}
Say ""
# -----------------------
# Defender
# -----------------------
Say "Defender:"
try {
$st = Get-MpComputerStatus
Say (" AMServiceEnabled: {0}" -f $st.AMServiceEnabled)
Say (" RealTimeProtectionEnabled: {0}" -f $st.RealTimeProtectionEnabled)
Say (" OnAccessProtectionEnabled: {0}" -f $st.OnAccessProtectionEnabled)
Say (" AntivirusEnabled: {0}" -f $st.AntivirusEnabled)
Say (" NISEnabled: {0}" -f $st.NISEnabled)
$mp = Get-MpPreference
$day = $mp.ScanScheduleDay
Say (" ScanScheduleDay: {0}" -f (Format-ScanDay $day))
try {
$t = $mp.ScanScheduleTime
if ($t) { Say (" ScanScheduleTime: {0}" -f ($t.ToString("HH:mm:ss"))) }
else { Say " ScanScheduleTime: (not set)" }
} catch {
Say " ScanScheduleTime: (unavailable)"
}
$ex = @($mp.ExclusionPath)
Say (" ExclusionPath count: {0}" -f $ex.Count)
$show = $ex | Select-Object -First 10
if ($show.Count -gt 0) {
Say " ExclusionPath (first 10):"
foreach ($p in $show) { Say (" {0}" -f $p) }
}
if ($mp.ScanScheduleDay -ne 8) {
Add-Rec "WARN" ("Defender ScanScheduleDay is {0} (expected Never)." -f (Format-ScanDay $mp.ScanScheduleDay))
}
$expectedEx = @(
"C:\Windows\Temp",
"C:\Windows\SoftwareDistribution",
"C:\ProgramData\Microsoft\Windows Defender",
"C:\pagefile.sys"
)
foreach ($e in $expectedEx) {
if ($ex -notcontains $e) { Add-Rec "WARN" ("Defender exclusion missing: {0}" -f $e) }
}
} catch {
Say " WARN: Defender cmdlets not available or access denied."
Add-Rec "WARN" "Defender preferences unavailable (cannot validate schedule/exclusions)."
}
Say ""
# -----------------------
# Services
# -----------------------
Say "Services:"
$svcNames = @("WSearch","SysMain","wuauserv","BITS","UsoSvc","WaaSMedicSvc")
$svcState = @{}
foreach ($n in $svcNames) {
$s = Get-Service -Name $n -ErrorAction SilentlyContinue
if ($s) {
$start = Get-ServiceStartType $n
$svcState[$n] = @{ Status = $s.Status; StartType = $start }
Say (" {0} Status={1} StartType={2}" -f $n, $s.Status, $start)
} else {
$svcState[$n] = @{ Status = "NotFound"; StartType = "NotFound" }
Say (" {0} (not found)" -f $n)
}
}
Expect-ServiceInline $svcState "WSearch" "Disabled"
Expect-ServiceInline $svcState "SysMain" "Disabled"
Expect-ServiceInline $svcState "wuauserv" @("Disabled", "Manual")
Expect-ServiceInline $svcState "UsoSvc" "Disabled"
Expect-ServiceInline $svcState "WaaSMedicSvc" @("Disabled", "Manual")
Expect-ServiceInline $svcState "BITS" "Manual"
Say ""
# -----------------------
# Scheduled Tasks (Windows Update)
# -----------------------
Say "Scheduled Tasks:"
$taskPaths = @(
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($tp in $taskPaths) {
try {
$folder = ($tp.Substring(0, $tp.LastIndexOf("\") + 1))
$name = ($tp.Substring($tp.LastIndexOf("\") + 1))
$t = Get-ScheduledTask -TaskPath $folder -TaskName $name -ErrorAction Stop
$ti = Get-ScheduledTaskInfo -TaskPath $folder -TaskName $name -ErrorAction SilentlyContinue
$state = if ($t.State) { "$($t.State)" } else { "Unknown" }
$enabled = $true
try { $enabled = [bool]$t.Settings.Enabled } catch { $enabled = $true }
if (-not $enabled) { $state = "Disabled" }
if ($ti) { Say (" {0} State={1} LastRun={2}" -f $tp, $state, $ti.LastRunTime) }
else { Say (" {0} State={1}" -f $tp, $state) }
if ($state -ne "Disabled") {
Add-Rec "INFO" ("Scheduled task not Disabled: {0} (State={1})" -f $tp, $state)
}
} catch {
Say (" {0} State=NotFound" -f $tp)
}
}
Say ""
# -----------------------
# Event Logs (max size)
# -----------------------
Say "Event Logs (max size):"
function Get-EventLogMaxMB($logName) {
try {
$out = & wevtutil gl $logName 2>$null
if (-not $out) { return $null }
$line = $out | Where-Object { $_ -match '^\s*maxSize:\s*\d+' } | Select-Object -First 1
if (-not $line) { return $null }
$bytes = [int64]($line -replace '^\s*maxSize:\s*','')
return [Math]::Round($bytes / 1MB, 0)
} catch {
return $null
}
}
$logMax = @{}
foreach ($ln in @("Application","System","Security")) {
$mb = Get-EventLogMaxMB $ln
$logMax[$ln] = $mb
if ($mb -ne $null) { Say (" {0,-12} MaxSizeMB={1}" -f $ln, $mb) }
else { Say (" {0,-12} MaxSizeMB=(unknown)" -f $ln) }
}
if ($logMax["Application"] -ne $null -and [int]$logMax["Application"] -ne $ExpectedAppLogMB) {
Add-Rec "WARN" ("EventLog Application max is {0} MB (expected {1} MB)." -f $logMax["Application"], $ExpectedAppLogMB)
}
if ($logMax["System"] -ne $null -and [int]$logMax["System"] -ne $ExpectedSysLogMB) {
Add-Rec "WARN" ("EventLog System max is {0} MB (expected {1} MB)." -f $logMax["System"], $ExpectedSysLogMB)
}
if ($logMax["Security"] -ne $null -and [int]$logMax["Security"] -ne $ExpectedSecLogMB) {
Add-Rec "INFO" ("EventLog Security max is {0} MB (expected {1} MB (some builds restrict values))." -f $logMax["Security"], $ExpectedSecLogMB)
}
Say ""
# -----------------------
# NTFS LastAccess
# -----------------------
Say "NTFS LastAccess:"
try {
$o = & fsutil behavior query disablelastaccess 2>$null
$lastAccessVal = ($o | Select-String -Pattern '\d+' | Select-Object -First 1).Matches.Value
if ($lastAccessVal) {
$meaning = switch ($lastAccessVal) {
"0" { "Enabled" }
"1" { "Disabled (recommended for low IO)" }
"2" { "System Managed" }
"3" { "Enabled (user-managed)" }
default { "Unknown" }
}
Say (" DisableLastAccess={0} ({1})" -f $lastAccessVal, $meaning)
if ($lastAccessVal -ne "1") {
Add-Rec "WARN" ("DisableLastAccess is {0} (expected 1 = Disabled)." -f $lastAccessVal)
}
} else {
Say " DisableLastAccess=(unknown)"
Add-Rec "WARN" "DisableLastAccess could not be determined."
}
} catch {
Say " DisableLastAccess=(unable to query)"
Add-Rec "WARN" "DisableLastAccess could not be determined."
}
Say ""
# -----------------------
# ACPI Shutdown reliability
# -----------------------
Say "ACPI Shutdown reliability:"
function Get-PowerButtonActionACDC_FromRegistry() {
$base = "HKLM:\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes"
$schemeGuid = $null
try {
$schemeGuid = (Get-ItemProperty -Path $base -Name "ActivePowerScheme" -ErrorAction Stop).ActivePowerScheme
} catch {
return @{ AC=$null; DC=$null; Note="ActivePowerScheme not readable" }
}
if ([string]::IsNullOrWhiteSpace($schemeGuid)) {
return @{ AC=$null; DC=$null; Note="ActivePowerScheme empty" }
}
$SUB_BUTTONS = "4f971e89-eebd-4455-a8de-9e59040e7347"
$PBUTTONACTION = "7648efa3-dd9c-4e3e-b566-50f929386280"
$p = Join-Path $base ($schemeGuid + "\" + $SUB_BUTTONS + "\" + $PBUTTONACTION)
$ac = $null
$dc = $null
try { $ac = (Get-ItemProperty -Path $p -Name "ACSettingIndex" -ErrorAction Stop).ACSettingIndex } catch {}
try { $dc = (Get-ItemProperty -Path $p -Name "DCSettingIndex" -ErrorAction Stop).DCSettingIndex } catch {}
return @{ AC=$ac; DC=$dc; Note=("Scheme={0}" -f $schemeGuid) }
}
$pb = Get-PowerButtonActionACDC_FromRegistry
Say (" Power button action (AC/DC): {0}/{1} (expected 3/3 = Shutdown)" -f $pb.AC, $pb.DC)
if ($pb.AC -ne $null -and $pb.AC -ne 3) { Add-Rec "WARN" ("Power button AC action is {0} (expected 3=Shutdown)." -f $pb.AC) }
if ($pb.DC -ne $null -and $pb.DC -ne 3) { Add-Rec "WARN" ("Power button DC action is {0} (expected 3=Shutdown)." -f $pb.DC) }
if ($pb.AC -eq $null -or $pb.DC -eq $null) {
Add-Rec "INFO" ("Power button AC/DC action could not be parsed (powercfg format/build). {0}" -f $pb.Note)
}
$wtkst = Get-RegStr "HKLM:\SYSTEM\CurrentControlSet\Control" "WaitToKillServiceTimeout"
Say (" WaitToKillServiceTimeout: {0} (expected 5000)" -f $wtkst)
if ($wtkst -and ($wtkst -ne "5000")) { Add-Rec "INFO" ("WaitToKillServiceTimeout is {0} (expected 5000)." -f $wtkst) }
$defAuto = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "AutoEndTasks"
$defHung = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "HungAppTimeout"
$defWait = Get-RegStr "Registry::HKEY_USERS\.DEFAULT\Control Panel\Desktop" "WaitToKillAppTimeout"
Say (" HKU\.DEFAULT AutoEndTasks/Hung/Wait: {0}/{1}/{2} (expected 1/2000/5000)" -f $defAuto, $defHung, $defWait)
if ($defAuto -and $defAuto -ne "1") { Add-Rec "WARN" ("HKU\.DEFAULT AutoEndTasks is {0} (expected 1)." -f $defAuto) }
if ($defHung -and $defHung -ne "2000") { Add-Rec "INFO" ("HKU\.DEFAULT HungAppTimeout is {0} (expected 2000)." -f $defHung) }
if ($defWait -and $defWait -ne "5000") { Add-Rec "INFO" ("HKU\.DEFAULT WaitToKillAppTimeout is {0} (expected 5000)." -f $defWait) }
$cuAuto = Get-RegStr "HKCU:\Control Panel\Desktop" "AutoEndTasks"
$cuHung = Get-RegStr "HKCU:\Control Panel\Desktop" "HungAppTimeout"
$cuWait = Get-RegStr "HKCU:\Control Panel\Desktop" "WaitToKillAppTimeout"
Say (" HKCU AutoEndTasks/Hung/Wait: {0}/{1}/{2} (expected 1/2000/5000)" -f $cuAuto, $cuHung, $cuWait)
if ($cuAuto -and $cuAuto -ne "1") { Add-Rec "WARN" ("HKCU AutoEndTasks is {0} (expected 1)." -f $cuAuto) }
if ($cuHung -and $cuHung -ne "2000") { Add-Rec "INFO" ("HKCU HungAppTimeout is {0} (expected 2000)." -f $cuHung) }
if ($cuWait -and $cuWait -ne "5000") { Add-Rec "INFO" ("HKCU WaitToKillAppTimeout is {0} (expected 5000)." -f $cuWait) }
Say ""
# -----------------------
# Reboot hint
# -----------------------
Say "Reboot hint:"
$rebootPending = $false
try {
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Component Based Servicing\RebootPending") { $rebootPending = $true }
if (Test-Path "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\WindowsUpdate\Auto Update\RebootRequired") { $rebootPending = $true }
$pfr = Get-ItemProperty "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager" -Name "PendingFileRenameOperations" -ErrorAction SilentlyContinue
if ($pfr) { $rebootPending = $true }
if ($rebootPending) { Say " WARN: Reboot appears pending." }
else { Say " OK: No reboot pending flags detected." }
} catch {
Say " (unable to determine reboot pending state)"
}
Say ""
# -----------------------
# CIRCLE Services
# -----------------------
Say "CIRCLE Services:"
$circleServices = @("circle-agent","circle-watchdog")
foreach ($name in $circleServices) {
$s = $null
try { $s = Get-CimInstance Win32_Service -Filter ("Name='{0}'" -f $name) -ErrorAction SilentlyContinue } catch {}
if ($s) {
Say (" {0} Name={1} State={2} StartMode={3}" -f $s.DisplayName, $s.Name, $s.State, $s.StartMode)
if ($s.StartMode -ne "Auto") {
Add-Rec "WARN" ("{0} StartMode is {1} (expected Auto)." -f $s.DisplayName, $s.StartMode)
}
if ($s.State -ne "Running") {
Add-Rec "WARN" ("{0} is not Running (State={1})." -f $s.DisplayName, $s.State)
}
Validate-CircleServiceRecovery $name $s.DisplayName
} else {
Say (" {0} NOT FOUND" -f $name)
Add-Rec "WARN" ("CIRCLE service not found: {0}" -f $name)
}
}
Say ""
# -----------------------
# CIRCLE notify (Task Scheduler)
# -----------------------
Say "CIRCLE notify (Task Scheduler):"
$notifyExe = "C:\circle\circle-notify.exe"
$notifyTaskName = "CIRCLE circle-notify (Logon)"
$notifyProcName = "circle-notify"
if (Test-Path $notifyExe) {
Say (" circle-notify.exe: present ({0})" -f $notifyExe)
} else {
Say (" circle-notify.exe: MISSING ({0})" -f $notifyExe)
Add-Rec "WARN" ("circle-notify.exe missing: {0}" -f $notifyExe)
}
try {
$t = Get-ScheduledTask -TaskName $notifyTaskName -ErrorAction Stop
$ti = Get-ScheduledTaskInfo -TaskName $notifyTaskName -ErrorAction SilentlyContinue
$enabled = $true
try { $enabled = [bool]$t.Settings.Enabled } catch { $enabled = $true }
$state = if ($t.State) { "$($t.State)" } else { "Unknown" }
if (-not $enabled) { $state = "Disabled" }
Say (" Task: present Name='{0}' State={1}" -f $notifyTaskName, $state)
if ($ti) { Say (" Task LastRun: {0}" -f $ti.LastRunTime) }
if ($state -eq "Disabled") {
Add-Rec "WARN" ("Task '{0}' is Disabled." -f $notifyTaskName)
}
try {
$mi = "$($t.Settings.MultipleInstances)"
Say (" Task MultipleInstances: {0} (expected IgnoreNew)" -f $mi)
if ($mi -ne "IgnoreNew") {
Add-Rec "WARN" ("Task '{0}' MultipleInstances is {1} (expected IgnoreNew)." -f $notifyTaskName, $mi)
}
} catch {}
try {
$rc = [int]$t.Settings.RestartCount
$ri = "$($t.Settings.RestartInterval)"
Say (" Task RestartCount/Interval: {0} / {1} (expected 5 / PT3M)" -f $rc, $ri)
if ($rc -ne 5) {
Add-Rec "WARN" ("Task '{0}' RestartCount is {1} (expected 5)." -f $notifyTaskName, $rc)
}
if ($ri -ne "PT3M") {
Add-Rec "INFO" ("Task '{0}' RestartInterval is {1} (expected PT3M)." -f $notifyTaskName, $ri)
}
} catch {}
try {
$etl = "$($t.Settings.ExecutionTimeLimit)"
Say (" Task ExecutionTimeLimit: {0} (expected PT0S)" -f $etl)
if ($etl -ne "PT0S") {
Add-Rec "INFO" ("Task '{0}' ExecutionTimeLimit is {1} (expected PT0S)." -f $notifyTaskName, $etl)
}
} catch {}
try {
$a = $t.Actions | Select-Object -First 1
if ($a -and $a.Execute) {
Say (" Task Action Execute: {0}" -f $a.Execute)
if ($a.Execute -notmatch 'circle-notify\.exe$') {
Add-Rec "WARN" ("Task '{0}' action is '{1}' (expected circle-notify.exe)." -f $notifyTaskName, $a.Execute)
}
}
} catch {}
} catch {
Say (" Task: MISSING Name='{0}'" -f $notifyTaskName)
Add-Rec "WARN" ("Scheduled Task missing: {0}" -f $notifyTaskName)
}
try {
$p = Get-Process -Name $notifyProcName -ErrorAction SilentlyContinue
if ($p) {
Say (" Process: running (count={0})" -f $p.Count)
} else {
Say " Process: not running (may be OK if user not logged on yet)"
}
} catch {}
Say ""
# -----------------------
# Recommendations + summary
# -----------------------
Say "Recommendations:"
if ($recs.Count -eq 0) {
Say " (none) All checked settings match the expected low-IOPS profile."
} else {
foreach ($r in $recs) { Say $r }
}
Say ""
$infoCount = ($recs | Where-Object { $_ -match '^ INFO:' }).Count
$warnCount = ($recs | Where-Object { $_ -match '^ WARN:' }).Count
if ($warnCount -eq 0 -and $infoCount -eq 0) {
Say "TEMPLATE READY: PASS"
} elseif ($warnCount -eq 0) {
Say ("TEMPLATE READY: PASS ({0} info)" -f $infoCount)
} else {
Say ("TEMPLATE READY: FAIL ({0} warn, {1} info)" -f $warnCount, $infoCount)
}
Say @"
Recommended general steps for making a new template:
====================================================
1) Windows update
2) Disable Windows Update (noVirusThanks / your method)
3) Apply baseline:
powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Apply.ps1
4) Verify baseline:
powershell -ExecutionPolicy Bypass -File .\WinVM-IOPS-Check.ps1
5) Clean up
DISM /Online /Cleanup-Image /StartComponentCleanup
cleanmgr
compact.exe /compactOS:always
shutdown /r /t 0
defrag C: /X
sdelete -z C:
shutdown /s /t 0
6) Save as template (dashboard)
"@
Say ""
Say "Done."
# WinVM-WindowsUpdate-Enable.ps1
# Re-enables Windows Update services and core scheduled tasks
# Run as Administrator
# RunCmd:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
$ErrorActionPreference = "Continue"
function Say($m) { Write-Host $m }
function Enable-ServiceSafe($name, $startType) {
$svc = Get-Service -Name $name -ErrorAction SilentlyContinue
if (-not $svc) {
Say " (skip) Service not found: $name"
return
}
try {
Say " Set service $name StartType=$startType"
sc.exe config $name start= $startType | Out-Null
} catch {}
try {
Say " Start service $name"
Start-Service -Name $name -ErrorAction SilentlyContinue
} catch {}
}
function Enable-TaskSafe($taskPath) {
Say " Enable scheduled task $taskPath"
try {
& schtasks.exe /Change /TN $taskPath /Enable | Out-Null
} catch {}
}
Say "=== Re-enable Windows Update ==="
Say ""
# --- Services ---
Say "Services:"
Enable-ServiceSafe "wuauserv" auto
Enable-ServiceSafe "UsoSvc" auto
Enable-ServiceSafe "BITS" delayed-auto
Enable-ServiceSafe "WaaSMedicSvc" demand
Say ""
# --- Scheduled Tasks ---
Say "Scheduled Tasks:"
$tasks = @(
"\Microsoft\Windows\UpdateOrchestrator\Schedule Scan",
"\Microsoft\Windows\UpdateOrchestrator\USO_UxBroker",
"\Microsoft\Windows\UpdateOrchestrator\Reboot",
"\Microsoft\Windows\UpdateOrchestrator\MusUx_UpdateInterval",
"\Microsoft\Windows\WindowsUpdate\Scheduled Start",
"\Microsoft\Windows\WindowsUpdate\sih",
"\Microsoft\Windows\WindowsUpdate\sihboot"
)
foreach ($t in $tasks) {
Enable-TaskSafe $t
}
Say ""
# --- Optional: clear Windows Update policy keys (best effort) ---
Say "Registry policy cleanup (best effort):"
$wuPolicy = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate"
if (Test-Path $wuPolicy) {
try {
Remove-Item -Path $wuPolicy -Recurse -Force
Say " Removed $wuPolicy"
} catch {
Say " WARN: Could not remove $wuPolicy (may be protected)"
}
} else {
Say " (none)"
}
Say ""
Say "Windows Update re-enabled."
Say "NOTE: A reboot is recommended."
# WinVM-WindowsUpdate-Toggle.ps1 (v1.0)
# Soft toggle for Windows Update (no ACL/SDDL hardening).
# -Mode Disable : disable wuauserv + disable WindowsUpdate tasks (Scheduled Start, sih, sihboot)
# -Mode Enable : enable/start services + enable those tasks
# Compatible: Windows 10/11 (incl. LTSC). Run as Administrator.
# RunCmd:
# single command:
# powershell -ExecutionPolicy Bypass -File .\WinVM-WindowsUpdate-Enable.ps1
# multiple commans:
# Admin PowerShell
# Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Disable
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Status
# .\WinVM-WindowsUpdate-Toggle.ps1 -Mode Enable
# ASCII-only comments
param(
[Parameter(Mandatory=$true)]
[ValidateSet("Enable","Disable","Status")]
[string]$Mode
)
$ErrorActionPreference = "Continue"
# include core
. "$PSScriptRoot\WindowsUpdate-Core.ps1"
Set-WindowsUpdate-State -Mode $Mode
\ No newline at end of file
......@@ -19,9 +19,8 @@ from twisted.internet import reactor
from .network import change_ip_windows
from context import BaseContext
from windows.winutils import (
is_frozen_exe, copy_running_exe,
update_service_binpath, servicePostUpdate,
start_process_async
is_frozen_exe, update_service_binpath,
update_component, start_process_async
)
try:
......@@ -34,15 +33,15 @@ logger = logging.getLogger(__name__)
class Context(BaseContext):
service_name = "CIRCLE-agent"
comp_name = "circle-agent"
workdir = r"C:\circle"
update_cmd = "update.ps1"
exe = "circle-agent.exe"
@staticmethod
def postUpdate():
exe_path = join(Context.workdir, Context.exe)
return servicePostUpdate(Context.service_name, exe_path)
ret = update_component(Context.comp_name, Context.workdir, service_fn=update_service_binpath)
logger.debug("update:component: %s", ret)
return ret == "exit"
@staticmethod
def change_password(password):
......@@ -133,11 +132,12 @@ class Context(BaseContext):
start_process_async(executable, workdir=Context.workdir)
else:
if executable.startswith("0_"):
old_exe = update_service_binpath("CIRCLE-agent", join(Context.workdir, executable))
logger.info('%s Updated', old_exe)
ret = update_component(Context.comp_name, Context.workdir, img_pendig=executable, service_fn=update_service_binpath)
logger.info("Update: %s", ret)
if exists(join(Context.workdir, Context.update_cmd)):
logger.debug("starting %s in %s", Context.update_cmd, Context.workdir)
start_process_async(Context.update_cmd, workdir=Context.workdir, delay_seconds=60)
if ret == "exit":
Context.exit_code = 1
reactor.callLater(0, reactor.stop)
......
......@@ -37,6 +37,73 @@ def update_service_binpath(service_name: str, exe_path: str) -> str:
return old_executable
import subprocess
def create_autostart_task(
task_name: str,
exe_path: str,
workdir: str = r"c:\circle",
username: str = "cloud",
restart_interval_minutes: int = 1,
restart_count: int = 5
):
"""
Create/Update a Scheduled Task for a local user (default: .\\cloud) that:
- runs at user logon
- restarts on failure (bounded by restart_count to avoid infinite crash loops)
- can start with a logon delay
Safe Mode handling should be done inside the target program (exit 0 if in safe mode).
"""
logger.debug("autostar:update: %s %s", task_name, exe_path)
# Force local account form for reliability
# if "\\" not in username:
# username = f".\\{username}"
ps_script = f"""
$ErrorActionPreference = "Stop"
$action = New-ScheduledTaskAction `
-Execute "{exe_path}" `
-WorkingDirectory "{workdir}"
$trigger = New-ScheduledTaskTrigger `
-AtLogOn `
-User "{username}"
$settings = New-ScheduledTaskSettingsSet `
-RestartCount {int(restart_count)} `
-RestartInterval (New-TimeSpan -Minutes {int(restart_interval_minutes)}) `
-MultipleInstances IgnoreNew `
-StartWhenAvailable `
-ExecutionTimeLimit ([TimeSpan]::Zero)
$principal = New-ScheduledTaskPrincipal `
-UserId "{username}" `
-LogonType Interactive `
-RunLevel Highest
$task = New-ScheduledTask `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-Principal $principal
Register-ScheduledTask `
-TaskName "{task_name}" `
-InputObject $task `
-Force | Out-Null
"""
res = subprocess.run(
["powershell", "-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", ps_script],
capture_output=True,
text=True
)
logger.error("PS rc=%s stdout=%s stderr=%s", res.returncode, res.stdout, res.stderr)
res.check_returncode()
return None
def copy_running_exe(dest: str) -> bool:
"""
Startup helper:
......@@ -56,15 +123,6 @@ def copy_running_exe(dest: str) -> bool:
copy2(current_exe, dest)
return True
def servicePostUpdate(service_name, exe_path):
logger.debug("Running exe %s", sys.executable)
if is_frozen_exe() and copy_running_exe(exe_path):
logger.debug("The running agent copyed to %s", exe_path)
old_exe = update_service_binpath(service_name, exe_path)
logger.debug("%s service binpath updated %s -> %s", service_name, old_exe, exe_path)
return True
return False
def getRegistryVal(reg_path: str, name: str, default=None):
"""
Read HKLM\\<reg_path>\\<name> and return its value.
......@@ -135,19 +193,21 @@ def start_process_async(path, args=None, workdir=None, delay_seconds=0):
except Exception:
logger.exception(f"Execution failed for: {path} (workdir={workdir})")
def file_is_newer(file_a, file_b):
"""
Returns True if file_a is newer than file_b.
Raises FileNotFoundError if any file does not exist.
Returns True if file_a is newer than file_b of file_b is not exist
Retirns False otherwise or file_a does not exist
"""
if not os.path.exists(file_a):
raise FileNotFoundError(file_a)
return False
if not os.path.exists(file_b):
raise FileNotFoundError(file_b)
return True
return os.path.getmtime(file_a) > os.path.getmtime(file_b)
def start_delayed_process(command, delay_seconds, workdir=None):
"""
Starts a delayed process asynchronously using cmd.exe (no threads).
......@@ -185,6 +245,7 @@ def load_json(path, default=None):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def save_json(path, data):
"""
Saves dict to JSON file atomically.
......@@ -216,7 +277,7 @@ def save_json(path, data):
pass
def update_component(state_file: str, base_dir: str) -> str:
def update_component(name: str, base_dir: str, img_pending=None, service_fn=None) -> str:
"""
Component self-update state handler.
Uses a per-component JSON state file to coordinate a two-phase update:
......@@ -229,26 +290,23 @@ def update_component(state_file: str, base_dir: str) -> str:
and user-mode processes. The running image is identified by comparing
sys.executable with the configured image names.
"""
if not is_frozen_exe():
return "Not frozen"
state_file = name + ".state"
state_file = os.path.join(base_dir, state_file)
img_pending = name + "_.exe" if img_pending is None else img_pending
try:
state = load_json(state_file)
except (FileNotFoundError, json.JSONDecodeError, OSError) as e:
logger.error("Cannot load state: %s", e)
return "State file error"
state["last_checked"] = datetime.now(timezone.utc).isoformat() + "Z"
img = state.get("img", name + ".exe")
img_pending = state.get("img_pending", img_pending)
status = state.get("status", "idle").lower()
save_json(state_file, state)
img = state.get("img")
img_pending = state.get("img_pending")
status = (state.get("status") or "idle").lower()
service = state.get("service")
if not img or not img_pending:
return "Miisng basic update info"
# raise ValueError("status json must contain 'img' and 'img_pending'")
img_path = os.path.join(base_dir, img)
img_pending_path = os.path.join(base_dir, img_pending)
......@@ -268,16 +326,17 @@ def update_component(state_file: str, base_dir: str) -> str:
if newer:
logger.debug("newer: %s", status);
if status == "idle" and is_self_img_pending:
if is_self_img_pending and status != "pending":
status = "pending" # img_pending started first
if status == "idle":
state["status"] = "pending"
save_json(state_file, state)
if service:
# set pending image as service image
update_service_binpath(service, img_pending_path)
if service_fn:
# set pending image as serving image
old_exe = service_fn(name, img_pending_path)
logger.debug("%s binpath updated %s -> %s", name, old_exe, img_pending_path)
return "exit"
else:
# start pending image after 60 sec, then the caller should exit
......@@ -288,14 +347,15 @@ def update_component(state_file: str, base_dir: str) -> str:
if status == "pending" and is_self_img_pending:
# we are running as img_pending -> safe to overwrite img (img is not running)
copy2(img_pending_path, img_path)
logger.debug("Copy: %s ---> %s", img_pending_path, img_path)
logger.info("Copy: %s ---> %s", img_pending_path, img_path)
state["status"] = "copied"
save_json(state_file, state)
if service:
# set standard image as service image
update_service_binpath(service, img_path)
if service_fn:
# set standard image as serving image
old_exe = service_fn(name, img_path)
logger.debug("%s binpath updated %s -> %s", name, old_exe, img_path)
return "exit"
else:
# start standard image after 60 sec
......@@ -303,6 +363,11 @@ def update_component(state_file: str, base_dir: str) -> str:
start_delayed_process(img, workdir=base_dir, delay_seconds=60)
return "copied_img_pending_to_img_and_started_img"
if status == "copyed" and is_self_img:
state["status"] = "idle"
save_json(state_file, state)
return "reset_to_idle1"
return "newer_no_action"
else:
if status != "idle":
......@@ -310,7 +375,9 @@ def update_component(state_file: str, base_dir: str) -> str:
save_json(state_file, state)
return "reset_to_idle"
return "idle_no_change"
if is_self_img_pending:
return "this_cannot_happen"
return "idle_no_change2"
def get_windows_version():
......@@ -324,21 +391,21 @@ def get_windows_version():
# Windows 7
if major == 6 and minor == 1:
return "Windows_7"
return "Win_7"
# Windows 8 / 8.1
if major == 6 and minor in (2, 3):
return "Windows_8"
return "Win_8"
# Windows 10 / 11
if major == 10:
# Windows 11 starts at build 22000
if build >= 22000:
return "Windows_11"
return "Win_11"
else:
return "Windows_10"
return "Win_10"
return f"Windows_{major}_{minor}_{build})"
return f"Win_{major}_{minor}_{build})"
if __name__ == '__main__':
logging.basicConfig(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment