Files
Codesys-MCP/python_scripting/deploy_winrm_agent.py

143 lines
5.3 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import argparse
import base64
import pathlib
import sys
from typing import Iterable
import winrm
def run_ps(session: winrm.Session, script: str) -> str:
result = session.run_ps(script)
stdout = result.std_out.decode("utf-8", errors="ignore").strip()
stderr = result.std_err.decode("utf-8", errors="ignore").strip()
if result.status_code != 0:
raise RuntimeError(f"PowerShell failed ({result.status_code}): {stderr or stdout}")
return stdout
def upload_file(session: winrm.Session, local_path: pathlib.Path, remote_path: str) -> None:
data_b64 = base64.b64encode(local_path.read_bytes()).decode("ascii")
temp_b64_path = remote_path + ".b64tmp"
# WinRM command payloads can be short; use conservative chunking.
chunk_size = 500
run_ps(
session,
(
f"$p='{remote_path}';"
"$d=Split-Path -Parent $p;"
"New-Item -ItemType Directory -Force -Path $d | Out-Null;"
f"[System.IO.File]::WriteAllText('{temp_b64_path}','');"
"Write-Output 'ready'"
),
)
for i in range(0, len(data_b64), chunk_size):
chunk = data_b64[i : i + chunk_size]
run_ps(
session,
f"Add-Content -Path '{temp_b64_path}' -Value '{chunk}' -NoNewline",
)
ps_finalize = (
f"$b64=[System.IO.File]::ReadAllText('{temp_b64_path}');"
f"[System.IO.File]::WriteAllBytes('{remote_path}',[Convert]::FromBase64String($b64));"
f"Remove-Item -Path '{temp_b64_path}' -Force;"
f"Write-Output ('uploaded:' + '{remote_path}')"
)
print(run_ps(session, ps_finalize))
def iter_agent_files(root: pathlib.Path) -> Iterable[pathlib.Path]:
for p in sorted(root.rglob("*")):
if p.is_file():
yield p
def main() -> int:
parser = argparse.ArgumentParser(description="Deploy Windows CODESYS agent via WinRM.")
parser.add_argument("--host", required=True)
parser.add_argument("--username", required=True)
parser.add_argument("--password", required=True)
parser.add_argument("--token", required=True)
parser.add_argument("--install-path", default=r"C:\codesys-agent")
parser.add_argument("--codesys-exe-path", required=True)
parser.add_argument("--codesys-profile", required=True)
args = parser.parse_args()
session = winrm.Session(
target=f"http://{args.host}:5985/wsman",
auth=(args.username, args.password),
transport="ntlm",
server_cert_validation="ignore",
)
print("Connected, checking identity...")
whoami = run_ps(session, "whoami")
print(f"Remote identity: {whoami}")
local_root = pathlib.Path(__file__).resolve().parent / "windows_codesys_agent"
if not local_root.is_dir():
raise RuntimeError(f"Local folder not found: {local_root}")
remote_root = args.install_path.rstrip("\\") + r"\windows_codesys_agent"
for f in iter_agent_files(local_root):
rel = f.relative_to(local_root).as_posix().replace("/", "\\")
remote_path = remote_root + "\\" + rel
upload_file(session, f, remote_path)
scripts_dir = remote_root + r"\scripts"
runtime_dir = args.install_path.rstrip("\\") + r"\runtime"
run_script_path = remote_root + r"\run-agent.ps1"
run_script = (
f"$env:CODESYS_AGENT_TOKEN = '{args.token}'\n"
f"$env:CODESYS_AGENT_SCRIPTS_DIR = '{scripts_dir}'\n"
f"$env:CODESYS_AGENT_RUNTIME_DIR = '{runtime_dir}'\n"
"$env:CODESYS_AGENT_ALLOW_DOWNLOAD = 'false'\n"
"$env:CODESYS_AGENT_TIMEOUT_SEC = '180'\n"
f"$env:CODESYS_EXE_PATH = '{args.codesys_exe_path}'\n"
f"$env:CODESYS_PROFILE = '{args.codesys_profile}'\n"
"$env:CODESYS_NO_UI = 'true'\n"
"$env:CODESYS_SCRIPT_TIMEOUT_SEC = '600'\n"
"Set-Location -Path $PSScriptRoot\n"
".\\.venv\\Scripts\\python.exe -m uvicorn agent:app --host 0.0.0.0 --port 8787\n"
)
run_b64 = base64.b64encode(run_script.encode("utf-8")).decode("ascii")
run_ps(
session,
(
f"$p='{run_script_path}';"
f"[System.IO.File]::WriteAllBytes($p,[Convert]::FromBase64String('{run_b64}'));"
"Write-Output ('uploaded:' + $p)"
),
)
setup_ps = (
f"Set-Location -Path '{remote_root}';"
"if (Get-Command py -ErrorAction SilentlyContinue) { py -3 -m venv .venv } "
"elseif (Get-Command python -ErrorAction SilentlyContinue) { python -m venv .venv } "
"else { throw 'Python is not installed on the Windows VM.' };"
".\\.venv\\Scripts\\python.exe -m pip install -r requirements.txt;"
"Get-Process -Name python -ErrorAction SilentlyContinue | "
"Where-Object { $_.Path -like '*windows_codesys_agent*' } | Stop-Process -Force -ErrorAction SilentlyContinue;"
"Start-Process -FilePath powershell.exe "
f"-ArgumentList '-ExecutionPolicy Bypass -File \"{run_script_path}\"' "
"-WindowStyle Hidden;"
"Write-Output 'agent-started'"
)
print(run_ps(session, setup_ps))
print(f"Deployment complete. Agent expected at: http://{args.host}:8787/health")
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except Exception as exc:
print(f"ERROR: {exc}", file=sys.stderr)
raise