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,12 +20,16 @@ module Vagrant
if !defined?(@_powershell_executable) if !defined?(@_powershell_executable)
@_powershell_executable = "powershell" @_powershell_executable = "powershell"
# Try to use WSL interoperability if PowerShell is not symlinked to if Which.which(@_powershell_executable).nil?
# the container. # Try to use WSL interoperability if PowerShell is not symlinked to
if Which.which(@_powershell_executable).nil? && Platform.wsl? # the container.
@_powershell_executable += ".exe" if Platform.wsl?
@_powershell_executable += ".exe"
if Which.which(@_powershell_executable).nil? if Which.which(@_powershell_executable).nil?
@_powershell_executable = nil
end
else
@_powershell_executable = nil @_powershell_executable = nil
end end
end end
@ -41,19 +45,26 @@ module Vagrant
# Execute a powershell script. # Execute a powershell script.
# #
# @param [String] path Path to the PowerShell script to execute. # @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] # @return [Subprocess::Result]
def self.execute(path, *args, **opts, &block) def self.execute(path, *args, **opts, &block)
validate_install! validate_install!
if opts.delete(:sudo) || opts.delete(:runas) if opts.delete(:sudo) || opts.delete(:runas)
powerup_command(path, args, opts) powerup_command(path, args, opts)
else else
env = opts.delete(:env)
if env
env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; "
end
command = [ command = [
executable, executable,
"-NoLogo", "-NoLogo",
"-NoProfile", "-NoProfile",
"-NonInteractive", "-NonInteractive",
"-ExecutionPolicy", "Bypass", "-ExecutionPolicy", "Bypass",
"&('#{path}')", "#{resize_console}#{env}&('#{path}')",
args args
].flatten ].flatten
@ -68,10 +79,16 @@ module Vagrant
# Execute a powershell command. # Execute a powershell command.
# #
# @param [String] command PowerShell command to execute. # @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. # @return [nil, String] Returns nil if exit code is non-zero.
# Returns stdout string if exit code is zero. # Returns stdout string if exit code is zero.
def self.execute_cmd(command) def self.execute_cmd(command, **opts)
validate_install! validate_install!
env = opts.delete(:env)
if env
env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; "
end
c = [ c = [
executable, executable,
"-NoLogo", "-NoLogo",
@ -79,7 +96,7 @@ module Vagrant
"-NonInteractive", "-NonInteractive",
"-ExecutionPolicy", "Bypass", "-ExecutionPolicy", "Bypass",
"-Command", "-Command",
command "#{resize_console}#{env}#{command}"
].flatten.compact ].flatten.compact
r = Subprocess.execute(*c) r = Subprocess.execute(*c)
@ -91,9 +108,14 @@ module Vagrant
# #
# @param [String] command PowerShell command to execute. # @param [String] command PowerShell command to execute.
# @param [Hash] opts A collection of options for subprocess::execute # @param [Hash] opts A collection of options for subprocess::execute
# @option opts [Hash] :env Custom environment variables
# @param [Block] block Ruby block # @param [Block] block Ruby block
def self.execute_inline(*command, **opts, &block) def self.execute_inline(*command, **opts, &block)
validate_install! validate_install!
env = opts.delete(:env)
if env
env = env.map{|k,v| "$env:#{k}=#{v}"}.join(";") + "; "
end
c = [ c = [
executable, executable,
"-NoLogo", "-NoLogo",
@ -101,7 +123,7 @@ module Vagrant
"-NonInteractive", "-NonInteractive",
"-ExecutionPolicy", "Bypass", "-ExecutionPolicy", "Bypass",
"-Command", "-Command",
command "#{resize_console}#{env}#{command}"
].flatten.compact ].flatten.compact
c << opts c << opts
@ -220,6 +242,19 @@ module Vagrant
def self.reset! def self.reset!
instance_variables.each(&method(:remove_instance_variable)) instance_variables.each(&method(:remove_instance_variable))
end 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 end
end end

View File

