module Process
def create(args)
Process.spawn method instead of Process.create where possible.
If you really to use Process.wait, then you should use the
sleep 0.1 while !Process.get_exitcode(info.process_id)
To simulate Process.wait you can use this approach:
executable file in the security context of the specified credentials.
If the 'with_logon' option is set, then the process runs the specified
before the ProcessInfo struct is returned.
process_handle and the thread_handle are automatically closed for you
If the 'close_handles' option is set to true (the default) then the
* thread_id - Thread ID.
* process_id - Process ID.
* thread_handle - The handle to the primary thread of the process.
* process_handle - The handle to the newly created process.
The ProcessInfo struct contains the following members:
automatically OR'd to the +startf_flags+ value.
is automatically set to true and the Process::STARTF_USESTDHANDLES flag is
If 'stdin', 'stdout' or 'stderr' are specified, then the +inherit+ value
an option for JRuby.
are not currently supported. Unfortunately, setting these is not currently
IO objects or file descriptors (i.e. a fileno). However, StringIO objects
Note that the 'stdin', 'stdout' and 'stderr' options can be either Ruby
* stderr
* stdout
* stdin
* startf_flags
* sw_flags
* fill_attribute
* y_count_chars
* x_count_chars
* y_size
* x_size
* y
* x
* title
* desktop
and the StartupInfo struct on MSDN for more information.
GUI or console processes. See the documentation on CreateProcess()
part of the StartupInfo struct, and are generally only meaningful for
The startup_info key takes a hash. Its keys are attributes that are
mandatory.
of 'with_logon'. If 'with_logon' is set, then the 'password' option is
The 'domain' and 'password' options are only relevent in the context
require an explicit path or extension to work.
be preferred if only one of them is set because it does not (necessarily)
error is raised. Both may be set individually, but 'command_line' should
Of these, the 'command_line' or 'app_name' must be specified or an
* password (default: nil, mandatory if with_logon)
* domain (default: nil)
* with_logon (default: nil)
* close_handles (default: true)
* environment (default: nil)
* startup_info (default: nil)
* cwd (default: Dir.pwd)
* creation_flags (default: 0)
* thread_inherit (default: false)
* process_inherit (default: false)
* inherit (default: false)
* app_name (default: nil)
* command_line (this or app_name must be present)
There are several primary keys:
returning a ProcessInfo struct. It accepts a hash as an argument.
This is a wrapper for the CreateProcess() function. It executes a process,
Process.create(key => value, ...) => ProcessInfo
def create(args) unless args.is_a?(Hash) raise TypeError, "hash keyword arguments expected" end valid_keys = %w{ app_name command_line inherit creation_flags cwd environment startup_info thread_inherit process_inherit close_handles with_logon domain password } valid_si_keys = %w{ startf_flags desktop title x y x_size y_size x_count_chars y_count_chars fill_attribute sw_flags stdin stdout stderr } # Set default values hash = { "app_name" => nil, "creation_flags" => 0, "close_handles" => true, } # Validate the keys, and convert symbols and case to lowercase strings. args.each { |key, val| key = key.to_s.downcase unless valid_keys.include?(key) raise ArgumentError, "invalid key '#{key}'" end hash[key] = val } si_hash = {} # If the startup_info key is present, validate its subkeys if hash["startup_info"] hash["startup_info"].each { |key, val| key = key.to_s.downcase unless valid_si_keys.include?(key) raise ArgumentError, "invalid startup_info key '#{key}'" end si_hash[key] = val } end # The +command_line+ key is mandatory unless the +app_name+ key # is specified. unless hash["command_line"] if hash["app_name"] hash["command_line"] = hash["app_name"] hash["app_name"] = nil else raise ArgumentError, "command_line or app_name must be specified" end end env = nil # The env string should be passed as a string of ';' separated paths. if hash["environment"] env = hash["environment"] unless env.respond_to?(:join) env = hash["environment"].split(File::PATH_SEPARATOR) end env = env.map { |e| e + 0.chr }.join("") + 0.chr env.to_wide_string! if hash["with_logon"] end # Process SECURITY_ATTRIBUTE structure process_security = nil if hash["process_inherit"] process_security = SECURITY_ATTRIBUTES.new process_security[:nLength] = 12 process_security[:bInheritHandle] = 1 end # Thread SECURITY_ATTRIBUTE structure thread_security = nil if hash["thread_inherit"] thread_security = SECURITY_ATTRIBUTES.new thread_security[:nLength] = 12 thread_security[:bInheritHandle] = 1 end # Automatically handle stdin, stdout and stderr as either IO objects # or file descriptors. This won't work for StringIO, however. It also # will not work on JRuby because of the way it handles internal file # descriptors. # %w{stdin stdout stderr}.each { |io| if si_hash[io] if si_hash[io].respond_to?(:fileno) handle = get_osfhandle(si_hash[io].fileno) else handle = get_osfhandle(si_hash[io]) end if handle == INVALID_HANDLE_VALUE ptr = FFI::MemoryPointer.new(:int) if windows_version >= 6 && get_errno(ptr) == 0 errno = ptr.read_int else errno = FFI.errno end raise SystemCallError.new("get_osfhandle", errno) end # Most implementations of Ruby on Windows create inheritable # handles by default, but some do not. RF bug #26988. bool = SetHandleInformation( handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT ) raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool si_hash[io] = handle si_hash["startf_flags"] ||= 0 si_hash["startf_flags"] |= STARTF_USESTDHANDLES hash["inherit"] = true end } procinfo = PROCESS_INFORMATION.new startinfo = STARTUPINFO.new unless si_hash.empty? startinfo[:cb] = startinfo.size startinfo[:lpDesktop] = si_hash["desktop"] if si_hash["desktop"] startinfo[:lpTitle] = si_hash["title"] if si_hash["title"] startinfo[:dwX] = si_hash["x"] if si_hash["x"] startinfo[:dwY] = si_hash["y"] if si_hash["y"] startinfo[:dwXSize] = si_hash["x_size"] if si_hash["x_size"] startinfo[:dwYSize] = si_hash["y_size"] if si_hash["y_size"] startinfo[:dwXCountChars] = si_hash["x_count_chars"] if si_hash["x_count_chars"] startinfo[:dwYCountChars] = si_hash["y_count_chars"] if si_hash["y_count_chars"] startinfo[:dwFillAttribute] = si_hash["fill_attribute"] if si_hash["fill_attribute"] startinfo[:dwFlags] = si_hash["startf_flags"] if si_hash["startf_flags"] startinfo[:wShowWindow] = si_hash["sw_flags"] if si_hash["sw_flags"] startinfo[:cbReserved2] = 0 startinfo[:hStdInput] = si_hash["stdin"] if si_hash["stdin"] startinfo[:hStdOutput] = si_hash["stdout"] if si_hash["stdout"] startinfo[:hStdError] = si_hash["stderr"] if si_hash["stderr"] end app = nil cmd = nil # Convert strings to wide character strings if present if hash["app_name"] app = hash["app_name"].to_wide_string end if hash["command_line"] cmd = hash["command_line"].to_wide_string end if hash["cwd"] cwd = hash["cwd"].to_wide_string end if hash["with_logon"] logon = hash["with_logon"].to_wide_string if hash["password"] passwd = hash["password"].to_wide_string else raise ArgumentError, "password must be specified if with_logon is used" end if hash["domain"] domain = hash["domain"].to_wide_string end hash["creation_flags"] |= CREATE_UNICODE_ENVIRONMENT bool = CreateProcessWithLogonW( logon, # User domain, # Domain passwd, # Password LOGON_WITH_PROFILE, # Logon flags app, # App name cmd, # Command line hash["creation_flags"], # Creation flags env, # Environment cwd, # Working directory startinfo, # Startup Info procinfo # Process Info ) unless bool raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno) end else inherit = hash["inherit"] ? 1 : 0 bool = CreateProcessW( app, # App name cmd, # Command line process_security, # Process attributes thread_security, # Thread attributes inherit, # Inherit handles? hash["creation_flags"], # Creation flags env, # Environment cwd, # Working directory startinfo, # Startup Info procinfo # Process Info ) unless bool raise SystemCallError.new("CreateProcess", FFI.errno) end end # Automatically close the process and thread handles in the # PROCESS_INFORMATION struct unless explicitly told not to. if hash["close_handles"] CloseHandle(procinfo[:hProcess]) CloseHandle(procinfo[:hThread]) end ProcessInfo.new( procinfo[:hProcess], procinfo[:hThread], procinfo[:dwProcessId], procinfo[:dwThreadId] ) end