Merge pull request #9872 from chrisroberts/e-hyperv-2

Hyper-V provider overhaul
This commit is contained in:
Chris Roberts 2018-06-04 16:51:12 -07:00 committed by GitHub
commit 120fa07a8d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
56 changed files with 2888 additions and 962 deletions

View File

@ -20,14 +20,18 @@ module Vagrant
if !defined?(@_powershell_executable)
@_powershell_executable = "powershell"
if Which.which(@_powershell_executable).nil?
# Try to use WSL interoperability if PowerShell is not symlinked to
# the container.
if Which.which(@_powershell_executable).nil? && Platform.wsl?
if Platform.wsl?
@_powershell_executable += ".exe"
if Which.which(@_powershell_executable).nil?
@_powershell_executable = nil
end
else
@_powershell_executable = nil
end
end
end
@_powershell_executable
@ -41,19 +45,26 @@ module Vagrant
# Execute a powershell script.
#
# @param [String] path Path to the PowerShell script to execute.
# @param [Array<String>] 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!
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}')",
"#{resize_console}#{env}&('#{path}')",
args
].flatten
@ -68,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",
@ -79,7 +96,7 @@ module Vagrant
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
command
"#{resize_console}#{env}#{command}"
].flatten.compact
r = Subprocess.execute(*c)
@ -91,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",
@ -101,7 +123,7 @@ module Vagrant
"-NonInteractive",
"-ExecutionPolicy", "Bypass",
"-Command",
command
"#{resize_console}#{env}#{command}"
].flatten.compact
c << opts
@ -220,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

View File

@ -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

View File

@ -0,0 +1,104 @@
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].to_s.downcase ||
s["Id"].downcase == opts[:bridge].to_s.downcase
}
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
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.empty?
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

View File

@ -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?
raise Errors::BoxInvalid
@logger.error("Required virtual machine directory not found!")
raise Errors::BoxInvalid, name: env[:machine].name
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, name: env[:machine].name
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
raise Errors::BoxInvalid
if !image_path
@logger.error("Failed to locate box image path")
raise Errors::BoxInvalid, name: env[:machine].name
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

View File

@ -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

View File

@ -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,21 +55,23 @@ 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
@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
}
@enable_checkpoints = UNSET_VALUE
@vm_integration_services = {}
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
@differencing_disk ||= @linked_clone
if @ip_address_timeout == UNSET_VALUE
@ip_address_timeout = 120
end
@ -48,20 +81,46 @@ 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
end
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)
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,
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

View File

