module Process

def create(args)

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
  ]
  # 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] = true
  end
  # Thread SECURITY_ATTRIBUTE structure
  thread_security = nil
  if hash['thread_inherit']
    thread_security = SECURITY_ATTRIBUTES.new
    thread_security[:nLength] = 12
    thread_security[:bInheritHandle] = true
  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.
  #
  ['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
  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(),  # 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 = winsta_name.read_string(return_size.read_ulong)
    # If running in the service windows station must do a log on to get
    # to the interactive desktop.  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 cann with the LOGON32_LOGON_INTERACTIVE flag.
    if winsta_name =~ /^Service-0x0-.*$/i
      token = FFI::MemoryPointer.new(:ulong)
      bool = LogonUserW(
        logon,                      # User
        domain,                     # Domain
        passwd,                     # Password
        LOGON32_LOGON_INTERACTIVE,  # Logon Type
        LOGON32_PROVIDER_DEFAULT,   # Logon Provider
        token                       # User token handle
      )
      unless bool
        raise SystemCallError.new("LogonUserW", FFI.errno)
      end
      token = token.read_ulong
      begin
        bool = CreateProcessAsUserW(
          token,                  # User token handle
          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
        )
      ensure
        CloseHandle(token)
      end
      unless bool
        raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission)", FFI.errno)
      end
    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
      )
    end
    unless bool
      raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno)
    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]) if procinfo[:hProcess]
    CloseHandle(procinfo[:hThread]) if procinfo[:hThread]
    # Set fields to nil 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] = nil
    procinfo[:hThread] = nil
  end
  ProcessInfo.new(
    procinfo[:hProcess],
    procinfo[:hThread],
    procinfo[:dwProcessId],
    procinfo[:dwThreadId]
  )
end