vagrant-windows 1.6.0 → 1.7.0.pre.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +0 -1
  3. data/CHANGELOG.md +7 -1
  4. data/Gemfile +5 -1
  5. data/README.md +3 -1
  6. data/lib/vagrant-windows/communication/command_filters/cat.rb +27 -0
  7. data/lib/vagrant-windows/communication/command_filters/chmod.rb +21 -0
  8. data/lib/vagrant-windows/communication/command_filters/chown.rb +21 -0
  9. data/lib/vagrant-windows/communication/command_filters/rm.rb +28 -0
  10. data/lib/vagrant-windows/communication/command_filters/test.rb +37 -0
  11. data/lib/vagrant-windows/communication/command_filters/uname.rb +36 -0
  12. data/lib/vagrant-windows/communication/command_filters/which.rb +26 -0
  13. data/lib/vagrant-windows/communication/guestnetwork.rb +16 -19
  14. data/lib/vagrant-windows/communication/linux_command_filter.rb +44 -0
  15. data/lib/vagrant-windows/communication/winrmcommunicator.rb +21 -24
  16. data/lib/vagrant-windows/communication/winrmfilemanager.rb +166 -0
  17. data/lib/vagrant-windows/communication/winrmshell.rb +8 -38
  18. data/lib/vagrant-windows/config/windows.rb +5 -5
  19. data/lib/vagrant-windows/errors.rb +5 -1
  20. data/lib/vagrant-windows/guest/cap/configure_networks.rb +1 -1
  21. data/lib/vagrant-windows/monkey_patches/plugins/provisioners/chef/provisioner/chef_client.rb +2 -5
  22. data/lib/vagrant-windows/monkey_patches/plugins/provisioners/chef/provisioner/chef_solo.rb +2 -5
  23. data/lib/vagrant-windows/monkey_patches/plugins/provisioners/shell/provisioner.rb +13 -6
  24. data/lib/vagrant-windows/provisioners/chef_command_builder.rb +7 -53
  25. data/lib/vagrant-windows/scripts/elevated_shell.ps1.erb +95 -0
  26. data/lib/vagrant-windows/version.rb +1 -1
  27. data/locales/en.yml +7 -0
  28. data/spec/vagrant-windows/chef_command_builder_spec.rb +7 -53
  29. data/spec/vagrant-windows/guestnetwork_spec.rb +10 -5
  30. data/spec/vagrant-windows/linux_command_filter_spec.rb +73 -0
  31. data/spec/vagrant-windows/winrmcommunicator_spec.rb +55 -6
  32. data/spec/vagrant-windows/winrmshell_spec.rb +12 -5
  33. metadata +16 -8
  34. data/lib/vagrant-windows/scripts/cheftask.ps1.erb +0 -48
  35. data/lib/vagrant-windows/scripts/cheftask.xml.erb +0 -45
  36. data/lib/vagrant-windows/scripts/cheftaskrun.ps1.erb +0 -18
  37. data/lib/vagrant-windows/scripts/command_alias.ps1 +0 -36
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6e89e485b8fae09a9d784792b184ebbc61d70185
4
- data.tar.gz: a36e78c2c59df8921a7ad3db46ac7713d02a04d7
3
+ metadata.gz: 668a6e4a7bc645fa0e951e3f04a33257e4eb5890
4
+ data.tar.gz: e080e229467c6a45f6d3ae5a180e1c8b4ed92528
5
5
  SHA512:
