WSH scripts and input redirection

  • Thread starter Thread starter Bill Stewart
  • Start date Start date
B

Bill Stewart

Hi all,

Suppose I have the following WSH 5.6 script (Test.js):

// START CODE
var tsinput = WScript.StdIn, tsoutput = WScript.StdOut;

while (! tsinput.AtEndOfStream)
tsoutput.WriteLine(tsinput.ReadLine());
// END CODE

All this script does is take each line of standard input and write it to
standard output. Suppose this script is saved as Test.js in the current
directory.

Why do I have to explicitly type "cscript" before the script name?
Cmd.exe shell session below:

C:\>type Test.js
var tsinput = WScript.StdIn, tsoutput = WScript.StdOut;

while (! tsinput.AtEndOfStream)
tsoutput.WriteLine(tsinput.ReadLine());

C:\>assoc .js
..js=JSFile

C:\>ftype JSFile
JSFile=%SystemRoot%\System32\CScript.exe "%1" %*

C:\>dir | Test.js
C:\Test.js(3, 1) (null): The handle is invalid.

C:\>dir | cscript Test.js

<output of dir command here>
....

C:\>_

This behavior is problematic because if Test.js doesn't sit in the
current directory, you have to specify its path.

It's almost like cmd.exe doesn't create the stdin and stdout streams
when executing a WSH script via its file association, even when CScript
is the default host (which is the case here).

Can anyone explain this behavior?

Thanks
 
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Bill said:
Hi all,

Suppose I have the following WSH 5.6 script (Test.js):

// START CODE
var tsinput = WScript.StdIn, tsoutput = WScript.StdOut;

while (! tsinput.AtEndOfStream)
tsoutput.WriteLine(tsinput.ReadLine());
// END CODE

All this script does is take each line of standard input and write it to
standard output. Suppose this script is saved as Test.js in the current
directory.

Why do I have to explicitly type "cscript" before the script name?
Cmd.exe shell session below:

WSH uses the WScript engine to interpret .js files by default, and as
WScript is a GUI-orientated engine, all handles to StdIn are invalid.

If you want to set the CScript engine to be the default (as well as some
other options), see: CScript /?

HTH

Adam Piggott, Proprietor, Proactive Services (Computing).
http://www.proactiveservices.co.uk/

Please replace dot invalid with dot uk to email me.
Apply personally for PGP public key.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (MingW32)

iD8DBQFDzpfc7uRVdtPsXDkRAkW/AJ9qkWy3qr112yXi3xqRMTRvsh6obACeO3fG
VZGH9ClQMCxoZ4hjhAle/7U=
=N4rz
-----END PGP SIGNATURE-----
 
Adam said:
WSH uses the WScript engine to interpret .js files by default, and as
WScript is a GUI-orientated engine, all handles to StdIn are
invalid.

Hi Adam,

I'm aware that the StdIn and StdOut properties of the WScript object are
unavailable when a script is run by wscript.exe. I think you missed the
part of my post where I said:
It's almost like cmd.exe doesn't create the stdin and stdout streams
when executing a WSH script via its file association, even when
CScript is the default host (which is the case here).

In other words, CScript is my default host. (Read the output of my
cmd.exe session carefully.)
 
Bill said:
Hi Adam,

I'm aware that the StdIn and StdOut properties of the WScript object
are unavailable when a script is run by wscript.exe. I think you
missed the part of my post where I said:


In other words, CScript is my default host. (Read the output of my
cmd.exe session carefully.)


(note: personal opinion follows, not an explanation,)

Even if an MS dev on the scripting team (at a pretty low ebb staffing-wise
in the new .Net era) where to provide an explanation of console mode bad
behavior, there would not likely be any fix/update to change reality. The
ActiveX scripting hosts/engines have been in 'sustained engineering' mode
(critical bug fixes only) ever since .Net released. Even if the cause were
in cmd.exe, it seems to me that a bug fix there is also a pretty remote
possibility.

Now, if you dabble with MSH (Monad) and can demonstrate bad behavior over
there, you might have a chance for a (distant future) fix in that scenario.
MSH traffic is all over in the *.windows.server.scripting group, at least
for now during the (long) beta...
 
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Bill said:
Hi Adam,

I'm aware that the StdIn and StdOut properties of the WScript object are
unavailable when a script is run by wscript.exe. I think you missed the
part of my post where I said:

