winrm-fs 0.2.3 → 0.3.0

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.
@@ -0,0 +1,166 @@
1
+ # -*- encoding: utf-8 -*-
2
+ #
3
+ # Author:: Fletcher (<fnichol@nichol.ca>)
4
+ #
5
+ # Copyright (C) 2015, Fletcher Nichol
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+
19
+ require 'delegate'
20
+ require 'pathname'
21
+ require 'tempfile'
22
+ require 'zip'
23
+
24
+ module WinRM
25
+ module FS
26
+ module Core
27
+ # A temporary Zip file for a given directory.
28
+ #
29
+ # @author Fletcher Nichol <fnichol@nichol.ca>
30
+ class TmpZip
31
+ # Contructs a new Zip file for the given directory.
32
+ #
33
+ # There are 2 ways to interpret the directory path:
34
+ #
35
+ # * If the directory has no path separator terminator, then the
36
+ # directory basename will be used as the base directory in the
37
+ # resulting zip file.
38
+ # * If the directory has a path separator terminator (such as `/` or
39
+ # `\\`), then the entries under the directory will be added to the
40
+ # resulting zip file.
41
+ #
42
+ # The following emaples assume a directory tree structure of:
43
+ #
44
+ # src
45
+ # |-- alpha.txt
46
+ # |-- beta.txt
47
+ # \-- sub
48
+ # \-- charlie.txt
49
+ #
50
+ # @example Including the base directory in the zip file
51
+ #
52
+ # TmpZip.new("/path/to/src")
53
+ # # produces a zip file with entries:
54
+ # # - src/alpha.txt
55
+ # # - src/beta.txt
56
+ # # - src/sub/charlie.txt
57
+ #
58
+ # @example Excluding the base directory in the zip file
59
+ #
60
+ # TmpZip.new("/path/to/src/")
61
+ # # produces a zip file with entries:
62
+ # # - alpha.txt
63
+ # # - beta.txt
64
+ # # - sub/charlie.txt
65
+ #
66
+ # @param dir [String,Pathname,#to_s] path to the directory
67
+ # @param logger [#debug,#debug?] an optional logger/ui object that
68
+ # responds to `#debug` and `#debug?` (default `nil`)
69
+ def initialize(dir, logger = nil)
70
+ @logger = logger || Logging.logger[self]
71
+ @dir = Pathname.new(dir)
72
+ @zip_io = Tempfile.open(['tmpzip-', '.zip'], binmode: true)
73
+ write_zip
74
+ @zip_io.close
75
+ end
76
+
77
+ # @return [Pathname] path to zip file
78
+ def path
79
+ Pathname.new(zip_io.path) if zip_io.path
80
+ end
81
+
82
+ # Unlinks (deletes) the zip file from the filesystem.
83
+ def unlink
84
+ zip_io.unlink
85
+ end
86
+
87
+ private
88
+
89
+ # @return [Pathname] the directory used to create the Zip file
90
+ # @api private
91
+ attr_reader :dir
92
+
93
+ # @return [#debug] the logger
94
+ # @api private
95
+ attr_reader :logger
96
+
97
+ # @return [IO] the Zip file IO
98
+ # @api private
99
+ attr_reader :zip_io
100
+
101
+ # @return [Array<Pathname] all recursive files under the base
102
+ # directory, excluding directories
103
+ # @api private
104
+ def entries
105
+ Pathname.glob(dir.join('**/*')).delete_if(&:directory?).sort
106
+ end
107
+
108
+ # (see Logging.log_subject)
109
+ # @api private
110
+ def log_subject
111
+ @log_subject ||= [self.class.to_s.split('::').last, path].join('::')
112
+ end
113
+
114
+ # Adds all file entries to the Zip output stream.
115
+ #
116
+ # @param zos [Zip::OutputStream] zip output stream
117
+ # @api private
118
+ def produce_zip_entries(zos)
119
+ entries.each do |entry|
120
+ entry_path = entry.sub(/#{dir}\//i, '')
121
+ logger.debug "+++ Adding #{entry_path}"
122
+ zos.put_next_entry(
123
+ zip_entry(entry_path),
124
+ nil, nil, ::Zip::Entry::DEFLATED, Zlib::BEST_COMPRESSION)
125
+ entry.open('rb') { |src| IO.copy_stream(src, zos) }
126
+ end
127
+ logger.debug '=== All files added.'
128
+ end
129
+
130
+ # Writes out a temporary Zip file.
131
+ #
132
+ # @api private
133
+ def write_zip
134
+ logger.debug 'Populating files'
135
+ Zip::OutputStream.write_buffer(NoDupIO.new(zip_io)) do |zos|
136
+ produce_zip_entries(zos)
137
+ end
138
+ end
139
+
140
+ def zip_entry(entry_path)
141
+ Zip::Entry.new(
142
+ zip_io.path,
143
+ entry_path.to_s,
144
+ nil, nil, nil, nil, nil, nil,
145
+ ::Zip::DOSTime.new(2000)
146
+ )
147
+ end
148
+
149
+ # Simple delegate wrapper to prevent `#dup` calls being made on IO
150
+ # objects. This is used to bypass an issue in the `Zip::Outputstream`
151
+ # constructor where an incoming IO is duplicated, leading to races
152
+ # on flushing the final stream to disk.
153
+ #
154
+ # @author Fletcher Nichol <fnichol@nichol.ca>
155
+ # @api private
156
+ class NoDupIO < SimpleDelegator
157
+ # @return [self] returns self and does *not* return a duplicate
158
+ # object
159
+ def dup
160
+ self
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -14,8 +14,9 @@
14
14
  # See the License for the specific language governing permissions and
15
15
  # limitations under the License.
16
16
 
17
+ require 'winrm'
17
18
  require_relative 'scripts/scripts'
18
- require_relative 'core/upload_orchestrator'
19
+ require_relative 'core/file_transporter'
19
20
 
20
21
  module WinRM
21
22
  module FS
@@ -25,7 +26,7 @@ module WinRM
25
26
  # @param [WinRMWebService] WinRM web service client
26
27
  def initialize(service)
27
28
  @service = service
28
- @logger = Logging.logger[self]
29
+ @logger = service.logger
29
30
  end
30
31
 
31
32
  # Gets the MD5 checksum of the specified file if it exists,
@@ -34,7 +35,7 @@ module WinRM
34
35
  def checksum(path)
35
36
  @logger.debug("checksum: #{path}")
36
37
  script = WinRM::FS::Scripts.render('checksum', path: path)
37
- @service.powershell(script).stdout.chomp
38
+ @service.create_executor { |e| e.run_powershell_script(script).stdout.chomp }
38
39
  end
39
40
 
40
41
  # Create the specifed directory recursively
@@ -43,7 +44,7 @@ module WinRM
43
44
  def create_dir(path)
44
45
  @logger.debug("create_dir: #{path}")
45
46
  script = WinRM::FS::Scripts.render('create_dir', path: path)
46
- @service.powershell(script)[:exitcode] == 0
47
+ @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
47
48
  end
48
49
 
49
50
  # Deletes the file or directory at the specified path
@@ -52,7 +53,7 @@ module WinRM
52
53
  def delete(path)
53
54
  @logger.debug("deleting: #{path}")
54
55
  script = WinRM::FS::Scripts.render('delete', path: path)
55
- @service.powershell(script)[:exitcode] == 0
56
+ @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
56
57
  end
57
58
 
58
59
  # Downloads the specified remote file to the specified local path
@@ -61,7 +62,7 @@ module WinRM
61
62
  def download(remote_path, local_path)
62
63
  @logger.debug("downloading: #{remote_path} -> #{local_path}")
63
64
  script = WinRM::FS::Scripts.render('download', path: remote_path)
64
- output = @service.powershell(script)
65
+ output = @service.create_executor { |e| e.run_powershell_script(script) }
65
66
  return false if output[:exitcode] != 0
66
67
  contents = output.stdout.gsub('\n\r', '')
67
68
  out = Base64.decode64(contents)
@@ -75,14 +76,16 @@ module WinRM
75
76
  def exists?(path)
76
77
  @logger.debug("exists?: #{path}")
77
78
  script = WinRM::FS::Scripts.render('exists', path: path)
78
- @service.powershell(script)[:exitcode] == 0
79
+ @service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
79
80
  end
80
81
 
81
82
  # Gets the current user's TEMP directory on the remote system, for example
82
83
  # 'C:/Windows/Temp'
83
84
  # @return [String] Full path to the temp directory
84
85
  def temp_dir
85
- @guest_temp ||= (@service.cmd('echo %TEMP%')).stdout.chomp.gsub('\\', '/')
86
+ @guest_temp ||= begin
87
+ (@service.create_executor { |e| e.run_cmd('echo %TEMP%') }).stdout.chomp.gsub('\\', '/')
88
+ end
86
89
  end
87
90
 
88
91
  # Upload one or more local files and directories to a remote directory
@@ -104,13 +107,9 @@ module WinRM
104
107
  # @yieldparam [String] Target path on the winrm endpoint
105
108
  # @return [Fixnum] The total number of bytes copied
106
109
  def upload(local_path, remote_path, &block)
107
- @logger.debug("uploading: #{local_path} -> #{remote_path}")
108
-
109
- upload_orchestrator = WinRM::FS::Core::UploadOrchestrator.new(@service)
110
- if File.file?(local_path)
111
- upload_orchestrator.upload_file(local_path, remote_path, &block)
112
- else
113
- upload_orchestrator.upload_directory(local_path, remote_path, &block)
110
+ @service.create_executor do |executor|
111
+ file_transporter ||= WinRM::FS::Core::FileTransporter.new(executor)
112
+ file_transporter.upload(local_path, remote_path, &block)[0]
114
113
  end
115
114
  end
116
115
  end
@@ -0,0 +1,48 @@
1
+ $hash_file = "<%= hash_file %>"
2
+
3
+ Function Cleanup($o) { if (($o -ne $null) -and ($o.GetType().GetMethod("Dispose") -ne $null)) { $o.Dispose() } }
4
+
5
+ Function Decode-Base64File($src, $dst) {
6
+ Try {
7
+ $in = (Get-Item $src).OpenRead()
8
+ $b64 = New-Object -TypeName System.Security.Cryptography.FromBase64Transform
9
+ $m = [System.Security.Cryptography.CryptoStreamMode]::Read
10
+ $d = New-Object -TypeName System.Security.Cryptography.CryptoStream $in,$b64,$m
11
+ Copy-Stream $d ($out = [System.IO.File]::OpenWrite($dst))
12
+ } Finally { Cleanup $in; Cleanup $out; Cleanup $d }
13
+ }
14
+
15
+ Function Copy-Stream($src, $dst) { $b = New-Object Byte[] 4096; while (($i = $src.Read($b, 0, $b.Length)) -ne 0) { $dst.Write($b, 0, $i) } }
16
+
17
+ Function Check-Files($h) {
18
+ return $h.GetEnumerator() | ForEach-Object {
19
+ $dst = Unresolve-Path $_.Key
20
+ New-Object psobject -Property @{
21
+ chk_exists = ($exists = Test-Path $dst -PathType Leaf)
22
+ src_md5 = ($sMd5 = $_.Value)
23
+ dst_md5 = ($dMd5 = if ($exists) { Get-MD5Sum $dst } else { $null })
24
+ chk_dirty = ($dirty = if ($sMd5 -ne $dMd5) { $true } else { $false })
25
+ verifies = if ($dirty -eq $false) { $true } else { $false }
26
+ }
27
+ } | Select-Object -Property chk_exists,src_md5,dst_md5,chk_dirty,verifies
28
+ }
29
+
30
+ Function Get-MD5Sum($src) {
31
+ Try {
32
+ $c = New-Object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
33
+ $bytes = $c.ComputeHash(($in = (Get-Item $src).OpenRead()))
34
+ return ([System.BitConverter]::ToString($bytes)).Replace("-", "").ToLower()
35
+ } Finally { Cleanup $c; Cleanup $in }
36
+ }
37
+
38
+ Function Invoke-Input($in) {
39
+ $in = Unresolve-Path $in
40
+ Decode-Base64File $in ($decoded = "$($in).ps1")
41
+ $expr = Get-Content $decoded | Out-String
42
+ Remove-Item $in,$decoded -Force
43
+ return Invoke-Expression "$expr"
44
+ }
45
+
46
+ Function Unresolve-Path($p) { if ($p -eq $null) { return $null } else { return $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($p) } }
47
+
48
+ Check-Files (Invoke-Input $hash_file) | ConvertTo-Csv -NoTypeInformation
@@ -0,0 +1,59 @@
1
+ trap {$e = $_.Exception; $e.InvocationInfo.ScriptName; do {$e.Message; $e = $e.InnerException} while ($e); break;}
2
+ $progresspreference = 'SilentlyContinue'
3
+ function Decode-Base64File($src, $dst) {folder (split-path $dst);sc -force -Encoding Byte -Path $dst -Value ([Convert]::FromBase64String([IO.File]::ReadAllLines($src)))}
4
+ function Copy-Stream($src, $dst) {$b = New-Object Byte[] 4096; while (($i = $src.Read($b, 0, $b.Length)) -ne 0) { $dst.Write($b, 0, $i) } }
5
+ function Resolve-ProviderPath{ $input | % {if ($_){(Resolve-Path $_).ProviderPath} else{$null}} }
6
+ function Get-FrameworkVersion { "Full", "Client" | % {([version](gp "HKLM:\Software\Microsoft\NET Framework Setup\NDP\v4\$_").Version)} | select -first 1}
7
+ function Test-NETStack($Version){ Get-FrameworkVersion -ge $Version }
8
+ function Test-IOCompression {($PSVersionTable.PSVersion.Major -ge 3) -and (Test-NETStack '4.5')}
9
+ function folder($path){ $path | ? {-not (test-path $_)} | % {$null = mkdir $_}}
10
+ function disposable($o){($o -is [IDisposable]) -and (($o | gm | %{$_.name}) -contains 'Dispose')}
11
+ function use($obj, [scriptblock]$sb){try {& $sb} catch [exception]{throw $_} finally {if (disposable $obj) {$obj.Dispose()}} }
12
+ set-alias RPP -Value Resolve-ProviderPath
13
+
14
+ Function Decode-Files($hash) {
15
+ foreach ($key in $hash.keys) {
16
+ $value = $hash[$key]
17
+ $tmp, $tzip, $dst = $Key, $Value["tmpzip"], $Value["dst"]
18
+ if($tmp -ne "") {
19
+ $sMd5 = (gi $tmp).BaseName.Replace("b64-", "")
20
+ $decoded = if ($tzip -ne $null) { $tzip } else { $dst }
21
+ Decode-Base64File $tmp $decoded
22
+ rm $tmp -Force
23
+ $dMd5 = Get-MD5Sum $decoded
24
+ $verifies = $sMd5 -like $dMd5
25
+ }
26
+ if ($tzip) {Unzip-File $tzip $dst}
27
+ New-Object psobject -Property @{dst=$dst;verifies=$verifies;src_md5=$sMd5;dst_md5=$dMd5;tmpfile=$tmp;tmpzip=$tzip}
28
+ }
29
+ }
30
+
31
+ Function Get-MD5Sum($src) {
32
+ if ($src -and (test-path $src)) {
33
+ use ($c = New-Object -TypeName Security.Cryptography.MD5CryptoServiceProvider) {
34
+ use ($in = (gi $src).OpenRead()) {([BitConverter]::ToString($c.ComputeHash($in))).Replace("-", "").ToLower()}}
35
+ }
36
+ }
37
+
38
+ Function Invoke-Input($in) {
39
+ $in = $in | rpp
40
+ $d = "$($in).ps1"
41
+ Decode-Base64File $in $d
42
+ $expr = gc $d | Out-String
43
+ rm $in,$d -Force
44
+ iex "$expr"
45
+ }
46
+
47
+ Function Unzip-File($src, $dst) {
48
+ $unpack = $src -replace '\.zip'
49
+ $dst_parent = Split-Path -Path $dst -Parent
50
+ if(!(Test-Path $dst_parent)) { $dst = $dst_parent }
51
+ folder $unpack, $dst
52
+ if (Test-IOCompression) {Add-Type -AN System.IO.Compression.FileSystem; [IO.Compression.ZipFile]::ExtractToDirectory($src, $unpack)}
53
+ else {Try {$s = New-Object -ComObject Shell.Application; ($s.NameSpace($unpack)).CopyHere(($s.NameSpace($src)).Items(), 0x610)} Finally {[void][Runtime.Interopservices.Marshal]::ReleaseComObject($s)}}
54
+ dir $unpack | cp -dest "$dst/" -force -recurse
55
+ rm $unpack -recurse -force
56
+ }
57
+
58
+ $hash_file = "<%= hash_file %>"
59
+ Decode-Files (Invoke-Input $hash_file) | ConvertTo-Csv -NoTypeInformation
@@ -1,7 +1,8 @@
1
1
  $p = $ExecutionContext.SessionState.Path
2
2
  $path = $p.GetUnresolvedProviderPathFromPSPath("<%= path %>")
3
3
  if (Test-Path $path -PathType Leaf) {
4
- [System.convert]::ToBase64String([System.IO.File]::ReadAllBytes($path))
4
+ $bytes = [System.convert]::ToBase64String([System.IO.File]::ReadAllBytes($path))
5
+ Write-Host $bytes
5
6
  exit 0
6
7
  }
7
8
  exit 1
@@ -1,5 +1,5 @@
1
1
  auth_type: plaintext
2
- endpoint: "http://localhost:55985/wsman"
2
+ endpoint: "http://192.168.137.20:5985/wsman"
3
3
  options:
4
4
  user: vagrant
5
5
  pass: vagrant
@@ -1,10 +1,10 @@
1
1
  # encoding: UTF-8
2
2
  require 'pathname'
3
3
 
4
- describe WinRM::FS::FileManager, integration: true do
4
+ describe WinRM::FS::FileManager do
5
5
  let(:dest_dir) { File.join(subject.temp_dir, "winrm_#{rand(2**16)}") }
6
6
  let(:temp_upload_dir) { '$env:TEMP/winrm-upload' }
7
- let(:this_dir) { File.expand_path(File.dirname(__FILE__)) }
7
+ let(:spec_dir) { File.expand_path(File.dirname(File.dirname(__FILE__))) }
8
8
  let(:this_file) { __FILE__ }
9
9
  let(:service) { winrm_connection }
10
10
 
@@ -35,7 +35,7 @@ describe WinRM::FS::FileManager, integration: true do
35
35
 
36
36
  context 'temp_dir' do
37
37
  it 'should return the remote users temp dir' do
38
- expect(subject.temp_dir).to match(%r{C:/Users/\w+/AppData/Local/Temp})
38
+ expect(subject.temp_dir).to match(%r{C:/Users/\S+/AppData/Local/Temp})
39
39
  end
40
40
  end
41
41
 
@@ -58,7 +58,7 @@ describe WinRM::FS::FileManager, integration: true do
58
58
  end
59
59
 
60
60
  it 'should upload using relative file path' do
61
- subject.upload('./spec/file_manager_spec.rb', dest_file)
61
+ subject.upload('./spec/integration/file_manager_spec.rb', dest_file)
62
62
  expect(subject).to have_created(dest_file).with_content(this_file)
63
63
  end
64
64
 
@@ -88,14 +88,16 @@ describe WinRM::FS::FileManager, integration: true do
88
88
 
89
89
  it 'yields progress data' do
90
90
  block_called = false
91
+ total_bytes_copied = 0
91
92
  total = subject.upload(this_file, dest_file) do \
92
93
  |bytes_copied, total_bytes, local_path, remote_path|
93
94
  expect(total_bytes).to be > 0
94
- expect(bytes_copied).to eq(total_bytes)
95
+ total_bytes_copied = bytes_copied
95
96
  expect(local_path).to eq(this_file)
96
97
  expect(remote_path).to eq(dest_file)
97
98
  block_called = true
98
99
  end
100
+ expect(total_bytes_copied).to eq(total)
99
101
  expect(block_called).to be true
100
102
  expect(total).to be > 0
101
103
  end
@@ -107,7 +109,7 @@ describe WinRM::FS::FileManager, integration: true do
107
109
  end
108
110
 
109
111
  it 'should upload when content differs' do
110
- matchers_file = File.join(this_dir, 'matchers.rb')
112
+ matchers_file = File.join(spec_dir, 'matchers.rb')
111
113
  subject.upload(matchers_file, dest_file)
112
114
  bytes_uploaded = subject.upload(this_file, dest_file)
113
115
  expect(bytes_uploaded).to be > 0
@@ -119,32 +121,48 @@ describe WinRM::FS::FileManager, integration: true do
119
121
  end
120
122
 
121
123
  context 'upload empty file' do
122
- let(:empty_src_file) { Tempfile.new('empty').path }
124
+ let(:empty_src_file) { Tempfile.new('empty') }
123
125
  let(:dest_file) { File.join(dest_dir, 'emptyfile.txt') }
124
126
 
125
127
  it 'creates a new empty file' do
126
- expect(subject.upload(empty_src_file, dest_file)).to be 0
128
+ expect(subject.upload(empty_src_file.path, dest_file)).to be 0
127
129
  expect(subject).to have_created(dest_file).with_content('')
128
130
  end
129
131
 
130
132
  it 'overwrites an existing file' do
131
133
  expect(subject.upload(this_file, dest_file)).to be > 0
132
- expect(subject.upload(empty_src_file, dest_file)).to be 0
134
+ expect(subject.upload(empty_src_file.path, dest_file)).to be 0
133
135
  expect(subject).to have_created(dest_file).with_content('')
134
136
  end
135
137
  end
136
138
 
137
139
  context 'upload directory' do
138
- let(:root_dir) { File.expand_path('../', File.dirname(__FILE__)) }
140
+ let(:root_dir) { File.expand_path('../../', File.dirname(__FILE__)) }
139
141
  let(:winrm_fs_dir) { File.join(root_dir, 'lib/winrm-fs') }
140
142
  let(:core_dir) { File.join(root_dir, 'lib/winrm-fs/core') }
141
143
 
142
- it 'copies the entire directory recursively' do
144
+ it 'copies the directory contents recursively when directory does not exist' do
143
145
  bytes_uploaded = subject.upload(winrm_fs_dir, dest_dir)
144
146
  expect(bytes_uploaded).to be > 0
145
147
 
146
148
  Dir.glob(winrm_fs_dir + '/**/*.rb').each do |host_file|
147
- host_file_rel = Pathname.new(host_file).relative_path_from(Pathname.new(winrm_fs_dir)).to_s
149
+ host_file_rel = Pathname.new(host_file).relative_path_from(
150
+ Pathname.new(winrm_fs_dir)
151
+ ).to_s
152
+ remote_file = File.join(dest_dir, host_file_rel)
153
+ expect(subject).to have_created(remote_file).with_content(host_file)
154
+ end
155
+ end
156
+
157
+ it 'copies the directory recursively when directory does exist' do
158
+ subject.create_dir(dest_dir)
159
+ bytes_uploaded = subject.upload(winrm_fs_dir, dest_dir)
160
+ expect(bytes_uploaded).to be > 0
161
+
162
+ Dir.glob(winrm_fs_dir + '/**/*.rb').each do |host_file|
163
+ host_file_rel = Pathname.new(host_file).relative_path_from(
164
+ Pathname.new(winrm_fs_dir).dirname
165
+ ).to_s
148
166
  remote_file = File.join(dest_dir, host_file_rel)
149
167
  expect(subject).to have_created(remote_file).with_content(host_file)
150
168
  end
@@ -156,6 +174,14 @@ describe WinRM::FS::FileManager, integration: true do
156
174
  expect(bytes_uploaded).to eq 0
157
175
  end
158
176
 
177
+ it 'unzips the directory when cached content is the same' do
178
+ subject.upload(winrm_fs_dir, dest_dir)
179
+ subject.delete(dest_dir)
180
+ expect(subject.exists?(dest_dir)).to be false
181
+ subject.upload(winrm_fs_dir, dest_dir)
182
+ expect(subject.exists?(dest_dir)).to be true
183
+ end
184
+
159
185
  it 'copies the directory when content differs' do
160
186
  subject.upload(winrm_fs_dir, dest_dir)
161
187
  bytes_uploaded = subject.upload(core_dir, dest_dir)