I have a spreadsheet that summarizes usage of a server. The spreadsheet references a text file that is updated when I have VBA send some commands to the Linux server through the Windows command prompt (and Cygwin). My problem is that the only way I have found to execute commands in Cygwin is through the SendKeys command. This results in two problems:
I have to trust that the user will not click somewhere until the script is done running.
I have to hardcode the amount of time to wait for the command to be executed before the next command can be sent.
I would (and did) try creating a bash file to run in Cygwin, but I don't know of a way to do this that also allows the user to enter their password to log into the Linux server. I am currently getting the user's password from a UserForm that is run before the main code is executed. This password is then saved to a "very hidden" worksheet that is deleted when the application is closed.
I am currently using the following script to run the commands:
Public Sub test()
' Declare Variables
Dim cmd As String
Dim ret As Double
Dim LastRow As Integer
Dim PssWrd As String
PssWrd = Worksheets("PssWrd").Range("A1").Value
' Run Linux Commands
cmd = "C:\cygwin\Cygwin.bat"
ret = Shell(cmd, vbMinimizedFocus)
wait 0.02
cmd = "ssh " & Worksheets("Settings").Range("LOGIN") & "~" ' Format: user#hostname
SendKeys cmd
cmd = PssWrd & "~"
SendKeys cmd
wait 2.78
cmd = "{(} date ; fs ; qstat ; date {)} > & zout &~"
SendKeys cmd
cmd = "exit~"
SendKeys cmd
wait 0.5
cmd = "exit~"
SendKeys cmd
wait 5
' Update PivotTable and other data
End Sub
Public Sub wait(PauseTime As Double)
Dim Start As Double
Dim Finish As Double
Dim TotalTime As Double
Start = Timer ' Set start time
Do While Timer < Start + PauseTime
DoEvents ' Yield to other processes
Loop
Finish = Timer ' Set end time
TotalTime = Finish - Start ' Calculate total time
End Sub
Is there any way to at least send the keystrokes to a specific window instead of just the active window? Any help would be greatly appreciated, and any additional necessary information will be readily provided.
Okay. I think I found the solution. Thanks for all the help (#anishsane) pointing me in the right direction! Here is my updated code:
Public Sub test()
' Declare Variables
Dim cmd As String
Dim ret As Double
Dim LastRow As Integer
Dim PssWrd As String
Dim WshShell As Object
Dim plink_object As Object
PssWrd = Worksheets("PssWrd").Range("A1").Value
' Run Linux Commands
Set WshShell = CreateObject("WScript.Shell")
On Error Resume Next
Set plink_object = WshShell.Run("""C:\Program Files (x86)\PuTTY\plink.exe"" -ssh " & Worksheets("Settings").Range("LOGIN") & " -pw " & PssWrd & " ""(date; fs; qstat; date) > &zout&""", 7, True)
On Error GoTo 0
' Update PivotTable and other data
End Sub
Evidently, in VBA the WScript is not needed. Also, wshshell.exec seems to fail at some point because the commands never get executed (though PuTTY/Plink is started). For some reason, the WshShell.Run command resulted in the "Run-time error '424': Object required" error, but the essential part of the commands still get executed properly, so I just have the error being ignored. I also figured out how to execute my command all in one line so I don't have to worry about the stdIn.Write command that wasn't working before. I could use the Shell() command, but I need to make sure that the command has finished executing (via the bWaitOnReturn option) to make sure I have the most up-to-date file when the update script runs. Having said that, I believe that since I am having Linux write out the output of the command to a file, the file hasn't finished updating when VBA says the command has finished executing. However, I believe I've figured out a means to check that the last line of text is formatted correctly in the file (since it should be the date), so that shouldn't be a problem.
Related
I have to run an .exe in VBA Excel and write in the input window "in.txt" "out.txt" in order to make the process automatic inside a macro. I tried to use shell but it works asynchrounous and I also don't know how to tell her to write inside the .exe.
I've also tried with SendKeys but apperently it doesen't work.
How could I make the VBA calling my .exe, open it, write inside the command window of the .exe, wait for the output and use it to go on?
thank you in advance
here are two attempts (both failed):
Sub write()
prog = Shell("C:\Users\arancia\Pictures\Camera Roll\axtur\axtur\AXTUR_64.exe", 1)
Application.Run "'AXTUR&EXCEL.xlsm'!inserisci_dati_input"
SendKeys.send "in.txt~", True
SendKeys.send "out.txt~", True
SendKeys "%{F4}", True
End Sub
Sub StartExeWithArgument()
Dim strProgramName As String
Dim strArgument As String
strProgramName = "C:\Users\arancia\Pictures\Camera Roll\axtur\axtur\AXTUR_64.exe"
strArgument = "in.txt~out.txt~"
Call Shell("""" & strProgramName & """ """ & strArgument & """", vbNormalFocus)
End Sub
One solution would be to write a batch file that includes all the parameters and then run the batch.
I have used WshShell (Windows scripting host) to run batch files to do what you want in the past but WshShell does not work on our computers since the Nov 2020 updates. WshShell allows you to wait for the outcome of the external program.
One way I found to go around it is to write a simple text file at the end of the batch and wait for it to show up. This is crude but it works.
In the code below, I write a simple batch file in the folder of the Excel sheet. The last line of the batch writes the content of the folder in a text file. The Do Until loop waits for the text file to show up in 1 second increments. When the code resumes after the loop, the text file is deleted. If you write the command line you would type in cmd instead of "echo Hello World" this should work.
You need to reference the Microsoft Scripting Runtime (scrrun) to use the file system object.
Good Luck!
Public Sub RunBatch()
Dim i As Integer
Dim k As Integer
Dim xlWB As Workbook
Dim fso1 As New FileSystemObject
Dim BatFile As Object
Dim IsDone As Boolean
Dim OutFileName As String
Set xlWB = ThisWorkbook
OutFileName = xlWB.Path & "\" & "HW.bat"
Set BatFile = fso1.CreateTextFile(OutFileName)
BatFile.WriteLine "cd /d " & xlWB.Path
BatFile.WriteLine "echo Hello World"
BatFile.WriteLine "dir > Done.txt"
BatFile.Close
IsDone = False
Call Shell(OutFileName, vbNormalFocus)
Do Until IsDone
If fso1.FileExists(xlWB.Path & "\Done.txt") Then IsDone = True
Application.Wait (Now + TimeValue("00:00:01"))
Loop
fso1.DeleteFile (OutFileName)
End Sub
I am having an issue finding/understanding how to solve the following issue when running a .bat file in VBA in Excel. I am getting an Open File - Security Warning prompt when I try and execute it.
The issue is i don't have administrative privileges, so I can't set .bat files as being ignored from this prompt, and this .bat file is one that I created myself so I don't understand why my computer is flagging it as potentially harmful.
If anyone could explain a workaround, and why such an issue arises despite it being a custom generated .bat file, I would greatly appreciate it. By a workaround I mean modifying the code I currently have to deal with this problem, without having to go through Administrators.
Below is the snippet of code pertaining to the problem, thanks:
sub test()
'some stuff
Open MY_FILENAME For Output As #FileNumber
Print #FileNumber, FileContents
Print #FileNumber, "exit"
Close #FileNumber
'run batch file
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim windowStyle As Integer: windowStyle = 1
Dim rtn As Integer
rtn = wsh.Run(Chr(34) & MY_FILENAME & Chr(34), windowStyle, waitOnReturn)
end sub
I should add that if run the shell like so I have no issues:
rtn = Shell(MY_FILENAME, vbNormalFocus)
Except that the code will continue running despite the shell not closing and completing its execution. I need the shell to halt until it has executed it's task.
I have a .bat file calling from VBA, it is working when I use a local folder as path (example as C:\Users\cthoud01\Desktop\my scripts\scripts).
However, I got an error if I use a path from a network directory (example - H:\scripts). I also tried replacing the path as """H:\scripts\""" but continue to get the same error. I would like to hear from our experts if there is any way around to make this work.
Below is the error message I receive:
VBA Code I am using:
Sub test()
Dim wsh As Object
Set wsh = VBA.CreateObject("WScript.Shell")
Dim waitOnReturn As Boolean: waitOnReturn = True
Dim WindowStyle As Integer: WindowStyle = 1
Dim errorCode As Long
Dim pth As String
errorCode = wsh.Run("cmd.exe /k cd """ & "H:\scripts\" & """ && DeleteMatrix.bat", WindowStyle, waitOnReturn)
If errorCode <> 0 Then
MsgBox "fail, please retry"
End
End If
End Sub
errorCode = wsh.Run("cmd.exe /k cd /d """ & "H:\scripts\" & """ && DeleteMatrix.bat", WindowStyle, waitOnReturn)
Look at the extra /d.
In MSDos each drive had a current directory.
C:\>cd D:\dog
C:\>Copy *.* D:
would copy files into the d:\dog directory. This makes typing easier. There were no mouses or menus.
Windows only has the concept of current directory (and it's per program).
So MSDos batch files run unchanged in CMD you have to tell windows you don't want MSDos behaviour with the CD command by using /d.
Likewise CMD simulates a default drive per directory.
This is what it looks like in the process's environment block
=C:=C:\Users\David Candy\Desktop\Editor\EditorSdi
=D:=d:\~MSSETUP.T
I spent most of the day searching for a solution to this, I'm starting to think its maybe not possible for my requirements
My basic setup is to run a vbscript (.vbs) called from an excel vba code. The vba code has to continue on and leave the vbscript running, but will monitor it from time to time using Exec.Status
In the vbscript I'm using WScript.StdOut.WriteLine "whatever" to track/debug it's progress, but as it stands I can only read it's output after the excel vba code is finished what it needs to do.
What I want is to see a real time output to the console from the vbscript
Here's the vba code...
Dim WSH As IWshRuntimeLibrary.WshShell 'Windows Script Host Object Model
Dim Exec As WshExec
Set WSH = CreateObject("WScript.Shell")
Set Exec = WSH.Exec("%COMSPEC% /C CSCRIPT.EXE //nologo " _
& VbsFileDir _
& " " & Arg1 _
& " " & Arg2 _
& " " & Arg3 _
& " " & Arg4)
I have been able to get a real time output by converting from WSH.Exec to WSH.Run, but I do need the access to Exec.Status, which is not available under WSH.Run
UPDATE - 2015-02-06
To clarify further... Using the example '...B.vbs' code provided by #Ekkehard.Horner's answer... The following excel-vba code WILL display a real-time output to the console...
WSH.Run("cscript C:\28353522-B.vbs")
...but the following WILL NOT display anything to the console
WSH.Exec("cscript C:\28353522-B.vbs")
I can't use the .Run() because I use the .Status flag from .Exec()
Also I can't just move the vbscript into the VBA code because the VBA goes on to do other tasks in parallel with the vbscript.
P.s. If anyone can submit an answer explaining why it can't be done, then I will mark that as accepted.
Use .Stdout.ReadLine() until the process has finished and .Stdout.ReadAll() to slurp the rest of the output - as in
28353522-A.vbs
Option Explicit
Const WshFinished = 1
Dim oExc : Set oExc = CreateObject("WScript.Shell").Exec("cscript 28353522-B.vbs")
WScript.Echo "A", "start"
Do While True
If oExc.Status = WshFinished Then
WScript.Echo "A", "WshFinished"
Exit Do
End If
WScript.Sleep 500
If Not oExc.Stdout.AtEndOfStream Then WScript.Echo "A", oExc.Stdout.ReadLine()
Loop
If Not oExc.Stdout.AtEndOfStream Then WScript.Echo "A", oExc.Stdout.ReadAll()
28353522-B.vbs
Option Explicit
Dim i
For i = 1 To 10
WScript.Echo "B", i, "whatever"
WScript.Sleep 100
Next
output:
cscript 28353522-A.vbs
A start
A B 1 whatever
A B 2 whatever
A B 3 whatever
A WshFinished
A B 4 whatever
B 5 whatever
B 6 whatever
B 7 whatever
B 8 whatever
B 9 whatever
B 10 whatever
BTW - How did you get real-time output with .Run()?
Why are you running two files? There is no need.
VBA, being full basic, can write to it's own console.
So put your vbs into VBA (you can cut and paste VBS into VBA and it will work if you put sub/end sub around it). To have the VBS run in VBA put a timer that fires it.
Here's a class module for VBA to create/read/write consoles.
'User global var gconsole
Public Function WriteToConsoles(sOut As String)
If IsConsoleAvailable() = True Then
Dim Result As Long, cWritten As Long
Result = WriteConsole(hConsole, ByVal sOut, Len(sOut), cWritten, ByVal 0&)
End If
End Function
Public Sub ExecuteCommand(Cmd As String, ReturnToPrompt As Boolean)
If IsConsoleAvailable() = True Then
If Len(Cmd) <> 0 Then
If ReturnToPrompt = True Then
Shell Environ$("comspec") & " /k " & Cmd
Else
Shell Environ$("comspec") & " /c " & Cmd
End If
End If
End If
End Sub
Public Sub CreateConsole()
If IsConsoleAvailable() = False Then
If AllocConsole() Then
hConsole = GetStdHandle(STD_OUTPUT_HANDLE)
If hConsole = 0 Then MsgBox "Couldn't allocate STDOUT"
Else
MsgBox "Couldn't allocate console"
End If
End If
End Sub
Public Sub CloseConsole()
If IsConsoleAvailable() = True Then
CloseHandle hConsole
hConsole = 0
FreeConsole
End If
End Sub
Public Function IsConsoleAvailable() As Boolean
If hConsole <> 0 Then
IsConsoleAvailable = True
Else
IsConsoleAvailable = False
End If
End Function
I've come up with an answer to my own question. Though this isn't a preferred solution (as I will explain below), so I'll not be marking as correct, but maybe someone can fix the issues with solution? (if so, post an answer and I'll mark as correct)
First off, +1 to #Ekkehard.Horner's answer for inspiring this solution.
Create the file 'B.vbs' representing my main vbscript to be run.
Option Explicit
Dim i
For i = 1 to 10
Wscript.Echo "B", i, "whatever"
Wscript.Sleep 100
Next
Create the file 'A.vbs' to act as a middle man between the main vbscript and my Excel VBA code
Option Explicit
Dim WSH
Set WSH = CreateObject("WScript.Shell")
WSH.Run "cscript C:\B.vbs", , True
Set WSH = Nothing
Now the excel VBA code...
Option Explicit
Sub Test()
Dim WSH As IWshRuntimeLibrary.WshShell
Dim Exec As WshExec
Set WSH = CreateObject("WScript.Shell")
Set Exec = WSH.Exec("cscript C:\A.vbs")
'Showing that I can still access the Exec.Status
While Exec.Status = WshRunning
Debug.Print "Running"
Wend
'But downside is nothing is avaiable from Stdout
Debug.Print Exec.StdOut.ReadAll
Set Exec = Nothing
Set WSH = Nothing
End Sub
So the Excel VBA calls the 'A.vbs' still using WSH.Exec(), then that will call the 'B.vbs' using WSH.Run(), which opens a second console window which will display the real-time output
Advantages
Excel VBA can still monitor the Exec.Status accurately
Progress of 'B.vbs' can be viewed from real-time console output
Disadvantages (reasons I'm not marking as correct)
The Exec.Terminate() will only terminate the 'A.vbs' (first console window), the 'B.vbs' will remain running
The Exec.StdOut. cannot read the output from 'B.vbs'
I made an executable based on a Python Script that I made from a Selenium Code where I put a number and then it returns a *.pdf file. Now I'm trying to create a VBA Macro in Excel to send the ActiveCell value through the Shell Command to my application:
Sub do_it()
Dim RetVal As Variant
RetVal = Shell("C:\Users\ghost\Desktop\assist_exe\dist\assist_inputc.exe " & ActiveCell.Value, 1)
End Sub
The assist_inputc.exe opens up but the ActiveCell's value is not being captured.
i tried this:
RetVal = Shell("cmd /k C:\Users\ghost\Desktop\assist_exe\dist\assist_inputc.exe ECHO " & ActiveCell.Value, 1)
and still can´t post the value from the ActiveCell into my aplication, but, if i execute the same code without the aplication path it works fine, maybe there´s a diferent way to paste the value?
Searching related post´s i find a solution that help me in this problem :
Dim RetVal As Variant
RetVal = Shell("cmd /k C:\Users\ghost\Desktop\temp\dist\assist_empty.exe ", 1)
Application.Wait Now + TimeValue("00:00:01")
SendKeys ActiveCell.Value
SendKeys "{ENTER}"
I hope it works for someone with the same or similar problem.