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 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