class WinRM::Transport::CommandExecutor

Object which can execute multiple commands and Powershell scripts in one shared remote shell session. The maximum number of commands per shell is determined by interrogating the remote host when the session is opened and the remote shell is automatically recycled before the threshold is reached.

@author Matt Wrock <matt@mattwrock.com> @author Fletcher Nichol <fnichol@nichol.ca>

Constants

LEGACY_LIMIT

@return [Integer] the default maximum number of commands which can be

executed in one remote shell session on "older" versions of Windows

@api private

MODERN_LIMIT

@return [Integer] the default maximum number of commands which can be

executed in one remote shell session on "modern" versions of Windows

@api private

PS1_OS_VERSION

@return [String] the PowerShell command used to determine the version

of Windows

@api private

Attributes

command_count[RW]

@return [Integer] the number of executed commands on the remote

shell session

@api private

logger[R]

@return [#debug,#info] the logger @api private

max_commands[R]

@return [Integer,nil] the safe maximum number of commands that can

be executed in one remote shell session, or `nil` if the
threshold has not yet been determined
service[R]

@return [WinRM::WinRMWebService] a WinRM web service object @api private

shell[R]

@return [String,nil] the identifier for the current open remote

shell session, or `nil` if the session is not open

Public Class Methods

new(service, logger = nil, closer = nil) click to toggle source

Creates a CommandExecutor given a `WinRM::WinRMWebService` object.

@param service [WinRM::WinRMWebService] a winrm web service object @param logger [#debug,#info] an optional logger/ui object that

responds to `#debug` and `#info` (default: `nil`)

@param closer [ShellCloser] an optional object to automatically

close the active open remote shell when CommandExecutor garbarge
collects
# File lib/winrm/transport/command_executor.rb, line 53
def initialize(service, logger = nil, closer = nil)
  @service        = service
  @logger         = logger
  @closer         = closer
  @command_count  = 0
end

Public Instance Methods

close() click to toggle source

Closes the open remote shell session. This method can be called multiple times, even if there is no open session.

# File lib/winrm/transport/command_executor.rb, line 62
def close
  return if shell.nil?

  service.close_shell(shell)
  remove_finalizer
  @shell = nil
end
open() click to toggle source

Opens a remote shell session for reuse. The maxiumum command-per-shell threshold is also determined the first time this method is invoked and cached for later invocations.

@return [String] the remote shell session indentifier

# File lib/winrm/transport/command_executor.rb, line 75
def open
  close
  @shell = service.open_shell
  add_finalizer(shell)
  @command_count = 0
  determine_max_commands unless max_commands
  shell
end
run_cmd(command, arguments = [], &block) click to toggle source

Runs a CMD command.

@param command [String] the command to run on the remote system @param arguments [Array<String>] arguments to the command @yield [stdout, stderr] yields more live access the standard

output and standard error streams as they are returns, if
streaming behavior is desired

@return [WinRM::Output] output object with stdout, stderr, and

exit code
# File lib/winrm/transport/command_executor.rb, line 93
def run_cmd(command, arguments = [], &block)
  reset if command_count_exceeded?
  ensure_open_shell!

  @command_count += 1
  result = nil
  service.run_command(shell, command, arguments) do |command_id|
    result = service.get_command_output(shell, command_id, &block)
  end
  result
end
run_powershell_script(script_file, &block) click to toggle source

Run a Powershell script that resides on the local box.

@param script_file [IO,String] an IO reference for reading the

Powershell script or the actual file contents

@yield [stdout, stderr] yields more live access the standard

output and standard error streams as they are returns, if
streaming behavior is desired

@return [WinRM::Output] output object with stdout, stderr, and

exit code
# File lib/winrm/transport/command_executor.rb, line 114
def run_powershell_script(script_file, &block)
  # this code looks overly compact in an attempt to limit local
  # variable assignments that may contain large strings and
  # consequently bloat the Ruby VM
  run_cmd(
    "powershell",
    [
      "-encodedCommand",
      ::WinRM::PowershellScript.new(
        script_file.is_a?(IO) ? script_file.read : script_file
      ).encoded
    ],
    &block
  )
end

Private Instance Methods

add_finalizer(shell_id) click to toggle source

Creates a finalizer for this connection which will close the open remote shell session when the object is garabage collected or on Ruby VM shutdown.

@param shell_id [String] the remote shell identifier @api private

# File lib/winrm/transport/command_executor.rb, line 166
def add_finalizer(shell_id)
  ObjectSpace.define_finalizer(self, @closer.for(shell_id)) if @closer
end
command_count_exceeded?() click to toggle source

@return [true,false] whether or not the number of exeecuted commands

have exceeded the maxiumum threshold

@api private

# File lib/winrm/transport/command_executor.rb, line 173
def command_count_exceeded?
  command_count > max_commands.to_i
end
determine_max_commands() click to toggle source

Determines the safe maximum number of commands that can be executed on a remote shell session by interrogating the remote host.

@api private

# File lib/winrm/transport/command_executor.rb, line 192
def determine_max_commands
  os_version = run_powershell_script(PS1_OS_VERSION).stdout.chomp
  @max_commands = os_version < "6.2" ? LEGACY_LIMIT : MODERN_LIMIT
  @max_commands -= 2 # to be safe
end
ensure_open_shell!() click to toggle source

Ensures that there is an open remote shell session.

@raise [WinRM::WinRMError] if there is no open shell @api private

# File lib/winrm/transport/command_executor.rb, line 181
def ensure_open_shell!
  if shell.nil?
    raise ::WinRM::WinRMError, "#{self.class}#open must be called " \
      "before any run methods are invoked"
  end
end
remove_finalizer() click to toggle source

Removes any finalizers for this connection.

@api private

# File lib/winrm/transport/command_executor.rb, line 201
def remove_finalizer
  ObjectSpace.undefine_finalizer(self) if @closer
end
reset() click to toggle source

Closes the remote shell session and opens a new one.

@api private

# File lib/winrm/transport/command_executor.rb, line 208
def reset
  debug {
    "Resetting WinRM shell (Max command limit is #{max_commands})"
  }
  open
end