From e6457d50617e558e9d8953ca76c597eb4469455d Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 24 May 2018 09:57:55 -0700 Subject: [PATCH 01/18] Overhaul the Hyper-V provider --- lib/vagrant/util/powershell.rb | 6 +- plugins/providers/hyperv/action.rb | 4 + plugins/providers/hyperv/action/configure.rb | 102 +++ plugins/providers/hyperv/action/import.rb | 160 +---- plugins/providers/hyperv/action/set_name.rb | 43 ++ plugins/providers/hyperv/config.rb | 89 ++- plugins/providers/hyperv/driver.rb | 221 +++++-- .../providers/hyperv/scripts/check_hyperv.ps1 | 6 +- .../providers/hyperv/scripts/clone_vhd.ps1 | 13 +- .../providers/hyperv/scripts/configure_vm.ps1 | 95 +++ .../hyperv/scripts/create_snapshot.ps1 | 15 +- .../hyperv/scripts/delete_snapshot.ps1 | 14 +- .../providers/hyperv/scripts/delete_vm.ps1 | 15 +- .../providers/hyperv/scripts/export_vm.ps1 | 28 +- .../providers/hyperv/scripts/file_sync.ps1 | 28 +- .../hyperv/scripts/get_network_config.ps1 | 94 +-- .../hyperv/scripts/get_network_mac.ps1 | 46 +- .../providers/hyperv/scripts/get_switches.ps1 | 4 +- .../hyperv/scripts/get_vm_status.ps1 | 10 +- .../hyperv/scripts/has_vmcx_support.ps1 | 4 +- .../providers/hyperv/scripts/import_vm.ps1 | 37 ++ .../hyperv/scripts/import_vm_vmcx.ps1 | 165 ----- .../hyperv/scripts/import_vm_xml.ps1 | 221 ------- .../hyperv/scripts/list_snapshots.ps1 | 21 +- .../hyperv/scripts/restore_snapshot.ps1 | 15 +- .../providers/hyperv/scripts/resume_vm.ps1 | 15 +- plugins/providers/hyperv/scripts/set_name.ps1 | 24 + .../hyperv/scripts/set_network_mac.ps1 | 28 +- .../hyperv/scripts/set_network_vlan.ps1 | 10 +- .../scripts/set_vm_integration_services.ps1 | 52 +- plugins/providers/hyperv/scripts/start_vm.ps1 | 45 +- plugins/providers/hyperv/scripts/stop_vm.ps1 | 15 +- .../providers/hyperv/scripts/suspend_vm.ps1 | 15 +- .../VagrantMessages/VagrantMessages.psm1 | 27 + .../scripts/utils/VagrantVM/VagrantVM.psm1 | 616 ++++++++++++++++++ .../hyperv/scripts/utils/write_messages.ps1 | 20 - 36 files changed, 1510 insertions(+), 813 deletions(-) create mode 100644 plugins/providers/hyperv/action/configure.rb create mode 100644 plugins/providers/hyperv/action/set_name.rb create mode 100644 plugins/providers/hyperv/scripts/configure_vm.ps1 create mode 100644 plugins/providers/hyperv/scripts/import_vm.ps1 delete mode 100644 plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 delete mode 100644 plugins/providers/hyperv/scripts/import_vm_xml.ps1 create mode 100644 plugins/providers/hyperv/scripts/set_name.ps1 create mode 100644 plugins/providers/hyperv/scripts/utils/VagrantMessages/VagrantMessages.psm1 create mode 100644 plugins/providers/hyperv/scripts/utils/VagrantVM/VagrantVM.psm1 delete mode 100644 plugins/providers/hyperv/scripts/utils/write_messages.ps1 diff --git a/lib/vagrant/util/powershell.rb b/lib/vagrant/util/powershell.rb index 4ffa0a75b..9afb4da65 100644 --- a/lib/vagrant/util/powershell.rb +++ b/lib/vagrant/util/powershell.rb @@ -47,13 +47,17 @@ module Vagrant if opts.delete(:sudo) || opts.delete(:runas) powerup_command(path, args, opts) else + env = opts.delete(:env) + if env + env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " + end command = [ executable, "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", - "&('#{path}')", + "#{env}&('#{path}')", args ].flatten diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index f5e9b25c6..a42c9966a 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -140,6 +140,8 @@ module VagrantPlugins end b2.use Provision + b2.use Configure + b2.use SetName b2.use NetSetVLan b2.use NetSetMac b2.use StartInstance @@ -288,6 +290,7 @@ module VagrantPlugins autoload :Export, action_root.join("export") autoload :CheckEnabled, action_root.join("check_enabled") + autoload :Configure, action_root.join("configure") autoload :DeleteVM, action_root.join("delete_vm") autoload :Import, action_root.join("import") autoload :Package, action_root.join("package") @@ -304,6 +307,7 @@ module VagrantPlugins autoload :SnapshotDelete, action_root.join("snapshot_delete") autoload :SnapshotRestore, action_root.join("snapshot_restore") autoload :SnapshotSave, action_root.join("snapshot_save") + autoload :SetName, action_root.join("set_name") end end end diff --git a/plugins/providers/hyperv/action/configure.rb b/plugins/providers/hyperv/action/configure.rb new file mode 100644 index 000000000..f8d1e7d74 --- /dev/null +++ b/plugins/providers/hyperv/action/configure.rb @@ -0,0 +1,102 @@ +require "fileutils" + +require "log4r" + +module VagrantPlugins + module HyperV + module Action + class Configure + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::configure") + end + + def call(env) + switches = env[:machine].provider.driver.execute(:get_switches) + if switches.empty? + raise Errors::NoSwitches + end + + switch = nil + env[:machine].config.vm.networks.each do |type, opts| + next if type != :public_network && type != :private_network + + if opts[:bridge] + @logger.debug("Looking for switch with name or ID: #{opts[:bridge]}") + switch = switches.find{ |s| + s["Name"].downcase == opts[:bridge] || + s["Id"].downcase == opts[:bridge] + } + if switch + @logger.debug("Found switch - Name: #{switch["Name"]} ID: #{switch["Id"]}") + switch = switch["Id"] + break + end + end + end + + # If we already configured previously don't prompt for switch + sentinel = env[:machine].data_dir.join("action_configure") + + if !switch && !sentinel.file? + if switches.length > 1 + env[:ui].detail(I18n.t("vagrant_hyperv.choose_switch") + "\n ") + switches.each_index do |i| + switch = switches[i] + env[:ui].detail("#{i+1}) #{switch["Name"]}") + end + env[:ui].detail(" ") + + switch = nil + while !switch + switch = env[:ui].ask("What switch would you like to use? ") + next if !switch + switch = switch.to_i - 1 + switch = nil if switch < 0 || switch >= switches.length + end + switch = switches[switch]["Id"] + else + switch = switches.first["Id"] + @logger.debug("Only single switch available so using that.") + end + end + + options = { + "VMID" => env[:machine].id, + "SwitchID" => switch, + "Memory" => env[:machine].provider_config.memory, + "MaxMemory" => env[:machine].provider_config.maxmemory, + "Processors" => env[:machine].provider_config.cpus, + "AutoStartAction" => env[:machine].provider_config.auto_start_action, + "AutoStopAction" => env[:machine].provider_config.auto_stop_action, + "EnableCheckpoints" => env[:machine].provider_config.enable_checkpoints, + "VirtualizationExtensions" => !!env[:machine].provider_config.enable_virtualization_extensions, + } + options.delete_if{|_,v| v.nil? } + + env[:ui].detail("Configuring the VM...") + env[:machine].provider.driver.execute(:configure_vm, options) + + # Create the sentinel + sentinel.open("w") do |f| + f.write(Time.now.to_i.to_s) + end + + if env[:machine].provider_config.vm_integration_services + env[:ui].detail("Setting VM Integration Services") + + env[:machine].provider_config.vm_integration_services.each do |key, value| + state = value ? "enabled" : "disabled" + env[:ui].output("#{key} is #{state}") + end + + env[:machine].provider.driver.set_vm_integration_services( + env[:machine].provider_config.vm_integration_services) + end + + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index 434d991ad..bbe19e0c3 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -1,11 +1,13 @@ require "fileutils" - require "log4r" module VagrantPlugins module HyperV module Action class Import + + VALID_HD_EXTENSIONS = [".vhd".freeze, ".vhdx".freeze].freeze + def initialize(app, env) @app = app @logger = Log4r::Logger.new("vagrant::hyperv::import") @@ -14,160 +16,64 @@ module VagrantPlugins def call(env) vm_dir = env[:machine].box.directory.join("Virtual Machines") hd_dir = env[:machine].box.directory.join("Virtual Hard Disks") - memory = env[:machine].provider_config.memory - maxmemory = env[:machine].provider_config.maxmemory - cpus = env[:machine].provider_config.cpus - vmname = env[:machine].provider_config.vmname - differencing_disk = env[:machine].provider_config.differencing_disk - auto_start_action = env[:machine].provider_config.auto_start_action - auto_stop_action = env[:machine].provider_config.auto_stop_action - enable_virtualization_extensions = env[:machine].provider_config.enable_virtualization_extensions - vm_integration_services = env[:machine].provider_config.vm_integration_services - - env[:ui].output("Configured Dynamic memory allocation, maxmemory is #{maxmemory}") if maxmemory - env[:ui].output("Configured startup memory is #{memory}") if memory - env[:ui].output("Configured cpus number is #{cpus}") if cpus - env[:ui].output("Configured enable virtualization extensions is #{enable_virtualization_extensions}") if enable_virtualization_extensions - env[:ui].output("Configured vmname is #{vmname}") if vmname - env[:ui].output("Configured differencing disk instead of cloning") if differencing_disk - env[:ui].output("Configured automatic start action is #{auto_start_action}") if auto_start_action - env[:ui].output("Configured automatic stop action is #{auto_stop_action}") if auto_stop_action if !vm_dir.directory? || !hd_dir.directory? + @logger.error("Required virtual machine directory not found!") raise Errors::BoxInvalid end + valid_config_ext = [".xml"] + if env[:machine].provider.driver.has_vmcx_support? + valid_config_ext << ".vmcx" + end + config_path = nil - config_type = nil - vm_dir.each_child do |f| - if f.extname.downcase == '.xml' - @logger.debug("Found XML config...") - config_path = f - config_type = 'xml' + vm_dir.each_child do |file| + if valid_config_ext.include?(file.extname.downcase) + config_path = file break end end - vmcx_support = env[:machine].provider.driver.execute("has_vmcx_support.ps1", {})['result'] - if vmcx_support - vm_dir.each_child do |f| - if f.extname.downcase == '.vmcx' - @logger.debug("Found VMCX config and support...") - config_path = f - config_type = 'vmcx' - break - end - end + if !config_path + @logger.error("Failed to locate box configuration path") + raise Errors::BoxInvalid + else + @logger.info("Found box configuration path: #{config_path}") end image_path = nil - image_ext = nil - image_filename = nil - hd_dir.each_child do |f| - if %w{.vhd .vhdx}.include?(f.extname.downcase) - image_path = f - image_ext = f.extname.downcase - image_filename = File.basename(f, image_ext) + hd_dir.each_child do |file| + if VALID_HD_EXTENSIONS.include?(file.extname.downcase) + image_path = file break end end - if !config_path || !image_path + if !image_path + @logger.error("Failed to locate box image path") raise Errors::BoxInvalid + else + @logger.info("Found box image path: #{image_path}") end env[:ui].output("Importing a Hyper-V instance") + dest_path = env[:machine].data_dir.join("Virtual Hard Disks").join(image_path.basename).to_s - switches = env[:machine].provider.driver.execute("get_switches.ps1", {}) - raise Errors::NoSwitches if switches.empty? - - switch = nil - env[:machine].config.vm.networks.each do |type, opts| - next if type != :public_network && type != :private_network - - switchToFind = opts[:bridge] - - if switchToFind - @logger.debug("Looking for switch with name: #{switchToFind}") - switch = switches.find { |s| s["Name"].downcase == switchToFind.downcase }["Id"] - @logger.debug("Found switch: #{switch}") - end - end - - if switch.nil? - if switches.length > 1 - env[:ui].detail(I18n.t("vagrant_hyperv.choose_switch") + "\n ") - switches.each_index do |i| - switch = switches[i] - env[:ui].detail("#{i+1}) #{switch["Name"]}") - end - env[:ui].detail(" ") - - switch = nil - while !switch - switch = env[:ui].ask("What switch would you like to use? ") - next if !switch - switch = switch.to_i - 1 - switch = nil if switch < 0 || switch >= switches.length - end - switch = switches[switch]["Id"] - else - switch = switches[0]["Id"] - end - end - - env[:ui].detail("Cloning virtual hard drive...") - source_path = image_path.to_s - dest_path = env[:machine].data_dir.join("Virtual Hard Disks").join("#{image_filename}#{image_ext}").to_s - - # Still hard copy the disk of old XML configurations - if config_type == 'xml' - if differencing_disk - env[:machine].provider.driver.execute("clone_vhd.ps1", {Source: source_path, Destination: dest_path}) - else - FileUtils.mkdir_p(env[:machine].data_dir.join("Virtual Hard Disks")) - FileUtils.cp(source_path, dest_path) - end - end - image_path = dest_path - - # We have to normalize the paths to be Windows paths since - # we're executing PowerShell. options = { - vm_config_file: config_path.to_s.gsub("/", "\\"), - vm_config_type: config_type, - source_path: source_path.to_s, - dest_path: dest_path, - data_path: env[:machine].data_dir.to_s.gsub("/", "\\") + "VMConfigFile" => config_path.to_s.gsub("/", "\\"), + "DestinationPath" => dest_path.to_s.gsub("/", "\\"), + "DataPath" => env[:machine].data_dir.to_s.gsub("/", "\\"), + "LinkedClone" => !!env[:machine].provider_config.linked_clone, + "SourcePath" => image_path.to_s.gsub("/", "\\"), + "VMName" => env[:machine].provider_config.vmname, } - options[:switchid] = switch if switch - options[:memory] = memory if memory - options[:maxmemory] = maxmemory if maxmemory - options[:cpus] = cpus if cpus - options[:vmname] = vmname if vmname - options[:auto_start_action] = auto_start_action if auto_start_action - options[:auto_stop_action] = auto_stop_action if auto_stop_action - options[:differencing_disk] = differencing_disk if differencing_disk - options[:enable_virtualization_extensions] = "True" if enable_virtualization_extensions and enable_virtualization_extensions == true + env[:ui].detail("Creating and registering the VM...") server = env[:machine].provider.driver.import(options) - env[:ui].detail("Setting VM Integration Services") - vm_integration_services.each do |key, value| - state = false - if value === true - state = "enabled" - elsif value === false - state = "disabled" - end - env[:ui].output("#{key} is #{state}") if state - end - - vm_integration_services[:VmId] = server["id"] - env[:machine].provider.driver.set_vm_integration_services(vm_integration_services) - - env[:ui].detail("Successfully imported a VM with name: #{server['name']}") + env[:ui].detail("Successfully imported VM") env[:machine].id = server["id"] @app.call(env) end diff --git a/plugins/providers/hyperv/action/set_name.rb b/plugins/providers/hyperv/action/set_name.rb new file mode 100644 index 000000000..91a82695a --- /dev/null +++ b/plugins/providers/hyperv/action/set_name.rb @@ -0,0 +1,43 @@ +require "log4r" + +module VagrantPlugins + module HyperV + module Action + class SetName + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::set_name") + end + + def call(env) + name = env[:machine].provider_config.vmname + + # If we already set the name before, then don't do anything + sentinel = env[:machine].data_dir.join("action_set_name") + if !name && sentinel.file? + @logger.info("Default name was already set before, not doing it again.") + return @app.call(env) + end + + # If no name was manually set, then use a default + if !name + prefix = "#{env[:root_path].basename.to_s}_#{env[:machine].name}" + prefix.gsub!(/[^-a-z0-9_]/i, "") + + # milliseconds + random number suffix to allow for simultaneous + # `vagrant up` of the same box in different dirs + name = prefix + "_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}" + end + + env[:machine].provider.driver.set_name(name) + + # Create the sentinel + sentinel.open("w") do |f| + f.write(Time.now.to_i.to_s) + end + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 9a4296701..684cf36f1 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -3,18 +3,49 @@ require "vagrant" module VagrantPlugins module HyperV class Config < Vagrant.plugin("2", :config) - attr_accessor :ip_address_timeout # Time to wait for an IP address when booting, in seconds @return [Integer] - attr_accessor :memory # Memory size in mb @return [Integer] - attr_accessor :maxmemory # Maximal memory size in mb enables dynamical memory allocation @return [Integer] - attr_accessor :cpus # Number of cpu's @return [Integer] - attr_accessor :vmname # Name that will be shoen in Hyperv Manager @return [String] - attr_accessor :vlan_id # VLAN ID for network interface for the virtual machine. @return [Integer] - attr_accessor :mac # MAC address for network interface for the virtual machine. @return [String] - attr_accessor :differencing_disk # Create differencing disk instead of cloning whole VHD [Boolean] - attr_accessor :auto_start_action #action on automatic start of VM. Values: Nothing, StartIfRunning, Start - attr_accessor :auto_stop_action #action on automatic stop of VM. Values: ShutDown, TurnOff, Save - attr_accessor :enable_virtualization_extensions # Enable virtualization extensions (nested virtualization). Values: true, false - attr_accessor :vm_integration_services # Options for VMServiceIntegration [Hash] + # Allowed automatic start actions for VM + ALLOWED_AUTO_START_ACTIONS = [ + "Nothing".freeze, + "StartIfRunning".freeze, + "Start".freeze + ].freeze + + # Allowed automatic stop actions for VM + ALLOWED_AUTO_STOP_ACTIONS = [ + "ShutDown".freeze, + "TurnOff".freeze, + "Save".freeze + ].freeze + + # @return [Integer] Seconds to wait for an IP address when booting + attr_accessor :ip_address_timeout + # @return [Integer] Memory size in MB + attr_accessor :memory + # @return [Integer] Maximum memory size in MB. Enables dynamic memory. + attr_accessor :maxmemory + # @return [Integer] Number of CPUs + attr_accessor :cpus + # @return [String] Name of the VM (Shown in the Hyper-V Manager) + attr_accessor :vmname + # @return [Integer] VLAN ID for network interface + attr_accessor :vlan_id + # @return [String] MAC address for network interface + attr_accessor :mac + # @return [Boolean] Create linked clone instead of full clone + # @note **DEPRECATED** use #linked_clone instead + attr_accessor :differencing_disk + # @return [Boolean] Create linked clone instead of full clone + attr_accessor :linked_clone + # @return [String] Automatic action on start of host. Default: Nothing (Nothing, StartIfRunning, Start) + attr_accessor :auto_start_action + # @return [String] Automatic action on stop of host. Default: ShutDown (ShutDown, TurnOff, Save) + attr_accessor :auto_stop_action + # @return [Boolean] Enable automatic checkpoints. Default: false + attr_accessor :enable_checkpoints + # @return [Boolean] Enable virtualization extensions + attr_accessor :enable_virtualization_extensions + # @return [Hash] Options for VMServiceIntegration + attr_accessor :vm_integration_services def initialize @ip_address_timeout = UNSET_VALUE @@ -24,10 +55,12 @@ module VagrantPlugins @vmname = UNSET_VALUE @vlan_id = UNSET_VALUE @mac = UNSET_VALUE + @linked_clone = UNSET_VALUE @differencing_disk = UNSET_VALUE @auto_start_action = UNSET_VALUE @auto_stop_action = UNSET_VALUE @enable_virtualization_extensions = UNSET_VALUE + @enable_checkpoints = UNSET_VALUE @vm_integration_services = { guest_service_interface: UNSET_VALUE, heartbeat: UNSET_VALUE, @@ -39,6 +72,10 @@ module VagrantPlugins end def finalize! + @linked_clone = false if @linked_clone == UNSET_VALUE + @differencing_disk = false if @differencing_disk == UNSET_VALUE + @linked_clone ||= @differencing_disk + @differencing_disk ||= @linked_clone if @ip_address_timeout == UNSET_VALUE @ip_address_timeout = 120 end @@ -48,20 +85,32 @@ module VagrantPlugins @vmname = nil if @vmname == UNSET_VALUE @vlan_id = nil if @vlan_id == UNSET_VALUE @mac = nil if @mac == UNSET_VALUE - @differencing_disk = false if @differencing_disk == UNSET_VALUE - @auto_start_action = nil if @auto_start_action == UNSET_VALUE - @auto_stop_action = nil if @auto_stop_action == UNSET_VALUE - @enable_virtualization_extensions = false if @enable_virtualization_extensions == UNSET_VALUE # TODO will this work? - @vm_integration_services.each { |key, value| - @vm_integration_services[key] = nil if value == UNSET_VALUE - } - @vm_integration_services = nil if @vm_integration_services.length == 0 + @auto_start_action = "Nothing" if @auto_start_action == UNSET_VALUE + @auto_stop_action = "ShutDown" if @auto_stop_action == UNSET_VALUE + @enable_virtualization_extensions = false if @enable_virtualization_extensions == UNSET_VALUE + if @enable_checkpoints == UNSET_VALUE + @enable_checkpoints = false + else + @enable_checkpoints = !!@enable_checkpoints + end + @vm_integration_services.delete_if{|_, v| v == UNSET_VALUE } + @vm_integration_services = nil if @vm_integration_services.empty? end def validate(machine) errors = _detected_errors + if !ALLOWED_AUTO_START_ACTIONS.include?(auto_start_action) + errors << I18n.t("vagrant.hyperv.config.invalid_auto_start_action", action: auto_start_action, + allowed_actions: ALLOWED_AUTO_START_ACTIONS.join(", ")) + end + + if !ALLOWED_AUTO_STOP_ACTIONS.include?(auto_stop_action) + errors << I18n.t("vagrant.hyperv.config.invalid_auto_stop_action", action: auto_stop_action, + allowed_actions: ALLOWED_AUTO_STOP_ACTIONS.join(", ")) + end + {"Hyper-V" => errors} end end diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 359e14b12..83c43f3ce 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -10,19 +10,41 @@ module VagrantPlugins ERROR_REGEXP = /===Begin-Error===(.+?)===End-Error===/m OUTPUT_REGEXP = /===Begin-Output===(.+?)===End-Output===/m + # Name mapping for integration services for nicer keys + INTEGRATION_SERVICES_MAP = { + guest_service_interface: "Guest Service Interface", + heartbeat: "Heartbeat", + key_value_pair_exchange: "Key-Value Pair Exchange", + shutdown: "Shutdown", + time_synchronization: "Time Synchronization", + vss: "VSS", + } + + # @return [String] VM ID attr_reader :vm_id def initialize(id) @vm_id = id end - def execute(path, options) - r = execute_powershell(path, options) - if r.exit_code != 0 - raise Errors::PowerShellError, - script: path, - stderr: r.stderr + # @return [Boolean] Supports VMCX + def has_vmcx_support? + !!execute(:has_vmcx_support)["result"] + end + + # Execute a PowerShell command and process the results + # + # @param [String] path Path to PowerShell script + # @param [Hash] options Options to pass to command + # + # @return [Object, nil] If the command returned JSON content + # it will be parsed and returned, otherwise + # nil will be returned + def execute(path, options={}) + if path.is_a?(Symbol) + path = "#{path}.ps1" end + r = execute_powershell(path, options) # We only want unix-style line endings within Vagrant r.stdout.gsub!("\r\n", "\n") @@ -40,104 +62,185 @@ module VagrantPlugins stderr: data["error"] end + if r.exit_code != 0 + raise Errors::PowerShellError, + script: path, + stderr: r.stderr + end + # Nothing return nil if !output_match return JSON.parse(output_match[1]) end + # Fetch current state of the VM + # + # @return [Hash] def get_current_state - execute('get_vm_status.ps1', { VmId: vm_id }) + execute(:get_vm_status, { VmId: vm_id }) end - def delete_vm - execute('delete_vm.ps1', { VmId: vm_id }) - end + # Delete the VM + # + # @return [nil] + def delete_vm + execute(:delete_vm, { VmId: vm_id }) + end + # Export the VM to the given path + # + # @param [String] path Path for export + # @return [nil] def export(path) - execute('export_vm.ps1', {VmId: vm_id, Path: path}) + execute(:export_vm, {VmId: vm_id, Path: path}) end - def read_guest_ip - execute('get_network_config.ps1', { VmId: vm_id }) - end + # Get the IP address of the VM + # + # @return [Hash] + def read_guest_ip + execute(:get_network_config, { VmId: vm_id }) + end + # Get the MAC address of the VM + # + # @return [Hash] def read_mac_address - execute('get_network_mac.ps1', { VmId: vm_id }) + execute(:get_network_mac, { VmId: vm_id }) end - def resume - execute('resume_vm.ps1', { VmId: vm_id }) - end + # Resume the VM from suspension + # + # @return [nil] + def resume + execute(:resume_vm, { VmId: vm_id }) + end - def start - execute('start_vm.ps1', { VmId: vm_id }) - end + # Start the VM + # + # @return [nil] + def start + execute(:start_vm, { VmId: vm_id }) + end - def stop - execute('stop_vm.ps1', { VmId: vm_id }) - end + # Stop the VM + # + # @return [nil] + def stop + execute(:stop_vm, { VmId: vm_id }) + end - def suspend - execute("suspend_vm.ps1", { VmId: vm_id }) - end + # Suspend the VM + # + # @return [nil] + def suspend + execute(:suspend_vm, { VmId: vm_id }) + end - def import(options) - config_type = options.delete(:vm_config_type) - if config_type === "vmcx" - execute('import_vm_vmcx.ps1', options) - else - options.delete(:data_path) - options.delete(:source_path) - options.delete(:differencing_disk) - execute('import_vm_xml.ps1', options) - end - end + # Import a new VM + # + # @param [Hash] options Configuration options + # @return [Hash] New VM ID + def import(options) + execute(:import_vm, options) + end - def net_set_vlan(vlan_id) - execute("set_network_vlan.ps1", { VmId: vm_id, VlanId: vlan_id }) - end + # Set the VLAN ID + # + # @param [String] vlan_id VLAN ID + # @return [nil] + def net_set_vlan(vlan_id) + execute(:set_network_vlan, { VmId: vm_id, VlanId: vlan_id }) + end - def net_set_mac(mac_addr) - execute("set_network_mac.ps1", { VmId: vm_id, Mac: mac_addr }) - end + # Set the VM adapter MAC address + # + # @param [String] mac_addr MAC address + # @return [nil] + def net_set_mac(mac_addr) + execute(:set_network_mac, { VmId: vm_id, Mac: mac_addr }) + end - def create_snapshot(snapshot_name) - execute("create_snapshot.ps1", { VmId: vm_id, SnapName: (snapshot_name) } ) - end + # Create a new snapshot with the given name + # + # @param [String] snapshot_name Name of the new snapshot + # @return [nil] + def create_snapshot(snapshot_name) + execute(:create_snapshot, { VmId: vm_id, SnapName: (snapshot_name) } ) + end - def restore_snapshot(snapshot_name) - execute("restore_snapshot.ps1", { VmId: vm_id, SnapName: (snapshot_name) } ) - end + # Restore the given snapshot + # + # @param [String] snapshot_name Name of snapshot to restore + # @return [nil] + def restore_snapshot(snapshot_name) + execute(:restore_snapshot, { VmId: vm_id, SnapName: (snapshot_name) } ) + end - def list_snapshots() - snaps = execute("list_snapshots.ps1", { VmID: vm_id } ) - snaps.map { |s| s['Name'] } - end + # Get list of current snapshots + # + # @return [Array] snapshot names + def list_snapshots + snaps = execute(:list_snapshots, { VmID: vm_id } ) + snaps.map { |s| s['Name'] } + end - def delete_snapshot(snapshot_name) - execute("delete_snapshot.ps1", {VmID: vm_id, SnapName: snapshot_name}) - end + # Delete snapshot with the given name + # + # @param [String] snapshot_name Name of snapshot to delete + # @return [nil] + def delete_snapshot(snapshot_name) + execute(:delete_snapshot, {VmID: vm_id, SnapName: snapshot_name}) + end + # Enable or disable VM integration services + # + # @param [Hash] config Integration services to enable or disable + # @return [nil] + # @note Keys in the config hash will be remapped if found in the + # INTEGRATION_SERVICES_MAP. If they are not, the name will + # be passed directly. This allows new integration services + # to configurable even if Vagrant is not aware of them. def set_vm_integration_services(config) - execute("set_vm_integration_services.ps1", config) + config.each_pair do |srv_name, srv_enable| + args = {VMID: vm_id, Name: INTEGRATION_SERVICES_MAP.fetch(srv_name.to_sym, srv_name)} + args[:Name] = true if srv_enable + execute(:set_vm_integration_services, args) + end + end + + # Set the name of the VM + # + # @param [String] vmname Name of the VM + # @return [nil] + def set_name(vmname) + execute(:set_name, VMID: vm_id, VMName: vmname) end protected def execute_powershell(path, options, &block) lib_path = Pathname.new(File.expand_path("../scripts", __FILE__)) + mod_path = lib_path.join("utils").to_s.gsub("/", "\\") path = lib_path.join(path).to_s.gsub("/", "\\") options = options || {} ps_options = [] options.each do |key, value| + next if value == false ps_options << "-#{key}" + # If the value is a TrueClass assume switch + next if value == true ps_options << "'#{value}'" end # Always have a stop error action for failures ps_options << "-ErrorAction" << "Stop" - opts = { notify: [:stdout, :stderr, :stdin] } + # Include our module path so we can nicely load helper modules + opts = { + notify: [:stdout, :stderr, :stdin], + env: {"PSModulePath" => "$env:PSModulePath+';#{mod_path}'"} + } Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block) end end diff --git a/plugins/providers/hyperv/scripts/check_hyperv.ps1 b/plugins/providers/hyperv/scripts/check_hyperv.ps1 index d9992b1e9..1d1775314 100644 --- a/plugins/providers/hyperv/scripts/check_hyperv.ps1 +++ b/plugins/providers/hyperv/scripts/check_hyperv.ps1 @@ -1,8 +1,6 @@ -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +#Requires -Modules VagrantMessages -$check = $(-Not (-Not (Get-Command "Hyper-V\Get-VMSwitch" -errorAction SilentlyContinue))) +$check = $(-Not (-Not (Get-Command "Hyper-V\Get-VMSwitch" -ErrorAction SilentlyContinue))) $result = @{ result = $check } diff --git a/plugins/providers/hyperv/scripts/clone_vhd.ps1 b/plugins/providers/hyperv/scripts/clone_vhd.ps1 index fc83a2c06..829366635 100644 --- a/plugins/providers/hyperv/scripts/clone_vhd.ps1 +++ b/plugins/providers/hyperv/scripts/clone_vhd.ps1 @@ -1,4 +1,6 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$Source, @@ -6,4 +8,11 @@ Param( [string]$Destination ) -Hyper-V\New-VHD -Path $Destination -ParentPath $Source -ErrorAction Stop +$ErrorActionPreference = "Stop" + +try { + Hyper-V\New-VHD -Path $Destination -ParentPath $Source +} catch { + Write-Error-Message "Failed to clone drive: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/configure_vm.ps1 b/plugins/providers/hyperv/scripts/configure_vm.ps1 new file mode 100644 index 000000000..908bb1f24 --- /dev/null +++ b/plugins/providers/hyperv/scripts/configure_vm.ps1 @@ -0,0 +1,95 @@ +#Requires -Modules VagrantVM, VagrantMessages + +param( + [parameter (Mandatory=$true)] + [Guid] $VMID, + [parameter (Mandatory=$false)] + [string] $SwitchID=$null, + [parameter (Mandatory=$false)] + [string] $Memory=$null, + [parameter (Mandatory=$false)] + [string] $MaxMemory=$null, + [parameter (Mandatory=$false)] + [string] $Processors=$null, + [parameter (Mandatory=$false)] + [string] $AutoStartAction=$null, + [parameter (Mandatory=$false)] + [string] $AutoStopAction=$null, + [parameter (Mandatory=$false)] + [switch] $VirtualizationExtensions, + [parameter (Mandatory=$false)] + [switch] $EnableCheckpoints +) + +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VMID +} catch { + Write-Error-Message "Failed to locate VM: ${PSItem}" + exit 1 +} + +if($Processors) { + try { + Set-VagrantVMCPUS -VM $VM -CPUCount ($Processors -as [int]) + } catch { + Write-Error-Message "Failed to configure CPUs: ${PSItem}" + exit 1 + } +} + +if($Memory -or $MaxMemory) { + try { + Set-VagrantVMMemory -VM $VM -Memory $Memory -MaxMemory $MaxMemory + } catch { + Write-Error-Message "Failed to configure memory: ${PSItem}" + exit 1 + } +} + +if($AutoStartAction -or $AutoStopAction) { + try { + Set-VagrantVMAutoActions -VM $VM -AutoStartAction $AutoStartAction -AutoStopAction $AutoStopAction + } catch { + Write-Error-Message "Failed to configure automatic actions: ${PSItem}" + exit 1 + } +} + +if($VirtualizationExtensions) { + $virtex = $true +} else { + $virtex = $false +} + +try { + Set-VagrantVMVirtExtensions -VM $VM -Enabled $virtex +} catch { + Write-Error-Message "Failed to configure virtualization extensions: ${PSItem}" + exit 1 +} + +if($SwitchID) { + try { + $SwitchName = Get-VagrantVMSwitch -NameOrID $SwitchID + Set-VagrantVMSwitch -VM $VM -SwitchName $SwitchName + } catch { + Write-Error-Message "Failed to configure network adapter: ${PSItem}" + } +} + +if($EnableCheckpoints) { + $checkpoints = "Standard" + $CheckpointAction = "enable" +} else { + $checkpoints = "Disabled" + $CheckpointAction = "disable" +} + +try { + Hyper-V\Set-VM -VM $VM -CheckpointType $checkpoints +} catch { + Write-Error-Message "Failed to ${CheckpointAction} checkpoints on VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/create_snapshot.ps1 b/plugins/providers/hyperv/scripts/create_snapshot.ps1 index 080c47d7a..775f91898 100644 --- a/plugins/providers/hyperv/scripts/create_snapshot.ps1 +++ b/plugins/providers/hyperv/scripts/create_snapshot.ps1 @@ -1,8 +1,17 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId, [string]$SnapName ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Checkpoint-VM $VM -SnapshotName $SnapName +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Checkpoint-VM $VM -SnapshotName $SnapName +} catch { + Write-Error-Message "Failed to create snapshot: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/delete_snapshot.ps1 b/plugins/providers/hyperv/scripts/delete_snapshot.ps1 index 54c2c33b7..bba44b588 100644 --- a/plugins/providers/hyperv/scripts/delete_snapshot.ps1 +++ b/plugins/providers/hyperv/scripts/delete_snapshot.ps1 @@ -1,8 +1,16 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId, [string]$SnapName ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Remove-VMSnapshot $VM -Name $SnapName +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Remove-VMSnapshot $VM -Name $SnapName +} catch { + Write-Error-Message "Failed to delete snapshot: ${PSItem}" +} diff --git a/plugins/providers/hyperv/scripts/delete_vm.ps1 b/plugins/providers/hyperv/scripts/delete_vm.ps1 index ce83cb6a7..53e160b16 100644 --- a/plugins/providers/hyperv/scripts/delete_vm.ps1 +++ b/plugins/providers/hyperv/scripts/delete_vm.ps1 @@ -1,7 +1,16 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Remove-VM $VM -Force +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Remove-VM $VM -Force +} catch { + Write-Error-Message "Failed to delete VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/export_vm.ps1 b/plugins/providers/hyperv/scripts/export_vm.ps1 index 207275981..263c12be2 100644 --- a/plugins/providers/hyperv/scripts/export_vm.ps1 +++ b/plugins/providers/hyperv/scripts/export_vm.ps1 @@ -1,15 +1,29 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId, [Parameter(Mandatory=$true)] [string]$Path ) -$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -$vm | Hyper-V\Export-VM -Path $Path +$ErrorActionPreference = "Stop" + +try { + $vm = Hyper-V\Get-VM -Id $VmId + $vm | Hyper-V\Export-VM -Path $Path +} catch { + Write-Error-Message "Failed to export VM: ${PSItem}" + exit 1 +} # Prepare directory structure for box import -$name = $vm.Name -Move-Item $Path/$name/* $Path -Remove-Item -Path $Path/Snapshots -Force -Recurse -Remove-Item -Path $Path/$name -Force \ No newline at end of file +try { + $name = $vm.Name + Move-Item $Path/$name/* $Path + Remove-Item -Path $Path/Snapshots -Force -Recurse + Remove-Item -Path $Path/$name -Force +} catch { + Write-Error-Message "Failed to format exported box: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/file_sync.ps1 b/plugins/providers/hyperv/scripts/file_sync.ps1 index bca5e41d6..ffe340407 100644 --- a/plugins/providers/hyperv/scripts/file_sync.ps1 +++ b/plugins/providers/hyperv/scripts/file_sync.ps1 @@ -1,22 +1,23 @@ +#Requires -Modules VagrantMessages #------------------------------------------------------------------------- # Copyright (c) Microsoft Open Technologies, Inc. # All Rights Reserved. Licensed under the MIT License. #-------------------------------------------------------------------------- param ( - [string]$vm_id = $(throw "-vm_id is required."), - [string]$guest_ip = $(throw "-guest_ip is required."), - [string]$username = $(throw "-guest_username is required."), - [string]$password = $(throw "-guest_password is required."), - [string]$host_path = $(throw "-host_path is required."), - [string]$guest_path = $(throw "-guest_path is required.") - ) - -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } + [parameter (Mandatory=$true)] + [string]$vm_id, + [parameter (Mandatory=$true)] + [string]$guest_ip, + [parameter (Mandatory=$true)] + [string]$username, + [parameter (Mandatory=$true)] + [string]$password, + [parameter (Mandatory=$true)] + [string]$host_path, + [parameter (Mandatory=$true)] + [string]$guest_path +) function Get-file-hash($source_path, $delimiter) { $source_files = @() @@ -120,4 +121,3 @@ $resultHash = @{ } $result = ConvertTo-Json $resultHash Write-Output-Message $result - diff --git a/plugins/providers/hyperv/scripts/get_network_config.ps1 b/plugins/providers/hyperv/scripts/get_network_config.ps1 index 5f9186a2c..959501add 100644 --- a/plugins/providers/hyperv/scripts/get_network_config.ps1 +++ b/plugins/providers/hyperv/scripts/get_network_config.ps1 @@ -1,56 +1,68 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +$ErrorActionPreference = "Stop" -$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -$networks = Hyper-V\Get-VMNetworkAdapter -VM $vm -foreach ($network in $networks) { - if ($network.IpAddresses.Length -gt 0) { - foreach ($ip_address in $network.IpAddresses) { - if ($ip_address.Contains(".") -And [string]::IsNullOrEmpty($ip4_address)) { - $ip4_address = $ip_address - } elseif ($ip_address.Contains(":") -And [string]::IsNullOrEmpty($ip6_address)) { - $ip6_address = $ip_address +try { + $vm = Hyper-V\Get-VM -Id $VmId +} catch { + Write-Error-Message "Failed to locate VM: ${PSItem}" + exit 1 +} + +try { + $networks = Hyper-V\Get-VMNetworkAdapter -VM $vm + foreach ($network in $networks) { + if ($network.IpAddresses.Length -gt 0) { + foreach ($ip_address in $network.IpAddresses) { + if ($ip_address.Contains(".") -And [string]::IsNullOrEmpty($ip4_address)) { + $ip4_address = $ip_address + } elseif ($ip_address.Contains(":") -And [string]::IsNullOrEmpty($ip6_address)) { + $ip6_address = $ip_address + } } } } -} -# If no address was found in the network settings, check for -# neighbor with mac address and see if an IP exists -if (([string]::IsNullOrEmpty($ip4_address)) -And ([string]::IsNullOrEmpty($ip6_address))) { - $macaddresses = $vm | select -ExpandProperty NetworkAdapters | select MacAddress - foreach ($macaddr in $macaddresses) { - $macaddress = $macaddr.MacAddress -replace '(.{2})(?!$)', '${1}-' - $addr = Get-NetNeighbor -LinkLayerAddress $macaddress -ErrorAction SilentlyContinue | select IPAddress - if ($ip_address) { - $ip_address = $addr.IPAddress - if ($ip_address.Contains(".")) { - $ip4_address = $ip_address - } elseif ($ip_address.Contains(":")) { - $ip6_address = $ip_address + # If no address was found in the network settings, check for + # neighbor with mac address and see if an IP exists + if (([string]::IsNullOrEmpty($ip4_address)) -And ([string]::IsNullOrEmpty($ip6_address))) { + $macaddresses = $vm | select -ExpandProperty NetworkAdapters | select MacAddress + foreach ($macaddr in $macaddresses) { + $macaddress = $macaddr.MacAddress -replace '(.{2})(?!$)', '${1}-' + $addr = Get-NetNeighbor -LinkLayerAddress $macaddress -ErrorAction SilentlyContinue | select IPAddress + if ($ip_address) { + $ip_address = $addr.IPAddress + if ($ip_address.Contains(".")) { + $ip4_address = $ip_address + } elseif ($ip_address.Contains(":")) { + $ip6_address = $ip_address + } } } } -} -if (-Not ([string]::IsNullOrEmpty($ip4_address))) { - $guest_ipaddress = $ip4_address -} elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) { - $guest_ipaddress = $ip6_address -} - -if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) { - $resultHash = @{ - ip = $guest_ipaddress + if (-Not ([string]::IsNullOrEmpty($ip4_address))) { + $guest_ipaddress = $ip4_address + } elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) { + $guest_ipaddress = $ip6_address } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result -} else { - Write-Error-Message "Failed to determine IP address" + + if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) { + $resultHash = @{ + ip = $guest_ipaddress + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result + } else { + Write-Error-Message "Failed to determine IP address" + exit 1 + } +} catch { + Write-Error-Message "Unexpected error while detecting network configuration: ${PSItem}" + exit 1 } diff --git a/plugins/providers/hyperv/scripts/get_network_mac.ps1 b/plugins/providers/hyperv/scripts/get_network_mac.ps1 index 9424917df..88d4c0306 100644 --- a/plugins/providers/hyperv/scripts/get_network_mac.ps1 +++ b/plugins/providers/hyperv/scripts/get_network_mac.ps1 @@ -1,28 +1,32 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId - ) +) -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +$ErrorActionPreference = "Stop" -$ip_address = "" -$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -$networks = Hyper-V\Get-VMNetworkAdapter -VM $vm -foreach ($network in $networks) { - if ($network.MacAddress -gt 0) { - $mac_address = $network.MacAddress - if (-Not ([string]::IsNullOrEmpty($mac_address))) { - # We found our mac address! - break +try { + $ip_address = "" + $vm = Hyper-V\Get-VM -Id $VmId + $networks = Hyper-V\Get-VMNetworkAdapter -VM $vm + foreach ($network in $networks) { + if ($network.MacAddress -gt 0) { + $mac_address = $network.MacAddress + if (-Not ([string]::IsNullOrEmpty($mac_address))) { + # We found our mac address! + break + } + } } - } -} - -$resultHash = @{ - mac = "$mac_address" + $resultHash = @{ + mac = "$mac_address" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} catch { + Write-Error-Message "Unexpected error while fetching MAC: ${PSItem}" + exit 1 } -$result = ConvertTo-Json $resultHash -Write-Output-Message $result \ No newline at end of file diff --git a/plugins/providers/hyperv/scripts/get_switches.ps1 b/plugins/providers/hyperv/scripts/get_switches.ps1 index 85c0f12fe..83632aaa8 100644 --- a/plugins/providers/hyperv/scripts/get_switches.ps1 +++ b/plugins/providers/hyperv/scripts/get_switches.ps1 @@ -1,11 +1,9 @@ +#Requires -Modules VagrantMessages # This will have a SwitchType property. As far as I know the values are: # # 0 - Private # 1 - Internal # -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) $Switches = @(Hyper-V\Get-VMSwitch ` | Select-Object Name,SwitchType,NetAdapterInterfaceDescription,Id) diff --git a/plugins/providers/hyperv/scripts/get_vm_status.ps1 b/plugins/providers/hyperv/scripts/get_vm_status.ps1 index 43c8595c9..90c05cbcc 100644 --- a/plugins/providers/hyperv/scripts/get_vm_status.ps1 +++ b/plugins/providers/hyperv/scripts/get_vm_status.ps1 @@ -1,12 +1,10 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) - # Make sure the exception type is loaded try { @@ -37,7 +35,7 @@ try { $State = "not_created" $Status = $State } - else + else { throw; } diff --git a/plugins/providers/hyperv/scripts/has_vmcx_support.ps1 b/plugins/providers/hyperv/scripts/has_vmcx_support.ps1 index 79dac42d7..bf7d89cda 100644 --- a/plugins/providers/hyperv/scripts/has_vmcx_support.ps1 +++ b/plugins/providers/hyperv/scripts/has_vmcx_support.ps1 @@ -1,6 +1,4 @@ -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +#Requires -Modules VagrantMessages # Windows version 10 and up have support for binary format $check = [System.Environment]::OSVersion.Version.Major -ge 10 diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 new file mode 100644 index 000000000..06fc65622 --- /dev/null +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -0,0 +1,37 @@ +#Requires -Modules VagrantVM, VagrantMessages + +param( + [parameter (Mandatory=$true)] + [string] $VMConfigFile, + [parameter (Mandatory=$true)] + [string] $DestinationPath, + [parameter (Mandatory=$true)] + [string] $DataPath, + [parameter (Mandatory=$true)] + [string] $SourcePath, + [parameter (Mandatory=$false)] + [switch] $LinkedClone, + [parameter (Mandatory=$false)] + [string] $VMName=$null +) + +$ErrorActionPreference = "Stop" + +try { + if($LinkedClone) { + $linked = $true + } else { + $linked = $false + } + + $VM = New-VagrantVM -VMConfigFile $VMConfigFile -DestinationPath $DestinationPath ` + -DataPath $DataPath -SourcePath $SourcePath -LinkedClone $linked -VMName $VMName + + $Result = @{ + id = $VM.Id.Guid; + } + Write-Output-Message (ConvertTo-Json $Result) +} catch { + Write-Error-Message "${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 b/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 deleted file mode 100644 index 682cabfbd..000000000 --- a/plugins/providers/hyperv/scripts/import_vm_vmcx.ps1 +++ /dev/null @@ -1,165 +0,0 @@ -Param( - [Parameter(Mandatory=$true)] - [string]$vm_config_file, - [Parameter(Mandatory=$true)] - [string]$source_path, - [Parameter(Mandatory=$true)] - [string]$dest_path, - [Parameter(Mandatory=$true)] - [string]$data_path, - - [string]$switchid=$null, - [string]$memory=$null, - [string]$maxmemory=$null, - [string]$cpus=$null, - [string]$vmname=$null, - [string]$auto_start_action=$null, - [string]$auto_stop_action=$null, - [string]$differencing_disk=$null, - [string]$enable_virtualization_extensions=$False -) - -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) - -$VmProperties = @{ - Path = $vm_config_file - SnapshotFilePath = Join-Path $data_path 'Snapshots' - VhdDestinationPath = Join-Path $data_path 'Virtual Hard Disks' - VirtualMachinePath = $data_path -} - -$vmConfig = (Hyper-V\Compare-VM -Copy -GenerateNewID @VmProperties) - -$generation = $vmConfig.VM.Generation - -if (!$vmname) { - # Get the name of the vm - $vm_name = $vmconfig.VM.VMName -} else { - $vm_name = $vmname -} - -if (!$cpus) { - # Get the processorcount of the VM - $processors = (Hyper-V\Get-VMProcessor -VM $vmConfig.VM).Count -}else { - $processors = $cpus -} - -function GetUniqueName($name) { - Hyper-V\Get-VM | ForEach-Object -Process { - if ($name -eq $_.Name) { - $name = $name + "_1" - } - } - return $name -} - -do { - $name = $vm_name - $vm_name = GetUniqueName $name -} while ($vm_name -ne $name) - -if (!$memory) { - $configMemory = Hyper-V\Get-VMMemory -VM $vmConfig.VM - $dynamicmemory = $configMemory.DynamicMemoryEnabled - - $MemoryMaximumBytes = ($configMemory.Maximum) - $MemoryStartupBytes = ($configMemory.Startup) - $MemoryMinimumBytes = ($configMemory.Minimum) -} else { - if (!$maxmemory){ - $dynamicmemory = $False - $MemoryMaximumBytes = ($memory -as [int]) * 1MB - $MemoryStartupBytes = ($memory -as [int]) * 1MB - $MemoryMinimumBytes = ($memory -as [int]) * 1MB - } else { - $dynamicmemory = $True - $MemoryMaximumBytes = ($maxmemory -as [int]) * 1MB - $MemoryStartupBytes = ($memory -as [int]) * 1MB - $MemoryMinimumBytes = ($memory -as [int]) * 1MB - } -} - -if (!$switchid) { - $switchname = (Hyper-V\Get-VMNetworkAdapter -VM $vmConfig.VM).SwitchName -} else { - $switchname = $(Hyper-V\Get-VMSwitch -Id $switchid).Name -} - -# Enable nested virtualization if configured -if ($enable_virtualization_extensions -eq "True") { - Hyper-V\Set-VMProcessor -VM $vmConfig.VM -ExposeVirtualizationExtensions $true -} - -$vmNetworkAdapter = Hyper-V\Get-VMNetworkAdapter -VM $vmConfig.VM -Hyper-V\Connect-VMNetworkAdapter -VMNetworkAdapter $vmNetworkAdapter -SwitchName $switchname -Hyper-V\Set-VM -VM $vmConfig.VM -NewVMName $vm_name -Hyper-V\Set-VM -VM $vmConfig.VM -ErrorAction "Stop" -Hyper-V\Set-VM -VM $vmConfig.VM -ProcessorCount $processors - -if ($dynamicmemory) { - Hyper-V\Set-VM -VM $vmConfig.VM -DynamicMemory - Hyper-V\Set-VM -VM $vmConfig.VM -MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes $MemoryMaximumBytes -MemoryStartupBytes $MemoryStartupBytes -} else { - Hyper-V\Set-VM -VM $vmConfig.VM -StaticMemory - Hyper-V\Set-VM -VM $vmConfig.VM -MemoryStartupBytes $MemoryStartupBytes -} - -if ($notes) { - Hyper-V\Set-VM -VM $vmConfig.VM -Notes $notes -} - -if ($auto_start_action) { - Hyper-V\Set-VM -VM $vmConfig.VM -AutomaticStartAction $auto_start_action -} - -if ($auto_stop_action) { - Hyper-V\Set-VM -VM $vmConfig.VM -AutomaticStopAction $auto_stop_action -} - -# Only set EFI secure boot for Gen 2 machines, not gen 1 -if ($generation -ne 1) { - Hyper-V\Set-VMFirmware -VM $vmConfig.VM -EnableSecureBoot (Hyper-V\Get-VMFirmware -VM $vmConfig.VM).SecureBoot -} - -$report = Hyper-V\Compare-VM -CompatibilityReport $vmConfig - -# Stop if there are incompatibilities -if($report.Incompatibilities.Length -gt 0){ - Write-Error-Message $(ConvertTo-Json $($report.Incompatibilities | Select -ExpandProperty Message)) - exit 0 -} - -if($differencing_disk){ - # Get all controller on the VM, first scsi, then IDE if it is a Gen 1 device - $controllers = Hyper-V\Get-VMScsiController -VM $vmConfig.VM - if($generation -eq 1){ - $controllers = @($controllers) + @(Hyper-V\Get-VMIdeController -VM $vmConfig.VM) - } - - foreach($controller in $controllers){ - foreach($drive in $controller.Drives){ - if([System.IO.Path]::GetFileName($drive.Path) -eq [System.IO.Path]::GetFileName($source_path)){ - # Remove the old disk and replace it with a differencing version - $path = $drive.Path - Hyper-V\Remove-VMHardDiskDrive $drive - Hyper-V\New-VHD -Path $dest_path -ParentPath $source_path -ErrorAction Stop - Hyper-V\Add-VMHardDiskDrive -VM $vmConfig.VM -Path $dest_path - } - } - } -} - -Hyper-V\Import-VM -CompatibilityReport $vmConfig - -$vm_id = (Hyper-V\Get-VM $vm_name).id.guid -$resultHash = @{ - name = $vm_name - id = $vm_id -} - -$result = ConvertTo-Json $resultHash -Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/import_vm_xml.ps1 b/plugins/providers/hyperv/scripts/import_vm_xml.ps1 deleted file mode 100644 index 655380c0e..000000000 --- a/plugins/providers/hyperv/scripts/import_vm_xml.ps1 +++ /dev/null @@ -1,221 +0,0 @@ -Param( - [Parameter(Mandatory=$true)] - [string]$vm_config_file, - [Parameter(Mandatory=$true)] - [string]$dest_path, - - [string]$switchname=$null, - [string]$memory=$null, - [string]$maxmemory=$null, - [string]$cpus=$null, - [string]$vmname=$null, - [string]$auto_start_action=$null, - [string]$auto_stop_action=$null, - [string]$enable_virtualization_extensions=$False -) - -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) - -[xml]$vmconfig = Get-Content -Path $vm_config_file - -$generation = [int]($vmconfig.configuration.properties.subtype.'#text')+1 - -if (!$vmname) { - # Get the name of the vm - $vm_name = $vmconfig.configuration.properties.name.'#text' -}else { - $vm_name = $vmname -} - -if (!$cpus) { - # Get the name of the vm - $processors = $vmconfig.configuration.settings.processors.count.'#text' -}else { - $processors = $cpus -} - -function GetUniqueName($name) { - Hyper-V\Get-VM | ForEach-Object -Process { - if ($name -eq $_.Name) { - $name = $name + "_1" - } - } - return $name -} - -do { - $name = $vm_name - $vm_name = GetUniqueName $name -} while ($vm_name -ne $name) - -if (!$memory) { - $xmlmemory = (Select-Xml -xml $vmconfig -XPath "//memory").node.Bank - if ($xmlmemory.dynamic_memory_enabled."#text" -eq "True") { - $dynamicmemory = $True - } - else { - $dynamicmemory = $False - } - # Memory values need to be in bytes - $MemoryMaximumBytes = ($xmlmemory.limit."#text" -as [int]) * 1MB - $MemoryStartupBytes = ($xmlmemory.size."#text" -as [int]) * 1MB - $MemoryMinimumBytes = ($xmlmemory.reservation."#text" -as [int]) * 1MB -} -else { - if (!$maxmemory){ - $dynamicmemory = $False - $MemoryMaximumBytes = ($memory -as [int]) * 1MB - $MemoryStartupBytes = ($memory -as [int]) * 1MB - $MemoryMinimumBytes = ($memory -as [int]) * 1MB - } - else { - $dynamicmemory = $True - $MemoryMaximumBytes = ($maxmemory -as [int]) * 1MB - $MemoryStartupBytes = ($memory -as [int]) * 1MB - $MemoryMinimumBytes = ($memory -as [int]) * 1MB - } -} - - -if (!$switchname) { - # Get the name of the virtual switch - $switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" -} - -if ($generation -eq 1) { - # Determine boot device - Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { - "Floppy" { $bootdevice = "Floppy" } - "HardDrive" { $bootdevice = "IDE" } - "Optical" { $bootdevice = "CD" } - "Network" { $bootdevice = "LegacyNetworkAdapter" } - "Default" { $bootdevice = "IDE" } - } #switch -} else { - # Determine boot device - Switch ((Select-Xml -xml $vmconfig -XPath "//boot").node.device0."#text") { - "HardDrive" { $bootdevice = "VHD" } - "Optical" { $bootdevice = "CD" } - "Network" { $bootdevice = "NetworkAdapter" } - "Default" { $bootdevice = "VHD" } - } #switch -} - -# Determine secure boot options -$secure_boot_enabled = (Select-Xml -xml $vmconfig -XPath "//secure_boot_enabled").Node."#text" - -# Define a hash map of parameter values for New-VM - -$vm_params = @{ - Name = $vm_name - NoVHD = $True - MemoryStartupBytes = $MemoryStartupBytes - SwitchName = $switchname - BootDevice = $bootdevice - ErrorAction = "Stop" -} - -# Generation parameter was added in ps v4 -if((get-command Hyper-V\New-VM).Parameters.Keys.Contains("generation")) { - $vm_params.Generation = $generation -} - -# Create the VM using the values in the hash map - -$vm = Hyper-V\New-VM @vm_params - -$notes = (Select-Xml -xml $vmconfig -XPath "//notes").node.'#text' - -# Set-VM parameters to configure new VM with old values - -$more_vm_params = @{ - ProcessorCount = $processors - MemoryStartupBytes = $MemoryStartupBytes -} - -If ($dynamicmemory) { - $more_vm_params.Add("DynamicMemory",$True) - $more_vm_params.Add("MemoryMinimumBytes",$MemoryMinimumBytes) - $more_vm_params.Add("MemoryMaximumBytes", $MemoryMaximumBytes) -} else { - $more_vm_params.Add("StaticMemory",$True) -} - -if ($notes) { - $more_vm_params.Add("Notes",$notes) -} - -if ($auto_start_action) { - $more_vm_params.Add("AutomaticStartAction",$auto_start_action) -} - -if ($auto_stop_action) { - $more_vm_params.Add("AutomaticStopAction",$auto_stop_action) -} - -# Set the values on the VM -$vm | Hyper-V\Set-VM @more_vm_params -Passthru - -# Add drives to the virtual machine -$controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]" - -# Only set EFI secure boot for Gen 2 machines, not gen 1 -if ($generation -ne 1) { - # Set EFI secure boot - if ($secure_boot_enabled -eq "True") { - Hyper-V\Set-VMFirmware -VM $vm -EnableSecureBoot On - } else { - Hyper-V\Set-VMFirmware -VM $vm -EnableSecureBoot Off - } -} - -# Enable nested virtualization if configured -if ($enable_virtualization_extensions -eq "True") { - Hyper-V\Set-VMProcessor -VM $vm -ExposeVirtualizationExtensions $true -} - -# A regular expression pattern to pull the number from controllers -[regex]$rx="\d" - -foreach ($controller in $controllers) { - $node = $controller.Node - - # Check for SCSI - if ($node.ParentNode.ChannelInstanceGuid) { - $ControllerType = "SCSI" - } else { - $ControllerType = "IDE" - } - - $drives = $node.ChildNodes | where {$_.pathname."#text"} - foreach ($drive in $drives) { - #if drive type is ISO then set DVD Drive accordingly - $driveType = $drive.type."#text" - - $addDriveParam = @{ - ControllerNumber = $rx.Match($controller.node.name).value - Path = $dest_path - } - - if ($drive.pool_id."#text") { - $ResourcePoolName = $drive.pool_id."#text" - $addDriveParam.Add("ResourcePoolname",$ResourcePoolName) - } - - if ($drivetype -eq 'VHD') { - $addDriveParam.add("ControllerType",$ControllerType) - $vm | Hyper-V\Add-VMHardDiskDrive @AddDriveparam - } - } -} - -$vm_id = (Hyper-V\Get-VM $vm_name).id.guid -$resultHash = @{ - name = $vm_name - id = $vm_id -} - -$result = ConvertTo-Json $resultHash -Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/list_snapshots.ps1 b/plugins/providers/hyperv/scripts/list_snapshots.ps1 index d5bd05811..7e4b85583 100644 --- a/plugins/providers/hyperv/scripts/list_snapshots.ps1 +++ b/plugins/providers/hyperv/scripts/list_snapshots.ps1 @@ -1,12 +1,19 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -$Snapshots = @(Hyper-V\Get-VMSnapshot $VM | Select-Object Name) -$result = ConvertTo-json $Snapshots +$ErrorActionPreference = "Stop" -Write-Host "===Begin-Output===" -Write-Host $result -Write-Host "===End-Output===" +try { + $VM = Hyper-V\Get-VM -Id $VmId + $Snapshots = @(Hyper-V\Get-VMSnapshot $VM | Select-Object Name) +} catch { + Write-Error-Message "Failed to get snapshot list: ${PSItem}" + exit 1 +} + +$result = ConvertTo-json $Snapshots +Write-Output-Message $result diff --git a/plugins/providers/hyperv/scripts/restore_snapshot.ps1 b/plugins/providers/hyperv/scripts/restore_snapshot.ps1 index 406804394..192e56075 100644 --- a/plugins/providers/hyperv/scripts/restore_snapshot.ps1 +++ b/plugins/providers/hyperv/scripts/restore_snapshot.ps1 @@ -1,8 +1,17 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId, [string]$SnapName ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Restore-VMSnapshot $VM -Name $SnapName -Confirm:$false +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Restore-VMSnapshot $VM -Name $SnapName -Confirm:$false +} catch { + Write-Error-Message "Failed to restore snapshot: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/resume_vm.ps1 b/plugins/providers/hyperv/scripts/resume_vm.ps1 index 8cbca00d4..f30e05b73 100644 --- a/plugins/providers/hyperv/scripts/resume_vm.ps1 +++ b/plugins/providers/hyperv/scripts/resume_vm.ps1 @@ -1,7 +1,16 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Resume-VM $VM +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Resume-VM $VM +} catch { + Write-Error-Message "Failed to resume VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/set_name.ps1 b/plugins/providers/hyperv/scripts/set_name.ps1 new file mode 100644 index 000000000..ac97fad48 --- /dev/null +++ b/plugins/providers/hyperv/scripts/set_name.ps1 @@ -0,0 +1,24 @@ +#Requires -Modules VagrantMessages + +param ( + [parameter (Mandatory=$true)] + [Guid] $VMID, + [parameter (Mandatory=$true)] + [string] $VMName +) + +$ErrorActionPreference = "Stop" + +try { + $VM = Hyper-V\Get-VM -Id $VMID +} catch { + Write-Error-Message "Failed to locate VM: ${PSItem}" + exit 1 +} + +try { + Hyper-V\Set-VM -VM $VM -NewVMName $VMName +} catch { + Write-Error-Message "Failed to assign new VM name ${VMName}: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/set_network_mac.ps1 b/plugins/providers/hyperv/scripts/set_network_mac.ps1 index 4ef157f38..3942eb621 100644 --- a/plugins/providers/hyperv/scripts/set_network_mac.ps1 +++ b/plugins/providers/hyperv/scripts/set_network_mac.ps1 @@ -1,18 +1,18 @@ -param ( - [string]$VmId = $(throw "-VmId is required."), - [string]$Mac = $(throw "-Mac ") - ) +#Requires -Modules VagrantMessages -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } +param ( + [parameter (Mandatory=$true)] + [string]$VmId, + [parameter (Mandatory=$true)] + [string]$Mac +) + +$ErrorActionPreference = "Stop" try { - $vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" - Hyper-V\Set-VMNetworkAdapter $vm -StaticMacAddress $Mac -ErrorAction "stop" -} -catch { - Write-Error-Message "Failed to set VM's MAC address $_" + $vm = Hyper-V\Get-VM -Id $VmId + Hyper-V\Set-VMNetworkAdapter $vm -StaticMacAddress $Mac +} catch { + Write-Error-Message "Failed to set VM MAC address: ${PSItem}" + exit 1 } diff --git a/plugins/providers/hyperv/scripts/set_network_vlan.ps1 b/plugins/providers/hyperv/scripts/set_network_vlan.ps1 index b385dc9c5..26dc8941d 100644 --- a/plugins/providers/hyperv/scripts/set_network_vlan.ps1 +++ b/plugins/providers/hyperv/scripts/set_network_vlan.ps1 @@ -1,7 +1,11 @@ +#Requires -Modules VagrantMessages + param ( - [string]$VmId = $(throw "-VmId is required."), - [int]$VlanId = $(throw "-VlanId ") - ) + [parameter (Mandatory=$true)] + [string]$VmId, + [parameter (Mandatory=$true)] + [int]$VlanId +) # Include the following modules $presentDir = Split-Path -parent $PSCommandPath diff --git a/plugins/providers/hyperv/scripts/set_vm_integration_services.ps1 b/plugins/providers/hyperv/scripts/set_vm_integration_services.ps1 index fd0d30063..a28d1229b 100644 --- a/plugins/providers/hyperv/scripts/set_vm_integration_services.ps1 +++ b/plugins/providers/hyperv/scripts/set_vm_integration_services.ps1 @@ -1,37 +1,27 @@ +#Requires -Modules VagrantVM, VagrantMessages + param ( - [string] $VmId, - [string] $guest_service_interface = $null, - [string] $heartbeat = $null, - [string] $key_value_pair_exchange = $null, - [string] $shutdown = $null, - [string] $time_synchronization = $null, - [string] $vss = $null + [parameter (Mandatory=$true)] + [string] $VMID, + [parameter (Mandatory=$true)] + [string] $Name, + [parameter (Mandatory=$false)] + [switch] $Enable ) -# Include the following modules -$Dir = Split-Path $script:MyInvocation.MyCommand.Path -. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1")) +$ErrorActionPreference = "Stop" -$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" - -# Set the service based on value -function VmSetService -{ - param ([string] $Name, [string] $Value, [Microsoft.HyperV.PowerShell.VirtualMachine] $Vm) - - if ($Value -ne $null){ - if($Value -eq "true"){ - Hyper-V\Enable-VMIntegrationService -VM $Vm -Name $Name - } - if($Value -eq "false"){ - Hyper-V\Disable-VMIntegrationService -VM $Vm -Name $Name - } - } +try { + $VM = Hyper-V\Get-VM -Id $VMID +} catch { + Write-Error-Message "Failed to locate VM: ${PSItem}" + exit 1 } -VmSetService -Name "Guest Service Interface" -Value $guest_service_interface -Vm $vm -VmSetService -Name "Heartbeat" -Value $heartbeat -Vm $vm -VmSetService -Name "Key-Value Pair Exchange" -Value $key_value_pair_exchange -Vm $vm -VmSetService -Name "Shutdown" -Value $shutdown -Vm $vm -VmSetService -Name "Time Synchronization" -Value $time_synchronization -Vm $vm -VmSetService -Name "VSS" -Value $vss -Vm $vm \ No newline at end of file +try { + Set-VagrantVMService -VM $VM -Name $Name -Enable $enabled +} catch { + if($enabled){ $action = "enable" } else { $action = "disable" } + Write-Error-Message "Failed to ${action} VM integration service ${Name}: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/start_vm.ps1 b/plugins/providers/hyperv/scripts/start_vm.ps1 index bfa67bc92..8380e1c8f 100644 --- a/plugins/providers/hyperv/scripts/start_vm.ps1 +++ b/plugins/providers/hyperv/scripts/start_vm.ps1 @@ -1,27 +1,26 @@ -param ( - [string]$VmId = $(throw "-VmId is required.") - ) +#Requires -Modules VagrantMessages -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\write_messages.ps1" -forEach ($module in $modules) { . $module } +param ( + [parameter (Mandatory=$true)] + [string]$VmId +) + +$ErrorActionPreference = "Stop" try { - $vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" - Hyper-V\Start-VM $vm -ErrorAction "stop" - $state = $vm.state - $status = $vm.status - $name = $vm.name - $resultHash = @{ - state = "$state" - status = "$status" - name = "$name" - } - $result = ConvertTo-Json $resultHash - Write-Output-Message $result + $vm = Hyper-V\Get-VM -Id $VmId + Hyper-V\Start-VM $vm + $state = $vm.state + $status = $vm.status + $name = $vm.name + $resultHash = @{ + state = "$state" + status = "$status" + name = "$name" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} catch { + Write-Error-Message "Failed to start VM ${PSItem}" + exit 1 } -catch { - Write-Error-Message "Failed to start a VM $_" -} \ No newline at end of file diff --git a/plugins/providers/hyperv/scripts/stop_vm.ps1 b/plugins/providers/hyperv/scripts/stop_vm.ps1 index 524a45fba..5cf88e636 100644 --- a/plugins/providers/hyperv/scripts/stop_vm.ps1 +++ b/plugins/providers/hyperv/scripts/stop_vm.ps1 @@ -1,8 +1,17 @@ +#Requires -Modules VagrantMessages + Param( [Parameter(Mandatory=$true)] [string]$VmId ) -# Shuts down virtual machine regardless of any unsaved application data -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Stop-VM $VM -Force +$ErrorActionPreference = "Stop" + +try{ + # Shuts down virtual machine regardless of any unsaved application data + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Stop-VM $VM -Force +} catch { + Write-Error-Message "Failed to stop VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/suspend_vm.ps1 b/plugins/providers/hyperv/scripts/suspend_vm.ps1 index 2f1d69da8..43993ac15 100644 --- a/plugins/providers/hyperv/scripts/suspend_vm.ps1 +++ b/plugins/providers/hyperv/scripts/suspend_vm.ps1 @@ -1,7 +1,16 @@ -Param( +#Requires -Modules VagrantMessages + +param( [Parameter(Mandatory=$true)] [string]$VmId ) -$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" -Hyper-V\Suspend-VM $VM +$ErrorActionPreference = "Stop" + +try{ + $VM = Hyper-V\Get-VM -Id $VmId + Hyper-V\Suspend-VM $VM +} catch { + Write-Error-Message "Failed to suspend VM: ${PSItem}" + exit 1 +} diff --git a/plugins/providers/hyperv/scripts/utils/VagrantMessages/VagrantMessages.psm1 b/plugins/providers/hyperv/scripts/utils/VagrantMessages/VagrantMessages.psm1 new file mode 100644 index 000000000..2f934b6c0 --- /dev/null +++ b/plugins/providers/hyperv/scripts/utils/VagrantMessages/VagrantMessages.psm1 @@ -0,0 +1,27 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +function Write-Error-Message { + param ( + [parameter (Mandatory=$true,Position=0)] + [string] $Message + ) + $error_message = @{ + error = $Message + } + Write-Host "===Begin-Error===" + Write-Host (ConvertTo-Json $error_message) + Write-Host "===End-Error===" +} + +function Write-Output-Message { + param ( + [parameter (Mandatory=$true,Position=0)] + [string] $Message + ) + Write-Host "===Begin-Output===" + Write-Host $Message + Write-Host "===End-Output===" +} diff --git a/plugins/providers/hyperv/scripts/utils/VagrantVM/VagrantVM.psm1 b/plugins/providers/hyperv/scripts/utils/VagrantVM/VagrantVM.psm1 new file mode 100644 index 000000000..885d3c6b1 --- /dev/null +++ b/plugins/providers/hyperv/scripts/utils/VagrantVM/VagrantVM.psm1 @@ -0,0 +1,616 @@ +# Vagrant VM creation functions + +function New-VagrantVM { + param ( + [parameter(Mandatory=$true)] + [string] $VMConfigFile, + [parameter(Mandatory=$true)] + [string] $DestinationPath, + [parameter (Mandatory=$true)] + [string] $DataPath, + [parameter (Mandatory=$true)] + [string] $SourcePath, + [parameter (Mandatory=$false)] + [bool] $LinkedClone = $false, + [parameter(Mandatory=$false)] + [string] $VMName + ) + if([IO.Path]::GetExtension($VMConfigFile).ToLower() -eq ".xml") { + return New-VagrantVMXML @PSBoundParameters + } else { + return New-VagrantVMVMCX @PSBoundParameters + } +<# +.SYNOPSIS + +Create a new Vagrant Hyper-V VM by cloning original. This +is the general use function with will call the specialized +function based on the extension of the configuration file. + +.DESCRIPTION + +Using an existing Hyper-V VM a new Hyper-V VM is created +by cloning the original. + +.PARAMETER VMConfigFile +Path to the original Hyper-V VM configuration file. + +.PARAMETER DestinationPath +Path to new Hyper-V VM hard drive. + +.PARAMETER DataPath +Directory path of the original Hyper-V VM to be cloned. + +.PARAMETER SourcePath +Path to the original Hyper-V VM hard drive. + +.PARAMETER LinkedClone +New Hyper-V VM should be linked clone instead of complete copy. + +.PARAMETER VMName +Name of the new Hyper-V VM. + +.INPUTS + +None. + +.OUTPUTS + +VirtualMachine. The cloned Hyper-V VM. +#> +} + +function New-VagrantVMVMCX { + param ( + [parameter(Mandatory=$true)] + [string] $VMConfigFile, + [parameter(Mandatory=$true)] + [string] $DestinationPath, + [parameter (Mandatory=$true)] + [string] $DataPath, + [parameter (Mandatory=$true)] + [string] $SourcePath, + [parameter (Mandatory=$false)] + [bool] $LinkedClone = $false, + [parameter(Mandatory=$false)] + [string] $VMName + ) + + $NewVMConfig = @{ + Path = $VMConfigFile; + SnapshotFilePath = Join-Path $DataPath "Snapshots"; + VhdDestinationPath = Join-Path $DataPath "Virtual Hard Disks"; + VirtualMachinePath = $DataPath; + } + $VMConfig = (Hyper-V\Compare-VM -Copy -GenerateNewID @NewVMConfig) + $VM = $VMConfig.VM + $Gen = $VM.Generation + + # Set VM name if name has been provided + if($VMName) { + Hyper-V\Set-VM -VM $VM -NewVMName $VMName + } + + # Set EFI secure boot on machines after Gen 1 + if($Gen -gt 1) { + Hyper-V\Set-VMFirmware -VM $VM -EnableSecureBoot (Hyper-V\Get-VMFirmware -VM $VM).SecureBoot + } + + # Verify new VM + $Report = Hyper-V\Compare-VM -CompatibilityReport $VMConfig + if($Report.Incompatibilities.Length -gt 0){ + throw $(ConvertTo-Json $($Report.Incompatibilities | Select -ExpandProperty Message)) + } + + if($LinkedClone) { + $Controllers = Hyper-V\Get-VMScsiController -VM $VM + if($Gen -eq 1){ + $Controllers = @($Controllers) + @(Hyper-V\Get-VMIdeController -VM $VM) + } + foreach($Controller in $Controllers) { + foreach($Drive in $Controller.Drives) { + if([System.IO.Path]::GetFileName($Drive.Path) -eq [System.IO.Path]::GetFileName($SourcePath)) { + $Path = $Drive.Path + Hyper-V\Remove-VMHardDiskDrive $Drive + Hyper-V\New-VHD -Path $DestinationPath -ParentPath $SourcePath + Hyper-V\AddVMHardDiskDrive -VM $VM -Path $DestinationPath + break + } + } + } + + } + return Hyper-V\Import-VM -CompatibilityReport $VMConfig +<# +.SYNOPSIS + +Create a new Vagrant Hyper-V VM by cloning original (VMCX based). + +.DESCRIPTION + +Using an existing Hyper-V VM a new Hyper-V VM is created +by cloning the original. + +.PARAMETER VMConfigFile +Path to the original Hyper-V VM configuration file. + +.PARAMETER DestinationPath +Path to new Hyper-V VM hard drive. + +.PARAMETER DataPath +Directory path of the original Hyper-V VM to be cloned. + +.PARAMETER SourcePath +Path to the original Hyper-V VM hard drive. + +.PARAMETER LinkedClone +New Hyper-V VM should be linked clone instead of complete copy. + +.PARAMETER VMName +Name of the new Hyper-V VM. + +.INPUTS + +None. + +.OUTPUTS + +VirtualMachine. The cloned Hyper-V VM. +#> +} + +function New-VagrantVMXML { + param ( + [parameter(Mandatory=$true)] + [string] $VMConfigFile, + [parameter(Mandatory=$true)] + [string] $DestinationPath, + [parameter (Mandatory=$true)] + [string] $DataPath, + [parameter (Mandatory=$true)] + [string] $SourcePath, + [parameter (Mandatory=$false)] + [bool] $LinkedClone = $false, + [parameter(Mandatory=$false)] + [string] $VMName + ) + + $DestinationDirectory = [System.IO.Path]::GetDirectoryName($DestinationPath) + New-Item -ItemType Directory -Force -Path $DestinationDirectory + + if($LinkedClone){ + Hyper-V\New-VHD -Path $DestinationPath -ParentPath $SourcePath -ErrorAction Stop + } else { + Copy-Item $SourcePath -Destination $DestinationPath -ErrorAction Stop + } + + [xml]$VMConfig = Get-Content -Path $VMConfigFile + $Gen = [int]($VMConfig.configuration.properties.subtype."#text") + 1 + if(!$VMName) { + $VMName = $VMConfig.configuration.properties.name."#text" + } + + # Determine boot device + if($Gen -eq 1) { + Switch ((Select-Xml -xml $VMConfig -XPath "//boot").node.device0."#text") { + "Floppy" { $BootDevice = "Floppy" } + "HardDrive" { $BootDevice = "IDE" } + "Optical" { $BootDevice = "CD" } + "Network" { $BootDevice = "LegacyNetworkAdapter" } + "Default" { $BootDevice = "IDE" } + } + } else { + Switch ((Select-Xml -xml $VMConfig -XPath "//boot").node.device0."#text") { + "HardDrive" { $BootDevice = "VHD" } + "Optical" { $BootDevice = "CD" } + "Network" { $BootDevice = "NetworkAdapter" } + "Default" { $BootDevice = "VHD" } + } + } + + # Determine if secure boot is enabled + $SecureBoot = (Select-Xml -XML $VMConfig -XPath "//secure_boot_enabled").Node."#text" + + $NewVMConfig = @{ + Name = $VMName; + NoVHD = $true; + BootDevice = $BootDevice; + } + + # Generation parameter in PS4 so validate before using + if((Get-Command Hyper-V\New-VM).Parameters.Keys.Contains("generation")) { + $NewVMConfig.Generation = $Gen + } + + # Create new VM instance + $VM = Hyper-V\New-VM @NewVMConfig + + # Configure secure boot + if($Gen -gt 1) { + if($SecureBoot -eq "True") { + Hyper-V\Set-VMFirmware -VM $VM -EnableSecureBoot On + } else { + Hyper-V\Set-VMFirmware -VM $VM -EnableSecureBoot Off + } + } + + # Configure drives + [regex]$DriveNumberMatcher = "\d" + $Controllers = Select-Xml -XML $VMConfig -XPath "//*[starts-with(name(.),'controller')]" + + foreach($Controller in $Controllers) { + $Node = $Controller.Node + if($Node.ParentNode.ChannelInstanceGuid) { + $ControllerType = "SCSI" + } else { + $ControllerType = "IDE" + } + $Drives = $Node.ChildNodes | where {$_.pathname."#text"} + foreach($Drive in $Drives) { + $DriveType = $Drive.type."#text" + if($DriveType -ne "VHD") { + continue + } + + $NewDriveConfig = @{ + ControllerNumber = $DriveNumberMatcher.Match($Controller.node.name).value; + Path = $DestinationPath; + ControllerType = $ControllerType; + } + if($Drive.pool_id."#text") { + $NewDriveConfig.ResourcePoolname = $Drive.pool_id."#text" + } + $VM | Hyper-V\Add-VMHardDiskDrive @NewDriveConfig + } + } + + # Apply original VM configuration to new VM instance + + $processors = $VMConfig.configuration.settings.processors.count."#text" + $notes = (Select-Xml -XML $VMConfig -XPath "//notes").node."#text" + $memory = (Select-Xml -XML $VMConfig -XPath "//memory").node.Bank + if ($memory.dynamic_memory_enabled."#text" -eq "True") { + $dynamicmemory = $True + } + else { + $dynamicmemory = $False + } + # Memory values need to be in bytes + $MemoryMaximumBytes = ($memory.limit."#text" -as [int]) * 1MB + $MemoryStartupBytes = ($memory.size."#text" -as [int]) * 1MB + $MemoryMinimumBytes = ($memory.reservation."#text" -as [int]) * 1MB + + $Config = @{ + ProcessorCount = $processors; + MemoryStartupBytes = $MemoryStartupBytes + } + if($dynamicmemory) { + $Config.DynamicMemory = $true + $Config.MemoryMinimumBytes = $MemoryMinimumBytes + $Config.MemoryMaximumBytes = $MemoryMaximumBytes + } else { + $Config.StaticMemory = $true + } + if($notes) { + $Config.Notes = $notes + } + Hyper-V\Set-VM -VM $VM @Config + + return $VM +<# +.SYNOPSIS + +Create a new Vagrant Hyper-V VM by cloning original (XML based). + +.DESCRIPTION + +Using an existing Hyper-V VM a new Hyper-V VM is created +by cloning the original. + +.PARAMETER VMConfigFile +Path to the original Hyper-V VM configuration file. + +.PARAMETER DestinationPath +Path to new Hyper-V VM hard drive. + +.PARAMETER DataPath +Directory path of the original Hyper-V VM to be cloned. + +.PARAMETER SourcePath +Path to the original Hyper-V VM hard drive. + +.PARAMETER LinkedClone +New Hyper-V VM should be linked clone instead of complete copy. + +.PARAMETER VMName +Name of the new Hyper-V VM. + +.INPUTS + +None. + +.OUTPUTS + +VirtualMachine. The cloned Hyper-V VM. +#> +} + +# Vagrant VM configuration functions + +function Set-VagrantVMMemory { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$false)] + [int] $Memory, + [parameter (Mandatory=$false)] + [int] $MaxMemory + ) + + $ConfigMemory = Hyper-V\Get-VMMemory -VM $VM + + if(!$Memory) { + $MemoryStartupBytes = ($ConfigMemory.Startup) + $MemoryMinimumBytes = ($ConfigMemory.Minimum) + $MemoryMaximumBytes = ($ConfigMemory.Maximum) + } else { + $MemoryStartupBytes = $Memory * 1MB + $MemoryMinimumBytes = $Memory * 1MB + $MemoryMaximumBytes = $Memory * 1MB + } + + if($MaxMemory) { + $DynamicMemory = $true + $MemoryMaximumBytes = $MaxMemory * 1MB + } + + if($DynamicMemory) { + Hyper-V\Set-VM -VM $VM -DynamicMemory + Hyper-V\Set-VM -VM $VM -MemoryMinimumBytes $MemoryMinimumBytes -MemoryMaximumBytes ` + $MemoryMaximumBytes -MemoryStartupBytes $MemoryStartupBytes + } else { + Hyper-V\Set-VM -VM $VM -StaticMemory + Hyper-V\Set-VM -VM $VM -MemoryStartupBytes $MemoryStartupBytes + } + return $VM +<# +.SYNOPSIS + +Configure VM memory settings. + +.DESCRIPTION + +Adjusts the VM memory settings. If MaxMemory is defined, dynamic memory +is enabled on the VM. + +.PARAMETER VM + +Hyper-V VM for modification. + +.Parameter Memory + +Memory to allocate to the given VM in MB. + +.Parameter MaxMemory + +Maximum memory to allocate to the given VM in MB. When this value is +provided dynamic memory is enabled for the VM. The Memory value or +the currently configured memory of the VM will be used as the minimum +and startup memory value. + +.Output + +VirtualMachine. +#> +} + +function Set-VagrantVMCPUS { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$false)] + [int] $CPUCount + ) + + if($CPUCount) { + Hyper-V\Set-VM -VM $VM -ProcessorCount $CPUCount + } + return $VM +<# +.SYNOPSIS + +Configure VM CPU count. + +.DESCRIPTION + +Configure the number of CPUs on the given VM. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER CPUCount + +Number of CPUs. + +.Output + +VirtualMachine. +#> +} + +function Set-VagrantVMVirtExtensions { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$false)] + [bool] $Enabled=$false + ) + + Hyper-V\Set-VMProcessor -VM $VM -ExposeVirtualizationExtensions $Enabled + return $VM +<# +.SYNOPSIS + +Enable virtualization extensions on VM. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER Enabled + +Enable virtualization extensions on given VM. + +.OUTPUT + +VirtualMachine. +#> +} + +function Set-VagrantVMAutoActions { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$false)] + [string] $AutoStartAction="Nothing", + [parameter (Mandatory=$false)] + [string] $AutoStopAction="ShutDown" + ) + + Hyper-V\Set-VM -VM $VM -AutomaticStartAction $AutoStartAction + Hyper-V\Set-VM -VM $VM -AutomaticStopAction $AutoStopAction + return $VM +<# +.SYNOPSIS + +Configure automatic start and stop actions for VM + +.DESCRIPTION + +Configures the automatic start and automatic stop actions for +the given VM. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER AutoStartAction + +Action the VM should automatically take when the host is started. + +.PARAMETER AutoStopAction + +Action the VM should automatically take when the host is stopped. + +.OUTPUT + +VirtualMachine. +#> +} + +function Set-VagrantVMService { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$true)] + [string] $Name, + [parameter (Mandatory=$true)] + [bool] $Enable + ) + + if($Enable) { + Hyper-V\Enable-VMIntegrationService -VM $VM -Name $Name + } else { + Hyper-V\Disable-VMIntegrationService -VM $VM -Name $Name + } + return $VM +<# +.SYNOPSIS + +Enable or disable Hyper-V VM integration services. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER Name + +Name of the integration service. + +.PARAMETER Enable + +Enable or disable the service. + +.OUTPUT + +VirtualMachine. +#> +} + +# Vagrant networking functions + +function Get-VagrantVMSwitch { + param ( + [parameter (Mandatory=$true)] + [string] $NameOrID + ) + $SwitchName = $(Hyper-V\Get-VMSwitch -Id $NameOrID).Name + if(!$SwitchName) { + $SwitchName = $(Hyper-V\Get-VMSwitch -Name $NameOrID).Name + } + if(!$SwitchName) { + throw "Failed to locate switch with name or ID: ${NameOrID}" + } + return $SwitchName +<# +.SYNOPSIS + +Get name of VMSwitch. + +.DESCRIPTION + +Find VMSwitch by name or ID and return name. + +.PARAMETER NameOrID + +Name or ID of VMSwitch. + +.OUTPUT + +Name of VMSwitch. +#> +} + +function Set-VagrantVMSwitch { + param ( + [parameter (Mandatory=$true)] + [Microsoft.HyperV.PowerShell.VirtualMachine] $VM, + [parameter (Mandatory=$true)] + [String] $SwitchName + ) + $Adapter = Hyper-V\Get-VMNetworkAdapter -VM $VM + Hyper-V\Connect-VMNetworkAdapter -VMNetworkAdapter $Adapter -SwitchName $SwitchName + return $VM +<# +.SYNOPSIS + +Configure VM to use given switch. + +.DESCRIPTION + +Configures VM adapter to use the the VMSwitch with the given name. + +.PARAMETER VM + +Hyper-V VM for modification. + +.PARAMETER SwitchName + +Name of the VMSwitch. + +.OUTPUT + +VirtualMachine. +#> +} diff --git a/plugins/providers/hyperv/scripts/utils/write_messages.ps1 b/plugins/providers/hyperv/scripts/utils/write_messages.ps1 deleted file mode 100644 index ce0d8d8cf..000000000 --- a/plugins/providers/hyperv/scripts/utils/write_messages.ps1 +++ /dev/null @@ -1,20 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -function Write-Error-Message($message) { - $error_message = @{ - error = "$message" - } - Write-Host "===Begin-Error===" - $result = ConvertTo-json $error_message - Write-Host $result - Write-Host "===End-Error===" -} - -function Write-Output-Message($message) { - Write-Host "===Begin-Output===" - Write-Host $message - Write-Host "===End-Output===" -} From 52bae2193342ed33381c7f59ee9507de24e422c9 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 24 May 2018 15:33:36 -0700 Subject: [PATCH 02/18] Remove unused PowerShell scripts --- .../hyperv/scripts/utils/create_session.ps1 | 34 ----------- .../smb/scripts/mount_share.ps1 | 56 ------------------- .../synced_folders/smb/scripts/ps_version.ps1 | 1 - 3 files changed, 91 deletions(-) delete mode 100644 plugins/providers/hyperv/scripts/utils/create_session.ps1 delete mode 100644 plugins/synced_folders/smb/scripts/mount_share.ps1 delete mode 100644 plugins/synced_folders/smb/scripts/ps_version.ps1 diff --git a/plugins/providers/hyperv/scripts/utils/create_session.ps1 b/plugins/providers/hyperv/scripts/utils/create_session.ps1 deleted file mode 100644 index fcf0f7f10..000000000 --- a/plugins/providers/hyperv/scripts/utils/create_session.ps1 +++ /dev/null @@ -1,34 +0,0 @@ -#------------------------------------------------------------------------- -# Copyright (c) Microsoft Open Technologies, Inc. -# All Rights Reserved. Licensed under the MIT License. -#-------------------------------------------------------------------------- - -function Get-Remote-Session($guest_ip, $username, $password) { - $secstr = convertto-securestring -AsPlainText -Force -String $password - $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr - New-PSSession -ComputerName $guest_ip -Credential $cred -ErrorAction "stop" -} - -function Create-Remote-Session($guest_ip, $username, $password) { - $count = 0 - $session_error = "" - $session = "" - do { - $count++ - try { - $session = Get-Remote-Session $guest_ip $username $password - $session_error = "" - } - catch { - Start-Sleep -s 1 - $session_error = $_ - $session = "" - } - } - while (!$session -and $count -lt 20) - - return @{ - session = $session - error = $session_error - } -} diff --git a/plugins/synced_folders/smb/scripts/mount_share.ps1 b/plugins/synced_folders/smb/scripts/mount_share.ps1 deleted file mode 100644 index fa1f98133..000000000 --- a/plugins/synced_folders/smb/scripts/mount_share.ps1 +++ /dev/null @@ -1,56 +0,0 @@ -param ( - [string]$share_name = $(throw "-share_name is required."), - [string]$guest_path = $(throw "-guest_path is required."), - [string]$guest_ip = $(throw "-guest_ip is required."), - [string]$username = $(throw "-username is required."), - [string]$password = $(throw "-password is required."), - [string]$host_ip = $(throw "-host_ip is required."), - [string]$host_share_username = $(throw "-host_share_username is required."), - [string]$host_share_password = $(throw "-host_share_password is required.") - ) - -# Include the following modules -$presentDir = Split-Path -parent $PSCommandPath -$modules = @() -$modules += $presentDir + "\utils\create_session.ps1" -$modules += $presentDir + "\utils\write_messages.ps1" - -forEach ($module in $modules) { . $module } - -try { - function Mount-File($share_name, $guest_path, $host_path, $host_share_username, $host_share_password) { - try { - # TODO: Check for folder exist. - # Use net use and prompt for password - $guest_path = $guest_path.replace("/", "\") - # Map a network drive to the guest machine - $result = net use * $host_path /user:$host_share_username $host_share_password /persistent:yes - $mapped_drive = (($result -match "\w:") -split (" "))[1] - Write-Host cmd /c mklink /d $guest_path $mapped_drive - # If a folder exist remove it. - if (Test-Path $guest_path) { - $junction = Get-Item $guest_path - $junction.Delete() - } - cmd /c mklink /d $guest_path $mapped_drive - } catch { - return $_ - } - } - - $response = Create-Remote-Session $guest_ip $username $password - - if (!$response["session"] -and $response["error"]) { - Write-Error-Message $response["error"] - return - } - $host_path = "\\$host_ip\$share_name" - $host_share_username = "$host_ip\$host_share_username" - $result = Invoke-Command -Session $response["session"] -ScriptBlock ${function:Mount-File} -ArgumentList $share_name, $guest_path, $host_path, $host_share_username, $host_share_password -ErrorAction "stop" - Remove-PSSession -Id $response["session"].Id - Write-Error-Message $result -} -catch { - Write-Error-Message "Failed to mount files VM $_" - return -} diff --git a/plugins/synced_folders/smb/scripts/ps_version.ps1 b/plugins/synced_folders/smb/scripts/ps_version.ps1 deleted file mode 100644 index 89fbab28e..000000000 --- a/plugins/synced_folders/smb/scripts/ps_version.ps1 +++ /dev/null @@ -1 +0,0 @@ -Write-Output $PSVersionTable.PSVersion.Major From d2bc634623a49343f555a79d387b1a3ae57c3352 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 24 May 2018 16:55:35 -0700 Subject: [PATCH 03/18] Add vm_integration_services validation --- plugins/providers/hyperv/config.rb | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 684cf36f1..3a4b2a22c 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -61,14 +61,7 @@ module VagrantPlugins @auto_stop_action = UNSET_VALUE @enable_virtualization_extensions = UNSET_VALUE @enable_checkpoints = UNSET_VALUE - @vm_integration_services = { - guest_service_interface: UNSET_VALUE, - heartbeat: UNSET_VALUE, - key_value_pair_exchange: UNSET_VALUE, - shutdown: UNSET_VALUE, - time_synchronization: UNSET_VALUE, - vss: UNSET_VALUE - } + @vm_integration_services = {} end def finalize! @@ -94,20 +87,30 @@ module VagrantPlugins else @enable_checkpoints = !!@enable_checkpoints end - @vm_integration_services.delete_if{|_, v| v == UNSET_VALUE } - @vm_integration_services = nil if @vm_integration_services.empty? end def validate(machine) errors = _detected_errors + if !vm_integration_services.is_a?(Hash) + errors << I18n.t("vagrant_hyperv.config.invalid_integration_services_type", + received: vm_integration_services.class) + else + vm_integration_services.each do |key, value| + if ![true, false].include?(value) + errors << I18n.t("vagrant_hyperv.config.invalid_integration_services_entry", + entry_name: name, entry_value: value) + end + end + end + if !ALLOWED_AUTO_START_ACTIONS.include?(auto_start_action) - errors << I18n.t("vagrant.hyperv.config.invalid_auto_start_action", action: auto_start_action, + errors << I18n.t("vagrant_hyperv.config.invalid_auto_start_action", action: auto_start_action, allowed_actions: ALLOWED_AUTO_START_ACTIONS.join(", ")) end if !ALLOWED_AUTO_STOP_ACTIONS.include?(auto_stop_action) - errors << I18n.t("vagrant.hyperv.config.invalid_auto_stop_action", action: auto_stop_action, + errors << I18n.t("vagrant_hyperv.config.invalid_auto_stop_action", action: auto_stop_action, allowed_actions: ALLOWED_AUTO_STOP_ACTIONS.join(", ")) end From b1f0f1566d607004b6a9bd9ad8000d757ede2bb4 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 24 May 2018 16:56:18 -0700 Subject: [PATCH 04/18] Add new entries for hyper-v config validation errors --- templates/locales/providers_hyperv.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index d81875297..a8a002fc3 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -11,6 +11,30 @@ en: message_not_running: |- Hyper-V machine isn't running. Can't SSH in! + config: + invalid_auto_start_action: |- + The requested auto start action for the Hyper-V VM is not a + valid action. Please provide a valid action and run the command + again. + + Received: %{action} + Allowed: %{allowed_actions} + invalid_auto_stop_action: |- + The requested auto stop action for the Hyper-V VM is not a + valid action. Please provide a valid action and run the command + again. + + Received: %{action} + Allowed: %{allowed_actions} + invalid_integration_services_type: |- + Invalid type provided for `vm_integration_services`. Type received + is `%{received}` but `Hash` was expected. + invalid_integration_services_entry: |- + The `%{entry_name}` entry in the `vm_integration_services` is set + to an unexpected value. + + Received: %{entry_value} + Allowed: true, false errors: admin_required: |- The Hyper-V provider requires that Vagrant be run with From beacb5bada0fa65478f0eabc1d22336ffead3dd0 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Thu, 24 May 2018 16:56:46 -0700 Subject: [PATCH 05/18] Add test coverage for all hyper-v configuration options --- .../plugins/providers/hyperv/config_test.rb | 153 +++++++++++++++++- 1 file changed, 152 insertions(+), 1 deletion(-) diff --git a/test/unit/plugins/providers/hyperv/config_test.rb b/test/unit/plugins/providers/hyperv/config_test.rb index 467fc7f97..efce526be 100644 --- a/test/unit/plugins/providers/hyperv/config_test.rb +++ b/test/unit/plugins/providers/hyperv/config_test.rb @@ -3,6 +3,9 @@ require_relative "../../../base" require Vagrant.source_root.join("plugins/providers/hyperv/config") describe VagrantPlugins::HyperV::Config do + + let(:machine){ double("machine") } + describe "#ip_address_timeout" do it "can be set" do subject.ip_address_timeout = 180 @@ -30,7 +33,7 @@ describe VagrantPlugins::HyperV::Config do expect(subject.mac).to eq("001122334455") end end - + describe "#vmname" do it "can be set" do subject.vmname = "test" @@ -62,4 +65,152 @@ describe VagrantPlugins::HyperV::Config do expect(subject.cpus).to eq(2) end end + + describe "#vmname" do + it "can be set" do + subject.vmname = "custom" + subject.finalize! + expect(subject.vmname).to eq("custom") + end + end + + describe "#differencing_disk" do + it "is false by default" do + subject.finalize! + expect(subject.differencing_disk).to eq(false) + end + + it "can be set" do + subject.differencing_disk = true + subject.finalize! + expect(subject.differencing_disk).to eq(true) + end + + it "should set linked_clone" do + subject.differencing_disk = true + subject.finalize! + expect(subject.differencing_disk).to eq(true) + expect(subject.linked_clone).to eq(true) + end + end + + describe "#linked_clone" do + it "is false by default" do + subject.finalize! + expect(subject.linked_clone).to eq(false) + end + + it "can be set" do + subject.linked_clone = true + subject.finalize! + expect(subject.linked_clone).to eq(true) + end + + it "should set differencing_disk" do + subject.linked_clone = true + subject.finalize! + expect(subject.linked_clone).to eq(true) + expect(subject.differencing_disk).to eq(true) + end + end + + describe "#auto_start_action" do + it "should be Nothing by default" do + subject.finalize! + expect(subject.auto_start_action).to eq("Nothing") + end + + it "can be set" do + subject.auto_start_action = "Start" + subject.finalize! + expect(subject.auto_start_action).to eq("Start") + end + + it "does not accept invalid values" do + subject.auto_start_action = "Invalid" + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).not_to be_empty + end + end + + describe "#auto_stop_action" do + it "should be ShutDown by default" do + subject.finalize! + expect(subject.auto_stop_action).to eq("ShutDown") + end + + it "can be set" do + subject.auto_stop_action = "Save" + subject.finalize! + expect(subject.auto_stop_action).to eq("Save") + end + + it "does not accept invalid values" do + subject.auto_stop_action = "Invalid" + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).not_to be_empty + end + end + + describe "#enable_checkpoints" do + it "is false by default" do + subject.finalize! + expect(subject.enable_checkpoints).to eq(false) + end + + it "can be set" do + subject.enable_checkpoints = true + subject.finalize! + expect(subject.enable_checkpoints).to eq(true) + end + end + + describe "#enable_virtualization_extensions" do + it "is false by default" do + subject.finalize! + expect(subject.enable_virtualization_extensions).to eq(false) + end + + it "can be set" do + subject.enable_virtualization_extensions = true + subject.finalize! + expect(subject.enable_virtualization_extensions).to eq(true) + end + end + + describe "#vm_integration_services" do + it "is empty by default" do + subject.finalize! + expect(subject.vm_integration_services).to be_empty + end + + it "accepts new entries" do + subject.vm_integration_services["entry"] = "value" + subject.finalize! + expect(subject.vm_integration_services["entry"]).to eq("value") + end + + it "does not accept non-Hash types" do + subject.vm_integration_services = "value" + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).not_to be_empty + end + + it "accepts boolean values within Hash" do + subject.vm_integration_services["custom"] = true + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).to be_empty + end + + it "does not accept non-boolean values within Hash" do + subject.vm_integration_services["custom"] = "value" + subject.finalize! + result = subject.validate(machine) + expect(result["Hyper-V"]).not_to be_empty + end + end end From 2bd6f537efd61f7069c9b270c4f3c95123e820b0 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 25 May 2018 10:11:05 -0700 Subject: [PATCH 06/18] Clean up syntax and force string type when setting integration option --- plugins/providers/hyperv/driver.rb | 34 +++++++++++++++--------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 83c43f3ce..04efa3eb1 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -77,14 +77,14 @@ module VagrantPlugins # # @return [Hash] def get_current_state - execute(:get_vm_status, { VmId: vm_id }) + execute(:get_vm_status, VmId: vm_id) end # Delete the VM # # @return [nil] def delete_vm - execute(:delete_vm, { VmId: vm_id }) + execute(:delete_vm, VmId: vm_id) end # Export the VM to the given path @@ -92,49 +92,49 @@ module VagrantPlugins # @param [String] path Path for export # @return [nil] def export(path) - execute(:export_vm, {VmId: vm_id, Path: path}) + execute(:export_vm, VmId: vm_id, Path: path) end # Get the IP address of the VM # # @return [Hash] def read_guest_ip - execute(:get_network_config, { VmId: vm_id }) + execute(:get_network_config, VmId: vm_id) end # Get the MAC address of the VM # # @return [Hash] def read_mac_address - execute(:get_network_mac, { VmId: vm_id }) + execute(:get_network_mac, VmId: vm_id) end # Resume the VM from suspension # # @return [nil] def resume - execute(:resume_vm, { VmId: vm_id }) + execute(:resume_vm, VmId: vm_id) end # Start the VM # # @return [nil] def start - execute(:start_vm, { VmId: vm_id }) + execute(:start_vm, VmId: vm_id ) end # Stop the VM # # @return [nil] def stop - execute(:stop_vm, { VmId: vm_id }) + execute(:stop_vm, VmId: vm_id) end # Suspend the VM # # @return [nil] def suspend - execute(:suspend_vm, { VmId: vm_id }) + execute(:suspend_vm, VmId: vm_id) end # Import a new VM @@ -150,7 +150,7 @@ module VagrantPlugins # @param [String] vlan_id VLAN ID # @return [nil] def net_set_vlan(vlan_id) - execute(:set_network_vlan, { VmId: vm_id, VlanId: vlan_id }) + execute(:set_network_vlan, VmId: vm_id, VlanId: vlan_id) end # Set the VM adapter MAC address @@ -158,7 +158,7 @@ module VagrantPlugins # @param [String] mac_addr MAC address # @return [nil] def net_set_mac(mac_addr) - execute(:set_network_mac, { VmId: vm_id, Mac: mac_addr }) + execute(:set_network_mac, VmId: vm_id, Mac: mac_addr) end # Create a new snapshot with the given name @@ -166,7 +166,7 @@ module VagrantPlugins # @param [String] snapshot_name Name of the new snapshot # @return [nil] def create_snapshot(snapshot_name) - execute(:create_snapshot, { VmId: vm_id, SnapName: (snapshot_name) } ) + execute(:create_snapshot, VmId: vm_id, SnapName: snapshot_name) end # Restore the given snapshot @@ -174,14 +174,14 @@ module VagrantPlugins # @param [String] snapshot_name Name of snapshot to restore # @return [nil] def restore_snapshot(snapshot_name) - execute(:restore_snapshot, { VmId: vm_id, SnapName: (snapshot_name) } ) + execute(:restore_snapshot, VmId: vm_id, SnapName: snapshot_name) end # Get list of current snapshots # # @return [Array] snapshot names def list_snapshots - snaps = execute(:list_snapshots, { VmID: vm_id } ) + snaps = execute(:list_snapshots, VmID: vm_id) snaps.map { |s| s['Name'] } end @@ -190,7 +190,7 @@ module VagrantPlugins # @param [String] snapshot_name Name of snapshot to delete # @return [nil] def delete_snapshot(snapshot_name) - execute(:delete_snapshot, {VmID: vm_id, SnapName: snapshot_name}) + execute(:delete_snapshot, VmID: vm_id, SnapName: snapshot_name) end # Enable or disable VM integration services @@ -203,8 +203,8 @@ module VagrantPlugins # to configurable even if Vagrant is not aware of them. def set_vm_integration_services(config) config.each_pair do |srv_name, srv_enable| - args = {VMID: vm_id, Name: INTEGRATION_SERVICES_MAP.fetch(srv_name.to_sym, srv_name)} - args[:Name] = true if srv_enable + args = {VMID: vm_id, Name: INTEGRATION_SERVICES_MAP.fetch(srv_name.to_sym, srv_name).to_s} + args[:Enable] = true if srv_enable execute(:set_vm_integration_services, args) end end From 6c731fb86cd7033ab02f0e9a0b868f1b6cc1fdf6 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 25 May 2018 10:11:44 -0700 Subject: [PATCH 07/18] Add test coverage on hyper-v provider driver --- .../plugins/providers/hyperv/driver_test.rb | 176 ++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 test/unit/plugins/providers/hyperv/driver_test.rb diff --git a/test/unit/plugins/providers/hyperv/driver_test.rb b/test/unit/plugins/providers/hyperv/driver_test.rb new file mode 100644 index 000000000..fa99484f0 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/driver_test.rb @@ -0,0 +1,176 @@ +require_relative "../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/driver") + +describe VagrantPlugins::HyperV::Driver do + def generate_result(obj) + "===Begin-Output===\n" + + JSON.dump(obj) + + "\n===End-Output===" + end + + def generate_error(msg) + "===Begin-Error===\n#{JSON.dump(error: msg)}\n===End-Error===\n" + end + + let(:result){ + Vagrant::Util::Subprocess::Result.new( + result_exit, result_stdout, result_stderr) } + let(:subject){ described_class.new(vm_id) } + let(:vm_id){ 1 } + let(:result_stdout){ "" } + let(:result_stderr){ "" } + let(:result_exit){ 0 } + + context "public methods" do + before{ allow(subject).to receive(:execute_powershell).and_return(result) } + + describe "#execute" do + it "should convert symbol into path string" do + expect(subject).to receive(:execute_powershell).with(kind_of(String), any_args) + .and_return(result) + subject.execute(:thing) + end + + it "should append extension when converting symbol" do + expect(subject).to receive(:execute_powershell).with("thing.ps1", any_args) + .and_return(result) + subject.execute(:thing) + end + + context "when command returns non-zero exit code" do + let(:result_exit){ 1 } + + it "should raise an error" do + expect{ subject.execute(:thing) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellError) + end + end + + context "when command stdout matches error pattern" do + let(:result_stdout){ generate_error("Error Message") } + + it "should raise an error" do + expect{ subject.execute(:thing) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellError) + end + end + + context "with valid JSON output" do + let(:result_stdout){ generate_result(:custom => "value") } + + it "should return parsed JSON data" do + expect(subject.execute(:thing)).to eq("custom" => "value") + end + end + + context "with invalid JSON output" do + let(:result_stdout){ "value" } + it "should return nil" do + expect(subject.execute(:thing)).to be_nil + end + end + end + + describe "#has_vmcx_support?" do + context "when support is available" do + let(:result_stdout){ generate_result(:result => true) } + + it "should be true" do + expect(subject.has_vmcx_support?).to eq(true) + end + end + + context "when support is not available" do + let(:result_stdout){ generate_result(:result => false) } + + it "should be false" do + expect(subject.has_vmcx_support?).to eq(false) + end + end + end + + describe "#set_vm_integration_services" do + it "should map known integration services names automatically" do + expect(subject).to receive(:execute) do |name, args| + expect(args[:Name]).to eq("Shutdown") + end + subject.set_vm_integration_services(shutdown: true) + end + + it "should set enable when value is true" do + expect(subject).to receive(:execute) do |name, args| + expect(args[:Enable]).to eq(true) + end + subject.set_vm_integration_services(shutdown: true) + end + + it "should not set enable when value is false" do + expect(subject).to receive(:execute) do |name, args| + expect(args[:Enable]).to be_nil + end + subject.set_vm_integration_services(shutdown: false) + end + + it "should pass unknown key names directly through" do + expect(subject).to receive(:execute) do |name, args| + expect(args[:Name]).to eq("CustomKey") + end + subject.set_vm_integration_services(CustomKey: true) + end + end + end + + describe "#execute_powershell" do + before{ allow(Vagrant::Util::PowerShell).to receive(:execute) } + + it "should call the PowerShell module to execute" do + expect(Vagrant::Util::PowerShell).to receive(:execute) + subject.send(:execute_powershell, "path", {}) + end + + it "should modify the path separators" do + expect(Vagrant::Util::PowerShell).to receive(:execute) + .with("\\path\\to\\script.ps1", any_args) + subject.send(:execute_powershell, "/path/to/script.ps1", {}) + end + + it "should include ErrorAction option as Stop" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + expect(args).to include("-ErrorAction") + expect(args).to include("Stop") + end + subject.send(:execute_powershell, "path", {}) + end + + it "should automatically include module path" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + opts = args.detect{|i| i.is_a?(Hash)} + expect(opts[:env]).not_to be_nil + expect(opts[:env]["PSModulePath"]).to include("$env:PSModulePath+") + end + subject.send(:execute_powershell, "path", {}) + end + + it "should covert hash options into arguments" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + expect(args).to include("-Custom") + expect(args).to include("'Value'") + end + subject.send(:execute_powershell, "path", "Custom" => "Value") + end + + it "should treat keys with `true` value as switches" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + expect(args).to include("-Custom") + expect(args).not_to include("'true'") + end + subject.send(:execute_powershell, "path", "Custom" => true) + end + + it "should not include keys with `false` value" do + expect(Vagrant::Util::PowerShell).to receive(:execute) do |path, *args| + expect(args).not_to include("-Custom") + end + subject.send(:execute_powershell, "path", "Custom" => false) + end + end +end From 35d89203256fa7e27ab9566d39ad92544b031726 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 25 May 2018 15:11:58 -0700 Subject: [PATCH 08/18] Case insensitive switch check and only write sentinel file if not found --- plugins/providers/hyperv/action/configure.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/providers/hyperv/action/configure.rb b/plugins/providers/hyperv/action/configure.rb index f8d1e7d74..30831ac12 100644 --- a/plugins/providers/hyperv/action/configure.rb +++ b/plugins/providers/hyperv/action/configure.rb @@ -24,8 +24,8 @@ module VagrantPlugins if opts[:bridge] @logger.debug("Looking for switch with name or ID: #{opts[:bridge]}") switch = switches.find{ |s| - s["Name"].downcase == opts[:bridge] || - s["Id"].downcase == opts[:bridge] + s["Name"].downcase == opts[:bridge].to_s.downcase || + s["Id"].downcase == opts[:bridge].to_s.downcase } if switch @logger.debug("Found switch - Name: #{switch["Name"]} ID: #{switch["Id"]}") @@ -78,11 +78,13 @@ module VagrantPlugins env[:machine].provider.driver.execute(:configure_vm, options) # Create the sentinel - sentinel.open("w") do |f| - f.write(Time.now.to_i.to_s) + if !sentinel.file? + sentinel.open("w") do |f| + f.write(Time.now.to_i.to_s) + end end - if env[:machine].provider_config.vm_integration_services + if !env[:machine].provider_config.vm_integration_services.empty? env[:ui].detail("Setting VM Integration Services") env[:machine].provider_config.vm_integration_services.each do |key, value| From d24b43227300ed38e652fd58dcfb628cce89fd9f Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 25 May 2018 15:12:39 -0700 Subject: [PATCH 09/18] Add Hyper-V provider actions test coverage --- .../hyperv/action/check_enabled_test.rb | 27 ++++ .../providers/hyperv/action/configure_test.rb | 125 +++++++++++++++++ .../providers/hyperv/action/export_test.rb | 42 ++++++ .../providers/hyperv/action/import_test.rb | 130 ++++++++++++++++++ 4 files changed, 324 insertions(+) create mode 100644 test/unit/plugins/providers/hyperv/action/check_enabled_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/configure_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/export_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/import_test.rb diff --git a/test/unit/plugins/providers/hyperv/action/check_enabled_test.rb b/test/unit/plugins/providers/hyperv/action/check_enabled_test.rb new file mode 100644 index 000000000..288d61b1f --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/check_enabled_test.rb @@ -0,0 +1,27 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/check_enabled") + +describe VagrantPlugins::HyperV::Action::CheckEnabled do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider) } + let(:subject){ described_class.new(app, env) } + + before{ allow(ui).to receive(:output) } + + it "should continue when Hyper-V is enabled" do + expect(driver).to receive(:execute).and_return("result" => true) + expect(app).to receive(:call) + subject.call(env) + end + + it "should raise error when Hyper-V is not enabled" do + expect(driver).to receive(:execute).and_return("result" => false) + expect(app).not_to receive(:call) + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::PowerShellFeaturesDisabled) + end +end diff --git a/test/unit/plugins/providers/hyperv/action/configure_test.rb b/test/unit/plugins/providers/hyperv/action/configure_test.rb new file mode 100644 index 000000000..9b2d8222b --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/configure_test.rb @@ -0,0 +1,125 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/configure") + +describe VagrantPlugins::HyperV::Action::Configure do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, config: config, provider_config: provider_config, data_dir: data_dir, id: "machineID") } + let(:data_dir){ double("data_dir") } + let(:config){ double("config", vm: vm) } + let(:vm){ double("vm", networks: networks) } + let(:networks){ [] } + let(:switches){ [ + {"Name" => "Switch1", "Id" => "ID1"}, + {"Name" => "Switch2", "Id" => "ID2"} + ]} + let(:sentinel){ double("sentinel") } + let(:provider_config){ + double("provider_config", + memory: "1024", + maxmemory: "1024", + cpus: 1, + auto_start_action: "Nothing", + auto_stop_action: "Save", + enable_checkpoints: false, + enable_virtualization_extensions: false, + vm_integration_services: vm_integration_services + ) + } + let(:vm_integration_services){ {} } + + let(:subject){ described_class.new(app, env) } + + before do + allow(driver).to receive(:execute) + allow(app).to receive(:call) + expect(driver).to receive(:execute).with(:get_switches).and_return(switches) + allow(ui).to receive(:detail) + allow(ui).to receive(:output) + allow(ui).to receive(:ask).and_return("1") + allow(data_dir).to receive(:join).and_return(sentinel) + allow(sentinel).to receive(:file?).and_return(false) + allow(sentinel).to receive(:open) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + context "with missing switch sentinel file" do + it "should prompt for switch to use" do + expect(ui).to receive(:ask) + subject.call(env) + end + + it "should write sentinel file" do + expect(sentinel).to receive(:open) + subject.call(env) + end + end + + context "with existing switch sentinel file" do + before{ allow(sentinel).to receive(:file?).twice.and_return(true) } + + it "should not prompt for switch to use" do + expect(ui).not_to receive(:ask) + subject.call(env) + end + + it "should not write sentinel file" do + expect(sentinel).not_to receive(:open) + subject.call(env) + end + end + + context "with bridge defined in networks" do + context "with valid bridge switch name" do + let(:networks){ [[:public_network, {bridge: "Switch1"}]] } + + it "should not prompt for switch" do + expect(ui).not_to receive(:ask) + subject.call(env) + end + end + + context "with valid bridge switch ID" do + let(:networks){ [[:public_network, {bridge: "ID1"}]] } + + it "should not prompt for switch" do + expect(ui).not_to receive(:ask) + subject.call(env) + end + end + + context "with invalid bridge switch name" do + let(:networks){ [[:public_network, {bridge: "UNKNOWN"}]] } + + it "should prompt for switch" do + expect(ui).to receive(:ask) + subject.call(env) + end + end + end + + context "with integration services enabled" do + let(:vm_integration_services){ {service: true} } + + it "should call the driver to set the services" do + expect(driver).to receive(:set_vm_integration_services) + subject.call(env) + end + end + + context "without available switches" do + let(:switches){ [] } + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::NoSwitches) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/export_test.rb b/test/unit/plugins/providers/hyperv/action/export_test.rb new file mode 100644 index 000000000..257225219 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/export_test.rb @@ -0,0 +1,42 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/export") + +describe VagrantPlugins::HyperV::Action::Export do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, state: state) } + let(:state){ double("state", id: machine_state) } + let(:machine_state){ :off } + + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(ui).to receive(:info) + allow(ui).to receive(:clear_line) + allow(ui).to receive(:report_progress) + allow(driver).to receive(:export) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to perform the export" do + expect(driver).to receive(:export) + subject.call(env) + end + + context "with invalid machine state" do + let(:machine_state){ :on } + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(Vagrant::Errors::VMPowerOffToPackage) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/import_test.rb b/test/unit/plugins/providers/hyperv/action/import_test.rb new file mode 100644 index 000000000..32c68695c --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/import_test.rb @@ -0,0 +1,130 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/import") + +describe VagrantPlugins::HyperV::Action::Import do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config, box: box, data_dir: data_dir) } + let(:provider_config){ + double("provider_config", + linked_clone: false, + vmname: "VMNAME" + ) + } + let(:box){ double("box", directory: box_directory) } + let(:box_directory){ double("box_directory") } + let(:data_dir){ double("data_dir") } + let(:vm_dir){ double("vm_dir") } + let(:hd_dir){ double("hd_dir") } + + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(box_directory).to receive(:join).with("Virtual Machines").and_return(vm_dir) + allow(box_directory).to receive(:join).with("Virtual Hard Disks").and_return(hd_dir) + allow(vm_dir).to receive(:directory?).and_return(true) + allow(vm_dir).to receive(:each_child).and_yield(Pathname.new("file.txt")) + allow(hd_dir).to receive(:directory?).and_return(true) + allow(hd_dir).to receive(:each_child).and_yield(Pathname.new("file.txt")) + allow(driver).to receive(:has_vmcx_support?).and_return(true) + allow(data_dir).to receive(:join).and_return(data_dir) + allow(data_dir).to receive(:to_s).and_return("DATA_DIR_PATH") + allow(driver).to receive(:import).and_return("id" => "VMID") + allow(machine).to receive(:id=) + allow(ui).to receive(:output) + allow(ui).to receive(:detail) + end + + context "with missing virtual machines directory" do + before{ expect(vm_dir).to receive(:directory?).and_return(false) } + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with missing hard disks directory" do + before{ expect(hd_dir).to receive(:directory?).and_return(false) } + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with missing configuration file" do + before do + allow(hd_dir).to receive(:each_child).and_yield(Pathname.new("image.vhd")) + end + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with missing image file" do + before do + allow(vm_dir).to receive(:each_child).and_yield(Pathname.new("config.xml")) + end + + it "should raise an error" do + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with image and config files" do + before do + allow(vm_dir).to receive(:each_child).and_yield(Pathname.new("config.xml")) + allow(hd_dir).to receive(:each_child).and_yield(Pathname.new("image.vhd")) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should request import via the driver" do + expect(driver).to receive(:import).and_return("id" => "VMID") + subject.call(env) + end + + it "should set the machine ID after import" do + expect(machine).to receive(:id=).with("VMID") + subject.call(env) + end + + context "with no vmcx support" do + before do + expect(driver).to receive(:has_vmcx_support?).and_return(false) + end + + it "should match XML config file" do + subject.call(env) + end + + it "should not match VMCX config file" do + expect(vm_dir).to receive(:each_child).and_yield(Pathname.new("config.vmcx")) + expect{ subject.call(env) }.to raise_error(VagrantPlugins::HyperV::Errors::BoxInvalid) + end + end + + context "with vmcx support" do + before do + expect(driver).to receive(:has_vmcx_support?).and_return(true) + end + + it "should match XML config file" do + subject.call(env) + end + + it "should match VMCX config file" do + expect(vm_dir).to receive(:each_child).and_yield(Pathname.new("config.vmcx")) + subject.call(env) + end + end + end +end From eba552ea73135fa460c73c7c97b1152573504918 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 25 May 2018 16:23:45 -0700 Subject: [PATCH 10/18] Add more test coverage on Hyper-V Provider actions --- .../providers/hyperv/action/delete_vm_test.rb | 29 +++++++++ .../hyperv/action/is_windows_test.rb | 37 +++++++++++ .../hyperv/action/net_set_mac_test.rb | 40 ++++++++++++ .../hyperv/action/net_set_vlan_test.rb | 40 ++++++++++++ .../hyperv/action/read_guest_ip_test.rb | 39 ++++++++++++ .../hyperv/action/read_state_test.rb | 56 +++++++++++++++++ .../providers/hyperv/action/set_name_test.rb | 61 +++++++++++++++++++ .../hyperv/action/wait_for_ip_address_test.rb | 40 ++++++++++++ 8 files changed, 342 insertions(+) create mode 100644 test/unit/plugins/providers/hyperv/action/delete_vm_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/is_windows_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/net_set_mac_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/net_set_vlan_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/read_guest_ip_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/read_state_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/set_name_test.rb create mode 100644 test/unit/plugins/providers/hyperv/action/wait_for_ip_address_test.rb diff --git a/test/unit/plugins/providers/hyperv/action/delete_vm_test.rb b/test/unit/plugins/providers/hyperv/action/delete_vm_test.rb new file mode 100644 index 000000000..dda24c90c --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/delete_vm_test.rb @@ -0,0 +1,29 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/delete_vm") + +describe VagrantPlugins::HyperV::Action::DeleteVM do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider) } + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(ui).to receive(:info) + allow(driver).to receive(:delete_vm) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to delete the vm" do + expect(driver).to receive(:delete_vm) + subject.call(env) + end +end diff --git a/test/unit/plugins/providers/hyperv/action/is_windows_test.rb b/test/unit/plugins/providers/hyperv/action/is_windows_test.rb new file mode 100644 index 000000000..6ed9aaf68 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/is_windows_test.rb @@ -0,0 +1,37 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/is_windows") + +describe VagrantPlugins::HyperV::Action::IsWindows do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, config: config) } + let(:config){ double("config", vm: vm) } + let(:vm){ double("vm", guest: :windows) } + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(env).to receive(:[]=) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should update the env with the result" do + expect(env).to receive(:[]=).with(:result, true) + subject.call(env) + end + + it "should set the result to false when not windows" do + expect(vm).to receive(:guest).and_return(:linux) + expect(env).to receive(:[]=).with(:result, false) + subject.call(env) + end + +end diff --git a/test/unit/plugins/providers/hyperv/action/net_set_mac_test.rb b/test/unit/plugins/providers/hyperv/action/net_set_mac_test.rb new file mode 100644 index 000000000..01bc3d6ef --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/net_set_mac_test.rb @@ -0,0 +1,40 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/net_set_mac") + +describe VagrantPlugins::HyperV::Action::NetSetMac do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config) } + let(:provider_config){ double("provider_config", mac: mac) } + let(:mac){ "ADDRESS" } + let(:subject){ described_class.new(app, env) } + + before do + allow(ui).to receive(:info) + allow(driver).to receive(:net_set_mac) + allow(app).to receive(:call) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to set the MAC address" do + expect(driver).to receive(:net_set_mac).with(mac) + subject.call(env) + end + + context "with no MAC address provided" do + let(:mac){ nil } + + it "should not call driver to set the MAC address" do + expect(driver).not_to receive(:net_set_mac) + subject.call(env) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/net_set_vlan_test.rb b/test/unit/plugins/providers/hyperv/action/net_set_vlan_test.rb new file mode 100644 index 000000000..c8b128d73 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/net_set_vlan_test.rb @@ -0,0 +1,40 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/net_set_vlan") + +describe VagrantPlugins::HyperV::Action::NetSetVLan do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config) } + let(:provider_config){ double("provider_config", vlan_id: vlan_id) } + let(:vlan_id){ "VID" } + let(:subject){ described_class.new(app, env) } + + before do + allow(ui).to receive(:info) + allow(driver).to receive(:net_set_vlan) + allow(app).to receive(:call) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to set the vlan id" do + expect(driver).to receive(:net_set_vlan).with(vlan_id) + subject.call(env) + end + + context "with no vlan id provided" do + let(:vlan_id){ nil } + + it "should not call driver to set the vlan id" do + expect(driver).not_to receive(:net_set_vlan) + subject.call(env) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/read_guest_ip_test.rb b/test/unit/plugins/providers/hyperv/action/read_guest_ip_test.rb new file mode 100644 index 000000000..1ec91639a --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/read_guest_ip_test.rb @@ -0,0 +1,39 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/read_guest_ip") + +describe VagrantPlugins::HyperV::Action::ReadGuestIP do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider) } + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(env).to receive(:[]=) + allow(machine).to receive(:id) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + context "with machine ID set" do + before{ allow(machine).to receive(:id).and_return("VMID") } + + it "should request guest IP from the driver" do + expect(driver).to receive(:read_guest_ip).and_return("ip" => "ADDRESS") + subject.call(env) + end + + it "should set the host information into the env" do + expect(env).to receive(:[]=).with(:machine_ssh_info, host: "ADDRESS") + expect(driver).to receive(:read_guest_ip).and_return("ip" => "ADDRESS") + subject.call(env) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/read_state_test.rb b/test/unit/plugins/providers/hyperv/action/read_state_test.rb new file mode 100644 index 000000000..50602f67d --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/read_state_test.rb @@ -0,0 +1,56 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/read_state") + +describe VagrantPlugins::HyperV::Action::ReadState do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine, machine_state_id: state_id} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider) } + let(:state_id){ nil } + + let(:subject){ described_class.new(app, env) } + + before do + allow(app).to receive(:call) + allow(env).to receive(:[]=) + allow(machine).to receive(:id) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should set machine state into the env as not created" do + expect(env).to receive(:[]=).with(:machine_state_id, :not_created) + subject.call(env) + end + + context "with machine ID set" do + before{ allow(machine).to receive(:id).and_return("VMID") } + + it "should request machine state from the driver" do + expect(driver).to receive(:get_current_state).and_return("state" => "running") + subject.call(env) + end + + it "should set machine state into the env" do + expect(driver).to receive(:get_current_state).and_return("state" => "running") + expect(env).to receive(:[]=).with(:machine_state_id, :running) + subject.call(env) + end + + context "with machine state ID as not_created" do + let(:state_id){ :not_created } + + it "should clear the machine ID" do + expect(driver).to receive(:get_current_state).and_return("state" => "not_created") + expect(machine).to receive(:id=).with(nil) + subject.call(env) + end + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/set_name_test.rb b/test/unit/plugins/providers/hyperv/action/set_name_test.rb new file mode 100644 index 000000000..9708140e8 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/set_name_test.rb @@ -0,0 +1,61 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/set_name") + +describe VagrantPlugins::HyperV::Action::SetName do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine, root_path: Pathname.new("path")} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config, data_dir: data_dir, name: "machname") } + let(:data_dir){ double("data_dir") } + let(:provider_config){ double("provider_config", vmname: vmname) } + let(:vmname){ "VMNAME" } + let(:sentinel){ double("sentinel") } + + let(:subject){ described_class.new(app, env) } + + before do + allow(ui).to receive(:info) + allow(driver).to receive(:set_name) + allow(app).to receive(:call) + allow(data_dir).to receive(:join).and_return(sentinel) + allow(sentinel).to receive(:file?).and_return(false) + allow(sentinel).to receive(:open) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should call the driver to set the name" do + expect(driver).to receive(:set_name) + subject.call(env) + end + + it "should use the configured name when setting" do + expect(driver).to receive(:set_name).with(vmname) + subject.call(env) + end + + it "should write sentinel after name is set" do + expect(sentinel).to receive(:open) + subject.call(env) + end + + context "when no name is provided in the config" do + let(:vmname){ nil } + + it "should generate a name based on path and machine" do + expect(driver).to receive(:set_name).with(/^#{env[:root_path].to_s}_#{machine.name}_.+/) + subject.call(env) + end + + it "should not set name if sentinel exists" do + expect(sentinel).to receive(:file?).and_return(true) + subject.call(env) + end + end +end diff --git a/test/unit/plugins/providers/hyperv/action/wait_for_ip_address_test.rb b/test/unit/plugins/providers/hyperv/action/wait_for_ip_address_test.rb new file mode 100644 index 000000000..4d5173236 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/action/wait_for_ip_address_test.rb @@ -0,0 +1,40 @@ +require_relative "../../../../base" + +require Vagrant.source_root.join("plugins/providers/hyperv/action/wait_for_ip_address") + +describe VagrantPlugins::HyperV::Action::WaitForIPAddress do + let(:app){ double("app") } + let(:env){ {ui: ui, machine: machine} } + let(:ui){ double("ui") } + let(:provider){ double("provider", driver: driver) } + let(:driver){ double("driver") } + let(:machine){ double("machine", provider: provider, provider_config: provider_config) } + let(:provider_config){ double("provider_config", ip_address_timeout: ip_address_timeout) } + let(:ip_address_timeout){ 1 } + + let(:subject){ described_class.new(app, env) } + + before do + allow(ui).to receive(:output) + allow(ui).to receive(:detail) + allow(driver).to receive(:read_guest_ip).and_return("ip" => "127.0.0.1") + allow(app).to receive(:call) + end + + it "should call the app on success" do + expect(app).to receive(:call) + subject.call(env) + end + + it "should set a timeout for waiting" do + expect(Timeout).to receive(:timeout).with(ip_address_timeout) + subject.call(env) + end + + it "should retry until it receives a valid address" do + expect(driver).to receive(:read_guest_ip).and_return("ip" => "ADDRESS") + expect(driver).to receive(:read_guest_ip).and_return("ip" => "127.0.0.1") + expect(subject).to receive(:sleep) + subject.call(env) + end +end From c238dc0a3541be82f4ef406068f225efcd83417b Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Fri, 25 May 2018 16:43:17 -0700 Subject: [PATCH 11/18] Include machine name when raising invalid box errors --- plugins/providers/hyperv/action/import.rb | 6 +++--- test/unit/plugins/providers/hyperv/action/import_test.rb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb index bbe19e0c3..25229e3d0 100644 --- a/plugins/providers/hyperv/action/import.rb +++ b/plugins/providers/hyperv/action/import.rb @@ -19,7 +19,7 @@ module VagrantPlugins if !vm_dir.directory? || !hd_dir.directory? @logger.error("Required virtual machine directory not found!") - raise Errors::BoxInvalid + raise Errors::BoxInvalid, name: env[:machine].name end valid_config_ext = [".xml"] @@ -37,7 +37,7 @@ module VagrantPlugins if !config_path @logger.error("Failed to locate box configuration path") - raise Errors::BoxInvalid + raise Errors::BoxInvalid, name: env[:machine].name else @logger.info("Found box configuration path: #{config_path}") end @@ -52,7 +52,7 @@ module VagrantPlugins if !image_path @logger.error("Failed to locate box image path") - raise Errors::BoxInvalid + raise Errors::BoxInvalid, name: env[:machine].name else @logger.info("Found box image path: #{image_path}") end diff --git a/test/unit/plugins/providers/hyperv/action/import_test.rb b/test/unit/plugins/providers/hyperv/action/import_test.rb index 32c68695c..9c66bb0c2 100644 --- a/test/unit/plugins/providers/hyperv/action/import_test.rb +++ b/test/unit/plugins/providers/hyperv/action/import_test.rb @@ -8,7 +8,7 @@ describe VagrantPlugins::HyperV::Action::Import do let(:ui){ double("ui") } let(:provider){ double("provider", driver: driver) } let(:driver){ double("driver") } - let(:machine){ double("machine", provider: provider, provider_config: provider_config, box: box, data_dir: data_dir) } + let(:machine){ double("machine", provider: provider, provider_config: provider_config, box: box, data_dir: data_dir, name: "machname") } let(:provider_config){ double("provider_config", linked_clone: false, From 73c09de1a99772b970be4850cdba3e77d5db1809 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 29 May 2018 10:21:06 -0700 Subject: [PATCH 12/18] Add deprecation warning when `differencing_disk` option is used --- plugins/providers/hyperv/config.rb | 7 +++++++ templates/locales/providers_hyperv.yml | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb index 3a4b2a22c..3403ef682 100644 --- a/plugins/providers/hyperv/config.rb +++ b/plugins/providers/hyperv/config.rb @@ -65,6 +65,9 @@ module VagrantPlugins end def finalize! + if @differencing_disk != UNSET_VALUE + @_differencing_disk_deprecation = true + end @linked_clone = false if @linked_clone == UNSET_VALUE @differencing_disk = false if @differencing_disk == UNSET_VALUE @linked_clone ||= @differencing_disk @@ -92,6 +95,10 @@ module VagrantPlugins def validate(machine) errors = _detected_errors + if @_differencing_disk_deprecation && machine + machine.ui.warn I18n.t("vagrant_hyperv.config.differencing_disk_deprecation") + end + if !vm_integration_services.is_a?(Hash) errors << I18n.t("vagrant_hyperv.config.invalid_integration_services_type", received: vm_integration_services.class) diff --git a/templates/locales/providers_hyperv.yml b/templates/locales/providers_hyperv.yml index a8a002fc3..d4deb6153 100644 --- a/templates/locales/providers_hyperv.yml +++ b/templates/locales/providers_hyperv.yml @@ -35,6 +35,10 @@ en: Received: %{entry_value} Allowed: true, false + differencing_disk_deprecation: |- + The `differencing_disk` configuration option is deprecated and should + no longer be used. The `linked_clone` configuration option should + be used instead. errors: admin_required: |- The Hyper-V provider requires that Vagrant be run with From 12194cba5add08e62db1457e878d65d6fe79f8ec Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 29 May 2018 10:21:52 -0700 Subject: [PATCH 13/18] Update Hyper-V provider configuration documentation --- .../source/docs/hyperv/configuration.html.md | 80 ++++++++++--------- 1 file changed, 42 insertions(+), 38 deletions(-) diff --git a/website/source/docs/hyperv/configuration.html.md b/website/source/docs/hyperv/configuration.html.md index 2fed569e9..224964eb1 100644 --- a/website/source/docs/hyperv/configuration.html.md +++ b/website/source/docs/hyperv/configuration.html.md @@ -12,41 +12,45 @@ description: |- The Vagrant Hyper-V provider has some provider-specific configuration options you may set. A complete reference is shown below: - * `vmname` (string) - Name of virtual machine as shown in Hyper-V manager. - Defaults is taken from box image XML. - * `cpus` (integer) - Number of virtual CPU given to machine. - Defaults is taken from box image XML. - * `memory` (integer) - Number of MegaBytes allocated to VM at startup. - Defaults is taken from box image XML. - * `maxmemory` (integer) - Number of MegaBytes maximal allowed to allocate for VM - This parameter is switch on Dynamic Allocation of memory. - Defaults is taken from box image XML. - * `vlan_id` (integer) - Number of Vlan ID for your guest network interface - Defaults is not defined, vlan configuration will be untouched if not set. - * `mac` (string) - MAC address for your guest network interface - Default is not defined, MAC address will be dynamically assigned by Hyper-V if not set. - * `ip_address_timeout` (integer) - The time in seconds to wait for the - virtual machine to report an IP address. This defaults to 120 seconds. - This may have to be increased if your VM takes longer to boot. - * `differencing_disk` (boolean) - Switch to use differencing disk instead of cloning whole VHD. - * `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs. - This allows Hyper-V to be nested and run inside another Hyper-VM VM. It requires Windows 10 - 1511 (build 10586) or newer. - Default is not defined. This will be disabled if not set. - * `auto_start_action` (Nothing, StartIfRunning, Start) - Action on automatic start of VM when booting OS - * `auto_stop_action` (ShutDown, TurnOff, Save) - Action on automatic stop of VM when shutting down OS. - * `vm_integration_services` (Hash) - Hash to set the state of integration services. - - Example: - - ```ruby - config.vm.provider "hyperv" do |h| - h.vm_integration_services = { - guest_service_interface: true, - heartbeat: true, - key_value_pair_exchange: true, - shutdown: true, - time_synchronization: true, - vss: true - } - end - ``` \ No newline at end of file +* `auto_start_action` (Nothing, StartIfRunning, Start) - Automatic start action for VM on host startup. Default: Nothing. +* `auto_stop_action` (ShutDown, TurnOff, Save) - Automatic stop action for VM on host shutdown. Default: ShutDown. +* `cpus` (integer) - Number of virtual CPUs allocated to VM at startup. +* `differencing_disk` (boolean) - **Deprecated** Use differencing disk instead of cloning entire VHD (use `linked_clone` instead) Default: false. +* `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs. Default: false +* `enable_checkpoints` (boolean) Enable automatic checkpoints of the VM. Default: false +* `ip_address_timeout` (integer) - Number of seconds to wait for the VM to report an IP address. Default: 120. +* `linked_clone` (boolean) - **Deprecated** Use differencing disk instead of cloning entire VHD. Default: false +* `mac` (string) - MAC address for the guest network interface +* `maxmemory` (integer) - Maximum number of megabytes allowed to be allocated for the VM. When set Dynamic Memory Allocation will be enabled. +* `memory` (integer) - Number of megabytes allocated to VM at startup. If `maxmemory` is set, this will be amount of memory allocated at startup. +* `vlan_id` (integer) - VLAN ID for the guest network interface. +* `vmname` (string) - Name of virtual machine as shown in Hyper-V manager. Default: Generated name. +* `vm_integration_services` (Hash) - Hash to set the state of integration services. (Note: Unknown key values will be passed directly.) + * `guest_service_interface` (boolean) + * `heartbeat` (boolean) + * `key_value_pair_exchange` (boolean) + * `shutdown` (boolean) + * `time_synchronization` (boolean) + * `vss` (boolean) + +## VM Integration Services + +The `vm_integration_services` configuration option consists of a simple Hash. The key values are the +names of VM integration services to enable or disable for the VM. Vagrant includes an internal +mapping of known services which allows them to be provided in a "snake case" format. When a provided +key is unknown, the key value is used "as-is" without any modifications. + +For example, if a new `CustomVMSRV` VM integration service was added and Vagrant is not aware of this +new service name, it can be provided as the key value explicitly: + +```ruby +config.vm.provider "hyperv" do |h| + h.vm_integration_services = { + guest_service_interface: true, + CustomVMSRV: true + } +end +``` + +This example would enable the `GuestServiceInterface` (which Vagrant is aware) and `CustomVMSRV` (which +Vagrant is _not_ aware) VM integration services. From 1becae50a5f680934d9a519d4ab43ca6f9534778 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 29 May 2018 10:25:48 -0700 Subject: [PATCH 14/18] Include test coverage on provider option deprecation --- test/unit/plugins/providers/hyperv/config_test.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/unit/plugins/providers/hyperv/config_test.rb b/test/unit/plugins/providers/hyperv/config_test.rb index efce526be..23328cf3b 100644 --- a/test/unit/plugins/providers/hyperv/config_test.rb +++ b/test/unit/plugins/providers/hyperv/config_test.rb @@ -4,7 +4,8 @@ require Vagrant.source_root.join("plugins/providers/hyperv/config") describe VagrantPlugins::HyperV::Config do - let(:machine){ double("machine") } + let(:machine){ double("machine", ui: ui) } + let(:ui){ double("ui") } describe "#ip_address_timeout" do it "can be set" do @@ -92,6 +93,13 @@ describe VagrantPlugins::HyperV::Config do expect(subject.differencing_disk).to eq(true) expect(subject.linked_clone).to eq(true) end + + it "should provide a deprecation warning when set" do + expect(ui).to receive(:warn) + subject.differencing_disk = true + subject.finalize! + subject.validate(machine) + end end describe "#linked_clone" do From 4e09a15acd1fa329539a7e2728337e2d4b1d78d4 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Tue, 29 May 2018 11:43:13 -0700 Subject: [PATCH 15/18] Remove deprecated warning from non-deprecated option --- website/source/docs/hyperv/configuration.html.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/source/docs/hyperv/configuration.html.md b/website/source/docs/hyperv/configuration.html.md index 224964eb1..178fadf10 100644 --- a/website/source/docs/hyperv/configuration.html.md +++ b/website/source/docs/hyperv/configuration.html.md @@ -19,7 +19,7 @@ you may set. A complete reference is shown below: * `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs. Default: false * `enable_checkpoints` (boolean) Enable automatic checkpoints of the VM. Default: false * `ip_address_timeout` (integer) - Number of seconds to wait for the VM to report an IP address. Default: 120. -* `linked_clone` (boolean) - **Deprecated** Use differencing disk instead of cloning entire VHD. Default: false +* `linked_clone` (boolean) - Use differencing disk instead of cloning entire VHD. Default: false * `mac` (string) - MAC address for the guest network interface * `maxmemory` (integer) - Maximum number of megabytes allowed to be allocated for the VM. When set Dynamic Memory Allocation will be enabled. * `memory` (integer) - Number of megabytes allocated to VM at startup. If `maxmemory` is set, this will be amount of memory allocated at startup. From 5f4e661155730f0be708cdabb4e70b76005c456d Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 30 May 2018 15:04:59 -0700 Subject: [PATCH 16/18] Add automatic console resize and environment support for powershell commands --- lib/vagrant/util/powershell.rb | 49 +++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/lib/vagrant/util/powershell.rb b/lib/vagrant/util/powershell.rb index 9afb4da65..8e48aad1d 100644 --- a/lib/vagrant/util/powershell.rb +++ b/lib/vagrant/util/powershell.rb @@ -20,12 +20,16 @@ module Vagrant if !defined?(@_powershell_executable) @_powershell_executable = "powershell" - # Try to use WSL interoperability if PowerShell is not symlinked to - # the container. - if Which.which(@_powershell_executable).nil? && Platform.wsl? - @_powershell_executable += ".exe" + if Which.which(@_powershell_executable).nil? + # Try to use WSL interoperability if PowerShell is not symlinked to + # the container. + if Platform.wsl? + @_powershell_executable += ".exe" - if Which.which(@_powershell_executable).nil? + if Which.which(@_powershell_executable).nil? + @_powershell_executable = nil + end + else @_powershell_executable = nil end end @@ -41,6 +45,9 @@ module Vagrant # Execute a powershell script. # # @param [String] path Path to the PowerShell script to execute. + # @param [Array] args Command arguments + # @param [Hash] opts Options passed to execute + # @option opts [Hash] :env Custom environment variables # @return [Subprocess::Result] def self.execute(path, *args, **opts, &block) validate_install! @@ -57,7 +64,7 @@ module Vagrant "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", - "#{env}&('#{path}')", + "#{resize_console}#{env}&('#{path}')", args ].flatten @@ -72,10 +79,16 @@ module Vagrant # Execute a powershell command. # # @param [String] command PowerShell command to execute. + # @param [Hash] opts Extra options + # @option opts [Hash] :env Custom environment variables # @return [nil, String] Returns nil if exit code is non-zero. # Returns stdout string if exit code is zero. - def self.execute_cmd(command) + def self.execute_cmd(command, **opts) validate_install! + env = opts.delete(:env) + if env + env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " + end c = [ executable, "-NoLogo", @@ -83,7 +96,7 @@ module Vagrant "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", - command + "#{resize_console}#{env}#{command}" ].flatten.compact r = Subprocess.execute(*c) @@ -95,9 +108,14 @@ module Vagrant # # @param [String] command PowerShell command to execute. # @param [Hash] opts A collection of options for subprocess::execute + # @option opts [Hash] :env Custom environment variables # @param [Block] block Ruby block def self.execute_inline(*command, **opts, &block) validate_install! + env = opts.delete(:env) + if env + env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; " + end c = [ executable, "-NoLogo", @@ -105,7 +123,7 @@ module Vagrant "-NonInteractive", "-ExecutionPolicy", "Bypass", "-Command", - command + "#{resize_console}#{env}#{command}" ].flatten.compact c << opts @@ -224,6 +242,19 @@ module Vagrant def self.reset! instance_variables.each(&method(:remove_instance_variable)) end + + # @private + # This is a helper method that provides the PowerShell command to resize + # the "console" to prevent output wrapping or truncating. An environment + # variable guard is provided to disable the behavior in cases where it + # may cause unexpected results (VAGRANT_POWERSHELL_RESIZE_DISABLE) + def self.resize_console + if ENV["VAGRANT_POWERSHELL_RESIZE_DISABLE"] + "" + else + "$host.UI.RawUI.BufferSize = New-Object System.Management.Automation.Host.Size(512,50); " + end + end end end end From 1be87662da5acce325382d773f58185248fa58aa Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 30 May 2018 15:05:27 -0700 Subject: [PATCH 17/18] Add missing test coverage for powershell util methods --- test/unit/vagrant/util/powershell_test.rb | 246 +++++++++++++++++++++- 1 file changed, 245 insertions(+), 1 deletion(-) diff --git a/test/unit/vagrant/util/powershell_test.rb b/test/unit/vagrant/util/powershell_test.rb index d5760fc28..f60626eb7 100644 --- a/test/unit/vagrant/util/powershell_test.rb +++ b/test/unit/vagrant/util/powershell_test.rb @@ -4,6 +4,9 @@ require 'vagrant/util/powershell' describe Vagrant::Util::PowerShell do include_context "unit" + + after{ described_class.reset! } + describe ".version" do before do allow(described_class).to receive(:executable) @@ -13,7 +16,6 @@ describe Vagrant::Util::PowerShell do after do described_class.version - described_class.reset! end it "should execute powershell command" do @@ -41,4 +43,246 @@ describe Vagrant::Util::PowerShell do end end end + + describe ".executable" do + before{ allow(Vagrant::Util::Platform).to receive(:wsl?).and_return(false) } + + context "when found in PATH" do + before{ expect(Vagrant::Util::Which).to receive(:which).with("powershell").and_return(true) } + + it "should return powershell string" do + expect(described_class.executable).to eq("powershell") + end + end + + context "when not found in PATH" do + before{ expect(Vagrant::Util::Which).to receive(:which).with("powershell").and_return(nil) } + + it "should return nil" do + expect(described_class.executable).to be_nil + end + + context "when within WSL" do + before{ expect(Vagrant::Util::Platform).to receive(:wsl?).and_return(true) } + + it "should check PATH with .exe extension" do + expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe") + described_class.executable + end + + it "should return powershell.exe when found" do + expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe").and_return(true) + expect(described_class.executable).to eq("powershell.exe") + end + + it "should return nil when not found" do + expect(Vagrant::Util::Which).to receive(:which).with("powershell.exe").and_return(nil) + expect(described_class.executable).to be_nil + end + end + end + end + + describe ".available?" do + context "when powershell executable is available" do + before{ expect(described_class).to receive(:executable).and_return("powershell") } + + it "should be true" do + expect(described_class.available?).to be(true) + end + end + + context "when powershell executable is not available" do + before{ expect(described_class).to receive(:executable).and_return(nil) } + + it "should be false" do + expect(described_class.available?).to be(false) + end + end + end + + describe ".execute" do + before do + allow(described_class).to receive(:validate_install!) + allow(Vagrant::Util::Subprocess).to receive(:execute) + end + + it "should validate installation before use" do + expect(described_class).to receive(:validate_install!) + described_class.execute("command") + end + + it "should include command to execute" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("custom-command") + end + described_class.execute("custom-command") + end + + it "should automatically include console resize" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("BufferSize") + end + described_class.execute("custom-command") + end + + it "should accept custom environment" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("$env:TEST_KEY=test-value") + end + described_class.execute("custom-command", env: {"TEST_KEY" => "test-value"}) + end + end + + describe ".execute_cmd" do + let(:result) do + Vagrant::Util::Subprocess::Result.new( + exit_code, stdout, stderr) + end + let(:exit_code){ 0 } + let(:stdout){ "" } + let(:stderr){ "" } + + before do + allow(described_class).to receive(:validate_install!) + allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result) + end + + it "should validate installation before use" do + expect(described_class).to receive(:validate_install!) + described_class.execute_cmd("command") + end + + it "should include command to execute" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("custom-command") + result + end + described_class.execute_cmd("custom-command") + end + + it "should automatically include console resize" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("BufferSize") + result + end + described_class.execute_cmd("custom-command") + end + + it "should accept custom environment" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("$env:TEST_KEY=test-value") + result + end + described_class.execute_cmd("custom-command", env: {"TEST_KEY" => "test-value"}) + end + + context "with command output" do + let(:stdout){ "custom-output" } + + it "should return stdout" do + expect(described_class.execute_cmd("cmd")).to eq(stdout) + end + end + + context "with failed command" do + let(:exit_code){ 1 } + + it "should return nil" do + expect(described_class.execute_cmd("cmd")).to be_nil + end + end + end + + describe ".execute_inline" do + let(:result) do + Vagrant::Util::Subprocess::Result.new( + exit_code, stdout, stderr) + end + let(:exit_code){ 0 } + let(:stdout){ "" } + let(:stderr){ "" } + + before do + allow(described_class).to receive(:validate_install!) + allow(Vagrant::Util::Subprocess).to receive(:execute).and_return(result) + end + + it "should validate installation before use" do + expect(described_class).to receive(:validate_install!) + described_class.execute_inline("command") + end + + it "should include command to execute" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("custom-command") + result + end + described_class.execute_inline("custom-command") + end + + it "should automatically include console resize" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("BufferSize") + result + end + described_class.execute_inline("custom-command") + end + + it "should accept custom environment" do + expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| + comm = args.detect{|s| s.include?("custom-command") } + expect(comm.to_s).to include("$env:TEST_KEY=test-value") + result + end + described_class.execute_inline("custom-command", env: {"TEST_KEY" => "test-value"}) + end + + it "should return a result instance" do + expect(described_class.execute_inline("cmd")).to eq(result) + end + end + + describe ".validate_install!" do + before do + allow(described_class).to receive(:available?).and_return(true) + end + + context "with version under minimum required" do + before{ expect(described_class).to receive(:version).and_return("2.1").at_least(:once) } + + it "should raise an error" do + expect{ described_class.validate_install! }.to raise_error(Vagrant::Errors::PowerShellInvalidVersion) + end + end + + context "with version above minimum required" do + before{ expect(described_class).to receive(:version).and_return("3.1").at_least(:once) } + + it "should return true" do + expect(described_class.validate_install!).to be(true) + end + end + + end + + describe ".resize_console" do + it "should return command string" do + expect(described_class.resize_console).to include("BufferSize") + end + + it "should return empty string when disabled" do + with_temp_env("VAGRANT_POWERSHELL_RESIZE_DISABLE" => "1") do + expect(described_class.resize_console).to be_empty + end + end + end end From 5e68c896cf9e31586878b6e53dce92a86c2d7884 Mon Sep 17 00:00:00 2001 From: Chris Roberts Date: Wed, 30 May 2018 15:42:04 -0700 Subject: [PATCH 18/18] Force types for testing --- test/unit/vagrant/util/powershell_test.rb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test/unit/vagrant/util/powershell_test.rb b/test/unit/vagrant/util/powershell_test.rb index f60626eb7..ce30bd4d1 100644 --- a/test/unit/vagrant/util/powershell_test.rb +++ b/test/unit/vagrant/util/powershell_test.rb @@ -114,7 +114,7 @@ describe Vagrant::Util::PowerShell do it "should include command to execute" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("custom-command") end described_class.execute("custom-command") @@ -122,7 +122,7 @@ describe Vagrant::Util::PowerShell do it "should automatically include console resize" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("BufferSize") end described_class.execute("custom-command") @@ -130,7 +130,7 @@ describe Vagrant::Util::PowerShell do it "should accept custom environment" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("$env:TEST_KEY=test-value") end described_class.execute("custom-command", env: {"TEST_KEY" => "test-value"}) @@ -158,7 +158,7 @@ describe Vagrant::Util::PowerShell do it "should include command to execute" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("custom-command") result end @@ -167,7 +167,7 @@ describe Vagrant::Util::PowerShell do it "should automatically include console resize" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("BufferSize") result end @@ -176,7 +176,7 @@ describe Vagrant::Util::PowerShell do it "should accept custom environment" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("$env:TEST_KEY=test-value") result end @@ -221,7 +221,7 @@ describe Vagrant::Util::PowerShell do it "should include command to execute" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("custom-command") result end @@ -230,7 +230,7 @@ describe Vagrant::Util::PowerShell do it "should automatically include console resize" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("BufferSize") result end @@ -239,7 +239,7 @@ describe Vagrant::Util::PowerShell do it "should accept custom environment" do expect(Vagrant::Util::Subprocess).to receive(:execute) do |*args| - comm = args.detect{|s| s.include?("custom-command") } + comm = args.detect{|s| s.to_s.include?("custom-command") } expect(comm.to_s).to include("$env:TEST_KEY=test-value") result end