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===" -}