I'm working on a program created with Visual Studio 2013. The program does a few things and I'm nearly complete, but one last issue appears.
Within the program I have three buttons. One "Force restart", "manual start" and one "force stop". The "force stop" stops a bunch of programs, and the "force restart" stops them and then starts them again. The "manual start" starts all the programs. I will use "manual start" as example further down.
What happens behind these buttons is that it launches a bunch of bat-files that does the job. The batfiles contains tasskill /f /im program.exe and start "c:\program.exe". Then a second with timeout and exits.
The issue:
So far so good. The issue is that when the batch starts a program, VB program doesnt move on the the next bat file. It leaves a cmd.exe running. Even tho I have exit in the batch. Now If I'd go in and manually close the program or cmd.exe, then it would start on the next bat file.
It basically goes like this now: VB button -> batch starts -> batch runs program -> batch doesn't close AKA VB doesn't move to the next batch.
It should be like this: VB button -> batch starts -> batch runs program -> batch exits -> next batch starts -> batch runs program -> batch exits ->...
Here is what I got so far in that section of VB script:
Private Sub btnManualStart_Click(sender As Object, e As EventArgs) Handles btnManualStart.Click
If MessageBox.Show("Do you want to manually start all programs and services?", "OBS", MessageBoxButtons.YesNo, MessageBoxIcon.Question) = Windows.Forms.DialogResult.Yes Then
Timer2.Start()
Dim FileNum As Integer = FreeFile()
FileClose(FileNum)
TextBox1.Text &= Environment.NewLine & TimeOfDay & (" Manual start made")
Dim shell
shell = CreateObject("wscript.shell")
shell.run("C:\RestartApps\Scripts\Start_program1.bat", 0, True)
shell.run("C:\RestartApps\Scripts\Start_program2.bat", 0, True)
shell.run("C:\RestartApps\Scripts\Start_program3.bat", 0, True)
shell.run("C:\RestartApps\Scripts\Start_program4.bat", 0, True)
shell = Nothing
end if
Hopefully this was understandable.
I do not know exactly what your batches are doing, but it seems to me that you can simply use the Shell shortcut:
Dim commands As String() = {
"C:\RestartApps\Scripts\Start_program1.bat",
"C:\RestartApps\Scripts\Start_program2.bat",
"C:\RestartApps\Scripts\Start_program3.bat",
"C:\RestartApps\Scripts\Start_program4.bat"
}
For Each cmd As String In commands
Shell(cmd, AppWinStyle.Hide, False)
Next
Make sure that you set the third argument to FALSE on the overload (String,AppWinStyle,Boolean). This boolean ensures that the execution is set to "fire and forget". (which is the same as the one you're already passing, as TRUE (will wait for exit code)).
EDIT: Changed the AppWinStyle to Hide, which will run your batches silently
You need to improve start used in your batch script(s) as follows:
start "" /B "c:\program.exe"
Start command: start a program, command or batch script (opens in a new window). Note:
"" always include a TITLE this can be a simple string or just a pair of empty quotes "";
/B start application without creating a new window.
Another approach: use call instead of start "" as follows:
call "c:\program.exe"
Related
I have written a script to mass-tag a large audio library. The tag info is stored in an Access database. I wrote a vba sub that invokes an FFmpeg command line.
newCmdStr = ffmpeg -i myFile.mp3 tag-info output.mp3
Shell ("cmd.exe /k " & newCmdStr)
Is there a way to determine if it runs correctly?
I tried printing the return
MsgBox(Shell(newCmdStr)),
but it seems to return a random number, I can't tell what indicates if the command worked.
The most likely error would be a file not found error, but there could be some other things.
If it ran, I would log the filepath of the new file, if it failed I would note that.
Shell invokes a command asynchronously, and can never get a return value. It returns the Process ID.
Furthermore, it would return the program ID of the program you're trying to run, which, in this case, is cmd.exe since that's first in your string.
Use WScript.Shell instead, which has .Run which can invoke commands synchronously, or .Exec which invokes asynchronously but allows getting return values.
Dim WshShell, oExec
Set WshShell = CreateObject("WScript.Shell")
Set oExec = WshShell.Exec("ffmpeg -i myFile.mp3 tag-info output.mp3")
Do While oExec.Status = 0
DoEvents
'You probably want to sleep here to prevent high CPU usage
'See https://stackoverflow.com/a/469358/7296893
Loop
Debug.Print oExec.ExitCode
Problem: Simple GUI with a button which triggers a batch file to run via cmd. Works for simple and fast scripts (ie mkdir foo), but more advanced scripts which need time to finish, it fails. The problem seems to surround cmd closing before the script can finish. I have included a WaitForExit() clause, but it seems to be ignored.
Private Sub RunButton_Click(sender As Object, e As EventArgs) Handles RunButton.Click
Dim foo as String = IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "Foo\Scripts\foo.bat")
Dim p As New Process
Application.DoEvents()
p.StartInfo.FileName = foo
p.Start()
p.WaitForExit()
End Sub
Any ideas how to correct this issue? I see lots of posts about WScript and creating a shell object; do I really need to do it like that? This process seems to work, but it just closes out before the process finishes.
I'm trying to save all lines from the output window into a log-file. That works fine when I enter the command directly into the immediate window. However, I need a way to enter the command line by code.
I searched for a way to enter a command into the immediate window so the log-file I want will be created and filled with data. That's not happening and I don't know if this is even possible. Searching Google didn't help so far.
My command line is:
> Tools.LogCommandWindowOutput C:\Users\user\AppData\test.log
And I try to get it into the immediate window by
Debug.Print("> Tools.LogCommandWindowOutput C:\Users\user\AppData\test.log")
The command line works fine when put directly into the immediate window (either by typing it in or pasting it in). When I try to do that by code, nothing happens. Is there a way to do that or do I have to try another option and if so, what option is it?
EDIT:
I've tried the solution provided by RobertBaron with the following changes:
'Dim dummy = $"C:\\log\\outputfileAddOn_{Date.Now:yyyy_MM_dd_HH_mm_ss}.log"
'cmdWindow.SendInput($"Tools.LogCommandWindowOutput {dummy}", True)
cmdWindow.SendInput("Tools.LogCommandWindowOutput C:\log\outputfile_AddOn.log", True)
(I want a new file to be written every time, so I tried to add the date at the end to have unique file names)
It creates the file but doesn't log anything in it. What do I do wrong?
Another solution I have found was to add a command parameter in project-properties-debug-command parameter:
> C:\log\outputfile.log
This creates the file and inserts all of the data from the output window. The only problem that I have now is that this file will be overwritten every time the program is started. Is there a way I can set the logging from the second, third, … start at the end of the file? (adding /on at the end of the command Parameter didn't help) Or can I provide something like "outputfile_yyyy_MM_dd_HH_mm_ss.log" (for example: outputfile_2019_07_23_15_47_45.log)?
Thank you for your help!
You need to create a Visual Studio Extension project.
Add a new Custom Command item to the VSIX project.
This will create the Command1.vb. This is where you implement the code that you want to execute inside Visual Studio. The code executes whenever you select the entry Invoke Command1 from the Tools menu.
At the bottom of the Command1.vb file, there is the Execute() Sub. It displays a message just to show that the command is working. You can remove this eventually. But for now, just run the project. Another instance of Visual Studio will start. This is where you can test and debug your code. Once the second instance of Visual Studio has started, go to its Tools menu, and select the Invoke Command1 entry. A message box will display. Stop execution.
Now we want to modify the Execute() Sub so that our code gets executed when Invoke Command1 is selected. Here is the Sub that will execute the command that you want. Add it to the Command1 class.
Public Sub ExecCommandWindow()
Dim dte As EnvDTE.DTE = CType(Microsoft.VisualStudio.Shell.Package.GetGlobalService(GetType(Microsoft.VisualStudio.Shell.Interop.SDTE)), EnvDTE.DTE)
Dim cmdWindow As CommandWindow = CType(dte.Windows.Item(EnvDTE.Constants.vsWindowKindCommandWindow).Object, CommandWindow)
'Display some text in the command window
cmdWindow.OutputString("Executing command from the automation OM...")
'Send some command strings to the command window and execute them...
'This command will start logging all input/output in the command window to the specified file
cmdWindow.SendInput("Tools.LogCommandWindowOutput cmdwindow.log", True)
''Open a file in a code editor:
'' 1. We use an alias, 'of', for the File.OpenFile command
'' 2. This command takes quote-delimited parameters (in this case,
'' the name of the editor to load the file in)
'Dim cmd As String = "of "
'cmd = (cmd + """""C:\Contoso\ContosoCommonFramework\Integration.cs""""")
'cmd = (cmd + "/e:""""CSharp Editor""""")
'cmdWindow.SendInput(cmd, True)
'cmdWindow.SendInput("Edit.Find MessageTrxId", True)
'Turn off logging
cmdWindow.SendInput("Tools.LogCommandWindowOutput /off", True)
End Sub
Change the Execute() Sub to call ExecCommandWindow().
You can change the name of the command by changing its title in the Command1 class.
The Visual Studio extension needs to be installed by each user. Just distribute the .vsix file. To install, double-click it.
It's not the best solution, but it works (for now):
adding
>> C:\log\outputfile.log
(with two '>' before the path for the log file) attaches every log at the end of the file. So I get all Information and nothing is being overwritten.
As I want to know if there is a better solution, I will keep this thread open if that is permitted.
I have been attempting to automate a series of administrative events for some of the users where I work by creating scripts and macro's and so on..
These scripts and macros work great, however, I would like to make a the process even easier for the users by running a single batch file that will systematically execute the scripts and macros.
The batch file I currently have, calls all the scripts one by one, and the very last script opens one of the xlsm workbooks which contains a few macro's in it - and here is where the issue is - There are still scripts to be executed but they can only be executed once this workbook has executed all its macros.
So my initial thought was to test if the workbook is open, if it is, delay the execution of the next script by a minute or so, then test again and again... until it is closed.. Then I thought perhaps it would be easier to execute the next set of scripts (also in a batch file) from within a macro.
So, I have this code:
Sub Run_BAT()
Set obj = CreateObject("Wscript.Shell")
obj.Run Chr(34) & "X:\Test\" & "Termination Reports Scripts\" & "Execute_Terminations.bat" & Chr(34), 0, True
Set obj = Nothing
End Sub
Which gives me an error:
Permission Denied
Then there's this code:
Sub WriteAndRunBatFile()
Call Shell("X:\Test\Termination Reports Scripts\Execute_Terminations.bat")
End Sub
Which gives me the error:
Invalid procedure call
Any and every single code sample that contains the "Shell" command gives this error.
Place your path to bat file in quotes:
Call Shell("cmd /c ""S:/somebatfile.bat""", vbNormalFocus)
Or
Call Shell("cmd.exe /C /K " & "ChDir X:\Test\Termination_Reports_Scripts && Execute_Terminations.bat", vbNormalFocus)
And yes, check permissions.
My theory is you're missing a reference in your application to the Windows Script Host Object Model.
In the VBA Editor, go to Tools, References, make sure that one's ticked.
It's not ticked by default for security reasons - imagine unintended access to the command prompt in every instance of a Microsoft Office application...!
(1) Check permission of user of that X directory.
(2) Test the code by removing spaces from directory name.
Also try the following code (Please try it by removing spaces from directory name).
Sub Button1_Click()
Call Shell("CMD.EXE /C " & "X:\Test\Termination_Reports_Scripts\Execute_Terminations.bat")
End Sub
The following codes is moving a file as long as the file doesn't already exist. If it does, it won't move the file.
My question is regarding the File.Move. When will the msgbox display? Will it display once the file is completely moved or will it display right after the File.Move line is executed.
Depending on the file size, it may take awhile to move the file and thus I don't want the msgbox to display until the file is moved completely.
Is there a better way of doing this?
For Each foundFile As String In My.Computer.FileSystem.GetFiles("C:\Temp\", FileIO.SearchOption.SearchAllSubDirectories, "*.zip")
Dim foundFileInfo As New System.IO.FileInfo(foundFile)
If My.Computer.FileSystem.FileExists("C:\Transfer\" & foundFileInfo.Name) Then
Msgbox("File already exists and will not moved!")
Exit Sub
Else
File.Move(foundFile, "C:\Transfer\" & foundFileInfo.Name)
Msgbox("File has been moved!")
End If
Next
Accordingly to this source, the File.Movecall is synchronous, which means that your msgbox will be shown only after the file is moved, regardless of its size.
For completeness, if you don't want to block the UI, you can try something like this:
' This must be placed outside your sub/function
Delegate Sub MoveDelegate(iSrc As String, iDest As String)
' This line and the following go inside your sub/function
Dim f As MoveDelegate = AddressOf File.Move
' Call File.Move asynchronously
f.BeginInvoke(
foundFile,
"C:\Transfer\" & foundFile,
New AsyncCallback(Sub(r As IAsyncResult)
' this code is executed when the move is complete
MsgBox("File has been moved!")
End Sub), Nothing)
or you can explore the new async / await instructions.
File.Move is a synchronous operation, so the application will not execute the next line of code (your messagebox) until the move is complete.
As you indicated, if the file is large (and you are moving across drives) the messagebox will not show up until the file move is complete. This can create a poor user experience, as your GUI will appear to be non-responsive during this time.
I would recommend taking the time to learn how to utilize background threads or async/await calls to perform the operation in the background.
There is a good article on Asynchronous IO on MSDN: http://msdn.microsoft.com/en-us/library/kztecsys.aspx
Finally you could also use the FileSystem object's MoveFile method, which can pop up a file move UI for you, if you are just worried about keeping your UI responsive:
FileSystem.MoveFile(sourceFileName, destinationFileName, UIOption.AllDialogs)
unfortunately, the code is executed line after the other so the Msgbox will pop up as long as the file has been completely moved.
if you want to monitor the progress, visit this link for more details.
The Message box will be displayed after the file is completely moved irrespective of the file size.
Unless a method is asynchronous, a line of code will always finish executing before proceeding with the next line.
Note, if the file move is slow, and it holding up your program is a Bad Thing, then you could do the move in a background thread using for instance a BackgroundWorker.