Does cmd.exe write to stdout/stderr? - windows

This is a weird question based on bad VBScript behavior that I am trying to work around. Please bear with me and thank you in advance.
In VBScript, you can do the following to start a task:
Set WshShell = CreateObject("WScript.Shell")
Set WshShellExec = WshShell.Exec(strCommand)
Do While WshShellExec.Status <> WshFinished
WScript.Sleep(1000)
Loop
However, there is a bug where, if the command/task started by the WshShell.Exec call writes >=4kb to either StdOut or StdErr, WshShellExec.Status will never be equal to WshFinished (1). I assume this is because there is no space in a buffer the system uses for stdout/stderr, so the initiated task hangs on the next write to stdout/stderr once it writes enough bytes to the buffer(s).
Depending on the program running, you may be able to do something like the following to get around it:
Set wshShellExec = WshShell.Exec(cmd)
Set objStdOut = wshShellExec.StdOut
Set objStdErr = wshShellExec.StdErr
Do Until objStdOut.AtEndOfStream
standardout = standardout & objStdOut.ReadLine & vbCrlf
Loop
Do Until objStdErr.AtEndOfStream
errtext = errtext & objStdErr.ReadLine & vbCrlf
Loop
However, this does not always work. If the running program writes 4kb to stderr before writing anything to stdout, this code hangs on the evaluation of objStdOut.AtEndOfStream or on objStdOut.ReadLine (note, you cannot use objStdOut.ReadAll because that will also block forever).
So, I've come up with the following solution that seems to work:
strCommand = "c:\Windows\System32\cmd.exe /C StdOutTester.exe > " & rsltTextFile & " 2>&1 || call echo %^errorLevel% > " & rsltErrCodeFile
Set WshShellExec = WshShell.Exec(strCommand)
Do While WshShellExec.Status <> WshFinished
Wscript.Sleep(1000)
secCount = secCount+1
If secCount >= 10 Then 'kill children
If KillTaskChildren(WshShellExec.ProcessID) <> 0 Then
WScript.Echo "WMI Terminate Failed. Killing CMD..."
WshShellExec.Terminate
End If
End If
Loop
What this does is run the actual task in another cmd window and redirect both stdout/stderr into a file (and also the error code if applicable). Because I'm running the command in another cmd window, stdout and stderr are decoupled from the WshShellExec object. This also allows me to kill the running task for a timeout (the real one, i.e. not the cmd window but the actual task, by using WMI).
So, my question becomes this:
Since I am running another cmd.exe window, it is possible that cmd prompt itself runs into the same issue if it writes enough data into stdout/stderr (in which case, my timeout logic should kill it)? Does it ever actually write anything into stdout/stderr? What about if I use the /Q option?

Related

Get possible syntax error when calling a VBScript without waiting till the end

I'm currently starting a VBScript from another VBScript via WScript.Exec without waiting for its result (which is working just fine):
Set ObjShell = CreateObject("WScript.Shell")
Set ShellExec = ObjShell.Exec(strFinalCommand)
Set ShellExec = Nothing
Set ObjShell = Nothing
I've just discovered a problem if the second script (which has its own error handling inside) does have a syntax issue.
Since this is determined before the script starts running, I can't notice the error in the second script but instead have to handle it in the first one.
Is there any possibility to check if the call threw an error at the start and - if not, just let it work
- if there is an error, stop the wscript and get the error text.
I haven't found anything on such a specific topic so far.
I've already tried ShellExec.ExitCode and ShellExec.StdErr, but are both are only updated after the second script finishes.
If there is an error, both are not updated.
I also tried ShellExec.SdtErr.AtEndOfStream, which crashed my process if an error appeared.
Edit:
The Command I'm running is wscript.exe D:\\...\RunAsyncShell.vbs /ParamOne:123 /ParamTwo:ABC with about 10 params more to follow (can't show the exact call due to company regulations).
You could do something like this:
Set ObjShell = CreateObject("WScript.Shell")
Set ShellExec = ObjShell.Exec(strFinalCommand)
limit = DateAdd("s", 2, Now)
Do While ShellExec.Status = 0 And Now < limit
WScript.Sleep 100
Loop
If ShellExec.ExitCode <> 0 Then
WScript.Echo "Script failed."
End If
That will wait up to 2 seconds after launching the second script, and report an error if the process returned a non-zero exit code within that time frame. However, you'll probably have to run the second script with cscript.exe instead of wscript.exe, because the latter will display a popup when a syntax error occurs, and the process will not terminate before you click that popup away.