Doh, sorry Bill! I must have completely glazed over 8-)

I'm seeing just what you are, but it's still definitely calling
CScript.exe. Using Sysinternal's ProcExp it does seem that the command line
is different for the two different methods...I'll have another look when
I've got time. An interesting conundrum!


Cheers
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.2 (MingW32)

iD8DBQFDz3Vg7uRVdtPsXDkRAkXAAJ9O2dnVmTC+VN0RChgF0PskZAIJ5gCdHFMR
mgn9hsXcHu0Od6fUjvKwofg=
=l9/r
-----END PGP SIGNATURE-----
 
Michael said:
(note: personal opinion follows, not an explanation,)

Even if an MS dev on the scripting team (at a pretty low ebb staffing-wise
in the new .Net era) where to provide an explanation of console mode bad
behavior, there would not likely be any fix/update to change reality. The
ActiveX scripting hosts/engines have been in 'sustained engineering' mode
(critical bug fixes only) ever since .Net released. Even if the cause were
in cmd.exe, it seems to me that a bug fix there is also a pretty remote
possibility.

Hi Michael,

Thanks for the reply. I suspected that this is the case. I just wanted
to verify that the behavior I'm seeing is predictable (and IMHO, not
correct).
Now, if you dabble with MSH (Monad) and can demonstrate bad behavior over
there, you might have a chance for a (distant future) fix in that scenario.
MSH traffic is all over in the *.windows.server.scripting group, at least
for now during the (long) beta...

Indeed...hence the reason I originally cross-posted this to
..server.scripting.

