[#wshexec]:

Windows Scripting Host Execution Code

Overview

This web page is about using WSH to run a program. WSH is a collection of code that comes with support for Microsoft JScript (which is essentially the same as JavaScript or ECMAScript) and VBScript.

Solutions bundled with WSH

The WSH package can create a WSH Shell object that provides the following options:

.Run
Official documentation of .Run

(MSDN documentation: WshShell .Run)

Description/Overview

This runs a program. The first parameter is the command to run. The second parameter describes how visible the window will be. the third parameter is a boolean value named “bWaitOnReturn”. If “bWaitOnReturn” is true, then the WSH program will wait for the other program that is being run. If “bWaitOnReturn” is true and a non-zero error code is returned by the program that WSH ran, then the .Run method returns that non-zero error code. In all other cases (if “bWaitOnReturn” is the default value of false, or if “bWaitOnReturn” is true but the program that ran returned zero), .Run returns zero.

This is often a simple way to run things, and to check the return value. However, this lacks some of the ability of .Exec to be able to capture the program's output. Another limitation is that if “bWaitOnReturn” is true, then WSH ends up waiting for the completion of the program that WSH ran. If that program never finishes, then WSH simply waits forever. That's not exactly the fault of the .Run method, but the .Exec method does have some ability to recover from such a problem. The .Run method is a bit simpler, and so doesn't provide that functionality.

Example usage
JScript
function demorun (sCmdToRun)

{   var iWinMode; // specifies window visibility, described by
      // http://msdn.microsoft.com/en-us/library/d5fk67ky.aspx
   var bWaitForCmd; //default=false=continue on, once program starts
      // if true, iRetVal will be return code of the run program
   var iRetVal // if bWaitForCmd, then is return value of started program
      // will always be zero when bWaitForCmd is false
   var oWscrSh; // http://msdn.microsoft.com/en-us/library/d5fk67ky.aspx

   iWindowMode=1; // "should" be 1=visible for first time, per Microsoft documentation
   bWaitForCmd=0; //0==do not wait
   oWscrSh = new ActiveXObject("WScript.Shell");
   if( oWscrSh )
   {   abcd....
.Exec

(MSDN documentation: WshShell .Exec)

This runs a program. The only parameter is the command to be run.

In general, the program typically runs invisibly. Its window is invisible. However, there are ways to determine what the program outputs, including the ability to show that on the screen.

This .Exec function provides some flexibility that is not found in the .Run function, although this flexibility can be a bit complex to use.

When the .Exec method is used, WSH ends up running the specified program in a way that causes the WSH Shell object to be updated. So, the WSH Shell object can be used to get information about the process that has been started. An example is that the .Status property of a WSH Shell object/.Status property of a WshScriptExec object, which may be checked to see if the program is still running. This could be used to implement a sort of time limit, working around potential lockups.

Also, the WSH Shell object ends up acting like a WshScriptExec object, which allows WSH to see data like the “standard output” stream (which is sometimes called StdOut), and the “standard error” output stream (which is sometimes called StdErr). WSH can access the content of those output streams even before the desired command is done running.

Another piece of information that is available by using .Exec is the PID. Besides the ability to view output streams, seeing the PID is yet another demonstration of how the .Exec method allows WSH to have a bit more control for interacting with the program that is being run.

Now, there is a key danger to using .Exec. Some workarounds are available. The threat is this: The way Microsoft's code works can lead to the a case where WSH is waiting for the output of the program that has been run, and the program that is running ends up waiting for WSH to acknowledge that output has been processed. With both programs waiting on each other, the two programs are effectively hung.

(This concludes the overview of the functions that are part of the WSH package.)

Using WSHExec
Introducing WSHExec...

This “WSHExec” code is essentially meant to be a fancy wrapper around the .Exec method. There is also a cmdRun function that acts like a wrapper around .Run, so that other code doesn't need to keep making sure that a WScript.Shell ActiveX object has been made. However, the more substantial benefits have been aimed at making a simple interface to accomplish some things with the .Exec method.

The cmdExec function runs a program in the background. Then, WSH waits for that program to return. If there is a desire to impose a maximum time limit for that program, then use .cmdExecTimed instead of .cmdExec. (If there is not a desire for a time limit, then the cmdExec function is meant to be slightly easier to use. The cmdExec function simply calls the cmdExecTimed function in a way that does not enforce any time limit.)

By using the bReportInLoop variable, WSH can provide some sort of progress indicator that demonstrates that WSH has not frozen up.

The return value of cmdExecTimed (and cmdExec, which is just a wrapper for cmdExecTimed, and so returns the same thing as the cmdExecTimed function) is a string. There are available functions to to able to extract the desired data, such as getStdOutFromCmdExec which is able to easily extract only the data from the output stream that is called “standard output” (“stdout”). Another example is getStdErrFromCmdExec which extracts only the data from the “standard error” output stream (which is sometimes called “stderr”). Both of those functions are simple wrappers that get the data from the string that cmdExecTimed returns.

By having wrapper functions like getStdErrFromCmdExec, accessing only one section of the data is a snap, thanks to the easy flexbility provided by getFieldResultsFromCmdExecs. However, there is a point where the data is combined into a single string. This can be great for easily creating text that goes into a log file. Logging the combined string reduces the possibility of accidentally missing data that might have been less expected. (If only “stdout” was expected, but “stderr” was generated, then the combined string will clearly show the unexpected “stderr” contents.)

Usage example

(This is not currently provided here...)

Other solutions

Perhaps there is some desire to implement things different, such as logging each line of output as the line of output is generated, in the order that the output is generated, instead of logging all of stdout and then separately logging all of stderr. That really isn't supported by this stock implementation of cmdExecTimed because such flexibility would add a fair amount of complexity to the code. That complexity would also slow things down a bit.

However, the good news is that the code from this WSHExec package has been released as “public domain”. (This is not saying that Microsoft's code is public domain. However, the wrapper functions are.) If there is a desire to have a custom implementation of interacting with the .Exec built into WSH, then consider using the pre-existing code from this WSHExec collection. Using this code as a starting point might be easier than trying to code this all from scratch.

Many people may find that they have exactly what they want if they simply look at the return value of the cmdExecTimed function (or the cmdExec function). If that output contains the desired information, but also contains more information that is not desired, then check out getStdErrFromCmdExec for an example of a pre-made easy way to parse the information.

Freezing/Hanging the (WSH) interpretor

Warning: (At least old versions of) this code can crash. If so, the program freezes up, which means that the command prompt that was used to run the code will likely be unavailable (until a user intervenes by pressing Ctrl-C). For details, MS KB 960246: Hang When Reading StdErr/StdOut Properties of WshScriptExec Object is some official documentation about how this can freeze things up.

This threat of freezing is really only related to using .exec (and the related functions in this code), not .run (or the related function(s) in this code). Also, some documentation suggests that there is no threat if a program sends less than 4KB of output (to StdOut or StdErr... so sending 3KB to StdOut and 2KB to StdErr could pose a threat since 3KB+2KB is more than 4KB).

According to a Google Groups forum post, “You just can't safely use the ReadAll method, because it is waiting for StdErr to be released by the called process - which becomes blocked before the ReadAll can get access to empty it.” The recommended solution (which, at the time of this writing, has not verified this text) is to use Read or possibly a Readline reference, and to do so in a loop.

There may be some other ways around the error. One approach is to write to a text file, in order to create a batch file, and then executing a command line. Perhaps the batch file may use the shell's internal start command. If so, the start command's “/WAIT” parameter might be useful. Or, this approach may be used to have a batch file run the desired command in a background process. The batch file may then exit right away, causing .Exec to not be locked up. Experimentation may be required to determine whether any of these approaches are useful, or not.

If too much output is generated by the program that .Exec runs, then accessing .AtEndOfStream will cause a hang. John Frensen's posts in a Usenet forum post about WSH .Exec freezing notes, “MS really messed this up badly.” ... “The most serious bug here is in the AtEndOfStream property, which blocks until some data has been written to the stream. This is clearly incorrect.” The biggest problem is that the .AtEndOfStream property must be used to figure out if performing a .Read (or a variation: .ReadLine or .ReadAll) will be safe, but there are times where .AtEndOfStream leads to a lockup if it is accessed before more data is read from the buffer (using .Read or similar functions). Some experimentation suggests that using either the .AtEndOfStream property, XOR reading one byte (with “.Read(1)”), should work. However, there does not appear to be any reliable way to determine which of those ways are safe.

This problem might simply not happen. One thing that could prevent the problem is if .Exec runs a program that just does not output too much data before WSH processes the data. The code might work just fine during testing stages. However, things could change. Microsoft Windows task management providing less CPU time to the WSH process. Or, probably even more likely to cause a problem, the program that .Exec runs might generate more output than previous times when the same program was run. (These perfectly legitimate actions may cause the WSH program to hang up, even though the same WSH program seemed to work just fine a week earlier.)

Note: the danger of the program freezing up is not primarily a problem with the ReadAll function's code. The danger is more from mixing the ReadAll function's code with a stream that does not have a clear “end”, which is true for the Wscript.Shell object's .StdErr property, and also for the object's .StdOut property. This does not suggest that the ReadAll function is generally dangerous. (The ReadAll function is presumably safe when the stream has a clear end.)

It appears that workarounds include:

Reading from StdOut and StdErr quickly

MS KB 960246: Hang When Reading StdErr/StdOut Properties of WshScriptExec Object shows a way to try to do this. (This is discussed in section one of “Resolution”, and a demonstration is in the “Advanced Steps” section.)

The example from MS KB 960246 might be a bit misleading. One issue is that the example in MS KB 960246 uses .ReadLine, which may be making some assumptions about the text. Those assumptions are valid for the sample program, but might not be true for all output. Using .Read(1) to read character-by-character may resolve this (but would provide slower speed.) Although, perhaps the end of a stream acts as the end of a line, and so ReadLine may be safe...

Secondly, the author of this text believes there may be a race condition, which just doesn't happen to get triggered in that sample. It may be that StdOut and StdErr need to be checked, and read if needed, fast enough that the 4KB buffer doesn't fill up. This means there may be a race between the WSH code, and the program that is being run. If the program outputs at least 4KB which isn't handled yet by the WSH code, then a lockup could occur.

This is the approach attempted by the cmdExecTimed function (which is code available on this page). As a result, the related code in the cmdExecTimed function may, or might not, work very reliably.

Reducing output that needs to be processed quickly

As noted by MS KB 960246, a program might be able to have its output redirected to a text file. This approach probably works just fine, without threat of the code freezing up from this bug. However, this does require a bit more complexity, and the code could theoretically break due to an inability to write to a temporary file. This approach is probably safe if this approach is done right, which does require that this is done carefully.

Note that outputting to a text file could also just be done with the .Run method. So, by redirecting output to a text file, there may be some loss of the advantage that comes from using the flexibility provided by using the .Exec method.

Read output from other methods

A way to do this might be shown by MS KB 960246. The example, mentioned in section 3 of the “Resolution” section, may require .NET Framework so that the System.Diagnostics.Process object may be used.

Basically, this approach involves using additional code to provide a solution. However, using a bunch of additional code from larger frameworks/libraries results in a solution that, overall, just seems less simplified. To some degree, using a library is exactly what is involved when using cmdExecTimed. However, cmdExecTimed is just part of a single file which is much smaller than the .NET framework.

Perhaps Microsoft's solution of using a component that implements asynchronous reads of the streams is an ideal solution, but the demonstrated implementation involves using a COM component and the .NET framework. These technologies seem to be making the solution more complex.

Instead of just using some more JavaScript code, this relies on the .NET framework which has been known to require a system reboot, and which has also been known to introduce some troublesome incompatabilities. Using some relatively simple code, like what is available with the cmdExecTimed, may be simpler to implement, be less prone to cause undesired side effects, and may be less prone to breaking when some other software updates the .NET framework. These advantages may not be guaranteed (if there are no troubles with .NET), but seem quite likely when considering the track record that some people have experienced with the .NET framework.

Don't use .exec
Just don't rely on the WScript.Shell .Exec method. One alternative, which might be plenty suitable in some cases, may be the .Run method. Or, perhaps use something other than WSH?

Multiple versions available

Following is code provided in both JScript (essentially JavaScript) and VBScript. All (two) of these forms of code are meant to be run within WSH. Starting with version 1.2 (which was the initial release for JScript), all forms are expected to be maintained by staff who has experience with both all languages that the code continues to be released in. So, all releases are likely to remain relatively synchronized: updates to one release are likely to be applied to other supported languages (and the version numbers will be updated for all supported languages). If there is any issue with that, a note is likely to be made in the release notes (and the version number might not be updated).

In general, such porting efforts (to code essentially the same thing, in multiple languages) may be unrecommended, because an alternative is to just use one language to just execute a copy of the code in the other language. However, since this code is the code that permits safely executing other code, porting this code to each language made sense.

JavaScript Code

(Version history is shown after this code.)

wshexec();
var giVerboseLevel;
function ifDbgShowRes (iRequiredLevel,sMsg)
//    Outputs sMsg if giVerboseLevel is at least iRequiredLevel
//    This code is an early predecissor to iveo iveo, which is described by
//    http://cyberpillar.com/dirsver/1/mainsite/techns/coding/coding.htm#oivefcn
//    That page describes some possible numbers (e.g. usable for iRequiredLevel)
{
   if ( typeof( giVerboseLevel ) == "number" ) // if defined
      if ( iRequiredLevel <= giVerboseLevel )
         WScript.Echo ( sMsg );
}

function cmdExecTimed (sPassedCmdToRun,sStdOTmp,sStdETmp,sCaller,uiMaxMs,uiMaxOutputChars,bReportInLoop)
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain
// This code executes the command specified.
// Passed values: sPassedCmdToRun: A string to execute.  It
//    is required that any necessary that any necessary sanitizing
//    occurs before this happens!
// sCaller: A string containing info to help debug code.
//    This is technically cosmetic, so can say anything.
//    Recommended: Include history of function calls.
// sStdOTmp : An optional temporary filename (not yet implemented?)
// sStdETmp : An optional temporary filename (not yet implemented?)
// uiMaxMs: If non-zero, maximum amount of time (in
//    milliseconds) that this function will wait on the program.
//    program that gets run. Zero = wait infinitely.
// uiMaxOutputChars: If non-zero, approximate maximum number of
//    characters possible in output. Zero = unlimited output
// Note: uiMaxMs and uiMaxOutputChars are checked at certain spots,
//    so these limits may be (slightly) exceeded.
// bReportInLoop : If true, function shows progress during loop
// Returns: A string.  The string is likely to be human readable, and is
//    therefore appropriate for output that a human would
//    read.  The string returned will start with the return/exit code of
//    what was ran, followed by white space.  It will also include any text
//    that was followed by white space.  It will also include any text that
//    was output to stderr or to stdout.  That may be nice for some quick
//    substring results when checking trusted output, but beyond that,
//    the recommended method to truely parse the output is to use some related
//    functions like getFieldResultsFromCmdExecs
// This function made by Scott Conrad VanderWoude of TOOGAM.com, may be
//    treated as public domain.  The same is true for
//    getFieldResultsFromCmdExecs and similar/related code.
// This code is version "1.2js" at the time this comment was updated.
// The home page for this code is at CyberPillar.com at
// /dirsver/1/mainsite/techns/coding/scriptin/winscrho/wshexec/wshexec.htm
// Requires: The code for ifDbgShowRes (or comment out the calls to that code)
// Note: in theory, the function should quit shortly after uiMaxMs
// milliseconds, but this may enter endless loop if the program that is
// run generates output (to stderr/stdout) faster than what
// that output is parsed.  The loop will not be infinite unless
// the ran program provides infinite output.
// (This could be rectified, at the cost of complicated code.)

var csSelfFcnName="cmdExecTimed"; //const, == function name

var ciIterationTime = 100; // const
var sCmdToRun, oWscrSh, oExecObj, sReturnStr, sTxtFromObj, bStayInLoop;
var iTimePassed, sTxtToShow, sStdOutTxt, sStdErrTxt, sTxtToLog;
var iCharsOutput, iRecentOutChars, iRecentErrChars, sNewOutTxt, uiCharsToRead;
var sNewErrTxt, iNumToRead, ciMaxBufferSize, sRedirO, sRedirE;

sStdErrText = ""
sStdOutText = ""
iCharsOutput = 0;
iNumToRead = 1; //Number of chars to read at once
ciMaxBufferSize = 4096; //4KB per MS KB 960246
// Ideas, not implemented here:
// Could call sanitizing function.
// Could compare sCmdToRun to the output of a sanitizing function.
// If they are different, warn that it is not clear if things were
// sanitized, and possibly abort (before running the code).

sCmdToRun = "" + sPassedCmdToRun;

if ( !sCmdToRun.length )
{ // Blank command was provided, probably by error. Experience revealed
   // that the coder probably did not intend this.  Rather than do
   // what was literally requested (do nothing), it is better to run
   // a command that reports cause of error. This way, when the
   // coder is not getting expected results and checks results of the
   // command line, some useful text gets shown.
   sCmdToRun = "echo " + csSelfFcnName + " got a blank command";
   sCmdToRun = sCmdToRun + " when called by " + sCaller;
   if(uiMaxMs)
      sCmdToRun = sCmdToRun + " (" & uiMaxMs + "ms)";
}

if ( ""+sStdOTmp )
sRedirO = " > " + sStdOTmp ;
else
sRedirO = "" ;
if ( ""+sStdETmp )
if ( ""+sStdETmp == ""+sStdOTmp )
sRedirE = " 2> " + sStdETmp ;
else
sRedirE = " 2>&1 " + sStdETmp ;http://support.microsoft.com/kb/110930
else
sRedirE = "" ;

sCmdToRun = "" + sCmdToRun + sRedirO + sRedirE;

oWscrSh = new ActiveXObject("Wscript.Shell");
// This usesWscript.Shell.Exec which has more output-capturing options
// than Wscript.Shell.Run (even though Wscript.Shell.Run) has more
// options about window placement.)  (An older approach was to use
   //iRetCode=oWscrSh.Run(sCmdToRun,0,True).)


try
{ oExecObj = oWscrSh.Exec(sCmdToRun); // Run the actual command
}
catch (oErr )
{
   // (no special handling; if error occurred, then just continue onward)
}

   // The following comment was copied from VBScript code, and adjusted
   // slightly to look more like JavaScript, but might not be accurate in
   // JavaScript? The comment seemed related to instability, which might be
   // specific to VBScript, but is more likely common in all WSH Engines?
   // typeof(oExecObj) should be "object", or similar; it
   // should not be undefined.  If undefined, oExecObj.status cannot be
   // successfully checked.  Result likely to be function hanging;
   // fault may be calling function passing sCmdToRun as empty str.

iTimePassed = 0;
bStayInLoop = true;

do // while bStayInLoop
{ //

   // Hmm...  was this addition meant to be repeated?
   //iTimePassed = iTimePassed + ciIterationTime;

   if (typeof(oExecObj.status) == "number" && !oExecObj.status )
   { if ( 1 || uiMaxMs )
      {   if( bReportInLoop )
         WScript.Echo( csSelfFcnName + ": " + (""+iTimePassed) + "ms of " + uiMaxMs + " += " + ciIterationTime );
      if ( uiMaxMs && iTimePassed > uiMaxMs)
         { bStayInLoop = false;
            if( bReportInLoop )
            WScript.Echo( csSelfFcnName + ": " + (""+iTimePassed) + "ms exceeded " + uiMaxMs + " += " + ciIterationTime );
            sTxtToShow = csSelfFcnName + " exceeded " + uiMaxMs + "milliseconds.  ";
            sTxtToShow = sTxtToShow + sCaller + " had requested to run [" + sCmdToRun;
            sTxtToShow = sTxtToShow + "].";
            sTxtToLog = sTxtToShow;
            ifDbgShowRes( 8,sTxtToShow );

      } //end if iTimePassed > non-zero uiMaxMs
      if (1)
      {
            //May want to store sTxtToShow for logging

            ifDbgShowRes(8,sTxtToShow); //Output something now in case readAll locks things up
            sTxtToShow="" //Prevent having previous msg be repeated since it was just output

            sNewOutTxt = "";
            sNewErrTxt = "";
            iRecentOutChars = 0; // sNewOutTxt.length
            iRecentErrChars = 0; // sNewErrTxt.length
            uiCharsToRead = ciMaxBufferSize - iNumToRead;
WScript.Echo("About to process output...");
            while ( (! oExecObj.StdOut.AtEndOfStream) || (! oExecObj.StdErr.AtEndOfStream) )
            {
            WScript.Echo("Survived first tests... about to test if end of stdOut");
            while( (! oExecObj.StdOut.AtEndOfStream ) && ( uiCharsToRead > iNumToRead ) )
               {   // Not at end of StdOut);
                  WScript.Echo("About to read a char from StdOut");
                  sTxtFromObj = "" + oExecObj.StdOut.Read(iNumToRead);
                  WScript.Echo("                                   " + "StdOut Char " + (iRecentOutChars+1) + " read : " + sTxtFromObj);
                  sNewOutTxt = sNewOutTxt + sTxtFromObj;
                  iRecentOutChars += sTxtFromObj.length;
                  uiCharsToRead -= sTxtFromObj.length;
                  WScript.Echo("End of loop iteration for StdOut");
               } // end while/if !AtEndOfStream of StdOut
               WScript.Echo("About to check StdErr");
            while( (! oExecObj.StdErr.AtEndOfStream ) && ( uiCharToRead > iNumToRead ) )
               {   // Not at end of StdErr);
                  WScript.Echo("About to read a char from StdErr");
               sTxtFromObj = "" + oExecObj.StdErr.Read(iNumToRead);
                  WScript.Echo("                     " + "StdErr Char " + (iRecentErrChars+1) + "read : " + sTxtFromObj);
                  sNewErrTxt = sNewErrTxt + sTxtFromObj;
                  iRecentErrChars += sTxtFromObj.length;
                  uiCharsToRead -= sTxtFromObj.length;
                  WScript.Echo("End of loop iteration for StdErr");
               WScript.Echo("                                " + "Total chars so far: " + (iRecentOutChars+iRecentErrChars) + "");
               } // end while/if !AtEndOfStream of StdErr
                  // could compare iRecentOutChars+iRecentErrChars to ciMaxBufferSize- iNumToRead, and perhaps break... would it help?
                  // However, this code isn't run until after the while/if loops/brnaches exit; may need to perform a check in each while/if loop/branch to exit the while/if loop/branch before ciMaxBufferSize is overfilled
            } // end while
WScript.Echo("Did process any output that needed processing...");
            if ( ("" + sNewOutTxt).length )
            { // sNewOutTxt.length == iRecentOutChars
            sTxtToShow = sTxtToShow + " STDOUT: ";
               sTxtToShow = sTxtToShow + iRecentOutChars + " [" + sNewOutTxt + "]";
            }
            if ( ("" + sNewErrTxt).length )
            { sTxtToShow = sTxtToShow + " STDERR: ";
               sTxtToShow = sTxtToShow + iRecentErrChars + " [" + sNewErrTxt + "]";
            }
            // (some disabled code in VBScript version was not copied to here...)
            // May want to log sTxtToShow?
            if( ("" + sTxtToShow).length )
            { // May want to log this?
            } //end if ("" + sTxtToShow).length
         }
         // Counting passed time would be nice, to help honor uiMaxMs better...
         // However, that seems complicated, so just assume code ran fast
         if( !( iRecentOutChars+iRecentErrChars ) )
{
WScript.Sleep( ciIterationTime );
   iTimePassed = iTimePassed + ciIterationTime;
         } // if there was no output
      } // end if true || uiMaxMs
   } // end if oExecObj.status is zero
   else // oExecObj.status is not zero
   {   bStayInLoop = false; // success
   } // end code branches depending on oExecObj.status
} while (bStayInLoop); // end of a do{} loop

   // WScript.Echo("Debug:LEFT WHILE:CmdExecW...( " + sPassedCmdToRun + "," + sCaller + "," + uiMaxMs & ")");


   // This could be an infinite loop.  If problems occur, this loop
   // could be enhanced to keep a running count of how much sleep
   // occurred, & log/abort/respond if things seem to take too long.
sReturnStr = "" + oExecObj.ExitCode;

if ( !oExecObj.StdErr.AtEndOfStream )
{ sTxtFromObj = "";
   // sTxtFromObj = "" + oExecObj.StdErr.ReadAll();
   // Above line may have led to lock-ups: try this Do Loop instead:
   do // while more characters exist before the end of the stream
   { sTxtFromObj = sTxtFromObj + oExecObj.StdErr.Read(1);
   } while( ! oExecObj.StdErr.AtEndOfStream ); //end do
   // Going character by character may be hugely inefficient, but
   // was fast to code.  So, this code was made quickly
   // (in order to allow quick deployment, to start preventing
   // instability).  (Perhaps finding stream length would permit stable
   // reading of an amount longer than just one character?)
   sReturnStr = sReturnStr + " STDERR: " + ( ( ""+sTxtFromObj).length ) + " [";
   sReturnStr = sReturnStr + sTxtFromObj + "]";
} //end if not at end of StdErr
//duplicate above code, except change StdErr references to StdOut
if ( !oExecObj.StdOut.AtEndOfStream )
{ sTxtFromObj = "";
   // sTxtFromObj = "" + oExecObj.StdOut.ReadAll();
   // Above line may have led to lock-ups: try this Do Loop instead:
   do // while more characters exist before the end of the stream
   { sTxtFromObj = sTxtFromObj + oExecObj.StdOut.Read(1);
   } while( ! oExecObj.StdOut.AtEndOfStream ); //end do
   // Going character by character may be hugely inefficient, but
   // was fast to code.  So, this code was made quickly
   // (in order to allow quick deployment, to start preventing
   // instability).  (Perhaps finding stream length would permit stable
   // reading of an amount longer than just one character?)
   sReturnStr = sReturnStr + " STDOUT: " + ( ( ""+sTxtFromObj).length ) + " [";
   sReturnStr = sReturnStr + sTxtFromObj + "]";
} //end if not at end of StdOut

ifDbgShowRes( 8,csSelfFcnName + " should, at this point, check if temp files were used, and add the contents of those files to the output, and then delete those temp files... ");
WScript.Echo( csSelfFcnName + " should, at this point, check if temp files were used, and add the contents of those files to the output, and then delete those temp files... ");

ifDbgShowRes( 8,csSelfFcnName + " returning " + sReturnStr + "after running " + sCmdToRun);
return "" + sReturnStr;

} //End Function cmdExecTimed

function cmdExec(sPassedCmdToRun,sStdOTmp,sStdETmp,sCaller) // Executes the command specified
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain
// WScript.Echo("Checking CmdExec parameters : " + sCaller + " redirected to " + sStdOTmp + " ... to run " + sPassedCmdToRun );
return "" + cmdExecTimed(sPassedCmdToRun,sStdETmp,sStdETmp,sCaller,0);
} // End Function cmdExec

function cmdRun(sPassedCmdToRun,iWinStyle,bPauseParentWhileChildRuns,sCaller)
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain
var oWscrSh;
var iReturn;

iWinStyle=0;// http://msdn.microsoft.com/en-us/library/d5fk67ky(VS.85).aspx
bPauseParentWhileChildRuns = false;

oWscrSh = new ActiveXObject("WScript.Shell");

try
{ iReturn = oWscrSh.Run(sPassedCmdToRun,iWinStyle,bPauseParentWhileChildRuns); // Run the actual command here!
}
catch (oErr )
{ // no error reporting/handling; if error occurred, then just continue on...
}
delete oWscrSh;
return iReturn;

} // End Function cmdRun

function getExecCodeFromCmdExec(sCmdResults)
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain
var aCmdResArray;
aCmdResArray = split(sCmdResults," ");
getExecCodeFromCmdExec = aCmdresArray(0);
} //End Function getExecCodeFromCmdExec

