# File lib/mixlib/shellout/windows/core_ext.rb, line 40
  def create(args)
    unless args.kind_of?(Hash)
      raise TypeError, 'Expecting hash-style keyword arguments'
    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 = %/
      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

    # The environment string should be passed as an array of A=B paths, or
    # as a string of ';' separated paths.
    if hash['environment']
      env = hash['environment']
      if !env.respond_to?(:join)
        # Backwards compat for ; separated paths
        env = hash['environment'].split(File::PATH_SEPARATOR)
      end
      # The argument format is a series of null-terminated strings, with an additional null terminator.
      env = env.map { |e| e + "\0" }.join("") + "\0"
      if hash['with_logon']
        env = env.multi_to_wide(e)
      end
      env = [env].pack('p*').unpack('L').first
    else
      env = nil
    end

    startinfo = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
    startinfo = startinfo.pack('LLLLLLLLLLLLSSLLLL')
    procinfo  = [0,0,0,0].pack('LLLL')

    # Process SECURITY_ATTRIBUTE structure
    process_security = 0
    if hash['process_inherit']
      process_security = [0,0,0].pack('LLL')
      process_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
      process_security[8,4] = [1].pack('L')  # TRUE
    end

    # Thread SECURITY_ATTRIBUTE structure
    thread_security = 0
    if hash['thread_inherit']
      thread_security = [0,0,0].pack('LLL')
      thread_security[0,4] = [12].pack('L') # sizeof(SECURITY_ATTRIBUTE)
      thread_security[8,4] = [1].pack('L')  # TRUE
    end

    # Automatically handle stdin, stdout and stderr as either IO objects
    # or file descriptors.  This won't work for StringIO, however.
    ['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
          raise Error, get_last_error
        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 Error, get_last_error unless bool

        si_hash[io] = handle
        si_hash['startf_flags'] ||= 0
        si_hash['startf_flags'] |= STARTF_USESTDHANDLES
        hash['inherit'] = true
      end
    }

    # The bytes not covered here are reserved (null)
    unless si_hash.empty?
      startinfo[0,4]  = [startinfo.size].pack('L')
      startinfo[8,4]  = [si_hash['desktop']].pack('p*') if si_hash['desktop']
      startinfo[12,4] = [si_hash['title']].pack('p*') if si_hash['title']
      startinfo[16,4] = [si_hash['x']].pack('L') if si_hash['x']
      startinfo[20,4] = [si_hash['y']].pack('L') if si_hash['y']
      startinfo[24,4] = [si_hash['x_size']].pack('L') if si_hash['x_size']
      startinfo[28,4] = [si_hash['y_size']].pack('L') if si_hash['y_size']
      startinfo[32,4] = [si_hash['x_count_chars']].pack('L') if si_hash['x_count_chars']
      startinfo[36,4] = [si_hash['y_count_chars']].pack('L') if si_hash['y_count_chars']
      startinfo[40,4] = [si_hash['fill_attribute']].pack('L') if si_hash['fill_attribute']
      startinfo[44,4] = [si_hash['startf_flags']].pack('L') if si_hash['startf_flags']
      startinfo[48,2] = [si_hash['sw_flags']].pack('S') if si_hash['sw_flags']
      startinfo[56,4] = [si_hash['stdin']].pack('L') if si_hash['stdin']
      startinfo[60,4] = [si_hash['stdout']].pack('L') if si_hash['stdout']
      startinfo[64,4] = [si_hash['stderr']].pack('L') if si_hash['stderr']
    end

    if hash['with_logon']
      logon  = multi_to_wide(hash['with_logon'])
      domain = multi_to_wide(hash['domain'])
      app    = hash['app_name'].nil? ? nil : multi_to_wide(hash['app_name'])
      cmd    = hash['command_line'].nil? ? nil : multi_to_wide(hash['command_line'])
      cwd    = multi_to_wide(hash['cwd'])
      passwd = multi_to_wide(hash['password'])

      hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT

      process_ran = 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
      )
    else
      process_ran = CreateProcess(
        hash['app_name'],       # App name
        hash['command_line'],   # Command line
        process_security,       # Process attributes
        thread_security,        # Thread attributes
        hash['inherit'],        # Inherit handles?
        hash['creation_flags'], # Creation flags
        env,                    # Environment
        hash['cwd'],            # Working directory
        startinfo,              # Startup Info
        procinfo                # Process Info
      )
    end

    # TODO: Close stdin, stdout and stderr handles in the si_hash unless
    # they're pointing to one of the standard handles already. [Maybe]
    if !process_ran
      raise_last_error("CreateProcess()")
    end

    # Automatically close the process and thread handles in the
    # PROCESS_INFORMATION struct unless explicitly told not to.
    if hash['close_handles']
      CloseHandle(procinfo[0,4].unpack('L').first)
      CloseHandle(procinfo[4,4].unpack('L').first)
    end

    ProcessInfo.new(
      procinfo[0,4].unpack('L').first, # hProcess
      procinfo[4,4].unpack('L').first, # hThread
      procinfo[8,4].unpack('L').first, # hProcessId
      procinfo[12,4].unpack('L').first # hThreadId
    )
  end