How to run multiple TCL scripts in same tclsh window? - vbscript

Description
I work with VBScript
From VBScript I call my first TCL script "name1"
After the script "name1" is finished, I continue to work with VBScript
After several VBScript functions I call my second TCL script "name2" and this script is a child process of the script "name1". The script "name2" have to use all variables from the script "name1"
Current result
My scripts "name1" and "name2" are executing in the different tclsh windows
As a result, the "name2" is not familiar with the variables of the "name1"
Expected result
"name1" and "name2" are executed in the same tclsh window
Comment
I tried to use these commands but I don't know its syntax in the VBScript
Tcl_Create tclHandler,
TCL_Eval Status, tclHandler,
It would be nice to have an example how to use these commands in the VBScript, or any other
Thanks
VBScript:
#$language = "VBScript"
#$interface = "1.0"
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8
crt.Screen.Synchronous = True
Sub Main
dim shell
set shell=createobject("wscript.shell")
shell.run "tclsh e:\RunTCL\name1.tcl"
crt.Sleep 10000 ' or any VBScript commands
shell.run "tclsh e:\RunTCL\name2.tcl"
End Sub
name1.tcl
package req SpirentTestCenter
set hProject [stc::create project]
set hTxPort [stc::create port -under $hProject -location //192.168.0.243/10/17 -useDefaultHost False]
name2.tcl
set hRxPort [stc::create port -under $hProject -location //192.168.0.243/10/25 -useDefaultHost False]
New code with tcl84.dll and its commands "TCL_Create tclHandler,":
#$language = "VBScript"
#$interface = "1.0"
Const ForReading = 1
Const ForWriting = 2
Const ForAppending = 8
crt.Screen.Synchronous = True
Sub Main
Dim wscriptTCL
tclHandler = 0
set wscriptTCL =CreateObject("C:\Tcl\bin\tcl84.dll")
TCL_Create tclHandler, wscriptTCL, 1
puts "Importing STC API"
TCL_Eval Status, tclHandler, "package req SpirentTestCenter"
puts "Creating API objects set"
TCL_Eval Status, tclHandler, "set hProject [stc::create project]"
puts "Connecting to STC ports"
TCL_Eval Status, tclHandler, "set hTxPort [stc::create port -under $hProject -location //10.110.10.243/8/9 -useDefaultHost False]"
src.Sleep 100000
TCL_Eval Status, tclHandler, "set hRxPort [stc::create port -under $hProject -location //10.110.10.243/8/10 -useDefaultHost False]"
As a result, I see this message: Error:ActiveX component can't create object 'c:\tcl\bin\tcl84.dll'
I can use 2 options:
Call the tcl file from my VB script
Call tcl commands from the VB script via dll
But nobody is working
Good news, it starts to work, however, I would prefer to execute separate TCL files by API
shell.run "tclsh"
crt.Sleep 10000
shell.AppActivate "tclsh"
shell.SendKeys("TCL command")

tcl84.dll is not something that provides a COM class. You cannot create a Tcl interpreter using the VBScript CreateObject call like this. VBScript does not have a mechanism to access exported functions directly from a DLL so you cannot run your Tcl code from VBScript except by executing a tclsh or wish process.
Each time you execute tclsh.exe scriptfile you are creating a new child process. It will run your script but when it exits the process is terminated and everything about it is lost unless you read the standard output or wrote information to a file to preserve it. So running a second script will of course have no knowledge of the first script.
One way to run two tcl scripts together is to create a script that uses the tcl source command to load both scripts into a single interpreter. Another method might be to read the output of the first script, parse out the values you need and pass these as command line arguments to the second. Or you could have the first script write a file of tcl commands that when sourced in the second process will update the variables you require.
Your final example is creating a single process and communicating via emulated keystrokes. This is really slow and error prone. There is a winsend package that can let you send messages to tcl interpreters from vbscript. There might be some others as part of TWAPI that can let you register the interpreter for external access.

Related

Launch an external exe and set timeout VBS [duplicate]

I am working on a script with vbscript, and I would like it to terminate itself after x number of minutes.
I was thinking something like grabbing the time when the script starts and then keeping the whole thing in a loop until the time is x number of minutes after the start time, but I need it to keep checking in the background, and not just wait until a loop is complete.
I want a message or something that notifies the user they took too long, which I can do myself.
Is there any way to keep track of the time in the background, or will it be a bit of a drawn-out process to determine it?
Re-launching the script with //T:xx as suggested by Ekkehard.Horner is probably your best option. Another, slightly different, approach could look like this:
Const Timeout = 4 'minutes
timedOut = False
If WScript.Arguments.Named.Exists("relaunch") Then
'your code here
Else
limit = DateAdd("n", Timeout, Now)
cmd = "wscript.exe """ & WScript.ScriptFullName & """ /relaunch"
Set p = CreateObject("WScript.Shell").Exec(cmd)
Do While p.Status = 0
If Now < limit Then
WScript.Sleep 100
Else
On Error Resume Next 'to ignore "invalid window handle" errors
p.Terminate
On Error Goto 0
timedOut = True
End If
Loop
End If
If timedOut Then WScript.Echo "Script timed out."
You'd still be re-launching the script, but in this case it's your script killing the child process, not the script interpreter.
Here is another short and elegant solution which allows to terminate both the script and the external executable ran asynchronously, via WScript.Timeout
Option Explicit
Dim oSmallWrapperWshExec
WScript.Timeout = 7
Set oSmallWrapperWshExec = New cSmallWrapperWshExec
' Some code here
MsgBox "Waiting timeout" & vbCrLf & vbCrLf & "You may close notepad manually and/or press OK to finish script immediately"
Class cSmallWrapperWshExec
Private oWshShell
Private oWshExec
Private Sub Class_Initialize()
Set oWshShell = CreateObject("WSCript.Shell")
With oWshShell
Set oWshExec = .Exec("notepad")
.PopUp "Launched executable", 2, , 64
End With
End Sub
Private Sub Class_Terminate()
On Error Resume Next
With oWshShell
If oWshExec.Status <> 0 Then
.PopUp "Executable has been already terminated", 2, , 64
Else
oWshExec.Terminate
.PopUp "Terminated executable", 2, , 64
End If
End With
End Sub
End Class
I appreciate all of the answers here, but they are more complicated than I wanted to get in to.
I was very surprised to find out that there is a way to do it built into WScript.
WScript.Timeout = x_seconds
cscript
Usage: CScript scriptname.extension [option...] [arguments...]
Options:
//B Batch mode: Suppresses script errors and prompts from displaying
//D Enable Active Debugging
//E:engine Use engine for executing script
//H:CScript Changes the default script host to CScript.exe
//H:WScript Changes the default script host to WScript.exe (default)
//I Interactive mode (default, opposite of //B)
//Job:xxxx Execute a WSF job
//Logo Display logo (default)
//Nologo Prevent logo display: No banner will be shown at execution time
//S Save current command line options for this user
**//T:nn Time out in seconds: Maximum time a script is permitted to run**
//X Execute script in debugger
//U Use Unicode for redirected I/O from the console
Update:
To help people who downvote a plain (and to the point) citation of cscript.exe's usage message (how can that be wrong?) to see the light through #PanayotKarabakalov's smoke screen:
The claim:
using //T switch not guarantee real time accuracy
that all 5 Echo command executed, even if the Sleep time between them
is 1.5 second and the //T is set to 4
The evidence:
The script is restarted via:
CreateObject("WScript.Shell").Run "WScript " & _
Chr(34) & WScript.ScriptFullName & _
Chr(34) & " /T:4", 0, False
which does not contain the host-specific //T (as opposed to the script-specific /T) switch.
The (counter) argument:
Whatever way you start the first instance of the script (//T or no //T), the second/relaunched instance will never have a time out and will always run to the bitter end.
If you still have doubts, change the invocation in P.'s script to
CreateObject("WScript.Shell").Run "WScript //T:4 " & _
and try it out.

Is there any method to detect whether STDIN has been redirected within VBscript?

I'm trying to process/filter input within a VBscript, but only if the input has been piped into the script. I don't want the script processing user/keyboard input. I'd like to code this as something like this:
stdin_is_tty = ...
if not stdin_is_tty then
...
input = WScript.StdIn.ReadAll
end if
Otherwise, the script will hang, waiting on user input when it executes WScript.StdIn.ReadAll (or even earlier if I test the stream with WScript.StdIn.AtEndOfStream).
In C#, I'd use:
stdin_is_tty = not System.Console.IsInputRedirected // NET 4.5+
The accepted answer for Q: "How to detect if Console.In (stdin) has been redirected?" shows how to build that result using Win32 calls via P/Invoke, for versions of NET earlier than NET 4.5. But I don't know of any way to translate that method into VBscript.
I've constructed a clumsy, partial solution using SendKeys to send an end-of-stream sequence into the scripts' keyboard buffer. But the solution leaves keys in the buffer if STDIN is redirected, which I can't clean up unless I know that STDIN was redirected... so, same problem.
I'd prefer to keep the script in one packaged piece, so I'd rather avoid a separate wrapping script or anything not available on a generic Windows 7+ installation.
Any brilliant ideas or workarounds?
EDIT: added copy of initial solution
I've added a copy of my improved initial solution here (admittedly, a "hack"), which now cleans up after itself but still has several negatives:
input = ""
stdin_is_tty = False
test_string_length = 5 ' arbitrary N (coder determined to minimize collision with possible inputs)
sendkey_string = ""
test_string = ""
for i = 1 to test_string_size
sendkey_string = sendkey_string & "{TAB}"
test_string = test_string & CHR(9)
next
sendkey_string = sendkey_string & "{ENTER}"
wsh.sendkeys sendkey_string ' send keyboard string signal to self
set stdin = WScript.StdIn
do while not stdin.AtEndOfStream
input = input & stdin.ReadLine
if input = test_string then
stdin_is_tty = True
else
input = input & stdin.ReadAll
end if
exit do
loop
stdin.Close
if not stdin_is_tty then
set stdin = fso.OpenTextFile( "CON:", 1 )
text = stdin.ReadLine
stdin.Close
end if
This solution suffers from the three problems:
leaving a visible trace at the command line (though now, just a single blank line which is low visibility)
possible collision of the test string (a set series of N [coder determined] TABs followed by a NEWLINE) with the first line of any redirected input causing a false positive redirection determination. Since the number of TABs can be modified, this possibility can be made arbitrarily low by the coder.
a race condition that if another window receives focus before the SendKeys portion is executed, the wrong window will receive the code string, leading to a false negative redirection determination. My estimate is that the possibility of this circumstance occurring is very low.
In short, no, but ...
I've tested everything i could think of and have not found a reasonable way to do it.
None of the properties/methods exposed by the TextStream wrappers retrieved with WScript.StdIn or fso.GetStdStream give enough information to determine if the input is redirected/piped.
Trying to obtain information from the behaviour/environment of a spawned process (how to create the executable is other story) is also unlikely to be useful because
WshShell.Execute always spawns the process with its input and output handles redirected
WshShell.Run creates a new process that does not inherit the handles of the current one
Shell.Application.ShellExecute has the same problem as WshShell.Run
So, none of these methods allow the spawned process to inherit the handles of the current process to check if they are redirected or not.
Using WMI to retrieve information from the running process does not return anything usable (well, HandleCount property for the process differs when there is a redirection, but it is not reliable)
So, not being able to determine from vbs code if there is a redirection, the remaining options are
Don't detect it: If the piped input must be present, behave as the more command and in all cases try to retrieve it
Indicate it: If the pipe input is not always required, use an argument to determine if the stdin stream needs to be read.
In my case, I usually use a single slash / as argument (for coherence with some of the findstr arguments that also use a slash to indicate stdin input). Then in the vbs code
If WScript.Arguments.Named.Exists("") Then
' here the stdin read part
End If
Check before: Determine if there is redirection before starting the script. A wrapper .cmd is needed, but with some tricks both files (.cmd and .vbs) can be combined into one
To be saved as .cmd
<?xml : version="1.0" encoding="UTF-8" ?> ^<!------------------------- cmd ----
#echo off
setlocal enableextensions disabledelayedexpansion
timeout 1 >nul 2>nul && set "arg=" || set "arg=/"
endlocal & cscript //nologo "%~f0?.wsf" //job:mainJob %arg% %*
exit /b
---------------------------------------------------------------------- wsf --->
<package>
<job id="mainJob">
<script language="VBScript"><![CDATA[
If WScript.Arguments.Named.Exists("") Then
Do Until WScript.StdIn.AtEndOfStream
WScript.StdOut.WriteLine WScript.StdIn.ReadLine
Loop
Else
WScript.StdOut.WriteLine "Input is not redirected"
End If
]]></script>
</job>
</package>
It is a .wsf file stored inside a .cmd. The batch part determines if the input is redirected (timeout command fails to get a console handle on redirected input) and pass the argument to the script part.
Then, the process can be invoked as
< inputfile.txt scriptwrapper.cmd input redirected
type inputfile.txt | scriptwrapper.cmd input piped
scriptwapper.cmd no redirection
While this is a convenient way to handle it, the invocation of the .wsf part from the .cmd, while being stable and working without problems, relies in an undocumented behaviour of the script host / cmd combination.
Of course you can do the same but with two separate files. Not as clean, but the behaviour is documented.

while running vba script through cmd prompt getting error [duplicate]

This question already has an answer here:
While Running the vba script i am getting error Microsoft VBScript runtime error: object required : 'DoCmd'
(1 answer)
Closed 8 years ago.
Hi Any one please help me..
while running the vba script i am getting error--object DoCmd need to create.
My script is given below..
ExecuteInsert
Sub ExecuteInsert()
Dim sheetPath
Dim dbs, DbFullName, acc
Set acc = CreateObject("Access.Application")
DbFullName = "D:\G\Diamond\FINAL MS-Access\Demo\MS-Access project.accdb"
Set dbs = acc.DBEngine.OpenDatabase(DbFullName, False, False)
dbs.Execute "Delete from TempRoadMap"
sheetPath = "C:\Users\270784\Desktop\CSPRV scheduled work - 2014 through 1-26-14.xlsx"
DoCmd.TransferSpreadsheet acImport, acSpreadsheetTypeExcel97, "TempRoadMap", sheetPath, True
MsgBox "Imported Sheet1 from " & sheetPath & " Successfully!"
dbs.Execute "Delete from RoadMap"
dbs.Execute "INSERT INTO [RoadMap] ( Release_Name,SPRF,SPRF_CC,Estimate_Type,PV_Work_ID,SPRF_Name,Estimate_Name,Project_Phase,CSPRV_Status,Scheduling_Status,Impact_Type,Onshore_Staffing_Restriction,Applications,Total_Appl_Estimate,Total_CQA_Estimate,Estimate_Total,Requested_Release,Item_Type,Path) SELECT [TempRoadMap.Release Name], [TempRoadMap.SPRF], [TempRoadMap.Estimate (SPRF-CC)],[TempRoadMap.Estimate Type],[TempRoadMap.PV Work ID],[TempRoadMap.SPRF Name],[TempRoadMap.Estimate Name],[TempRoadMap.Project Phase],[TempRoadMap.CSPRV Status],[TempRoadMap.Scheduling Status],[TempRoadMap.Impact Type],[TempRoadMap.Onshore Staffing Restriction],[TempRoadMap.Applications],[TempRoadMap.Total Appl Estimate],[TempRoadMap.Total CQA Estimate],[TempRoadMap.Estimate Total],[TempRoadMap.Requested Release],[TempRoadMap.Item Type],[TempRoadMap.Path] FROM [TempRoadMap] "
dbs.Close
MsgBox "Done"
End Sub
Assuming that by "...through cmd prompt..." you mean to imply that you are running this script as part of a VBScript file, here are the changes you would want to make:
DoCmd is a child of an Access.Application, and it can only be referenced globally in the scope of an Access database. To reference it in VBScript, you must explicitly use an Access.Application instance, which you have as variable acc. Modify the beginning of the DoCmd line like so:
acc.DoCmd.TransferSpreadsheet
VBScript won't recognize Access constants like acImport or acSpreadsheetTypeExcel97, so you will have to replace them with their actual values. A quick look through msdn reveals that acImport is 0 and acSpreadsheetTypeExcel97 doesn't exist, but acSpreadsheetTypeExcel8 (Excel 97 format) is 8. So now the DoCmd line looks like:
acc.DoCmd.TransferSpreadsheet 0, 8, "TempRoadMap", sheetPath, True
VBScript does not have MsgBox - this is only available through VBA. Instead, you can use Wscript.Echo. For example, the last line would be: WScript.Echo "Done"
If you still have issues after making those changes, please let me know with a comment below.
VBScript certainly does have msgbox and echo is not part of vbscript but of Windows Scripting Host.

Terminate vbscript after x minutes

I am working on a script with vbscript, and I would like it to terminate itself after x number of minutes.
I was thinking something like grabbing the time when the script starts and then keeping the whole thing in a loop until the time is x number of minutes after the start time, but I need it to keep checking in the background, and not just wait until a loop is complete.
I want a message or something that notifies the user they took too long, which I can do myself.
Is there any way to keep track of the time in the background, or will it be a bit of a drawn-out process to determine it?
Re-launching the script with //T:xx as suggested by Ekkehard.Horner is probably your best option. Another, slightly different, approach could look like this:
Const Timeout = 4 'minutes
timedOut = False
If WScript.Arguments.Named.Exists("relaunch") Then
'your code here
Else
limit = DateAdd("n", Timeout, Now)
cmd = "wscript.exe """ & WScript.ScriptFullName & """ /relaunch"
Set p = CreateObject("WScript.Shell").Exec(cmd)
Do While p.Status = 0
If Now < limit Then
WScript.Sleep 100
Else
On Error Resume Next 'to ignore "invalid window handle" errors
p.Terminate
On Error Goto 0
timedOut = True
End If
Loop
End If
If timedOut Then WScript.Echo "Script timed out."
You'd still be re-launching the script, but in this case it's your script killing the child process, not the script interpreter.
Here is another short and elegant solution which allows to terminate both the script and the external executable ran asynchronously, via WScript.Timeout
Option Explicit
Dim oSmallWrapperWshExec
WScript.Timeout = 7
Set oSmallWrapperWshExec = New cSmallWrapperWshExec
' Some code here
MsgBox "Waiting timeout" & vbCrLf & vbCrLf & "You may close notepad manually and/or press OK to finish script immediately"
Class cSmallWrapperWshExec
Private oWshShell
Private oWshExec
Private Sub Class_Initialize()
Set oWshShell = CreateObject("WSCript.Shell")
With oWshShell
Set oWshExec = .Exec("notepad")
.PopUp "Launched executable", 2, , 64
End With
End Sub
Private Sub Class_Terminate()
On Error Resume Next
With oWshShell
If oWshExec.Status <> 0 Then
.PopUp "Executable has been already terminated", 2, , 64
Else
oWshExec.Terminate
.PopUp "Terminated executable", 2, , 64
End If
End With
End Sub
End Class
I appreciate all of the answers here, but they are more complicated than I wanted to get in to.
I was very surprised to find out that there is a way to do it built into WScript.
WScript.Timeout = x_seconds
cscript
Usage: CScript scriptname.extension [option...] [arguments...]
Options:
//B Batch mode: Suppresses script errors and prompts from displaying
//D Enable Active Debugging
//E:engine Use engine for executing script
//H:CScript Changes the default script host to CScript.exe
//H:WScript Changes the default script host to WScript.exe (default)
//I Interactive mode (default, opposite of //B)
//Job:xxxx Execute a WSF job
//Logo Display logo (default)
//Nologo Prevent logo display: No banner will be shown at execution time
//S Save current command line options for this user
**//T:nn Time out in seconds: Maximum time a script is permitted to run**
//X Execute script in debugger
//U Use Unicode for redirected I/O from the console
Update:
To help people who downvote a plain (and to the point) citation of cscript.exe's usage message (how can that be wrong?) to see the light through #PanayotKarabakalov's smoke screen:
The claim:
using //T switch not guarantee real time accuracy
that all 5 Echo command executed, even if the Sleep time between them
is 1.5 second and the //T is set to 4
The evidence:
The script is restarted via:
CreateObject("WScript.Shell").Run "WScript " & _
Chr(34) & WScript.ScriptFullName & _
Chr(34) & " /T:4", 0, False
which does not contain the host-specific //T (as opposed to the script-specific /T) switch.
The (counter) argument:
Whatever way you start the first instance of the script (//T or no //T), the second/relaunched instance will never have a time out and will always run to the bitter end.
If you still have doubts, change the invocation in P.'s script to
CreateObject("WScript.Shell").Run "WScript //T:4 " & _
and try it out.

Is the Sleep operation no longer used in VBscript?

The "Sleep" command as stated in many places over the internet (including here on this forum) DOES NOT WORK. Is it now an obsolete command?
I am writing the VBScript code like this:
sub button1_onclick()
Wscript.Sleep 1000
div1.innerHTML = textbox1.value
end sub
It should wait 1 second and then execute that simple command. This is an utterly simple statement but it does not work. Plain and simple. It comes up with an error every time saying:
Object Required: 'Wscript'
Daniel's answer is absolutely correct about context being the key here. Although you don't have the WScript method available, you do have the full browser DOM, including the window.setTimeout method. With VBScript, the semantics of passing code to setTimeout are a little bit different than JavaScript, but it's still possible:
Sub button1_onclick()
window.setTimeout GetRef("Delayed"), 1000
End Sub
Sub Delayed()
div1.innerHTML = textbox1.value
End Sub
Another option would be to (ab)use ping (if you want to avoid an additional script):
Sub Sleep(seconds)
CreateObject("WScript.Shell").Run "%COMSPEC% /c ping 127.0.0.1 -n " _
& seconds+1, 0, True
End Sub
ping sends echo requests in (roughly) 1 second intervals, so you can get an n-second delay by sending n+1 echo requests.
When run from a browser, VBScript code does not have a Wscript object. This is only for stand-alone VBS. As such, Wscript.Sleep isn't obsolete, it just doesn't work in a browser without a work around.
This thread suggests a potential work around. The rest of this post comes from the entry by mayayana on the linked page:
If your security can allow WScript.Shell to run
you can do it this way -
Script sub in webpage:
<SCRIPT LANGUAGE="VBScript">
Sub Sleep(NumberSeconds)
Dim SH, Ret
Set SH = CreateObject("WScript.Shell")
Ret = SH.Run("sleeper.vbs " & NumberSeconds, , True)
End Sub
</SCRIPT>
In the same folder as the webpage, put a file named
sleeper.vbs and put this code into it:
Dim Arg
on error resume next
Arg = WScript.Arguments(0) * 1000
WScript.sleep Arg
You can then call something like:
Sleep 5 '-- pauses 5 seconds.

Resources