Skip to content

Commit

Permalink
initial integration work with winrm-v2
Browse files Browse the repository at this point in the history
  • Loading branch information
mwrock committed Jul 8, 2016
1 parent 5d76bbd commit 2f8f76e
Show file tree
Hide file tree
Showing 8 changed files with 67 additions and 58 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# encoding: UTF-8
source 'https://rubygems.org'
gemspec

gem 'winrm', github: 'winrb/winrm', branch: 'winrm-v2'
gem 'rb-readline'
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ end

# Run the integration test suite
RSpec::Core::RakeTask.new(:integration) do |task|
task.pattern = 'spec/integration/*_spec.rb'
task.pattern = 'spec/integration/benchmark_spec.rb'
task.rspec_opts = ['--color', '-f documentation']
end

Expand Down
51 changes: 17 additions & 34 deletions lib/winrm-fs/core/file_transporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,21 @@ class FileTransporterFailed < ::WinRM::WinRMError; end
# sessions being invoked which can be 2 orders of magnitude more
# expensive than vanilla CMD commands.
#
# This object is supported by either a `CommandExecutor` instance as it
# depends on the `#run_cmd` and `#run_powershell_script` API contracts.
# This object is supported by a `PowerShell` instance as it
# depends on the `#run` API contract.
#
# An optional logger can be supplied, assuming it can respond to the
# `#debug` and `#debug?` messages.
#
# @author Fletcher Nichol <[email protected]>
# @author Matt Wrock <[email protected]>
class FileTransporter
# Creates a FileTransporter given a CommandExecutor object.
# Creates a FileTransporter given a PowerShell object.
#
# @param executor [CommandExecutor] a winrm CommandExecutor object
def initialize(executor, opts = {})
@executor = executor
@logger = executor.service.logger
# @param shell [PowerShell] a winrm PowerShell object
def initialize(shell, opts = {})
@shell = shell
@logger = shell.logger
@id_generator = opts.fetch(:id_generator) { -> { SecureRandom.uuid } }
end

Expand Down Expand Up @@ -112,7 +112,7 @@ def upload(locals, remote)
# a Windows CMD prompt without exceeded the maximum command line
# length
# @api private
MAX_ENCODED_WRITE = 8000
MAX_ENCODED_WRITE = 376000

# @return [String] the Array pack template for Base64 encoding a stream
# of data
Expand All @@ -128,9 +128,9 @@ def upload(locals, remote)
# @api private
attr_reader :logger

# @return [Winrm::CommandExecutor] a WinRM CommandExecutor
# @return [Winrm::Commandshell] a WinRM Commandshell
# @api private
attr_reader :executor
attr_reader :shell

# Examines the files and corrects the file destination if it is
# targeting an existing folder. In this case, the destination path
Expand Down Expand Up @@ -200,7 +200,7 @@ def check_files(files)
logger.debug 'Running check_files.ps1'
hash_file = create_remote_hash_file(check_files_ps_hash(files))
script = WinRM::FS::Scripts.render('check_files', hash_file: hash_file)
parse_response(executor.run_powershell_script(script))
parse_response(shell.run(script))
end

# Constructs a collection of destination path/MD5 checksum pairs as a
Expand Down Expand Up @@ -270,7 +270,7 @@ def decode_files(files)
hash_file = create_remote_hash_file(decoded_files)
script = WinRM::FS::Scripts.render('decode_files', hash_file: hash_file)

parse_response(executor.run_powershell_script(script))
parse_response(shell.run(script))
end
end

Expand Down Expand Up @@ -358,20 +358,6 @@ def pad(depth = 0)
' ' * depth
end

# Parses CLIXML String into regular String (without any XML syntax).
# Inspired by https://github.com/WinRb/WinRM/issues/106.
#
# @param clixml [String] clixml text
# @return [String] parsed clixml into String
def clixml_to_s(clixml)
doc = REXML::Document.new(clixml)
text = doc.get_elements('//S').map(&:text).join
text.gsub(/_x(\h\h\h\h)_/) do
code = Regexp.last_match[1]
code.hex.chr
end
end

# Parses response of a PowerShell script or CMD command which contains
# a CSV-formatted document in the standard output stream.
#
Expand All @@ -388,14 +374,13 @@ def parse_response(output)
fail StandardError, 'The command line is too long' \
' (powershell script is too long)'
end
pretty_stderr = clixml_to_s(stderr)

