winrm 1.8.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +10 -11
  3. data/.rubocop.yml +26 -22
  4. data/.travis.yml +11 -12
  5. data/Gemfile +3 -9
  6. data/LICENSE +202 -202
  7. data/README.md +232 -215
  8. data/Rakefile +34 -36
  9. data/Vagrantfile +6 -9
  10. data/WinrmAppveyor.psm1 +31 -31
  11. data/appveyor.yml +51 -51
  12. data/bin/rwinrm +97 -97
  13. data/changelog.md +86 -86
  14. data/lib/winrm.rb +39 -42
  15. data/lib/winrm/connection.rb +82 -0
  16. data/lib/winrm/connection_opts.rb +87 -0
  17. data/lib/winrm/{exceptions/exceptions.rb → exceptions.rb} +76 -57
  18. data/lib/winrm/http/response_handler.rb +96 -82
  19. data/lib/winrm/http/transport.rb +424 -435
  20. data/lib/winrm/http/transport_factory.rb +68 -0
  21. data/lib/winrm/output.rb +59 -43
  22. data/lib/winrm/psrp/create_pipeline.xml.erb +167 -0
  23. data/lib/winrm/psrp/fragment.rb +70 -0
  24. data/lib/winrm/psrp/init_runspace_pool.xml.erb +224 -0
  25. data/lib/winrm/psrp/message.rb +130 -0
  26. data/lib/winrm/psrp/message_data.rb +41 -0
  27. data/lib/winrm/psrp/message_data/base.rb +49 -0
  28. data/lib/winrm/psrp/message_data/error_record.rb +68 -0
  29. data/lib/winrm/psrp/message_data/pipeline_host_call.rb +32 -0
  30. data/lib/winrm/psrp/message_data/pipeline_output.rb +49 -0
  31. data/lib/winrm/psrp/message_data/runspacepool_host_call.rb +32 -0
  32. data/lib/winrm/psrp/message_data/runspacepool_state.rb +39 -0
  33. data/lib/winrm/psrp/message_data/session_capability.rb +36 -0
  34. data/lib/winrm/psrp/message_defragmenter.rb +62 -0
  35. data/lib/winrm/psrp/message_factory.rb +75 -0
  36. data/lib/winrm/psrp/message_fragmenter.rb +60 -0
  37. data/lib/winrm/psrp/powershell_output_decoder.rb +120 -0
  38. data/lib/winrm/psrp/receive_response_reader.rb +93 -0
  39. data/lib/winrm/psrp/session_capability.xml.erb +7 -0
  40. data/lib/winrm/psrp/uuid.rb +40 -0
  41. data/lib/winrm/shells/base.rb +175 -0
  42. data/lib/winrm/shells/cmd.rb +65 -0
  43. data/lib/winrm/shells/power_shell.rb +201 -0
  44. data/lib/winrm/shells/retryable.rb +45 -0
  45. data/lib/winrm/shells/shell_factory.rb +50 -0
  46. data/lib/winrm/version.rb +7 -7
  47. data/lib/winrm/wsmv/base.rb +59 -0
  48. data/lib/winrm/wsmv/cleanup_command.rb +61 -0
  49. data/lib/winrm/wsmv/close_shell.rb +50 -0
  50. data/lib/winrm/wsmv/command.rb +101 -0
  51. data/lib/winrm/wsmv/command_output.rb +76 -0
  52. data/lib/winrm/wsmv/command_output_decoder.rb +55 -0
  53. data/lib/winrm/wsmv/configuration.rb +46 -0
  54. data/lib/winrm/wsmv/create_pipeline.rb +66 -0
  55. data/lib/winrm/wsmv/create_shell.rb +119 -0
  56. data/lib/winrm/wsmv/header.rb +203 -0
  57. data/lib/winrm/wsmv/init_runspace_pool.rb +95 -0
  58. data/lib/winrm/wsmv/iso8601_duration.rb +60 -0
  59. data/lib/winrm/wsmv/keep_alive.rb +68 -0
  60. data/lib/winrm/wsmv/receive_response_reader.rb +128 -0
  61. data/lib/winrm/wsmv/send_data.rb +68 -0
  62. data/lib/winrm/wsmv/soap.rb +51 -0
  63. data/lib/winrm/wsmv/wql_query.rb +79 -0
  64. data/lib/winrm/wsmv/write_stdin.rb +88 -0
  65. data/preamble +17 -17
  66. data/{spec → tests/integration}/auth_timeout_spec.rb +18 -16
  67. data/{spec → tests/integration}/cmd_spec.rb +104 -102
  68. data/{spec → tests/integration}/config-example.yml +16 -19
  69. data/{spec → tests/integration}/issue_59_spec.rb +26 -23
  70. data/tests/integration/powershell_spec.rb +154 -0
  71. data/{spec → tests/integration}/spec_helper.rb +65 -73
  72. data/{spec → tests/integration}/transport_spec.rb +99 -139
  73. data/{spec → tests/integration}/wql_spec.rb +16 -14
  74. data/{spec → tests}/matchers.rb +60 -74
  75. data/tests/spec/configuration_spec.rb +93 -0
  76. data/tests/spec/connection_spec.rb +39 -0
  77. data/{spec → tests/spec}/exception_spec.rb +50 -50
  78. data/tests/spec/http/transport_factory_spec.rb +68 -0
  79. data/tests/spec/http/transport_spec.rb +44 -0
  80. data/{spec → tests/spec}/output_spec.rb +127 -110
  81. data/tests/spec/psrp/fragment_spec.rb +62 -0
  82. data/tests/spec/psrp/message_data/base_spec.rb +13 -0
  83. data/tests/spec/psrp/message_data/error_record_spec.rb +41 -0
  84. data/tests/spec/psrp/message_data/pipeline_host_call_spec.rb +25 -0
  85. data/tests/spec/psrp/message_data/pipeline_output_spec.rb +32 -0
  86. data/tests/spec/psrp/message_data/runspace_pool_host_call_spec.rb +25 -0
  87. data/tests/spec/psrp/message_data/runspacepool_state_spec.rb +16 -0
  88. data/tests/spec/psrp/message_data/session_capability_spec.rb +30 -0
  89. data/tests/spec/psrp/message_data_spec.rb +35 -0
  90. data/tests/spec/psrp/message_defragmenter_spec.rb +47 -0
  91. data/tests/spec/psrp/message_fragmenter_spec.rb +105 -0
  92. data/tests/spec/psrp/powershell_output_decoder_spec.rb +84 -0
  93. data/tests/spec/psrp/psrp_message_spec.rb +70 -0
  94. data/tests/spec/psrp/recieve_response_reader_spec.rb +154 -0
  95. data/tests/spec/psrp/uuid_spec.rb +28 -0
  96. data/{spec → tests/spec}/response_handler_spec.rb +61 -61
  97. data/tests/spec/shells/base_spec.rb +202 -0
  98. data/tests/spec/shells/cmd_spec.rb +75 -0
  99. data/tests/spec/shells/powershell_spec.rb +175 -0
  100. data/tests/spec/spec_helper.rb +47 -0
  101. data/tests/spec/stubs/clixml/error_record.xml.erb +84 -0
  102. data/{spec → tests/spec}/stubs/responses/get_command_output_response.xml.erb +13 -13
  103. data/tests/spec/stubs/responses/get_command_output_response_not_done.xml.erb +10 -0
  104. data/tests/spec/stubs/responses/get_powershell_keepalive_response.xml.erb +10 -0
  105. data/tests/spec/stubs/responses/get_powershell_output_response.xml.erb +12 -0
  106. data/tests/spec/stubs/responses/get_powershell_output_response_not_done.xml.erb +9 -0
  107. data/{spec → tests/spec}/stubs/responses/open_shell_v1.xml +19 -19
  108. data/{spec → tests/spec}/stubs/responses/open_shell_v2.xml +20 -20
  109. data/{spec → tests/spec}/stubs/responses/soap_fault_v1.xml +36 -36
  110. data/{spec → tests/spec}/stubs/responses/soap_fault_v2.xml +42 -42
  111. data/{spec → tests/spec}/stubs/responses/wmi_error_v2.xml +41 -41
  112. data/tests/spec/wsmv/cleanup_command_spec.rb +22 -0
  113. data/tests/spec/wsmv/close_shell_spec.rb +17 -0
  114. data/{spec → tests/spec/wsmv}/command_output_decoder_spec.rb +37 -37
  115. data/tests/spec/wsmv/command_output_spec.rb +45 -0
  116. data/tests/spec/wsmv/command_spec.rb +19 -0
  117. data/tests/spec/wsmv/configuration_spec.rb +17 -0
  118. data/tests/spec/wsmv/create_pipeline_spec.rb +31 -0
  119. data/tests/spec/wsmv/create_shell_spec.rb +38 -0
  120. data/tests/spec/wsmv/init_runspace_pool_spec.rb +36 -0
  121. data/tests/spec/wsmv/keep_alive_spec.rb +21 -0
  122. data/tests/spec/wsmv/receive_response_reader_spec.rb +123 -0
  123. data/tests/spec/wsmv/send_data_spec.rb +30 -0
  124. data/tests/spec/wsmv/wql_query_spec.rb +13 -0
  125. data/tests/spec/wsmv/write_stdin_spec.rb +22 -0
  126. data/winrm.gemspec +42 -40
  127. metadata +140 -38
  128. data/.rspec +0 -3
  129. data/lib/winrm/command_executor.rb +0 -243
  130. data/lib/winrm/command_output_decoder.rb +0 -53
  131. data/lib/winrm/helpers/iso8601_duration.rb +0 -58
  132. data/lib/winrm/helpers/powershell_script.rb +0 -42
  133. data/lib/winrm/soap_provider.rb +0 -39
  134. data/lib/winrm/winrm_service.rb +0 -550
  135. data/spec/command_executor_spec.rb +0 -475
  136. data/spec/issue_184_spec.rb +0 -67
  137. data/spec/powershell_spec.rb +0 -97
  138. data/spec/winrm_options_spec.rb +0 -76
  139. data/spec/winrm_primitives_spec.rb +0 -51
