2014-02-16 00:28:11 +00:00
|
|
|
require "json"
|
|
|
|
|
|
|
|
require "vagrant/util/powershell"
|
2014-02-15 23:29:16 +00:00
|
|
|
|
2014-02-16 00:35:04 +00:00
|
|
|
require_relative "plugin"
|
|
|
|
|
2014-02-15 23:29:16 +00:00
|
|
|
module VagrantPlugins
|
|
|
|
module HyperV
|
2014-02-16 00:28:11 +00:00
|
|
|
class Driver
|
2014-02-16 00:51:25 +00:00
|
|
|
ERROR_REGEXP = /===Begin-Error===(.+?)===End-Error===/m
|
|
|
|
OUTPUT_REGEXP = /===Begin-Output===(.+?)===End-Output===/m
|
2014-03-06 16:51:07 +00:00
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# 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
|
2014-03-06 16:58:31 +00:00
|
|
|
attr_reader :vm_id
|
|
|
|
|
|
|
|
def initialize(id)
|
|
|
|
@vm_id = id
|
2014-03-06 16:51:07 +00:00
|
|
|
end
|
2014-02-16 00:51:25 +00:00
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# @return [Boolean] Supports VMCX
|
|
|
|
def has_vmcx_support?
|
|
|
|
!!execute(:has_vmcx_support)["result"]
|
|
|
|
end
|
|
|
|
|
|
|
|
# Execute a PowerShell command and process the results
|
|
|
|
#
|
|
|
|
# @param [String] path Path to PowerShell script
|
|
|
|
# @param [Hash] options Options to pass to command
|
|
|
|
#
|
|
|
|
# @return [Object, nil] If the command returned JSON content
|
|
|
|
# it will be parsed and returned, otherwise
|
|
|
|
# nil will be returned
|
|
|
|
def execute(path, options={})
|
|
|
|
if path.is_a?(Symbol)
|
|
|
|
path = "#{path}.ps1"
|
2014-02-16 00:35:04 +00:00
|
|
|
end
|
2018-05-24 16:57:55 +00:00
|
|
|
r = execute_powershell(path, options)
|
2014-02-16 00:35:04 +00:00
|
|
|
|
2014-02-16 00:51:25 +00:00
|
|
|
# We only want unix-style line endings within Vagrant
|
|
|
|
r.stdout.gsub!("\r\n", "\n")
|
|
|
|
r.stderr.gsub!("\r\n", "\n")
|
2014-02-16 00:28:11 +00:00
|
|
|
|
2014-02-16 00:51:25 +00:00
|
|
|
error_match = ERROR_REGEXP.match(r.stdout)
|
|
|
|
output_match = OUTPUT_REGEXP.match(r.stdout)
|
|
|
|
|
|
|
|
if error_match
|
|
|
|
data = JSON.parse(error_match[1])
|
|
|
|
|
|
|
|
# We have some error data.
|
|
|
|
raise Errors::PowerShellError,
|
|
|
|
script: path,
|
|
|
|
stderr: data["error"]
|
2014-02-16 00:28:11 +00:00
|
|
|
end
|
2014-02-16 00:51:25 +00:00
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
if r.exit_code != 0
|
|
|
|
raise Errors::PowerShellError,
|
|
|
|
script: path,
|
|
|
|
stderr: r.stderr
|
|
|
|
end
|
|
|
|
|
2014-02-16 00:51:25 +00:00
|
|
|
# Nothing
|
|
|
|
return nil if !output_match
|
|
|
|
return JSON.parse(output_match[1])
|
2014-02-16 00:28:11 +00:00
|
|
|
end
|
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# Fetch current state of the VM
|
|
|
|
#
|
|
|
|
# @return [Hash<state, status>]
|
2014-03-06 16:51:07 +00:00
|
|
|
def get_current_state
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:get_vm_status, VmId: vm_id)
|
2014-03-06 16:51:07 +00:00
|
|
|
end
|
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# Delete the VM
|
|
|
|
#
|
|
|
|
# @return [nil]
|
|
|
|
def delete_vm
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:delete_vm, VmId: vm_id)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
2014-03-06 16:51:07 +00:00
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# Export the VM to the given path
|
|
|
|
#
|
|
|
|
# @param [String] path Path for export
|
|
|
|
# @return [nil]
|
2016-10-05 11:48:48 +00:00
|
|
|
def export(path)
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:export_vm, VmId: vm_id, Path: path)
|
2016-10-05 11:48:48 +00:00
|
|
|
end
|
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# Get the IP address of the VM
|
|
|
|
#
|
|
|
|
# @return [Hash<ip>]
|
|
|
|
def read_guest_ip
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:get_network_config, VmId: vm_id)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
2014-03-06 16:51:07 +00:00
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# Get the MAC address of the VM
|
|
|
|
#
|
|
|
|
# @return [Hash<mac>]
|
2016-10-05 11:48:48 +00:00
|
|
|
def read_mac_address
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:get_network_mac, VmId: vm_id)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Resume the VM from suspension
|
|
|
|
#
|
|
|
|
# @return [nil]
|
|
|
|
def resume
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:resume_vm, VmId: vm_id)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Start the VM
|
|
|
|
#
|
|
|
|
# @return [nil]
|
|
|
|
def start
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:start_vm, VmId: vm_id )
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
2016-03-05 16:41:18 +00:00
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# Stop the VM
|
|
|
|
#
|
|
|
|
# @return [nil]
|
|
|
|
def stop
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:stop_vm, VmId: vm_id)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Suspend the VM
|
|
|
|
#
|
|
|
|
# @return [nil]
|
|
|
|
def suspend
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:suspend_vm, VmId: vm_id)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Import a new VM
|
|
|
|
#
|
|
|
|
# @param [Hash] options Configuration options
|
|
|
|
# @return [Hash<id>] New VM ID
|
|
|
|
def import(options)
|
|
|
|
execute(:import_vm, options)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Set the VLAN ID
|
|
|
|
#
|
|
|
|
# @param [String] vlan_id VLAN ID
|
|
|
|
# @return [nil]
|
|
|
|
def net_set_vlan(vlan_id)
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:set_network_vlan, VmId: vm_id, VlanId: vlan_id)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Set the VM adapter MAC address
|
|
|
|
#
|
|
|
|
# @param [String] mac_addr MAC address
|
|
|
|
# @return [nil]
|
|
|
|
def net_set_mac(mac_addr)
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:set_network_mac, VmId: vm_id, Mac: mac_addr)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Create a new snapshot with the given name
|
|
|
|
#
|
|
|
|
# @param [String] snapshot_name Name of the new snapshot
|
|
|
|
# @return [nil]
|
|
|
|
def create_snapshot(snapshot_name)
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:create_snapshot, VmId: vm_id, SnapName: snapshot_name)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Restore the given snapshot
|
|
|
|
#
|
|
|
|
# @param [String] snapshot_name Name of snapshot to restore
|
|
|
|
# @return [nil]
|
|
|
|
def restore_snapshot(snapshot_name)
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:restore_snapshot, VmId: vm_id, SnapName: snapshot_name)
|
2018-05-24 16:57:55 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
# Get list of current snapshots
|
|
|
|
#
|
|
|
|
# @return [Array<String>] snapshot names
|
|
|
|
def list_snapshots
|
2018-05-25 17:11:05 +00:00
|
|
|
snaps = execute(:list_snapshots, VmID: vm_id)
|
2018-05-24 16:57:55 +00:00
|
|
|
snaps.map { |s| s['Name'] }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Delete snapshot with the given name
|
|
|
|
#
|
|
|
|
# @param [String] snapshot_name Name of snapshot to delete
|
|
|
|
# @return [nil]
|
|
|
|
def delete_snapshot(snapshot_name)
|
2018-05-25 17:11:05 +00:00
|
|
|
execute(:delete_snapshot, VmID: vm_id, SnapName: snapshot_name)
|
2018-05-24 16:57:55 +00:00
|
|
|
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.
|
2017-03-15 21:12:19 +00:00
|
|
|
def set_vm_integration_services(config)
|
2018-05-24 16:57:55 +00:00
|
|
|
config.each_pair do |srv_name, srv_enable|
|
2018-05-25 17:11:05 +00:00
|
|
|
args = {VMID: vm_id, Name: INTEGRATION_SERVICES_MAP.fetch(srv_name.to_sym, srv_name).to_s}
|
|
|
|
args[:Enable] = true if srv_enable
|
2018-05-24 16:57:55 +00:00
|
|
|
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)
|
2017-03-15 21:12:19 +00:00
|
|
|
end
|
|
|
|
|
2014-02-16 00:28:11 +00:00
|
|
|
protected
|
|
|
|
|
|
|
|
def execute_powershell(path, options, &block)
|
2014-02-16 00:51:25 +00:00
|
|
|
lib_path = Pathname.new(File.expand_path("../scripts", __FILE__))
|
2018-06-15 14:43:46 +00:00
|
|
|
mod_path = Vagrant::Util::Platform.wsl_to_windows_path(lib_path.join("utils")).gsub("/", "\\")
|
|
|
|
path = Vagrant::Util::Platform.wsl_to_windows_path(lib_path.join(path)).gsub("/", "\\")
|
2014-02-16 00:28:11 +00:00
|
|
|
options = options || {}
|
|
|
|
ps_options = []
|
|
|
|
options.each do |key, value|
|
2018-05-24 16:57:55 +00:00
|
|
|
next if value == false
|
2014-02-16 00:28:11 +00:00
|
|
|
ps_options << "-#{key}"
|
2018-05-24 16:57:55 +00:00
|
|
|
# If the value is a TrueClass assume switch
|
|
|
|
next if value == true
|
2014-02-16 00:28:11 +00:00
|
|
|
ps_options << "'#{value}'"
|
|
|
|
end
|
2014-02-16 19:42:20 +00:00
|
|
|
|
|
|
|
# Always have a stop error action for failures
|
|
|
|
ps_options << "-ErrorAction" << "Stop"
|
|
|
|
|
2018-05-24 16:57:55 +00:00
|
|
|
# Include our module path so we can nicely load helper modules
|
|
|
|
opts = {
|
|
|
|
notify: [:stdout, :stderr, :stdin],
|
2018-06-15 14:43:46 +00:00
|
|
|
module_path: Vagrant::Util::Platform.wsl_to_windows_path(mod_path)
|
2018-05-24 16:57:55 +00:00
|
|
|
}
|
2018-06-15 14:43:46 +00:00
|
|
|
|
2014-02-16 00:28:11 +00:00
|
|
|
Vagrant::Util::PowerShell.execute(path, *ps_options, **opts, &block)
|
|
|
|
end
|
2014-02-15 23:29:16 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|