winrm 1.3.0.dev.1 → 1.3.0.dev.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|