Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

WScript.Shell.Exec().StdOut.AtEndOfStream waits for text

3,573 views
Skip to first unread message

don.jscri...@gmail.com

unread,
Jan 11, 2008, 10:21:53 AM1/11/08
to
I want to execute an app, process its output, but terminate the app if
it takes too long. I thought using WScript.Shell.Exec() would be the
ticket. However, I'm finding that AtEndOfStream waits until text
appears in the executed app's stdout, or the app terminates. Read()
works the same, and ReadAll() waits for the app to terminate, though I
expect that behavior.

Is there a workaround or alternative? As it stands, I can either
process the app's output as it comes, or terminate the app if it takes
too long, but not both.

Here's a simple example. output.js pauses before and after outputting
a single character. process.js executes output.js, checks
AtEndOfStream, Read()'s the single character, and checks AtEndOfStream
again. You'll notice the pause before each AtEndOfStream is output --
AtEndOfStream waits for the single character, and then waits for the
app to terminate.

process.js
========================================
var WshShell = new ActiveXObject("WScript.Shell");
var oExec = WshShell.Exec("cscript /nologo output.js");
var ch;
var aeos;
WScript.StdOut.WriteLine("starting...");
aeos = oExec.StdOut.AtEndOfStream;
WScript.StdOut.WriteLine("AtEndOfStream=" + aeos);
ch = oExec.StdOut.Read(1);
WScript.StdOut.WriteLine("Read(1)=" + ch);
aeos = oExec.StdOut.AtEndOfStream;
WScript.StdOut.WriteLine("AtEndOfStream=" + aeos);

output.js
========================================
WScript.Sleep(3000);
WScript.StdOut.Write("a");
WScript.Sleep(3000);

badba...@googlemail.com

unread,
Jan 27, 2008, 6:28:37 PM1/27/08
to
Instead of exec'ing the thing you want to execute you can exec another
cmd.exe.
You can then fire in whatever commands you actually want to execute
into oExec.StdIn.
This lets you send in your desired command but also markers, such as
'echo marker1'
which you can search for in the output stream so you know when to stop
reading.
It isn't pretty but it works. Plus you can use the same oExec for many
commands
which is more efficient if you want to exec a lot.

Here is some sample code, it is vbscript but you can use the same idea
in jscript:

dim WshShell, oExec, iExitCodeNum, iExitCode, sOut
set WshShell = WScript.CreateObject("WScript.Shell")
set oExec = WshShell.Exec("%comspec% /d/q")
iExitCodeNum = 1
oExec.StdIn.WriteLine("prompt TIMESTAMP $D $T$_")
' skip over any blurb before we start our commands
iExitCode = getExitCode(sOut)

function getExitCode(byRef sOut)
getExitCode = -1
dim sExitCodeName, sOutLine
' the Jkdfhudi just makes accidental matches v.unlikely
sExitCodeName = "exitCodeJkdfhudi" & iExitCodeNum
oExec.StdIn.WriteLine("echo " & sExitCodeName & "=%ERRORLEVEL%")
dim outRE, outM
set outRE = new RegExp
outRE.Global = false
outRE.ignoreCase = false
outRE.Pattern = sExitCodeName & "=(\d+)"
sOut = ""
do while not oExec.StdOut.AtEndOfStream
sOutLine = oExec.StdOut.ReadLine
set outM = outRE.Execute(sOutLine)
if outM.count = 1 then
getExitCode = CLng(outM(0).SubMatches(0))
exit do
else
sOut = sOut & sOutLine & vbcrlf
end if
loop
iExitCodeNum = iExitCodeNum + 1
end function

sOut = ""
iExitCode = -1
on error resume next
oExec.StdIn.WriteLine("cscript /nologo output.js 2>&1")
iExitCode = getExitCode(sOut)
on error goto 0
if iExitCode = -1 then
' failed - exec'ed cmd shell may have been closed
...
end if
WScript.Echo(sOut)

don.jscri...@gmail.com