6
- metadata.gz: 035e50bbe58c5e2512624982e1a2797f36530ce8855c67e45dbd5ab3aea8d15390aab866b870c660a870799061cdcec33f006f610928812ee4cb747685a5901d
7
- data.tar.gz: a23744a7c72a969c0ea40a935b1a5deb8c85bafc0a4815f4729d232c7f74a7a12ea0283156b88499740b175d6085e702d69b80a66ad129e873e72c059a868c80
6
+ metadata.gz: 77c523c5c5bd2a464cb4cabc5ce613f31ec4ab7f76c8765fffdc79fffd9175b90dfd59cd19cac4cdc4c3527cf5ba7392b7933e29cbcfaed50702b998c81761e2
7
+ data.tar.gz: d2b1f5f4198cdaa1e3b88480b95e69d8af18ce4f01d55836ee7a361e488a4168e821b7b9d336857b8165dca7163b6b0e57e0c63483115d7881dc0076cec33836
data/.travis.yml CHANGED
@@ -1,5 +1,4 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 1.9.3
4
3
  - 2.0.0
5
4
  - 2.1.0
data/CHANGELOG.md CHANGED
@@ -76,4 +76,10 @@
76
76
 
77
77
  1.6.0
78
78
  - Added chef-client provisioner support and extracted shared code from chef-solo provisioner
79
- - WinRM communicator download support
79
+ - WinRM communicator download support
80
+
81
+ 1.7.0
82
+ - Added recursive directory upload on communicator, similar to core Vagrant SCP functionality.
83
+ - Redesigned elevated command execution in Chef to make it reusable
84
+ - Shell provisioner now runs elevated by default unless privileged is false
85
+ - Removed *nix command alias PS script and created extensible linux command filter system
data/Gemfile CHANGED
@@ -7,6 +7,10 @@ group :development do
7
7
  # We depend on Vagrant for development, but we don't add it as a
8
8
  # gem dependency because we expect to be installed within the
9
9
  # Vagrant environment itself using `vagrant plugin`.
10
- gem "vagrant", :git => "git://github.com/mitchellh/vagrant.git", :tag => 'v1.4.0'
10
+ gem "vagrant", :git => "git://github.com/mitchellh/vagrant.git", :tag => 'v1.5.2'
11
11
  gem "mocha", :require => false
12
12
  end
