diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb new file mode 100644 index 000000000..c2ca42694 --- /dev/null +++ b/plugins/providers/hyperv/action.rb @@ -0,0 +1,128 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "pathname" +require "vagrant/action/builder" + +module VagrantPlugins + module HyperV + module Action + # Include the built-in modules so we can use them as top-level things. + include Vagrant::Action::Builtin + + def self.action_reload + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + b2.use action_halt + b2.use Call, WaitForState, :off, 120 do |env2, b3| + if env2[:result] + b3.use action_up + else + env2[:ui].info("Machine did not reload, Check machine's status") + end + end + end + end + end + + def self.action_halt + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + b2.use StopInstance + end + end + end + + def self.action_start + Vagrant::Action::Builder.new.tap do |b| + b.use StartInstance + b.use ShareFolders + b.use SyncFolders + end + end + + def self.action_up + Vagrant::Action::Builder.new.tap do |b| + b.use HandleBoxUrl + b.use ConfigValidate + b.use Call, IsCreated do |env1, b1| + if env1[:result] + b1.use Call, IsStopped do |env2, b2| + if env2[:result] + b2.use action_start + else + b2.use MessageAlreadyCreated + end + end + else + b1.use Import + b1.use action_start + end + end + end + end + + def self.action_read_state + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use ReadState + end + end + + def self.action_ssh + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use Call, IsCreated do |env, b2| + if !env[:result] + b2.use MessageNotCreated + next + end + b2.use Call, IsStopped do |env1, b3| + if env1[:result] + b3.use MessageNotRunning + else + b3.use SSHExec + end + end + end + end + end + + def self.action_read_guest_ip + Vagrant::Action::Builder.new.tap do |b| + b.use ConfigValidate + b.use ReadGuestIP + end + end + + + # The autoload farm + action_root = Pathname.new(File.expand_path("../action", __FILE__)) + autoload :IsCreated, action_root.join("is_created") + autoload :IsStopped, action_root.join("is_stopped") + autoload :ReadState, action_root.join("read_state") + autoload :Import, action_root.join("import") + autoload :StartInstance, action_root.join('start_instance') + autoload :StopInstance, action_root.join('stop_instance') + autoload :MessageNotCreated, action_root.join('message_not_created') + autoload :MessageAlreadyCreated, action_root.join('message_already_created') + autoload :MessageNotRunning, action_root.join('message_not_running') + autoload :SyncFolders, action_root.join('sync_folders') + autoload :WaitForState, action_root.join('wait_for_state') + autoload :ReadGuestIP, action_root.join('read_guest_ip') + autoload :ShareFolders, action_root.join('share_folders') + end + end +end diff --git a/plugins/providers/hyperv/action/import.rb b/plugins/providers/hyperv/action/import.rb new file mode 100644 index 000000000..25e834935 --- /dev/null +++ b/plugins/providers/hyperv/action/import.rb @@ -0,0 +1,49 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class Import + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::connection") + end + + def call(env) + box_directory = env[:machine].box.directory.to_s + path = Pathname.new(box_directory.to_s + '/Virtual Machines') + config_path = "" + path.each_child do |f| + config_path = f.to_s if f.extname.downcase == ".xml" + end + + path = Pathname.new(box_directory.to_s + '/Virtual Hard Disks') + vhdx_path = "" + path.each_child do |f| + vhdx_path = f.to_s if f.extname.downcase == ".vhdx" + end + + options = { + vm_xml_config: config_path.gsub("/", "\\"), + vhdx_path: vhdx_path.gsub("/", "\\") + } + + env[:ui].info "Importing a Hyper-V instance" + begin + server = env[:machine].provider.driver.execute('import_vm.ps1', options) + rescue Error::SubprocessError => e + env[:ui].info e.message + return + end + env[:ui].info "Successfully imported a VM with name #{server['name']}" + env[:machine].id = server["id"] + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/is_created.rb b/plugins/providers/hyperv/action/is_created.rb new file mode 100644 index 000000000..1d9934f99 --- /dev/null +++ b/plugins/providers/hyperv/action/is_created.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class IsCreated + def initialize(app, env) + @app = app + end + + def call(env) + env[:result] = env[:machine].state.id != :not_created + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/is_stopped.rb b/plugins/providers/hyperv/action/is_stopped.rb new file mode 100644 index 000000000..83e87bd8c --- /dev/null +++ b/plugins/providers/hyperv/action/is_stopped.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class IsStopped + def initialize(app, env) + @app = app + end + + def call(env) + env[:result] = env[:machine].state.id == :off + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/message_already_created.rb b/plugins/providers/hyperv/action/message_already_created.rb new file mode 100644 index 000000000..72e4b052d --- /dev/null +++ b/plugins/providers/hyperv/action/message_already_created.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class MessageAlreadyCreated + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info("Machine already created") + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/message_not_created.rb b/plugins/providers/hyperv/action/message_not_created.rb new file mode 100644 index 000000000..5af00f180 --- /dev/null +++ b/plugins/providers/hyperv/action/message_not_created.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class MessageNotCreated + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info("Machine not created") + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/message_not_running.rb b/plugins/providers/hyperv/action/message_not_running.rb new file mode 100644 index 000000000..1a5ca2502 --- /dev/null +++ b/plugins/providers/hyperv/action/message_not_running.rb @@ -0,0 +1,22 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class MessageNotRunning + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info("Machine is not running, Please turn it on.") + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/read_guest_ip.rb b/plugins/providers/hyperv/action/read_guest_ip.rb new file mode 100644 index 000000000..209d02c58 --- /dev/null +++ b/plugins/providers/hyperv/action/read_guest_ip.rb @@ -0,0 +1,46 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "log4r" +require "timeout" + +module VagrantPlugins + module HyperV + module Action + # This action reads the SSH info for the machine and puts it into the + # `:machine_ssh_info` key in the environment. + class ReadGuestIP + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::connection") + end + + def call(env) + env[:machine_ssh_info] = read_host_ip(env) + @app.call(env) + end + + def read_host_ip(env) + return nil if env[:machine].id.nil? + # Get Network details from WMI Provider + # Wait for 120 sec By then the machine should be ready + host_ip = nil + begin + Timeout.timeout(120) do + begin + options = { vm_id: env[:machine].id } + network_info = env[:machine].provider.driver.execute('get_network_config.ps1', options) + host_ip = network_info["ip"] + sleep 10 if host_ip.empty? + end while host_ip.empty? + end + rescue Timeout::Error + @logger.info("Cannot find the IP address of the virtual machine") + end + return { host: host_ip } unless host_ip.nil? + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/read_state.rb b/plugins/providers/hyperv/action/read_state.rb new file mode 100644 index 000000000..b0bc82242 --- /dev/null +++ b/plugins/providers/hyperv/action/read_state.rb @@ -0,0 +1,36 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "debugger" +require "log4r" +module VagrantPlugins + module HyperV + module Action + class ReadState + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant::hyperv::connection") + end + + def call(env) + if env[:machine].id + begin + options = { vm_id: env[:machine].id } + response = env[:machine].provider.driver.execute('get_vm_status.ps1', options) + env[:machine_state_id] = response["state"].downcase.to_sym + rescue Error::SubprocessError => e + env[:machine].id = nil + env[:ui].info "Could not find a machine, assuming it to be deleted or terminated." + env[:machine_state_id] = :not_created + end + else + env[:machine_state_id] = :not_created + end + @app.call(env) + end + + end + end + end +end diff --git a/plugins/providers/hyperv/action/share_folders.rb b/plugins/providers/hyperv/action/share_folders.rb new file mode 100644 index 000000000..c46f89162 --- /dev/null +++ b/plugins/providers/hyperv/action/share_folders.rb @@ -0,0 +1,123 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "debugger" +require "vagrant/util/subprocess" +module VagrantPlugins + module HyperV + module Action + class ShareFolders + + def initialize(app, env) + @app = app + end + + def call(env) + @env = env + smb_shared_folders + prepare_smb_share + # A BIG Clean UP + # There should be a communicator class which branches between windows + # and Linux + if @smb_shared_folders.length > 0 + env[:ui].info('Mounting shared folders with VM, This process may take few minutes.') + end + if env[:machine].config.vm.guest == :windows + env[:ui].info "Mounting shared folders to windows is under development." + # mount_shared_folders_to_windows + elsif env[:machine].config.vm.guest == :linux + mount_shared_folders_to_linux + end + @app.call(env) + end + + def smb_shared_folders + @smb_shared_folders = {} + @env[:machine].config.vm.synced_folders.each do |id, data| + # Ignore disabled shared folders + next if data[:disabled] + + # Collect all SMB shares + next unless data[:smb] + # This to prevent overwriting the actual shared folders data + @smb_shared_folders[id] = data.dup + end + end + + def prepare_smb_share + @smb_shared_folders.each do |id, data| + begin + hostpath = File.expand_path(data[:hostpath], @env[:root_path]) + host_share_username = @env[:machine].provider_config.host_share.username + options = {:path => hostpath, + :share_name => data[:share_name], + :host_share_username => host_share_username} + response = @env[:machine].provider.driver.execute('set_smb_share.ps1', options) + if response["message"] == "OK" + @env[:ui].info "Successfully created SMB share for #{hostpath} with name #{data[:share_name]}" + end + rescue Error::SubprocessError => e + @env[:ui].info e.message + end + end + end + + def ssh_info + @ssh_info || @env[:machine].ssh_info + end + + def mount_shared_folders_to_windows + result = @env[:machine].provider.driver.execute('host_info.ps1', {}) + @smb_shared_folders.each do |id, data| + begin + options = { :share_name => data[:share_name], + :guest_path => data[:guestpath].gsub("/", "\\"), + :guest_ip => ssh_info[:host], + :username => ssh_info[:username], + :host_ip => result["host_ip"], + :password => @env[:machine].provider_config.guest.password, + :host_share_username => @env[:machine].provider_config.host_share.username, + :host_share_password => @env[:machine].provider_config.host_share.password} + @env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...") + @env[:machine].provider.driver.execute('mount_share.ps1', options) + rescue Error::SubprocessError => e + @env[:ui].info "Failed to link #{data[:share_name]} to Guest" + @env[:ui].info e.message + end + end + end + + def mount_shared_folders_to_linux + # Find Host Machine's credentials + result = @env[:machine].provider.driver.execute('host_info.ps1', {}) + host_share_username = @env[:machine].provider_config.host_share.username + host_share_password = @env[:machine].provider_config.host_share.password + @smb_shared_folders.each do |id, data| + begin + # Mount the Network drive to Guest VM + @env[:ui].info("Linking #{data[:share_name]} to Guest at #{data[:guestpath]} ...") + + # Create a location in guest to guestpath + @env[:machine].communicate.sudo("mkdir -p #{data[:guestpath]}") + owner = data[:owner] || ssh_info[:username] + group = data[:group] || ssh_info[:username] + + mount_options = "-o rw,username=#{host_share_username},pass=#{host_share_password}," + mount_options += "sec=ntlm,file_mode=0777,dir_mode=0777," + mount_options += "uid=`id -u #{owner}`,gid=`id -g #{group}`,rw #{data[:guestpath]}" + + command = "mount -t cifs //#{result["host_ip"]}/#{data[:share_name]} #{mount_options}" + + @env[:machine].communicate.sudo(command) + + rescue RuntimeError => e + @env[:ui].error(e.message) + end + end + + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/start_instance.rb b/plugins/providers/hyperv/action/start_instance.rb new file mode 100644 index 000000000..66bdad6ee --- /dev/null +++ b/plugins/providers/hyperv/action/start_instance.rb @@ -0,0 +1,29 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + module Action + class StartInstance + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info('Starting the Machine') + options = { vm_id: env[:machine].id } + begin + response = env[:machine].provider.driver.execute('start_vm.ps1', options) + env[:ui].info "Machine #{response["name"]} started" + rescue Error::SubprocessError => e + env[:ui].info e.message + return + end + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/stop_instance.rb b/plugins/providers/hyperv/action/stop_instance.rb new file mode 100644 index 000000000..7dc2244ec --- /dev/null +++ b/plugins/providers/hyperv/action/stop_instance.rb @@ -0,0 +1,24 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "log4r" +module VagrantPlugins + module HyperV + module Action + class StopInstance + def initialize(app, env) + @app = app + end + + def call(env) + env[:ui].info('Stopping the Machine') + options = { vm_id: env[:machine].id } + response = env[:machine].provider.driver.execute('stop_vm.ps1', options) + @app.call(env) + end + end + end + end +end diff --git a/plugins/providers/hyperv/action/sync_folders.rb b/plugins/providers/hyperv/action/sync_folders.rb new file mode 100644 index 000000000..baf38d76e --- /dev/null +++ b/plugins/providers/hyperv/action/sync_folders.rb @@ -0,0 +1,74 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "debugger" +require "log4r" +require "vagrant/util/subprocess" +require "vagrant/util/which" + +module VagrantPlugins + module HyperV + module Action + class SyncFolders + + def initialize(app, env) + @app = app + @logger = Log4r::Logger.new("vagrant_hyperv::action::sync_folders") + end + + def call(env) + @env = env + @app.call(env) + if env[:machine].config.vm.guest == :windows + sync_folders_to_windows + elsif env[:machine].config.vm.guest == :linux + sync_folders_to_linux + end + end + + def ssh_info + @ssh_info ||= @env[:machine].ssh_info + end + + def sync_folders_to_windows + @env[:machine].config.vm.synced_folders.each do |id, data| + # Ignore disabled shared folders + next if data[:disabled] || data[:smb] + hostpath = File.expand_path(data[:hostpath], @env[:root_path]).gsub("/", "\\") + guestpath = data[:guestpath].gsub("/", "\\") + options = { :guest_ip => ssh_info[:host], + :username => ssh_info[:username], + :host_path => hostpath, + :guest_path => guestpath, + :vm_id => @env[:machine].id, + :password => @env[:machine].provider_config.guest.password } + response = @env[:machine].provider.driver.execute('file_sync.ps1', options) + end + end + + def sync_folders_to_linux + if ssh_info.nil? + @env[:ui].info('SSH Info not available, Aborting Sync folder') + return + end + + @env[:machine].config.vm.synced_folders.each do |id, data| + # Ignore disabled shared folders + next if data[:disabled] || data[:smb] + hostpath = File.expand_path(data[:hostpath], @env[:root_path]) + guestpath = data[:guestpath] + @env[:ui].info('Starting Sync folders') + begin + @env[:machine].communicate.upload(hostpath, guestpath) + rescue RuntimeError => e + @env[:ui].error(e.message) + end + + end + end + + end + end + end +end diff --git a/plugins/providers/hyperv/action/wait_for_state.rb b/plugins/providers/hyperv/action/wait_for_state.rb new file mode 100644 index 000000000..6ed873410 --- /dev/null +++ b/plugins/providers/hyperv/action/wait_for_state.rb @@ -0,0 +1,40 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "log4r" +require "timeout" +require "debugger" + +module VagrantPlugins + module HyperV + module Action + class WaitForState + def initialize(app, env, state, timeout) + @app = app + @state = state + @timeout = timeout + end + + def call(env) + env[:result] = true + # Wait until the Machine's state is disabled (ie State of Halt) + unless env[:machine].state.id == @state + env[:ui].info("Waiting for machine to #{@state}") + begin + Timeout.timeout(@timeout) do + until env[:machine].state.id == @state + sleep 2 + end + end + rescue Timeout::Error + env[:result] = false # couldn't reach state in time + end + end + @app.call(env) + end + + end + end + end +end diff --git a/plugins/providers/hyperv/communicator.rb b/plugins/providers/hyperv/communicator.rb new file mode 100644 index 000000000..883992bb8 --- /dev/null +++ b/plugins/providers/hyperv/communicator.rb @@ -0,0 +1,13 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + module Communicator + lib_path = Pathname.new(File.expand_path("../communicator", __FILE__)) + autoload :SSH, lib_path.join("ssh") + end + end +end diff --git a/plugins/providers/hyperv/config.rb b/plugins/providers/hyperv/config.rb new file mode 100644 index 000000000..5d16c04b4 --- /dev/null +++ b/plugins/providers/hyperv/config.rb @@ -0,0 +1,50 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "vagrant" +require_relative "guest_config/config" +require_relative "host_share/config" +module VagrantPlugins + module HyperV + class Config < Vagrant.plugin("2", :config) + # If set to `true`, then VirtualBox will be launched with a GUI. + # + # @return [Boolean] + attr_accessor :gui + attr_reader :host_share, :guest + + def host_config(&block) + block.call(@host_share) + end + + def guest_config(&block) + block.call(@guest) + end + + def finalize! + @gui = nil if @gui == UNSET_VALUE + end + + def initialize(region_specific=false) + @gui = UNSET_VALUE + @host_share = HostShare::Config.new + @guest = GuestConfig::Config.new + end + + def validate(machine) + errors = _detected_errors + unless host_share.valid_config? + errors << host_share.errors.flatten.join(" ") + end + + unless guest.valid_config? + errors << guest.errors.flatten.join(" ") + end + { "HyperV" => errors } + end + + end + end +end diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb new file mode 100644 index 000000000..be47fb5ce --- /dev/null +++ b/plugins/providers/hyperv/driver.rb @@ -0,0 +1,13 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + module Driver + lib_path = Pathname.new(File.expand_path("../driver", __FILE__)) + autoload :Base, lib_path.join("base") + end + end +end diff --git a/plugins/providers/hyperv/driver/base.rb b/plugins/providers/hyperv/driver/base.rb new file mode 100644 index 000000000..d9dbb636f --- /dev/null +++ b/plugins/providers/hyperv/driver/base.rb @@ -0,0 +1,110 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "debugger" +require "json" +require "vagrant/util/which" +require "vagrant/util/subprocess" + +module VagrantPlugins + module HyperV + module Driver + class Base + attr_reader :vmid + + def initialize(id=nil) + @vmid = id + check_power_shell + @output = nil + end + + def execute(path, options) + r = execute_powershell(path, options) do |type, data| + process_output(type, data) + end + if success? + JSON.parse(json_output[:success].join) unless json_output[:success].empty? + else + message = json_output[:error].join unless json_output[:error].empty? + raise Error::SubprocessError, message if message + end + end + + def raw_execute(command) + command = [command , {notify: [:stdout, :stderr, :stdin]}].flatten + clear_output_buffer + Vagrant::Util::Subprocess.execute(*command) do |type, data| + process_output(type, data) + end + end + + protected + + def json_output + return @json_output if @json_output + json_success_begin = false + json_error_begin = false + success = [] + error = [] + @output.split("\n").each do |line| + json_error_begin = false if line.include?("===End-Error===") + json_success_begin = false if line.include?("===End-Output===") + message = "" + if json_error_begin || json_success_begin + message = line.gsub("\\'","\"") + end + success << message if json_success_begin + error << message if json_error_begin + json_success_begin = true if line.include?("===Begin-Output===") + json_error_begin = true if line.include?("===Begin-Error===") + end + @json_output = { :success => success, :error => error } + end + + def success? + @error_messages.empty? && json_output[:error].empty? + end + + def process_output(type, data) + if type == :stdout + @output = data.gsub("\r\n", "\n") + end + if type == :stdin + # $stdin.gets.chomp || "" + end + if type == :stderr + @error_messages = data.gsub("\r\n", "\n") + end + end + + def clear_output_buffer + @output = "" + @error_messages = "" + @json_output = nil + end + + def check_power_shell + unless Vagrant::Util::Which.which('powershell') + raise "Power Shell not found" + end + end + + def execute_powershell(path, options, &block) + lib_path = Pathname.new(File.expand_path("../../scripts", __FILE__)) + path = lib_path.join(path).to_s.gsub("/", "\\") + options = options || {} + ps_options = [] + options.each do |key, value| + ps_options << "-#{key}" + ps_options << "'#{value}'" + end + clear_output_buffer + command = ["powershell", "-NoProfile", "-ExecutionPolicy", + "Bypass", path, ps_options, {notify: [:stdout, :stderr, :stdin]}].flatten + Vagrant::Util::Subprocess.execute(*command, &block) + end + end + end + end +end diff --git a/plugins/providers/hyperv/error.rb b/plugins/providers/hyperv/error.rb new file mode 100644 index 000000000..1b6cb1cc1 --- /dev/null +++ b/plugins/providers/hyperv/error.rb @@ -0,0 +1,13 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + module Error + lib_path = Pathname.new(File.expand_path("../error", __FILE__)) + autoload :SubprocessError, lib_path.join("subprocess_error") + end + end +end diff --git a/plugins/providers/hyperv/error/subprocess_error.rb b/plugins/providers/hyperv/error/subprocess_error.rb new file mode 100644 index 000000000..06773cdc6 --- /dev/null +++ b/plugins/providers/hyperv/error/subprocess_error.rb @@ -0,0 +1,24 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +require "json" +require "vagrant/util/which" +require "vagrant/util/subprocess" + +module VagrantPlugins + module HyperV + module Error + class SubprocessError < RuntimeError + def initialize(message) + @message = JSON.parse(message) if message + end + + def message + @message["error"] + end + end + end + end +end diff --git a/plugins/providers/hyperv/guest_config/config.rb b/plugins/providers/hyperv/guest_config/config.rb new file mode 100644 index 000000000..5b02d5a21 --- /dev/null +++ b/plugins/providers/hyperv/guest_config/config.rb @@ -0,0 +1,33 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +module VagrantPlugins + module HyperV + module GuestConfig + class Config < Vagrant.plugin("2", :config) + attr_accessor :username, :password + + def errors + @errors + end + + def validate + @errors = [] + if username.nil? + @errors << "Please configure a Guest VM's username" + end + if password.nil? + @errors << "Please configure a Guest VM's password" + end + end + + def valid_config? + validate + errors.empty? + end + + end + end + end +end diff --git a/plugins/providers/hyperv/host_share/config.rb b/plugins/providers/hyperv/host_share/config.rb new file mode 100644 index 000000000..93a4e9cda --- /dev/null +++ b/plugins/providers/hyperv/host_share/config.rb @@ -0,0 +1,33 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +module VagrantPlugins + module HyperV + module HostShare + class Config < Vagrant.plugin("2", :config) + attr_accessor :username, :password + + def errors + @errors + end + + def validate + @errors = [] + if username.nil? + @errors << "Please configure a Windows user account to share folders" + end + if password.nil? + @errors << "Please configure a Windows user account password to share folders" + end + end + + def valid_config? + validate + errors.empty? + end + + end + end + end +end diff --git a/plugins/providers/hyperv/plugin.rb b/plugins/providers/hyperv/plugin.rb new file mode 100644 index 000000000..5fdff60af --- /dev/null +++ b/plugins/providers/hyperv/plugin.rb @@ -0,0 +1,76 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +begin + require "vagrant" +rescue LoadError + raise "The Vagrant Hyper-V plugin must be run within Vagrant." +end + +# This is a sanity check to make sure no one is attempting to install +# this into an early Vagrant version. +if Vagrant::VERSION < "1.3.5" + raise "The Vagrant Hyper-V plugin is only compatible with Vagrant 1.3+" +end + +module VagrantPlugins + module HyperV + class Plugin < Vagrant.plugin("2") + name "HyperV" + description <<-DESC + This plugin installs a provider that allows Vagrant to manage + machines in Hyper-V. + DESC + + config(:hyperv, :provider) do + require_relative "config" + Config + end + + provider(:hyperv, parallel: true) do + # Setup logging and i18n + # setup_logging + # setup_i18n + + # Return the provider + require_relative "provider" + Provider + end + + # This initializes the internationalization strings. + def self.setup_i18n + + end + + # This sets up our log level to be whatever VAGRANT_LOG is. + def self.setup_logging + require "log4r" + level = nil + begin + level = Log4r.const_get(ENV["VAGRANT_LOG"].upcase) + rescue NameError + # This means that the logging constant wasn't found, + # which is fine. We just keep `level` as `nil`. But + # we tell the user. + level = nil + end + + # Some constants, such as "true" resolve to booleans, so the + # above error checking doesn't catch it. This will check to make + # sure that the log level is an integer, as Log4r requires. + level = nil if !level.is_a?(Integer) + + # Set the logging level on all "vagrant" namespaced + # logs as long as we have a valid level. + if level + logger = Log4r::Logger.new("vagrant_hyperv") + logger.outputters = Log4r::Outputter.stderr + logger.level = level + logger = nil + end + end + end + end +end diff --git a/plugins/providers/hyperv/provider.rb b/plugins/providers/hyperv/provider.rb new file mode 100644 index 000000000..f1bfcd4a2 --- /dev/null +++ b/plugins/providers/hyperv/provider.rb @@ -0,0 +1,61 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- +require "log4r" +require "vagrant" + +module VagrantPlugins + module HyperV + class Provider < Vagrant.plugin("2", :provider) + + def initialize(machine) + @machine = machine + end + + def action(name) + # Attempt to get the action method from the Action class if it + # exists, otherwise return nil to show that we don't support the + # given action. + action_method = "action_#{name}" + return Action.send(action_method) if Action.respond_to?(action_method) + nil + end + + def state + # Run a custom action we define called "read_state" which does + # what it says. It puts the state in the `:machine_state_id` + # key in the environment. + env = @machine.action("read_state") + state_id = env[:machine_state_id] + + # Get the short and long description + # TODO + short = "Machine's current state is #{state_id}" + long = "" + + # Return the MachineState object + Vagrant::MachineState.new(state_id, short, long) + end + + def to_s + id = @machine.id.nil? ? "new" : @machine.id + "Hyper-V (#{id})" + end + + def ssh_info + # Run a custom action called "read_guest_ip" which does what it + # says and puts the resulting SSH info into the `:machine_ssh_info` + # key in the environment. + env = @machine.action("read_guest_ip") + if env[:machine_ssh_info] + env[:machine_ssh_info].merge!(:port => 22) + end + end + + def driver + @driver ||= Driver::Base.new() + end + end + end +end diff --git a/plugins/providers/hyperv/scripts/file_sync.ps1 b/plugins/providers/hyperv/scripts/file_sync.ps1 new file mode 100644 index 000000000..4caad2bde --- /dev/null +++ b/plugins/providers/hyperv/scripts/file_sync.ps1 @@ -0,0 +1,123 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required."), + [string]$guest_ip = $(throw "-guest_ip is required."), + [string]$username = $(throw "-guest_username is required."), + [string]$password = $(throw "-guest_password is required."), + [string]$host_path = $(throw "-host_path is required."), + [string]$guest_path = $(throw "-guest_path is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +function Get-file-hash($source_path, $delimiter) { + $source_files = @() + (Get-ChildItem $source_path -rec | ForEach-Object -Process { + Get-FileHash -Path $_.FullName -Algorithm MD5 } ) | + ForEach-Object -Process { + $source_files += $_.Path.Replace($source_path, "") + $delimiter + $_.Hash + } + $source_files +} + +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 +} + +function Get-remote-file-hash($source_path, $delimiter, $session) { + Invoke-Command -Session $session -ScriptBlock ${function:Get-file-hash} -ArgumentList $source_path, $delimiter + # TODO: + # Check if remote PS Scripting errors +} + +function Sync-Remote-Machine($machine, $remove_files, $copy_files, $host_path, $guest_path) { + ForEach ($item in $copy_files) { + $from = $host_path + $item + $to = $guest_path + $item + # Copy VM can also take a VM object + Copy-VMFile -VM $machine -SourcePath $from -DestinationPath $to -CreateFullPath -FileSource Host -Force + } +} + +function Create-Remote-Folders($empty_source_folders, $guest_path) { + ForEach ($item in $empty_source_folders) { + $new_name = $guest_path + $item + New-Item "$new_name" -type directory -Force + } +} + +function Get-Empty-folders-From-Source($host_path) { + Get-ChildItem $host_path -recurse | + Where-Object {$_.PSIsContainer -eq $True} | + Where-Object {$_.GetFiles().Count -eq 0} | + Select-Object FullName | ForEach-Object -Process { + $empty_source_folders += ($_.FullName.Replace($host_path, "")) + } +} + +$delimiter = " || " + +$machine = Get-VM -Id $vm_id + +# FIXME: PowerShell guys please fix this. +# The below script checks for all VMIntegrationService which are not enabled +# and will enable this. +# When when all the services are enabled this throws an error. +# Enable VMIntegrationService to true +try { + Get-VM -Id $vm_id | Get-VMIntegrationService -Name "Guest Service Interface" | Enable-VMIntegrationService -Passthru + } + catch { } + +$session = Get-Remote-Session $guest_ip $username $password + +$source_files = Get-file-hash $host_path $delimiter +$destination_files = Get-remote-file-hash $guest_path $delimiter $session + +if (!$destination_files) { + $destination_files = @() +} +if (!$source_files) { + $source_files = @() +} + +# Compare source and destination files +$remove_files = @() +$copy_files = @() + + +Compare-Object -ReferenceObject $source_files -DifferenceObject $destination_files | ForEach-Object { + if ($_.SideIndicator -eq '=>') { + $remove_files += $_.InputObject.Split($delimiter)[0] + } else { + $copy_files += $_.InputObject.Split($delimiter)[0] + } +} + +# Update the files to remote machine +Sync-Remote-Machine $machine $remove_files $copy_files $host_path $guest_path + +# Create any empty folders which missed to sync to remote machine +$empty_source_folders = @() +$directories = Get-Empty-folders-From-Source $host_path + +$result = Invoke-Command -Session $session -ScriptBlock ${function:Create-Remote-Folders} -ArgumentList $empty_source_folders, $guest_path +# Always remove the connection after Use +Remove-PSSession -Id $session.Id + +$resultHash = @{ + message = "OK" +} +$result = ConvertTo-Json $resultHash +Write-Output-Message $result + diff --git a/plugins/providers/hyperv/scripts/get_network_config.ps1 b/plugins/providers/hyperv/scripts/get_network_config.ps1 new file mode 100644 index 000000000..bc48163d8 --- /dev/null +++ b/plugins/providers/hyperv/scripts/get_network_config.ps1 @@ -0,0 +1,28 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $vm_id -ErrorAction "stop" + $network = Get-VMNetworkAdapter -VM $vm + $ip_address = $network.IpAddresses[0] + $resultHash = @{ + ip = "$ip_address" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message "Failed to obtain network info of VM $_" +} diff --git a/plugins/providers/hyperv/scripts/get_vm_status.ps1 b/plugins/providers/hyperv/scripts/get_vm_status.ps1 new file mode 100644 index 000000000..d7bfcc0ba --- /dev/null +++ b/plugins/providers/hyperv/scripts/get_vm_status.ps1 @@ -0,0 +1,30 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $vm_id -ErrorAction "stop" + $state = $vm.state + $status = $vm.status + $resultHash = @{ + state = "$state" + status = "$status" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result + +} +catch { + Write-Error-Message $_ +} diff --git a/plugins/providers/hyperv/scripts/host_info.ps1 b/plugins/providers/hyperv/scripts/host_info.ps1 new file mode 100644 index 000000000..6b655f9dd --- /dev/null +++ b/plugins/providers/hyperv/scripts/host_info.ps1 @@ -0,0 +1,25 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { +$hostname = $(whoami) +$ip = (Get-WmiObject -class win32_NetworkAdapterConfiguration -Filter 'ipenabled = "true"').ipaddress[0] + $resultHash = @{ + host_name = "$username" + host_ip = "$ip" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message $_ +} + diff --git a/plugins/providers/hyperv/scripts/import_vm.ps1 b/plugins/providers/hyperv/scripts/import_vm.ps1 new file mode 100644 index 000000000..587d0394b --- /dev/null +++ b/plugins/providers/hyperv/scripts/import_vm.ps1 @@ -0,0 +1,150 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + + +param ( + [string]$vm_xml_config = $(throw "-vm_xml_config is required."), + [string]$vhdx_path = $(throw "-vhdx_path is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + [xml]$vmconfig = Get-Content -Path $vm_xml_config + + $vm_name = $vmconfig.configuration.properties.name.'#text' + $processors = $vmconfig.configuration.settings.processors.count.'#text' + + function Get_unique_name($name) { + Get-VM | ForEach-Object -Process { + if ($name -eq $_.Name) { + $name = $name + "_1" + } + } + return $name + } + + do { + $name = $vm_name + $vm_name = Get_unique_name $name + } while ($vm_name -ne $name) + + $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 + + # Get the name of the virtual switch + $switchname = (Select-Xml -xml $vmconfig -XPath "//AltSwitchName").node."#text" + + # 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 + + # 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" + } + + # Create the VM using the values in the hash map + + $vm = 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) + } + + # Set the values on the VM + $vm | Set-VM @more_vm_params -Passthru + + # Add drives to the virtual machine + $controllers = Select-Xml -xml $vmconfig -xpath "//*[starts-with(name(.),'controller')]" + # 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 = $vhdx_path + } + if ($drive.pool_id."#text") { + $ResourcePoolName = $drive.pool_id."#text" + $addDriveParam.Add("ResourcePoolname",$ResourcePoolName) + } + + if ($drivetype -eq 'VHD') { + $addDriveParam.add("ControllerType",$ControllerType) + $vm | Add-VMHardDiskDrive @AddDriveparam + } + } + } + + $vm_id = (Get-VM $vm_name).id.guid + $resultHash = @{ + name = $vm_name + id = $vm_id + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message $_ + return +} diff --git a/plugins/providers/hyperv/scripts/mount_share.ps1 b/plugins/providers/hyperv/scripts/mount_share.ps1 new file mode 100644 index 000000000..de099e0a9 --- /dev/null +++ b/plugins/providers/hyperv/scripts/mount_share.ps1 @@ -0,0 +1,62 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + + +param ( + [string]$share_name = $(throw "-share_name is required."), + [string]$guest_path = $(throw "-guest_path is required."), + [string]$guest_ip = $(throw "-guest_ip is required."), + [string]$username = $(throw "-username is required."), + [string]$password = $(throw "-password is required."), + [string]$host_ip = $(throw "-host_ip is required."), + [string]$host_share_username = $(throw "-host_share_username is required."), + [string]$host_share_password = $(throw "-host_share_password is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\create_session.ps1" +$modules += $presentDir + "\utils\write_messages.ps1" + +forEach ($module in $modules) { . $module } + +try { + function Mount-File($share_name, $guest_path, $host_path, $host_share_username, $host_share_password) { + try { + # TODO: Check for folder exist. + # Use net use and prompt for password + $guest_path = $guest_path.replace("/", "\") + # Map a network drive to the guest machine + $result = net use * $host_path /user:$host_share_username $host_share_password /persistent:yes + $mapped_drive = (($result -match "\w:") -split (" "))[1] + Write-Host cmd /c mklink /d $guest_path $mapped_drive + # If a folder exist remove it. + if (Test-Path $guest_path) { + $junction = Get-Item $guest_path + $junction.Delete() + } + cmd /c mklink /d $guest_path $mapped_drive + } catch { + return $_ + } + } + + $response = Create-Remote-Session $guest_ip $username $password + + if (!$response["session"] -and $response["error"]) { + Write-Error-Message $response["error"] + return + } + $host_path = "\\$host_ip\$share_name" + $host_share_username = "$host_ip\$host_share_username" + $result = Invoke-Command -Session $response["session"] -ScriptBlock ${function:Mount-File} -ArgumentList $share_name, $guest_path, $host_path, $host_share_username, $host_share_password -ErrorAction "stop" + Remove-PSSession -Id $response["session"].Id + Write-Error-Message $result +} +catch { + Write-Error-Message "Failed to mount files VM $_" + return +} diff --git a/plugins/providers/hyperv/scripts/set_smb_share.ps1 b/plugins/providers/hyperv/scripts/set_smb_share.ps1 new file mode 100644 index 000000000..6f64183c8 --- /dev/null +++ b/plugins/providers/hyperv/scripts/set_smb_share.ps1 @@ -0,0 +1,46 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$path = $(throw "-path is required."), + [string]$share_name = $(throw "-share_name is required."), + [string]$host_share_username = $(throw "-host_share_username is required") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + # See all available shares and check alert user for existing / conflicting share name + $shared_folders = net share + $reg = "$share_name(\s+)$path(\s)" + $existing_share = $shared_folders -Match $reg + if ($existing_share) { + # Always clear the existing share name and create a new one + net share $share_name /delete /y + } + + $computer_name = $(Get-WmiObject Win32_Computersystem).name + $grant_permission = "$computer_name\$host_share_username,Full" + $result = net share $share_name=$path /unlimited /GRANT:$grant_permission + if ($result -Match "$share_name was shared successfully.") { + $resultHash = @{ + message = "OK" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result + } else { + $reg = "^$share_name(\s+)" + $existing_share = $shared_folders -Match $reg + Write-Error-Message "IGNORING Conflicting share name, A share name already exist $existing_share" + } +} catch { + Write-Error-Message $_ + return +} + diff --git a/plugins/providers/hyperv/scripts/start_vm.ps1 b/plugins/providers/hyperv/scripts/start_vm.ps1 new file mode 100644 index 000000000..6ccf45111 --- /dev/null +++ b/plugins/providers/hyperv/scripts/start_vm.ps1 @@ -0,0 +1,32 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $vm_id -ErrorAction "stop" + Start-VM $vm + $state = $vm.state + $status = $vm.status + $name = $vm.name + $resultHash = @{ + state = "$state" + status = "$status" + name = "$name" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message "Failed to start a VM $_" +} diff --git a/plugins/providers/hyperv/scripts/stop_vm.ps1 b/plugins/providers/hyperv/scripts/stop_vm.ps1 new file mode 100644 index 000000000..759930b68 --- /dev/null +++ b/plugins/providers/hyperv/scripts/stop_vm.ps1 @@ -0,0 +1,31 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +param ( + [string]$vm_id = $(throw "-vm_id is required.") + ) + +# Include the following modules +$presentDir = Split-Path -parent $PSCommandPath +$modules = @() +$modules += $presentDir + "\utils\write_messages.ps1" +forEach ($module in $modules) { . $module } + +try { + $vm = Get-VM -Id $vm_id -ErrorAction stop + # Shuts down virtual machine regardless of any unsaved application data + Stop-VM $vm -Force + $state = $vm.state + $status = $vm.status + $resultHash = @{ + state = "$state" + status = "$status" + } + $result = ConvertTo-Json $resultHash + Write-Output-Message $result +} +catch { + Write-Error-Message "Failed to stop a VM $_" +} diff --git a/plugins/providers/hyperv/scripts/utils/create_session.ps1 b/plugins/providers/hyperv/scripts/utils/create_session.ps1 new file mode 100644 index 000000000..fcf0f7f10 --- /dev/null +++ b/plugins/providers/hyperv/scripts/utils/create_session.ps1 @@ -0,0 +1,34 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +function Get-Remote-Session($guest_ip, $username, $password) { + $secstr = convertto-securestring -AsPlainText -Force -String $password + $cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $username, $secstr + New-PSSession -ComputerName $guest_ip -Credential $cred -ErrorAction "stop" +} + +function Create-Remote-Session($guest_ip, $username, $password) { + $count = 0 + $session_error = "" + $session = "" + do { + $count++ + try { + $session = Get-Remote-Session $guest_ip $username $password + $session_error = "" + } + catch { + Start-Sleep -s 1 + $session_error = $_ + $session = "" + } + } + while (!$session -and $count -lt 20) + + return @{ + session = $session + error = $session_error + } +} diff --git a/plugins/providers/hyperv/scripts/utils/write_messages.ps1 b/plugins/providers/hyperv/scripts/utils/write_messages.ps1 new file mode 100644 index 000000000..ce0d8d8cf --- /dev/null +++ b/plugins/providers/hyperv/scripts/utils/write_messages.ps1 @@ -0,0 +1,20 @@ +#------------------------------------------------------------------------- +# 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===" +} diff --git a/plugins/providers/hyperv/version.rb b/plugins/providers/hyperv/version.rb new file mode 100644 index 000000000..6278a48c7 --- /dev/null +++ b/plugins/providers/hyperv/version.rb @@ -0,0 +1,10 @@ +#------------------------------------------------------------------------- +# Copyright (c) Microsoft Open Technologies, Inc. +# All Rights Reserved. Licensed under the MIT License. +#-------------------------------------------------------------------------- + +module VagrantPlugins + module HyperV + VERSION = "0.0.1" + end +end