function getFieldResultsFromCmdExecs(sCmdResults,tagToUse)
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain


// (This seems deceivingly simple, but took a bit
// of time to debug to get the desired results)
var strToCheck;
var aCmdResArray;
var iBegChr;
var iEndChr;
var sResults;
   sResults = "";

// Get rid of the exit code
iBegChr = ( (getExecCodeFromCmdExec(sCmdresults)).length ) + ( (" ").length );
strToCheck = (""+sCmdResults).substr(iBegChr, (sCmdResults).length );

do // as long as strToCheck has characters
{   aCmdResArray = split(strToCheck," ");
   if( (aCmdResArray.length) > 1)
   { iBegChr = (""+aCmdResArray(0)).length + (" ").length + (""+aCmdResArray(1)).length + (" [").length ;
      iEndChr = ("" + aCmdResArray(1) ).length;

      //Remove text that no longer needs to be checked
      if( !("" + tagToUse).indexOf( aCmdResArray(0) ) && !("" + aCmdResArray(0) ).indexOf(tagToUse) )
      { //This means both variables contain each other, so they are identical
         sResults = (""+strToCheck).substr(iBegChr, iEndChr);
         // No more checking is required
         // Nothing still needs to be checked, so clear strToCheck
         strToCheck = ""; // We are done, so leave while loop
      }
      else
      { // Remove the first part of strToCheck as we no longer need to check it
         strToCheck = (""+strToCheck).substr( iEndChr + ("]").length, (strToCheck).length );
      } //End If...
   }
   else
   { strToCheck = ""; // We are done.  Set to empty so while loop gets ended
   } // End If (branching based on size of aCmdResArray)
} while ( Len(strToCheck) > 0); // end do

