winrm 1.3.0.dev.1 → 1.3.0.dev.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.
- checksums.yaml +4 -4
- data/README.md +14 -0
- data/VERSION +1 -1
- data/bin/rwinrm +51 -13
- data/lib/winrm/exceptions/exceptions.rb +2 -0
- data/lib/winrm/file_transfer/remote_file.rb +184 -0
- data/lib/winrm/file_transfer/remote_zip_file.rb +60 -0
- data/lib/winrm/file_transfer.rb +39 -0
- data/lib/winrm/winrm_service.rb +31 -15
- data/lib/winrm.rb +1 -0
- data/spec/file_transfer/remote_file_spec.rb +71 -0
- data/spec/file_transfer/remote_zip_file_spec.rb +51 -0
- data/spec/file_transfer_spec.rb +39 -0
- data/winrm.gemspec +5 -1
- metadata +40 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cd79ebba5bae6d5d3a3149b46ec99c8bef9ddfac
|
4
|
+
data.tar.gz: 080075faa797bbfacd52383b8f5c3847ce1f6ee4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37c993dab7b631fd83e13eb9dffcd0aec1c9769c9ff21af1bbe1ba8e8db88a2e7b94c2174096e591268525f9ff942be8a4dc8b47a57e9f3fb77f47bb4403cd1b
|
7
|
+
data.tar.gz: 68589e411195257d6380409938e5ee311d5001f747d5727f96f95df4c0a044c4d975b4fdfcc4ece08d8e0fd28359b6a01adb0a1efa19aacf5dc7aecefb0fd47d
|
data/README.md
CHANGED
@@ -72,6 +72,20 @@ iex $cmd
|
|
72
72
|
WinRM::WinRMWebService.new(endpoint, :kerberos, :realm => 'MYREALM.COM')
|
73
73
|
```
|
74
74
|
|
75
|
+
### Uploading files
|
76
|
+
Files may be copied from the local machine to the winrm endpoint. Individual files or directories may be specified:
|
77
|
+
```ruby
|
78
|
+
WinRM::FileTransfer.upload(client, 'c:/dev/my_dir', '$env:AppData')
|
79
|
+
```
|
80
|
+
Or an array of several files and/or directories can be included:
|
81
|
+
```ruby
|
82
|
+
WinRM::FileTransfer.upload(client, ['c:/dev/file1.txt','c:/dev/dir1'], '$env:AppData')
|
83
|
+
```
|
84
|
+
**Note**:The winrm protocol poseses some limitations that can adversely affect the performance of large file transfers. By default, the above upload call will display a progress bar to indicate the progress of a file transfer currently underway. This progress bar can be suppressed by setting the `:quiet` option to `true`:
|
85
|
+
```ruby
|
86
|
+
WinRM::FileTransfer.upload(client, 'c:/dev/my_dir', '$env:AppData', :quiet => true)
|
87
|
+
```
|
88
|
+
|
75
89
|
## Troubleshooting
|
76
90
|
You may have some errors like ```WinRM::WinRMHTTPTransportError: Bad HTTP response returned from server (401).```.
|
77
91
|
You can run the following commands on the server to try to solve the problem:
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.3.0.dev.
|
1
|
+
1.3.0.dev.2
|
data/bin/rwinrm
CHANGED
@@ -22,12 +22,18 @@ require 'winrm'
|
|
22
22
|
|
23
23
|
def parse_options
|
24
24
|
options = {}
|
25
|
-
|
26
|
-
|
25
|
+
options[:auth_type] = :plaintext
|
26
|
+
options[:basic_auth_only] = true
|
27
|
+
options[:mode] = :repl
|
27
28
|
|
28
|
-
|
29
|
-
|
30
|
-
|
29
|
+
optparse = OptionParser.new do |opts|
|
30
|
+
opts.banner = "Usage: rwinrm [command] [local_path] [remote_path] endpoint [options]"
|
31
|
+
opts.separator ""
|
32
|
+
opts.separator "Commands"
|
33
|
+
opts.separator " repl(default)"
|
34
|
+
opts.separator " upload"
|
35
|
+
opts.separator ""
|
36
|
+
opts.separator "Options"
|
31
37
|
|
32
38
|
opts.on('-u', '--user username', String, 'WinRM user name') do |v|
|
33
39
|
options[:user] = v
|
@@ -38,27 +44,53 @@ def parse_options
|
|
38
44
|
end
|
39
45
|
|
40
46
|
opts.on('-h', '--help', 'Display this screen') do
|
41
|
-
puts
|
47
|
+
puts optparse
|
42
48
|
exit
|
43
49
|
end
|
44
50
|
end
|
45
51
|
|
46
52
|
optparse.parse!
|
53
|
+
|
54
|
+
if ARGV[0] && ARGV[0].downcase !~ /\A#{URI::regexp(['http', 'https'])}\z/
|
55
|
+
options[:mode] = ARGV.shift.downcase.to_sym
|
56
|
+
|
57
|
+
case options[:mode]
|
58
|
+
when :upload
|
59
|
+
options[:local_path] = ARGV.shift
|
60
|
+
options[:remote_path] = ARGV.shift
|
61
|
+
|
62
|
+
raise OptionParser::MissingArgument.new(:local_path) if options[:local_path].nil?
|
63
|
+
raise OptionParser::MissingArgument.new(:remote_path) if options[:remote_path].nil?
|
64
|
+
when :repl
|
65
|
+
else
|
66
|
+
raise OptionParser::InvalidArgument.new(options[:mode])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
options[:endpoint] = ARGV.shift
|
70
|
+
|
47
71
|
raise OptionParser::MissingArgument.new(:endpoint) if options[:endpoint].nil?
|
48
72
|
|
49
73
|
options
|
50
|
-
rescue OptionParser::InvalidOption, OptionParser::MissingArgument
|
74
|
+
rescue OptionParser::InvalidOption, OptionParser::MissingArgument, OptionParser::InvalidArgument
|
51
75
|
puts $!.message
|
52
76
|
puts optparse
|
53
77
|
exit 1
|
54
78
|
end
|
55
79
|
|
56
|
-
def
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
80
|
+
def winrm_client(options)
|
81
|
+
WinRM::WinRMWebService.new(
|
82
|
+
options[:endpoint],
|
83
|
+
options[:auth_type].to_sym,
|
84
|
+
options)
|
85
|
+
end
|
61
86
|
|
87
|
+
def upload(client, options)
|
88
|
+
bytes = WinRM::FileTransfer.upload(client, options[:local_path], options[:remote_path])
|
89
|
+
shell_id = client.open_shell()
|
90
|
+
puts "#{bytes} total bytes transfered"
|
91
|
+
end
|
92
|
+
|
93
|
+
def repl(client, options)
|
62
94
|
shell_id = client.open_shell()
|
63
95
|
command_id = client.run_command(shell_id, 'cmd')
|
64
96
|
|
@@ -79,6 +111,12 @@ def repl(options)
|
|
79
111
|
client.write_stdin(shell_id, command_id, "#{buf}\r\n")
|
80
112
|
end
|
81
113
|
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def run(options)
|
117
|
+
client = winrm_client(options)
|
118
|
+
method(options[:mode]).call(client, options)
|
119
|
+
exit 0
|
82
120
|
rescue Interrupt
|
83
121
|
# ctrl-c
|
84
122
|
rescue StandardError => e
|
@@ -86,4 +124,4 @@ rescue StandardError => e
|
|
86
124
|
exit 1
|
87
125
|
end
|
88
126
|
|
89
|
-
|
127
|
+
run(parse_options())
|
@@ -20,6 +20,8 @@ module WinRM
|
|
20
20
|
# Authorization Error
|
21
21
|
class WinRMAuthorizationError < WinRMError; end
|
22
22
|
|
23
|
+
class WinRMUploadFailed < WinRMError; end
|
24
|
+
|
23
25
|
# A Fault returned in the SOAP response. The XML node is a WSManFault
|
24
26
|
class WinRMWSManFault < WinRMError
|
25
27
|
attr_reader :fault_code
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'io/console'
|
2
|
+
require 'json'
|
3
|
+
require 'ruby-progressbar'
|
4
|
+
|
5
|
+
module WinRM
|
6
|
+
class RemoteFile
|
7
|
+
|
8
|
+
attr_reader :local_path
|
9
|
+
attr_reader :remote_path
|
10
|
+
attr_reader :closed
|
11
|
+
attr_reader :options
|
12
|
+
|
13
|
+
def initialize(service, local_path, remote_path, opts = {})
|
14
|
+
@logger = Logging.logger[self]
|
15
|
+
@options = opts
|
16
|
+
@closed = false
|
17
|
+
@service = service
|
18
|
+
@shell = service.open_shell
|
19
|
+
@local_path = local_path
|
20
|
+
@remote_path = full_remote_path(local_path, remote_path)
|
21
|
+
@logger.debug("Creating RemoteFile of local '#{local_path}' at '#{@remote_path}'")
|
22
|
+
ensure
|
23
|
+
if !shell.nil?
|
24
|
+
ObjectSpace.define_finalizer( self, self.class.close(shell, service) )
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def upload
|
29
|
+
raise WinRMUploadFailed.new("This RemoteFile is closed.") if closed
|
30
|
+
raise WinRMUploadFailed.new("Cannot find path: '#{local_path}'") unless File.exist?(local_path)
|
31
|
+
|
32
|
+
@remote_path, should_upload = powershell_batch do | builder |
|
33
|
+
builder << resolve_remote_command
|
34
|
+
builder << is_dirty_command
|
35
|
+
end
|
36
|
+
|
37
|
+
if should_upload
|
38
|
+
size = upload_to_remote
|
39
|
+
powershell_batch {|builder| builder << create_post_upload_command}
|
40
|
+
else
|
41
|
+
size = 0
|
42
|
+
logger.debug("Files are equal. Not copying #{local_path} to #{remote_path}")
|
43
|
+
end
|
44
|
+
size
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
service.close_shell(shell) unless shell.nil? or closed
|
49
|
+
@closed = true
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
attr_reader :logger
|
55
|
+
attr_reader :service
|
56
|
+
attr_reader :shell
|
57
|
+
|
58
|
+
def self.close(shell_id, service)
|
59
|
+
proc { service.close_shell(shell_id) }
|
60
|
+
end
|
61
|
+
|
62
|
+
def full_remote_path(local_path, remote_path)
|
63
|
+
base_file_name = File.basename(local_path)
|
64
|
+
if File.basename(remote_path) != base_file_name
|
65
|
+
remote_path = File.join(remote_path, base_file_name)
|
66
|
+
end
|
67
|
+
remote_path
|
68
|
+
end
|
69
|
+
|
70
|
+
def resolve_remote_command
|
71
|
+
<<-EOH
|
72
|
+
$dest_file_path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("#{remote_path}")
|
73
|
+
|
74
|
+
if (!(Test-Path $dest_file_path)) {
|
75
|
+
$dest_dir = ([System.IO.Path]::GetDirectoryName($dest_file_path))
|
76
|
+
New-Item -ItemType directory -Force -Path $dest_dir | Out-Null
|
77
|
+
}
|
78
|
+
|
79
|
+
$dest_file_path
|
80
|
+
EOH
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_dirty_command
|
84
|
+
local_md5 = Digest::MD5.file(local_path).hexdigest
|
85
|
+
<<-EOH
|
86
|
+
$dest_file_path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("#{remote_path}")
|
87
|
+
|
88
|
+
if (Test-Path $dest_file_path) {
|
89
|
+
$crypto_prov = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
|
90
|
+
try {
|
91
|
+
$file = [System.IO.File]::Open($dest_file_path,
|
92
|
+
[System.IO.Filemode]::Open, [System.IO.FileAccess]::Read)
|
93
|
+
$guest_md5 = ([System.BitConverter]::ToString($crypto_prov.ComputeHash($file)))
|
94
|
+
$guest_md5 = $guest_md5.Replace("-","").ToLower()
|
95
|
+
}
|
96
|
+
finally {
|
97
|
+
$file.Dispose()
|
98
|
+
}
|
99
|
+
if ($guest_md5 -eq '#{local_md5}') {
|
100
|
+
return $false
|
101
|
+
}
|
102
|
+
}
|
103
|
+
if(Test-Path $dest_file_path){remove-item $dest_file_path -Force}
|
104
|
+
return $true
|
105
|
+
EOH
|
106
|
+
end
|
107
|
+
|
108
|
+
def upload_to_remote
|
109
|
+
logger.debug("Uploading '#{local_path}' to temp file '#{remote_path}'")
|
110
|
+
base64_host_file = Base64.encode64(IO.binread(local_path)).gsub("\n", "")
|
111
|
+
base64_array = base64_host_file.chars.to_a
|
112
|
+
unless options[:quiet]
|
113
|
+
console_width = IO.console.winsize[1]
|
114
|
+
bar = ProgressBar.create(:title => "Copying #{File.basename(local_path)}...", :total => base64_array.count, :length => console_width-1)
|
115
|
+
end
|
116
|
+
base64_array.each_slice(8000 - remote_path.size) do |chunk|
|
117
|
+
cmd("echo #{chunk.join} >> \"#{remote_path}\"")
|
118
|
+
bar.progress += chunk.count unless options[:quiet]
|
119
|
+
end
|
120
|
+
base64_array.length
|
121
|
+
end
|
122
|
+
|
123
|
+
def decode_command
|
124
|
+
<<-EOH
|
125
|
+
$base64_string = Get-Content '#{remote_path}'
|
126
|
+
$bytes = [System.Convert]::FromBase64String($base64_string)
|
127
|
+
[System.IO.File]::WriteAllBytes('#{remote_path}', $bytes) | Out-Null
|
128
|
+
EOH
|
129
|
+
end
|
130
|
+
|
131
|
+
def create_post_upload_command
|
132
|
+
[decode_command]
|
133
|
+
end
|
134
|
+
|
135
|
+
def powershell_batch(&block)
|
136
|
+
ps_builder = []
|
137
|
+
yield ps_builder
|
138
|
+
|
139
|
+
commands = [ "$result = @{}" ]
|
140
|
+
idx = 0
|
141
|
+
ps_builder.flatten.each do |cmd_item|
|
142
|
+
commands << <<-EOH
|
143
|
+
$result.ret#{idx} = Invoke-Command { #{cmd_item} }
|
144
|
+
EOH
|
145
|
+
idx += 1
|
146
|
+
end
|
147
|
+
commands << "$(ConvertTo-Json $result)"
|
148
|
+
|
149
|
+
result = []
|
150
|
+
JSON.parse(powershell(commands.join("\n"))).each do |k,v|
|
151
|
+
result << v unless v.nil?
|
152
|
+
end
|
153
|
+
result unless result.empty?
|
154
|
+
end
|
155
|
+
|
156
|
+
def powershell(script)
|
157
|
+
script = "$ProgressPreference='SilentlyContinue';" + script
|
158
|
+
logger.debug("executing powershell script: \n#{script}")
|
159
|
+
script = script.encode('UTF-16LE', 'UTF-8')
|
160
|
+
script = Base64.strict_encode64(script)
|
161
|
+
cmd("powershell", ['-encodedCommand', script])
|
162
|
+
end
|
163
|
+
|
164
|
+
def cmd(command, arguments = [])
|
165
|
+
command_output = nil
|
166
|
+
out_stream = []
|
167
|
+
err_stream = []
|
168
|
+
service.run_command(shell, command, arguments) do |command_id|
|
169
|
+
command_output = service.get_command_output(shell, command_id) do |stdout, stderr|
|
170
|
+
out_stream << stdout if stdout
|
171
|
+
err_stream << stderr if stderr
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
if !command_output[:exitcode].zero? or !err_stream.empty?
|
176
|
+
raise WinRMUploadFailed,
|
177
|
+
:from => local_path,
|
178
|
+
:to => remote_path,
|
179
|
+
:message => command_output.inspect
|
180
|
+
end
|
181
|
+
out_stream.join.chomp
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'zip'
|
2
|
+
|
3
|
+
module WinRM
|
4
|
+
class RemoteZipFile < RemoteFile
|
5
|
+
|
6
|
+
attr_reader :archive
|
7
|
+
|
8
|
+
def initialize(service, remote_path, opts = {})
|
9
|
+
@logger = Logging.logger[self]
|
10
|
+
@archive = create_archive(remote_path)
|
11
|
+
@unzip_remote_path = remote_path
|
12
|
+
remote_path = "$env:temp/WinRM_file_transfer"
|
13
|
+
super(service, @archive, remote_path, opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_file(path)
|
17
|
+
path = path.gsub("\\","/")
|
18
|
+
logger.debug("adding '#{path}' to zip file")
|
19
|
+
raise WinRMUploadFailed.new("Cannot find path: '#{path}'") unless File.exist?(path)
|
20
|
+
File.directory?(path) ? glob = File.join(path, "**/*") : glob = path
|
21
|
+
logger.debug("iterating files in '#{glob}'")
|
22
|
+
Zip::File.open(archive, 'w') do |zipfile|
|
23
|
+
Dir.glob(glob).each do |file|
|
24
|
+
logger.debug("adding zip entry for '#{file}'")
|
25
|
+
entry = Zip::Entry.new(archive, file.sub(File.dirname(path)+'/',''), nil, nil, nil, nil, nil, nil, ::Zip::DOSTime.new(2000))
|
26
|
+
zipfile.add(entry,file)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
def create_post_upload_command
|
34
|
+
super << extract_zip_command
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def create_archive(remote_path)
|
40
|
+
archive_folder = File.join(ENV['TMP'] || ENV['TMPDIR'] || '/tmp', 'WinRM_file_transfer_local')
|
41
|
+
Dir.mkdir(archive_folder) unless File.exist?(archive_folder)
|
42
|
+
archive = File.join(archive_folder,File.basename(remote_path))+'.zip'
|
43
|
+
FileUtils.rm archive, :force=>true
|
44
|
+
|
45
|
+
archive
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_zip_command
|
49
|
+
<<-EOH
|
50
|
+
$destination = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath("#{@unzip_remote_path}")
|
51
|
+
$shellApplication = new-object -com shell.application
|
52
|
+
|
53
|
+
$zipPackage = $shellApplication.NameSpace('#{remote_path}')
|
54
|
+
mkdir $destination -ErrorAction SilentlyContinue | Out-Null
|
55
|
+
$destinationFolder = $shellApplication.NameSpace($destination)
|
56
|
+
$destinationFolder.CopyHere($zipPackage.Items(),0x10) | Out-Null
|
57
|
+
EOH
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'winrm/file_transfer/remote_file'
|
2
|
+
require 'winrm/file_transfer/remote_zip_file'
|
3
|
+
|
4
|
+
module WinRM
|
5
|
+
# Perform file transfer operations between a local machine and winrm endpoint
|
6
|
+
class FileTransfer
|
7
|
+
# Upload one or more local files and directories to a remote directory
|
8
|
+
# @example copy a single directory to a winrm endpoint
|
9
|
+
#
|
10
|
+
# WinRM::FileTransfer.upload(client, 'c:/dev/my_dir', '$env:AppData')
|
11
|
+
#
|
12
|
+
# @example copy several paths to the winrm endpoint
|
13
|
+
#
|
14
|
+
# WinRM::FileTransfer.upload(client, ['c:/dev/file1.txt','c:/dev/dir1'], '$env:AppData')
|
15
|
+
#
|
16
|
+
# @param [WinRM::WinRMService] a winrm service client connected to the endpoint where the remote path resides
|
17
|
+
# @param [Array<String>] One or more paths that will be copied to the remote path. These can be files or directories to be deeply copied
|
18
|
+
# @param [String] The directory on the remote endpoint to copy the local items to. This path may contain powershell style environment variables
|
19
|
+
# @option opts [String] options to be used for the copy. Currently only :quiet is supported to suppress the progress bar
|
20
|
+
# @return [Fixnum] The total number of bytes copied
|
21
|
+
def self.upload(service, local_path, remote_path, opts = {})
|
22
|
+
file = nil
|
23
|
+
local_path = [local_path] if local_path.is_a? String
|
24
|
+
|
25
|
+
if local_path.count == 1 && !File.directory?(local_path[0])
|
26
|
+
file = RemoteFile.new(service, local_path[0], remote_path, opts)
|
27
|
+
else
|
28
|
+
file = RemoteZipFile.new(service, remote_path, opts)
|
29
|
+
local_path.each do |path|
|
30
|
+
file.add_file(path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
file.upload
|
35
|
+
ensure
|
36
|
+
file.close unless file.nil?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/winrm/winrm_service.rb
CHANGED
@@ -81,7 +81,7 @@ module WinRM
|
|
81
81
|
# @option shell_opts [Hash] :env_vars environment variables to set for the shell. For instance;
|
82
82
|
# :env_vars => {:myvar1 => 'val1', :myvar2 => 'var2'}
|
83
83
|
# @return [String] The ShellId from the SOAP response. This is our open shell instance on the remote machine.
|
84
|
-
def open_shell(shell_opts = {})
|
84
|
+
def open_shell(shell_opts = {}, &block)
|
85
85
|
i_stream = shell_opts.has_key?(:i_stream) ? shell_opts[:i_stream] : 'stdin'
|
86
86
|
o_stream = shell_opts.has_key?(:o_stream) ? shell_opts[:o_stream] : 'stdout stderr'
|
87
87
|
codepage = shell_opts.has_key?(:codepage) ? shell_opts[:codepage] : 437
|
@@ -113,7 +113,16 @@ module WinRM
|
|
113
113
|
|
114
114
|
resp_doc = send_message(builder.target!)
|
115
115
|
shell_id = REXML::XPath.first(resp_doc, "//*[@Name='ShellId']").text
|
116
|
-
|
116
|
+
|
117
|
+
if block_given?
|
118
|
+
begin
|
119
|
+
yield shell_id
|
120
|
+
ensure
|
121
|
+
close_shell(shell_id)
|
122
|
+
end
|
123
|
+
else
|
124
|
+
shell_id
|
125
|
+
end
|
117
126
|
end
|
118
127
|
|
119
128
|
# Run a command on a machine with an open shell
|
@@ -121,7 +130,7 @@ module WinRM
|
|
121
130
|
# @param [String] command The command to run on the remote machine
|
122
131
|
# @param [Array<String>] arguments An array of arguments for this command
|
123
132
|
# @return [String] The CommandId from the SOAP response. This is the ID we need to query in order to get output.
|
124
|
-
def run_command(shell_id, command, arguments = [], cmd_opts = {})
|
133
|
+
def run_command(shell_id, command, arguments = [], cmd_opts = {}, &block)
|
125
134
|
consolemode = cmd_opts.has_key?(:console_mode_stdin) ? cmd_opts[:console_mode_stdin] : 'TRUE'
|
126
135
|
skipcmd = cmd_opts.has_key?(:skip_cmd_shell) ? cmd_opts[:skip_cmd_shell] : 'FALSE'
|
127
136
|
|
@@ -147,7 +156,16 @@ module WinRM
|
|
147
156
|
|
148
157
|
resp_doc = send_message(xml)
|
149
158
|
command_id = REXML::XPath.first(resp_doc, "//#{NS_WIN_SHELL}:CommandId").text
|
150
|
-
|
159
|
+
|
160
|
+
if block_given?
|
161
|
+
begin
|
162
|
+
yield command_id
|
163
|
+
ensure
|
164
|
+
cleanup_command(shell_id, command_id)
|
165
|
+
end
|
166
|
+
else
|
167
|
+
command_id
|
168
|
+
end
|
151
169
|
end
|
152
170
|
|
153
171
|
def write_stdin(shell_id, command_id, stdin)
|
@@ -261,13 +279,15 @@ module WinRM
|
|
261
279
|
# Run a CMD command
|
262
280
|
# @param [String] command The command to run on the remote system
|
263
281
|
# @param [Array <String>] arguments arguments to the command
|
282
|
+
# @param [String] an existing and open shell id to reuse
|
264
283
|
# @return [Hash] :stdout and :stderr
|
265
284
|
def run_cmd(command, arguments = [], &block)
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
285
|
+
command_output = nil
|
286
|
+
open_shell do |shell_id|
|
287
|
+
run_command(shell_id, command, arguments) do |command_id|
|
288
|
+
command_output = get_command_output(shell_id, command_id, &block)
|
289
|
+
end
|
290
|
+
end
|
271
291
|
command_output
|
272
292
|
end
|
273
293
|
alias :cmd :run_cmd
|
@@ -275,18 +295,14 @@ module WinRM
|
|
275
295
|
|
276
296
|
# Run a Powershell script that resides on the local box.
|
277
297
|
# @param [IO,String] script_file an IO reference for reading the Powershell script or the actual file contents
|
298
|
+
# @param [String] an existing and open shell id to reuse
|
278
299
|
# @return [Hash] :stdout and :stderr
|
279
300
|
def run_powershell_script(script_file, &block)
|
280
301
|
# if an IO object is passed read it..otherwise assume the contents of the file were passed
|
281
302
|
script = script_file.kind_of?(IO) ? script_file.read : script_file
|
282
303
|
script = script.encode('UTF-16LE', 'UTF-8')
|
283
304
|
script = Base64.strict_encode64(script)
|
284
|
-
|
285
|
-
command_id = run_command(shell_id, "powershell -encodedCommand #{script}")
|
286
|
-
command_output = get_command_output(shell_id, command_id, &block)
|
287
|
-
cleanup_command(shell_id, command_id)
|
288
|
-
close_shell(shell_id)
|
289
|
-
command_output
|
305
|
+
run_cmd("powershell -encodedCommand #{script}", &block)
|
290
306
|
end
|
291
307
|
alias :powershell :run_powershell_script
|
292
308
|
|
data/lib/winrm.rb
CHANGED
@@ -0,0 +1,71 @@
|
|
1
|
+
describe WinRM::RemoteFile, :integration => true do
|
2
|
+
|
3
|
+
let(:this_file) { __FILE__ }
|
4
|
+
let(:service) { winrm_connection }
|
5
|
+
let(:destination) {"#{ENV['temp']}/WinRM_tests"}
|
6
|
+
after {
|
7
|
+
subject.close
|
8
|
+
FileUtils.rm_rf(destination)
|
9
|
+
}
|
10
|
+
|
11
|
+
context 'Upload a new file to directory path' do
|
12
|
+
subject {WinRM::RemoteFile.new(service, this_file, destination, :quiet => true)}
|
13
|
+
|
14
|
+
it 'copies the file inside the directory' do
|
15
|
+
expect(subject.upload).to be > 0
|
16
|
+
expect(File.exist?(File.join(destination, File.basename(this_file)))).to be_truthy
|
17
|
+
end
|
18
|
+
it 'copies the exact file content' do
|
19
|
+
expect(subject.upload).to be > 0
|
20
|
+
expect(File.read(File.join(destination, File.basename(this_file)))).to eq(File.read(this_file))
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'Upload an identical file to directory path' do
|
26
|
+
subject {WinRM::RemoteFile.new(service, this_file, destination, :quiet => true)}
|
27
|
+
let (:next_transfer) {WinRM::RemoteFile.new(service, this_file, destination, :quiet => true)}
|
28
|
+
|
29
|
+
it 'does not copy the file' do
|
30
|
+
expect(subject.upload).to be > 0
|
31
|
+
expect(next_transfer.upload).to be == 0
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'Upload a file to file path' do
|
36
|
+
subject {WinRM::RemoteFile.new(service, this_file, File.join(destination, File.basename(this_file)), :quiet => true)}
|
37
|
+
|
38
|
+
it 'copies the file to the exact path' do
|
39
|
+
expect(subject.upload).to be > 0
|
40
|
+
expect(File.exist?(File.join(destination, File.basename(this_file)))).to be_truthy
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'Upload a new file to nested directory' do
|
45
|
+
let (:nested) {File.join(destination, 'nested')}
|
46
|
+
subject {WinRM::RemoteFile.new(service, this_file, nested, :quiet => true)}
|
47
|
+
|
48
|
+
it 'copies the file to the nested path' do
|
49
|
+
expect(subject.upload).to be > 0
|
50
|
+
expect(File.exist?(File.join(nested, File.basename(this_file)))).to be_truthy
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'Upload a file after RemoteFile is closed' do
|
55
|
+
subject {WinRM::RemoteFile.new(service, this_file, destination, :quiet => true)}
|
56
|
+
|
57
|
+
it 'raises WinRMUploadFailed' do
|
58
|
+
expect(subject.upload).to be > 0
|
59
|
+
subject.close
|
60
|
+
expect{subject.upload}.to raise_error(WinRM::WinRMUploadFailed)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'Upload a bad path' do
|
65
|
+
subject {WinRM::RemoteFile.new(service, 'c:/some/bad/path', destination, :quiet => true)}
|
66
|
+
|
67
|
+
it 'raises WinRMUploadFailed' do
|
68
|
+
expect { subject.upload }.to raise_error(WinRM::WinRMUploadFailed)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
describe WinRM::RemoteZipFile, :integration => true do
|
2
|
+
|
3
|
+
let(:this_dir) { File.dirname(__FILE__) }
|
4
|
+
let(:service) { winrm_connection }
|
5
|
+
let(:destination) {"#{ENV['temp']}/WinRM_tests"}
|
6
|
+
before { FileUtils.rm_rf(Dir.glob("#{ENV['temp'].gsub("\\","/")}/WinRM_*")) }
|
7
|
+
after { subject.close }
|
8
|
+
subject {WinRM::RemoteZipFile.new(service, destination, :quiet => true)}
|
9
|
+
|
10
|
+
context 'Upload a new directory' do
|
11
|
+
it 'copies the directory' do
|
12
|
+
subject.add_file(this_dir)
|
13
|
+
expect(subject.upload).to be > 0
|
14
|
+
expect(File.read(File.join(destination, "file_transfer", "remote_file_spec.rb"))).to eq(File.read(File.join(this_dir, 'remote_file_spec.rb')))
|
15
|
+
expect(File.read(File.join(destination, "file_transfer", "remote_zip_file_spec.rb"))).to eq(File.read(File.join(this_dir, 'remote_zip_file_spec.rb')))
|
16
|
+
expect(File.exist?(File.join(this_dir, "file_transfer.zip"))).to be_falsey
|
17
|
+
expect(File.exist?(File.join(destination, "file_transfer.zip"))).to be_falsey
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'Upload an identical directory' do
|
22
|
+
let (:next_transfer) {WinRM::RemoteZipFile.new(service, destination, :quiet => true)}
|
23
|
+
|
24
|
+
it 'does not copy the directory' do
|
25
|
+
subject.add_file(this_dir)
|
26
|
+
expect(subject.upload).to be > 0
|
27
|
+
next_transfer.add_file(this_dir)
|
28
|
+
expect(next_transfer.upload).to be == 0
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'Upload multiple entries' do
|
33
|
+
it 'copies each entry' do
|
34
|
+
subject.add_file(this_dir)
|
35
|
+
spec_helper = File.join(File.dirname(this_dir), 'spec_helper.rb')
|
36
|
+
subject.add_file(spec_helper)
|
37
|
+
expect(subject.upload).to be > 0
|
38
|
+
expect(File.read(File.join(destination, "file_transfer", "remote_file_spec.rb"))).to eq(File.read(File.join(this_dir, 'remote_file_spec.rb')))
|
39
|
+
expect(File.read(File.join(destination, "file_transfer", "remote_zip_file_spec.rb"))).to eq(File.read(File.join(this_dir, 'remote_zip_file_spec.rb')))
|
40
|
+
expect(File.read(File.join(destination, "spec_helper.rb"))).to eq(File.read(spec_helper))
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'Upload a bad path' do
|
45
|
+
it 'raises WinRMUploadFailed' do
|
46
|
+
expect {
|
47
|
+
subject.add_file('c:/some/bad/path')
|
48
|
+
}.to raise_error(WinRM::WinRMUploadFailed)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
describe WinRM::FileTransfer, :unit => true do
|
2
|
+
|
3
|
+
let(:this_file) { __FILE__ }
|
4
|
+
let(:remote_file) {double('RemoteFile')}
|
5
|
+
let(:remote_zip_file) {double('RemoteZipFile')}
|
6
|
+
|
7
|
+
before {
|
8
|
+
allow(WinRM::RemoteFile).to receive(:new).and_return(remote_file)
|
9
|
+
allow(WinRM::RemoteZipFile).to receive(:new).and_return(remote_zip_file)
|
10
|
+
}
|
11
|
+
subject {WinRM::FileTransfer}
|
12
|
+
|
13
|
+
context 'copying a single file' do
|
14
|
+
it 'uploads a remote_file' do
|
15
|
+
expect(remote_file).to receive(:upload)
|
16
|
+
expect(remote_file).to receive(:close)
|
17
|
+
subject.upload("service", this_file, "c:/directory")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'copying a single directory' do
|
22
|
+
it 'uploads a remote_zip_file' do
|
23
|
+
expect(remote_zip_file).to receive(:add_file).with(File.dirname(this_file))
|
24
|
+
expect(remote_zip_file).to receive(:upload)
|
25
|
+
expect(remote_zip_file).to receive(:close)
|
26
|
+
subject.upload("service", File.dirname(this_file), "c:/directory")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'copying both a file and a directory' do
|
31
|
+
it 'adds both to a remote_zip_file' do
|
32
|
+
expect(remote_zip_file).to receive(:upload)
|
33
|
+
expect(remote_zip_file).to receive(:add_file).with(this_file)
|
34
|
+
expect(remote_zip_file).to receive(:add_file).with(File.dirname(this_file))
|
35
|
+
expect(remote_zip_file).to receive(:close)
|
36
|
+
subject.upload("service", [File.dirname(this_file), this_file], "c:/directory")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/winrm.gemspec
CHANGED
@@ -23,15 +23,19 @@ Gem::Specification.new do |s|
|
|
23
23
|
s.rdoc_options = %w(-x test/ -x examples/)
|
24
24
|
s.extra_rdoc_files = %w(README.md LICENSE)
|
25
25
|
|
26
|
+
s.bindir = "bin"
|
27
|
+
s.executables = ['rwinrm']
|
26
28
|
s.required_ruby_version = '>= 1.9.0'
|
27
29
|
s.add_runtime_dependency 'gssapi', '~> 1.0'
|
28
30
|
s.add_runtime_dependency 'httpclient', '~> 2.2', '>= 2.2.0.2'
|
29
|
-
s.add_runtime_dependency 'rubyntlm', '~> 0.
|
31
|
+
s.add_runtime_dependency 'rubyntlm', '~> 0.4.0'
|
30
32
|
s.add_runtime_dependency 'uuidtools', '~> 2.1.2'
|
31
33
|
s.add_runtime_dependency 'logging', '~> 1.6', '>= 1.6.1'
|
32
34
|
s.add_runtime_dependency 'nori', '~> 2.0'
|
33
35
|
s.add_runtime_dependency 'gyoku', '~> 1.0'
|
34
36
|
s.add_runtime_dependency 'builder', '>= 2.1.2'
|
37
|
+
s.add_runtime_dependency 'rubyzip', '~> 1.1'
|
38
|
+
s.add_runtime_dependency 'ruby-progressbar', '~> 1.6'
|
35
39
|
s.add_development_dependency 'rspec', '~> 3.0.0'
|
36
40
|
s.add_development_dependency 'rake', '~> 10.3.2'
|
37
41
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: winrm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.0.dev.
|
4
|
+
version: 1.3.0.dev.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dan Wanek
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-11-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: gssapi
|
@@ -51,14 +51,14 @@ dependencies:
|
|
51
51
|
requirements:
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
54
|
+
version: 0.4.0
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
57
|
version_requirements: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ~>
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 0.4.0
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
63
|
name: uuidtools
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -135,6 +135,34 @@ dependencies:
|
|
135
135
|
- - '>='
|
136
136
|
- !ruby/object:Gem::Version
|
137
137
|
version: 2.1.2
|
138
|
+
- !ruby/object:Gem::Dependency
|
139
|
+
name: rubyzip
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ~>
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '1.1'
|
145
|
+
type: :runtime
|
146
|
+
prerelease: false
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ~>
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '1.1'
|
152
|
+
- !ruby/object:Gem::Dependency
|
153
|
+
name: ruby-progressbar
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - ~>
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '1.6'
|
159
|
+
type: :runtime
|
160
|
+
prerelease: false
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
162
|
+
requirements:
|
163
|
+
- - ~>
|
164
|
+
- !ruby/object:Gem::Version
|
165
|
+
version: '1.6'
|
138
166
|
- !ruby/object:Gem::Dependency
|
139
167
|
name: rspec
|
140
168
|
requirement: !ruby/object:Gem::Requirement
|
@@ -168,7 +196,8 @@ description: |2
|
|
168
196
|
email:
|
169
197
|
- dan.wanek@gmail.com
|
170
198
|
- paul@themortonsonline.com
|
171
|
-
executables:
|
199
|
+
executables:
|
200
|
+
- rwinrm
|
172
201
|
extensions: []
|
173
202
|
extra_rdoc_files:
|
174
203
|
- README.md
|
@@ -186,6 +215,9 @@ files:
|
|
186
215
|
- changelog.md
|
187
216
|
- lib/winrm.rb
|
188
217
|
- lib/winrm/exceptions/exceptions.rb
|
218
|
+
- lib/winrm/file_transfer.rb
|
219
|
+
- lib/winrm/file_transfer/remote_file.rb
|
220
|
+
- lib/winrm/file_transfer/remote_zip_file.rb
|
189
221
|
- lib/winrm/helpers/iso8601_duration.rb
|
190
222
|
- lib/winrm/http/response_handler.rb
|
191
223
|
- lib/winrm/http/transport.rb
|
@@ -196,6 +228,9 @@ files:
|
|
196
228
|
- spec/cmd_spec.rb
|
197
229
|
- spec/config-example.yml
|
198
230
|
- spec/exception_spec.rb
|
231
|
+
- spec/file_transfer/remote_file_spec.rb
|
232
|
+
- spec/file_transfer/remote_zip_file_spec.rb
|
233
|
+
- spec/file_transfer_spec.rb
|
199
234
|
- spec/matchers.rb
|
200
235
|
- spec/powershell_spec.rb
|
201
236
|
- spec/response_handler_spec.rb
|