winrm-elevated 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,99 +1,104 @@
1
- # encoding: UTF-8
2
- #
3
- # Copyright 2015 Shawn Neal <sneal@sneal.net>
4
- #
5
- # Licensed under the Apache License, Version 2.0 (the "License");
6
- # you may not use this file except in compliance with the License.
7
- # You may obtain a copy of the License at
8
- #
9
- # http://www.apache.org/licenses/LICENSE-2.0
10
- #
11
- # Unless required by applicable law or agreed to in writing, software
12
- # distributed under the License is distributed on an "AS IS" BASIS,
13
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- # See the License for the specific language governing permissions and
15
- # limitations under the License.
16
-
17
- require 'erubis'
18
- require 'winrm'
19
- require 'winrm-fs'
20
- require 'securerandom'
21
-
22
- module WinRM
23
- module Shells
24
- # Runs PowerShell commands elevated via a scheduled task
25
- class Elevated
26
- # Create a new elevated shell
27
- # @param connection_opts [ConnectionOpts] The WinRM connection options
28
- # @param transport [HttpTransport] The WinRM SOAP transport
29
- # @param logger [Logger] The logger to log diagnostic messages to
30
- def initialize(connection_opts, transport, logger)
31
- @logger = logger
32
- @username = connection_opts[:user]
33
- @password = connection_opts[:password]
34
- @shell = Powershell.new(connection_opts, transport, logger)
35
- @winrm_file_transporter = WinRM::FS::Core::FileTransporter.new(@shell)
36
- end
37
-
38
- # @return [String] The admin user name to execute the scheduled task as
39
- attr_accessor :username
40
-
41
- # @return [String] The admin user password
42
- attr_accessor :password
43
-
44
- # Run a command or PowerShell script elevated without any of the
45
- # restrictions that WinRM puts in place.
46
- #
47
- # @param [String] The command or PS script to wrap in a scheduled task
48
- #
49
- # @return [WinRM::Output] :stdout and :stderr
50
- def run(command, &block)
51
- # if an IO object is passed read it, otherwise assume the contents of the file were passed
52
- script_text = command.respond_to?(:read) ? command.read : command
53
-
54
- script_path = upload_elevated_shell_script(script_text)
55
- wrapped_script = wrap_in_scheduled_task(script_path, username, password)
56
- @shell.run(wrapped_script, &block)
57
- end
58
-
59
- # Closes the shell if one is open
60
- def close
61
- @shell.close
62
- end
63
-
64
- private
65
-
66
- def upload_elevated_shell_script(script_text)
67
- elevated_shell_path = 'c:/windows/temp/winrm-elevated-shell-' + SecureRandom.uuid + '.ps1'
68
- with_temp_file(script_text) do |temp_file|
69
- @winrm_file_transporter.upload(temp_file, elevated_shell_path)
70
- end
71
- elevated_shell_path
72
- end
73
-
74
- def with_temp_file(script_text)
75
- file = Tempfile.new(['winrm-elevated-shell', 'ps1'])
76
- file.write(script_text)
77
- file.write("\r\n$Host.SetShouldExit($LASTEXITCODE)")
78
- file.fsync
79
- file.close
80
- yield file.path
81
- ensure
82
- file.close
83
- file.unlink
84
- end
85
-
86
- def elevated_shell_script_content
87
- IO.read(File.expand_path('../../../winrm-elevated/scripts/elevated_shell.ps1', __FILE__))
88
- end
89
-
90
- def wrap_in_scheduled_task(script_path, username, password)
91
- Erubis::Eruby.new(elevated_shell_script_content).result(
92
- username: username,
93
- password: password,
94
- script_path: script_path
95
- )
96
- end
97
- end
98
- end
99
- end
1
+ # encoding: UTF-8
2
+ #
3
+ # Copyright 2015 Shawn Neal <sneal@sneal.net>
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require 'erubis'
18
+ require 'winrm'
19
+ require 'winrm-fs'
20
+ require 'securerandom'
21
+
22
+ module WinRM
23
+ module Shells
24
+ # Runs PowerShell commands elevated via a scheduled task
25
+ class Elevated
26
+ # Create a new elevated shell
27
+ # @param connection_opts [ConnectionOpts] The WinRM connection options
28
+ # @param transport [HttpTransport] The WinRM SOAP transport
29
+ # @param logger [Logger] The logger to log diagnostic messages to
30
+ def initialize(connection_opts, transport, logger)
31
+ @logger = logger
32
+ @username = connection_opts[:user]
33
+ @password = connection_opts[:password]
34
+ @interactive_logon = false
35
+ @shell = Powershell.new(connection_opts, transport, logger)
36
+ @winrm_file_transporter = WinRM::FS::Core::FileTransporter.new(@shell)
37
+ end
38
+
39
+ # @return [String] The admin user name to execute the scheduled task as
40
+ attr_accessor :username
41
+
42
+ # @return [String] The admin user password
43
+ attr_accessor :password
44
+
45
+ # @return [Bool] Using an interactive logon
46
+ attr_accessor :interactive_logon
47
+
48
+ # Run a command or PowerShell script elevated without any of the
49
+ # restrictions that WinRM puts in place.
50
+ #
51
+ # @param [String] The command or PS script to wrap in a scheduled task
52
+ #
53
+ # @return [WinRM::Output] :stdout and :stderr
54
+ def run(command, &block)
55
+ # if an IO object is passed read it, otherwise assume the contents of the file were passed
56
+ script_text = command.respond_to?(:read) ? command.read : command
57
+
58
+ script_path = upload_elevated_shell_script(script_text)
59
+ wrapped_script = wrap_in_scheduled_task(script_path, username, password)
60
+ @shell.run(wrapped_script, &block)
61
+ end
62
+
63
+ # Closes the shell if one is open
64
+ def close
65
+ @shell.close
66
+ end
67
+
68
+ private
69
+
70
+ def upload_elevated_shell_script(script_text)
71
+ elevated_shell_path = 'c:/windows/temp/winrm-elevated-shell-' + SecureRandom.uuid + '.ps1'
72
+ with_temp_file(script_text) do |temp_file|
73
+ @winrm_file_transporter.upload(temp_file, elevated_shell_path)
74
+ end
75
+ elevated_shell_path
76
+ end
77
+
78
+ def with_temp_file(script_text)
79
+ file = Tempfile.new(['winrm-elevated-shell', 'ps1'])
80
+ file.write(script_text)
81
+ file.write("\r\n$Host.SetShouldExit($LASTEXITCODE)")
82
+ file.fsync
83
+ file.close
84
+ yield file.path
85
+ ensure
86
+ file.close
87
+ file.unlink
88
+ end
89
+
90
+ def elevated_shell_script_content
91
+ IO.read(File.expand_path('../../../winrm-elevated/scripts/elevated_shell.ps1', __FILE__))
92
+ end
93
+
94
+ def wrap_in_scheduled_task(script_path, username, password)
95
+ Erubis::Eruby.new(elevated_shell_script_content).result(
96
+ username: username,
97
+ password: password,
98
+ script_path: script_path,
99
+ interactive_logon: interactive_logon
100
+ )
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,3 +1,3 @@
1
- endpoint: "http://localhost:55985/wsman"
2
- user: vagrant
3
- password: vagrant
1
+ endpoint: "http://localhost:55985/wsman"
2
+ user: vagrant
3
+ password: vagrant
@@ -1,50 +1,50 @@
1
- # encoding: UTF-8
2
- require 'rspec/expectations'
3
-
4
- # rspec matchers
5
- RSpec::Matchers.define :have_stdout_match do |expected_stdout|
6
- match do |actual_output|
7
- !expected_stdout.match(actual_output.stdout).nil?
8
- end
9
- failure_message do |actual_output|
10
- "expected that '#{actual_output.stdout}' would match #{expected_stdout}"
11
- end
12
- end
13
-
14
- RSpec::Matchers.define :have_stderr_match do |expected_stderr|
15
- match do |actual_output|
16
- !expected_stderr.match(actual_output.stderr).nil?
17
- end
18
- failure_message do |actual_output|
19
- "expected that '#{actual_output.stderr}' would match #{expected_stderr}"
20
- end
21
- end
22
-
23
- RSpec::Matchers.define :have_no_stdout do
24
- match do |actual_output|
25
- stdout = actual_output.stdout
26
- stdout == '\r\n' || stdout == ''
27
- end
28
- failure_message do |actual_output|
29
- "expected that '#{actual_output.stdout}' would have no stdout"
30
- end
31
- end
32
-
33
- RSpec::Matchers.define :have_no_stderr do
34
- match do |actual_output|
35
- stderr = actual_output.stderr
36
- stderr == '\r\n' || stderr == ''
37
- end
38
- failure_message do |actual_output|
39
- "expected that '#{actual_output.stderr}' would have no stderr"
40
- end
41
- end
42
-
43
- RSpec::Matchers.define :have_exit_code do |expected_exit_code|
44
- match do |actual_output|
45
- expected_exit_code == actual_output.exitcode
46
- end
47
- failure_message do |actual_output|
48
- "expected exit code #{expected_exit_code}, but got #{actual_output.exitcode}"
49
- end
50
- end
1
+ # encoding: UTF-8
2
+ require 'rspec/expectations'
3
+
4
+ # rspec matchers
5
+ RSpec::Matchers.define :have_stdout_match do |expected_stdout|
6
+ match do |actual_output|
7
+ !expected_stdout.match(actual_output.stdout).nil?
8
+ end
9
+ failure_message do |actual_output|
10
+ "expected that '#{actual_output.stdout}' would match #{expected_stdout}"
11
+ end
12
+ end
13
+
14
+ RSpec::Matchers.define :have_stderr_match do |expected_stderr|
15
+ match do |actual_output|
16
+ !expected_stderr.match(actual_output.stderr).nil?
17
+ end
18
+ failure_message do |actual_output|
19
+ "expected that '#{actual_output.stderr}' would match #{expected_stderr}"
20
+ end
21
+ end
22
+
23
+ RSpec::Matchers.define :have_no_stdout do
24
+ match do |actual_output|
25
+ stdout = actual_output.stdout
26
+ stdout == '\r\n' || stdout == ''
27
+ end
28
+ failure_message do |actual_output|
29
+ "expected that '#{actual_output.stdout}' would have no stdout"
30
+ end
31
+ end
32
+
33
+ RSpec::Matchers.define :have_no_stderr do
34
+ match do |actual_output|
35
+ stderr = actual_output.stderr
36
+ stderr == '\r\n' || stderr == ''
37
+ end
38
+ failure_message do |actual_output|
39
+ "expected that '#{actual_output.stderr}' would have no stderr"
40
+ end
41
+ end
42
+
43
+ RSpec::Matchers.define :have_exit_code do |expected_exit_code|
44
+ match do |actual_output|
45
+ expected_exit_code == actual_output.exitcode
46
+ end
47
+ failure_message do |actual_output|
48
+ "expected exit code #{expected_exit_code}, but got #{actual_output.exitcode}"
49
+ end
50
+ end
@@ -1,87 +1,87 @@
1
- # encoding: UTF-8
2
- describe 'powershell elevated runner', integration: true do
3
- describe 'ipconfig' do
4
- subject(:output) { elevated_shell.run('ipconfig') }
5
- it { should have_exit_code 0 }
6
- it { should have_stdout_match(/Windows IP Configuration/) }
7
- it { should have_no_stderr }
8
- end
9
-
10
- describe 'ipconfig as Service' do
11
- subject(:output) do
12
- elevated_shell.username = 'System'
13
- elevated_shell.password = nil
14
- elevated_shell.run('ipconfig')
15
- end
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) { elevated_shell.run("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 'ipconfig with incorrect argument -z' do
29
- subject(:output) { elevated_shell.run('ipconfig 127.0.0.1 -z') }
30
- it { should have_exit_code 1 }
31
- end
32
-
33
- describe 'Math area calculation' do
34
- subject(:output) do
35
- cmd = <<-EOH
36
- $diameter = 4.5
37
- $area = [Math]::pow([Math]::PI * ($diameter/2), 2)
38
- Write-Host $area
39
- EOH
40
- elevated_shell.run(cmd)
41
- end
42
- it { should have_exit_code 0 }
43
- it { should have_stdout_match(/49.9648722805149/) }
44
- it { should have_no_stderr }
45
- end
46
-
47
- describe 'ipconfig with a block' do
48
- subject(:stdout) do
49
- outvar = ''
50
- elevated_shell.run('ipconfig') do |stdout, _stderr|
51
- outvar << stdout
52
- end
53
- outvar
54
- end
55
- it { should match(/Windows IP Configuration/) }
56
- end
57
-
58
- describe 'capturing output from Write-Host and Write-Error' do
59
- subject(:output) do
60
- script = <<-eos
61
- Write-Host 'Hello'
62
- $host.ui.WriteErrorLine(', world!')
63
- eos
64
-
65
- @captured_stdout = ''
66
- @captured_stderr = ''
67
- elevated_shell.run(script) do |stdout, stderr|
68
- @captured_stdout << stdout if stdout
69
- @captured_stderr << stderr if stderr
70
- end
71
- end
72
-
73
- it 'should have stdout' do
74
- expect(output.stdout).to eq("Hello\r\n")
75
- expect(output.stdout).to eq(@captured_stdout)
76
- end
77
-
78
- it 'should have stderr' do
79
- expect(output.stderr).to eq(", world!\r\n")
80
- expect(output.stderr).to eq(@captured_stderr)
81
- end
82
-
83
- it 'should have output' do
84
- expect(output.output).to eq("Hello\r\n, world!\r\n")
85
- end
86
- end
87
- end
1
+ # encoding: UTF-8
2
+ describe 'powershell elevated runner', integration: true do
3
+ describe 'ipconfig' do
4
+ subject(:output) { elevated_shell.run('ipconfig') }
5
+ it { should have_exit_code 0 }
6
+ it { should have_stdout_match(/Windows IP Configuration/) }
7
+ it { should have_no_stderr }
8
+ end
9
+
10
+ describe 'ipconfig as Service' do
11
+ subject(:output) do
12
+ elevated_shell.username = 'System'
13
+ elevated_shell.password = nil
14
+ elevated_shell.run('ipconfig')
15
+ end
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) { elevated_shell.run("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 'ipconfig with incorrect argument -z' do
29
+ subject(:output) { elevated_shell.run('ipconfig 127.0.0.1 -z') }
30
+ it { should have_exit_code 1 }
31
+ end
32
+
33
+ describe 'Math area calculation' do
34
+ subject(:output) do
35
+ cmd = <<-EOH
36
+ $diameter = 4.5
37
+ $area = [Math]::pow([Math]::PI * ($diameter/2), 2)
38
+ Write-Host $area
39
+ EOH
40
+ elevated_shell.run(cmd)
41
+ end
42
+ it { should have_exit_code 0 }
43
+ it { should have_stdout_match(/49.9648722805149/) }
44
+ it { should have_no_stderr }
45
+ end
46
+
47
+ describe 'ipconfig with a block' do
48
+ subject(:stdout) do
49
+ outvar = ''
50
+ elevated_shell.run('ipconfig') do |stdout, _stderr|
51
+ outvar << stdout
52
+ end
53
+ outvar
54
+ end
55
+ it { should match(/Windows IP Configuration/) }
56
+ end
57
+
58
+ describe 'capturing output from Write-Host and Write-Error' do
59
+ subject(:output) do
60
+ script = <<-eos
61
+ Write-Host 'Hello'
62
+ $host.ui.WriteErrorLine(', world!')
63
+ eos
64
+
65
+ @captured_stdout = ''
66
+ @captured_stderr = ''
67
+ elevated_shell.run(script) do |stdout, stderr|
68
+ @captured_stdout << stdout if stdout
69
+ @captured_stderr << stderr if stderr
70
+ end
71
+ end
72
+
73
+ it 'should have stdout' do
74
+ expect(output.stdout).to eq("Hello\r\n")
75
+ expect(output.stdout).to eq(@captured_stdout)
76
+ end
77
+
78
+ it 'should have stderr' do
79
+ expect(output.stderr).to eq(", world!\r\n")
80
+ expect(output.stderr).to eq(@captured_stderr)
81
+ end
82
+
83
+ it 'should have output' do
84
+ expect(output.output).to eq("Hello\r\n, world!\r\n")
85
+ end
86
+ end
87
+ end