return "" + sResults
} // End Function getFieldResultsFromCmdExecs

function getStdErrFromCmdExec(sCmdResults) // Just a simple wrapper function
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain
return getFieldResultsFromCmdExecs(sCmdResults,"STDERR:");
} // End Function

function getStdOutFromCmdExec(sCmdResults) // Just a simple wrapper function
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain
return getFieldResultsFromCmdExecs(sCmdResults,"STDOUT:");
} // End Function


function wshexec()
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain
var csCodeFile; //const str: identifier for this file

csCodeFile = "wshexecj";

if( !this.mainFcn )
{ this.mainFcn = ""+csCodeFile;
}
if( ""+ this.mainFcn == ""+ csCodeFile )
{ wshexecTests();
}
} // End Function

function wshexecTests()
{ // This function, made by Scott Conrad VanderWoude, may be treated as public domain
if(!this.mainFcn)
var csCodeFile; //const str: identifier for this file

csCodeFile = "wshexecj";

WScript.Echo("This is preliminary testing.");


	var selfFcnName="wshexecTests";
	
	var runOK;
	var sResults;

	runOK=false;

//function cmdRun(sPassedCmdToRun,iWinStyle,bPauseParentWhileChildRuns,sCaller)
// http://msdn.microsoft.com/en-us/library/d5fk67ky%28v=vs.84%29.aspx

	if(runOK)
	{
		// These tests worked out okay
	WScript.Echo(selfFcnName + "Run calc test #1?");
		cmdRun("Calc"); //works
	WScript.Echo(selfFcnName + "Run calc test #1?");
		cmdRun("Calc",1,true,selfFcnName); // works, shows

	}


	WScript.Echo(selfFcnName + "Run notepad test?");
//		cmdRun("Notepad"); // seems to not work (?)
	WScript.Echo(selfFcnName + "Show dir?");
//		cmdRun('cmd /k dir /s "%ProgramData%"');

//		cmdRun("start cmd /c dir C:\\  /s",1);
	//if(!confirm("abort?"))
	//	jsError();

//function cmdExec(sPassedCmdToRun,sCaller) // Executes the command specified

	WScript.Echo(selfFcnName + "About to run Calc, and wait for Calc to exit");
		cmdExec("Calc");
	WScript.Echo(selfFcnName + "Exec notepad test?");
		sResults = cmdExec("Notepad");
	WScript.Echo(selfFcnName + "Notepad: results type  : " + typeof(sResults));
	WScript.Echo(selfFcnName + "Notepad: results value : " + sResults);
	WScript.Echo(selfFcnName + "Exec Show dir?");
	WScript.Echo(selfFcnName + "(Press Ctrl-C if this seems to lock up)");

		//sResults = cmdExec("cmd /c dir C:");
		//sResults = cmdExec("cmd /c dir C:");
//		sResults = cmdExecTimed('cmd /k dir /s "%ProgramData%"',selfFcnName,15000,15000,1);
		sResults = cmdExecTimed('cmd /k dir /s "C:\\ProgramData%"',"",selfFcnName,15000,15000,1);

//function cmdExecWithMaxTime (sPassedCmdToRun,sCaller,uiMaxMs)


	WScript.Echo(selfFcnName + "dir: results type  : " + typeof(sResults));
	WScript.Echo(selfFcnName + "dir: results value : " + sResults);

	
	//WScript.Echo("abort?"))
	//	jsError();



	WScript.Echo("done");

WScript.Quit();
{ this.mainFcn = ""+csCodeFile;
}
if( ""+ this.mainFcn == ""+ csCodeFile )
{ wshexecTests();
}
} // End Function