@ -10,20 +10,42 @@ 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")
r.stderr.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<state, status>]
def get_current_state
execute('get_vm_status.ps1', { VmId: vm_id })
execute(:get_vm_status, VmId: vm_id)
end
# Delete the VM
#
# @return [nil]
def delete_vm
execute('delete_vm.ps1', { VmId: vm_id })
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
# Get the IP address of the VM
#
# @return [Hash<ip>]
def read_guest_ip
execute('get_network_config.ps1', { VmId: vm_id })
execute(:get_network_config, VmId: vm_id)
end
# Get the MAC address of the VM
#
# @return [Hash<mac>]
def read_mac_address
execute('get_network_mac.ps1', { VmId: vm_id })
execute(:get_network_mac, VmId: vm_id)
end
# Resume the VM from suspension
#
# @return [nil]
def resume
execute('resume_vm.ps1', { VmId: vm_id })
execute(:resume_vm, VmId: vm_id)
end
# Start the VM
#
# @return [nil]
def start
execute('start_vm.ps1', { VmId: vm_id })
execute(:start_vm, VmId: vm_id )
end
# Stop the VM
#
# @return [nil]
def stop
execute('stop_vm.ps1', { VmId: vm_id })
execute(:stop_vm, VmId: vm_id)
end
# Suspend the VM
#
# @return [nil]
def suspend
execute("suspend_vm.ps1", { VmId: vm_id })
execute(:suspend_vm, VmId: vm_id)
end
# Import a new VM
#
# @param [Hash] options Configuration options
# @return [Hash<id>] New VM ID
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
execute(:import_vm, options)
end
# Set the VLAN ID
#
# @param [String] vlan_id VLAN ID
# @return [nil]
def net_set_vlan(vlan_id)
execute("set_network_vlan.ps1", { VmId: vm_id, VlanId: vlan_id })
execute(:set_network_vlan, VmId: vm_id, VlanId: vlan_id)
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.ps1", { 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
#
# @param [String] snapshot_name Name of the new snapshot
# @return [nil]
def create_snapshot(snapshot_name)
execute("create_snapshot.ps1", { VmId: vm_id, SnapName: (snapshot_name) } )
execute(:create_snapshot, 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.ps1", { VmId: vm_id, SnapName: (snapshot_name) } )
execute(:restore_snapshot, VmId: vm_id, SnapName: snapshot_name)
end
def list_snapshots()
snaps = execute("list_snapshots.ps1", { VmID: vm_id } )
# Get list of current snapshots
#
# @return [Array<String>] snapshot names
def list_snapshots
snaps = execute(:list_snapshots, VmID: vm_id)
snaps.map { |s| s['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.ps1", {VmID: vm_id, SnapName: 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).to_s}
args[:Enable] = 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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

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

View File

@ -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
}

View File

@ -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
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
}

View File

@ -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

View File

@ -1,15 +1,22 @@
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) {
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)) {
@ -19,11 +26,11 @@ foreach ($network in $networks) {
}
}
}
}
}
# 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))) {
# 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}-'
@ -37,20 +44,25 @@ if (([string]::IsNullOrEmpty($ip4_address)) -And ([string]::IsNullOrEmpty($ip6_a
}
}
}
}
}
if (-Not ([string]::IsNullOrEmpty($ip4_address))) {
if (-Not ([string]::IsNullOrEmpty($ip4_address))) {
$guest_ipaddress = $ip4_address
} elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) {
} elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) {
$guest_ipaddress = $ip6_address
}
}
if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) {
if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) {
$resultHash = @{
ip = $guest_ipaddress
}
$result = ConvertTo-Json $resultHash
Write-Output-Message $result
} else {
} else {
Write-Error-Message "Failed to determine IP address"
exit 1
}
} catch {
Write-Error-Message "Unexpected error while detecting network configuration: ${PSItem}"
exit 1
}

View File

@ -1,16 +1,17 @@
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) {
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))) {
@ -18,11 +19,14 @@ foreach ($network in $networks) {
break
}
}
}
}
$resultHash = @{
$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

View File

@ -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)

View File

@ -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
{

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -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
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
}

View File

@ -1,16 +1,15 @@
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"
$vm = Hyper-V\Get-VM -Id $VmId
Hyper-V\Start-VM $vm
$state = $vm.state
$status = $vm.status
$name = $vm.name
@ -21,7 +20,7 @@ try {
}
$result = ConvertTo-Json $resultHash
Write-Output-Message $result
}
catch {
Write-Error-Message "Failed to start a VM $_"
} catch {
Write-Error-Message "Failed to start VM ${PSItem}"
exit 1
}

View File

@ -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
}

View File

@ -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
}

View File

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

View File

@ -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.
#>
}

View File

@ -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
}
}

View File

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

View File

@ -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
}

View File

@ -1 +0,0 @@
Write-Output $PSVersionTable.PSVersion.Major

View File

@ -11,6 +11,34 @@ 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
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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, name: "machname") }
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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -3,6 +3,10 @@ require_relative "../../../base"
require Vagrant.source_root.join("plugins/providers/hyperv/config")
describe VagrantPlugins::HyperV::Config do
let(:machine){ double("machine", ui: ui) }
let(:ui){ double("ui") }
describe "#ip_address_timeout" do
it "can be set" do
subject.ip_address_timeout = 180
@ -62,4 +66,159 @@ 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
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
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

View File

@ -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

View File

@ -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.to_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.to_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.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"})
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.to_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.to_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.to_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.to_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.to_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.to_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

View File

@ -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.
* `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) - 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)
Example:
## VM Integration Services
```ruby
config.vm.provider "hyperv" do |h|
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,
heartbeat: true,
key_value_pair_exchange: true,
shutdown: true,
time_synchronization: true,
vss: true
CustomVMSRV: true
}
end
```
end
```
This example would enable the `GuestServiceInterface` (which Vagrant is aware) and `CustomVMSRV` (which
Vagrant is _not_ aware) VM integration services.