I’m developing a plugin that needs to call an external command line program to perform a task that cannot be performed in Ruby (nor with a Ruby C extension). Everything works fine, except that the command line window shows while the external program is running. I think I’ve read somewhere that there is a way to call such an external program without showing the command line, but cannot find it.
Currently I call the program with parameters using `backticks`. I’m targeting SketchUp 2014 (Ruby 2.0.0) on Windows.
(Simplest solution: avoid to run the plugin on Windows)
There are several criteria that seem to exclude each other when working out a solution for Windows.
You want to run it “quiet” and avoid the black scary command prompt.
You may not want to freeze SketchUp’s single-threaded Ruby process and the whole UI: not completely synchronous.
You may want to know when it is finished: not completely asynchronous.
You may want to have stdout and stderr.
I thought my old TextureResizer code was ugly and I wanted to renovate it. Most of all I wanted get rid of writing output to temporary files. After a long research it turned out the only, commonly used solution was similar to what I already had. Take a look into the latest async_batch.rb in TextureResizer on EWH (my API wraps it into a promise which you maybe do not need).
Running a batch file with call or start always opens a black GUI window. The standard work-around is to invoke it from a visual basic script with wscript.
I dispatch the process asynchronously.
I have to observe when the external process completes. Currently I do this with SketchUp’s asynchronous UI timer.
To get stdout/stderr from an IO object, you would need to run synchronously. But when you use handle = WsShell.Exec you get again a flashing black window. Otherwise, the output is empty and the only workaround is to write&read it from a file.
In Windows Ruby’s running a system command with back-ticks etc always opens a window - albeit briefly.
You can’t sidestep it, but you can minimize the time it appears by running a very short .cmd [or .bat] file which then runs a main .cmd file, by using the START /B method - which then runs it window-free - the initial window closes as soon as it’s completed.
You can write the two files into the TEMP folder and launch the short one using:
UI.openURL("file:///#{full_path_to_temp.cmd}")
Another [better?] way to sidestep any command windows opening is to use VBScript to run all of the system code.
Again you’d write a VBS file to the TEMP folder, and then use openURL to run it…
Along the lines of:
Set wShell = CreateObject ("Wscript.Shell")
wShell.Run "cmd /c SOME_CMD_CODE", 0
Based on TIG’s and Aerilius’ replies I managed to find a solution.
Here’s my general system-call-with-no-flashing-window method.
require "tempfile"
# Run system call without flashing command line window on Windows.
# Runs asynchronously.
# Windows only hack.
#
# @param cmd String.
#
# @return [Void].
def self.system_call(cmd)
# HACK: Run the command through a VBS script to avoid flashing command line
# window.
file = Tempfile.new(["cmd", ".vbs"])
file.write("Set WshShell = CreateObject(\"WScript.Shell\")\n")
file.write("WshShell.Run \"#{cmd.gsub('"', '""')}\", 0\n")
file.close
UI.openURL("file://#{file.path}")
nil
end
As this code runs asynchronously I used this method, based on Aerilius’s ImageMagic library, to run a piece of code only once a file has been created.
# Run block once a file has been created.
#
# @param path [String]
# @param async [Boolean]
# @param delay [Flaot]
# @param block [Proc]
#
# @return [Void]
def self.on_exist(path, async = true, delay = 0.2, &block)
if File.exist?(path)
block.call
return
end
if async
UI.start_timer(delay) { on_exist(path, async, delay, &block) }
else
sleep(delay)
on_exist(path, async, delay, &block)
end
nil
end