Version history (for the JScript version)
Version 1.2js

First version of the JScript code. The version number was meant to be similar to version 1.2vbs (which is the VBScript version of this code).

VBScript Code

Here is some example code that uses VBScript. Some modifications have been made since the code was made and tested. At the time of this writing, this code hasn't been tested since some of the modifications have been made. Specifically, code to use .Read instead of .ReadAll This code has not been verified to be free of being susceptible to the bug that may freeze the system, so use at your own risk.

(Version history is shown after this code.)

Function CmdExecTimed (sPassedCmdToRun,sTmpFile,sStdETmp,sCaller,uiMaxMs)
' This code executes the command specified.
' Passed values: sPassedCmdToRun: A string to execute.  It
'    is required that any necesary that any necessary sanitizing
'    occurs before this happens!
' sStdOTmp: Not implemented yet
' sStdETmp: Not implemented yet
' sCaller: A string containing info to help debug code.
'    This is technically cosmetic, so can say anything.
'    Recommended: Include history of function calls.
' uiMaxMs: If non-zero, maximum amount of time (in
'    milliseconds) that this function will wait on the program.
'    program that gets run. Zero = wait infinitely.
' Returns: A string.  The string is likely to be human readable, and is
' therefore appropriate for output that a human would read.  The string
' returned will start with the return/exit code of what was ran, followed by
' white space.  It will also include any text that was
' output to stderr or to stdout.  That may be nice for some quick
' substring results when checking trusted output, but beyond that, the
' recommended method to truely parse the output is to use some related
' functions like getFieldResultsFromCmdExecs
' This function made by Scott Conrad VanderWoude of TOOGAM.com, may be
' treated as public domain.  The same is true for
' getFieldResultsFromCmdExecs and similar/related code.
' This code is version "1.2vbs" at the time this comment was updated.
' The home page for this code is at CyberPillar.com at
' /dirsver/1/mainsite/techns/coding/scriptin/winscrho/wshexec/wshexec.htm
' Requires: The code for IfDbgShowRes (or comment out the calls to that code)

