Windows Scripting Host Execution Code
- Solutions bundled with WSH
The WSH package can create a WSH Shell object that provides the following options:
Official documentation of
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
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),
This is often a simple way to run things, and to check the return value. However, this lacks some of the ability of
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
method, but the
method does have some ability to recover from such a problem. The
method is a bit simpler, and so doesn't provide that functionality.
- Example usage
// specifies window visibility, described by
//default=false=continue on, once program starts
// if true,
iRetValwill be return code of the run program
bWaitForCmd, then is return value of started program
// will always be zero when
// "should" be 1=visible for first time, per Microsoft documentation
//0==do not wait
- Official documentation of
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.
function provides some flexibility that is not found in the
function, although this flexibility can be a bit complex to use.
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
.Statusproperty of a WSH Shell object/
.Statusproperty 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
is the PID. Besides the ability to view output streams, seeing the PID is yet another demonstration of how the
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
. 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
method. There is also a
function that acts like a wrapper around
, 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
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
. (If there is not a desire for a time limit, then the
function is meant to be slightly easier to use. The
function simply calls the
function in a way that does not enforce any time limit.)
By using the
variable, WSH can provide some sort of progress indicator that demonstrates that WSH has not frozen up.
The return value of
, which is just a wrapper for
, and so returns the same thing as the
function) is a string. There are available functions to to able to extract the desired data, such as
which is able to easily extract only the data from the output stream that is called “standard output” (“stdout”). Another example is
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
By having wrapper functions like
, accessing only one section of the data is a snap, thanks to the easy flexbility provided by
. 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
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
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
function (or the
function). If that output contains the desired information, but also contains more information that is not desired, then check out
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
WshScriptExecObject is some official documentation about how this can freeze things up.
This threat of freezing is really only related to using
(and the related functions in this code), not
(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
method, because it is waiting for StdErr to be released by the called process - which becomes blocked before the
can get access to empty it.” The recommended solution (which, at the time of this writing, has not verified this text) is to use
or possibly a
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
command. If so, the
/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
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
runs, then accessing
will cause a hang. John Frensen's posts in a Usenet forum post about WSH
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
property must be used to figure out if performing a
(or a variation:
) will be safe, but there are times where
leads to a lockup if it is accessed before more data is read from the buffer (using
or similar functions). Some experimentation suggests that using either the
property, XOR reading one byte (with “
”), 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
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
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
function's code. The danger is more from mixing the
function's code with a stream that does not have a clear “end”, which is true for the
property, and also for the object's
property. This does not suggest that the
function is generally dangerous. (The
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
WshScriptExecObject 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
, 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
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
function (which is code available on this page). As a result, the related code in the
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
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
- 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
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.
, 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.
Just don't rely on the WScript.Shell
method. One alternative, which might be plenty suitable in some cases, may be the
method. Or, perhaps use something other than WSH?
Multiple versions available
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.
(Version history is shown after this code.)
- 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).
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
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.)
- 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
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.
function now runs
instead of trying (presumably unsuccessfully) to run a function named “
- 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 (“&”).
was misspelled (as
- 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.)
got a new variable,
- 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
- 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.
- Version 1.1vbs
Added version number to
'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
Whileloop is used so that the dangerous
.ReadAllfunction does not get used.
The prior code is currently remaining, but the code now shows “
”..., so the code is effectively disabled.
- The prior code is currently remaining, but the code now shows “
- Added version number to
- 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
function or use the example
function. In some cases, one may work better than the other. The
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
(which simply uses
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.)
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
, 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
and VBScript uses a “
” method, as well as where JScript uses
and the corresponding code in VBScript which sets a variable to the value of “
Pleasantly, the “
split” function in JScript works the same as the “
Split” function in VBScript.
In most cases, the JScript property
.lengthrefers 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
which also had more types of string handling tasks than the other functions.