def create(args)
unless args.kind_of?(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
]
hash = {
'app_name' => nil,
'creation_flags' => 0,
'close_handles' => true
}
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 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
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
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 = nil
if hash['process_inherit']
process_security = SECURITY_ATTRIBUTES.new
process_security[:nLength] = 12
process_security[:bInheritHandle] = true
end
thread_security = nil
if hash['thread_inherit']
thread_security = SECURITY_ATTRIBUTES.new
thread_security[:nLength] = 12
thread_security[:bInheritHandle] = true
end
['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
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
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
inherit = hash['inherit'] || false
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
winsta_name = FFI::MemoryPointer.new(:char, 256)
return_size = FFI::MemoryPointer.new(:ulong)
bool = GetUserObjectInformationA(
GetProcessWindowStation(),
UOI_NAME,
winsta_name,
winsta_name.size,
return_size
)
unless bool
raise SystemCallError.new("GetUserObjectInformationA", FFI.errno)
end
winsta_name = winsta_name.read_string(return_size.read_ulong)
if winsta_name =~ /^Service-0x0-.*$/i
token = FFI::MemoryPointer.new(:ulong)
bool = LogonUserW(
logon,
domain,
passwd,
LOGON32_LOGON_INTERACTIVE,
LOGON32_PROVIDER_DEFAULT,
token
)
unless bool
raise SystemCallError.new("LogonUserW", FFI.errno)
end
token = token.read_ulong
bool = CreateProcessAsUserW(
token,
app,
cmd,
process_security,
thread_security,
inherit,
hash['creation_flags'],
env,
cwd,
startinfo,
procinfo
)
unless bool
raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission)", FFI.errno)
end
else
bool = CreateProcessWithLogonW(
logon,
domain,
passwd,
LOGON_WITH_PROFILE,
app,
cmd,
hash['creation_flags'],
env,
cwd,
startinfo,
procinfo
)
end
unless bool
raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
end
else
bool = CreateProcessW(
app,
cmd,
process_security,
thread_security,
inherit,
hash['creation_flags'],
env,
cwd,
startinfo,
procinfo
)
unless bool
raise SystemCallError.new("CreateProcessW", FFI.errno)
end
end
if hash['close_handles']
CloseHandle(procinfo[:hProcess])
CloseHandle(procinfo[:hThread])
CloseHandle(token)
end
ProcessInfo.new(
procinfo[:hProcess],
procinfo[:hThread],
procinfo[:dwProcessId],
procinfo[:dwThreadId]
)
end