unread,
Jan 29, 2008, 10:13:32 AM1/29/08
to
Unfortunately, AtEndOfStream will still wait. I will not be able to
terminate the app if it takes too long without returning any output.

I love your use of cmd.exe, feeding in commands by writing to stdin.
You can change a command to execute based on the results of the
previous command.

don.jscri...@gmail.com

unread,
Jan 29, 2008, 10:44:10 AM1/29/08
to
For those interested, my solution to the problem was complicated.

1. Execute the app.
2. Execute the monitor.
3. Read output from the app.
4. Read output from the monitor.

The monitor is another script. It has two parameters: the process ID
of the app to monitor, and the timeout in seconds. It uses WMI to
watch the specified process. If the process terminates, the monitor
ends. If the timeout elapses, the monitor terminates the process and
ends.

The next two posts will be the final code:
1. ExecWithTimeout.wsf: the main script.
2. ProcessTimeout.wsf: the monitor.


I'm sure this would have been much simpler in a compiled language or
dotNET. But I don't have that expertise or time to devote to learning
it.

don.jscri...@gmail.com

unread,
Jan 29, 2008, 10:45:45 AM1/29/08
to
On Jan 29, 10:44 am, don.jscript.use...@gmail.com wrote:
> For those interested, my solution to the problem was complicated.
>
> 1. Execute the app.
> 2. Execute the monitor.
> 3. Read output from the app.
> 4. Read output from the monitor.
>
> The monitor is another script. It has two parameters: the process ID
> of the app to monitor, and the timeout in seconds. It uses WMI to
> watch the specified process. If the process terminates, the monitor
> ends. If the timeout elapses, the monitor terminates the process and
> ends.
>
> The next two posts will be the final code:
> 1. ExecWithTimeout.wsf: the main script.
> 2. ProcessTimeout.wsf: the monitor.
>
> I'm sure this would have been much simpler in a compiled language or
> dotNET. But I don't have that expertise or time to devote to learning
> it.
>

<?xml version="1.0" ?>
<job>
<comment>
<![CDATA[
/*
ExecWithTimeout.wsf
execute a Windows command, copying stdout and stderr.
if specified timeout elapses and command is still running,
terminate the command and exit.

ExecWithTimeout <timeout> <command> <parameter>...
where
<timeout> is the maximum number of seconds to wait;
<command> is the command to run;
<parameter>... are parameters of the command, if any.

Before command is executed, each <parameter> is surrounded with
double quotes
if the parameter contains a Windows special character: &<>[]
{}^=;!'+,`~ or space.
Special cmd.exe processing using those special characters is not
supported.

ProcessTimeout.wsf is used to monitor the command and terminate it
if necessary.
ProcessTimeout.wsf should be located in the same folder as this
script.

Unable to output the executed app's output as it comes.
-=- oExecCmd.stdout.AtEndOfStream waits until there is text, or
the executed app terminates.
-=- oExecCmd.stdout.Read(1) waits until there is text, or the
executed app terminates.
-=- oExecCmd.stdout.ReadAll() waits until the executed app
terminates.
*/
]]>
</comment>


