diff options
-rw-r--r-- | lib/mixlib/shellout/windows.rb | 37 | ||||
-rw-r--r-- | mixlib-shellout.gemspec | 1 | ||||
-rw-r--r-- | spec/mixlib/shellout_spec.rb | 17 |
3 files changed, 51 insertions, 4 deletions
diff --git a/lib/mixlib/shellout/windows.rb b/lib/mixlib/shellout/windows.rb index 6003ae8..fe7fd19 100644 --- a/lib/mixlib/shellout/windows.rb +++ b/lib/mixlib/shellout/windows.rb @@ -20,8 +20,14 @@ require 'win32/process' require 'mixlib/shellout/windows/core_ext' +require 'mixlib/log' module Mixlib + class ShelloutLog + extend Mixlib::Log + init("#{ENV['temp']}/shellout.txt") + end + class ShellOut module Windows @@ -43,6 +49,7 @@ module Mixlib # Missing lots of features from the UNIX version, such as # uid, etc. def run_command + logger = ShelloutLog.logger # # Create pipes to capture stdout and stderr, @@ -90,6 +97,7 @@ module Mixlib # Wait for the process to finish, consuming output as we go # start_wait = Time.now + logger.warn(Utils.format_process(process, app_name, command_line, timeout)) if logger while true wait_status = WaitForSingleObject(process.process_handle, 0) case wait_status @@ -107,12 +115,15 @@ module Mixlib # Kill the process if (Time.now - start_wait) > timeout begin + require 'wmi-lite/wmi' + wmi = WmiLite::Wmi.new + Utils.kill_process_tree(process.process_id, wmi, logger) Process.kill(:KILL, process.process_id) - rescue Errno::EIO + rescue Errno::EIO, SystemCallError logger.warn("Failed to kill timed out process #{process.process_id}") if logger end - raise Mixlib::ShellOut::CommandTimeout, "command timed out:\n#{format_for_exception}" + raise Mixlib::ShellOut::CommandTimeout, "command timed out:\n#{format_for_exception}\n#{Utils.format_process(process, app_name, command_line, timeout)}" end consume_output(open_streams, stdout_read, stderr_read) @@ -313,6 +324,28 @@ module Mixlib def self.executable?(path) File.executable?(path) && !File.directory?(path) end + + def self.kill_process_tree(pid, wmi, logger) + wmi.query("select ProcessID, Name from Win32_Process where ParentProcessID=#{pid}").each do |instance| + child_pid = instance.wmi_ole_object.processid + kill_process_tree(child_pid, wmi, logger) + begin + logger.warn("killing child process #{child_pid}::#{instance.wmi_ole_object.Name} of parent #{pid}") if logger + Process.kill(:KILL, child_pid) + rescue Errno::EIO, SystemCallError + logger.warn("Failed to kill child process #{child_pid}::#{instance.wmi_ole_object.Name} of parent #{pid}") if logger + end + end + end + + def self.format_process(process, app_name, command_line, timeout) + msg = [] + msg << "ProcessId: #{process.process_id}" + msg << "app_name: #{app_name}" + msg << "command_line: #{command_line}" + msg << "timeout: #{timeout}" + msg.join("\n") + end end end # class end diff --git a/mixlib-shellout.gemspec b/mixlib-shellout.gemspec index e406c74..b02514b 100644 --- a/mixlib-shellout.gemspec +++ b/mixlib-shellout.gemspec @@ -14,6 +14,7 @@ Gem::Specification.new do |s| s.required_ruby_version = ">= 1.9.3" + s.add_dependency "wmi-lite", "~> 1.0" s.add_development_dependency "rspec", "~> 3.0" s.bindir = "bin" diff --git a/spec/mixlib/shellout_spec.rb b/spec/mixlib/shellout_spec.rb index 1c5ce31..6b57619 100644 --- a/spec/mixlib/shellout_spec.rb +++ b/spec/mixlib/shellout_spec.rb @@ -621,7 +621,7 @@ describe Mixlib::ShellOut do end context "when running under Windows", :windows_only do - let(:cmd) { 'whoami.exe' } + let(:cmd) { '%windir%/system32/whoami.exe' } let(:running_user) { shell_cmd.run_command.stdout.strip.downcase } context "when no user is set" do @@ -629,7 +629,7 @@ describe Mixlib::ShellOut do # to match how whoami returns the information it "should run as current user" do - expect(running_user).to eql("#{ENV['COMPUTERNAME'].downcase}\\#{ENV['USERNAME'].downcase}") + expect(running_user).to eql("#{ENV['USERDOMAIN'].downcase}\\#{ENV['USERNAME'].downcase}") end end @@ -1116,11 +1116,24 @@ describe Mixlib::ShellOut do 'powershell -c "sleep 10"' end + before do + require "wmi-lite/wmi" + allow(WmiLite::Wmi).to receive(:new) + allow(Mixlib::ShellOut::Windows::Utils).to receive(:kill_process_tree) + end + it "should raise CommandTimeout" do Timeout::timeout(5) do expect { executed_cmd }.to raise_error(Mixlib::ShellOut::CommandTimeout) end end + + context 'and child processes should be killed' do + it 'kills the child processes' do + expect(Mixlib::ShellOut::Windows::Utils).to receive(:kill_process_tree) + expect { executed_cmd }.to raise_error(Mixlib::ShellOut::CommandTimeout) + end + end end context 'on unix', :unix_only do |