@ -140,6 +140,8 @@ module VagrantPlugins
end end
b2.use Provision b2.use Provision
b2.use Configure
b2.use SetName
b2.use NetSetVLan b2.use NetSetVLan
b2.use NetSetMac b2.use NetSetMac
b2.use StartInstance b2.use StartInstance
@ -288,6 +290,7 @@ module VagrantPlugins
autoload :Export, action_root.join("export") autoload :Export, action_root.join("export")
autoload :CheckEnabled, action_root.join("check_enabled") autoload :CheckEnabled, action_root.join("check_enabled")
autoload :Configure, action_root.join("configure")
autoload :DeleteVM, action_root.join("delete_vm") autoload :DeleteVM, action_root.join("delete_vm")
autoload :Import, action_root.join("import") autoload :Import, action_root.join("import")
autoload :Package, action_root.join("package") autoload :Package, action_root.join("package")
@ -304,6 +307,7 @@ module VagrantPlugins
autoload :SnapshotDelete, action_root.join("snapshot_delete") autoload :SnapshotDelete, action_root.join("snapshot_delete")
autoload :SnapshotRestore, action_root.join("snapshot_restore") autoload :SnapshotRestore, action_root.join("snapshot_restore")
autoload :SnapshotSave, action_root.join("snapshot_save") autoload :SnapshotSave, action_root.join("snapshot_save")
autoload :SetName, action_root.join("set_name")
end end
end 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 "fileutils"
require "log4r" require "log4r"
module VagrantPlugins module VagrantPlugins
module HyperV module HyperV
module Action module Action
class Import class Import
VALID_HD_EXTENSIONS = [".vhd".freeze, ".vhdx".freeze].freeze
def initialize(app, env) def initialize(app, env)
@app = app @app = app
@logger = Log4r::Logger.new("vagrant::hyperv::import") @logger = Log4r::Logger.new("vagrant::hyperv::import")
@ -14,160 +16,64 @@ module VagrantPlugins
def call(env) def call(env)
vm_dir = env[:machine].box.directory.join("Virtual Machines") vm_dir = env[:machine].box.directory.join("Virtual Machines")
hd_dir = env[:machine].box.directory.join("Virtual Hard Disks") 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? 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 end
config_path = nil config_path = nil
config_type = nil vm_dir.each_child do |file|
vm_dir.each_child do |f| if valid_config_ext.include?(file.extname.downcase)
if f.extname.downcase == '.xml' config_path = file
@logger.debug("Found XML config...")
config_path = f
config_type = 'xml'
break break
end end
end end
vmcx_support = env[:machine].provider.driver.execute("has_vmcx_support.ps1", {})['result'] if !config_path
if vmcx_support @logger.error("Failed to locate box configuration path")
vm_dir.each_child do |f| raise Errors::BoxInvalid, name: env[:machine].name
if f.extname.downcase == '.vmcx' else
@logger.debug("Found VMCX config and support...") @logger.info("Found box configuration path: #{config_path}")
config_path = f
config_type = 'vmcx'
break
end
end
end end
image_path = nil image_path = nil
image_ext = nil hd_dir.each_child do |file|
image_filename = nil if VALID_HD_EXTENSIONS.include?(file.extname.downcase)
hd_dir.each_child do |f| image_path = file
if %w{.vhd .vhdx}.include?(f.extname.downcase)
image_path = f
image_ext = f.extname.downcase
image_filename = File.basename(f, image_ext)
break break
end end
end end
if !config_path || !image_path if !image_path
raise Errors::BoxInvalid @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 end
env[:ui].output("Importing a Hyper-V instance") 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 = { options = {
vm_config_file: config_path.to_s.gsub("/", "\\"), "VMConfigFile" => config_path.to_s.gsub("/", "\\"),
vm_config_type: config_type, "DestinationPath" => dest_path.to_s.gsub("/", "\\"),
source_path: source_path.to_s, "DataPath" => env[:machine].data_dir.to_s.gsub("/", "\\"),
dest_path: dest_path, "LinkedClone" => !!env[:machine].provider_config.linked_clone,
data_path: env[:machine].data_dir.to_s.gsub("/", "\\") "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...") env[:ui].detail("Creating and registering the VM...")
server = env[:machine].provider.driver.import(options) server = env[:machine].provider.driver.import(options)
env[:ui].detail("Setting VM Integration Services") env[:ui].detail("Successfully imported VM")
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[:machine].id = server["id"] env[:machine].id = server["id"]
@app.call(env) @app.call(env)
end 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 VagrantPlugins
module HyperV module HyperV
class Config < Vagrant.plugin("2", :config) class Config < Vagrant.plugin("2", :config)
attr_accessor :ip_address_timeout # Time to wait for an IP address when booting, in seconds @return [Integer] # Allowed automatic start actions for VM
attr_accessor :memory # Memory size in mb @return [Integer] ALLOWED_AUTO_START_ACTIONS = [
attr_accessor :maxmemory # Maximal memory size in mb enables dynamical memory allocation @return [Integer] "Nothing".freeze,
attr_accessor :cpus # Number of cpu's @return [Integer] "StartIfRunning".freeze,
attr_accessor :vmname # Name that will be shoen in Hyperv Manager @return [String] "Start".freeze
attr_accessor :vlan_id # VLAN ID for network interface for the virtual machine. @return [Integer] ].freeze
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] # Allowed automatic stop actions for VM
attr_accessor :auto_start_action #action on automatic start of VM. Values: Nothing, StartIfRunning, Start ALLOWED_AUTO_STOP_ACTIONS = [
attr_accessor :auto_stop_action #action on automatic stop of VM. Values: ShutDown, TurnOff, Save "ShutDown".freeze,
attr_accessor :enable_virtualization_extensions # Enable virtualization extensions (nested virtualization). Values: true, false "TurnOff".freeze,
attr_accessor :vm_integration_services # Options for VMServiceIntegration [Hash] "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 def initialize
@ip_address_timeout = UNSET_VALUE @ip_address_timeout = UNSET_VALUE
@ -24,21 +55,23 @@ module VagrantPlugins
@vmname = UNSET_VALUE @vmname = UNSET_VALUE
@vlan_id = UNSET_VALUE @vlan_id = UNSET_VALUE
@mac = UNSET_VALUE @mac = UNSET_VALUE
@linked_clone = UNSET_VALUE
@differencing_disk = UNSET_VALUE @differencing_disk = UNSET_VALUE
@auto_start_action = UNSET_VALUE @auto_start_action = UNSET_VALUE
@auto_stop_action = UNSET_VALUE @auto_stop_action = UNSET_VALUE
@enable_virtualization_extensions = UNSET_VALUE @enable_virtualization_extensions = UNSET_VALUE
@vm_integration_services = { @enable_checkpoints = UNSET_VALUE
guest_service_interface: UNSET_VALUE, @vm_integration_services = {}
heartbeat: UNSET_VALUE,
key_value_pair_exchange: UNSET_VALUE,
shutdown: UNSET_VALUE,
time_synchronization: UNSET_VALUE,
vss: UNSET_VALUE
}
end end
def finalize! 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 if @ip_address_timeout == UNSET_VALUE
@ip_address_timeout = 120 @ip_address_timeout = 120
end end
@ -48,20 +81,46 @@ module VagrantPlugins
@vmname = nil if @vmname == UNSET_VALUE @vmname = nil if @vmname == UNSET_VALUE
@vlan_id = nil if @vlan_id == UNSET_VALUE @vlan_id = nil if @vlan_id == UNSET_VALUE
@mac = nil if @mac == 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| @auto_start_action = "Nothing" if @auto_start_action == UNSET_VALUE
@vm_integration_services[key] = nil if value == UNSET_VALUE @auto_stop_action = "ShutDown" if @auto_stop_action == UNSET_VALUE
} @enable_virtualization_extensions = false if @enable_virtualization_extensions == UNSET_VALUE
@vm_integration_services = nil if @vm_integration_services.length == 0 if @enable_checkpoints == UNSET_VALUE
@enable_checkpoints = false
else
@enable_checkpoints = !!@enable_checkpoints
end
end end
def validate(machine) def validate(machine)
errors = _detected_errors 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} {"Hyper-V" => errors}
end end
end end