const sSelfFcnName="CmdExecTimed"

const iIterationTime = 100
Dim sCmdToRun, oWscrSh, oExecObj, sReturnStr, sTxtFromObj, bStayInLoop
Dim sCmdToRun, iTimePassed, sTxtToShow, sStdOutTxt, sStdErrTxt, sTxtToLog

' Ideas, not implemented here:
' Could call sanitizing function.
' Could compare sCmdToRun to the output of a sanitizing function.
' If they are different, warn that it is not clear if things were
' sanitized, and possibly abort (before running the code).

sCmdToRun = "" & sPassedCmdToRun

If (Len(sSmdToRun) = 0) Then
   ' Blank command was provided, probably by error. Experience revealed
   ' that the coder probably did not intend this.  Rather than do
   ' what was literally requested (do nothing), it is better to run
   ' a command that reports cause of error. This way, when the
   ' coder is not getting expected results and checks results of the
   ' command line, some useful text gets shown.
   sCmdToRun = "echo " & sSelfFcnName & " got a blank command"
   sCmdToRun = sCmdToRun & " when called by " & sCaller
   if(uiMaxMs > 0) Then
      sCmdToRun = sCmdToRun & " (" & uiMaxMs & "ms)"
   End If
End If
Set oWscrSh = WScript.CreateObject("Wscript.Shell")
' This usesWscript.Shell.Exec which has more output-capturing options
' than Wscript.Shell.Run (even though Wscript.Shell.Run) has more
' options about window placement.)  (An older approach was to use
   'iRetCode=oWscrSh.Run(sCmdToRun,0,True).)


On Error Resume Next

   Set oExecObj = oWscrSh.Exec(sCmdToRun) ' Run the actual command

   ' TypeName(oExecObj) should be object, or similar; it
   ' should not be Empty.  If Empty, oExecObj.status cannot be
   ' successfully checked.  Result likely to be function hanging;
   ' fault may be calling function passing sCmdToRun as empty str.

iTimePassed = 0
bStayInLoop = true