if exitcode != 0
fail FileTransporterFailed, "[#{self.class}] Upload failed " \
"(exitcode: #{exitcode})\n#{pretty_stderr}"
elsif pretty_stderr != '\r\n' && pretty_stderr != ''
"(exitcode: #{exitcode})\n#{stderr}"
elsif stderr != '\r\n' && stderr != ''
fail FileTransporterFailed, "[#{self.class}] Upload failed " \
"(exitcode: 0), but stderr present\n#{pretty_stderr}"
"(exitcode: 0), but stderr present\n#{stderr}"
end

logger.debug 'Parsing CSV Response'
Expand Down Expand Up @@ -441,15 +426,13 @@ def ps_hash(obj, depth = 0)
# the number of bytes transferred to the remote host
# @api private
def stream_upload(input_io, dest)
dest_cmd = dest.sub('$env:TEMP', '%TEMP%')
read_size = (MAX_ENCODED_WRITE.to_i / 4) * 3
chunk, bytes = 1, 0
buffer = ''
executor.run_cmd(%(echo|set /p=>"#{dest_cmd}")) # truncate empty file
shell.run("\"\" | Out-File #{dest} -Encoding ascii")
while input_io.read(read_size, buffer)
bytes += (buffer.bytesize / 3 * 4)
executor.run_cmd([buffer].pack(BASE64_PACK)
.insert(0, 'echo ').concat(%( >> "#{dest_cmd}")))
shell.run("\"#{[buffer].pack(BASE64_PACK)}\" | Out-File #{dest} -Encoding ascii -Append")
logger.debug "Wrote chunk #{chunk} for #{dest}" if chunk % 25 == 0
chunk += 1
yield bytes if block_given?
Expand Down
24 changes: 12 additions & 12 deletions lib/winrm-fs/file_manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ module FS
# Perform file transfer operations between a local machine and winrm endpoint
class FileManager
# Creates a new FileManager instance
# @param [WinRMWebService] WinRM web service client
def initialize(service)
@service = service
@logger = service.logger
# @param [WinRM::Connection] WinRM web connection client
def initialize(connection)
@connection = connection
@logger = connection.logger
end

# Gets the MD5 checksum of the specified file if it exists,
Expand All @@ -35,7 +35,7 @@ def initialize(service)
def checksum(path)
@logger.debug("checksum: #{path}")
script = WinRM::FS::Scripts.render('checksum', path: path)
@service.create_executor { |e| e.run_powershell_script(script).stdout.chomp }
@connection.shell(:powershell) { |e| e.run(script).stdout.chomp }
end

# Create the specifed directory recursively
Expand All @@ -44,7 +44,7 @@ def checksum(path)
def create_dir(path)
@logger.debug("create_dir: #{path}")
script = WinRM::FS::Scripts.render('create_dir', path: path)
@service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
@connection.shell(:powershell) { |e| e.run(script)[:exitcode] == 0 }
end

# Deletes the file or directory at the specified path
Expand All @@ -53,7 +53,7 @@ def create_dir(path)
def delete(path)
@logger.debug("deleting: #{path}")
script = WinRM::FS::Scripts.render('delete', path: path)
@service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
@connection.shell(:powershell) { |e| e.run(script)[:exitcode] == 0 }
end

# Downloads the specified remote file to the specified local path
Expand All @@ -62,7 +62,7 @@ def delete(path)
def download(remote_path, local_path)
@logger.debug("downloading: #{remote_path} -> #{local_path}")
script = WinRM::FS::Scripts.render('download', path: remote_path)
output = @service.create_executor { |e| e.run_powershell_script(script) }
output = @connection.shell(:powershell) { |e| e.run(script) }
return false if output[:exitcode] != 0
contents = output.stdout.gsub('\n\r', '')
out = Base64.decode64(contents)
Expand All @@ -76,15 +76,15 @@ def download(remote_path, local_path)
def exists?(path)
@logger.debug("exists?: #{path}")
script = WinRM::FS::Scripts.render('exists', path: path)
@service.create_executor { |e| e.run_powershell_script(script)[:exitcode] == 0 }
@connection.shell(:powershell) { |e| e.run(script)[:exitcode] == 0 }
end