View File

@ -10,19 +10,41 @@ module VagrantPlugins
ERROR_REGEXP = /===Begin-Error===(.+?)===End-Error===/m ERROR_REGEXP = /===Begin-Error===(.+?)===End-Error===/m
OUTPUT_REGEXP = /===Begin-Output===(.+?)===End-Output===/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 attr_reader :vm_id
def initialize(id) def initialize(id)
@vm_id = id @vm_id = id
end end
def execute(path, options) # @return [Boolean] Supports VMCX
r = execute_powershell(path, options) def has_vmcx_support?
if r.exit_code != 0 !!execute(:has_vmcx_support)["result"]
raise Errors::PowerShellError, end
script: path,
stderr: r.stderr # 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 end
r = execute_powershell(path, options)
# We only want unix-style line endings within Vagrant # We only want unix-style line endings within Vagrant
r.stdout.gsub!("\r\n", "\n") r.stdout.gsub!("\r\n", "\n")
@ -40,104 +62,185 @@ module VagrantPlugins
stderr: data["error"] stderr: data["error"]
end end
if r.exit_code != 0
raise Errors::PowerShellError,
script: path,
stderr: r.stderr
end
# Nothing # Nothing
return nil if !output_match return nil if !output_match
return JSON.parse(output_match[1]) return JSON.parse(output_match[1])
end end
# Fetch current state of the VM
#
# @return [Hash<state, status>]
def get_current_state def get_current_state
execute('get_vm_status.ps1', { VmId: vm_id }) execute(:get_vm_status, VmId: vm_id)
end end
def delete_vm # Delete the VM
execute('delete_vm.ps1', { VmId: vm_id }) #
end # @return [nil]
def delete_vm
execute(:delete_vm, VmId: vm_id)
end
# Export the VM to the given path
#
# @param [String] path Path for export
# @return [nil]
def export(path) def export(path)
execute('export_vm.ps1', {VmId: vm_id, Path: path}) execute(:export_vm, VmId: vm_id, Path: path)
end end
def read_guest_ip # Get the IP address of the VM
execute('get_network_config.ps1', { VmId: vm_id }) #
end # @return [Hash<ip>]
def read_guest_ip
execute(:get_network_config, VmId: vm_id)
end
# Get the MAC address of the VM
#
# @return [Hash<mac>]
def read_mac_address def read_mac_address
execute('get_network_mac.ps1', { VmId: vm_id }) execute(:get_network_mac, VmId: vm_id)
end end
def resume # Resume the VM from suspension
execute('resume_vm.ps1', { VmId: vm_id }) #
end # @return [nil]
def resume
execute(:resume_vm, VmId: vm_id)
end
def start # Start the VM
execute('start_vm.ps1', { VmId: vm_id }) #
end # @return [nil]
def start
execute(:start_vm, VmId: vm_id )
end
def stop # Stop the VM
execute('stop_vm.ps1', { VmId: vm_id }) #
end # @return [nil]
def stop
execute(:stop_vm, VmId: vm_id)
end
def suspend # Suspend the VM
execute("suspend_vm.ps1", { VmId: vm_id }) #
end # @return [nil]
def suspend
execute(:suspend_vm, VmId: vm_id)
end
def import(options) # Import a new VM
config_type = options.delete(:vm_config_type) #
if config_type === "vmcx" # @param [Hash] options Configuration options
execute('import_vm_vmcx.ps1', options) # @return [Hash<id>] New VM ID
else def import(options)
options.delete(:data_path) execute(:import_vm, options)
options.delete(:source_path) end
options.delete(:differencing_disk)
execute('import_vm_xml.ps1', options)
end
end
def net_set_vlan(vlan_id) # Set the VLAN ID
execute("set_network_vlan.ps1", { VmId: vm_id, VlanId: vlan_id }) #
end # @param [String] vlan_id VLAN ID
# @return [nil]
def net_set_vlan(vlan_id)
execute(:set_network_vlan, VmId: vm_id, VlanId: vlan_id)
end
def net_set_mac(mac_addr) # Set the VM adapter MAC address
execute("set_network_mac.ps1", { VmId: vm_id, Mac: mac_addr }) #
end # @param [String] mac_addr MAC address
# @return [nil]
def net_set_mac(mac_addr)
execute(:set_network_mac, VmId: vm_id, Mac: mac_addr)
end
def create_snapshot(snapshot_name) # Create a new snapshot with the given name
execute("create_snapshot.ps1", { VmId: vm_id, SnapName: (snapshot_name) } ) #
end # @param [String] snapshot_name Name of the new snapshot
# @return [nil]
def create_snapshot(snapshot_name)
execute(:create_snapshot, VmId: vm_id, SnapName: snapshot_name)
end
def restore_snapshot(snapshot_name) # Restore the given snapshot
execute("restore_snapshot.ps1", { VmId: vm_id, SnapName: (snapshot_name) } ) #
end # @param [String] snapshot_name Name of snapshot to restore
# @return [nil]
def restore_snapshot(snapshot_name)
execute(:restore_snapshot, VmId: vm_id, SnapName: snapshot_name)
end
def list_snapshots() # Get list of current snapshots
snaps = execute("list_snapshots.ps1", { VmID: vm_id } ) #
snaps.map { |s| s['Name'] } # @return [Array<String>] snapshot names
end def list_snapshots
snaps = execute(:list_snapshots, VmID: vm_id)
snaps.map { |s| s['Name'] }
end
def delete_snapshot(snapshot_name) # Delete snapshot with the given name
execute("delete_snapshot.ps1", {VmID: vm_id, SnapName: snapshot_name}) #
end # @param [String] snapshot_name Name of snapshot to delete
# @return [nil]
def delete_snapshot(snapshot_name)
execute(:delete_snapshot, VmID: vm_id, SnapName: snapshot_name)
end
# Enable or disable VM integration services
#
# @param [Hash] config Integration services to enable or disable
# @return [nil]
# @note Keys in the config hash will be remapped if found in the
# INTEGRATION_SERVICES_MAP. If they are not, the name will
# be passed directly. This allows new integration services
# to configurable even if Vagrant is not aware of them.
def set_vm_integration_services(config) 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 end
protected protected
def execute_powershell(path, options, &block) def execute_powershell(path, options, &block)
lib_path = Pathname.new(File.expand_path("../scripts", __FILE__)) 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("/", "\\") path = lib_path.join(path).to_s.gsub("/", "\\")
options = options || {} options = options || {}
ps_options = [] ps_options = []
options.each do |key, value| options.each do |key, value|
next if value == false
ps_options << "-#{key}" ps_options << "-#{key}"
# If the value is a TrueClass assume switch
next if value == true
ps_options << "'#{value}'" ps_options << "'#{value}'"
end end
# Always have a stop error action for failures # Always have a stop error action for failures
ps_options << "-ErrorAction" << "Stop" 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) Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block)
end end
end end