<script language="JavaScript">
<![CDATA[

////////////////////////////////////////////////////////////////////////////////

var timeoutSeconds = 0;
var cmd = "";
var stdout = WScript.StdOut;
var stderr = WScript.StdErr;
var isTerminatedPrematurely = false;

var reWindowsSpecialChars = /[&<>[\]{}^=;!'+,`~ ]/
var rePosInt = /^[0-9]+$/

////////////////////////////////////////////////////////////////////////////////

// get arguments.
var args = WScript.Arguments;
var iarg;
var arg;
if (args.length < 2) {
stderr.WriteLine("ExecWithTimeout: Too few parameters.");
WriteSyntax(stderr);
WScript.Quit();
}

// first arg is timeout number of seconds.
arg = args(0);
if (!rePosInt.test(arg)) {
stderr.WriteLine("ExecWithTimeout: First parameter must be
number of seconds before timeout.");
WriteSyntax(stderr);
WScript.Quit();
}
timeoutSeconds = parseInt(arg, 10);

// rest of args make up the command to execute.
// add quotes to the arg if has special characters.
for (iarg=1; iarg<args.length; iarg++) {
arg = args(iarg);
if (cmd.length > 0) cmd += " ";
if (reWindowsSpecialChars.test(arg)) {
cmd += "\"" + arg + "\"";
}
else {
cmd += arg;
}
}

////////////////////////////////////////////////////////////////////////////////


// execute command.
var oExecCmd = shell.Exec(cmd);
WScript.Sleep(100);

// execute ProcessTimeout monitor, same folder as
ExecWithTime.wsf.
var scriptPath = fso.GetParentFolderName(WScript.ScriptFullName);
if (scriptPath.charAt(scriptPath.length-1) != "\\") scriptPath +=
"\\";
var oExecTimeout = shell.Exec("cscript.exe /nologo "
+ "\"" + scriptPath + "ProcessTimeout.wsf\" "
+ oExecCmd.ProcessID + " " + timeoutSeconds);

// copy output.
stdout.Write(oExecCmd.StdOut.ReadAll());
stderr.Write(oExecCmd.StdErr.ReadAll());

stdout.Write(oExecTimeout.StdOut.ReadAll());
stderr.Write(oExecTimeout.StdErr.ReadAll());

// quit.
WScript.Quit();

//////////////////////////////////////////////////////////////////////

function WriteSyntax(stdout) {
stderr.WriteLine("ExecWithTimeout <timeout> <command>
<parameter>...\n"
+ " where\n"
+ " <timeout> is the maximum number of seconds to wait;
\n"
+ " <command> is the command to run;\n"
+ " <parameter>... are parameters of the command, if
any.\n");
}

//////////////////////////////////////////////////////////////////////

]]>
</script>


<object id="shell" progid="wscript.Shell"/>
<object id="fso" progid="Scripting.FileSystemObject"/>


</job>

don.jscri...@gmail.com

unread,
Jan 29, 2008, 10:47:10 AM1/29/08
to
On Jan 29, 10:44 am, don.jscript.use...@gmail.com wrote:
> For those interested, my solution to the problem was complicated.
>
> 1. Execute the app.
> 2. Execute the monitor.
> 3. Read output from the app.
> 4. Read output from the monitor.
>
> The monitor is another script. It has two parameters: the process ID
> of the app to monitor, and the timeout in seconds. It uses WMI to
> watch the specified process. If the process terminates, the monitor
> ends. If the timeout elapses, the monitor terminates the process and
> ends.
>
> The next two posts will be the final code:
> 1. ExecWithTimeout.wsf: the main script.
> 2. ProcessTimeout.wsf: the monitor.
>
> I'm sure this would have been much simpler in a compiled language or
> dotNET. But I don't have that expertise or time to devote to learning
> it.
>

<?xml version="1.0" ?>
<job>
<comment>
<![CDATA[
/*

ProcessTimeout.wsf
monitors a Windows process.
if the process ends, this script ends.
if timeout is exceeded, this script terminates the process and
ends.

ProcessTimeout <processid> <timeout>
where
<processid> is process ID to monitor;
<timeout> is the maximum number of seconds to wait.

WMI is used to monitor the process.

TASKKILL.exe is used to kill the process. parameters /T and /F are
used to forcibly
kill the process and child processes.

if the process is terminated, the following is output to stderr:
ProcessTimeout: Timeout period exceeded. Command was forcibly
terminated.
*/
]]>
</comment>


<script language="JavaScript">
<![CDATA[

////////////////////////////////////////////////////////////////////////////////

var stdout = WScript.StdOut;
var stderr = WScript.StdErr;
var processId = 0;
var timeoutSeconds = 0;

var rePosInt = /^[0-9]+$/

////////////////////////////////////////////////////////////////////////////////

// get arguments.
var args = WScript.Arguments;
var iarg;
var arg;
if (args.length < 2) {

stderr.WriteLine("ProcessTimeout: Too few parameters.");
WriteSyntax(stderr);
WScript.Quit();
}

// first arg is Process ID.


arg = args(0);
if (!rePosInt.test(arg)) {

stderr.WriteLine("ProcessTimeout: First parameter must be the
Windows Process ID.");
WriteSyntax(stderr);
WScript.Quit();
}
processId = parseInt(arg, 10);

// second arg is timeout number of seconds.
arg = args(1);
if (!rePosInt.test(arg)) {
stderr.WriteLine("ProcessTimeout: Second parameter must be


number of seconds before timeout.");
WriteSyntax(stderr);
WScript.Quit();
}
timeoutSeconds = parseInt(arg, 10);

////////////////////////////////////////////////////////////////////////////////

var wbemErrTimedOut = -2147209215; // error if notification query
NextEvent timed out. NOTE: not same value as in docs.

var startTime = new Date();
var checkTime;
var timeoutMilliseconds = timeoutSeconds * 1000;
var timeoutLeftMilliseconds = timeoutMilliseconds;

var colProcesses;
var enumProcesses;
var objProcess;
var objEvent;
var objEventSource;
var isProcessDeleted = false;

var wmiServices = GetObject("winmgmts:
{impersonationLevel=impersonate}!\\\\.\\root\\cimv2");

// check that process exists.
colProcesses = wmiServices.ExecQuery("Select * from Win32_Process
Where ProcessID = " + processId);
enumProcesses = new Enumerator(colProcesses);
if (enumProcesses.atEnd()) {
stderr.WriteLine("ProcessTimeout: The specified Process ID
does not exist.");
WScript.Quit();
}

// query for process deletions.
objEventSource = wmiServices.ExecNotificationQuery(
"Select * From __InstanceDeletionEvent Within 1 Where
TargetInstance ISA 'Win32_Process' ");

// loop until specified process deleted or timeout period elapsed.
while (!isProcessDeleted && (timeoutLeftMilliseconds > 0)) {
checkTime = new Date();
elapsedMilliseconds = (checkTime.valueOf() -
startTime.valueOf());
timeoutLeftMilliseconds = timeoutMilliseconds -
elapsedMilliseconds;
if (timeoutLeftMilliseconds > 0) {
try {
// wait for next process deletion.
objEvent =
objEventSource.NextEvent(timeoutLeftMilliseconds);
isProcessDeleted = (objEvent.TargetInstance.ProcessID
= processId);
}
catch(err) {
// ignore error if NextEvent() timeout; throw all
other errors.
if (err.number != wbemErrTimedOut) {
throw(err);
}
}
}
}

// terminate process if timeout.
if (timeoutLeftMilliseconds <= 0) {
var oExecCmd = shell.Exec("taskkill /F /T /PID " + processId);
oExecCmd.StdOut.ReadAll(); //ignore output. just wait until
complete.
stderr.WriteLine("\n\nProcessTimeout: Timeout period exceeded.
Command was forcibly terminated.");
/* this code was terminating cmd.exe but not java.exe and it
all still waited for java to finish.
colProcesses = wmiServices.ExecQuery("Select * from
Win32_Process Where ProcessID = " + processId)
enumProcesses = new Enumerator(colProcesses);
if (!enumProcesses.atEnd()) {
objProcess = enumProcesses.item();
objProcess.Terminate;
stderr.WriteLine("ProcessTimeout: Timeout period exceeded.
Command was forcibly terminated.");
}
*/
}

// quit.
WScript.Quit();

//////////////////////////////////////////////////////////////////////

function WriteSyntax(stdout) {
stderr.WriteLine("ProcessTimeout <processid> <timeout>\n"
+ " where\n"
+ " <processid> is process ID to monitor;\n"
+ " <timeout> is the maximum number of seconds to wait.
\n");
}

//////////////////////////////////////////////////////////////////////

]]>
</script>

<object id="shell" progid="wscript.Shell"/>

</job>

0 new messages