Module: Mixlib::ShellOut::Windows

Includes:
Process::Constants, Process::Functions
Included in:
Mixlib::ShellOut, Utils
Defined in:
lib/mixlib/shellout/windows.rb

Defined Under Namespace

Classes: ThingThatLooksSortOfLikeAProcessStatus, Utils

Constant Summary collapse

TIME_SLICE =
0.05

Constants included from Process::Constants

Process::Constants::ENVIRONMENT_BLOCK_ENDS, Process::Constants::ERROR_LOGON_TYPE_NOT_GRANTED, Process::Constants::ERROR_PRIVILEGE_NOT_HELD, Process::Constants::LOGON32_LOGON_BATCH, Process::Constants::LOGON32_LOGON_INTERACTIVE, Process::Constants::LOGON32_PROVIDER_DEFAULT, Process::Constants::UOI_NAME, Process::Constants::WAIT_ABANDONED, Process::Constants::WAIT_ABANDONED_0, Process::Constants::WAIT_FAILED, Process::Constants::WAIT_OBJECT_0, Process::Constants::WAIT_TIMEOUT, Process::Constants::WIN32_PROFILETYPE_LOCAL, Process::Constants::WIN32_PROFILETYPE_PT_MANDATORY, Process::Constants::WIN32_PROFILETYPE_PT_ROAMING, Process::Constants::WIN32_PROFILETYPE_PT_ROAMING_PREEXISTING, Process::Constants::WIN32_PROFILETYPE_PT_TEMPORARY

Instance Method Summary collapse

Instance Method Details

#run_commandObject

– Missing lots of features from the UNIX version, such as uid, etc.



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/mixlib/shellout/windows.rb', line 55

def run_command
  #
  # Create pipes to capture stdout and stderr,
  #
  stdout_read, stdout_write = IO.pipe
  stderr_read, stderr_write = IO.pipe
  stdin_read, stdin_write = IO.pipe
  open_streams = [ stdout_read, stderr_read ]
  @execution_time = 0

  begin

    #
    # Set cwd, environment, appname, etc.
    #
    app_name, command_line = command_to_run(combine_args(*command))
    create_process_args = {
      app_name: app_name,
      command_line: command_line,
      startup_info: {
        stdout: stdout_write,
        stderr: stderr_write,
        stdin: stdin_read,
      },
      environment: inherit_environment.map { |k, v| "#{k}=#{v}" },
      close_handles: false,
    }
    create_process_args[:cwd] = cwd if cwd
    # default to local account database if domain is not specified
    create_process_args[:domain] = domain.nil? ? "." : domain
    create_process_args[:with_logon] = with_logon if with_logon
    create_process_args[:password] = password if password
    create_process_args[:elevated] = elevated if elevated

    #
    # Start the process
    #
    process, profile, token = Process.create3(create_process_args)
    logger&.debug(format_process(process, app_name, command_line, timeout))
    begin
      # Start pushing data into input
      stdin_write << input if input

      # Close pipe to kick things off
      stdin_write.close

      #
      # Wait for the process to finish, consuming output as we go
      #
      start_wait = Time.now
      loop do
        wait_status = WaitForSingleObject(process.process_handle, 0)
        case wait_status
        when WAIT_OBJECT_0
          # Save the execution time
          @execution_time = Time.now - start_wait
          # Get process exit code
          exit_code = [0].pack("l")
          unless GetExitCodeProcess(process.process_handle, exit_code)
            raise get_last_error
          end

          @status = ThingThatLooksSortOfLikeAProcessStatus.new
          @status.exitstatus = exit_code.unpack("l").first

          return self
        when WAIT_TIMEOUT
          # Kill the process
          if (Time.now - start_wait) > timeout
            begin
              require "wmi-lite/wmi"
              wmi = WmiLite::Wmi.new
              kill_process_tree(process.process_id, wmi, logger)
              Process.kill(:KILL, process.process_id)
            rescue SystemCallError
              logger&.warn("Failed to kill timed out process #{process.process_id}")
            end

            # Save the execution time
            @execution_time = Time.now - start_wait

            raise Mixlib::ShellOut::CommandTimeout, [
              "command timed out:",
              format_for_exception,
              format_process(process, app_name, command_line, timeout),
            ].join("\n")
          end

          consume_output(open_streams, stdout_read, stderr_read)
        else
          raise "Unknown response from WaitForSingleObject(#{process.process_handle}, #{timeout * 1000}): #{wait_status}"
        end

      end

    ensure
      CloseHandle(process.thread_handle) if process.thread_handle
      CloseHandle(process.process_handle) if process.process_handle
      Process.(token, profile) if profile
      CloseHandle(token) if token
    end

  ensure
    #
    # Consume all remaining data from the pipes until they are closed
    #
    stdout_write.close
    stderr_write.close

    while consume_output(open_streams, stdout_read, stderr_read)
    end
  end
end

#validate_options(opts) ⇒ Object

Option validation that is windows specific



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/mixlib/shellout/windows.rb', line 34

def validate_options(opts)
  if opts[:user] && !opts[:password]
    raise InvalidCommandOption, "You must supply a password when supplying a user in windows"
  end

  if !opts[:user] && opts[:password]
    raise InvalidCommandOption, "You must supply a user when supplying a password in windows"
  end

  if opts[:elevated] && !opts[:user] && !opts[:password]
    raise InvalidCommandOption, "`elevated` option should be passed only with `username` and `password`."
  end

  if opts[:elevated] && opts[:elevated] != true && opts[:elevated] != false
    raise InvalidCommandOption, "Invalid value passed for `elevated`. Please provide true/false."
  end
end