@@ -6,6 +6,7 @@ import { findVerifiedGatewayListenerPidsOnPortSync } from "../infra/gateway-proc
66import { inspectPortUsage } from "../infra/ports.js" ;
77import { getWindowsInstallRoots } from "../infra/windows-install-roots.js" ;
88import { killProcessTree } from "../process/kill-tree.js" ;
9+ import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js" ;
910import { sleep } from "../utils.js" ;
1011import { parseCmdScriptCommandLine , quoteCmdScriptArg } from "./cmd-argv.js" ;
1112import { assertNoCmdLineBreak , parseCmdSetAssignment , renderCmdSetAssignment } from "./cmd-set.js" ;
@@ -121,7 +122,7 @@ export async function readScheduledTaskCommand(
121122 if ( ! line ) {
122123 continue ;
123124 }
124- const lower = line . toLowerCase ( ) ;
125+ const lower = normalizeLowercaseStringOrEmpty ( line ) ;
125126 if ( line . startsWith ( "@echo" ) ) {
126127 continue ;
127128 }
@@ -192,7 +193,7 @@ function normalizeTaskResultCode(value?: string): string | null {
192193 if ( ! value ) {
193194 return null ;
194195 }
195- const raw = value . trim ( ) . toLowerCase ( ) ;
196+ const raw = normalizeLowercaseStringOrEmpty ( value ) ;
196197 if ( ! raw ) {
197198 return null ;
198199 }
@@ -542,7 +543,6 @@ async function restartStartupEntry(
542543 await terminateGatewayProcessTree ( runtime . pid , 300 ) ;
543544 }
544545 launchFallbackTaskScript ( resolveTaskScriptPath ( env ) ) ;
545- stdout . write ( `${ formatLine ( "Restarted Windows login item" , resolveTaskName ( env ) ) } \n` ) ;
546546 return { outcome : "completed" } ;
547547}
548548
@@ -581,6 +581,45 @@ export async function stageScheduledTask({
581581 return { scriptPath } ;
582582}
583583
584+ async function updateExistingScheduledTask ( params : {
585+ env : GatewayServiceEnv ;
586+ stdout : NodeJS . WritableStream ;
587+ taskName : string ;
588+ quotedScript : string ;
589+ scriptPath : string ;
590+ } ) : Promise < boolean > {
591+ if ( ! ( await isRegisteredScheduledTask ( params . env ) ) ) {
592+ return false ;
593+ }
594+ const change = await execSchtasks ( [
595+ "/Change" ,
596+ "/TN" ,
597+ params . taskName ,
598+ "/TR" ,
599+ params . quotedScript ,
600+ ] ) ;
601+ if ( change . code !== 0 ) {
602+ return false ;
603+ }
604+ await runScheduledTaskOrThrow ( params . taskName ) ;
605+ writeFormattedLines (
606+ params . stdout ,
607+ [
608+ { label : "Updated Scheduled Task" , value : params . taskName } ,
609+ { label : "Task script" , value : params . scriptPath } ,
610+ ] ,
611+ { leadingBlankLine : true } ,
612+ ) ;
613+ return true ;
614+ }
615+
616+ async function runScheduledTaskOrThrow ( taskName : string ) : Promise < void > {
617+ const run = await execSchtasks ( [ "/Run" , "/TN" , taskName ] ) ;
618+ if ( run . code !== 0 ) {
619+ throw new Error ( `schtasks run failed: ${ run . stderr || run . stdout } ` . trim ( ) ) ;
620+ }
621+ }
622+
584623async function activateScheduledTask ( params : {
585624 env : GatewayServiceEnv ;
586625 stdout : NodeJS . WritableStream ;
@@ -591,6 +630,11 @@ async function activateScheduledTask(params: {
591630
592631 const taskName = resolveTaskName ( params . env ) ;
593632 const quotedScript = quoteSchtasksArg ( params . scriptPath ) ;
633+
634+ if ( await updateExistingScheduledTask ( { ...params , taskName, quotedScript } ) ) {
635+ return ;
636+ }
637+
594638 const baseArgs = [
595639 "/Create" ,
596640 "/F" ,
@@ -634,7 +678,7 @@ async function activateScheduledTask(params: {
634678 throw new Error ( `schtasks create failed: ${ detail } ` . trim ( ) ) ;
635679 }
636680
637- await execSchtasks ( [ "/Run" , "/TN" , taskName ] ) ;
681+ await runScheduledTaskOrThrow ( taskName ) ;
638682 // Ensure we don't end up writing to a clack spinner line (wizards show progress without a newline).
639683 writeFormattedLines (
640684 params . stdout ,
@@ -686,7 +730,7 @@ export async function uninstallScheduledTask({
686730}
687731
688732function isTaskNotRunning ( res : { stdout : string ; stderr : string ; code : number } ) : boolean {
689- const detail = ( res . stderr || res . stdout ) . toLowerCase ( ) ;
733+ const detail = normalizeLowercaseStringOrEmpty ( res . stderr || res . stdout ) ;
690734 return detail . includes ( "not running" ) ;
691735}
692736
@@ -761,11 +805,7 @@ export async function restartScheduledTask({
761805 }
762806 }
763807 }
764- const res = await execSchtasks ( [ "/Run" , "/TN" , taskName ] ) ;
765- if ( res . code !== 0 ) {
766- throw new Error ( `schtasks run failed: ${ res . stderr || res . stdout } ` . trim ( ) ) ;
767- }
768- stdout . write ( `${ formatLine ( "Restarted Scheduled Task" , taskName ) } \n` ) ;
808+ await runScheduledTaskOrThrow ( taskName ) ;
769809 return { outcome : "completed" } ;
770810}
771811
@@ -798,7 +838,7 @@ export async function readScheduledTaskRuntime(
798838 return await resolveFallbackRuntime ( env ) ;
799839 }
800840 const detail = ( res . stderr || res . stdout ) . trim ( ) ;
801- const missing = detail . toLowerCase ( ) . includes ( "cannot find the file" ) ;
841+ const missing = normalizeLowercaseStringOrEmpty ( detail ) . includes ( "cannot find the file" ) ;
802842 return {
803843 status : missing ? "stopped" : "unknown" ,
804844 detail : detail || undefined ,
0 commit comments