winrm-fs 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.rubocop.yml +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENSE +202 -0
- data/README.md +62 -0
- data/Rakefile +30 -0
- data/VERSION +1 -0
- data/Vagrantfile +9 -0
- data/bin/rwinrmcp +86 -0
- data/changelog.md +1 -0
- data/lib/winrm-fs.rb +27 -0
- data/lib/winrm-fs/core/command_executor.rb +69 -0
- data/lib/winrm-fs/core/file_uploader.rb +84 -0
- data/lib/winrm-fs/core/temp_zip_file.rb +95 -0
- data/lib/winrm-fs/core/upload_orchestrator.rb +118 -0
- data/lib/winrm-fs/exceptions.rb +28 -0
- data/lib/winrm-fs/file_manager.rb +123 -0
- data/lib/winrm-fs/scripts/checksum.ps1.erb +13 -0
- data/lib/winrm-fs/scripts/create_dir.ps1.erb +6 -0
- data/lib/winrm-fs/scripts/decode_file.ps1.erb +36 -0
- data/lib/winrm-fs/scripts/delete.ps1.erb +6 -0
- data/lib/winrm-fs/scripts/download.ps1.erb +7 -0
- data/lib/winrm-fs/scripts/exists.ps1.erb +8 -0
- data/lib/winrm-fs/scripts/scripts.rb +31 -0
- data/spec/config-example.yml +5 -0
- data/spec/file_manager_spec.rb +140 -0
- data/spec/matchers.rb +54 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/temp_zip_file_spec.rb +71 -0
- data/winrm-fs.gemspec +36 -0
- metadata +189 -0
data/changelog.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0 - Initial alpha quality release
|
data/lib/winrm-fs.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'winrm'
|
18
|
+
require 'logger'
|
19
|
+
require_relative 'winrm-fs/exceptions'
|
20
|
+
require_relative 'winrm-fs/file_manager'
|
21
|
+
|
22
|
+
module WinRM
|
23
|
+
# WinRM File System
|
24
|
+
module FS
|
25
|
+
# Top level module code
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module WinRM
|
18
|
+
module FS
|
19
|
+
module Core
|
20
|
+
# Executes commands used by the WinRM file management module
|
21
|
+
class CommandExecutor
|
22
|
+
def initialize(service)
|
23
|
+
@service = service
|
24
|
+
end
|
25
|
+
|
26
|
+
def open
|
27
|
+
@shell = @service.open_shell
|
28
|
+
@shell_open = true
|
29
|
+
end
|
30
|
+
|
31
|
+
def close
|
32
|
+
@service.close_shell(@shell) if @shell
|
33
|
+
@shell_open = false
|
34
|
+
end
|
35
|
+
|
36
|
+
def run_powershell(script)
|
37
|
+
assert_shell_is_open
|
38
|
+
run_cmd('powershell', ['-encodedCommand', encode_script(script)])
|
39
|
+
end
|
40
|
+
|
41
|
+
def run_cmd(command, arguments = [])
|
42
|
+
assert_shell_is_open
|
43
|
+
result = nil
|
44
|
+
@service.run_command(@shell, command, arguments) do |command_id|
|
45
|
+
result = @service.get_command_output(@shell, command_id)
|
46
|
+
end
|
47
|
+
assert_command_success(command, result)
|
48
|
+
result.stdout
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def assert_shell_is_open
|
54
|
+
fail 'You must call open before calling any run methods' unless @shell_open
|
55
|
+
end
|
56
|
+
|
57
|
+
def assert_command_success(command, result)
|
58
|
+
return if result[:exitcode] == 0 && result.stderr.length == 0
|
59
|
+
fail WinRMUploadError, command + '\n' + result.output
|
60
|
+
end
|
61
|
+
|
62
|
+
def encode_script(script)
|
63
|
+
encoded_script = script.encode('UTF-16LE', 'UTF-8')
|
64
|
+
Base64.strict_encode64(encoded_script)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require_relative 'command_executor'
|
18
|
+
|
19
|
+
module WinRM
|
20
|
+
module FS
|
21
|
+
module Core
|
22
|
+
# Uploads the given source file to a temp file in 8k chunks
|
23
|
+
class FileUploader
|
24
|
+
def initialize(command_executor)
|
25
|
+
@command_executor = command_executor
|
26
|
+
end
|
27
|
+
|
28
|
+
# Uploads the given file to the specified temp file as base64 encoded.
|
29
|
+
#
|
30
|
+
# @param [String] Path to the local source file on this machine
|
31
|
+
# @param [String] Path to the file on the target machine
|
32
|
+
# @return [Integer] Count of bytes uploaded
|
33
|
+
def upload(local_file, remote_file)
|
34
|
+
@command_executor.run_powershell(prepare_script(remote_file))
|
35
|
+
do_upload(local_file, dos_path(remote_file))
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def do_upload(local_file, remote_file)
|
41
|
+
bytes_copied = 0
|
42
|
+
base64_array = base64_content(local_file)
|
43
|
+
base64_array.each_slice(8000 - remote_file.size) do |chunk|
|
44
|
+
@command_executor.run_cmd("echo #{chunk.join} >> \"#{remote_file}\"")
|
45
|
+
bytes_copied += chunk.count
|
46
|
+
yield bytes_copied, base64_array.count if block_given?
|
47
|
+
end
|
48
|
+
base64_array.length
|
49
|
+
end
|
50
|
+
|
51
|
+
def base64_content(local_file)
|
52
|
+
base64_host_file = Base64.encode64(IO.binread(local_file)).gsub("\n", '')
|
53
|
+
base64_host_file.chars.to_a
|
54
|
+
end
|
55
|
+
|
56
|
+
def dos_path(path)
|
57
|
+
# TODO: convert all env vars
|
58
|
+
path = path.gsub(/\$env:TEMP/, '%TEMP%')
|
59
|
+
path.gsub(/\\/, '/')
|
60
|
+
end
|
61
|
+
|
62
|
+
# rubocop:disable Metrics/MethodLength
|
63
|
+
def prepare_script(remote_file)
|
64
|
+
<<-EOH
|
65
|
+
$p = $ExecutionContext.SessionState.Path
|
66
|
+
$path = $p.GetUnresolvedProviderPathFromPSPath("#{remote_file}")
|
67
|
+
|
68
|
+
# if the file exists, truncate it
|
69
|
+
if (Test-Path $path -PathType Leaf) {
|
70
|
+
'' | Set-Content $path
|
71
|
+
}
|
72
|
+
|
73
|
+
# ensure the target directory exists
|
74
|
+
$dir = [System.IO.Path]::GetDirectoryName($path)
|
75
|
+
if (!(Test-Path $dir -PathType Container)) {
|
76
|
+
mkdir $dir -ErrorAction SilentlyContinue | Out-Null
|
77
|
+
}
|
78
|
+
EOH
|
79
|
+
end
|
80
|
+
# rubocop:enable Metrics/MethodLength
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require 'zip'
|
18
|
+
|
19
|
+
module WinRM
|
20
|
+
module FS
|
21
|
+
module Core
|
22
|
+
# Temporary zip file on the local system
|
23
|
+
class TempZipFile
|
24
|
+
attr_reader :path
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@logger = Logging.logger[self]
|
28
|
+
@zip_file = Tempfile.new(['winrm_upload', '.zip'])
|
29
|
+
@zip_file.close
|
30
|
+
@path = @zip_file.path
|
31
|
+
end
|
32
|
+
|
33
|
+
# Adds a file or directory to the temporary zip file
|
34
|
+
# @param [String] Directory or file path to add into zip
|
35
|
+
def add(path)
|
36
|
+
if File.directory?(path)
|
37
|
+
add_directory(path)
|
38
|
+
elsif File.file?(path)
|
39
|
+
add_file(path)
|
40
|
+
else
|
41
|
+
fail "#{path} doesn't exist"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Adds all files in the specified directory recursively into the zip file
|
46
|
+
# @param [String] Directory to add into zip
|
47
|
+
def add_directory(dir)
|
48
|
+
fail "#{dir} isn't a directory" unless File.directory?(dir)
|
49
|
+
glob = File.join(dir, '**/*')
|
50
|
+
Dir.glob(glob).each do |file|
|
51
|
+
add_file_entry(file, dir)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def add_file(file)
|
56
|
+
fail "#{file} isn't a file" unless File.file?(file)
|
57
|
+
add_file_entry(file, File.dirname(file))
|
58
|
+
end
|
59
|
+
|
60
|
+
def delete
|
61
|
+
@zip_file.delete
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def add_file_entry(file, base_dir)
|
67
|
+
base_dir = "#{base_dir}/" unless base_dir.end_with?('/')
|
68
|
+
file_entry_path = file[base_dir.length..-1]
|
69
|
+
write_zip_entry(file, file_entry_path)
|
70
|
+
end
|
71
|
+
|
72
|
+
def write_zip_entry(file, file_entry_path)
|
73
|
+
@logger.debug("adding zip entry: #{file_entry_path}")
|
74
|
+
Zip::File.open(@path, 'w') do |zipfile|
|
75
|
+
entry = new_zip_entry(file_entry_path)
|
76
|
+
zipfile.add(entry, file)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def new_zip_entry(file_entry_path)
|
81
|
+
Zip::Entry.new(
|
82
|
+
@path,
|
83
|
+
file_entry_path,
|
84
|
+
nil,
|
85
|
+
nil,
|
86
|
+
nil,
|
87
|
+
nil,
|
88
|
+
nil,
|
89
|
+
nil,
|
90
|
+
::Zip::DOSTime.new(2000))
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require_relative 'temp_zip_file'
|
18
|
+
require_relative 'file_uploader'
|
19
|
+
require_relative 'command_executor'
|
20
|
+
require_relative '../scripts/scripts'
|
21
|
+
|
22
|
+
module WinRM
|
23
|
+
module FS
|
24
|
+
module Core
|
25
|
+
# Orchestrates the upload of a file or directory
|
26
|
+
class UploadOrchestrator
|
27
|
+
def initialize(service)
|
28
|
+
@service = service
|
29
|
+
@logger = Logging.logger[self]
|
30
|
+
end
|
31
|
+
|
32
|
+
def upload_file(local_path, remote_path)
|
33
|
+
# If the src has a file extension and the destination does not
|
34
|
+
# we can assume the caller specified the dest as a directory
|
35
|
+
if File.extname(local_path) != '' && File.extname(remote_path) == ''
|
36
|
+
remote_path = File.join(remote_path, File.basename(local_path))
|
37
|
+
end
|
38
|
+
temp_path = temp_file_path(local_path)
|
39
|
+
with_command_executor do |cmd_executor|
|
40
|
+
return 0 unless out_of_date?(cmd_executor, local_path, remote_path)
|
41
|
+
do_file_upload(cmd_executor, local_path, temp_path, remote_path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def upload_directory(local_paths, remote_path)
|
46
|
+
with_local_zip(local_paths) do |local_zip|
|
47
|
+
temp_path = temp_file_path(local_zip.path)
|
48
|
+
with_command_executor do |cmd_executor|
|
49
|
+
return 0 unless out_of_date?(cmd_executor, local_zip.path, temp_path)
|
50
|
+
do_file_upload(cmd_executor, local_zip.path, temp_path, remote_path)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def do_file_upload(cmd_executor, local_path, temp_path, remote_path)
|
58
|
+
file_uploader = WinRM::FS::Core::FileUploader.new(cmd_executor)
|
59
|
+
bytes = file_uploader.upload(local_path, temp_path) do |bytes_copied, total_bytes|
|
60
|
+
yield bytes_copied, total_bytes, local_path, remote_path if block_given?
|
61
|
+
end
|
62
|
+
|
63
|
+
cmd_executor.run_powershell(
|
64
|
+
WinRM::FS::Scripts.render('decode_file', src: temp_path, dest: remote_path))
|
65
|
+
|
66
|
+
bytes
|
67
|
+
end
|
68
|
+
|
69
|
+
def with_command_executor
|
70
|
+
cmd_executor = WinRM::FS::Core::CommandExecutor.new(@service)
|
71
|
+
cmd_executor.open
|
72
|
+
yield cmd_executor
|
73
|
+
ensure
|
74
|
+
cmd_executor.close
|
75
|
+
end
|
76
|
+
|
77
|
+
def with_local_zip(local_paths)
|
78
|
+
local_zip = create_temp_zip_file(local_paths)
|
79
|
+
yield local_zip
|
80
|
+
ensure
|
81
|
+
local_zip.delete if local_zip
|
82
|
+
end
|
83
|
+
|
84
|
+
def out_of_date?(cmd_executor, local_path, remote_path)
|
85
|
+
local_checksum = local_checksum(local_path)
|
86
|
+
remote_checksum = remote_checksum(cmd_executor, remote_path)
|
87
|
+
|
88
|
+
if remote_checksum == local_checksum
|
89
|
+
@logger.debug("#{remote_path} is up to date")
|
90
|
+
return false
|
91
|
+
end
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
def remote_checksum(cmd_executor, remote_path)
|
96
|
+
script = WinRM::FS::Scripts.render('checksum', path: remote_path)
|
97
|
+
cmd_executor.run_powershell(script).chomp
|
98
|
+
end
|
99
|
+
|
100
|
+
def local_checksum(local_path)
|
101
|
+
Digest::MD5.file(local_path).hexdigest
|
102
|
+
end
|
103
|
+
|
104
|
+
def temp_file_path(local_path)
|
105
|
+
ext = '.tmp'
|
106
|
+
ext = '.zip' if File.extname(local_path) == '.zip'
|
107
|
+
"$env:TEMP/winrm-upload/#{local_checksum(local_path)}#{ext}"
|
108
|
+
end
|
109
|
+
|
110
|
+
def create_temp_zip_file(local_paths)
|
111
|
+
temp_zip = WinRM::FS::Core::TempZipFile.new
|
112
|
+
local_paths.each { |p| temp_zip.add(p) }
|
113
|
+
temp_zip
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
module WinRM
|
18
|
+
module FS
|
19
|
+
# WinRM-FS base class for errors
|
20
|
+
class WinRMFSError < StandardError; end
|
21
|
+
|
22
|
+
# Error that occurs when a file upload fails
|
23
|
+
class WinRMUploadError < WinRMFSError; end
|
24
|
+
|
25
|
+
# Error that occurs when a file download fails
|
26
|
+
class WinRMDownloadError < WinRMFSError; end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require_relative 'scripts/scripts'
|
18
|
+
require_relative 'core/upload_orchestrator'
|
19
|
+
|
20
|
+
module WinRM
|
21
|
+
module FS
|
22
|
+
# Perform file transfer operations between a local machine and winrm endpoint
|
23
|
+
class FileManager
|
24
|
+
# Creates a new FileManager instance
|
25
|
+
# @param [WinRMWebService] WinRM web service client
|
26
|
+
def initialize(service)
|
27
|
+
@service = service
|
28
|
+
@logger = Logging.logger[self]
|
29
|
+
end
|
30
|
+
|
31
|
+
# Gets the MD5 checksum of the specified file if it exists,
|
32
|
+
# otherwise ''
|
33
|
+
# @param [String] The remote file path
|
34
|
+
def checksum(path)
|
35
|
+
@logger.debug("checksum: #{path}")
|
36
|
+
script = WinRM::FS::Scripts.render('checksum', path: path)
|
37
|
+
@service.powershell(script).stdout.chomp
|
38
|
+
end
|
39
|
+
|
40
|
+
# Create the specifed directory recursively
|
41
|
+
# @param [String] The remote dir to create
|
42
|
+
# @return [Boolean] True if successful, otherwise false
|
43
|
+
def create_dir(path)
|
44
|
+
@logger.debug("create_dir: #{path}")
|
45
|
+
script = WinRM::FS::Scripts.render('create_dir', path: path)
|
46
|
+
@service.powershell(script)[:exitcode] == 0
|
47
|
+
end
|
48
|
+
|
49
|
+
# Deletes the file or directory at the specified path
|
50
|
+
# @param [String] The path to remove
|
51
|
+
# @return [Boolean] True if successful, otherwise False
|
52
|
+
def delete(path)
|
53
|
+
@logger.debug("deleting: #{path}")
|
54
|
+
script = WinRM::FS::Scripts.render('delete', path: path)
|
55
|
+
@service.powershell(script)[:exitcode] == 0
|
56
|
+
end
|
57
|
+
|
58
|
+
# Downloads the specified remote file to the specified local path
|
59
|
+
# @param [String] The full path on the remote machine
|
60
|
+
# @param [String] The full path to write the file to locally
|
61
|
+
def download(remote_path, local_path)
|
62
|
+
@logger.debug("downloading: #{remote_path} -> #{local_path}")
|
63
|
+
script = WinRM::FS::Scripts.render('download', path: remote_path)
|
64
|
+
output = @service.powershell(script)
|
65
|
+
return false if output[:exitcode] != 0
|
66
|
+
contents = output.stdout.gsub('\n\r', '')
|
67
|
+
out = Base64.decode64(contents)
|
68
|
+
IO.binwrite(local_path, out)
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
# Checks to see if the given path exists on the target file system.
|
73
|
+
# @param [String] The full path to the directory or file
|
74
|
+
# @return [Boolean] True if the file/dir exists, otherwise false.
|
75
|
+
def exists?(path)
|
76
|
+
@logger.debug("exists?: #{path}")
|
77
|
+
script = WinRM::FS::Scripts.render('exists', path: path)
|
78
|
+
@service.powershell(script)[:exitcode] == 0
|
79
|
+
end
|
80
|
+
|
81
|
+
# Gets the current user's TEMP directory on the remote system, for example
|
82
|
+
# 'C:/Windows/Temp'
|
83
|
+
# @return [String] Full path to the temp directory
|
84
|
+
def temp_dir
|
85
|
+
@guest_temp ||= (@service.cmd('echo %TEMP%')).stdout.chomp.gsub('\\', '/')
|
86
|
+
end
|
87
|
+
|
88
|
+
# Upload one or more local files and directories to a remote directory
|
89
|
+
# @example copy a single directory to a winrm endpoint
|
90
|
+
#
|
91
|
+
# file_manager.upload('c:/dev/my_dir', '$env:AppData')
|
92
|
+
#
|
93
|
+
# @example copy several paths to the winrm endpoint
|
94
|
+
#
|
95
|
+
# file_manager.upload(['c:/dev/file1.txt','c:/dev/dir1'], '$env:AppData')
|
96
|
+
#
|
97
|
+
# @param [Array<String>] One or more paths that will be copied to the remote path.
|
98
|
+
# These can be files or directories to be deeply copied
|
99
|
+
# @param [String] The target directory or file
|
100
|
+
# This path may contain powershell style environment variables
|
101
|
+
# @yieldparam [Fixnum] Number of bytes copied in current payload sent to the winrm endpoint
|
102
|
+
# @yieldparam [Fixnum] The total number of bytes to be copied
|
103
|
+
# @yieldparam [String] Path of file being copied
|
104
|
+
# @yieldparam [String] Target path on the winrm endpoint
|
105
|
+
# @return [Fixnum] The total number of bytes copied
|
106
|
+
def upload(local_paths, remote_path, &block)
|
107
|
+
@logger.debug("uploading: #{local_paths} -> #{remote_path}")
|
108
|
+
local_paths = [local_paths] if local_paths.is_a? String
|
109
|
+
|
110
|
+
upload_orchestrator = WinRM::FS::Core::UploadOrchestrator.new(@service)
|
111
|
+
if FileManager.src_is_single_file?(local_paths)
|
112
|
+
upload_orchestrator.upload_file(local_paths[0], remote_path, &block)
|
113
|
+
else
|
114
|
+
upload_orchestrator.upload_directory(local_paths, remote_path, &block)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def self.src_is_single_file?(local_paths)
|
119
|
+
local_paths.count == 1 && File.file?(local_paths[0])
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|