View File

@ -1,8 +1,6 @@
# Include the following modules #Requires -Modules VagrantMessages
$Dir = Split-Path $script:MyInvocation.MyCommand.Path
. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1"))
$check = $(-Not (-Not (Get-Command "Hyper-V\Get-VMSwitch" -errorAction SilentlyContinue))) $check = $(-Not (-Not (Get-Command "Hyper-V\Get-VMSwitch" -ErrorAction SilentlyContinue)))
$result = @{ $result = @{
result = $check result = $check
} }

View File

@ -1,4 +1,6 @@
Param( #Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$Source, [string]$Source,
@ -6,4 +8,11 @@ Param(
[string]$Destination [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)] [Parameter(Mandatory=$true)]
[string]$VmId, [string]$VmId,
[string]$SnapName [string]$SnapName
) )
$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ErrorActionPreference = "Stop"
Hyper-V\Checkpoint-VM $VM -SnapshotName $SnapName
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)] [Parameter(Mandatory=$true)]
[string]$VmId, [string]$VmId,
[string]$SnapName [string]$SnapName
) )
$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ErrorActionPreference = "Stop"
Hyper-V\Remove-VMSnapshot $VM -Name $SnapName
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)] [Parameter(Mandatory=$true)]
[string]$VmId [string]$VmId
) )
$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ErrorActionPreference = "Stop"
Hyper-V\Remove-VM $VM -Force
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)] [Parameter(Mandatory=$true)]
[string]$VmId, [string]$VmId,
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$Path [string]$Path
) )
$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ErrorActionPreference = "Stop"
$vm | Hyper-V\Export-VM -Path $Path
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 # Prepare directory structure for box import
$name = $vm.Name try {
Move-Item $Path/$name/* $Path $name = $vm.Name
Remove-Item -Path $Path/Snapshots -Force -Recurse Move-Item $Path/$name/* $Path
Remove-Item -Path $Path/$name -Force 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. # Copyright (c) Microsoft Open Technologies, Inc.
# All Rights Reserved. Licensed under the MIT License. # All Rights Reserved. Licensed under the MIT License.
#-------------------------------------------------------------------------- #--------------------------------------------------------------------------
param ( param (
[string]$vm_id = $(throw "-vm_id is required."), [parameter (Mandatory=$true)]
[string]$guest_ip = $(throw "-guest_ip is required."), [string]$vm_id,
[string]$username = $(throw "-guest_username is required."), [parameter (Mandatory=$true)]
[string]$password = $(throw "-guest_password is required."), [string]$guest_ip,
[string]$host_path = $(throw "-host_path is required."), [parameter (Mandatory=$true)]
[string]$guest_path = $(throw "-guest_path is required.") [string]$username,
) [parameter (Mandatory=$true)]
[string]$password,
# Include the following modules [parameter (Mandatory=$true)]
$presentDir = Split-Path -parent $PSCommandPath [string]$host_path,
$modules = @() [parameter (Mandatory=$true)]
$modules += $presentDir + "\utils\write_messages.ps1" [string]$guest_path
forEach ($module in $modules) { . $module } )
function Get-file-hash($source_path, $delimiter) { function Get-file-hash($source_path, $delimiter) {
$source_files = @() $source_files = @()
@ -120,4 +121,3 @@ $resultHash = @{
} }
$result = ConvertTo-Json $resultHash $result = ConvertTo-Json $resultHash
Write-Output-Message $result Write-Output-Message $result

View File

@ -1,56 +1,68 @@
Param( #Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$VmId [string]$VmId
) )
# Include the following modules $ErrorActionPreference = "Stop"
$Dir = Split-Path $script:MyInvocation.MyCommand.Path
. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1"))
$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" try {
$networks = Hyper-V\Get-VMNetworkAdapter -VM $vm $vm = Hyper-V\Get-VM -Id $VmId
foreach ($network in $networks) { } catch {
if ($network.IpAddresses.Length -gt 0) { Write-Error-Message "Failed to locate VM: ${PSItem}"
foreach ($ip_address in $network.IpAddresses) { exit 1
if ($ip_address.Contains(".") -And [string]::IsNullOrEmpty($ip4_address)) { }
$ip4_address = $ip_address
} elseif ($ip_address.Contains(":") -And [string]::IsNullOrEmpty($ip6_address)) { try {
$ip6_address = $ip_address $networks = Hyper-V\Get-VMNetworkAdapter -VM $vm
foreach ($network in $networks) {
if ($network.IpAddresses.Length -gt 0) {
foreach ($ip_address in $network.IpAddresses) {
if ($ip_address.Contains(".") -And [string]::IsNullOrEmpty($ip4_address)) {
$ip4_address = $ip_address
} elseif ($ip_address.Contains(":") -And [string]::IsNullOrEmpty($ip6_address)) {
$ip6_address = $ip_address
}
} }
} }
} }
}
# If no address was found in the network settings, check for # If no address was found in the network settings, check for
# neighbor with mac address and see if an IP exists # neighbor with mac address and see if an IP exists
if (([string]::IsNullOrEmpty($ip4_address)) -And ([string]::IsNullOrEmpty($ip6_address))) { if (([string]::IsNullOrEmpty($ip4_address)) -And ([string]::IsNullOrEmpty($ip6_address))) {
$macaddresses = $vm | select -ExpandProperty NetworkAdapters | select MacAddress $macaddresses = $vm | select -ExpandProperty NetworkAdapters | select MacAddress
foreach ($macaddr in $macaddresses) { foreach ($macaddr in $macaddresses) {
$macaddress = $macaddr.MacAddress -replace '(.{2})(?!$)', '${1}-' $macaddress = $macaddr.MacAddress -replace '(.{2})(?!$)', '${1}-'
$addr = Get-NetNeighbor -LinkLayerAddress $macaddress -ErrorAction SilentlyContinue | select IPAddress $addr = Get-NetNeighbor -LinkLayerAddress $macaddress -ErrorAction SilentlyContinue | select IPAddress
if ($ip_address) { if ($ip_address) {
$ip_address = $addr.IPAddress $ip_address = $addr.IPAddress
if ($ip_address.Contains(".")) { if ($ip_address.Contains(".")) {
$ip4_address = $ip_address $ip4_address = $ip_address
} elseif ($ip_address.Contains(":")) { } elseif ($ip_address.Contains(":")) {
$ip6_address = $ip_address $ip6_address = $ip_address
}
} }
} }
} }
}
if (-Not ([string]::IsNullOrEmpty($ip4_address))) { if (-Not ([string]::IsNullOrEmpty($ip4_address))) {
$guest_ipaddress = $ip4_address $guest_ipaddress = $ip4_address
} elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) { } elseif (-Not ([string]::IsNullOrEmpty($ip6_address))) {
$guest_ipaddress = $ip6_address $guest_ipaddress = $ip6_address
}
if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) {
$resultHash = @{
ip = $guest_ipaddress
} }
$result = ConvertTo-Json $resultHash
Write-Output-Message $result if (-Not ([string]::IsNullOrEmpty($guest_ipaddress))) {
} else { $resultHash = @{
Write-Error-Message "Failed to determine IP address" ip = $guest_ipaddress
}
$result = ConvertTo-Json $resultHash
Write-Output-Message $result
} else {
Write-Error-Message "Failed to determine IP address"
exit 1
}
} catch {
Write-Error-Message "Unexpected error while detecting network configuration: ${PSItem}"
exit 1
} }

View File

@ -1,28 +1,32 @@
Param( #Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$VmId [string]$VmId
) )
# Include the following modules $ErrorActionPreference = "Stop"
$Dir = Split-Path $script:MyInvocation.MyCommand.Path
. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1"))
$ip_address = "" try {
$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ip_address = ""
$networks = Hyper-V\Get-VMNetworkAdapter -VM $vm $vm = Hyper-V\Get-VM -Id $VmId
foreach ($network in $networks) { $networks = Hyper-V\Get-VMNetworkAdapter -VM $vm
if ($network.MacAddress -gt 0) { foreach ($network in $networks) {
$mac_address = $network.MacAddress if ($network.MacAddress -gt 0) {
if (-Not ([string]::IsNullOrEmpty($mac_address))) { $mac_address = $network.MacAddress
# We found our mac address! if (-Not ([string]::IsNullOrEmpty($mac_address))) {
break # We found our mac address!
break
}
}
} }
}
}
$resultHash = @{
$resultHash = @{ mac = "$mac_address"
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: # This will have a SwitchType property. As far as I know the values are:
# #
# 0 - Private # 0 - Private
# 1 - Internal # 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 ` $Switches = @(Hyper-V\Get-VMSwitch `
| Select-Object Name,SwitchType,NetAdapterInterfaceDescription,Id) | Select-Object Name,SwitchType,NetAdapterInterfaceDescription,Id)

View File

@ -1,12 +1,10 @@
Param( #Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$VmId [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 # Make sure the exception type is loaded
try try
{ {
@ -37,7 +35,7 @@ try {
$State = "not_created" $State = "not_created"
$Status = $State $Status = $State
} }
else else
{ {
throw; throw;
} }

View File

@ -1,6 +1,4 @@
# Include the following modules #Requires -Modules VagrantMessages
$Dir = Split-Path $script:MyInvocation.MyCommand.Path
. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1"))
# Windows version 10 and up have support for binary format # Windows version 10 and up have support for binary format
$check = [System.Environment]::OSVersion.Version.Major -ge 10 $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)] [Parameter(Mandatory=$true)]
[string]$VmId [string]$VmId
) )
$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ErrorActionPreference = "Stop"
$Snapshots = @(Hyper-V\Get-VMSnapshot $VM | Select-Object Name)
$result = ConvertTo-json $Snapshots
Write-Host "===Begin-Output===" try {
Write-Host $result $VM = Hyper-V\Get-VM -Id $VmId
Write-Host "===End-Output===" $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)] [Parameter(Mandatory=$true)]
[string]$VmId, [string]$VmId,
[string]$SnapName [string]$SnapName
) )
$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ErrorActionPreference = "Stop"
Hyper-V\Restore-VMSnapshot $VM -Name $SnapName -Confirm:$false
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)] [Parameter(Mandatory=$true)]
[string]$VmId [string]$VmId
) )
$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ErrorActionPreference = "Stop"
Hyper-V\Resume-VM $VM
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 ( #Requires -Modules VagrantMessages
[string]$VmId = $(throw "-VmId is required."),
[string]$Mac = $(throw "-Mac ")
)
# Include the following modules param (
$presentDir = Split-Path -parent $PSCommandPath [parameter (Mandatory=$true)]
$modules = @() [string]$VmId,
$modules += $presentDir + "\utils\write_messages.ps1" [parameter (Mandatory=$true)]
forEach ($module in $modules) { . $module } [string]$Mac
)
$ErrorActionPreference = "Stop"
try { try {
$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" $vm = Hyper-V\Get-VM -Id $VmId
Hyper-V\Set-VMNetworkAdapter $vm -StaticMacAddress $Mac -ErrorAction "stop" Hyper-V\Set-VMNetworkAdapter $vm -StaticMacAddress $Mac
} } catch {
catch { Write-Error-Message "Failed to set VM MAC address: ${PSItem}"
Write-Error-Message "Failed to set VM's MAC address $_" exit 1
} }

View File

@ -1,7 +1,11 @@
#Requires -Modules VagrantMessages
param ( param (
[string]$VmId = $(throw "-VmId is required."), [parameter (Mandatory=$true)]
[int]$VlanId = $(throw "-VlanId ") [string]$VmId,
) [parameter (Mandatory=$true)]
[int]$VlanId
)
# Include the following modules # Include the following modules
$presentDir = Split-Path -parent $PSCommandPath $presentDir = Split-Path -parent $PSCommandPath

View File

@ -1,37 +1,27 @@
#Requires -Modules VagrantVM, VagrantMessages
param ( param (
[string] $VmId, [parameter (Mandatory=$true)]
[string] $guest_service_interface = $null, [string] $VMID,
[string] $heartbeat = $null, [parameter (Mandatory=$true)]
[string] $key_value_pair_exchange = $null, [string] $Name,
[string] $shutdown = $null, [parameter (Mandatory=$false)]
[string] $time_synchronization = $null, [switch] $Enable
[string] $vss = $null
) )
# Include the following modules $ErrorActionPreference = "Stop"
$Dir = Split-Path $script:MyInvocation.MyCommand.Path
. ([System.IO.Path]::Combine($Dir, "utils\write_messages.ps1"))
$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" try {
$VM = Hyper-V\Get-VM -Id $VMID
# Set the service based on value } catch {
function VmSetService Write-Error-Message "Failed to locate VM: ${PSItem}"
{ exit 1
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
}
}
} }
VmSetService -Name "Guest Service Interface" -Value $guest_service_interface -Vm $vm try {
VmSetService -Name "Heartbeat" -Value $heartbeat -Vm $vm Set-VagrantVMService -VM $VM -Name $Name -Enable $enabled
VmSetService -Name "Key-Value Pair Exchange" -Value $key_value_pair_exchange -Vm $vm } catch {
VmSetService -Name "Shutdown" -Value $shutdown -Vm $vm if($enabled){ $action = "enable" } else { $action = "disable" }
VmSetService -Name "Time Synchronization" -Value $time_synchronization -Vm $vm Write-Error-Message "Failed to ${action} VM integration service ${Name}: ${PSItem}"
VmSetService -Name "VSS" -Value $vss -Vm $vm exit 1
}

View File

@ -1,27 +1,26 @@
param ( #Requires -Modules VagrantMessages
[string]$VmId = $(throw "-VmId is required.")
)
# Include the following modules param (
$presentDir = Split-Path -parent $PSCommandPath [parameter (Mandatory=$true)]
$modules = @() [string]$VmId
$modules += $presentDir + "\utils\write_messages.ps1" )
forEach ($module in $modules) { . $module }
$ErrorActionPreference = "Stop"
try { try {
$vm = Hyper-V\Get-VM -Id $VmId -ErrorAction "stop" $vm = Hyper-V\Get-VM -Id $VmId
Hyper-V\Start-VM $vm -ErrorAction "stop" Hyper-V\Start-VM $vm
$state = $vm.state $state = $vm.state
$status = $vm.status $status = $vm.status
$name = $vm.name $name = $vm.name
$resultHash = @{ $resultHash = @{
state = "$state" state = "$state"
status = "$status" status = "$status"
name = "$name" name = "$name"
} }
$result = ConvertTo-Json $resultHash $result = ConvertTo-Json $resultHash
Write-Output-Message $result Write-Output-Message $result
} catch {
Write-Error-Message "Failed to start VM ${PSItem}"
exit 1
} }
catch {
Write-Error-Message "Failed to start a VM $_"
}

View File

@ -1,8 +1,17 @@
#Requires -Modules VagrantMessages
Param( Param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
[string]$VmId [string]$VmId
) )
# Shuts down virtual machine regardless of any unsaved application data $ErrorActionPreference = "Stop"
$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop"
Hyper-V\Stop-VM $VM -Force 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)] [Parameter(Mandatory=$true)]
[string]$VmId [string]$VmId
) )
$VM = Hyper-V\Get-VM -Id $VmId -ErrorAction "Stop" $ErrorActionPreference = "Stop"
Hyper-V\Suspend-VM $VM
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: |- message_not_running: |-
Hyper-V machine isn't running. Can't SSH in! 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: errors:
admin_required: |- admin_required: |-
The Hyper-V provider requires that Vagrant be run with 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") require Vagrant.source_root.join("plugins/providers/hyperv/config")
describe VagrantPlugins::HyperV::Config do describe VagrantPlugins::HyperV::Config do
let(:machine){ double("machine", ui: ui) }
let(:ui){ double("ui") }
describe "#ip_address_timeout" do describe "#ip_address_timeout" do
it "can be set" do it "can be set" do
subject.ip_address_timeout = 180 subject.ip_address_timeout = 180
@ -30,7 +34,7 @@ describe VagrantPlugins::HyperV::Config do
expect(subject.mac).to eq("001122334455") expect(subject.mac).to eq("001122334455")
end end
end end
describe "#vmname" do describe "#vmname" do
it "can be set" do it "can be set" do
subject.vmname = "test" subject.vmname = "test"
@ -62,4 +66,159 @@ describe VagrantPlugins::HyperV::Config do
expect(subject.cpus).to eq(2) expect(subject.cpus).to eq(2)
end end
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 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 describe Vagrant::Util::PowerShell do
include_context "unit" include_context "unit"
after{ described_class.reset! }
describe ".version" do describe ".version" do
before do before do
allow(described_class).to receive(:executable) allow(described_class).to receive(:executable)
@ -13,7 +16,6 @@ describe Vagrant::Util::PowerShell do
after do after do
described_class.version described_class.version
described_class.reset!
end end
it "should execute powershell command" do it "should execute powershell command" do
@ -41,4 +43,246 @@ describe Vagrant::Util::PowerShell do
end end
end 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 end

View File

@ -12,41 +12,45 @@ description: |-
The Vagrant Hyper-V provider has some provider-specific configuration options The Vagrant Hyper-V provider has some provider-specific configuration options
you may set. A complete reference is shown below: you may set. A complete reference is shown below:
* `vmname` (string) - Name of virtual machine as shown in Hyper-V manager. * `auto_start_action` (Nothing, StartIfRunning, Start) - Automatic start action for VM on host startup. Default: Nothing.
Defaults is taken from box image XML. * `auto_stop_action` (ShutDown, TurnOff, Save) - Automatic stop action for VM on host shutdown. Default: ShutDown.
* `cpus` (integer) - Number of virtual CPU given to machine. * `cpus` (integer) - Number of virtual CPUs allocated to VM at startup.
Defaults is taken from box image XML. * `differencing_disk` (boolean) - **Deprecated** Use differencing disk instead of cloning entire VHD (use `linked_clone` instead) Default: false.
* `memory` (integer) - Number of MegaBytes allocated to VM at startup. * `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs. Default: false
Defaults is taken from box image XML. * `enable_checkpoints` (boolean) Enable automatic checkpoints of the VM. Default: false
* `maxmemory` (integer) - Number of MegaBytes maximal allowed to allocate for VM * `ip_address_timeout` (integer) - Number of seconds to wait for the VM to report an IP address. Default: 120.
This parameter is switch on Dynamic Allocation of memory. * `linked_clone` (boolean) - Use differencing disk instead of cloning entire VHD. Default: false
Defaults is taken from box image XML. * `mac` (string) - MAC address for the guest network interface
* `vlan_id` (integer) - Number of Vlan ID for your guest network interface * `maxmemory` (integer) - Maximum number of megabytes allowed to be allocated for the VM. When set Dynamic Memory Allocation will be enabled.
Defaults is not defined, vlan configuration will be untouched if not set. * `memory` (integer) - Number of megabytes allocated to VM at startup. If `maxmemory` is set, this will be amount of memory allocated at startup.
* `mac` (string) - MAC address for your guest network interface * `vlan_id` (integer) - VLAN ID for the guest network interface.
Default is not defined, MAC address will be dynamically assigned by Hyper-V if not set. * `vmname` (string) - Name of virtual machine as shown in Hyper-V manager. Default: Generated name.
* `ip_address_timeout` (integer) - The time in seconds to wait for the * `vm_integration_services` (Hash) - Hash to set the state of integration services. (Note: Unknown key values will be passed directly.)
virtual machine to report an IP address. This defaults to 120 seconds. * `guest_service_interface` (boolean)
This may have to be increased if your VM takes longer to boot. * `heartbeat` (boolean)
* `differencing_disk` (boolean) - Switch to use differencing disk instead of cloning whole VHD. * `key_value_pair_exchange` (boolean)
* `enable_virtualization_extensions` (boolean) - Enable virtualization extensions for the virtual CPUs. * `shutdown` (boolean)
This allows Hyper-V to be nested and run inside another Hyper-VM VM. It requires Windows 10 - 1511 (build 10586) or newer. * `time_synchronization` (boolean)
Default is not defined. This will be disabled if not set. * `vss` (boolean)
* `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
* `vm_integration_services` (Hash) - Hash to set the state of integration services.
The `vm_integration_services` configuration option consists of a simple Hash. The key values are the
Example: 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
```ruby key is unknown, the key value is used "as-is" without any modifications.
config.vm.provider "hyperv" do |h|
h.vm_integration_services = { For example, if a new `CustomVMSRV` VM integration service was added and Vagrant is not aware of this
guest_service_interface: true, new service name, it can be provided as the key value explicitly:
heartbeat: true,
key_value_pair_exchange: true, ```ruby
shutdown: true, config.vm.provider "hyperv" do |h|
time_synchronization: true, h.vm_integration_services = {
vss: true guest_service_interface: 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.