module Process
Override Process.create
to check for running in the Service window station and doing a full logon with LogonUser, instead of a CreateProcessWithLogon Cloned from github.com/djberg96/win32-process/blob/ffi/lib/win32/process.rb as of 2015-10-15 from commit cc066e5df25048f9806a610f54bf5f7f253e86f7
Public Class Methods
Source
# File lib/mixlib/shellout/windows/core_ext.rb, line 70 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 elevated } 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 do |key, val| key = key.to_s.downcase unless valid_keys.include?(key) raise ArgumentError, "invalid key '#{key}'" end hash[key] = val end si_hash = {} # If the startup_info key is present, validate its subkeys if hash["startup_info"] hash["startup_info"].each do |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 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 do |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 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 inherit = hash["inherit"] ? 1 : 0 if hash["with_logon"] logon, passwd, domain = format_creds_from_hash(hash) hash["creation_flags"] |= CREATE_UNICODE_ENVIRONMENT winsta_name = get_windows_station_name # If running in the service windows station must do a log on to get # to the interactive desktop. The running process user account must have # the 'Replace a process level token' permission. This is necessary as # the logon (which happens with CreateProcessWithLogon) must have an # interactive windows station to attach to, which is created with the # LogonUser call with the LOGON32_LOGON_INTERACTIVE flag. # # User Access Control (UAC) only applies to interactive logons, so we # can simulate running a command 'elevated' by running it under a separate # logon as a batch process. if hash["elevated"] || winsta_name =~ /^Service-0x0-.*$/i logon_type = if hash["elevated"] LOGON32_LOGON_BATCH else LOGON32_LOGON_INTERACTIVE end token = logon_user(logon, domain, passwd, logon_type) create_process_as_user(token, app, cmd, process_security, thread_security, inherit, hash["creation_flags"], env, cwd, startinfo, procinfo) else 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 end else 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("CreateProcessW", 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]) # Clear these fields so callers don't attempt to close the handle # which can result in the wrong handle being closed or an # exception in some circumstances. procinfo[:hProcess] = 0 procinfo[:hThread] = 0 end ProcessInfo.new( procinfo[:hProcess], procinfo[:hThread], procinfo[:dwProcessId], procinfo[:dwThreadId] ) end
Source
# File lib/mixlib/shellout/windows/core_ext.rb, line 349 def create_process_as_user(token, app, cmd, process_security, thread_security, inherit, creation_flags, env, cwd, startinfo, procinfo) bool = CreateProcessAsUserW( token, # User token handle app, # App name cmd, # Command line process_security, # Process attributes thread_security, # Thread attributes inherit, # Inherit handles creation_flags, # Creation Flags env, # Environment cwd, # Working directory startinfo, # Startup Info procinfo # Process Info ) unless bool if FFI.errno == ERROR_PRIVILEGE_NOT_HELD raise SystemCallError.new("CreateProcessAsUserW (User '#{::ENV['USERNAME']}' must hold the 'Replace a process level token' and 'Adjust Memory Quotas for a process' permissions. Logoff the user after adding this right to make it effective.)", FFI.errno) else raise SystemCallError.new("CreateProcessAsUserW failed.", FFI.errno) end end ensure CloseHandle(token) end
Source
# File lib/mixlib/shellout/windows/core_ext.rb, line 394 def format_creds_from_hash(hash) 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 [ logon, passwd, domain ] end
Source
# File lib/mixlib/shellout/windows/core_ext.rb, line 375 def get_windows_station_name winsta_name = FFI::MemoryPointer.new(:char, 256) return_size = FFI::MemoryPointer.new(:ulong) bool = GetUserObjectInformationA( GetProcessWindowStation(), # Window station handle UOI_NAME, # Information to get winsta_name, # Buffer to receive information winsta_name.size, # Size of buffer return_size # Size filled into buffer ) unless bool raise SystemCallError.new("GetUserObjectInformationA", FFI.errno) end winsta_name.read_string(return_size.read_ulong) end
Source
# File lib/mixlib/shellout/windows/core_ext.rb, line 325 def logon_user(user, domain, passwd, type, provider = LOGON32_PROVIDER_DEFAULT) token = FFI::MemoryPointer.new(:ulong) bool = LogonUserW( user, # User domain, # Domain passwd, # Password type, # Logon Type provider, # Logon Provider token # User token handle ) unless bool if (FFI.errno == ERROR_LOGON_TYPE_NOT_GRANTED) && (type == LOGON32_LOGON_BATCH) user_utf8 = user.encode( "UTF-8", invalid: :replace, undef: :replace, replace: "" ).delete("\0") raise SystemCallError.new("LogonUserW (User '#{user_utf8}' must hold 'Log on as a batch job' permissions.)", FFI.errno) else raise SystemCallError.new("LogonUserW", FFI.errno) end end token.read_ulong end