Hi. I have successfully gotten my x64 application to launch a VBScript with parameters using the code below. I was pushed in this direction due to the TScriptControl object not being available in 64 bit applications. What I am looking for here is a suggestion to get better return information from the script e.g. if the script fails due to a syntax error, return that error like you would see it on the command line. TScriptControl had this functionality built in.
{code}
// CommandLine format: 'cscript.exe "C:\Temp\ScriptName.vbs" "parm1" "parm2" .....
// DefaultDir would match the path the VBS file is in
If CreateProcess(Nil, PChar(CommandLine), Nil, Nil, False, 0, Nil, PChar(DefaultDir), si, pi) Then Begin
WaitForSingleObject(pi.hProcess, INFINITE);
GetExitCodeProcess(pi.hProcess,wResult);
Result := (wResult > 0);
End Else Begin
Raise Exception.CreateFmt(''System error: %s', [SysErrorMessage(GetLastError)]);
End;
{code}
This works fine but has no way to retrieve error messages should the script fail due to a programmer or like type of error which would cause the script to stop in a less than graceful manner. By default the value for wResult will be 0 in that case, but I have nothing else to return to the user other than the fact that the script failed. BTW: If the script fails due to a syntax or like type error, it does NOT go into the ELSE portion and raise the exception. That only occurs if the command line supplied is
invalid for some reason (e.g. the script doesn't exist.)
I look forward to community input on this.
Edited by: Rod Sherer on Sep 19, 2014 12:35 PM
Vote for best question.
Score: 0
# Vote: 0
Date Posted: 19-Sep-2014, at 12:35 PM EST
From: Rod Sherer
Re: Running VBScripts through Winapi [Edit] [Edit] [Edit]
> {quote:title=Remy Lebeau (TeamB) wrote:}{quote}
> Wojciech wrote:
>
> > Excuse me, what is TScriptControl anyway?
>
> A Delphi-imported wrapper of Microsoft's "Windows Script Control" COM object.
>
> > My help (XE4) does not say a word about it, DocWiki neither.
>
> That is because it is not an Embarcadero-provided unit/component, it is one
> that is auto-generated when importing the WSC object from a Type Library.
>
> --
> Remy Lebeau (TeamB)
Thank you Remy for your explanation. I guess there is no sense in getting to know WSC as Microsoft abandoned it in favour of IActiveScript.
This topic is interesting and rarely covered.
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 25-Nov-2014, at 12:27 AM EST
From: Wojciech Pomianowski
Re: Running VBScripts through Winapi [Edit] [Edit] [Edit]
Wojciech wrote:
> Excuse me, what is TScriptControl anyway?
A Delphi-imported wrapper of Microsoft's "Windows Script Control" COM object.
> My help (XE4) does not say a word about it, DocWiki neither.
That is because it is not an Embarcadero-provided unit/component, it is one
that is auto-generated when importing the WSC object from a Type Library.
--
Remy Lebeau (TeamB)
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 24-Nov-2014, at 2:10 PM EST
From: Remy Lebeau (TeamB)
Re: Running VBScripts through Winapi [Edit] [Edit] [Edit]
{code}
unit ActiveScriptDemoUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, AscrLib, ActiveX, ComObj, StdCtrls, Automation;
const
CLSID_VBScript: TGUID = '{b54f3741-5b07-11cf-a4b0-00aa004a55e8}';
CLSID_JScript: TGUID = '{f414c260-6ac0-11cf-b6d1-00aa00bbbb58}';
SCRIPT_E_REPORTED = HRESULT($80020101);
type
TForm3 = class(TForm, IActiveScriptSite, IActiveScriptSiteWindow)
Memo1: TMemo;
Button1: TButton;
RadioButton1: TRadioButton;
RadioButton2: TRadioButton;
ListBox1: TListBox;
ComboBox1: TComboBox;
Edit1: TEdit;
CheckBox1: TCheckBox;
CheckBox2: TCheckBox;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
FScript: IActiveScript;
FParse: IActiveScriptParse64;
{ IActiveScriptSite }
function GetLCID(out plcid: LongWord): HResult; stdcall;
function GetItemInfo(pstrName: PWideChar;
dwReturnMask: LongWord;
out ppiunkItem: IUnknown;
out ppti: IUnknown): HResult; stdcall;
function GetDocVersionString(out pbstrVersion: WideString): HResult; stdcall;
function OnScriptTerminate(var pvarResult: OleVariant;
var pexcepinfo: EXCEPINFO): HResult; stdcall;
function OnStateChange(ssScriptState: tagSCRIPTSTATE): HResult; stdcall;
function OnScriptError(const pscripterror: IActiveScriptError): HResult; stdcall;
function OnEnterScript: HResult; stdcall;
function OnLeaveScript: HResult; stdcall;
{ IActiveScriptSiteWindow }
function GetWindow(out phwnd: HWND): HResult; stdcall;
function EnableModeless(fEnable: Integer): HResult; stdcall;
public
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
{ TForm3 }
procedure TForm3.Button1Click(Sender: TObject);
var
Info: EXCEPINFO;
Code: WideString;
begin
Code := Memo1.Text;
OleCheck(FParse.ParseScriptText(PWideChar(Code), nil, nil, nil, 0, 0,
SCRIPTITEM_ISVISIBLE or SCRIPTITEM_ISPERSISTENT, nil, Info));
OleCheck(FScript.SetScriptState(SCRIPTSTATE_CONNECTED));
OleCheck(FScript.SetScriptState(SCRIPTSTATE_DISCONNECTED));
end;
function TForm3.EnableModeless(fEnable: Integer): HResult;
begin
Result := S_OK;
end;
procedure TForm3.FormCreate(Sender: TObject);
begin
CoInitializeEx(nil, COINIT_APARTMENTTHREADED);
OleCheck(CoCreateInstance(CLSID_JScript, nil, CLSCTX_INPROC_SERVER, IID_IActiveScript, FScript));
OleCheck(FScript.SetScriptSite(Self as IActiveScriptSite));
OleCheck(FScript.AddNamedItem('Application', SCRIPTITEM_ISVISIBLE or SCRIPTITEM_ISSOURCE));
if Supports(FScript, IActiveScriptParse64, FParse) then
OleCheck(FParse.InitNew);
end;
function TForm3.GetDocVersionString(out pbstrVersion: WideString): HResult;
begin
pbstrVersion := '1.0';
Result := S_OK;
end;
function TForm3.GetItemInfo(pstrName: PWideChar; dwReturnMask: LongWord; out ppiunkItem,
ppti: IInterface): HResult;
begin
Result := S_FALSE;
if SameText('Application', pstrName) then
begin
if dwReturnMask and SCRIPTINFO_IUNKNOWN <> 0 then
begin
ppiunkItem := TAutoObjectDispatch.Create(TApplicationWrapper.Connect(Application)) as IInterface;
Result := S_OK;
Exit;
end;
if dwReturnMask and SCRIPTINFO_ITYPEINFO <> 0 then
begin
Result := TYPE_E_ELEMENTNOTFOUND;
Exit;
end;
end;
end;
function TForm3.GetLCID(out plcid: LongWord): HResult;
begin
plcid := GetThreadLocale;
Result := S_OK;
end;
function TForm3.GetWindow(out phwnd: HWND): HResult;
begin
phwnd := Handle;
Result := S_OK;
end;
function TForm3.OnEnterScript: HResult;
begin
Result := S_OK;
end;
function TForm3.OnLeaveScript: HResult;
begin
Result := S_OK;
end;
function TForm3.OnScriptError(const pscripterror: IActiveScriptError): HResult;
var
Info: EXCEPINFO;
Ctxt: DWORD;
Line: Cardinal;
Pos: Longint;
begin
if Succeeded(pscripterror.GetExceptionInfo(Info)) and
Succeeded(pscripterror.GetSourcePosition(Ctxt, Line, Pos)) then
ShowMessage(Format('%s: Position: (%d, %d)', [Info.bstrDescription, Line, Pos]));
Result := SCRIPT_E_REPORTED;
end;
function TForm3.OnScriptTerminate(var pvarResult: OleVariant; var pexcepinfo: EXCEPINFO): HResult;
begin
Result := S_OK;
end;
function TForm3.OnStateChange(ssScriptState: tagSCRIPTSTATE): HResult;
begin
Result := S_OK;
end;
end.
{code}
This is from the example project taken from the blog at http://blogs.embarcadero.com/abauer/2007/06/13/36013
Edited by: Rod Sherer on Sep 24, 2014 12:33 PM I found a second reference to IActiveScriptParse, but updating that to IActiveParse64 didn't help either. I have updated the code above.
Edited by: Rod Sherer on Sep 25, 2014 7:47 AM
Correction: The line it is failing on is
OleCheck(FScript.SetScriptState(SCRIPTSTATE_CONNECTED));
This is in TForm3.Button1Click and it is the call to SetScriptState which is raising the error.
Edited by: Rod Sherer on Sep 25, 2014 8:26 AM
It is the state that is being set which causes the issue. Trying to set SCRIPTSTATE_CONNECTED or SCRIPTSTATE_DISCONNECTED raises an exception. Apparently these states are not compatible with x64, which is unfortunate because I believe they are required to share objects between the application and the script. This still gives me much more script control than I had though, so this is still very useful. I just need to use SCRIPTSTATE_STARTED and SCRIPTSTATE_CLOSED instead and I can run VBScript or JScript. N
ow I just need to make sure I can set parameter values for the script, since I can't share objects.
Edited by: Rod Sherer on Sep 25, 2014 12:26 PM
Once I got the sample project implemented in 64 bit, I tried to implement my own script executer object which would receive information about a script, parse it, run it, and return any feedback. I was disappointed to find out that the active script site and window must piggyback on another class, so I just piggybacked them on my "job" class, which was going to create and call the original object as needed. I don't even think I need the site window as there will be non visible feedback. All feedback is log
ged to a database. So with that thought in mind, I only piggybacked the IActiveScriptSite. It appears to be reliant upon the object it is riding on to have implemented the following functions though.
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
I found QueryInterface on the TCustomForm object and mimicked it to always return 0. (I should mention that my application has no forms. It is strictly a command line executable with no feedback other than database entries (log records in the database). The other two I could not find anywhere. Can I just have them return 0 at all times as well? What is the purpose of these methods? They are declared on IInterface.
I am beginning to think that I am in way over my head.
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 25-Sep-2014, at 12:34 PM EST
From: Rod Sherer
Re: Running VBScripts through Winapi [Edit] [Edit] [Edit]
Rod wrote:
> However, adding 64 bit to the Target Platforms and changing
> the FParse object from IActiveScriptParse to IActiveScriptParse64
> didn't seem to be enough. It is raising an error from the KERNELBASE.DLL
Please show your actual code, and indicate which line of code is actually
crashing.
--
Remy Lebeau (TeamB)
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 24-Sep-2014, at 10:14 AM EST
From: Remy Lebeau (TeamB)
Re: Running VBScripts through Winapi [Edit] [Edit] [Edit]
Let me switch gears now that I have had a chance to look at the IActiveScript example that Allen Bauer supplied in his blog. I really like the functionality that this promises. Being able to have access to Delphi objects is a huge plus for me. However, adding 64 bit to the Target Platforms and changing the FParse object from IActiveScriptParse to IActiveScriptParse64 didn't seem to be enough. It is raising an error from the KERNELBASE.DLL. What more do I need to do to get his example project to run as a 6
4 bit project?
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 24-Sep-2014, at 9:05 AM EST
From: Rod Sherer
Re: Running VBScripts through Winapi [Edit] [Edit] [Edit]
Rod wrote:
> So I thought that if the script failed for any reason, syntax or
> otherwise, that I would get a return value of 0 from
> GetExitCodeProcess.
Don't make assumptions like that. Test it and find out for sure. For instance,
you could have run cscript.exe in a .bat file and have it echo the %ERRORLEVEL%
value after cscript exited.
> If I have a programmer/syntax error in my VBScript, I have a return
> value of 1. If I have another type of error, e.g. an insert fails returning
> a database error (Oracle in this case), then I get a 0. So in my code
> example, the check versus zero is invalid. This seems confusing to me
> as I would expect a failure to return the same value regardless of what
> stopped the script from running correctly
Not really. There is a difference between a failure parsing the script so
it can't run at all versus a failure due to a logic error while running the
script.
> My script is not using On Error Resume Next, so any errors just stop the
> script from running.
But the script ran, thus cscript did its job, so it returned 0 for success.
If the script could not be parsed, cscript could not run the script, so
it returned 1 for failure.
The result of the running script is irrelevant to the exit code. cscript
can only report whether it was able to run the script or not.
All the more reason why you need to switch to ActiveScript, so that you can
differentiate between parser errors versus runtime errors.
> I'm a little confused as to why ReadPipe is being assigned to Input and
not Output.
It should not be. That is a bug in your code.
> I am guessing because it is input from the process/scripts perspective, not
> the applications? I'm also confused as to how I am supposed to assign two
> pipes to three possible elements (input, output, error).
You are calling CreatePipe() only once, so you only have one pipe, not two
pipes. You have two HANDLES to one pipe - one handle is used for writing
data into the pipe, and the other handle is for reading data from the pipe.
You give one handle to the process/script, and you use the other handle
in your code.
If you want to redirect hStdInput, you need to assign the pipe's READING
handle, and then use its WRITING handle to send data to the process.
If you want to redirect hStdOutput or hStdError, you need to assign the pipe's
WRITING handle, and then use its READING handle to receive data from the
process.
If you want to redirect multiple items, you need multiple pipes, and thus
multiple calls to CreatePipe(). So, if you were to redirect all three items,
you would make 3 calls to CreatePipe(), resulting in 6 handles, 2 for each
pipe.
--
Remy Lebeau (TeamB)
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 23-Sep-2014, at 4:29 PM EST
From: Remy Lebeau (TeamB)
Re: Running VBScripts through Winapi [Edit] [Edit] [Edit]
I'm really not sure I'm approaching this correctly, but I've been taking my best guesses and I'm getting inconsistent results.
So I thought that if the script failed for any reason, syntax or otherwise, that I would get a return value of 0 from GetExitCodeProcess. This is not the case. If I have a programmer/syntax error in my VBScript, I have a return value of 1. If I have another type of error, e.g. an insert fails returning a database error (Oracle in this case), then I get a 0. So in my code example, the check versus zero is invalid. This seems confusing to me as I would expect a failure to return the same value regardless of
what stopped the script from running correctly (unless I specifically return a value.) My script is not using On Error Resume Next, so any errors just stop the script from running.
Since it looks like the ReadPipe is the one I am using to pull out data, I tried assigning that to Start.hStdError instead of Start.hStdInput. I got the same results. I'm a little confused as to why ReadPipe is being assigned to Input and not Output. I am guessing because it is input from the process/scripts perspective, not the applications? I'm also confused as to how I am supposed to assign two pipes to three possible elements (input, output, error). --I tried assigning the ReadPipe object to both Inpu
t and Error and that just locked things up.-- Edit: Assigning ReadPipe to bothin input and error doesn't seem to have any detrimental or beneficial effect. I must have tried assigning it to output and error. Doh!
I have done next to none when it comes to WinApi programming. I really appreciate the prompt, informative responses so far and the patience.
Edited by: Rod Sherer on Sep 23, 2014 1:06 PM
Edited by: Rod Sherer on Sep 23, 2014 1:08 PM
Vote for best answer.
Score: 0
# Vote: 0
Date Posted: 23-Sep-2014, at 1:09 PM EST
From: Rod Sherer
Re: Running VBScripts through Winapi [Edit] [Edit]
Rod wrote:
> So based on your comment, can I assume that Application.ProcessMessages
> has nothing to do with the reason the pipe was only returning the "header"
> message and not the actual error?
Yes.
> I really think I am close with that approach, I just can't seem to get
> the actual error message out.
My guess would be that CScript is using STDERR to output the error info.
The code you are using is only reading from STDOUT.
--
Remy Lebeau (TeamB)