Right now, it looks like MSH (I'm running beta 3) doesn't like a WSH
script on the right hand side of a pipe...MSH output below:

MSH C:\> get-content Test.js
var tsinput = WScript.StdIn, tsoutput = WScript.StdOut;

while (! tsinput.AtEndOfStream)
tsoutput.WriteLine(tsinput.ReadLine());
MSH C:\> get-childitem | Test.js
Program 'Test.js' failed to execute: The specified executable is not a
valid Win32 application.
At line:1 char:23
+ get-childitem | Test.js <<<< .
At line:1 char:1
+ g <<<< et-childitem | Test.js

It works if I say "get-childitem | cscript Test.js", but that approach
ends up having the same problem as cmd.exe: If Test.js isn't in the
current directory, I have to specify its path.
 
A very late response on this, but it's an interesting issue, and the problem
is shell execution, _not_ WSH - Perl has the same problem if you don't
"wrap" Perl scripts with a batch file.


And it unfortunately would take a change in cmd.exe. :(

The problem is that anything which is not directly an executable (or a batch
file that is directly invoked) is seen by cmd.exe as a "document" - that's
why when you do

file.vbs arga argb

the resulting running commandline as found by ProcExplorer or the XP+
Win32_Process Commandline property suddenly looks like the document handler
expansion from the registry for a VBScript file. In the process, apparently
the input pipe is broken. The same thing happens if you have Python or Perl
installed on a system and try to directly invoke an stdin-reading script
foir one of those; input breaks.

I usually fix this by creating a "shadow script". You can see the similar
thing Perl does if you look at the batch files in Perl\bin on a Windows
system. For example, when you run encode-base64, you end up invoking
encode-base64.bat; the critical line in encode-base64.bat for WinNT family
systems is this:
perl -x -S %0 %*

In fact, the exact same line appears in all of the Perl wrappers.

Basically the same thing works for any WSH script; I get a little fancier to
make sure that directories with embedded spaces will be handled. If I have 3
scripts I want to use with correct stdin handling, call them foo.vbs,
bar.js, and baz.wsf, I simply create 3 cmd scripts that live in the same
folder as the script they wrap, with the following content:

@::foo.cmd
@cscript "%~dpn0.vbs" %*

@::bar.cmd
@cscript "%~dpn0.js" %*


@::baz.cmd
@cscript "%~dpn0.wsf" %*
 
Alex said:
A very late response on this, but it's an interesting issue, and the problem
is shell execution, _not_ WSH - Perl has the same problem if you don't
"wrap" Perl scripts with a batch file.

Hi Alex,

Thanks for weighing in on this issue, even if it won't make a difference
and actually get it fixed. :) (FWIW, MSH has the exact same bug, the
last time I checked.)
And it unfortunately would take a change in cmd.exe. :(

The problem is that anything which is not directly an executable (or a batch
file that is directly invoked) is seen by cmd.exe as a "document" - that's
why when you do

file.vbs arga argb

the resulting running commandline as found by ProcExplorer or the XP+
Win32_Process Commandline property suddenly looks like the document handler
expansion from the registry for a VBScript file. In the process, apparently
the input pipe is broken. The same thing happens if you have Python or Perl
installed on a system and try to directly invoke an stdin-reading script
foir one of those; input breaks.

Yes, I've noticed this (IMHO broken, buggy, incorrect, etc.) behavior
from cmd.exe. The interesting point is your comment that "apparently the
input pipe is broken." It seems so, but why? My suspicion is that
cmd.exe is doing something wrong when it creates the process, but I've
been unable to figure out exactly what (admittedly, I haven't spent that
much time on it).

However, I should point out that redirection to WSH scripts works just
fine in 4NT. (I rarely use cmd.exe interactively, so it took me a while
to notice that this is broken in cmd.exe.)
 
Bill said:
Bill Stewart wrote:

(FWIW, MSH has the exact same bug, the last time I checked.)

Correction: MSH doesn't have the exact same bug. MSH just doesn't like a
non-executable on the RHS of a pipe. Different problem.
 
Bill Stewart said:
Correction: MSH doesn't have the exact same bug. MSH just doesn't like a
non-executable on the RHS of a pipe. Different problem.

Explain what you mean?

It looks like an extension of the same problem - it works fine if the script
is wrapped, if not, instead of dumping out an input pipe error it pops up
the script in a separate window, prompting for input. :|
 
Alex said:
Explain what you mean?

It looks like an extension of the same problem - it works fine if the script
is wrapped, if not, instead of dumping out an input pipe error it pops up
the script in a separate window, prompting for input. :|

Ah. I haven't tested this extensively, but MSH is returning Win32 error
193 ("<whatever> is not a valid Win32 application"). Here's what I mean:

MSH C:\> get-content Test.js
var tsinput = WScript.StdIn, tsoutput = WScript.StdOut;

while (! tsinput.AtEndOfStream)
tsoutput.WriteLine(tsinput.ReadLine());
MSH C:\> get-childitem | Test.js
Program 'Test.js' failed to execute: The specified executable is not a
valid Win32 application.
At line:1 char:23
+ get-childitem | Test.js <<<< .
At line:1 char:1
+ g <<<< et-childitem | Test.js

This is a bit different than error 6 ("The handle is invalid"). MSH's
behavior, while IMHO not correct, at least makes sense at a beta stage:
The command at the beginning of the RHS of the pipe has to be an
executable (i.e., not a file that gets executed indirectly via an
association). Cmd.exe's behavior seems to be broken in a different manner.
 
Yeah, I'm seeing this too. This is worth posting as a bug in
windows.server.scripting with [MSH] in the title.

Even if it is going to mimic the behavior of cmd.exe for "compatibility"
reasons, the error message is misleading - it points away from the broken
input pipe problem.

It _does_ "work natively" if you explicitly run it with cscript as the host,
of course.
 
.... The interesting point is your comment that "apparently the input pipe
is broken." It seems so, but why? My suspicion is that cmd.exe is doing
something wrong when it creates the process, but I've been unable to
figure out exactly what (admittedly, I haven't spent that much time on
it).

It's actually performing a ShellExecute() if the item is not "native" -
meaning if it isn't an executable or a cmd/bat file. Thus cmd.exe's
dependency on Shell32.dll and the fact that it always expands names for
things like this using the relevant registered handler.

This just "describes" the details of the bug, of course. It's ironic because
from what I can see via quick checking using Dependency Walker and Regmon on
4NT, it goes through almost precisely the same process as cmd.exe. It just
happens to deal with passing on the input stream the right way, whether
because it is doing an extra step of "hooking up" the input stream, or is
making sure handles get duplicated before the command is invoked.

If it is the second one, there is a chance that it might be possible to
"fix" this in MSH, since it has to do command parsing and could just as
easily perform the expansion.
However, I should point out that redirection to WSH scripts works just
fine in 4NT. (I rarely use cmd.exe interactively, so it took me a while to
notice that this is broken in cmd.exe.)

I suspect that
 
Alex said:
It's actually performing a ShellExecute() if the item is not "native" -
meaning if it isn't an executable or a cmd/bat file. Thus cmd.exe's
dependency on Shell32.dll and the fact that it always expands names for
things like this using the relevant registered handler.

Are you certain that it's really using ShellExecute? When I use an API
monitor, watch cmd.exe and search for Test.js (the "program" for the RHS
of the pipe), I see CreateProcess (which of course fails, because a
script isn't a Win32 executable), and then I see another CreateProcess
with cscript and Test.js as an argument. (Of course, I could be
"reading" it wrong...)
This just "describes" the details of the bug, of course. It's ironic because
from what I can see via quick checking using Dependency Walker and Regmon on
4NT, it goes through almost precisely the same process as cmd.exe. It just
happens to deal with passing on the input stream the right way, whether
because it is doing an extra step of "hooking up" the input stream, or is
making sure handles get duplicated before the command is invoked.

If it is the second one, there is a chance that it might be possible to
"fix" this in MSH, since it has to do command parsing and could just as
easily perform the expansion.

Yes, I came to basically the same conclusion...
 
Bill Stewart said:
Are you certain that it's really using ShellExecute? When I use an API
monitor, watch cmd.exe and search for Test.js (the "program" for the RHS
of the pipe), I see CreateProcess (which of course fails, because a script
isn't a Win32 executable), and then I see another CreateProcess with
cscript and Test.js as an argument. (Of course, I could be "reading" it
wrong...)

What API monitoring tool are you using? I'm curious because I've had little
luck with tracing the entire process for anything using Dependency Walker's
profiling.

I do not have solid proof that ShellExecute is used, and what you've seen
makes it look makes it very questionable. A ShellExecute call should
eventually result in a CreateProcess, but I think you would have seen the
earlier ShellExecute invocation if that was really happening.
 
Alex said:
What API monitoring tool are you using? I'm curious because I've had little
luck with tracing the entire process for anything using Dependency Walker's
profiling.
http://www.rohitab.com/apimonitor/

I do not have solid proof that ShellExecute is used, and what you've seen
makes it look makes it very questionable. A ShellExecute call should
eventually result in a CreateProcess, but I think you would have seen the
earlier ShellExecute invocation if that was really happening.

Yes, I do think that a ShellExecute probably eventually results in a
call to CreateProcess, but I'm not convinced that cmd.exe uses
ShellExecute directly.

If I'm reading the API monitor's output correctly, it looks like cmd.exe
first tries to call CreateProcess with the script file's name, which of
course fails. It looks like cmd.exe then determines the .js file
association and calls CreateProcess again with cscript.exe as the
executable name and the script file as its parameter (which looks
correct, as far as it goes). I'm not sure where in this process that the
input pipe gets broken, but I suspect that it's in the second call to
CreateProcess.
 
Bill Stewart said:

I like this tool; I just spent an hour playing with it, and I think it shows
us EXACTLY what is happening.
Yes, I do think that a ShellExecute probably eventually results in a call
to CreateProcess, but I'm not convinced that cmd.exe uses ShellExecute
directly.

I am retracting my earlier suggestion that this is what is happening. Based
on items below, I don't see any reason for the call; it doesn't really save
much. It might happen, but it could just as easily be cmd.exe directly using
the registry expansion and then neglecting to try passing handles to the new
process.
If I'm reading the API monitor's output correctly, it looks like cmd.exe
first tries to call CreateProcess with the script file's name, which of
course fails.

Yep. And if you look at the following extract showing two calls - the first
made calling the script directly, the second invoking it explicitly with
cscript - you can see that bInheritHandles is 0x0 (false) for the
creation-by-name case.

lpApplicationName: "C:\WINDOWS\System32\CScript.exe"
lpCommandLine: ""C:\WINDOWS\System32\CScript.exe" "c:\bin\scripts\rvs.vbs" "
bInheritHandles:0x0

lpApplicationName: "c:\windows\system32\cscript.exe"
lpCommandLine: "cscript rvs.vbs"
bInheritHandles:0x1

It looks like cmd.exe then determines the .js file
association and calls CreateProcess again with cscript.exe as the
executable name and the script file as its parameter (which looks correct,
as far as it goes). I'm not sure where in this process that the input pipe
gets broken, but I suspect that it's in the second call to CreateProcess.

Yeah - that bInheritHandles:0x0 proves you're right, that it does happen
during the second call. It also confirms that whatever the reason, cmd.exe
itself is explicitly saying to NOT inherit the handles.
 
Back
Top