Do
   WScript.Sleep iIterationTime
   iTimePassed = iTimePassed + iIterationTime

   ' Hmm...  was this addition meant to be repeated?
   //iTimePassed = iTimePassed + iIterationTime

   If (oExecObj.status = 0) Then
      If (uiMaxMs > 0) Then
         If (iTimePassed > uiMaxMs) Then
            bStayInLoop = false
            sTxtToShow = sSelfFcnName & " exceeded " & uiMaxMs & "milliseconds.  "
            sTxtToShow = sTxtToShow & sCaller & " had requested to run [" & sCmdToRun
            sTxtToShow = sTxtToShow & "]."
            sTxtToLog = sTxtToShow
            IfDbgShowRes 8,sTxtToShow

            ' May want to store sTxtToShow for logging

            IfDbgShowRes 8,sTxtToShow 'Output something now in case readAll locks things up
            sTxtToShow="" 'Prevent having previous msg be repeated since it was just output

            sStdOutText = ""
            sStdErrText = ""
            While ( (Not oExecObj.StdErr.AtEndOfStream) OR (Not oExecObj.StdErr.AtEndOfStream) )
               If Not oExecObj.StdErr.AtEndOfStream Then
                  sTxtFromObj = "" & oExecObj.StdErr.Read(1)
                  sStdErrTxt = sStdErrTxt & sTxtFromObj
               End If
               If Not oExecObj.StdOut.AtEndOfStream Then
                  sTxtFromObj = "" & oExecObj.StdOut.Read(1)
                  sStdOutTxt = sStdOutTxt & sTxtFromObj
               End If
            Wend
            If 0 < Len(sStdErrTxt) Then
               sTxtToShow = sTxtToShow & " STDERR: "
               sTxtToShow = sTxtToShow & Len(sStdErrTxt) & " [" & sStdErrTxt & "]"
            End If
            If 0 < Len(sStdOutTxt) Then
               sTxtToShow = sTxtToShow & " STDOUT: "
               sTxtToShow = sTxtToShow & Len(sStdOutTxt) & " [" & sStdOutTxt & "]"
            End If
            If false AND Not oExecObj.StdErr.AtEndOfStream Then
               sTxtFromObj = "" & oExecObj.StdErr.ReadAll
               sTxtToShow = sTxtToShow & " STDERR: "
               sTxtToShow = sTxtToShow & Len(sTextFromObj) & " [ & sTxtFromObj & "]"
            End If
            If false AND Not oExecObj.StdOut.AtEndOfStream Then
               sTxtFromObj = "" & oExecObj.StdOut.ReadAll
               sTxtToShow = sTxtToShow & " STDOUT: "
               sTxtToShow = sTxtToShow & Len(sTextFromObj) & " [ & sTxtFromObj & "]"
            End If
            ' May want to log sTxtToShow?
            If(Len(sTxtToShow) > 0) Then
               ' May want to log this?
            End If 'end if ("" + sTxtToShow).length
         End If 'end if iTimePassed > uiMaxMs
      End If ' end if uiMaxMs
   Else // oExecObj.status is not zero
      bStayInLoop = false ' success
   End If // end code branches depending on oExecObj.status
Loop while (bStayInLoop)

   ' WScript.Echo "Debug:LEFT WHILE:CmdExecW...( " & sPassedCmdToRun & "," & sCaller & "," & uiMaxMs & ")"


   ' This could be an infinite loop.  If problems occur, this loop
   ' could be enhanced to keep a running count of how much sleep
   ' occurred, & log/abort/respond if things seem to take too long.
sReturnStr = "" & oExecObj.ExitCode

If Not oExecObj.StdErr.AtEndOfStream Then
   sTxtFromObj = ""
   ' sTxtFromObj = "" & oExecObj.StdErr.ReadAll
   ' Above line may have led to lock-ups: try this Do Loop instead:
   Do
      sTxtFromObj = sTxtFromObj & oExecObj.StdErr.Read(1)
   Loop While Not oExecObj.StdErr.AtEndOfStream
   ' Going character by character may be hugely inefficient, but
   ' was fast to code.  So this code was made in order to
   ' allow quick deployment to start preventing instability.
   sReturnStr = sReturnStr & " STDERR: " & Len(sTxtFromObj) & " ["
   sReturnStr = sReturnStr & sTxtFromObj & "]"
End If
If Not oExecObj.StdOut.AtEndOfStream Then
   sTxtFromObj = ""
   ' sTxtFromObj = "" & oExecObj.StdOut.ReadAll
   ' Above line may have led to lock-ups: try this Do Loop instead:
   Do
      sTxtFromObj = sTxtFromObj & oExecObj.StdOut.Read(1)
   Loop While Not oExecObj.StdOut.AtEndOfStream
   ' Going character by character may be hugely inefficient, but
   ' was fast to code.  So this code was made in order to
   ' allow quick deployment to start preventing instability.
   sReturnStr = sReturnStr & " STDOUT: " & Len(sTxtFromObj) & " ["
   sReturnStr = sReturnStr & sTxtFromObj & "]"
End If

IfDbgShowRes 8,sSelfFcnName & " returning " & sReturnStr & "after running " & sCmdToRun

End Function

Function CmdExec(sPassedCmdToRun,sTmpFile,sCaller) ' Executes the command specified
' WScript.Echo "Checking CmdExec parameters : " & sCaller & " ran " & sPassedCmdToRun
CmdExec = "" & CmdExecTimed(sPassedCmdToRun,sTmpFile,sCaller,0)
End Function

Function CmdRun(sPassedCmdToRun,iWinStyle,bPauseParentWhileChildRuns,sCaller)
Dim oWscrSh
Dim iReturn

iWinStyle = 0 ' http://msdn.microsoft.com/en-us/library/d5fk67ky(VS.85).aspx
bPauseParentWhileChildRuns = false

Set oWscrSh = WScript.CreateObject("WScript.Shell")

On Error Resume Next
   iReturn = oWscrSh.Run(sPassedCmdToRun,iWinStyle,bPauseParentWhileChildRuns) ' Run the actual command here!
Set oWscrSh = Nothing
CmdRun = iReturn

End Function

Function getExecCodeFromCmdExec(sCmdResults)
' This function, made by Scott Conrad VanderWoude, may be treated as public domain
Dim aCmdResArray
aCmdResArray = Split(sCmdResults," ")
getExecCodeFromCmdExec = aCmdresArray(0)
End Function

Function getFieldResultsFromCmdExecs(sCmdResults,tagToUse)
' This function, made by Scott Conrad VanderWoude, may be treated as public domain

' (This seems deceivingly simple, but took a bit
' of time to debug to get the desired results)
Dim strToCheck
Dim aCmdResArray
Dim iBegChr
Dim iEndChr
Dim iMidOffset//Used only because Mid requires a 1-based index
Dim sResults : sResults = ""

' Get rid of the exit code
iBegChr = Len(getExecCodeFromCmdExec(sCmdresults)) + Len(" ")+iMidOffset
strToCheck = Mid(sCmdResults,iBegChr,Len(sCmdResults))

do
   aCmdResArray = Split(strToCheck," ")
   If(UBound(aCmdResArray) > 1) Then
      iBegChr = Len(aCmdResArray(0)) + Len(" ") + Len(aCmdResArray(1)) + Len(" [") + iMidOffset
      iEndChr = Len( aCmdResArray(1) )

      If( Instr(tagToUse,aCmdResArray(0))=1 AND Instr(aCmdResArray(0),tagToUse)=1 ) Then
         sResults = Mid(strToCheck, iBegChr, iEndChr)
         strToCheck = "" ' We are done, so leave while loop
      Else
         ' Remove the first part of strToCheck as we no longer need to check it
         strToCheck = Mid(strToCheck, iEndChr + Len("]"), Len(strToCheck))
      End If
   Else
      strToCheck = "" ' We are done so leave while loop
   End If
Loop while ( Len(strToCheck) > 0)

getFieldResultsFromCmdExecs = "" & sResults
End Function

Function getStdErrFromCmdExec(sCmdResults) ' Just a simple wrapper function
' This function, made by Scott Conrad VanderWoude, may be treated as public domain
getStdErrFromCmdExec = getFieldResultsFromCmdExecs(sCmdResults,"STDERR:")
End Function

