Commit d99e366f by Szeberényi Imre

win_exe + *.ps1

parent fe93383e
...@@ -7,6 +7,8 @@ version.txt ...@@ -7,6 +7,8 @@ version.txt
.coverage .coverage
*~ *~
*.log *.log
build build/
dist dist/
*.spec *.spec
.venv/
.venv64/
\ No newline at end of file
# 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 @@ ...@@ -3,20 +3,31 @@
# Should be in autostart and run by the user logged in # Should be in autostart and run by the user logged in
import logging import logging
import sys
from os.path import join from os.path import join
from os import environ 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 from notify import run_client, get_temp_dir
logger = logging.getLogger() workdir = r"C:\circle"
logfile = join(get_temp_dir(), "agent-client.log") comp_name = "circle-notify"
fh = logging.FileHandler(logfile)
formatter = logging.Formatter( if getattr(sys, "frozen", False):
"%(asctime)s - %(name)s [%(levelname)s] %(message)s") file = os.path.join(workdir, comp_name)
fh.setFormatter(formatter) logger = setup_logging(logfile=file + ".log")
logger.addHandler(fh) else:
logger = logging.getLogger()
level = environ.get('LOGLEVEL', 'INFO') level = environ.get('LOGLEVEL', 'INFO')
logger.setLevel(level) 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__': if __name__ == '__main__':
run_client() run_client()
#
import logging import logging
from logging.handlers import NTEventLogHandler from logging.handlers import NTEventLogHandler
from time import sleep from time import sleep
...@@ -7,38 +8,45 @@ import servicemanager ...@@ -7,38 +8,45 @@ import servicemanager
import socket import socket
import sys import sys
import winerror import winerror
import win32api
import win32event import win32event
import win32service import win32service
import win32serviceutil import win32serviceutil
from utils import setup_logging 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" workdir = r"C:\circle"
comp_name = "circle-watchdog"
if getattr(sys, "frozen", False): 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: else:
logger = setup_logging() logger = setup_logging()
fh = NTEventLogHandler("CIRCLE Watchdog")
fh = NTEventLogHandler(comp_name)
formatter = logging.Formatter( formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s") "%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter) fh.setFormatter(formatter)
logger.addHandler(fh) logger.addHandler(fh)
level = getRegistryVal( level = getRegistryVal(
r"SYSTEM\\CurrentControlSet\\Services\\CIRCLE-Watchdog\\Parameters", fr"SYSTEM\\CurrentControlSet\\Services\\{comp_name}\\Parameters",
"LogLevel", "LogLevel",
"INFO" "INFO"
) )
logger.setLevel(level) logger.setLevel(level)
logger.info("%s loaded", __file__) logger.info("%s loaded, level: %s", __file__, level)
class AppServerSvc (win32serviceutil.ServiceFramework): class AppServerSvc (win32serviceutil.ServiceFramework):
_svc_name_ = "circle-watchdog" _svc_name_ = comp_name
_svc_display_name_ = "CIRCLE Watchdog" _svc_display_name_ = "CIRCLE Watchdog"
_svc_description_ = "Watchdog for CIRCLE Agent" _svc_description_ = "Watchdog for CIRCLE Agent"
def __init__(self, args): def __init__(self, args):
win32serviceutil.ServiceFramework.__init__(self, args) win32serviceutil.ServiceFramework.__init__(self, args)
self.hWaitStop = win32event.CreateEvent(None, 0, 0, None) self.hWaitStop = win32event.CreateEvent(None, 0, 0, None)
...@@ -50,7 +58,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework): ...@@ -50,7 +58,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
def check_service(checked_service): def check_service(checked_service):
return win32serviceutil.QueryServiceStatus(checked_service)[1] == 4 return win32serviceutil.QueryServiceStatus(checked_service)[1] == 4
def start_service(): def start_service(checked_service):
win32serviceutil.StartService(checked_service) win32serviceutil.StartService(checked_service)
timo_base = 20 timo_base = 20
...@@ -61,7 +69,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework): ...@@ -61,7 +69,7 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
if not check_service(checked_service): if not check_service(checked_service):
logger.info("Service %s is not running.", checked_service) logger.info("Service %s is not running.", checked_service)
try: try:
start_service() start_service(checked_service)
timo = timo_base timo = timo_base
logger.info("Service %s restarted.", checked_service) logger.info("Service %s restarted.", checked_service)
except Exception: except Exception:
...@@ -80,11 +88,12 @@ class AppServerSvc (win32serviceutil.ServiceFramework): ...@@ -80,11 +88,12 @@ class AppServerSvc (win32serviceutil.ServiceFramework):
servicemanager.PYS_SERVICE_STARTED, servicemanager.PYS_SERVICE_STARTED,
(self._svc_name_, '')) (self._svc_name_, ''))
logger.info("%s starting", __file__) logger.info("%s starting", __file__)
working_dir = r"C:\circle"
exe = "circle-watchdog.exe" u = win32api.GetUserName()
exe_path = join(working_dir, exe) logger.info("Running as user: %s", u)
logger.debug("Update check: %s %s", self._svc_name_, exe_path)
update = update_component(self._svc_name_ + ".state", workdir) 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) logger.info("Update status: %s", update)
if update == "exit": if update == "exit":
# Service updated, Restart needed # Service updated, Restart needed
......
...@@ -9,24 +9,37 @@ import win32service ...@@ -9,24 +9,37 @@ import win32service
import win32serviceutil import win32serviceutil
import logging import logging
from logging.handlers import NTEventLogHandler from logging.handlers import NTEventLogHandler
from twisted.internet import reactor
#import agent, reactor #import agent, reactor
from agent import main as agent_main 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(comp_name)
fh = NTEventLogHandler("CIRCLE Agent")
formatter = logging.Formatter( formatter = logging.Formatter(
"%(asctime)s - %(name)s [%(levelname)s] %(message)s") "%(asctime)s - %(name)s [%(levelname)s] %(message)s")
fh.setFormatter(formatter) fh.setFormatter(formatter)
logger.addHandler(fh) logger.addHandler(fh)
level = getRegistryVal(
fr"SYSTEM\\CurrentControlSet\\Services\\{comp_name}\\Parameters",
"LogLevel",
"INFO"
)
#logger.propagate = False #logger.propagate = False
#logger.setLevel('INFO') #logger.setLevel('INFO')
logger.info("%s loaded", __file__) logger.setLevel(level)
logger.info("%s loaded, level: %s", __file__, level)
class AppServerSvc (win32serviceutil.ServiceFramework): class AppServerSvc (win32serviceutil.ServiceFramework):
_svc_name_ = "circle-agent" _svc_name_ = comp_name
_svc_display_name_ = "CIRCLE Agent" _svc_display_name_ = "CIRCLE Agent"
_svc_description_ = "CIRCLE cloud contextualization agent" _svc_description_ = "CIRCLE cloud contextualization agent"
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from os import environ, chdir from os import environ, chdir
from os.path import join
import logging import logging
import platform import platform
import subprocess import subprocess
...@@ -12,8 +13,12 @@ from logging.handlers import TimedRotatingFileHandler ...@@ -12,8 +13,12 @@ from logging.handlers import TimedRotatingFileHandler
from pathlib import Path from pathlib import Path
from utils import setup_logging from utils import setup_logging
workdir = r"C:\circle"
comp_name = "circle-agent"
if getattr(sys, "frozen", False): 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: else:
logger = setup_logging() logger = setup_logging()
...@@ -53,7 +58,7 @@ if win: ...@@ -53,7 +58,7 @@ if win:
) )
logger.setLevel(level) logger.setLevel(level)
system = get_windows_version() system = get_windows_version()
system = "DEBUG" # system = "DEBUG"
from context import get_context, get_serial # noqa 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: ...@@ -299,7 +299,7 @@ if win:
message="This VM expiring soon", message="This VM expiring soon",
yes_label="Renew", yes_label="Renew",
no_label="Cancel", no_label="Cancel",
timeout_seconds=60, ) timeout_seconds=120, )
if ans == "yes": if ans == "yes":
ret = accept(line) ret = accept(line)
prompt_yes_no( prompt_yes_no(
...@@ -307,7 +307,7 @@ if win: ...@@ -307,7 +307,7 @@ if win:
message=ret['msg'], message=ret['msg'],
yes_label="OK", yes_label="OK",
no_label="", no_label="",
timeout_seconds=10 if ret['ret'] else 60) timeout_seconds=30 if ret['ret'] else 60)
class SubFactory(protocol.ReconnectingClientFactory): 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-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 --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 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 \ No newline at end of file
# 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-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 ...@@ -19,9 +19,8 @@ from twisted.internet import reactor
from .network import change_ip_windows from .network import change_ip_windows
from context import BaseContext from context import BaseContext
from windows.winutils import ( from windows.winutils import (
is_frozen_exe, copy_running_exe, is_frozen_exe, update_service_binpath,
update_service_binpath, servicePostUpdate, update_component, start_process_async
start_process_async
) )
try: try:
...@@ -34,15 +33,15 @@ logger = logging.getLogger(__name__) ...@@ -34,15 +33,15 @@ logger = logging.getLogger(__name__)
class Context(BaseContext): class Context(BaseContext):
service_name = "CIRCLE-agent" comp_name = "circle-agent"
workdir = r"C:\circle" workdir = r"C:\circle"
update_cmd = "update.ps1" update_cmd = "update.ps1"
exe = "circle-agent.exe"
@staticmethod @staticmethod
def postUpdate(): def postUpdate():
exe_path = join(Context.workdir, Context.exe) ret = update_component(Context.comp_name, Context.workdir, service_fn=update_service_binpath)
return servicePostUpdate(Context.service_name, exe_path) logger.debug("update:component: %s", ret)
return ret == "exit"
@staticmethod @staticmethod
def change_password(password): def change_password(password):
...@@ -133,11 +132,12 @@ class Context(BaseContext): ...@@ -133,11 +132,12 @@ class Context(BaseContext):
start_process_async(executable, workdir=Context.workdir) start_process_async(executable, workdir=Context.workdir)
else: else:
if executable.startswith("0_"): if executable.startswith("0_"):
old_exe = update_service_binpath("CIRCLE-agent", join(Context.workdir, executable)) ret = update_component(Context.comp_name, Context.workdir, img_pendig=executable, service_fn=update_service_binpath)
logger.info('%s Updated', old_exe) logger.info("Update: %s", ret)
if exists(join(Context.workdir, Context.update_cmd)): if exists(join(Context.workdir, Context.update_cmd)):
logger.debug("starting %s in %s", Context.update_cmd, Context.workdir) logger.debug("starting %s in %s", Context.update_cmd, Context.workdir)
start_process_async(Context.update_cmd, workdir=Context.workdir, delay_seconds=60) start_process_async(Context.update_cmd, workdir=Context.workdir, delay_seconds=60)
if ret == "exit":
Context.exit_code = 1 Context.exit_code = 1
reactor.callLater(0, reactor.stop) reactor.callLater(0, reactor.stop)
......
...@@ -37,6 +37,73 @@ def update_service_binpath(service_name: str, exe_path: str) -> str: ...@@ -37,6 +37,73 @@ def update_service_binpath(service_name: str, exe_path: str) -> str:
return old_executable 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: def copy_running_exe(dest: str) -> bool:
""" """
Startup helper: Startup helper:
...@@ -56,15 +123,6 @@ def copy_running_exe(dest: str) -> bool: ...@@ -56,15 +123,6 @@ def copy_running_exe(dest: str) -> bool:
copy2(current_exe, dest) copy2(current_exe, dest)
return True 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): def getRegistryVal(reg_path: str, name: str, default=None):
""" """
Read HKLM\\<reg_path>\\<name> and return its value. 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): ...@@ -135,19 +193,21 @@ def start_process_async(path, args=None, workdir=None, delay_seconds=0):
except Exception: except Exception:
logger.exception(f"Execution failed for: {path} (workdir={workdir})") logger.exception(f"Execution failed for: {path} (workdir={workdir})")
def file_is_newer(file_a, file_b): def file_is_newer(file_a, file_b):
""" """
Returns True if file_a is newer than file_b. Returns True if file_a is newer than file_b of file_b is not exist
Raises FileNotFoundError if any file does not exist. Retirns False otherwise or file_a does not exist
""" """
if not os.path.exists(file_a): if not os.path.exists(file_a):
raise FileNotFoundError(file_a) return False
if not os.path.exists(file_b): if not os.path.exists(file_b):
raise FileNotFoundError(file_b) return True
return os.path.getmtime(file_a) > os.path.getmtime(file_b) return os.path.getmtime(file_a) > os.path.getmtime(file_b)
def start_delayed_process(command, delay_seconds, workdir=None): def start_delayed_process(command, delay_seconds, workdir=None):
""" """
Starts a delayed process asynchronously using cmd.exe (no threads). Starts a delayed process asynchronously using cmd.exe (no threads).
...@@ -185,6 +245,7 @@ def load_json(path, default=None): ...@@ -185,6 +245,7 @@ def load_json(path, default=None):
with open(path, "r", encoding="utf-8") as f: with open(path, "r", encoding="utf-8") as f:
return json.load(f) return json.load(f)
def save_json(path, data): def save_json(path, data):
""" """
Saves dict to JSON file atomically. Saves dict to JSON file atomically.
...@@ -216,7 +277,7 @@ def save_json(path, data): ...@@ -216,7 +277,7 @@ def save_json(path, data):
pass 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. Component self-update state handler.
Uses a per-component JSON state file to coordinate a two-phase update: 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: ...@@ -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 and user-mode processes. The running image is identified by comparing
sys.executable with the configured image names. sys.executable with the configured image names.
""" """
if not is_frozen_exe(): if not is_frozen_exe():
return "Not frozen" return "Not frozen"
state_file = name + ".state"
state_file = os.path.join(base_dir, state_file) state_file = os.path.join(base_dir, state_file)
img_pending = name + "_.exe" if img_pending is None else img_pending
try: try:
state = load_json(state_file) state = load_json(state_file)
except (FileNotFoundError, json.JSONDecodeError, OSError) as e: except (FileNotFoundError, json.JSONDecodeError, OSError) as e:
logger.error("Cannot load state: %s", e) logger.error("Cannot load state: %s", e)
return "State file error"
state["last_checked"] = datetime.now(timezone.utc).isoformat() + "Z" 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) 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_path = os.path.join(base_dir, img)
img_pending_path = os.path.join(base_dir, img_pending) img_pending_path = os.path.join(base_dir, img_pending)
...@@ -268,16 +326,17 @@ def update_component(state_file: str, base_dir: str) -> str: ...@@ -268,16 +326,17 @@ def update_component(state_file: str, base_dir: str) -> str:
if newer: if newer:
logger.debug("newer: %s", status); 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 status = "pending" # img_pending started first
if status == "idle": if status == "idle":
state["status"] = "pending" state["status"] = "pending"
save_json(state_file, state) save_json(state_file, state)
if service: if service_fn:
# set pending image as service image # set pending image as serving image
update_service_binpath(service, img_pending_path) old_exe = service_fn(name, img_pending_path)
logger.debug("%s binpath updated %s -> %s", name, old_exe, img_pending_path)
return "exit" return "exit"
else: else:
# start pending image after 60 sec, then the caller should exit # 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: ...@@ -288,14 +347,15 @@ def update_component(state_file: str, base_dir: str) -> str:
if status == "pending" and is_self_img_pending: if status == "pending" and is_self_img_pending:
# we are running as img_pending -> safe to overwrite img (img is not running) # we are running as img_pending -> safe to overwrite img (img is not running)
copy2(img_pending_path, img_path) 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" state["status"] = "copied"
save_json(state_file, state) save_json(state_file, state)
if service: if service_fn:
# set standard image as service image # set standard image as serving image
update_service_binpath(service, img_path) old_exe = service_fn(name, img_path)
logger.debug("%s binpath updated %s -> %s", name, old_exe, img_path)
return "exit" return "exit"
else: else:
# start standard image after 60 sec # start standard image after 60 sec
...@@ -303,6 +363,11 @@ def update_component(state_file: str, base_dir: str) -> str: ...@@ -303,6 +363,11 @@ def update_component(state_file: str, base_dir: str) -> str:
start_delayed_process(img, workdir=base_dir, delay_seconds=60) start_delayed_process(img, workdir=base_dir, delay_seconds=60)
return "copied_img_pending_to_img_and_started_img" 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" return "newer_no_action"
else: else:
if status != "idle": if status != "idle":
...@@ -310,7 +375,9 @@ def update_component(state_file: str, base_dir: str) -> str: ...@@ -310,7 +375,9 @@ def update_component(state_file: str, base_dir: str) -> str:
save_json(state_file, state) save_json(state_file, state)
return "reset_to_idle" 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(): def get_windows_version():
...@@ -324,21 +391,21 @@ def get_windows_version(): ...@@ -324,21 +391,21 @@ def get_windows_version():
# Windows 7 # Windows 7
if major == 6 and minor == 1: if major == 6 and minor == 1:
return "Windows_7" return "Win_7"
# Windows 8 / 8.1 # Windows 8 / 8.1
if major == 6 and minor in (2, 3): if major == 6 and minor in (2, 3):
return "Windows_8" return "Win_8"
# Windows 10 / 11 # Windows 10 / 11
if major == 10: if major == 10:
# Windows 11 starts at build 22000 # Windows 11 starts at build 22000
if build >= 22000: if build >= 22000:
return "Windows_11" return "Win_11"
else: else:
return "Windows_10" return "Win_10"
return f"Windows_{major}_{minor}_{build})" return f"Win_{major}_{minor}_{build})"
if __name__ == '__main__': if __name__ == '__main__':
logging.basicConfig( 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