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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6e51474e140d25ef1680a4c431b1bcdee6185fd0
4
- data.tar.gz: 6c1cb088c144cf90125121f25727f277d0f7a3f2
3
+ metadata.gz: cd79ebba5bae6d5d3a3149b46ec99c8bef9ddfac
4
+ data.tar.gz: 080075faa797bbfacd52383b8f5c3847ce1f6ee4
5
5
  SHA512:
6
- metadata.gz: 7dc6b9dfa831f92c35488bef5cdf25e54e44289b20b1c61fcd7bafffea6cb361f98bb05fa8a7022ba74f9b1c903fa7e0ec95cf54ad053017ea9f80b27824e769
7
- data.tar.gz: 00000ad60eaf4ea00f4c55814c74af0270dc80840e7eaf3c2b3e89c9e70005678121895a4274d4acd51552ca4b2fdeeb27b88aa4dc8beee0f9e207b36f463890
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
+ 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
- optparse = OptionParser.new do |opts|
26
- opts.banner = "Usage: rwinrm endpoint [options]"
25
+ options[:auth_type] = :plaintext
26
+ options[:basic_auth_only] = true
27
+ options[:mode] = :repl
27
28
 
28
- options[:auth_type] = :plaintext
29
- options[:basic_auth_only] = true
30
- options[:endpoint] = ARGV[0]
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 opts
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 repl(options)
57
- client = WinRM::WinRMWebService.new(
58
- options[:endpoint],
59
- options[:auth_type].to_sym,
60
- options)
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
- repl(parse_options())
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
@@ -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
- shell_id
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
- command_id
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
- shell_id = open_shell
267
- command_id = run_command(shell_id, command, arguments)
268
- command_output = get_command_output(shell_id, command_id, &block)
269
- cleanup_command(shell_id, command_id)
270
- close_shell(shell_id)
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
- shell_id = open_shell
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
@@ -35,3 +35,4 @@ end
35
35
 
36
36
  require 'winrm/helpers/iso8601_duration'
37
37
  require 'winrm/soap_provider'
38
+ require 'winrm/file_transfer'
@@ -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.1'
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.1
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-10-14 00:00:00.000000000 Z
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: '0.1'
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: '0.1'
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