@@ -0,0 +1,28 @@
1
+ # encoding: UTF-8
2
+ describe WinRM::PSRP::UUID do
3
+ subject(:uuid_helper) do
4
+ Object.new.extend(WinRM::PSRP::UUID)
5
+ end
6
+ context 'uuid is nil' do
7
+ uuid = nil
8
+ it 'should return an empty byte array' do
9
+ bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
10
+ expect(bytes).to eq([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
11
+ end
12
+ end
13
+ context 'uuid 08785e96-eb1b-4a74-a767-7b56e8f13ea9 with lower case letters' do
14
+ uuid = '08785e96-eb1b-4a74-a767-7b56e8f13ea9'
15
+ it 'should return a Windows GUID struct compatible little endian byte array' do
16
+ bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
17
+ expect(bytes).to eq([150, 94, 120, 8, 27, 235, 116, 74, 167, 103, 123, 86, 232, 241, 62, 169])
18
+ end
19
+ end
20
+ context 'uuid 045F9E19D-8B77-4394-AB0C-197497661668 with upper case letters' do
21
+ uuid = '45F9E19D-8B77-4394-AB0C-197497661668'
22
+ it 'should return a Windows GUID struct compatible little endian byte array' do
23
+ bytes = uuid_helper.uuid_to_windows_guid_bytes(uuid)
24
+ expect(bytes).to eq(
25
+ [157, 225, 249, 69, 119, 139, 148, 67, 171, 12, 25, 116, 151, 102, 22, 104])
26
+ end
27
+ end
28
+ end
@@ -1,61 +1,61 @@
1
- # encoding: UTF-8
2
- require 'winrm/http/response_handler'
3
-
4
- describe 'response handler', unit: true do
5
- %w(v1 v2).each do |winrm_version|
6
- context "winrm_version #{winrm_version}" do
7
- let(:soap_fault) { File.read("spec/stubs/responses/soap_fault_#{winrm_version}.xml") }
8
- let(:open_shell) { File.read("spec/stubs/responses/open_shell_#{winrm_version}.xml") }
9
-
10
- describe "successful 200 #{winrm_version} response" do
11
- it 'returns an xml doc' do
12
- handler = WinRM::ResponseHandler.new(open_shell, 200)
13
- xml_doc = handler.parse_to_xml
14
- expect(xml_doc).to be_instance_of(REXML::Document)
15
- expect(xml_doc.to_s).to eq(REXML::Document.new(open_shell).to_s)
16
- end
17
- end
18
-
19
- describe "failed 500 #{winrm_version} response" do
20
- it 'raises a WinRMHTTPTransportError' do
21
- handler = WinRM::ResponseHandler.new('', 500)
22
- expect { handler.parse_to_xml }.to raise_error(WinRM::WinRMHTTPTransportError)
23
- end
24
- end
25
-
26
- describe "failed 401 #{winrm_version} response" do
27
- it 'raises a WinRMAuthorizationError' do
28
- handler = WinRM::ResponseHandler.new('', 401)
29
- expect { handler.parse_to_xml }.to raise_error(WinRM::WinRMAuthorizationError)
30
- end
31
- end
32
-
33
- describe "failed 400 #{winrm_version} response" do
34
- it 'raises a WinRMWSManFault' do
35
- handler = WinRM::ResponseHandler.new(soap_fault, 400)
36
- begin
37
- handler.parse_to_xml
38
- rescue WinRM::WinRMWSManFault => e
39
- expect(e.fault_code).to eq('2150858778')
40
- expect(e.fault_description).to include(
41
- 'The specified class does not exist in the given namespace')
42
- end
43
- end
44
- end
45
- end
46
- end
47
-
48
- describe 'failed 500 WMI error response' do
49
- let(:wmi_error) { File.read('spec/stubs/responses/wmi_error_v2.xml') }
50
-
51
- it 'raises a WinRMWMIError' do
52
- handler = WinRM::ResponseHandler.new(wmi_error, 500)
53
- begin
54
- handler.parse_to_xml
55
- rescue WinRM::WinRMWMIError => e
56
- expect(e.error_code).to eq('2150859173')
57
- expect(e.error).to include('The WS-Management service cannot process the request.')
58
- end
59
- end
60
- end
61
- end
1
+ # encoding: UTF-8
2
+ require 'winrm/http/response_handler'
3
+
4
+ describe 'response handler', unit: true do
5
+ %w(v1 v2).each do |winrm_version|
6
+ context "winrm_version #{winrm_version}" do
7
+ let(:soap_fault) { stubbed_response("soap_fault_#{winrm_version}.xml") }
8
+ let(:open_shell) { stubbed_response("open_shell_#{winrm_version}.xml") }
9
+
10
+ describe "successful 200 #{winrm_version} response" do
11
+ it 'returns an xml doc' do
12
+ handler = WinRM::ResponseHandler.new(open_shell, 200)
13
+ xml_doc = handler.parse_to_xml
14
+ expect(xml_doc).to be_instance_of(REXML::Document)
15
+ expect(xml_doc.to_s).to eq(REXML::Document.new(open_shell).to_s)
16
+ end
17
+ end
18
+
19
+ describe "failed 500 #{winrm_version} response" do
20
+ it 'raises a WinRMHTTPTransportError' do
21
+ handler = WinRM::ResponseHandler.new('', 500)
22
+ expect { handler.parse_to_xml }.to raise_error(WinRM::WinRMHTTPTransportError)
23
+ end
24
+ end
25
+
26
+ describe "failed 401 #{winrm_version} response" do
27
+ it 'raises a WinRMAuthorizationError' do
28
+ handler = WinRM::ResponseHandler.new('', 401)
29
+ expect { handler.parse_to_xml }.to raise_error(WinRM::WinRMAuthorizationError)
30
+ end
31
+ end
32
+
33
+ describe "failed 400 #{winrm_version} response" do
34
+ it 'raises a WinRMWSManFault' do
35
+ handler = WinRM::ResponseHandler.new(soap_fault, 400)
36
+ begin
37
+ handler.parse_to_xml
38
+ rescue WinRM::WinRMWSManFault => e
39
+ expect(e.fault_code).to eq('2150858778')
40
+ expect(e.fault_description).to include(
41
+ 'The specified class does not exist in the given namespace')
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ describe 'failed 500 WMI error response' do
49
+ let(:wmi_error) { stubbed_response('wmi_error_v2.xml') }
50
+
51
+ it 'raises a WinRMWMIError' do
52
+ handler = WinRM::ResponseHandler.new(wmi_error, 500)
53
+ begin
54
+ handler.parse_to_xml
55
+ rescue WinRM::WinRMWMIError => e
56
+ expect(e.error_code).to eq('2150859173')
57
+ expect(e.error).to include('The WS-Management service cannot process the request.')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,202 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'winrm/shells/base'
4
+
5
+ # Dummy shell class
6
+ class DummyShell < WinRM::Shells::Base
7
+ class << self
8
+ def finalize(connection_opts, transport, shell_id)
9
+ proc { DummyShell.close_shell(connection_opts, transport, shell_id) }
10
+ end
11
+
12
+ def close_shell(_connection_opts, _transport, _shell_id)
13
+ @closed = true
14
+ end
15
+
16
+ def closed?
17
+ @closed
18
+ end
19
+ end
20
+
21
+ def open_shell
22
+ @closed = false
23
+ 'shell_id'
24
+ end
25
+
26
+ def send_command(_command, _arguments)
27
+ 'command_id'
28
+ end
29
+
30
+ def out_streams
31
+ %w(std)
32
+ end
33
+ end
34
+
35
+ describe DummyShell do
36
+ let(:retry_limit) { 1 }
37
+ let(:shell_id) { 'shell_id' }
38
+ let(:output) { 'output' }
39
+ let(:command_id) { 'command_id' }
40
+ let(:payload) { 'message_payload' }
41
+ let(:command) { 'command' }
42
+ let(:arguments) { ['args'] }
43
+ let(:output_message) { double('output_message', build: 'output_message') }
44
+ let(:connection_options) { { max_commands: 100, retry_limit: retry_limit, retry_delay: 0 } }
45
+ let(:transport) { double('transport') }
46
+ let(:reader) { double('reader') }
47
+
48
+ before do
49
+ allow(subject).to receive(:response_reader).and_return(reader)
50
+ allow(subject).to receive(:command_output_message).with(shell_id, command_id)
51
+ .and_return(output_message)
52
+ allow(reader).to receive(:read_output).with(output_message).and_return(output)
53
+ allow(transport).to receive(:send_request)
54
+ end
55
+
56
+ subject { described_class.new(connection_options, transport, Logging.logger['test']) }
57
+
58
+ shared_examples 'retry shell command' do
59
+ it 'only closes the shell if there are too many' do
60
+ if fault == WinRM::Shells::Base::TOO_MANY_COMMANDS
61
+ expect(DummyShell).to receive(:close_shell)
62
+ else
63
+ expect(DummyShell).not_to receive(:close_shell)
64
+ end
65
+
66
+ subject.run(command, arguments)
67
+ end
68
+
69
+ it 'opens a new shell' do
70
+ expect(subject).to receive(:open).and_call_original.twice
71
+
72
+ subject.run(command, arguments)
73
+ end
74
+
75
+ it 'retries the command once' do
76
+ expect(subject).to receive(:send_command).twice
77
+
78
+ subject.run(command, arguments)
79
+ end
80
+ end
81
+
82
+ describe '#run' do
83
+ it 'opens a shell' do
84
+ subject.run(command, arguments)
85
+ expect(subject.shell_id).not_to be nil
86
+ end
87
+
88
+ it 'returns output from generated command' do
89
+ expect(subject.run(command, arguments)).to eq output
90
+ end
91
+
92
+ it 'sends cleanup message through transport' do
93
+ allow(SecureRandom).to receive(:uuid).and_return('uuid')
94
+ expect(transport).to receive(:send_request)
95
+ .with(
96
+ WinRM::WSMV::CleanupCommand.new(
97
+ connection_options,
98
+ shell_uri: nil,
99
+ shell_id: shell_id,
100
+ command_id: command_id
101
+ ).build
102
+ )
103
+ subject.run(command, arguments)
104
+ end
105
+
106
+ it 'does not error if cleanup is aborted' do
107
+ allow(SecureRandom).to receive(:uuid).and_return('uuid')
108
+ expect(transport).to receive(:send_request)
109
+ .with(
110
+ WinRM::WSMV::CleanupCommand.new(
111
+ connection_options,
112
+ shell_uri: nil,
113
+ shell_id: shell_id,
114
+ command_id: command_id
115
+ ).build
116
+ ).and_raise(WinRM::WinRMWSManFault.new('oops', '995'))
117
+ subject.run(command, arguments)
118
+ end
119
+
120
+ it 'opens a shell only once when shell is already open' do
121
+ expect(subject).to receive(:open_shell).and_call_original.once
122
+ subject.run(command, arguments)
123
+ subject.run(command, arguments)
124
+ end
125
+
126
+ describe 'connection resets' do
127
+ before do
128
+ @times_called = 0
129
+
130
+ allow(subject).to receive(:send_command) do
131
+ @times_called += 1
132
+ raise WinRM::WinRMWSManFault.new('oops', fault) if @times_called == 1
133
+ command_id
134
+ end
135
+ end
136
+
137
+ context 'when shell is closed on server' do
138
+ let(:fault) { '2150858843' }
139
+
140
+ include_examples 'retry shell command'
141
+ end
142
+
143
+ context 'when shell accesses a deleted registry key' do
144
+ let(:fault) { '2147943418' }
145
+
146
+ include_examples 'retry shell command'
147
+ end
148
+
149
+ context 'when maximum number of concurrent shells is exceeded' do
150
+ let(:fault) { '2150859174' }
151
+
152
+ include_examples 'retry shell command'
153
+ end
154
+ end
155
+
156
+ context 'open_shell fails' do
157
+ let(:retry_limit) { 2 }
158
+ let(:output_message2) { double('message') }
159
+
160
+ it 'retries and raises failure if it never succeeds' do
161
+ expect(subject).to receive(:open_shell)
162
+ .and_raise(Errno::ECONNREFUSED).exactly(retry_limit).times
163
+ expect { subject.run(command) }.to raise_error(Errno::ECONNREFUSED)
164
+ end
165
+
166
+ it 'retries and returns shell on success' do
167
+ @times = 0
168
+ allow(subject).to receive(:command_output_message).with('shell_id 2', command_id)
169
+ .and_return(output_message2)
170
+ allow(reader).to receive(:read_output)
171
+ .with(output_message2).and_return(output)
172
+ allow(subject).to receive(:open_shell) do
173
+ @times += 1
174
+ raise(Errno::ECONNREFUSED) if @times == 1
175
+ "shell_id #{@times}"
176
+ end
177
+
178
+ subject.run(command, arguments)
179
+ expect(subject.shell_id).to eq 'shell_id 2'
180
+ end
181
+ end
182
+ end
183
+
184
+ describe '#close' do
185
+ it 'does not close if not opened' do
186
+ expect(DummyShell).not_to receive(:close_shell)
187
+ subject.close
188
+ end
189
+
190
+ it 'close shell if opened' do
191
+ subject.run(command, arguments)
192
+ subject.close
193
+ expect(DummyShell.closed?).to be(true)
194
+ end
195
+
196
+ it 'nils out the shell_id' do
197
+ subject.run(command, arguments)
198
+ subject.close
199
+ expect(subject.shell_id).to be(nil)
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,75 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'winrm/shells/cmd'
4
+
5
+ describe WinRM::Shells::Cmd do
6
+ let(:retry_limit) { 1 }
7
+ let(:shell_id) { 'shell_id' }
8
+ let(:output) { 'output' }
9
+ let(:create_shell_payload) { 'create_shell_payload' }
10
+ let(:close_shell_payload) { 'close_shell_payload' }
11
+ let(:cleanup_payload) { 'cleanup_payload' }
12
+ let(:command) { 'run this command' }
13
+ let(:arguments) { ['args'] }
14
+ let(:command_response) { "<a xmlns:rsp='foo'><rsp:CommandId>command_id</rsp:CommandId></a>" }
15
+ let(:connection_options) { { max_commands: 100, retry_limit: retry_limit, retry_delay: 0 } }
16
+ let(:transport) { double('transport', send_request: nil) }
17
+
18
+ before do
19
+ allow_any_instance_of(WinRM::WSMV::CloseShell).to receive(:build)
20
+ .and_return(close_shell_payload)
21
+ allow_any_instance_of(WinRM::WSMV::CreateShell).to receive(:build)
22
+ .and_return(create_shell_payload)
23
+ allow_any_instance_of(WinRM::WSMV::CleanupCommand).to receive(:build)
24
+ .and_return(cleanup_payload)
25
+ allow_any_instance_of(WinRM::WSMV::ReceiveResponseReader).to receive(:read_output)
26
+ .and_return(output)
27
+ allow(transport).to receive(:send_request).with(create_shell_payload)
28
+ .and_return(REXML::Document.new("<blah Name='ShellId'>#{shell_id}</blah>"))
29
+ allow(transport).to receive(:send_request).with(/#{command}/)
30
+ .and_return(REXML::Document.new(command_response))
31
+ end
32
+
33
+ subject { described_class.new(connection_options, transport, Logging.logger['test']) }
34
+
35
+ describe '#run' do
36
+ it 'opens a shell and gets shell id' do
37
+ subject.run(command, arguments)
38
+ expect(subject.shell_id).to eq shell_id
39
+ end
40
+
41
+ it 'sends create shell through transport' do
42
+ expect(transport).to receive(:send_request).with(create_shell_payload)
43
+ subject.run(command, arguments)
44
+ end
45
+
46
+ it 'returns output from generated command' do
47
+ expect(subject.run(command, arguments)).to eq output
48
+ end
49
+
50
+ it 'sends command through transport' do
51
+ expect(transport).to receive(:send_request).with(/#{command}/)
52
+ subject.run(command, arguments)
53
+ end
54
+
55
+ it 'sends cleanup message through transport' do
56
+ expect(transport).to receive(:send_request).with(cleanup_payload)
57
+ subject.run(command, arguments)
58
+ end
59
+ end
60
+
61
+ describe '#close' do
62
+ it 'sends close shell through transport' do
63
+ subject.run(command, arguments)
64
+ expect(transport).to receive(:send_request).with(close_shell_payload)
65
+ subject.close
66
+ end
67
+
68
+ it 'creates a shell closer with default shell uri' do
69
+ allow(WinRM::WSMV::CloseShell).to receive(:new) do |_, opts|
70
+ expect(opts[:shell_uri]).to be nil
71
+ end.and_call_original
72
+ subject.close
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,175 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'winrm/shells/power_shell'
4
+
5
+ describe WinRM::Shells::Powershell do
6
+ let(:retry_limit) { 1 }
7
+ let(:max_envelope_size_kb) { 150 }
8
+ let(:shell_id) { 'bc1bfbba-8215-4a04-b2df-7a3ac0310e16' }
9
+ let(:output) { 'output' }
10
+ let(:command_id) { '4218A578-0F18-4B19-82C3-46B433319126' }
11
+ let(:keepalive_payload) { 'keepalive_payload' }
12
+ let(:configuration_payload) { 'configuration_payload' }
13
+ let(:command_payload) { 'command_payload' }
14
+ let(:create_shell_payload) { 'create_shell_payload' }
15
+ let(:close_shell_payload) { 'close_shell_payload' }
16
+ let(:cleanup_payload) { 'cleanup_payload' }
17
+ let(:command) { 'command' }
18
+ let(:output_message) { double('output_message', build: 'output_message') }
19
+ let(:command_response) { "<a xmlns:rsp='foo'><rsp:CommandId>#{command_id}</rsp:CommandId></a>" }
20
+ let(:configuration_response) do
21
+ "<a xmlns:cfg='foo'><cfg:MaxEnvelopeSizekb>#{max_envelope_size_kb}</cfg:MaxEnvelopeSizekb></a>"
22
+ end
23
+ let(:connection_options) { { max_commands: 100, retry_limit: retry_limit, retry_delay: 0 } }
24
+ let(:transport) { double('transport', send_request: nil) }
25
+ let(:test_data_xml_template) do
26
+ ERB.new(stubbed_response('get_powershell_keepalive_response.xml.erb'))
27
+ end
28
+ let(:protocol_version) { 2.2 }
29
+ let(:test_data1) do
30
+ <<-EOH
31
+ <Obj RefId="0">
32
+ <MS>
33
+ <Version N="protocolversion">#{protocol_version}</Version>
34
+ <Version N="PSVersion">2.0</Version>
35
+ <Version N="SerializationVersion">1.1.0.1</Version>
36
+ </MS>
37
+ </Obj>
38
+ EOH
39
+ end
40
+ let(:test_data2) { '<Obj RefId="0"><MS><I32 N="RunspaceState">2</I32></MS></Obj>' }
41
+ let(:message1) do
42
+ WinRM::PSRP::Message.new(
43
+ shell_id,
44
+ WinRM::PSRP::Message::MESSAGE_TYPES[:session_capability],
45
+ test_data1,
46
+ command_id
47
+ )
48
+ end
49
+ let(:message2) do
50
+ WinRM::PSRP::Message.new(
51
+ shell_id,
52
+ WinRM::PSRP::Message::MESSAGE_TYPES[:runspacepool_state],
53
+ test_data2,
54
+ command_id
55
+ )
56
+ end
57
+ let(:fragment1) { WinRM::PSRP::Fragment.new(1, message1.bytes) }
58
+ let(:fragment2) { WinRM::PSRP::Fragment.new(1, message2.bytes) }
59
+ let(:test_data_stdout1) { Base64.strict_encode64(fragment1.bytes.pack('C*')) }
60
+ let(:test_data_stdout2) { Base64.strict_encode64(fragment2.bytes.pack('C*')) }
61
+
62
+ before do
63
+ allow(SecureRandom).to receive(:uuid).and_return(command_id)
64
+ allow(subject).to receive(:command_output_message).with(shell_id, command_id)
65
+ .and_return(output_message)
66
+ allow_any_instance_of(WinRM::WSMV::CreatePipeline).to receive(:build)
67
+ .and_return(command_payload)
68
+ allow_any_instance_of(WinRM::WSMV::CloseShell).to receive(:build)
69
+ .and_return(close_shell_payload)
70
+ allow_any_instance_of(WinRM::WSMV::InitRunspacePool).to receive(:build)
71
+ .and_return(create_shell_payload)
72
+ allow_any_instance_of(WinRM::WSMV::Configuration).to receive(:build)
73
+ .and_return(configuration_payload)
74
+ allow_any_instance_of(WinRM::WSMV::CleanupCommand).to receive(:build)
75
+ .and_return(cleanup_payload)
76
+ allow_any_instance_of(WinRM::WSMV::KeepAlive).to receive(:build).and_return(keepalive_payload)
77
+ allow_any_instance_of(WinRM::PSRP::ReceiveResponseReader).to receive(:read_output)
78
+ .with(output_message).and_return(output)
79
+ allow(transport).to receive(:send_request).with(configuration_payload).and_return(
80
+ REXML::Document.new(configuration_response))
81
+ allow(transport).to receive(:send_request).with(create_shell_payload)
82
+ .and_return(REXML::Document.new("<blah Name='ShellId'>#{shell_id}</blah>"))
83
+ allow(transport).to receive(:send_request).with(command_payload)
84
+ .and_return(REXML::Document.new(command_response))
85
+ allow(transport).to receive(:send_request).with(keepalive_payload)
86
+ .and_return(REXML::Document.new(test_data_xml_template.result(binding)))
87
+ end
88
+
89
+ subject { described_class.new(connection_options, transport, Logging.logger['test']) }
90
+
91
+ describe '#run' do
92
+ it 'opens a shell and gets shell id' do
93
+ subject.run(command)
94
+ expect(subject.shell_id).to eq shell_id
95
+ end
96
+
97
+ it 'sends create shell through transport' do
98
+ expect(transport).to receive(:send_request).with(create_shell_payload)
99
+ subject.run(command)
100
+ end
101
+
102
+ it 'sends keepalive shell through transport' do
103
+ expect(transport).to receive(:send_request).with(keepalive_payload)
104
+ subject.run(command)
105
+ end
106
+
107
+ it 'returns output from generated command' do
108
+ expect(subject.run(command)).to eq output
109
+ end
110
+
111
+ it 'sends command through transport' do
112
+ expect(transport).to receive(:send_request).with(command_payload)
113
+ subject.run(command)
114
+ end
115
+
116
+ it 'sends cleanup message through transport' do
117
+ expect(transport).to receive(:send_request).with(cleanup_payload)
118
+ subject.run(command)
119
+ end
120
+
121
+ context 'non admin user' do
122
+ before do
123
+ allow(transport).to receive(:send_request).with(configuration_payload)
124
+ .and_raise(WinRM::WinRMWSManFault.new('no access for you', '5'))
125
+ end
126
+
127
+ context 'protocol version 2.1' do
128
+ let(:protocol_version) { 2.1 }
129
+
130
+ it 'sets the fragmenter max_blob_length' do
131
+ expect_any_instance_of(WinRM::PSRP::MessageFragmenter).to receive(:max_blob_length=)
132
+ .with(153600)
133
+ subject.run(command)
134
+ end
135
+ end
136
+
137
+ context 'protocol version 2.2' do
138
+ let(:protocol_version) { 2.2 }
139
+
140
+ it 'sets the fragmenter max_blob_length' do
141
+ expect_any_instance_of(WinRM::PSRP::MessageFragmenter).to receive(:max_blob_length=)
142
+ .with(512000)
143
+ subject.run(command)
144
+ end
145
+ end
146
+ end
147
+
148
+ context 'fragment large command' do
149
+ let(:command) { 'c' * 200000 }
150
+
151
+ it 'fragments messages as large as max envelope size' do
152
+ allow_any_instance_of(WinRM::WSMV::CreatePipeline).to receive(:build).and_call_original
153
+ allow(transport).to receive(:send_request).with(/CommandLine/) do |payload|
154
+ expect(payload.length).to be max_envelope_size_kb * 1024
155
+ end.and_return(REXML::Document.new(command_response))
156
+ subject.run(command)
157
+ end
158
+ end
159
+ end
160
+
161
+ describe '#close' do
162
+ it 'sends close shell through transport' do
163
+ subject.run(command)
164
+ expect(transport).to receive(:send_request).with(close_shell_payload)
165
+ subject.close
166
+ end
167
+
168
+ it 'creates a shell closer with powershell uri' do
169
+ allow(WinRM::WSMV::CloseShell).to receive(:new) do |_, opts|
170
+ expect(opts[:shell_uri]).to be WinRM::WSMV::Header::RESOURCE_URI_POWERSHELL
171
+ end.and_call_original
172
+ subject.close
173
+ end
174
+ end
175
+ end