@@ -69,36 +69,44 @@ exit 0
6969 }
7070
7171 function expectWindowsRestartWaitOrdering ( content : string , port = 18789 ) {
72- const endCommand = 'schtasks /End /TN "' ;
73- const pollAttemptsInit = "set /a attempts=0" ;
74- const pollLabel = ":wait_for_port_release" ;
75- const pollAttemptIncrement = "set /a attempts+=1" ;
76- const pollNetstatCheck = `netstat -ano | findstr /R /C:":${ port } .*LISTENING" >nul` ;
77- const forceKillLabel = ":force_kill_listener" ;
78- const forceKillCommand = "taskkill /F /PID %%P >>" ;
79- const portReleasedLabel = ":port_released" ;
80- const runCommand = 'schtasks /Run /TN "' ;
81- const endIndex = content . indexOf ( endCommand ) ;
82- const attemptsInitIndex = content . indexOf ( pollAttemptsInit , endIndex ) ;
83- const pollLabelIndex = content . indexOf ( pollLabel , attemptsInitIndex ) ;
84- const pollAttemptIncrementIndex = content . indexOf ( pollAttemptIncrement , pollLabelIndex ) ;
85- const pollNetstatCheckIndex = content . indexOf ( pollNetstatCheck , pollAttemptIncrementIndex ) ;
86- const forceKillLabelIndex = content . indexOf ( forceKillLabel , pollNetstatCheckIndex ) ;
87- const forceKillCommandIndex = content . indexOf ( forceKillCommand , forceKillLabelIndex ) ;
88- const portReleasedLabelIndex = content . indexOf ( portReleasedLabel , forceKillCommandIndex ) ;
89- const runIndex = content . indexOf ( runCommand , portReleasedLabelIndex ) ;
90-
91- expect ( endIndex ) . toBeGreaterThanOrEqual ( 0 ) ;
92- expect ( attemptsInitIndex ) . toBeGreaterThan ( endIndex ) ;
93- expect ( pollLabelIndex ) . toBeGreaterThan ( attemptsInitIndex ) ;
94- expect ( pollAttemptIncrementIndex ) . toBeGreaterThan ( pollLabelIndex ) ;
95- expect ( pollNetstatCheckIndex ) . toBeGreaterThan ( pollAttemptIncrementIndex ) ;
96- expect ( forceKillLabelIndex ) . toBeGreaterThan ( pollNetstatCheckIndex ) ;
97- expect ( forceKillCommandIndex ) . toBeGreaterThan ( forceKillLabelIndex ) ;
98- expect ( portReleasedLabelIndex ) . toBeGreaterThan ( forceKillCommandIndex ) ;
99- expect ( runIndex ) . toBeGreaterThan ( portReleasedLabelIndex ) ;
72+ const stateCheck = "$taskState = Get-OpenClawScheduledTaskState -TaskName $taskName" ;
73+ const runningGuard = 'if ($taskState -eq "Running")' ;
74+ const endCommand =
75+ 'Invoke-OpenClawSchtasksWithTimeout -Arguments @("/End", "/TN", $taskName) -TimeoutSeconds 10' ;
76+ const skipEndLog = "openclaw restart skipped schtasks end" ;
77+ const pollLoop = "for ($attempt = 1; $attempt -le 10; $attempt++)" ;
78+ const pollCall = `Get-OpenClawListenerPids -Port $port` ;
79+ const forceKillBranch = "if ($attempt -eq 10)" ;
80+ const forceKillCommand = "Stop-Process -Id $listenerPid -Force" ;
81+ const runCommand =
82+ 'Invoke-OpenClawSchtasksWithTimeout -Arguments @("/Run", "/TN", $taskName) -TimeoutSeconds 30' ;
83+ const portAssignment = `$port = ${ port } ` ;
84+ const stateCheckIndex = content . indexOf ( stateCheck ) ;
85+ const runningGuardIndex = content . indexOf ( runningGuard , stateCheckIndex ) ;
86+ const endIndex = content . indexOf ( endCommand , runningGuardIndex ) ;
87+ const skipEndLogIndex = content . indexOf ( skipEndLog , endIndex ) ;
88+ const portAssignmentIndex = content . indexOf ( portAssignment ) ;
89+ const pollLoopIndex = content . indexOf ( pollLoop , skipEndLogIndex ) ;
90+ const pollCallIndex = content . indexOf ( pollCall , pollLoopIndex ) ;
91+ const forceKillBranchIndex = content . indexOf ( forceKillBranch , pollCallIndex ) ;
92+ const forceKillCommandIndex = content . indexOf ( forceKillCommand , forceKillBranchIndex ) ;
93+ const runIndex = content . indexOf ( runCommand , forceKillCommandIndex ) ;
94+
95+ expect ( stateCheckIndex ) . toBeGreaterThanOrEqual ( 0 ) ;
96+ expect ( runningGuardIndex ) . toBeGreaterThan ( stateCheckIndex ) ;
97+ expect ( endIndex ) . toBeGreaterThan ( runningGuardIndex ) ;
98+ expect ( skipEndLogIndex ) . toBeGreaterThan ( endIndex ) ;
99+ expect ( portAssignmentIndex ) . toBeGreaterThanOrEqual ( 0 ) ;
100+ expect ( pollLoopIndex ) . toBeGreaterThan ( skipEndLogIndex ) ;
101+ expect ( pollCallIndex ) . toBeGreaterThan ( pollLoopIndex ) ;
102+ expect ( forceKillBranchIndex ) . toBeGreaterThan ( pollCallIndex ) ;
103+ expect ( forceKillCommandIndex ) . toBeGreaterThan ( forceKillBranchIndex ) ;
104+ expect ( runIndex ) . toBeGreaterThan ( forceKillCommandIndex ) ;
100105
101106 expect ( content ) . not . toContain ( "timeout /t 3 /nobreak >nul" ) ;
107+ expect ( content ) . not . toContain ( "findstr" ) ;
108+ expect ( content ) . not . toContain ( "netstat -ano |" ) ;
109+ expect ( content ) . not . toContain ( "schtasks /End /TN" ) ;
102110 }
103111
104112 beforeEach ( ( ) => {
@@ -296,21 +304,25 @@ exit 0
296304 await cleanupScript ( scriptPath ) ;
297305 } ) ;
298306
299- it ( "creates a schtasks restart script on Windows" , async ( ) => {
307+ it ( "creates a guarded schtasks restart script on Windows" , async ( ) => {
300308 Object . defineProperty ( process , "platform" , { value : "win32" } ) ;
301309
302310 const { scriptPath, content } = await prepareAndReadScript ( {
303311 OPENCLAW_PROFILE : "default" ,
304312 } ) ;
305- expect ( scriptPath . endsWith ( ".bat " ) ) . toBe ( true ) ;
313+ expect ( scriptPath . endsWith ( ".cmd " ) ) . toBe ( true ) ;
306314 expect ( content ) . toContain ( "@echo off" ) ;
315+ expect ( content ) . toContain ( "powershell -NoProfile -ExecutionPolicy Bypass -Command" ) ;
316+ expect ( content ) . not . toContain ( "-File" ) ;
317+ expect ( content ) . toContain ( '$ErrorActionPreference = "Continue"' ) ;
307318 expect ( content ) . toContain ( "gateway-restart.log" ) ;
308- expect ( content ) . toContain ( "openclaw restart attempt source=update target=OpenClaw Gateway" ) ;
309- expect ( content ) . toContain ( 'schtasks /End /TN "OpenClaw Gateway"' ) ;
310- expect ( content ) . toContain ( 'schtasks /Run /TN "OpenClaw Gateway" >>' ) ;
319+ expect ( content ) . toContain ( "$taskName = 'OpenClaw Gateway'" ) ;
320+ expect ( content ) . toContain ( "function Invoke-OpenClawSchtasksWithTimeout" ) ;
321+ expect ( content ) . toContain ( "function Get-OpenClawScheduledTaskState" ) ;
322+ expect ( content ) . toContain ( "Get-ScheduledTask -TaskName $TaskName" ) ;
323+ expect ( content ) . toContain ( "openclaw restart skipped schtasks end" ) ;
311324 expectWindowsRestartWaitOrdering ( content ) ;
312- // Batch self-cleanup
313- expect ( content ) . toContain ( 'del "%~f0"' ) ;
325+ expect ( content ) . toContain ( 'del "%~f0" >nul 2>&1' ) ;
314326 await cleanupScript ( scriptPath ) ;
315327 } ) ;
316328
@@ -321,8 +333,11 @@ exit 0
321333 OPENCLAW_PROFILE : "default" ,
322334 OPENCLAW_WINDOWS_TASK_NAME : "OpenClaw Gateway (custom)" ,
323335 } ) ;
324- expect ( content ) . toContain ( 'schtasks /End /TN "OpenClaw Gateway (custom)"' ) ;
325- expect ( content ) . toContain ( 'schtasks /Run /TN "OpenClaw Gateway (custom)"' ) ;
336+ expect ( content ) . toContain ( "$taskName = 'OpenClaw Gateway (custom)'" ) ;
337+ expect ( content ) . toContain ( "Get-OpenClawScheduledTaskState -TaskName $taskName" ) ;
338+ expect ( content ) . toContain (
339+ 'Invoke-OpenClawSchtasksWithTimeout -Arguments @("/End", "/TN", $taskName) -TimeoutSeconds 10' ,
340+ ) ;
326341 expectWindowsRestartWaitOrdering ( content ) ;
327342 await cleanupScript ( scriptPath ) ;
328343 } ) ;
@@ -337,10 +352,10 @@ exit 0
337352 } ,
338353 customPort ,
339354 ) ;
340- expect ( content ) . toContain ( `netstat -ano | findstr /R /C:": ${ customPort } .*LISTENING" >nul ` ) ;
341- expect ( content ) . toContain (
342- `for /f "tokens=5" %%P in (' netstat -ano ^| findstr /R /C:": ${ customPort } .*LISTENING"') do (` ,
343- ) ;
355+ expect ( content ) . toContain ( `$port = ${ customPort } ` ) ;
356+ expect ( content ) . toContain ( "Get-NetTCPConnection -LocalPort $Port -State Listen" ) ;
357+ expect ( content ) . toContain ( "& netstat.exe -ano -p tcp" ) ;
358+ expect ( content ) . not . toContain ( "findstr" ) ;
344359 expectWindowsRestartWaitOrdering ( content , customPort ) ;
345360 await cleanupScript ( scriptPath ) ;
346361 } ) ;
@@ -371,7 +386,7 @@ exit 0
371386 const { scriptPath, content } = await prepareAndReadScript ( {
372387 OPENCLAW_PROFILE : "production" ,
373388 } ) ;
374- expect ( content ) . toContain ( 'schtasks /End /TN " OpenClaw Gateway (production)"' ) ;
389+ expect ( content ) . toContain ( "$taskName = ' OpenClaw Gateway (production)'" ) ;
375390 expectWindowsRestartWaitOrdering ( content ) ;
376391 await cleanupScript ( scriptPath ) ;
377392 } ) ;
0 commit comments