winrm 1.7.0 → 1.7.1
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.
- checksums.yaml +4 -4
- data/.gitignore +10 -10
- data/.rspec +3 -3
- data/.rubocop.yml +12 -12
- data/.travis.yml +12 -12
- data/Gemfile +9 -9
- data/LICENSE +202 -202
- data/README.md +194 -194
- data/Rakefile +36 -36
- data/Vagrantfile +9 -9
- data/appveyor.yml +42 -42
- data/bin/rwinrm +97 -97
- data/changelog.md +74 -71
- data/lib/winrm.rb +42 -42
- data/lib/winrm/command_executor.rb +224 -224
- data/lib/winrm/exceptions/exceptions.rb +57 -57
- data/lib/winrm/helpers/iso8601_duration.rb +58 -58
- data/lib/winrm/helpers/powershell_script.rb +42 -42
- data/lib/winrm/http/response_handler.rb +82 -82
- data/lib/winrm/http/transport.rb +421 -421
- data/lib/winrm/output.rb +43 -43
- data/lib/winrm/soap_provider.rb +39 -39
- data/lib/winrm/version.rb +7 -7
- data/lib/winrm/winrm_service.rb +556 -556
- data/preamble +17 -17
- data/spec/auth_timeout_spec.rb +16 -16
- data/spec/cmd_spec.rb +102 -102
- data/spec/command_executor_spec.rb +429 -429
- data/spec/config-example.yml +19 -19
- data/spec/exception_spec.rb +50 -50
- data/spec/issue_184_spec.rb +67 -67
- data/spec/issue_59_spec.rb +23 -23
- data/spec/matchers.rb +74 -74
- data/spec/output_spec.rb +110 -110
- data/spec/powershell_spec.rb +97 -97
- data/spec/response_handler_spec.rb +59 -59
- data/spec/spec_helper.rb +73 -73
- data/spec/stubs/responses/get_command_output_response.xml.erb +13 -13
- data/spec/stubs/responses/open_shell_v1.xml +19 -19
- data/spec/stubs/responses/open_shell_v2.xml +20 -20
- data/spec/stubs/responses/soap_fault_v1.xml +36 -36
- data/spec/stubs/responses/soap_fault_v2.xml +42 -42
- data/spec/stubs/responses/wmi_error_v2.xml +41 -41
- data/spec/transport_spec.rb +124 -124
- data/spec/winrm_options_spec.rb +76 -76
- data/spec/winrm_primitives_spec.rb +51 -51
- data/spec/wql_spec.rb +14 -14
- data/winrm.gemspec +40 -40
- metadata +2 -2
data/preamble
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
=begin
|
2
|
-
This file is part of WinRM; the Ruby library for Microsoft WinRM.
|
3
|
-
|
4
|
-
Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
|
5
|
-
|
6
|
-
Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
you may not use this file except in compliance with the License.
|
8
|
-
You may obtain a copy of the License at
|
9
|
-
|
10
|
-
http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
|
12
|
-
Unless required by applicable law or agreed to in writing, software
|
13
|
-
distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
See the License for the specific language governing permissions and
|
16
|
-
limitations under the License.
|
17
|
-
=end
|
1
|
+
=begin
|
2
|
+
This file is part of WinRM; the Ruby library for Microsoft WinRM.
|
3
|
+
|
4
|
+
Copyright © 2010 Dan Wanek <dan.wanek@gmail.com>
|
5
|
+
|
6
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
you may not use this file except in compliance with the License.
|
8
|
+
You may obtain a copy of the License at
|
9
|
+
|
10
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
|
12
|
+
Unless required by applicable law or agreed to in writing, software
|
13
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
See the License for the specific language governing permissions and
|
16
|
+
limitations under the License.
|
17
|
+
=end
|
data/spec/auth_timeout_spec.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
# This test may only be meaningful with kerberos auth
|
3
|
-
# Against server 2012, a kerberos connection will require reauth (get a 401)
|
4
|
-
# if there are no requests for >= 15 seconds
|
5
|
-
|
6
|
-
describe 'Verify kerberos will reauth when necessary', kerberos: true do
|
7
|
-
before(:all) do
|
8
|
-
@winrm = winrm_connection
|
9
|
-
end
|
10
|
-
|
11
|
-
it 'work with a 18 second sleep' do
|
12
|
-
ps_command = 'Start-Sleep -s 18'
|
13
|
-
output = @winrm.run_powershell_script(ps_command)
|
14
|
-
output[:exitcode].should == 0
|
15
|
-
end
|
16
|
-
end
|
1
|
+
# encoding: UTF-8
|
2
|
+
# This test may only be meaningful with kerberos auth
|
3
|
+
# Against server 2012, a kerberos connection will require reauth (get a 401)
|
4
|
+
# if there are no requests for >= 15 seconds
|
5
|
+
|
6
|
+
describe 'Verify kerberos will reauth when necessary', kerberos: true do
|
7
|
+
before(:all) do
|
8
|
+
@winrm = winrm_connection
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'work with a 18 second sleep' do
|
12
|
+
ps_command = 'Start-Sleep -s 18'
|
13
|
+
output = @winrm.run_powershell_script(ps_command)
|
14
|
+
output[:exitcode].should == 0
|
15
|
+
end
|
16
|
+
end
|
data/spec/cmd_spec.rb
CHANGED
@@ -1,102 +1,102 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
describe 'winrm client cmd', integration: true do
|
3
|
-
before(:all) do
|
4
|
-
@winrm = winrm_connection
|
5
|
-
end
|
6
|
-
|
7
|
-
describe 'empty string' do
|
8
|
-
subject(:output) { @winrm.cmd('') }
|
9
|
-
it { should have_exit_code 0 }
|
10
|
-
it { should have_no_stdout }
|
11
|
-
it { should have_no_stderr }
|
12
|
-
end
|
13
|
-
|
14
|
-
describe 'ipconfig' do
|
15
|
-
subject(:output) { @winrm.cmd('ipconfig') }
|
16
|
-
it { should have_exit_code 0 }
|
17
|
-
it { should have_stdout_match(/Windows IP Configuration/) }
|
18
|
-
it { should have_no_stderr }
|
19
|
-
end
|
20
|
-
|
21
|
-
describe 'echo \'hello world\' using apostrophes' do
|
22
|
-
subject(:output) { @winrm.cmd("echo 'hello world'") }
|
23
|
-
it { should have_exit_code 0 }
|
24
|
-
it { should have_stdout_match(/'hello world'/) }
|
25
|
-
it { should have_no_stderr }
|
26
|
-
end
|
27
|
-
|
28
|
-
describe 'echo "string with trailing \\" using double quotes' do
|
29
|
-
# This is a regression test for #131. " is converted to " when serializing
|
30
|
-
# the command to SOAP/XML. Any naive substitution performed on such a serialized
|
31
|
-
# string can result in any \& sequence being interpreted as a back-substitution.
|
32
|
-
subject(:output) { @winrm.cmd('echo "string with trailing \\"') }
|
33
|
-
it { should have_exit_code 0 }
|
34
|
-
it { should have_stdout_match(/string with trailing \\/) }
|
35
|
-
it { should have_no_stderr }
|
36
|
-
end
|
37
|
-
|
38
|
-
describe 'capturing output from stdout and stderr' do
|
39
|
-
subject(:output) do
|
40
|
-
# Note: Multiple lines doesn't work:
|
41
|
-
# script = <<-eos
|
42
|
-
# echo Hello
|
43
|
-
# echo , world! 1>&2
|
44
|
-
# eos
|
45
|
-
|
46
|
-
script = 'echo Hello & echo , world! 1>&2'
|
47
|
-
|
48
|
-
@captured_stdout = ''
|
49
|
-
@captured_stderr = ''
|
50
|
-
@winrm.cmd(script) do |stdout, stderr|
|
51
|
-
@captured_stdout << stdout if stdout
|
52
|
-
@captured_stderr << stderr if stderr
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'should have stdout' do
|
57
|
-
expect(output.stdout).to eq("Hello \r\n")
|
58
|
-
expect(output.stdout).to eq(@captured_stdout)
|
59
|
-
end
|
60
|
-
|
61
|
-
it 'should have stderr' do
|
62
|
-
expect(output.stderr).to eq(", world! \r\n")
|
63
|
-
expect(output.stderr).to eq(@captured_stderr)
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'should have output' do
|
67
|
-
expect(output.output).to eq("Hello \r\n, world! \r\n")
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
describe 'ipconfig with /all argument' do
|
72
|
-
subject(:output) { @winrm.cmd('ipconfig', %w(/all)) }
|
73
|
-
it { should have_exit_code 0 }
|
74
|
-
it { should have_stdout_match(/Windows IP Configuration/) }
|
75
|
-
it { should have_no_stderr }
|
76
|
-
end
|
77
|
-
|
78
|
-
describe 'dir with incorrect argument /z' do
|
79
|
-
subject(:output) { @winrm.cmd('dir /z') }
|
80
|
-
it { should have_exit_code 1 }
|
81
|
-
it { should have_no_stdout }
|
82
|
-
it { should have_stderr_match(/Invalid switch/) }
|
83
|
-
end
|
84
|
-
|
85
|
-
describe 'ipconfig && echo error 1>&2' do
|
86
|
-
subject(:output) { @winrm.cmd('ipconfig && echo error 1>&2') }
|
87
|
-
it { should have_exit_code 0 }
|
88
|
-
it { should have_stdout_match(/Windows IP Configuration/) }
|
89
|
-
it { should have_stderr_match(/error/) }
|
90
|
-
end
|
91
|
-
|
92
|
-
describe 'ipconfig with a block' do
|
93
|
-
subject(:stdout) do
|
94
|
-
outvar = ''
|
95
|
-
@winrm.cmd('ipconfig') do |stdout, _stderr|
|
96
|
-
outvar << stdout
|
97
|
-
end
|
98
|
-
outvar
|
99
|
-
end
|
100
|
-
it { should match(/Windows IP Configuration/) }
|
101
|
-
end
|
102
|
-
end
|
1
|
+
# encoding: UTF-8
|
2
|
+
describe 'winrm client cmd', integration: true do
|
3
|
+
before(:all) do
|
4
|
+
@winrm = winrm_connection
|
5
|
+
end
|
6
|
+
|
7
|
+
describe 'empty string' do
|
8
|
+
subject(:output) { @winrm.cmd('') }
|
9
|
+
it { should have_exit_code 0 }
|
10
|
+
it { should have_no_stdout }
|
11
|
+
it { should have_no_stderr }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'ipconfig' do
|
15
|
+
subject(:output) { @winrm.cmd('ipconfig') }
|
16
|
+
it { should have_exit_code 0 }
|
17
|
+
it { should have_stdout_match(/Windows IP Configuration/) }
|
18
|
+
it { should have_no_stderr }
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'echo \'hello world\' using apostrophes' do
|
22
|
+
subject(:output) { @winrm.cmd("echo 'hello world'") }
|
23
|
+
it { should have_exit_code 0 }
|
24
|
+
it { should have_stdout_match(/'hello world'/) }
|
25
|
+
it { should have_no_stderr }
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'echo "string with trailing \\" using double quotes' do
|
29
|
+
# This is a regression test for #131. " is converted to " when serializing
|
30
|
+
# the command to SOAP/XML. Any naive substitution performed on such a serialized
|
31
|
+
# string can result in any \& sequence being interpreted as a back-substitution.
|
32
|
+
subject(:output) { @winrm.cmd('echo "string with trailing \\"') }
|
33
|
+
it { should have_exit_code 0 }
|
34
|
+
it { should have_stdout_match(/string with trailing \\/) }
|
35
|
+
it { should have_no_stderr }
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'capturing output from stdout and stderr' do
|
39
|
+
subject(:output) do
|
40
|
+
# Note: Multiple lines doesn't work:
|
41
|
+
# script = <<-eos
|
42
|
+
# echo Hello
|
43
|
+
# echo , world! 1>&2
|
44
|
+
# eos
|
45
|
+
|
46
|
+
script = 'echo Hello & echo , world! 1>&2'
|
47
|
+
|
48
|
+
@captured_stdout = ''
|
49
|
+
@captured_stderr = ''
|
50
|
+
@winrm.cmd(script) do |stdout, stderr|
|
51
|
+
@captured_stdout << stdout if stdout
|
52
|
+
@captured_stderr << stderr if stderr
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should have stdout' do
|
57
|
+
expect(output.stdout).to eq("Hello \r\n")
|
58
|
+
expect(output.stdout).to eq(@captured_stdout)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'should have stderr' do
|
62
|
+
expect(output.stderr).to eq(", world! \r\n")
|
63
|
+
expect(output.stderr).to eq(@captured_stderr)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should have output' do
|
67
|
+
expect(output.output).to eq("Hello \r\n, world! \r\n")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'ipconfig with /all argument' do
|
72
|
+
subject(:output) { @winrm.cmd('ipconfig', %w(/all)) }
|
73
|
+
it { should have_exit_code 0 }
|
74
|
+
it { should have_stdout_match(/Windows IP Configuration/) }
|
75
|
+
it { should have_no_stderr }
|
76
|
+
end
|
77
|
+
|
78
|
+
describe 'dir with incorrect argument /z' do
|
79
|
+
subject(:output) { @winrm.cmd('dir /z') }
|
80
|
+
it { should have_exit_code 1 }
|
81
|
+
it { should have_no_stdout }
|
82
|
+
it { should have_stderr_match(/Invalid switch/) }
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'ipconfig && echo error 1>&2' do
|
86
|
+
subject(:output) { @winrm.cmd('ipconfig && echo error 1>&2') }
|
87
|
+
it { should have_exit_code 0 }
|
88
|
+
it { should have_stdout_match(/Windows IP Configuration/) }
|
89
|
+
it { should have_stderr_match(/error/) }
|
90
|
+
end
|
91
|
+
|
92
|
+
describe 'ipconfig with a block' do
|
93
|
+
subject(:stdout) do
|
94
|
+
outvar = ''
|
95
|
+
@winrm.cmd('ipconfig') do |stdout, _stderr|
|
96
|
+
outvar << stdout
|
97
|
+
end
|
98
|
+
outvar
|
99
|
+
end
|
100
|
+
it { should match(/Windows IP Configuration/) }
|
101
|
+
end
|
102
|
+
end
|
@@ -1,429 +1,429 @@
|
|
1
|
-
# -*- encoding: utf-8 -*-
|
2
|
-
#
|
3
|
-
# Author:: Fletcher (<fnichol@nichol.ca>)
|
4
|
-
#
|
5
|
-
# Copyright (C) 2015, Fletcher Nichol
|
6
|
-
#
|
7
|
-
# Licensed under the Apache License, Version 2.0 (the 'License');
|
8
|
-
# you may not use this file except in compliance with the License.
|
9
|
-
# You may obtain a copy of the License at
|
10
|
-
#
|
11
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
-
#
|
13
|
-
# Unless required by applicable law or agreed to in writing, software
|
14
|
-
# distributed under the License is distributed on an 'AS IS' BASIS,
|
15
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
-
# See the License for the specific language governing permissions and
|
17
|
-
# limitations under the License.
|
18
|
-
|
19
|
-
require 'winrm/command_executor'
|
20
|
-
|
21
|
-
require 'base64'
|
22
|
-
require 'securerandom'
|
23
|
-
|
24
|
-
describe WinRM::CommandExecutor, unit: true do
|
25
|
-
let(:logged_output) { StringIO.new }
|
26
|
-
let(:shell_id) { 'shell-123' }
|
27
|
-
let(:executor_args) { [service, logger] }
|
28
|
-
let(:executor) { WinRM::CommandExecutor.new(service) }
|
29
|
-
let(:service) do
|
30
|
-
double(
|
31
|
-
'winrm_service',
|
32
|
-
logger: Logging.logger['test'],
|
33
|
-
retry_limit: 1,
|
34
|
-
retry_delay: 1
|
35
|
-
)
|
36
|
-
end
|
37
|
-
|
38
|
-
let(:version_output) { { xml_fragment: [{ version: '6.3.9600' }] } }
|
39
|
-
|
40
|
-
before do
|
41
|
-
allow(service).to receive(:open_shell).and_return(shell_id)
|
42
|
-
allow(service).to receive(:run_wql).and_return(version_output)
|
43
|
-
end
|
44
|
-
|
45
|
-
describe '#close' do
|
46
|
-
it 'calls service#close_shell' do
|
47
|
-
executor.open
|
48
|
-
expect(service).to receive(:close_shell).with(shell_id)
|
49
|
-
|
50
|
-
executor.close
|
51
|
-
end
|
52
|
-
|
53
|
-
it 'only calls service#close_shell once for multiple calls' do
|
54
|
-
executor.open
|
55
|
-
expect(service).to receive(:close_shell).with(shell_id).once
|
56
|
-
|
57
|
-
executor.close
|
58
|
-
executor.close
|
59
|
-
executor.close
|
60
|
-
end
|
61
|
-
|
62
|
-
it 'undefines finalizer' do
|
63
|
-
allow(service).to receive(:close_shell)
|
64
|
-
allow(ObjectSpace).to receive(:define_finalizer) { |e, _| e == executor }
|
65
|
-
expect(ObjectSpace).to receive(:undefine_finalizer).with(executor)
|
66
|
-
executor.open
|
67
|
-
|
68
|
-
executor.close
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
describe '#open' do
|
73
|
-
it 'calls service#open_shell' do
|
74
|
-
expect(service).to receive(:open_shell).and_return(shell_id)
|
75
|
-
|
76
|
-
executor.open
|
77
|
-
end
|
78
|
-
|
79
|
-
it 'defines a finalizer' do
|
80
|
-
expect(ObjectSpace).to receive(:define_finalizer) do |e, _|
|
81
|
-
expect(e).to eq(executor)
|
82
|
-
end
|
83
|
-
|
84
|
-
executor.open
|
85
|
-
end
|
86
|
-
|
87
|
-
it 'returns a shell id as a string' do
|
88
|
-
expect(executor.open).to eq shell_id
|
89
|
-
end
|
90
|
-
|
91
|
-
describe 'failed connection attempts' do
|
92
|
-
let(:error) { HTTPClient::ConnectTimeoutError }
|
93
|
-
let(:limit) { 3 }
|
94
|
-
let(:delay) { 0.1 }
|
95
|
-
|
96
|
-
before do
|
97
|
-
allow(service).to receive(:open_shell).and_raise(error)
|
98
|
-
allow(service).to receive(:retry_delay).and_return(delay)
|
99
|
-
allow(service).to receive(:retry_limit).and_return(limit)
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'attempts to connect :retry_limit times' do
|
103
|
-
begin
|
104
|
-
allow(service).to receive(:open_shell).exactly.times(limit)
|
105
|
-
executor.open
|
106
|
-
rescue # rubocop:disable Lint/HandleExceptions
|
107
|
-
# the raise is not what is being tested here, rather its side-effect
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
it 'raises the inner error after retries' do
|
112
|
-
expect { executor.open }.to raise_error(error)
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
describe 'for modern windows distributions' do
|
117
|
-
let(:version_output) { { xml_fragment: [{ version: '
|
118
|
-
|
119
|
-
it 'sets #max_commands to 1500 - 2' do
|
120
|
-
expect(executor.max_commands).to eq(1500 - 2)
|
121
|
-
end
|
122
|
-
|
123
|
-
it 'sets code_page to UTF-8' do
|
124
|
-
expect(executor.code_page).to eq 65_001
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
describe 'for older/legacy windows distributions' do
|
129
|
-
let(:version_output) { { xml_fragment: [{ version: '6.1.8500' }] } }
|
130
|
-
|
131
|
-
it 'sets #max_commands to 15 - 2' do
|
132
|
-
expect(executor.max_commands).to eq(15 - 2)
|
133
|
-
end
|
134
|
-
|
135
|
-
it 'sets code_page to UTF-8' do
|
136
|
-
expect(executor.code_page).to eq 65_001
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
describe 'for super duper older/legacy windows distributions' do
|
141
|
-
let(:version_output) { { xml_fragment: [{ version: '6.0.8500' }] } }
|
142
|
-
|
143
|
-
it 'sets #max_commands to 15 - 2' do
|
144
|
-
expect(executor.max_commands).to eq(15 - 2)
|
145
|
-
end
|
146
|
-
|
147
|
-
it 'sets code_page to MS-DOS' do
|
148
|
-
expect(executor.code_page).to eq 437
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
describe 'when unable to find os version' do
|
153
|
-
let(:version_output) { { xml_fragment: [{ funny_clowns: 'haha' }] } }
|
154
|
-
|
155
|
-
it 'raises WinRMError' do
|
156
|
-
expect { executor.code_page }.to raise_error(
|
157
|
-
::WinRM::WinRMError,
|
158
|
-
'Unable to determine endpoint os version'
|
159
|
-
)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
describe '#run_cmd' do
|
165
|
-
describe 'when #open has not been previously called' do
|
166
|
-
it 'raises a WinRMError error' do
|
167
|
-
expect { executor.run_cmd('nope') }.to raise_error(
|
168
|
-
::WinRM::WinRMError,
|
169
|
-
"#{executor.class}#open must be called before any run methods are invoked"
|
170
|
-
)
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
describe 'when #open has been previously called' do
|
175
|
-
let(:command_id) { 'command-123' }
|
176
|
-
|
177
|
-
let(:echo_output) do
|
178
|
-
o = ::WinRM::Output.new
|
179
|
-
o[:exitcode] = 0
|
180
|
-
o[:data].concat([
|
181
|
-
{ stdout: 'Hello\r\n' },
|
182
|
-
{ stderr: 'Psst\r\n' }
|
183
|
-
])
|
184
|
-
o
|
185
|
-
end
|
186
|
-
|
187
|
-
before do
|
188
|
-
stub_cmd(shell_id, 'echo', ['Hello'], echo_output, command_id)
|
189
|
-
|
190
|
-
executor.open
|
191
|
-
end
|
192
|
-
|
193
|
-
it 'calls service#run_command' do
|
194
|
-
expect(service).to receive(:run_command).with(shell_id, 'echo', ['Hello'])
|
195
|
-
|
196
|
-
executor.run_cmd('echo', ['Hello'])
|
197
|
-
end
|
198
|
-
|
199
|
-
it 'calls service#get_command_output to get results' do
|
200
|
-
expect(service).to receive(:get_command_output).with(shell_id, command_id)
|
201
|
-
|
202
|
-
executor.run_cmd('echo', ['Hello'])
|
203
|
-
end
|
204
|
-
|
205
|
-
it 'calls service#get_command_output with a block to get results' do
|
206
|
-
blk = proc { |_, _| 'something' }
|
207
|
-
expect(service).to receive(:get_command_output).with(shell_id, command_id, &blk)
|
208
|
-
|
209
|
-
executor.run_cmd('echo', ['Hello'], &blk)
|
210
|
-
end
|
211
|
-
|
212
|
-
it 'returns an Output object hash' do
|
213
|
-
expect(executor.run_cmd('echo', ['Hello'])).to eq echo_output
|
214
|
-
end
|
215
|
-
|
216
|
-
it 'runs the block in #get_command_output when given' do
|
217
|
-
io_out = StringIO.new
|
218
|
-
io_err = StringIO.new
|
219
|
-
stub_cmd(
|
220
|
-
shell_id,
|
221
|
-
'echo',
|
222
|
-
['Hello'],
|
223
|
-
echo_output,
|
224
|
-
command_id
|
225
|
-
).and_yield(echo_output.stdout, echo_output.stderr)
|
226
|
-
output = executor.run_cmd('echo', ['Hello']) do |stdout, stderr|
|
227
|
-
io_out << stdout if stdout
|
228
|
-
io_err << stderr if stderr
|
229
|
-
end
|
230
|
-
|
231
|
-
expect(io_out.string).to eq 'Hello\r\n'
|
232
|
-
expect(io_err.string).to eq 'Psst\r\n'
|
233
|
-
expect(output).to eq echo_output
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
describe 'when called many times over time' do
|
238
|
-
# use a 'old' version of windows with lower max_commands threshold
|
239
|
-
# to trigger quicker shell recyles
|
240
|
-
let(:version_output) { { xml_fragment: [{ version: '6.1.8500' }] } }
|
241
|
-
|
242
|
-
let(:echo_output) do
|
243
|
-
o = ::WinRM::Output.new
|
244
|
-
o[:exitcode] = 0
|
245
|
-
o[:data].concat([{ stdout: 'Hello\r\n' }])
|
246
|
-
o
|
247
|
-
end
|
248
|
-
|
249
|
-
before do
|
250
|
-
allow(service).to receive(:open_shell).and_return('s1', 's2')
|
251
|
-
allow(service).to receive(:close_shell)
|
252
|
-
allow(service).to receive(:run_command).and_yield('command-xxx')
|
253
|
-
allow(service).to receive(:get_command_output).and_return(echo_output)
|
254
|
-
allow(service).to receive(:run_wql).with('select version from Win32_OperatingSystem')
|
255
|
-
.and_return(version_output)
|
256
|
-
end
|
257
|
-
|
258
|
-
it 'resets the shell when #max_commands threshold is tripped' do
|
259
|
-
iterations = 35
|
260
|
-
reset_times = iterations / (15 - 2)
|
261
|
-
|
262
|
-
expect(service).to receive(:close_shell).exactly(reset_times).times
|
263
|
-
executor.open
|
264
|
-
iterations.times { executor.run_cmd('echo', ['Hello']) }
|
265
|
-
end
|
266
|
-
end
|
267
|
-
end
|
268
|
-
|
269
|
-
describe '#run_powershell_script' do
|
270
|
-
describe 'when #open has not been previously called' do
|
271
|
-
it 'raises a WinRMError error' do
|
272
|
-
expect { executor.run_powershell_script('nope') }.to raise_error(
|
273
|
-
::WinRM::WinRMError,
|
274
|
-
"#{executor.class}#open must be called before any run methods are invoked"
|
275
|
-
)
|
276
|
-
end
|
277
|
-
end
|
278
|
-
|
279
|
-
describe 'when #open has been previously called' do
|
280
|
-
let(:command_id) { 'command-123' }
|
281
|
-
|
282
|
-
let(:echo_output) do
|
283
|
-
o = ::WinRM::Output.new
|
284
|
-
o[:exitcode] = 0
|
285
|
-
o[:data].concat([
|
286
|
-
{ stdout: 'Hello\r\n' },
|
287
|
-
{ stderr: 'Psst\r\n' }
|
288
|
-
])
|
289
|
-
o
|
290
|
-
end
|
291
|
-
|
292
|
-
before do
|
293
|
-
stub_powershell_script(
|
294
|
-
shell_id,
|
295
|
-
'echo Hello',
|
296
|
-
echo_output,
|
297
|
-
command_id
|
298
|
-
)
|
299
|
-
|
300
|
-
executor.open
|
301
|
-
end
|
302
|
-
|
303
|
-
it 'calls service#run_command' do
|
304
|
-
expect(service).to receive(:run_command).with(
|
305
|
-
shell_id,
|
306
|
-
'powershell',
|
307
|
-
[
|
308
|
-
'-encodedCommand',
|
309
|
-
::WinRM::PowershellScript.new('echo Hello')
|
310
|
-
.encoded
|
311
|
-
]
|
312
|
-
)
|
313
|
-
|
314
|
-
executor.run_powershell_script('echo Hello')
|
315
|
-
end
|
316
|
-
|
317
|
-
it 'calls service#get_command_output to get results' do
|
318
|
-
expect(service).to receive(:get_command_output).with(shell_id, command_id)
|
319
|
-
|
320
|
-
executor.run_powershell_script('echo Hello')
|
321
|
-
end
|
322
|
-
|
323
|
-
it 'calls service#get_command_output with a block to get results' do
|
324
|
-
blk = proc { |_, _| 'something' }
|
325
|
-
expect(service).to receive(:get_command_output).with(shell_id, command_id, &blk)
|
326
|
-
|
327
|
-
executor.run_powershell_script('echo Hello', &blk)
|
328
|
-
end
|
329
|
-
|
330
|
-
it 'returns an Output object hash' do
|
331
|
-
expect(executor.run_powershell_script('echo Hello')).to eq echo_output
|
332
|
-
end
|
333
|
-
|
334
|
-
it 'runs the block in #get_command_output when given' do
|
335
|
-
io_out = StringIO.new
|
336
|
-
io_err = StringIO.new
|
337
|
-
stub_cmd(shell_id, 'echo', ['Hello'], echo_output, command_id)
|
338
|
-
.and_yield(echo_output.stdout, echo_output.stderr)
|
339
|
-
output = executor.run_powershell_script('echo Hello') do |stdout, stderr|
|
340
|
-
io_out << stdout if stdout
|
341
|
-
io_err << stderr if stderr
|
342
|
-
end
|
343
|
-
|
344
|
-
expect(io_out.string).to eq 'Hello\r\n'
|
345
|
-
expect(io_err.string).to eq 'Psst\r\n'
|
346
|
-
expect(output).to eq echo_output
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
describe 'when called many times over time' do
|
351
|
-
# use a 'old' version of windows with lower max_commands threshold
|
352
|
-
# to trigger quicker shell recyles
|
353
|
-
let(:version_output) { { xml_fragment: [{ version: '6.1.8500' }] } }
|
354
|
-
|
355
|
-
let(:echo_output) do
|
356
|
-
o = ::WinRM::Output.new
|
357
|
-
o[:exitcode] = 0
|
358
|
-
o[:data].concat([{ stdout: 'Hello\r\n' }])
|
359
|
-
o
|
360
|
-
end
|
361
|
-
|
362
|
-
before do
|
363
|
-
allow(service).to receive(:open_shell).and_return('s1', 's2')
|
364
|
-
allow(service).to receive(:close_shell)
|
365
|
-
allow(service).to receive(:run_command).and_yield('command-xxx')
|
366
|
-
allow(service).to receive(:get_command_output).and_return(echo_output)
|
367
|
-
allow(service).to receive(:wsman_identify).with('select version from Win32_OperatingSystem')
|
368
|
-
.and_return(version_output)
|
369
|
-
end
|
370
|
-
|
371
|
-
it 'resets the shell when #max_commands threshold is tripped' do
|
372
|
-
iterations = 35
|
373
|
-
reset_times = iterations / (15 - 2)
|
374
|
-
|
375
|
-
expect(service).to receive(:close_shell).exactly(reset_times).times
|
376
|
-
executor.open
|
377
|
-
iterations.times { executor.run_powershell_script('echo Hello') }
|
378
|
-
end
|
379
|
-
end
|
380
|
-
end
|
381
|
-
|
382
|
-
describe '#shell' do
|
383
|
-
it 'is initially nil' do
|
384
|
-
expect(executor.shell).to eq nil
|
385
|
-
end
|
386
|
-
|
387
|
-
it 'is set after #open is called' do
|
388
|
-
executor.open
|
389
|
-
|
390
|
-
expect(executor.shell).to eq shell_id
|
391
|
-
end
|
392
|
-
end
|
393
|
-
|
394
|
-
def decode(powershell)
|
395
|
-
Base64.strict_decode64(powershell).encode('UTF-8', 'UTF-16LE')
|
396
|
-
end
|
397
|
-
|
398
|
-
def debug_line_with(msg)
|
399
|
-
/^D, .* : #{Regexp.escape(msg)}/
|
400
|
-
end
|
401
|
-
|
402
|
-
def regexify(string)
|
403
|
-
Regexp.new(Regexp.escape(string))
|
404
|
-
end
|
405
|
-
|
406
|
-
def regexify_line(string)
|
407
|
-
Regexp.new("^#{Regexp.escape(string)}$")
|
408
|
-
end
|
409
|
-
|
410
|
-
# rubocop:disable Metrics/ParameterLists
|
411
|
-
def stub_cmd(shell_id, cmd, args, output, command_id = nil, &block)
|
412
|
-
command_id ||= SecureRandom.uuid
|
413
|
-
|
414
|
-
allow(service).to receive(:run_command).with(shell_id, cmd, args).and_yield(command_id)
|
415
|
-
allow(service).to receive(:get_command_output).with(shell_id, command_id, &block)
|
416
|
-
.and_return(output)
|
417
|
-
end
|
418
|
-
|
419
|
-
def stub_powershell_script(shell_id, script, output, command_id = nil)
|
420
|
-
stub_cmd(
|
421
|
-
shell_id,
|
422
|
-
'powershell',
|
423
|
-
['-encodedCommand', ::WinRM::PowershellScript.new(script).encoded],
|
424
|
-
output,
|
425
|
-
command_id
|
426
|
-
)
|
427
|
-
end
|
428
|
-
# rubocop:enable Metrics/ParameterLists
|
429
|
-
end
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
#
|
3
|
+
# Author:: Fletcher (<fnichol@nichol.ca>)
|
4
|
+
#
|
5
|
+
# Copyright (C) 2015, Fletcher Nichol
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the 'License');
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an 'AS IS' BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
require 'winrm/command_executor'
|
20
|
+
|
21
|
+
require 'base64'
|
22
|
+
require 'securerandom'
|
23
|
+
|
24
|
+
describe WinRM::CommandExecutor, unit: true do
|
25
|
+
let(:logged_output) { StringIO.new }
|
26
|
+
let(:shell_id) { 'shell-123' }
|
27
|
+
let(:executor_args) { [service, logger] }
|
28
|
+
let(:executor) { WinRM::CommandExecutor.new(service) }
|
29
|
+
let(:service) do
|
30
|
+
double(
|
31
|
+
'winrm_service',
|
32
|
+
logger: Logging.logger['test'],
|
33
|
+
retry_limit: 1,
|
34
|
+
retry_delay: 1
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
let(:version_output) { { xml_fragment: [{ version: '6.3.9600' }] } }
|
39
|
+
|
40
|
+
before do
|
41
|
+
allow(service).to receive(:open_shell).and_return(shell_id)
|
42
|
+
allow(service).to receive(:run_wql).and_return(version_output)
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#close' do
|
46
|
+
it 'calls service#close_shell' do
|
47
|
+
executor.open
|
48
|
+
expect(service).to receive(:close_shell).with(shell_id)
|
49
|
+
|
50
|
+
executor.close
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'only calls service#close_shell once for multiple calls' do
|
54
|
+
executor.open
|
55
|
+
expect(service).to receive(:close_shell).with(shell_id).once
|
56
|
+
|
57
|
+
executor.close
|
58
|
+
executor.close
|
59
|
+
executor.close
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'undefines finalizer' do
|
63
|
+
allow(service).to receive(:close_shell)
|
64
|
+
allow(ObjectSpace).to receive(:define_finalizer) { |e, _| e == executor }
|
65
|
+
expect(ObjectSpace).to receive(:undefine_finalizer).with(executor)
|
66
|
+
executor.open
|
67
|
+
|
68
|
+
executor.close
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
describe '#open' do
|
73
|
+
it 'calls service#open_shell' do
|
74
|
+
expect(service).to receive(:open_shell).and_return(shell_id)
|
75
|
+
|
76
|
+
executor.open
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'defines a finalizer' do
|
80
|
+
expect(ObjectSpace).to receive(:define_finalizer) do |e, _|
|
81
|
+
expect(e).to eq(executor)
|
82
|
+
end
|
83
|
+
|
84
|
+
executor.open
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns a shell id as a string' do
|
88
|
+
expect(executor.open).to eq shell_id
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'failed connection attempts' do
|
92
|
+
let(:error) { HTTPClient::ConnectTimeoutError }
|
93
|
+
let(:limit) { 3 }
|
94
|
+
let(:delay) { 0.1 }
|
95
|
+
|
96
|
+
before do
|
97
|
+
allow(service).to receive(:open_shell).and_raise(error)
|
98
|
+
allow(service).to receive(:retry_delay).and_return(delay)
|
99
|
+
allow(service).to receive(:retry_limit).and_return(limit)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'attempts to connect :retry_limit times' do
|
103
|
+
begin
|
104
|
+
allow(service).to receive(:open_shell).exactly.times(limit)
|
105
|
+
executor.open
|
106
|
+
rescue # rubocop:disable Lint/HandleExceptions
|
107
|
+
# the raise is not what is being tested here, rather its side-effect
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'raises the inner error after retries' do
|
112
|
+
expect { executor.open }.to raise_error(error)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'for modern windows distributions' do
|
117
|
+
let(:version_output) { { xml_fragment: [{ version: '10.0.10586.63' }] } }
|
118
|
+
|
119
|
+
it 'sets #max_commands to 1500 - 2' do
|
120
|
+
expect(executor.max_commands).to eq(1500 - 2)
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'sets code_page to UTF-8' do
|
124
|
+
expect(executor.code_page).to eq 65_001
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
describe 'for older/legacy windows distributions' do
|
129
|
+
let(:version_output) { { xml_fragment: [{ version: '6.1.8500' }] } }
|
130
|
+
|
131
|
+
it 'sets #max_commands to 15 - 2' do
|
132
|
+
expect(executor.max_commands).to eq(15 - 2)
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'sets code_page to UTF-8' do
|
136
|
+
expect(executor.code_page).to eq 65_001
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
describe 'for super duper older/legacy windows distributions' do
|
141
|
+
let(:version_output) { { xml_fragment: [{ version: '6.0.8500' }] } }
|
142
|
+
|
143
|
+
it 'sets #max_commands to 15 - 2' do
|
144
|
+
expect(executor.max_commands).to eq(15 - 2)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'sets code_page to MS-DOS' do
|
148
|
+
expect(executor.code_page).to eq 437
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe 'when unable to find os version' do
|
153
|
+
let(:version_output) { { xml_fragment: [{ funny_clowns: 'haha' }] } }
|
154
|
+
|
155
|
+
it 'raises WinRMError' do
|
156
|
+
expect { executor.code_page }.to raise_error(
|
157
|
+
::WinRM::WinRMError,
|
158
|
+
'Unable to determine endpoint os version'
|
159
|
+
)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe '#run_cmd' do
|
165
|
+
describe 'when #open has not been previously called' do
|
166
|
+
it 'raises a WinRMError error' do
|
167
|
+
expect { executor.run_cmd('nope') }.to raise_error(
|
168
|
+
::WinRM::WinRMError,
|
169
|
+
"#{executor.class}#open must be called before any run methods are invoked"
|
170
|
+
)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
describe 'when #open has been previously called' do
|
175
|
+
let(:command_id) { 'command-123' }
|
176
|
+
|
177
|
+
let(:echo_output) do
|
178
|
+
o = ::WinRM::Output.new
|
179
|
+
o[:exitcode] = 0
|
180
|
+
o[:data].concat([
|
181
|
+
{ stdout: 'Hello\r\n' },
|
182
|
+
{ stderr: 'Psst\r\n' }
|
183
|
+
])
|
184
|
+
o
|
185
|
+
end
|
186
|
+
|
187
|
+
before do
|
188
|
+
stub_cmd(shell_id, 'echo', ['Hello'], echo_output, command_id)
|
189
|
+
|
190
|
+
executor.open
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'calls service#run_command' do
|
194
|
+
expect(service).to receive(:run_command).with(shell_id, 'echo', ['Hello'])
|
195
|
+
|
196
|
+
executor.run_cmd('echo', ['Hello'])
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'calls service#get_command_output to get results' do
|
200
|
+
expect(service).to receive(:get_command_output).with(shell_id, command_id)
|
201
|
+
|
202
|
+
executor.run_cmd('echo', ['Hello'])
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'calls service#get_command_output with a block to get results' do
|
206
|
+
blk = proc { |_, _| 'something' }
|
207
|
+
expect(service).to receive(:get_command_output).with(shell_id, command_id, &blk)
|
208
|
+
|
209
|
+
executor.run_cmd('echo', ['Hello'], &blk)
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'returns an Output object hash' do
|
213
|
+
expect(executor.run_cmd('echo', ['Hello'])).to eq echo_output
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'runs the block in #get_command_output when given' do
|
217
|
+
io_out = StringIO.new
|
218
|
+
io_err = StringIO.new
|
219
|
+
stub_cmd(
|
220
|
+
shell_id,
|
221
|
+
'echo',
|
222
|
+
['Hello'],
|
223
|
+
echo_output,
|
224
|
+
command_id
|
225
|
+
).and_yield(echo_output.stdout, echo_output.stderr)
|
226
|
+
output = executor.run_cmd('echo', ['Hello']) do |stdout, stderr|
|
227
|
+
io_out << stdout if stdout
|
228
|
+
io_err << stderr if stderr
|
229
|
+
end
|
230
|
+
|
231
|
+
expect(io_out.string).to eq 'Hello\r\n'
|
232
|
+
expect(io_err.string).to eq 'Psst\r\n'
|
233
|
+
expect(output).to eq echo_output
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe 'when called many times over time' do
|
238
|
+
# use a 'old' version of windows with lower max_commands threshold
|
239
|
+
# to trigger quicker shell recyles
|
240
|
+
let(:version_output) { { xml_fragment: [{ version: '6.1.8500' }] } }
|
241
|
+
|
242
|
+
let(:echo_output) do
|
243
|
+
o = ::WinRM::Output.new
|
244
|
+
o[:exitcode] = 0
|
245
|
+
o[:data].concat([{ stdout: 'Hello\r\n' }])
|
246
|
+
o
|
247
|
+
end
|
248
|
+
|
249
|
+
before do
|
250
|
+
allow(service).to receive(:open_shell).and_return('s1', 's2')
|
251
|
+
allow(service).to receive(:close_shell)
|
252
|
+
allow(service).to receive(:run_command).and_yield('command-xxx')
|
253
|
+
allow(service).to receive(:get_command_output).and_return(echo_output)
|
254
|
+
allow(service).to receive(:run_wql).with('select version from Win32_OperatingSystem')
|
255
|
+
.and_return(version_output)
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'resets the shell when #max_commands threshold is tripped' do
|
259
|
+
iterations = 35
|
260
|
+
reset_times = iterations / (15 - 2)
|
261
|
+
|
262
|
+
expect(service).to receive(:close_shell).exactly(reset_times).times
|
263
|
+
executor.open
|
264
|
+
iterations.times { executor.run_cmd('echo', ['Hello']) }
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
describe '#run_powershell_script' do
|
270
|
+
describe 'when #open has not been previously called' do
|
271
|
+
it 'raises a WinRMError error' do
|
272
|
+
expect { executor.run_powershell_script('nope') }.to raise_error(
|
273
|
+
::WinRM::WinRMError,
|
274
|
+
"#{executor.class}#open must be called before any run methods are invoked"
|
275
|
+
)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
describe 'when #open has been previously called' do
|
280
|
+
let(:command_id) { 'command-123' }
|
281
|
+
|
282
|
+
let(:echo_output) do
|
283
|
+
o = ::WinRM::Output.new
|
284
|
+
o[:exitcode] = 0
|
285
|
+
o[:data].concat([
|
286
|
+
{ stdout: 'Hello\r\n' },
|
287
|
+
{ stderr: 'Psst\r\n' }
|
288
|
+
])
|
289
|
+
o
|
290
|
+
end
|
291
|
+
|
292
|
+
before do
|
293
|
+
stub_powershell_script(
|
294
|
+
shell_id,
|
295
|
+
'echo Hello',
|
296
|
+
echo_output,
|
297
|
+
command_id
|
298
|
+
)
|
299
|
+
|
300
|
+
executor.open
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'calls service#run_command' do
|
304
|
+
expect(service).to receive(:run_command).with(
|
305
|
+
shell_id,
|
306
|
+
'powershell',
|
307
|
+
[
|
308
|
+
'-encodedCommand',
|
309
|
+
::WinRM::PowershellScript.new('echo Hello')
|
310
|
+
.encoded
|
311
|
+
]
|
312
|
+
)
|
313
|
+
|
314
|
+
executor.run_powershell_script('echo Hello')
|
315
|
+
end
|
316
|
+
|
317
|
+
it 'calls service#get_command_output to get results' do
|
318
|
+
expect(service).to receive(:get_command_output).with(shell_id, command_id)
|
319
|
+
|
320
|
+
executor.run_powershell_script('echo Hello')
|
321
|
+
end
|
322
|
+
|
323
|
+
it 'calls service#get_command_output with a block to get results' do
|
324
|
+
blk = proc { |_, _| 'something' }
|
325
|
+
expect(service).to receive(:get_command_output).with(shell_id, command_id, &blk)
|
326
|
+
|
327
|
+
executor.run_powershell_script('echo Hello', &blk)
|
328
|
+
end
|
329
|
+
|
330
|
+
it 'returns an Output object hash' do
|
331
|
+
expect(executor.run_powershell_script('echo Hello')).to eq echo_output
|
332
|
+
end
|
333
|
+
|
334
|
+
it 'runs the block in #get_command_output when given' do
|
335
|
+
io_out = StringIO.new
|
336
|
+
io_err = StringIO.new
|
337
|
+
stub_cmd(shell_id, 'echo', ['Hello'], echo_output, command_id)
|
338
|
+
.and_yield(echo_output.stdout, echo_output.stderr)
|
339
|
+
output = executor.run_powershell_script('echo Hello') do |stdout, stderr|
|
340
|
+
io_out << stdout if stdout
|
341
|
+
io_err << stderr if stderr
|
342
|
+
end
|
343
|
+
|
344
|
+
expect(io_out.string).to eq 'Hello\r\n'
|
345
|
+
expect(io_err.string).to eq 'Psst\r\n'
|
346
|
+
expect(output).to eq echo_output
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
describe 'when called many times over time' do
|
351
|
+
# use a 'old' version of windows with lower max_commands threshold
|
352
|
+
# to trigger quicker shell recyles
|
353
|
+
let(:version_output) { { xml_fragment: [{ version: '6.1.8500' }] } }
|
354
|
+
|
355
|
+
let(:echo_output) do
|
356
|
+
o = ::WinRM::Output.new
|
357
|
+
o[:exitcode] = 0
|
358
|
+
o[:data].concat([{ stdout: 'Hello\r\n' }])
|
359
|
+
o
|
360
|
+
end
|
361
|
+
|
362
|
+
before do
|
363
|
+
allow(service).to receive(:open_shell).and_return('s1', 's2')
|
364
|
+
allow(service).to receive(:close_shell)
|
365
|
+
allow(service).to receive(:run_command).and_yield('command-xxx')
|
366
|
+
allow(service).to receive(:get_command_output).and_return(echo_output)
|
367
|
+
allow(service).to receive(:wsman_identify).with('select version from Win32_OperatingSystem')
|
368
|
+
.and_return(version_output)
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'resets the shell when #max_commands threshold is tripped' do
|
372
|
+
iterations = 35
|
373
|
+
reset_times = iterations / (15 - 2)
|
374
|
+
|
375
|
+
expect(service).to receive(:close_shell).exactly(reset_times).times
|
376
|
+
executor.open
|
377
|
+
iterations.times { executor.run_powershell_script('echo Hello') }
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe '#shell' do
|
383
|
+
it 'is initially nil' do
|
384
|
+
expect(executor.shell).to eq nil
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'is set after #open is called' do
|
388
|
+
executor.open
|
389
|
+
|
390
|
+
expect(executor.shell).to eq shell_id
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
def decode(powershell)
|
395
|
+
Base64.strict_decode64(powershell).encode('UTF-8', 'UTF-16LE')
|
396
|
+
end
|
397
|
+
|
398
|
+
def debug_line_with(msg)
|
399
|
+
/^D, .* : #{Regexp.escape(msg)}/
|
400
|
+
end
|
401
|
+
|
402
|
+
def regexify(string)
|
403
|
+
Regexp.new(Regexp.escape(string))
|
404
|
+
end
|
405
|
+
|
406
|
+
def regexify_line(string)
|
407
|
+
Regexp.new("^#{Regexp.escape(string)}$")
|
408
|
+
end
|
409
|
+
|
410
|
+
# rubocop:disable Metrics/ParameterLists
|
411
|
+
def stub_cmd(shell_id, cmd, args, output, command_id = nil, &block)
|
412
|
+
command_id ||= SecureRandom.uuid
|
413
|
+
|
414
|
+
allow(service).to receive(:run_command).with(shell_id, cmd, args).and_yield(command_id)
|
415
|
+
allow(service).to receive(:get_command_output).with(shell_id, command_id, &block)
|
416
|
+
.and_return(output)
|
417
|
+
end
|
418
|
+
|
419
|
+
def stub_powershell_script(shell_id, script, output, command_id = nil)
|
420
|
+
stub_cmd(
|
421
|
+
shell_id,
|
422
|
+
'powershell',
|
423
|
+
['-encodedCommand', ::WinRM::PowershellScript.new(script).encoded],
|
424
|
+
output,
|
425
|
+
command_id
|
426
|
+
)
|
427
|
+
end
|
428
|
+
# rubocop:enable Metrics/ParameterLists
|
429
|
+
end
|