Wait until program starts

I was wondering if there is any better way to wait until program starts before interacting with it?
Right now I'm using sleep which isn't exactly great.
Just to be clear I don't want to wait until program is finished (terminated) just to wait until it starts to be able to interact with it.
my code:
set MyShell = WScript.CreateObject("WScript.Shell")
MyShell.Run "<path to my exe app>"
WScript.Sleep 4000
MyShell.AppActivate "<my app name>"
MyShell.SendKeys "{TAB}"
...

How to write a VB task script that checks when a file is no longer being used?

Is there an event that triggers when a particular file, which was being used by some process, is no longer being used? If there is no event, is there another way to detect this in a way that can trigger a task?
Try this code:
Dim file
Dim app
file = "C:\Test\file.xlsx"
app = "notepad.exe"
On Error Resume Next
Do
CreateObject("Scripting.FileSystemObject").MoveFile file, file
If Err <> 70 Then Exit Do ' 70 - Access denied
Err.Clear
WScript.Sleep 1
Loop
CreateObject("WScript.Shell").Run app
Once being launched the script waits until the file is free, then runs the app and exits.

How Can I make A Shorter Delay in CMD (XP)

Im using ping to create a delay in my batch file, but there seems to be quite a big limit on how short you can make the ping delay.
ping -n 1 -w 1 1.1.1.1
this will wait for maybe 500ms
ping 127.0.0.1
this will wait for maybe 100ms
So is there a way to get an even smaller delay?
This is on XP, so "timeout" isnt enabled
You can do this in XP with Windows scripting, by creating a VBScript file millisleep.vbs that looks like this (with decent error checking built in):
if wscript.arguments.count <> 1 then
wscript.echo "millisleep.vbs: Not enough arguments"
wscript.quit 1
end if
delay = wscript.arguments(0)
if not isnumeric(delay) then
wscript.echo "millisleep.vbs: " & delay & " is not numeric"
wscript.quit 1
end if
wscript.sleep delay
Then, from your own script, you can use something like this to get a quarter-second delay:
cscript //nologo millisleep.vbs 250

.vbs to stop .bat being run past a certain time

I have a scheduled task which runs on my server to put 2 other desktop machines asleep at 2am.
I also have a .bat file scheduled to run at 1:40am on all of my systems to let me know of the impending shutdown operation and give me the option to cancel, standby immediately or close the window and allow it to proceed. Works fine on all systems except a Win7 Laptop.
Despite me having the task set not to run past it's scheduled time, if I open my laptop and it resumes from standby any time after 2am, the file will still run.
I wanted to try and create a workaround by scheduling a .vbs to launch the .bat instead. Along the lines of:
if Hour(Time) > 1 Then
wscript.quit
else if Hour(Time) = 1 Then
Set objShell = WScript.CreateObject("WScript.Shell")
objShell.Run "cancel-confirm-autostandby.bat C:\WINDOWS\system32\cmd.exe", 1
end if
wscript.quit
But the .bat file runs regardless, even testing now at 3am. If I try;
msgbox Hour(Time)
It returns a value of 3, so I don't understand this behavior. 3 is > than 1...
I have tried assigning the cutoff time (2am) to a varible tried Hour(Now) & Hour(Date).
Any suggestions welcome, thank you for reading...
Seem to have got it with
if Hour(Time) > 1 Then
wscript.quit
else if Hour(Time) = 1 Then
Set objShell = WScript.CreateObject("WScript.Shell")
objShell.Run "cancel-confirm-autostandby.bat C:\WINDOWS\system32\cmd.exe", 1
end if
wscript.quit
End if
Don't know why it wouldn't work with just the second last "End if", but there you go...

Resources