Skip to content

Commit

Permalink
Allow timeout option for WinRM commands
Browse files Browse the repository at this point in the history
Allows end users (e.g. InSpec test coders) to specify a timeout for a potentially long running command.
If the timeout is reached, the command is expected to be terminated on the host and an exception is raised (a subsequent change to InSpec will handle this exception).
This complements recent changes to the base connection and ssh connection in train: inspec/train#625

Signed-off-by: James Stocks <[email protected]>
  • Loading branch information
James Stocks committed Sep 28, 2020
1 parent a7818c7 commit f2ff783
Showing 1 changed file with 36 additions and 4 deletions.
40 changes: 36 additions & 4 deletions lib/train-winrm/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,15 +107,47 @@ def file_via_connection(path)
Train::File::Remote::Windows.new(self, path)
end

def run_command_via_connection(command, &data_handler)
def run_command_via_connection(command, opts = {}, &data_handler)
return if command.nil?

logger.debug("[WinRM] #{self} (#{command})")
out = ""
response = nil
timeout = opts[:timeout]&.to_i

# Run the command in a thread, to support timing out the command
thr = Thread.new do
# Surface any exceptions in this thread back to this method
Thread.current.report_on_exception = false
Thread.current.abort_on_exception = true
begin
response = session.run(command) do |stdout, _|
yield(stdout) if data_handler && stdout
out << stdout if stdout
end
rescue RuntimeError => e
# Ref: https://github.com/WinRb/WinRM/issues/315
# If this command hits timeout, calling close with the command currently running causes
# a RuntimeError error in WinRM's cleanup code. This specific error can be ignored.
# The command will be terminated and further commands can be sent on the connection.
raise e unless timeout && e.to_s == "opts[:shell_id] is required"
rescue WinRM::WinRMHTTPTransportError => e
# If this command hits timeout, there is also a potential race in the HTTP transport
# where decryption is attempted on an empty message.
raise e unless timeout && e.to_s == "Could not decrypt NTLM message. ()."
end
end

response = session.run(command) do |stdout, _|
yield(stdout) if data_handler && stdout
out << stdout if stdout
if timeout
res = thr.join(timeout)
unless res
msg = "PowerShell command '(#{command})' reached timeout (#{timeout}s)"
logger.info("[WinRM] #{msg}")
close
raise Train::CommandTimeoutReached.new msg
end
else
thr.join
end

CommandResult.new(out, response.stderr, response.exitcode)
Expand Down

0 comments on commit f2ff783

Please sign in to comment.