Class: WinRM::Shells::Base

Inherits:
Object
  • Object
show all
Includes:
Retryable
Defined in:
lib/winrm/shells/base.rb

Overview

Base class for remote shell

Direct Known Subclasses

Cmd, Powershell

Constant Summary collapse

TOO_MANY_COMMANDS =
'2150859174'.freeze
ERROR_OPERATION_ABORTED =
'995'.freeze
SHELL_NOT_FOUND =
'2150858843'.freeze
FAULTS_FOR_RESET =
[
  '2150858843', # Shell has been closed
  '2147943418', # Error reading registry key
  TOO_MANY_COMMANDS, # Maximum commands per user exceeded
].freeze

Constants included from Retryable

Retryable::RETRYABLE_EXCEPTIONS

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Retryable

#retryable

Constructor Details

#initialize(connection_opts, transport, logger, shell_opts = {}) ⇒ Base

Create a new Cmd shell

Parameters:

  • The WinRM connection options

  • The WinRM SOAP transport

  • The logger to log diagnostic messages to

  • (defaults to: {})

    Options targeted for the created shell



46
47
48
49
50
51
# File 'lib/winrm/shells/base.rb', line 46

def initialize(connection_opts, transport, logger, shell_opts = {})
  @connection_opts = connection_opts
  @transport = transport
  @logger = logger
  @shell_opts = shell_opts
end

Instance Attribute Details

#connection_optsConnectionOpts (readonly)

Returns connection options of the shell.

Returns:

  • connection options of the shell



60
61
62
# File 'lib/winrm/shells/base.rb', line 60

def connection_opts
  @connection_opts
end

#loggerLogger (readonly)

Returns logger used for diagnostic messages.

Returns:

  • logger used for diagnostic messages



66
67
68
# File 'lib/winrm/shells/base.rb', line 66

def logger
  @logger
end

#shell_idString (readonly)

Returns shell id of the currently opn shell or nil if shell is closed.

Returns:

  • shell id of the currently opn shell or nil if shell is closed



54
55
56
# File 'lib/winrm/shells/base.rb', line 54

def shell_id
  @shell_id
end

#shell_optsHash (readonly)

Returns Options targeted for the created shell.

Returns:

  • Options targeted for the created shell



69
70
71
# File 'lib/winrm/shells/base.rb', line 69

def shell_opts
  @shell_opts
end

#shell_uriString (readonly)

Returns uri that SOAP calls use to identify shell type.

Returns:

  • uri that SOAP calls use to identify shell type



57
58
59
# File 'lib/winrm/shells/base.rb', line 57

def shell_uri
  @shell_uri
end

#transportWinRM::HTTP::HttpTransport (readonly)

Returns transport used to talk with endpoint.

Returns:

  • transport used to talk with endpoint



63
64
65
# File 'lib/winrm/shells/base.rb', line 63

def transport
  @transport
end

Class Method Details

.finalize(connection_opts, transport, shell_id) ⇒ Object

Ruby 3.1+ introduced a change where finalizers cannot allocate new threads. This can raise a ThreadError when the WinRM shell cleanup tries to run in a Thread during garbage collection. Previously, the gem used Thread.new in finalizers.

Our solution preserves original behavior safely:

  1. Attempt cleanup in a new thread (same as before) for normal execution.
  2. If a ThreadError occurs specifically due to "can't alloc thread", defer cleanup to an at_exit block to ensure the shell is closed properly without crashing the program.
  3. Any other ThreadError is re-raised to avoid hiding unexpected issues.


107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/winrm/shells/base.rb', line 107

def self.finalize(connection_opts, transport, shell_id)
  proc do
    begin
      # Attempt normal cleanup in a new thread (preserves existing behavior)
      Thread.new { close_shell(connection_opts, transport, shell_id) }
    rescue ThreadError => e
      # Only handle the specific Ruby 3.1+ GC thread allocation restriction
      raise unless e.message.include?("can't alloc thread")

      # Defer cleanup to at_exit when thread allocation is permitted
      at_exit do
        begin
          close_shell(connection_opts, transport, shell_id)
        rescue StandardError => cleanup_error
          # Warn about cleanup failures in deferred context only
          warn "[WinRM] Deferred shell cleanup failed: #{cleanup_error.class}: #{cleanup_error.message}"
        end
      end
    end
  end
end

Instance Method Details

#closeObject

Closes the shell if one is open



85
86
87
88
89
90
91
92
93
94
95
# File 'lib/winrm/shells/base.rb', line 85

def close
  return unless shell_id

  begin
    self.class.close_shell(connection_opts, transport, shell_id)
  rescue WinRMWSManFault => e
    raise unless [ERROR_OPERATION_ABORTED, SHELL_NOT_FOUND].include?(e.fault_code)
  end
  remove_finalizer
  @shell_id = nil
end

#run(command, arguments = [], &block) {|standard, standard| ... } ⇒ Object

Runs the specified command with optional arguments

Parameters:

  • The command or executable to run

  • (defaults to: [])

    The optional command arguments

  • The optional callback for any realtime output

Yield Parameters:

  • standard (string)

    out response text

  • standard (string)

    error response text

Yield Returns:



78
79
80
81
82
# File 'lib/winrm/shells/base.rb', line 78

def run(command, arguments = [], &block)
  with_command_shell(command, arguments) do |shell, cmd|
    response_reader.read_output(command_output_message(shell, cmd), &block)
  end
end