13
+
14
+ group :plugins do
15
+ gem "vagrant-windows", path: '.'
16
+ end
data/README.md CHANGED
@@ -13,7 +13,9 @@ This [Vagrant](http://www.vagrantup.com/) plugin allows you to standup Windows g
13
13
 
14
14
  ## Installation
15
15
 
16
- - For Vagrant 1.1 and above execute `vagrant plugin install vagrant-windows`.
16
+ Vagrant 1.6 will natively support Windows guests, this plugin should not be used with Vagrant 1.6 or later.
17
+
18
+ - For Vagrant 1.1 -> 1.5 execute `vagrant plugin install vagrant-windows`.
17
19
  - For Vagrant 1.0 execute `vagrant plugin install vagrant-windows --plugin-version 0.1.2`.
18
20
 
19
21
  ## Creating a Base Box
@@ -0,0 +1,27 @@
1
+ module VagrantWindows
2
+ module Communication
3
+ module CommandFilters
4
+
5
+ # Handles the special case of determining the guest OS using cat
6
+ class Cat
7
+
8
+ def filter(command)
9
+ # cat /etc/release | grep -i OmniOS
10
+ # cat /etc/redhat-release
11
+ # cat /etc/issue | grep 'Core Linux'
12
+ # cat /etc/release | grep -i SmartOS
13
+ ''
14
+ end
15
+
16
+ def accept?(command)
17
+ # cat works in PowerShell, however we don't want to run Guest
18
+ # OS detection as this will fail on Windows because the lack of the
19
+ # grep command
20
+ command.start_with?('cat /etc/')
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,21 @@
1
+ module VagrantWindows
2
+ module Communication
3
+ module CommandFilters
4
+
5
+ # Converts a *nix 'chmod' command to a PowerShell equivalent
6
+ class Chmod
7
+
8
+ def filter(command)
9
+ # Not support on Windows, the communicator will skip this command
10
+ ''
11
+ end
12
+
13
+ def accept?(command)
14
+ command.start_with?('chmod ')
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module VagrantWindows
2
+ module Communication
3
+ module CommandFilters
4
+
5
+ # Converts a *nix 'chown' command to a PowerShell equivalent
6
+ class Chown
7
+
8
+ def filter(command)
9
+ # Not support on Windows, the communicator will skip this command
10
+ ''
11
+ end
12
+
13
+ def accept?(command)
14
+ command.start_with?('chown ')
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,28 @@
1
+ module VagrantWindows
2
+ module Communication
3
+ module CommandFilters
4
+
5
+ # Converts a *nix 'rm' command to a PowerShell equivalent
6
+ class Rm
7
+
8
+ def filter(command)
9
+ # rm -Rf /some/dir
10
+ # rm /some/dir
11
+ cmd_parts = command.strip.split(/\s+/)
12
+ dir = cmd_parts[1]
13
+ if dir == '-Rf'
14
+ dir = cmd_parts[2]
15
+ return "rm '#{dir}' -recurse -force"
16
+ end
17
+ return "rm '#{dir}' -force"
18
+ end
19
+
20
+ def accept?(command)
21
+ command.start_with?('rm ')
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,37 @@
1
+ module VagrantWindows
2
+ module Communication
3
+ module CommandFilters
4
+
5
+ # Converts a *nix 'test' command to a PowerShell equivalent
6
+ class Test
7
+
8
+ def filter(command)
9
+ # test -d /tmp/dir
10
+ # test -f /tmp/afile
11
+ # test -L /somelink
12
+ # test -x /tmp/some.exe
13
+
14
+ cmd_parts = command.strip.split(/\s+/)
15
+ if cmd_parts[1] == '-d'
16
+ # ensure it exists and is a directory
17
+ return "if ((Test-Path '#{cmd_parts[2]}') -and (get-item '#{cmd_parts[2]}').PSIsContainer) { exit 0 } exit 1"
18
+ elsif cmd_parts[1] == '-f' || cmd_parts[1] == '-x'
19
+ # ensure it exists and is a file
20
+ return "if ((Test-Path '#{cmd_parts[2]}') -and (!(get-item '#{cmd_parts[2]}').PSIsContainer)) { exit 0 } exit 1"
21
+ end
22
+
23
+ # otherwise, just check for existence
24
+ return "if (Test-Path '#{cmd_parts[2]}') { exit 0 } exit 1"
25
+ end
26
+
27
+ # if (Test-Path 'c:\windows' && (get-item 'c:\windows').PSIsContainer) { Write-Host 0 } Write-Host 1
28
+
29
+ def accept?(command)
30
+ command.start_with?('test ')
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,36 @@
1
+ # cat /etc/release | grep -i OmniOS
2
+ # cat /etc/redhat-release
3
+ # cat /etc/issue | grep 'Core Linux'
4
+ # cat /etc/release | grep -i SmartOS
5
+
6
+ module VagrantWindows
7
+ module Communication
8
+ module CommandFilters
9
+
10
+ # Converts a *nix 'uname' command to a PowerShell equivalent
11
+ class Uname
12
+
13
+ def filter(command)
14
+ # uname -s | grep 'Darwin'
15
+ # uname -s | grep VMkernel
16
+ # uname -s | grep 'FreeBSD'
17
+ # uname -s | grep 'Linux'
18
+ # uname -s | grep NetBSD
19
+ # uname -s | grep 'OpenBSD'
20
+ # uname -sr | grep SunOS | grep -v 5.11
21
+ # uname -sr | grep 'SunOS 5.11'
22
+
23
+ # uname is used to detect the guest type in Vagrant, so don't bother running
24
+ # to speed up OS detection
25
+ ''
26
+ end
27
+
28
+ def accept?(command)
29
+ command.start_with?('uname ')
30
+ end
31
+
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,26 @@
1
+ module VagrantWindows
2
+ module Communication
3
+ module CommandFilters
4
+
5
+ # Converts a *nix 'which' command to a PowerShell equivalent
6
+ class Which
7
+
8
+ def filter(command)
9
+ executable = command.strip.split(/\s+/)[1]
10
+ return <<-EOH
11
+ $command = [Array](Get-Command #{executable} -errorAction SilentlyContinue)
12
+ if ($null -eq $command) { exit 1 }
13
+ write-host $command[0].Definition
14
+ exit 0
15
+ EOH
16
+ end
17
+
18
+ def accept?(command)
19
+ command.start_with?('which ')
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -12,13 +12,10 @@ module VagrantWindows
12
12
  PS_GET_WSMAN_VER = '((test-wsman).productversion.split(" ") | select -last 1).split("\.")[0]'
13
13
  WQL_NET_ADAPTERS_V2 = 'SELECT * FROM Win32_NetworkAdapter WHERE MACAddress IS NOT NULL'
14
14
 
15
- attr_reader :logger
16
- attr_reader :winrmshell
17
-
18
- def initialize(winrmshell)
19
- @logger = Log4r::Logger.new("vagrant_windows::communication::winrmshell")
15
+ def initialize(communicator)
16
+ @logger = Log4r::Logger.new("vagrant_windows::communication::guestnetwork")
20
17
  @logger.debug("initializing WinRMShell")
21
- @winrmshell = winrmshell
18
+ @communicator = communicator
22
19
  end
23
20
 
24
21
  # Returns an array of all NICs on the guest. Each array entry is a
@@ -33,13 +30,13 @@ module VagrantWindows
33
30
  #
34
31
  # @return [Boolean]
35
32
  def is_dhcp_enabled(nic_index)
36
- has_dhcp_enabled = false
37
- cmd = "Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter \"Index=#{nic_index} and DHCPEnabled=True\""
38
- @winrmshell.powershell(cmd) do |type, line|
39
- has_dhcp_enabled = !line.nil?
40
- end
41
- @logger.debug("NIC #{nic_index} has DHCP enabled: #{has_dhcp_enabled}")
42
- has_dhcp_enabled
33
+ cmd = <<-EOH
34
+ if (Get-WmiObject -Class Win32_NetworkAdapterConfiguration -Filter "Index=#{nic_index} and DHCPEnabled=True") {
35
+ exit 0
36
+ }
37
+ exit 1
38
+ EOH
39
+ @communicator.test(cmd)
43
40
  end
44
41
 
45
42
  # Configures the specified interface for DHCP
@@ -50,7 +47,7 @@ module VagrantWindows
50
47
  @logger.info("Configuring NIC #{net_connection_id} for DHCP")
51
48
  if !is_dhcp_enabled(nic_index)
52
49
  netsh = "netsh interface ip set address \"#{net_connection_id}\" dhcp"
53
- @winrmshell.powershell(netsh)
50
+ @communicator.execute(netsh)
54
51
  end
55
52
  end
56
53
 
@@ -64,7 +61,7 @@ module VagrantWindows
64
61
  @logger.info("Configuring NIC #{net_connection_id} using static ip #{ip}")
65
62
  #netsh interface ip set address "Local Area Connection 2" static 192.168.33.10 255.255.255.0
66
63
  netsh = "netsh interface ip set address \"#{net_connection_id}\" static #{ip} #{netmask}"
67
- @winrmshell.powershell(netsh)
64
+ @communicator.execute(netsh)
68
65
  end
69
66
 
70
67
  # Sets all networks on the guest to 'Work Network' mode. This is
@@ -73,7 +70,7 @@ module VagrantWindows
73
70
  def set_all_networks_to_work()
74
71
  @logger.info("Setting all networks to 'Work Network'")
75
72
  command = VagrantWindows.load_script("set_work_network.ps1")
76
- @winrmshell.powershell(command)
73
+ @communicator.execute(command)
77
74
  end
78
75
 
79
76
 
@@ -86,7 +83,7 @@ module VagrantWindows
86
83
  def wsman_version()
87
84
  @logger.debug("querying WSMan version")
88
85
  version = ''
89
- @winrmshell.powershell(PS_GET_WSMAN_VER) do |type, line|
86
+ @communicator.execute(PS_GET_WSMAN_VER) do |type, line|
90
87
  version = version + "#{line}" if type == :stdout && !line.nil?
91
88
  end
92
89
  @logger.debug("wsman version: #{version}")
@@ -102,7 +99,7 @@ module VagrantWindows
102
99
  @logger.debug("querying network adapters")
103
100
  # Get all NICs that have a MAC address
104
101
  # http://msdn.microsoft.com/en-us/library/windows/desktop/aa394216(v=vs.85).aspx
105
- adapters = @winrmshell.wql(WQL_NET_ADAPTERS_V2)[:win32_network_adapter]
102
+ adapters = @communicator.execute(WQL_NET_ADAPTERS_V2, { :shell => :wql } )[:win32_network_adapter]
106
103
  @logger.debug("#{adapters.inspect}")
107
104
  return adapters
108
105
  end
@@ -117,7 +114,7 @@ module VagrantWindows
117
114
  def network_adapters_v3_winrm()
118
115
  winrs_v3_get_adapters_ps1 = VagrantWindows.load_script("winrs_v3_get_adapters.ps1")
119
116
  output = ''
120
- @winrmshell.powershell(winrs_v3_get_adapters_ps1) do |type, line|
117
+ @communicator.execute(winrs_v3_get_adapters_ps1) do |type, line|
121
118
  output = output + "#{line}" if type == :stdout && !line.nil?
122
119
  end
123
120
  adapters = []
@@ -0,0 +1,44 @@
1
+ module VagrantWindows
2
+ module Communication
3
+
4
+ # Handles loading all available Linix command filters and applying them
5
+ # to specified command
6
+ class LinuxCommandFilter
7
+
8
+ # Filter the given Vagrant command to ensure compatibility with Windows
9
+ #
10
+ # @param [String] The Vagrant shell command
11
+ # @returns [String] Windows runnable command or empty string
12
+ def filter(command)
13
+ win_friendly_cmd = command
14
+ command_filters.each do |cmd_filter|
15
+ win_friendly_cmd = cmd_filter.filter(win_friendly_cmd) if cmd_filter.accept?(win_friendly_cmd)
16
+ end
17
+ win_friendly_cmd
18
+ end
19
+
20
+
21
+ # All the available Linux command filters
22
+ #
23
+ # @returns [Array] All Linux command filter instances
24
+ def command_filters
25
+ @command_filters ||= create_command_filters()
26
+ end
27
+
28
+ # Creates all the available Linux command filters
29
+ #
30
+ # @returns [Array] All Linux command filter instances
31
+ def create_command_filters
32
+ filters = []
33
+ Dir[File.join(File.dirname(__FILE__), '/command_filters/*.rb')].each do |file|
34
+ require file
35
+ clazz = File.basename(file, '.*').capitalize
36
+ filters << Module.const_get("VagrantWindows::Communication::CommandFilters::#{clazz}").new
37
+ end
38
+ filters
39
+ end
40
+
41
+ end
42
+
43
+ end
44
+ end
@@ -2,7 +2,9 @@ require 'timeout'
2
2
  require 'log4r'
3
3
  require_relative 'winrmshell_factory'
4
4
  require_relative 'winrmshell'
5
+ require_relative 'winrmfilemanager'
5
6
  require_relative 'winrmfinder'
7
+ require_relative 'linux_command_filter'
6
8
  require_relative '../errors'
7
9
  require_relative '../windows_machine'
8
10
 
@@ -18,7 +20,7 @@ module VagrantWindows
18
20
  def initialize(machine)
19
21
  @windows_machine = VagrantWindows::WindowsMachine.new(machine)
20
22
  @winrm_shell_factory = WinRMShellFactory.new(@windows_machine, WinRMFinder.new(@windows_machine))
21
-
23
+ @linux_cmd_filter = LinuxCommandFilter.new()
22
24
  @logger = Log4r::Logger.new("vagrant_windows::communication::winrmcommunicator")
23
25
  @logger.debug("initializing WinRMCommunicator")
24
26
  end
@@ -45,38 +47,42 @@ module VagrantWindows
45
47
  end
46
48
 
47
49
  def execute(command, opts={}, &block)
50
+ # If this is a *nix command with no Windows equivilant, don't run it
51
+ win_friendly_cmd = @linux_cmd_filter.filter(command)
52
+ return 0 if win_friendly_cmd.empty?
53
+
48
54
  opts = {
49
55
  :error_check => true,
50
56
  :error_class => VagrantWindows::Errors::WinRMExecutionError,
51
57
  :error_key => :winrm_execution_error,
52
- :command => command,
58
+ :command => win_friendly_cmd,
53
59
  :shell => :powershell
54
60
  }.merge(opts || {})
55
- exit_status = do_execute(command, opts[:shell], &block)
56
- if opts[:error_check] && exit_status != 0
57
- raise_execution_error(opts, exit_status)
58
- end
59
- exit_status
61
+
62
+ win_friendly_cmd << "\r\nexit $LASTEXITCODE" if opts[:shell] == :powershell
63
+ output = winrmshell.send(opts[:shell], win_friendly_cmd, &block)
64
+
65
+ return output if opts[:shell] == :wql
66
+ exitcode = output[:exitcode]
67
+ raise_execution_error(opts, exitcode) if opts[:error_check] && exitcode != 0
68
+ exitcode
60
69
  end
61
70
  alias_method :sudo, :execute
62
71
 
63
72
  def test(command, opts=nil)
64
- @logger.debug("Testing: #{command}")
65
-
66
- # HACK: to speed up Vagrant 1.2 OS detection, skip checking for *nix OS
67
- return false unless (command =~ /^uname|^cat \/etc|^cat \/proc|grep 'Fedora/).nil?
73
+ # If this is a *nix command with no Windows equivilant, don't run it
74
+ win_friendly_cmd = @linux_cmd_filter.filter(command)
75
+ return false if win_friendly_cmd.empty?
68
76
 
69
77
  opts = { :error_check => false }.merge(opts || {})
70
- execute(command, opts) == 0
78
+ execute(win_friendly_cmd, opts) == 0
71
79
  end
72
80
 
73
81
  def upload(from, to)
74
- @logger.debug("Uploading: #{from} to #{to}")
75
82
  winrmshell.upload(from, to)
76
83
  end
77
84
 
78
85
  def download(from, to)
79
- @logger.debug("Downloading: #{from} to #{to}")
80
86
  winrmshell.download(from, to)
81
87
  end
82
88
 
@@ -87,19 +93,10 @@ module VagrantWindows
87
93
  def winrmshell
88
94
  @winrmshell ||= @winrm_shell_factory.create_winrm_shell()
89
95
  end
90
-
96
+
91
97
 
92
98
  protected
93
99
 
94
- def do_execute(command, shell, &block)
95
- if shell.eql? :cmd
96
- winrmshell.cmd(command, &block)[:exitcode]
97
- else
98
- command = VagrantWindows.load_script("command_alias.ps1") << "\r\n" << command << "\r\nexit $LASTEXITCODE"
99
- winrmshell.powershell(command, &block)[:exitcode]
100
- end
101
- end
102
-
103
100
  def raise_execution_error(opts, exit_code)
104
101
  # The error classes expect the translation key to be _key, but that makes for an ugly
105
102
  # configuration parameter, so we set it here from `error_key`
@@ -0,0 +1,166 @@
1
+ require 'log4r'
2
+ require 'winrm'
3
+ require_relative 'winrmshell'
4
+ require_relative '../errors'
5
+
6
+ module VagrantWindows
7
+ module Communication
8
+
9
+ # Manages the file system on the remote guest allowing for file tranfer
10
+ # between the guest and host.
11
+ class WinRMFileManager
12
+
13
+ def initialize(winrm_shell)
14
+ @logger = Log4r::Logger.new("vagrant_windows::communication::winrmfilemanager")
15
+ @winrm_shell = winrm_shell
16
+ end
17
+
18
+ # Uploads the given file or directory from the host to the guest (recursively).
19
+ #
20
+ # @param [String] The source file or directory path on the host
21
+ # @param [String] The destination file or directory path on the host
22
+ def upload(host_src_file_path, guest_dest_file_path)
23
+ @logger.debug("Upload: #{host_src_file_path} -> #{guest_dest_file_path}")
24
+ if File.directory?(host_src_file_path)
25
+ upload_directory(host_src_file_path, guest_dest_file_path)
26
+ else
27
+ upload_file(host_src_file_path, guest_dest_file_path)
28
+ end
29
+ end
30
+
31
+ # Downloads the given file from the guest to the host.
32
+ # NOTE: This currently only supports single file download
33
+ #
34
+ # @param [String] The source file path on the guest
35
+ # @param [String] The destination file path on the host
36
+ def download(guest_src_file_path, host_dest_file_path)
37
+ @logger.debug("#{guest_src_file_path} -> #{host_dest_file_path}")
38
+ output = @winrm_shell.powershell("[System.convert]::ToBase64String([System.IO.File]::ReadAllBytes(\"#{guest_src_file_path}\"))")
39
+ contents = output[:data].map!{|line| line[:stdout]}.join.gsub("\\n\\r", '')
40
+ out = Base64.decode64(contents)
41
+ IO.binwrite(host_dest_file_path, out)
42
+ end
43
+
44
+
45
+ private
46
+
47
+ # Recursively uploads the given directory from the host to the guest
48
+ #
49
+ # @param [String] The source file or directory path on the host
50
+ # @param [String] The destination file or directory path on the host
51
+ def upload_directory(host_src_file_path, guest_dest_file_path)
52
+ glob_patt = File.join(host_src_file_path, '**/*')
53
+ Dir.glob(glob_patt).select { |f| !File.directory?(f) }.each do |host_file_path|
54
+ guest_file_path = guest_file_path(host_src_file_path, guest_dest_file_path, host_file_path)
55
+ upload_file(host_file_path, guest_file_path)
56
+ end
57
+ end
58
+
59
+ # Uploads the given file, but only if the target file doesn't exist
60
+ # or its MD5 checksum doens't match the host's source checksum.
61
+ #
62
+ # @param [String] The source file path on the host
63
+ # @param [String] The destination file path on the guest
64
+ def upload_file(host_src_file_path, guest_dest_file_path)
65
+ if should_upload_file?(host_src_file_path, guest_dest_file_path)
66
+ tmp_file_path = upload_to_temp_file(host_src_file_path)
67
+ decode_temp_file(tmp_file_path, guest_dest_file_path)
68
+ else
69
+ @logger.debug("Up to date: #{guest_dest_file_path}")
70
+ end
71
+ end
72
+
73
+ # Uploads the given file to a new temp file on the guest
74
+ #
75
+ # @param [String] The source file path on the host
76
+ # @return [String] The temp file path on the guest
77
+ def upload_to_temp_file(host_src_file_path)
78
+ tmp_file_path = File.join(guest_temp_dir, "winrm-upload-#{rand()}")
79
+ @logger.debug("Uploading '#{host_src_file_path}' to temp file '#{tmp_file_path}'")
80
+ base64_host_file = Base64.encode64(IO.binread(host_src_file_path)).gsub("\n",'')
81
+ base64_host_file.chars.to_a.each_slice(8000-tmp_file_path.size) do |chunk|
82
+ out = @winrm_shell.cmd("echo #{chunk.join} >> \"#{tmp_file_path}\"")
83
+ raise_upload_error_if_failed(out, host_src_file_path, tmp_file_path)
84
+ end
85
+ tmp_file_path
86
+ end
87
+
88
+ # Moves and decodes the given file temp file on the guest to its
89
+ # permanent location
90
+ #
91
+ # @param [String] The source base64 encoded temp file path on the guest
92
+ # @param [String] The destination file path on the guest
93
+ def decode_temp_file(guest_tmp_file_path, guest_dest_file_path)
94
+ @logger.debug("Decoding temp file '#{guest_tmp_file_path}' to '#{guest_dest_file_path}'")
95
+ out = @winrm_shell.powershell <<-EOH
96
+ $tmp_file_path = [System.IO.Path]::GetFullPath('#{guest_tmp_file_path}')
97
+ $dest_file_path = [System.IO.Path]::GetFullPath('#{guest_dest_file_path}')
98
+
99
+ if (Test-Path $dest_file_path) {
100
+ rm $dest_file_path
101
+ }
102
+ else {
103
+ $dest_dir = ([System.IO.Path]::GetDirectoryName($dest_file_path))
104
+ New-Item -ItemType directory -Force -Path $dest_dir
105
+ }
106
+
107
+ $base64_string = Get-Content $tmp_file_path
108
+ $bytes = [System.Convert]::FromBase64String($base64_string)
109
+ [System.IO.File]::WriteAllBytes($dest_file_path, $bytes)
110
+ EOH
111
+ raise_upload_error_if_failed(out, guest_tmp_file_path, guest_dest_file_path)
112
+ end
113
+
114
+ # Checks to see if the target file on the guest is missing or out of date.
115
+ #
116
+ # @param [String] The source file path on the host
117
+ # @param [String] The destination file path on the guest
118
+ # @return [Boolean] True if the file is missing or out of date
119
+ def should_upload_file?(host_src_file_path, guest_dest_file_path)
120
+ local_md5 = Digest::MD5.file(host_src_file_path).hexdigest
121
+ cmd = <<-EOH
122
+ $dest_file_path = [System.IO.Path]::GetFullPath('#{guest_dest_file_path}')
123
+
124
+ if (Test-Path $dest_file_path) {
125
+ $crypto_provider = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider
126
+ try {
127
+ $file = [System.IO.File]::Open($dest_file_path, [System.IO.Filemode]::Open, [System.IO.FileAccess]::Read)
128
+ $guest_md5 = ([System.BitConverter]::ToString($crypto_provider.ComputeHash($file))).Replace("-","").ToLower()
129
+ }
130
+ finally {
131
+ $file.Dispose()
132
+ }
133
+ if ($guest_md5 -eq '#{local_md5}') {
134
+ exit 0
135
+ }
136
+ }
137
+ exit 1
138
+ EOH
139
+ @winrm_shell.powershell(cmd)[:exitcode] == 1
140
+ end
141
+
142
+ # Creates a guest file path equivalent from a host file path
143
+ #
144
+ # @param [String] The base host directory we're going to copy from
145
+ # @param [String] The base guest directory we're going to copy to
146
+ # @param [String] A full path to a file on the host underneath host_base_dir
147
+ # @return [String] The guest file path equivalent
148
+ def guest_file_path(host_base_dir, guest_base_dir, host_file_path)
149
+ rel_path = File.dirname(host_file_path[host_base_dir.length, host_file_path.length])
150
+ File.join(guest_base_dir, rel_path, File.basename(host_file_path))
151
+ end
152
+
153
+ def guest_temp_dir
154
+ @guest_temp ||= (@winrm_shell.cmd('echo %TEMP%'))[:data][0][:stdout].chomp
155
+ end
156
+
157
+ def raise_upload_error_if_failed(out, from, to)
158
+ raise Errors::WinRMFileTransferError,
159
+ :from => from,
160
+ :to => to,
161
+ :message => out.inspect if out[:exitcode] != 0
162
+ end
163
+
164
+ end #class
165
+ end
166
+ end