Function getStdOutFromCmdExec(sCmdResults) ' Just a simple wrapper function
' This function, made by Scott Conrad VanderWoude, may be treated as public domain
getStdErrFromCmdExec = getFieldResultsFromCmdExecs(sCmdResults,"STDOUT:")
End Function
Version history (for the VBScript version)
Version 1.2vbs
  • Like the prior version, some modifications have been made and this code was not tested since that time. Until such testing is performed and then documented on the project's main page, consider this code to be untested.
    • For version 1.2, the JScript version has some additional/updated comments that might not have been merged into this version quite yet.
  • The parameters to cmdExecTimed and cmdExec have changed. Since all code that has previously been publicly posted is code that was also not functional yet, this change is expected to provide minimal disruption.
  • Noticed several syntax errors.
    • The CmdRun function now runs oWscrSh.Run instead of trying (presumably unsuccessfully) to run a function named “oWscrShRun”.
    • Missing apostrophes at the start of some comments.
    • Missing quotation marks, leading to syntax errors
    • Also, there was an HTML error showing “amp;” instead of the ampersand (“&”).
    • The variable iEndChr was misspelled (as endChar) once.
    All of these errors were relatively easy to fix, but would have caused the code to not run properly.
  • The old function named cmdExecWithMaxTime has been renamed to cmdExecTimed. Since all prior versions are expected to have had extremely limited distribution, no wrapper function is being made. (That keeps the library slightly clearner in the long run. Also, if anyone has code that relies on an older public release, that had to have been debugged to be useful.)
  • getFieldResultsFromCmdExecs got a new variable, iMidOffset.
  • A new dedicated home page is now used for this code. In the code, documentation (comment) updated to reflect a new dedicated home page for this code. (The URL mentioned in version 1.1vbs was at ../winscrho.htm#wshexec near the current home page, and the old location nicely provides a convenient hyperlink to the new home page.)
  • Commentary updates: Fixed a typo in a comment: an uppercase N should have been a lowercase n. Also removed a(n unintentional, unnecessary) duplicate line of commentary. “probably” no longer missing an l.
  • Added white space between an equal sign and a zero. This is a cosmetic change in the code, and has no impact.
  • Renamed variable from iMaxms to uiMaxms
Version 1.1vbs
  • Added version number to CmdExecTimed's comment. Also documented the current public home page for this code.
  • Like the prior version, some modifications have been made and this code was not tested since that time. Until such testing is performed and then documented on the project's main page, consider this code to be untested.
  • Made some further modifications. A While loop is used so that the dangerous .ReadAll function does not get used.
    • The prior code is currently remaining, but the code now shows “If false AND”..., so the code is effectively disabled.
Prior versions

Prior versions have not been labelled with a specific version number.

  • It is believed that the first publicly posted/shared version of this code was the version just before Version 1.1vbs
  • This was initially made at home with no usage of resources by the author's employer, so the author placed the code in public domain. The code was deployed at the author's employer, just like other free code used by that employer. Any modifications between the time of that initial creation, and the first public posting of this code, were likely minor and were not publicly documented.

The following is some code before being manually CSS'ed. Until the code is tested further, this un-CSS'ed code remains available for comparison (in case any errors crept in during the process of adding CSS to the code).

(CSS has been added. Actually, the CSS'ed version of the code, above, might be updated more than the following text version...)

Function CmdExecTimed(sPassedCmdToRun,sCaller,uiMaxMs)
' This code executes the command specified.
' Passed values: sPassedCmdToRun: A string to execute.  It is required that any
'		necessary sanitizing occurs before this happens!
'	sCaller: A string containing info to help debug code.
'		Recommended: Include history of function calls.
'		Technically, this is cosmetic so can say anything.
'	uiMaxMs: If non-zero, maximum amount of time (in milliseconds) that this
'		function will wait on the program
' Returns: A string.  The string is likely to be human readable, and is
'	therefore appropriate for output that a human would read.  The string
'	returned will start with the return/exit code of what was ran, followed by
'	white space.  It will also include any text that was output to
'	stderr or to stdout.  That may be nice for some quick substring
'	results when checking trusted output, but beyond that, the
'	recommended method to truely parse the output is to use some related
'	functions like getFieldResultsFromCmdExecs
'	This function made by Scott Conrad VanderWoude of TOOGAM.com, may be
'	treated as public domain.  The same is true for
'	getFieldResultsFromCmdExecs and similar/related code.

	const sSelfFcnName="CmdExecTimed"

	const iIterationTime = 100
	Dim sCmdToRun, oWscrSh, oExecObj, sReturnStr, sTxtFromObj, bStayInLoop
	Dim iTimePassed, sTxtToShow, sTxtToLog

	' Ideas, not implemented here:
	'	Could call sanitizing function.  Could compare sCmdToRun to the
	'	output of a sanitizing function, and if they are different, warn
	'	that it is not clear if things were sanitized, and possibly abort.

	sCmdToRun = sPassedCmdToRun

	If (Len(sCmdToRun) = 0) Then
		sCmdToRun = "echo " & sSelfFcnName & " with blank command, called by " & sCaller
		if(uiMaxMs > 0) Then
			sCmdToRun = sCmdToRun & " (" & uiMaxMs & "ms)"
		End If
	End If

	Set oWscrSh = Wscript.CreateObject("Wscript.Shell")

	' This uses Wscript.Shell.Exec which has more output capturing options than
	'	Wscript.Shell.Run (even though Wscript.Shell.Run has more options
	'	about window placement).  (An older approach was to use
		'iRetCode = oWscrSh.Run(sCmdToRun,0,True)

	IfDbgShowRes 8,sSelfFcnName & " about to run " & sCmdToRun



	On Error Resume Next

		set oExecObj = oWscrSh.Exec(sCmdToRun) ' Run the actual command here!

		' TypeName(oExecObj) should be approx object, not Empty.
		' If empty, oExecObj.status cannot be successfully checked.  Result
		' likely to be function hanging; fault may be calling function.
		' passing sCmdToRun as empty string.

	iTimePassed = 0
	bStayInLoop = true

	Do 
		WScript.Sleep iIterationTime
		iTimePassed = iTimePassed + iIterationTime

		If (oExecObj.status = 0) Then
			If (uiMaxMs > 0) Then
				If(iTimePassed > uiMaxMs) Then
					bStayInLoop = false
					sTxtToShow = sSelfFcnName & " exceeded " & uiMaxMs & " milliseconds."
					sTxtToShow = sTxtToShow & "  " & sCaller & " had requested to run [" & sCmdToRun & "]."
					sTxtToLog = sTxtToShow
					IfDbgShowRes 8,sTxtToShow

					' May want to store sTxtToShow for logging

					IfDbgShowRes 8,sTxtToShow ' Output something now in case ReadAll locks things up
					sTxtToShow="" ' Prevent having previous message be repeated since it was just output

					If Not oExecObj.StdErr.AtEndOfStream Then
						sTxtFromObj = "" & oExecObj.StdErr.ReadAll
						sTxtToShow = sTxtToShow & " STDERR: " & Len(sTxtFromObj) & " [" & sTxtFromObj & "]"
					End If
					If Not oExecObj.StdOut.AtEndOfStream Then
						sTxtFromObj = "" & oExecObj.StdOut.ReadAll
						sTxtToShow = sTxtToShow & " STDOUT: " & Len(sTxtFromObj) & " [" & sTxtFromObj & "]"
					End If
					' May want to log sTxtToShow?
					If(Len(sTxtToShow) > 0) Then
						sTxtToShow = " (Additional info found: " & sTxtToShow
						sTxtToLog = sTxtToLog & sTxtToShow ' Add new info to old info
						IfDbgShowRes 8,sSelfFcnName & sTxtToShow ' Show new info
					End If
					If(Len(sTxtToLog) > 0) Then
						' May want to log this?
					End If
				End If
			End If
		Else
			bStayInLoop = false ' success
		End If
	Loop while (bStayInLoop)

		' WScript.Echo "Debug:LEFT WHILE:CmdExecW...( " & sPassedCmdToRun & "," & sCaller & "," & uiMaxMs & ")"


' This could be an infinite loop.  If problems occur, this loop could
		' be enhanced to keep a running count of how much sleep occurred,
		' and log/abort/respond if things seem to be taking too long.
	sReturnStr = "" & oExecObj.ExitCode

	If Not oExecObj.StdErr.AtEndOfStream Then
		sTxtFromObj = ""
		' sTxtFromObj = "" & oExecObj.StdErr.ReadAll
		' Above line may have led to lock-ups: try this Do Loop instead:
		Do
			sTxtFromObj = sTxtFromObj & oExecObj.StdErr.Read(1)
		Loop While Not oExecObj.StdErr.AtEndOfStream
		' Going character by character may be hugely inefficient, but
		'	was fast to code so that instability might be prevented.
		sReturnStr = sReturnStr & " STDERR: " & Len(sTxtFromObj) & " [" & sTxtFromObj & "]"
	End If
	If Not oExecObj.StdOut.AtEndOfStream Then
		sTxtFromObj = ""
		' sTxtFromObj = "" & oExecObj.StdOut.ReadAll
		' Above line may have led to lock-ups: try this Do Loop instead:
		Do
			sTxtFromObj = sTxtFromObj & oExecObj.StdOut.Read(1)
		Loop While Not oExecObj.StdOut.AtEndOfStream

		sReturnStr = sReturnStr & " STDOUT: " & Len(sTxtFromObj) & " [" & sTxtFromObj & "]"
	End If

	IfDbgShowRes 8,sSelfFcnName & " returning " & sReturnStr & " after running " & sCmdToRun

	set oWscrSh = Nothing
	CmdExecTimed = sReturnStr

End Function

Function CmdExec(sPassedCmdToRun,sCaller) ' Executes the command specified
	' WScript.Echo "Checking CmdExec parameters : " & sCaller & " ran " & sPassedCmdToRun
	CmdExec = "" & CmdExecTimed(sPassedCmdToRun,sCaller,0)
End Function

Function CmdRun (sPassedCmdToRun,iWinStyle,bPauseParentWhileChildRuns,sCaller)
	Dim oWscrSh
	Dim iReturn

	iWinStyle = 0 ' http://msdn.microsoft.com/en-us/library/d5fk67ky%28VS.85%29.aspx
	bPauseParentWhileChildRuns = false

	Set oWscrSh = Wscript.CreateObject("Wscript.Shell")

	On Error Resume Next
		iReturn = oWscrSh.Run(sPassedCmdToRun,iWinStyle,bPauseParentWhileChildRuns) ' Run the actual command here!
		IfDbgShowRes 8,"CmdRun: Return of " & sPassedCmdToRun & " was " & iReturn
	Set oWscrSh = Nothing
	CmdRun = iReturn

End Function


Function getExitCodeFromCmdExec(sCmdResults)
' This function made by Scott Conrad VanderWoude, may be treated as public domain
	Dim aCmdResArray
	aCmdResArray = Split(sCmdResults," ")
	getExitCodeFromCmdExec = aCmdResArray(0)
End Function

Function getFieldResultsFromCmdExecs(sCmdResults,tagToUse)
' This function made by Scott Conrad VanderWoude, may be treated as public domain
	' (This seems deceivingly simple, but took a bit
	' of time to debug to get the desired results)
	Dim strToCheck
	Dim aCmdResArray
	Dim iBegChr
	Dim iEndChr
	Dim sResults : sResults = ""

	' Get rid of the exit code
	iBegChr = Len(getExitCodeFromCmdExec(sCmdResults)) + Len(" ")+1
	strToCheck = Mid(sCmdResults,iBegChr,Len(sCmdResults))

	do
		aCmdResArray = Split(strToCheck," ")
		If(UBound(aCmdResArray) > 1) Then
			iBegChr = Len(aCmdResArray(0)) + Len(" ") + Len(aCmdResArray(1)) + Len(" [") + 1
			iEndChr = aCmdResArray(1)

			If( Instr(tagToUse,aCmdResArray(0))=1 AND Instr(aCmdResArray(0),tagToUse)=1 ) Then
				sResults = Mid(strToCheck, iBegChr, iEndChr)
				strToCheck = "" ' We are done so leave while loop
			Else
				' Remove the first part of strToCheck as we no longer need to check it
				strToCheck = Mid(strToCheck,iEndChr + Len("]"),Len(strToCheck))
			End If
		Else
			strToCheck = "" ' We are done so leave while loop
		End If
	Loop while ( Len(strToCheck) > 0)

	getFieldResultsFromCmdExecs = "" & sResults
End Function

Function getStdErrFromCmdExec(sCmdResults) ' Just a simple wrapper function
' This function made by Scott Conrad VanderWoude, may be treated as public domain
	getStdErrFromCmdExec = getFieldResultsFromCmdExecs(sCmdResults,"STDERR:")
End Function

Function getStdOutFromCmdExec(sCmdResults) ' Just a simple wrapper function
' This function made by Scott Conrad VanderWoude, may be treated as public domain
	getStdErrFromCmdExec = getFieldResultsFromCmdExecs(sCmdResults,"STDOUT:")
End Function

To run a program, either use the example CmdRun function or use the example CmdExecTimed function. In some cases, one may work better than the other. The CmdExecTimed allows one to specify a time limit. If there is not a desire to enforce a specific time limit, either specify a time limit of zero, or just use CmdExec (which simply uses CmdExecTimed and specifies a time limit of zero).

Here is some small example code which may show both:

(At the time of this writing, this sample code has not been tested since it was written. If there is a simple syntax error, fix/adjust as needed.)
Function Sample
Dim cmdResults
Dim sCmdToRun
Dim bWaitForCmdToStop

sCmdToRun="Calc.exe"
bWaitForCmdToStop=false

CmdRun (sCmdToRun,0,bWaitForCmdToStop,"Sample@CmdRun-Test")

sCmdToRun="COMMAND.COM /C DIR"
cmdResults=CmdExec(sCmdToRun,"Sample@CmdExec-Test")
WScript.Echo "Exit Value=" & getExitCodeFromCmdExec(cmdResults)
WScript.Echo "Error Text={" & getStdErrFromCmdExec(cmdResults & "}" )
WScript.Echo "Output was={" & getStdOutFromCmdExec(cmdResults & "}" )
End Sample

Sample()

Porting Notes

For the most part, this code involves interacting with the objects provided as part of WSH, and simple string manipulations. There are some language differences, like JScript using return, but VBScript assigning the return value to the function name. However, such differences are able to be quickly handled by people familiar with both languages.

(These porting notes are not meant to provide all of the details needed for porting. Familiarity with the languages is expected by anyone wishing to compare multiple ports.)

This code uses ActiveX, which can be seen in the lines where JScript uses ActiveXObject and VBScript uses a “WScript.CreateObject” method, as well as where JScript uses delete and the corresponding code in VBScript which sets a variable to the value of “Nothing”.

Pleasantly, the “split” function in JScript works the same as the “Split” function in VBScript.

In most cases, the JScript property .length refers to the VBScript function Len (because the code is referring to a string), although there is a case where the appropriate VBScript uses Len (because the code is checking the size of an array). That is in getFieldResultsFromCmdExecs which also had more types of string handling tasks than the other functions.