# Gets the current user's TEMP directory on the remote system, for example
# 'C:/Windows/Temp'
# @return [String] Full path to the temp directory
def temp_dir
@guest_temp ||= begin
(@service.create_executor { |e| e.run_cmd('echo %TEMP%') }).stdout.chomp.gsub('\\', '/')
(@connection.shell(:powershell) { |e| e.run('$env:TEMP') }).stdout.chomp.gsub('\\', '/')
end
end

Expand All @@ -107,8 +107,8 @@ def temp_dir
# @yieldparam [String] Target path on the winrm endpoint
# @return [Fixnum] The total number of bytes copied
def upload(local_path, remote_path, &block)
@service.create_executor do |executor|
file_transporter ||= WinRM::FS::Core::FileTransporter.new(executor)
@connection.shell(:powershell) do |shell|
file_transporter ||= WinRM::FS::Core::FileTransporter.new(shell)
file_transporter.upload(local_path, remote_path, &block)[0]
end
end
Expand Down
7 changes: 3 additions & 4 deletions spec/config-example.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
auth_type: plaintext
transport: plaintext
endpoint: "http://192.168.137.20:5985/wsman"
options:
user: vagrant
pass: vagrant
user: vagrant
password: vagrant
25 changes: 25 additions & 0 deletions spec/integration/benchmark_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# encoding: UTF-8
require 'pathname'

describe WinRM::FS::FileManager do
let(:dest_dir) { subject.temp_dir }
let(:this_file) { "C:/Users/matt/Downloads/[MS-WSMV].pdf" }
let(:service) { winrm_connection }

subject { WinRM::FS::FileManager.new(service) }

context 'upload file' do
let(:dest_file) { File.join(dest_dir, "test.pdf") }

before(:each) do
expect(subject.delete(dest_dir)).to be true
end

it 'should upload the specified file' do
blah = Benchmark.measure do
subject.upload(this_file, dest_file)
end
puts blah
end
end
end
11 changes: 5 additions & 6 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,34 +9,33 @@
module ConnectionHelper
# rubocop:disable AbcSize
def winrm_connection
WinRM::WinRMWebService.new(
config[:endpoint], config[:auth_type].to_sym, config[:options])
WinRM::Connection.new(config)
end
# rubocop:enable AbcSize

def config
@config ||= begin
cfg = symbolize_keys(YAML.load(File.read(winrm_config_path)))
cfg[:options].merge!(basic_auth_only: true) unless cfg[:auth_type].eql? :kerberos
cfg.merge!(basic_auth_only: true) unless cfg[:transport].eql? :kerberos
merge_environment!(cfg)
cfg
end
end

def merge_environment!(config)
merge_config_option_from_environment(config, 'user')
merge_config_option_from_environment(config, 'pass')
merge_config_option_from_environment(config, 'password')
merge_config_option_from_environment(config, 'no_ssl_peer_verification')
if ENV['use_ssl_peer_fingerprint']
config[:options][:ssl_peer_fingerprint] = ENV['winrm_cert']
end
config[:endpoint] = ENV['winrm_endpoint'] if ENV['winrm_endpoint']
config[:auth_type] = ENV['winrm_auth_type'] if ENV['winrm_auth_type']
config[:transport] = ENV['winrm_transport'] if ENV['winrm_transport']
end

def merge_config_option_from_environment(config, key)
env_key = 'winrm_' + key
config[:options][key.to_sym] = ENV[env_key] if ENV[env_key]
config[key.to_sym] = ENV[env_key] if ENV[env_key]
end

def winrm_config_path
Expand Down
2 changes: 1 addition & 1 deletion winrm-fs.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'erubis', '~> 2.7'
s.add_runtime_dependency 'logging', ['>= 1.6.1', '< 3.0']
s.add_runtime_dependency 'rubyzip', '~> 1.1'
s.add_runtime_dependency 'winrm', '~> 1.5'
s.add_runtime_dependency 'winrm'
s.add_development_dependency 'pry'
s.add_development_dependency 'rspec', '~> 3.0.0'
s.add_development_dependency 'rake', '~> 10.3.2'
Expand Down

0 comments on commit 2f8f76e

Please sign in to comment.