winrm-elevated 1.1.0 → 1.2.2

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.
@@ -1,18 +1,17 @@
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 'winrm'
18
- require_relative 'winrm/shells/elevated'
1
+ #
2
+ # Copyright 2015 Shawn Neal <sneal@sneal.net>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'winrm' unless defined?(WinRM::Connection)
17
+ require_relative 'winrm/shells/elevated'
@@ -1,116 +1,136 @@
1
- $username = '<%= username %>'
2
- $password = '<%= password %>'
3
- $script_file = '<%= script_path %>'
4
-
5
- $interactive = '<%= interactive_logon %>'
6
- $pass_to_use = $password
7
- $logon_type = 1
8
- $logon_type_xml = "<LogonType>Password</LogonType>"
9
- if($pass_to_use.length -eq 0) {
10
- $pass_to_use = $null
11
- $logon_type = 5
12
- $logon_type_xml = ""
13
- }
14
- if($interactive -eq 'true') {
15
- $logon_type = 3
16
- $logon_type_xml = "<LogonType>InteractiveTokenOrPassword</LogonType>"
17
- }
18
-
19
- $task_name = "WinRM_Elevated_Shell"
20
- $out_file = [System.IO.Path]::GetTempFileName()
21
- $err_file = [System.IO.Path]::GetTempFileName()
22
-
23
- $task_xml = @'
24
- <?xml version="1.0" encoding="UTF-16"?>
25
- <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
26
- <Principals>
27
- <Principal id="Author">
28
- <UserId>{username}</UserId>
29
- {logon_type}
30
- <RunLevel>HighestAvailable</RunLevel>
31
- </Principal>
32
- </Principals>
33
- <Settings>
34
- <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
35
- <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
36
- <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
37
- <AllowHardTerminate>true</AllowHardTerminate>
38
- <StartWhenAvailable>false</StartWhenAvailable>
39
- <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
40
- <IdleSettings>
41
- <StopOnIdleEnd>false</StopOnIdleEnd>
42
- <RestartOnIdle>false</RestartOnIdle>
43
- </IdleSettings>
44
- <AllowStartOnDemand>true</AllowStartOnDemand>
45
- <Enabled>true</Enabled>
46
- <Hidden>false</Hidden>
47
- <RunOnlyIfIdle>false</RunOnlyIfIdle>
48
- <WakeToRun>false</WakeToRun>
49
- <ExecutionTimeLimit>PT24H</ExecutionTimeLimit>
50
- <Priority>4</Priority>
51
- </Settings>
52
- <Actions Context="Author">
53
- <Exec>
54
- <Command>cmd</Command>
55
- <Arguments>{arguments}</Arguments>
56
- </Exec>
57
- </Actions>
58
- </Task>
59
- '@
60
-
61
- $arguments = "/c powershell.exe -executionpolicy bypass -NoProfile -File $script_file &gt; $out_file 2&gt;$err_file"
62
-
63
- $task_xml = $task_xml.Replace("{arguments}", $arguments)
64
- $task_xml = $task_xml.Replace("{username}", $username)
65
- $task_xml = $task_xml.Replace("{logon_type}", $logon_type_xml)
66
-
67
- $schedule = New-Object -ComObject "Schedule.Service"
68
- $schedule.Connect()
69
- $task = $schedule.NewTask($null)
70
- $task.XmlText = $task_xml
71
- $folder = $schedule.GetFolder("\")
72
- $folder.RegisterTaskDefinition($task_name, $task, 6, $username, $pass_to_use, $logon_type, $null) | Out-Null
73
-
74
- $registered_task = $folder.GetTask("\$task_name")
75
- $registered_task.Run($null) | Out-Null
76
-
77
- $timeout = 10
78
- $sec = 0
79
- while ( (!($registered_task.state -eq 4)) -and ($sec -lt $timeout) ) {
80
- Start-Sleep -s 1
81
- $sec++
82
- }
83
-
84
- function SlurpOutput($file, $cur_line, $out_type) {
85
- if (Test-Path $file) {
86
- get-content $file | select -skip $cur_line | ForEach {
87
- $cur_line += 1
88
- if ($out_type -eq 'err') {
89
- $host.ui.WriteErrorLine("$_")
90
- } else {
91
- $host.ui.WriteLine("$_")
92
- }
93
- }
94
- }
95
- return $cur_line
96
- }
97
-
98
- $err_cur_line = 0
99
- $out_cur_line = 0
100
- do {
101
- Start-Sleep -m 100
102
- $out_cur_line = SlurpOutput $out_file $out_cur_line 'out'
103
- $err_cur_line = SlurpOutput $err_file $err_cur_line 'err'
104
- } while (!($registered_task.state -eq 3))
105
-
106
- # We'll make a best effort to clean these files
107
- # But a reboot could possibly end the task while the process
108
- # still runs and locks the file. If we can't delete we don't want to fail
109
- try { Remove-Item $out_file -ErrorAction Stop } catch {}
110
- try { Remove-Item $err_file -ErrorAction Stop } catch {}
111
- try { Remove-Item $script_file -ErrorAction Stop } catch {}
112
-
113
- $exit_code = $registered_task.LastTaskResult
114
- [System.Runtime.Interopservices.Marshal]::ReleaseComObject($schedule) | Out-Null
115
-
116
- exit $exit_code
1
+ $username = '<%= username %>'
2
+ $password = '<%= password %>'
3
+ $script_file = '<%= script_path %>'
4
+
5
+ $interactive = '<%= interactive_logon %>'
6
+ $pass_to_use = $password
7
+ $logon_type = 1
8
+ $logon_type_xml = "<LogonType>Password</LogonType>"
9
+ if($pass_to_use.length -eq 0) {
10
+ $pass_to_use = $null
11
+ $logon_type = 5
12
+ $logon_type_xml = ""
13
+ }
14
+ if($interactive -eq 'true') {
15
+ $logon_type = 3
16
+ $logon_type_xml = "<LogonType>InteractiveTokenOrPassword</LogonType>"
17
+ }
18
+
19
+ $task_name = "WinRM_Elevated_Shell_" + [guid]::NewGuid()
20
+ $out_file = [System.IO.Path]::GetTempFileName()
21
+ $err_file = [System.IO.Path]::GetTempFileName()
22
+
23
+ $task_xml = @'
24
+ <?xml version="1.0" encoding="UTF-16"?>
25
+ <Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
26
+ <Principals>
27
+ <Principal id="Author">
28
+ <UserId>{username}</UserId>
29
+ {logon_type}
30
+ <RunLevel>HighestAvailable</RunLevel>
31
+ </Principal>
32
+ </Principals>
33
+ <Settings>
34
+ <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
35
+ <DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
36
+ <StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
37
+ <AllowHardTerminate>true</AllowHardTerminate>
38
+ <StartWhenAvailable>false</StartWhenAvailable>
39
+ <RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
40
+ <IdleSettings>
41
+ <StopOnIdleEnd>false</StopOnIdleEnd>
42
+ <RestartOnIdle>false</RestartOnIdle>
43
+ </IdleSettings>
44
+ <AllowStartOnDemand>true</AllowStartOnDemand>
45
+ <Enabled>true</Enabled>
46
+ <Hidden>false</Hidden>
47
+ <RunOnlyIfIdle>false</RunOnlyIfIdle>
48
+ <WakeToRun>false</WakeToRun>
49
+ <ExecutionTimeLimit>PT24H</ExecutionTimeLimit>
50
+ <Priority>4</Priority>
51
+ </Settings>
52
+ <Actions Context="Author">
53
+ <Exec>
54
+ <Command>cmd</Command>
55
+ <Arguments>{arguments}</Arguments>
56
+ </Exec>
57
+ </Actions>
58
+ </Task>
59
+ '@
60
+
61
+ $arguments = "/c powershell.exe -executionpolicy bypass -NoProfile -File $script_file &gt; $out_file 2&gt;$err_file"
62
+
63
+ $task_xml = $task_xml.Replace("{arguments}", $arguments)
64
+ $task_xml = $task_xml.Replace("{username}", $username)
65
+ $task_xml = $task_xml.Replace("{logon_type}", $logon_type_xml)
66
+
67
+ try {
68
+ $schedule = New-Object -ComObject "Schedule.Service"
69
+ $schedule.Connect()
70
+ $task = $schedule.NewTask($null)
71
+ $task.XmlText = $task_xml
72
+ $folder = $schedule.GetFolder("\")
73
+ $folder.RegisterTaskDefinition($task_name, $task, 6, $username, $pass_to_use, $logon_type, $null) | Out-Null
74
+
75
+ $registered_task = $folder.GetTask("\$task_name")
76
+ $registered_task.Run($null) | Out-Null
77
+
78
+ $timeout = 10
79
+ $sec = 0
80
+ while ( (!($registered_task.state -eq 4)) -and ($sec -lt $timeout) ) {
81
+ Start-Sleep -s 1
82
+ $sec++
83
+ }
84
+ } catch {
85
+ Write-Error -ErrorRecord $PSItem
86
+ exit $PSItem.Exception.HResult
87
+ }
88
+
89
+ function SlurpOutput($file, $cur_line, $out_type) {
90
+ if (Test-Path $file) {
91
+ get-content $file | Select-Object -skip $cur_line | ForEach-Object {
92
+ $cur_line += 1
93
+ if ($out_type -eq 'err') {
94
+ $host.ui.WriteErrorLine("$_")
95
+ } else {
96
+ $host.ui.WriteLine("$_")
97
+ }
98
+ }
99
+ }
100
+ return $cur_line
101
+ }
102
+
103
+ $err_cur_line = 0
104
+ $out_cur_line = 0
105
+ $timeout = <%= execution_timeout %>
106
+ $startDate = Get-Date
107
+ do {
108
+ Start-Sleep -m 100
109
+ $out_cur_line = SlurpOutput $out_file $out_cur_line 'out'
110
+ $err_cur_line = SlurpOutput $err_file $err_cur_line 'err'
111
+ } while( (!($registered_task.state -eq 3)) -and ($startDate.AddSeconds($timeout) -gt (Get-Date)) )
112
+
113
+ # We'll make a best effort to clean these files
114
+ # But a reboot could possibly end the task while the process
115
+ # still runs and locks the file. If we can't delete we don't want to fail
116
+ try { Remove-Item $out_file -ErrorAction Stop } catch {}
117
+ try { Remove-Item $err_file -ErrorAction Stop } catch {}
118
+ try { Remove-Item $script_file -ErrorAction Stop } catch {}
119
+
120
+ $exit_code = $registered_task.LastTaskResult
121
+
122
+ try {
123
+ # Clean current task
124
+ $folder.DeleteTask($task_name, 0)
125
+ # Clean old tasks if required
126
+ $old_tasks_filter_date = [datetime]::Now.AddSeconds(<%= -1 * execution_timeout %>)
127
+ $old_tasks_to_kill = $folder.GetTasks(0) | Select Name,LastRunTime | Where-Object {
128
+ ($_.Name -like "WinRM_Elevated_Shell*") -and ($_.LastRunTime -le $old_tasks_filter_date) -and ($_.Name -ne $task_name)
129
+ }
130
+ $old_tasks_to_kill | ForEach-Object { try { $folder.DeleteTask($_.Name, 0) } catch {} }
131
+ }
132
+ catch {}
133
+
134
+ [System.Runtime.Interopservices.Marshal]::ReleaseComObject($schedule) | Out-Null
135
+
136
+ exit $exit_code
@@ -1,104 +1,101 @@
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
+ #
2
+ # Copyright 2015 Shawn Neal <sneal@sneal.net>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+ require 'erubi'
17
+ require 'winrm' unless defined?(WinRM::Connection)
18
+ require 'winrm-fs'
19
+ require 'securerandom' unless defined?(SecureRandom)
20
+ require 'stringio'
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
+ @execution_timeout = 86_400
38
+ end
39
+
40
+ # @return [String] The admin user name to execute the scheduled task as
41
+ attr_accessor :username
42
+
43
+ # @return [String] The admin user password
44
+ attr_accessor :password
45
+
46
+ # @return [Bool] Using an interactive logon
47
+ attr_accessor :interactive_logon
48
+
49
+ # @return [Integer] Timeout for the task to be executed
50
+ attr_accessor :execution_timeout
51
+
52
+ # Run a command or PowerShell script elevated without any of the
53
+ # restrictions that WinRM puts in place.
54
+ #
55
+ # @param [String] The command or PS script to wrap in a scheduled task
56
+ #
57
+ # @return [WinRM::Output] :stdout and :stderr
58
+ def run(command, &block)
59
+ # if an IO object is passed read it, otherwise assume the contents of the file were passed
60
+ script_text = command.respond_to?(:read) ? command.read : command
61
+
62
+ script_path = upload_elevated_shell_script(script_text)
63
+ wrapped_script = wrap_in_scheduled_task(script_path, username, password)
64
+ @shell.run(wrapped_script, &block)
65
+ end
66
+
67
+ # Closes the shell if one is open
68
+ def close
69
+ @shell.close
70
+ end
71
+
72
+ private
73
+
74
+ def upload_elevated_shell_script(script_text)
75
+ elevated_shell_path = 'c:/windows/temp/winrm-elevated-shell-' + SecureRandom.uuid + '.ps1'
76
+ script_text_with_exit = "#{script_text}\r\n$Host.SetShouldExit($LASTEXITCODE)"
77
+ @winrm_file_transporter.upload(StringIO.new(script_text_with_exit), elevated_shell_path)
78
+ elevated_shell_path
79
+ end
80
+
81
+ def elevated_shell_script_content
82
+ IO.read(File.expand_path('../../winrm-elevated/scripts/elevated_shell.ps1', __dir__))
83
+ end
84
+
85
+ def wrap_in_scheduled_task(script_path, username, password)
86
+ context = {
87
+ username: username,
88
+ password: password,
89
+ script_path: script_path,
90
+ interactive_logon: interactive_logon,
91
+ execution_timeout: execution_timeout
92
+ }
93
+
94
+ b = binding
95
+ locals = context.collect { |k, _| "#{k} = context[#{k.inspect}]; " }
96
+ b.eval(locals.join)
97
+ b.eval(Erubi::Engine.new(elevated_shell_script_content).src)
98
+ end
99
+ end
100
+ end
101
+ end