vagrant-windows-hyperv 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +7 -0
- data/README.md +89 -0
- data/Rakefile +29 -0
- data/example_box/metadata.json +1 -0
- data/lib/vagrant-windows-hyperv.rb +38 -0
- data/lib/vagrant-windows-hyperv/action.rb +81 -0
- data/lib/vagrant-windows-hyperv/action/export.rb +54 -0
- data/lib/vagrant-windows-hyperv/action/package.rb +21 -0
- data/lib/vagrant-windows-hyperv/action/rdp.rb +49 -0
- data/lib/vagrant-windows-hyperv/action/setup_package_files.rb +55 -0
- data/lib/vagrant-windows-hyperv/command/rdp/command.rb +22 -0
- data/lib/vagrant-windows-hyperv/communication/powershell.rb +37 -0
- data/lib/vagrant-windows-hyperv/driver.rb +89 -0
- data/lib/vagrant-windows-hyperv/errors.rb +31 -0
- data/lib/vagrant-windows-hyperv/guest/cap/halt.rb +19 -0
- data/lib/vagrant-windows-hyperv/guest/windows.rb +25 -0
- data/lib/vagrant-windows-hyperv/monkey_patch/action/provision.rb +32 -0
- data/lib/vagrant-windows-hyperv/monkey_patch/machine.rb +22 -0
- data/lib/vagrant-windows-hyperv/monkey_patch/plugins/synced_folders/smb/synced_folders.rb +55 -0
- data/lib/vagrant-windows-hyperv/monkey_patch/util/powershell.rb +37 -0
- data/lib/vagrant-windows-hyperv/plugin.rb +85 -0
- data/lib/vagrant-windows-hyperv/provider.rb +30 -0
- data/lib/vagrant-windows-hyperv/provisioner/chef_solo.rb +181 -0
- data/lib/vagrant-windows-hyperv/provisioner/puppet.rb +99 -0
- data/lib/vagrant-windows-hyperv/provisioner/shell.rb +81 -0
- data/lib/vagrant-windows-hyperv/scripts/check_winrm.ps1 +41 -0
- data/lib/vagrant-windows-hyperv/scripts/export_vm.ps1 +31 -0
- data/lib/vagrant-windows-hyperv/scripts/file_sync.ps1 +145 -0
- data/lib/vagrant-windows-hyperv/scripts/host_info.ps1 +25 -0
- data/lib/vagrant-windows-hyperv/scripts/hyperv_manager.ps1 +36 -0
- data/lib/vagrant-windows-hyperv/scripts/run_in_remote.ps1 +31 -0
- data/lib/vagrant-windows-hyperv/scripts/upload_file.ps1 +95 -0
- data/lib/vagrant-windows-hyperv/scripts/utils/create_session.ps1 +34 -0
- data/lib/vagrant-windows-hyperv/scripts/utils/write_messages.ps1 +18 -0
- data/lib/vagrant-windows-hyperv/version.rb +10 -0
- data/locales/en.yml +15 -0
- data/spec/hyper-v/config_spec.rb +36 -0
- data/spec/hyper-v/spec_helper.rb +9 -0
- data/templates/provisioners/chef-solo/solo.erb +51 -0
- data/vagrant-windows-hyperv.gemspec +63 -0
- data/vagrantfile_examples/Vagrantfile_linux +23 -0
- data/vagrantfile_examples/Vagrantfile_windows +24 -0
- metadata +171 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
#-------------------------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Open Technologies, Inc.
|
3
|
+
# All Rights Reserved. Licensed under the Apache 2.0 License.
|
4
|
+
#--------------------------------------------------------------------------
|
5
|
+
|
6
|
+
require "#{Vagrant::source_root}/plugins/providers/hyperv/provider"
|
7
|
+
|
8
|
+
module VagrantPlugins
|
9
|
+
module VagrantHyperV
|
10
|
+
class Provider < VagrantPlugins::HyperV::Provider
|
11
|
+
|
12
|
+
def machine_id_changed
|
13
|
+
@driver = Driver.new(@machine)
|
14
|
+
end
|
15
|
+
|
16
|
+
def action(name)
|
17
|
+
# Attempt to get the action method from the Action class if it
|
18
|
+
# exists, otherwise return nil to show that we don't support the
|
19
|
+
# given action.
|
20
|
+
action_method = "action_#{name}"
|
21
|
+
if Action.respond_to?(action_method)
|
22
|
+
return Action.send(action_method)
|
23
|
+
else
|
24
|
+
super
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,181 @@
|
|
1
|
+
#-------------------------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Open Technologies, Inc.
|
3
|
+
# All Rights Reserved. Licensed under the Apache 2.0 License.
|
4
|
+
#--------------------------------------------------------------------------
|
5
|
+
|
6
|
+
require "fileutils"
|
7
|
+
require "tempfile"
|
8
|
+
|
9
|
+
module VagrantPlugins
|
10
|
+
module VagrantHyperV
|
11
|
+
module Provisioner
|
12
|
+
class ChefSolo
|
13
|
+
attr_reader :provisioner
|
14
|
+
def initialize(env)
|
15
|
+
@env = env
|
16
|
+
@provisioner = env[:provisioner]
|
17
|
+
end
|
18
|
+
|
19
|
+
def provision_for_windows
|
20
|
+
# TODO
|
21
|
+
# Verify that the proper shared folders exist.
|
22
|
+
|
23
|
+
# Copy the chef cookbooks roles data bags and environment folders to Guest
|
24
|
+
copy_folder_to_guest(provisioner.cookbook_folders)
|
25
|
+
copy_folder_to_guest(provisioner.role_folders)
|
26
|
+
copy_folder_to_guest(provisioner.data_bags_folders)
|
27
|
+
copy_folder_to_guest(provisioner.environments_folders)
|
28
|
+
|
29
|
+
# Upload Encrypted data bag
|
30
|
+
upload_encrypted_data_bag_secret if config.encrypted_data_bag_secret_key_path
|
31
|
+
setup_json
|
32
|
+
setup_solo_config
|
33
|
+
run_chef_solo
|
34
|
+
delete_encrypted_data_bag_secret
|
35
|
+
end
|
36
|
+
|
37
|
+
def setup_json
|
38
|
+
@env[:machine].env.ui.info I18n.t("vagrant.provisioners.chef.json")
|
39
|
+
|
40
|
+
# Get the JSON that we're going to expose to Chef
|
41
|
+
json = config.json
|
42
|
+
json[:run_list] = config.run_list if !config.run_list.empty?
|
43
|
+
json = JSON.pretty_generate(json)
|
44
|
+
|
45
|
+
# Create a temporary file to store the data so we
|
46
|
+
# can upload it
|
47
|
+
temp = Tempfile.new("vagrant")
|
48
|
+
temp.write(json)
|
49
|
+
temp.close
|
50
|
+
|
51
|
+
remote_file = File.join(config.provisioning_path, "dna.json")
|
52
|
+
@env[:machine].provider.driver.upload(temp.path, remote_file)
|
53
|
+
end
|
54
|
+
|
55
|
+
def setup_solo_config
|
56
|
+
cookbooks_path = guest_paths(provisioner.cookbook_folders)
|
57
|
+
roles_path = guest_paths(provisioner.role_folders)
|
58
|
+
data_bags_path = guest_paths(provisioner.data_bags_folders).first
|
59
|
+
environments_path = guest_paths(provisioner.environments_folders).first
|
60
|
+
source_path = "#{VagrantPlugins::VagrantHyperV.source_root}"
|
61
|
+
template_path = source_path + "/templates/provisioners/chef-solo/solo"
|
62
|
+
setup_config(template_path, "solo.rb", {
|
63
|
+
:cookbooks_path => cookbooks_path,
|
64
|
+
:recipe_url => config.recipe_url,
|
65
|
+
:roles_path => roles_path,
|
66
|
+
:data_bags_path => data_bags_path,
|
67
|
+
:environments_path => environments_path
|
68
|
+
})
|
69
|
+
end
|
70
|
+
|
71
|
+
def setup_config(template, filename, template_vars)
|
72
|
+
# If we have custom configuration, upload it
|
73
|
+
remote_custom_config_path = nil
|
74
|
+
if config.custom_config_path
|
75
|
+
expanded = File.expand_path(
|
76
|
+
config.custom_config_path, @machine.env.root_path)
|
77
|
+
remote_custom_config_path = File.join(
|
78
|
+
config.provisioning_path, "custom-config.rb")
|
79
|
+
|
80
|
+
@env[:machine].provider.driver.upload(expanded, remote_custom_config_path)
|
81
|
+
end
|
82
|
+
|
83
|
+
config_file = Vagrant::Util::TemplateRenderer.render(template, {
|
84
|
+
:custom_configuration => remote_custom_config_path,
|
85
|
+
:file_cache_path => config.file_cache_path,
|
86
|
+
:file_backup_path => config.file_backup_path,
|
87
|
+
:log_level => config.log_level.to_sym,
|
88
|
+
:verbose_logging => config.verbose_logging,
|
89
|
+
:http_proxy => config.http_proxy,
|
90
|
+
:http_proxy_user => config.http_proxy_user,
|
91
|
+
:http_proxy_pass => config.http_proxy_pass,
|
92
|
+
:https_proxy => config.https_proxy,
|
93
|
+
:https_proxy_user => config.https_proxy_user,
|
94
|
+
:https_proxy_pass => config.https_proxy_pass,
|
95
|
+
:no_proxy => config.no_proxy,
|
96
|
+
:formatter => config.formatter
|
97
|
+
}.merge(template_vars))
|
98
|
+
|
99
|
+
# Create a temporary file to store the data so we
|
100
|
+
# can upload it
|
101
|
+
temp = Tempfile.new("vagrant")
|
102
|
+
temp.write(config_file)
|
103
|
+
temp.close
|
104
|
+
|
105
|
+
remote_file = File.join(config.provisioning_path, filename)
|
106
|
+
@env[:machine].provider.driver.upload(temp.path, remote_file)
|
107
|
+
end
|
108
|
+
|
109
|
+
def run_chef_solo
|
110
|
+
if config.run_list && config.run_list.empty?
|
111
|
+
@env[:machine].ui.warn(I18n.t("vagrant.chef_run_list_empty"))
|
112
|
+
end
|
113
|
+
|
114
|
+
options = [
|
115
|
+
"-c #{config.provisioning_path}/solo.rb",
|
116
|
+
"-j #{config.provisioning_path}/dna.json"
|
117
|
+
]
|
118
|
+
|
119
|
+
command_env = config.binary_env ? "#{config.binary_env} " : ""
|
120
|
+
command_args = config.arguments ? " #{config.arguments}" : ""
|
121
|
+
command = "#{command_env}#{chef_binary_path("chef-solo")} " +
|
122
|
+
"#{options.join(" ")} #{command_args}"
|
123
|
+
config.attempts.times do |attempt|
|
124
|
+
if attempt == 0
|
125
|
+
@env[:machine].env.ui.info I18n.t("vagrant.provisioners.chef.running_solo")
|
126
|
+
else
|
127
|
+
@env[:machine].env.ui.info I18n.t("vagrant.provisioners.chef.running_solo_again")
|
128
|
+
end
|
129
|
+
|
130
|
+
@env[:machine].provider.driver.run_remote_ps(command) do |type, data|
|
131
|
+
# Output the data with the proper color based on the stream.
|
132
|
+
if (type == :stdout || type == :stderr)
|
133
|
+
@env[:ui].detail data
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
def delete_encrypted_data_bag_secret
|
141
|
+
# Run remote command to delete
|
142
|
+
# config.encrypted_data_bag_secret
|
143
|
+
end
|
144
|
+
|
145
|
+
def upload_encrypted_data_bag_secret
|
146
|
+
@machine.env.ui.info I18n.t("vagrant.provisioners.chef.upload_encrypted_data_bag_secret_key")
|
147
|
+
@env[:machine].provider.driver.upload(encrypted_data_bag_secret_key_path,
|
148
|
+
config.encrypted_data_bag_secret)
|
149
|
+
end
|
150
|
+
|
151
|
+
def encrypted_data_bag_secret_key_path
|
152
|
+
File.expand_path(config.encrypted_data_bag_secret_key_path, @env[:machine].env.root_path)
|
153
|
+
end
|
154
|
+
|
155
|
+
def config
|
156
|
+
provisioner.config
|
157
|
+
end
|
158
|
+
|
159
|
+
def guest_paths(folders)
|
160
|
+
folders.map { |parts| parts[2] }
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns the path to the Chef binary, taking into account the
|
164
|
+
# `binary_path` configuration option.
|
165
|
+
def chef_binary_path(binary)
|
166
|
+
return binary if !config.binary_path
|
167
|
+
return File.join(config.binary_path, binary)
|
168
|
+
end
|
169
|
+
|
170
|
+
def copy_folder_to_guest(folders)
|
171
|
+
folders.each do |type, local_path, remote_path|
|
172
|
+
if type == :host
|
173
|
+
@env[:machine].provider.driver.upload(local_path, remote_path)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
#-------------------------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Open Technologies, Inc.
|
3
|
+
# All Rights Reserved. Licensed under the Apache 2.0 License.
|
4
|
+
#--------------------------------------------------------------------------
|
5
|
+
|
6
|
+
require "fileutils"
|
7
|
+
require "tempfile"
|
8
|
+
|
9
|
+
module VagrantPlugins
|
10
|
+
module VagrantHyperV
|
11
|
+
module Provisioner
|
12
|
+
class Puppet
|
13
|
+
attr_reader :provisioner
|
14
|
+
def initialize(env)
|
15
|
+
@env = env
|
16
|
+
@provisioner = env[:provisioner]
|
17
|
+
end
|
18
|
+
|
19
|
+
def provision_for_windows
|
20
|
+
|
21
|
+
options = [config.options].flatten
|
22
|
+
@module_paths = provisioner.instance_variable_get("@module_paths")
|
23
|
+
@hiera_config_path = provisioner.instance_variable_get("@hiera_config_path")
|
24
|
+
@manifest_file = provisioner.instance_variable_get("@manifest_file")
|
25
|
+
|
26
|
+
# Copy the manifests directory to the guest
|
27
|
+
if config.manifests_path[0].to_sym == :host
|
28
|
+
@env[:machine].provider.driver.upload(
|
29
|
+
File.expand_path(config.manifests_path[1], @env[:machine].env.root_path),
|
30
|
+
provisioner.manifests_guest_path)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Copy the module paths to the guest
|
34
|
+
@module_paths.each do |from, to|
|
35
|
+
@env[:machine].provider.driver.upload(from, to)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
module_paths = @module_paths.map { |_, to| to }
|
40
|
+
if !@module_paths.empty?
|
41
|
+
# Prepend the default module path
|
42
|
+
module_paths.unshift("/ProgramData/PuppetLabs/puppet/etc/modules")
|
43
|
+
|
44
|
+
# Add the command line switch to add the module path
|
45
|
+
options << "--modulepath '#{module_paths.join(':')}'"
|
46
|
+
end
|
47
|
+
|
48
|
+
if @hiera_config_path
|
49
|
+
options << "--hiera_config=#{@hiera_config_path}"
|
50
|
+
|
51
|
+
# Upload Hiera configuration if we have it
|
52
|
+
local_hiera_path = File.expand_path(config.hiera_config_path,
|
53
|
+
@env[:machine].env.root_path)
|
54
|
+
@env[:machine].provider.driver.upload(local_hiera_path, @hiera_config_path)
|
55
|
+
end
|
56
|
+
|
57
|
+
options << "--manifestdir #{provisioner.manifests_guest_path}"
|
58
|
+
options << "--detailed-exitcodes"
|
59
|
+
options << @manifest_file
|
60
|
+
options = options.join(" ")
|
61
|
+
|
62
|
+
# Build up the custom facts if we have any
|
63
|
+
facter = ""
|
64
|
+
if !config.facter.empty?
|
65
|
+
facts = []
|
66
|
+
config.facter.each do |key, value|
|
67
|
+
facts << "FACTER_#{key}='#{value}'"
|
68
|
+
end
|
69
|
+
|
70
|
+
facter = "#{facts.join(" ")} "
|
71
|
+
end
|
72
|
+
|
73
|
+
command = "#{facter}puppet apply #{options}"
|
74
|
+
if config.working_directory
|
75
|
+
command = "cd #{config.working_directory} && #{command}"
|
76
|
+
end
|
77
|
+
|
78
|
+
@env[:ui].info I18n.t("vagrant.provisioners.puppet.running_puppet",
|
79
|
+
:manifest => config.manifest_file)
|
80
|
+
|
81
|
+
@env[:ui].info "Executing puppet script in Guest"
|
82
|
+
@env[:machine].provider.driver.run_remote_ps(command) do |type, data|
|
83
|
+
# Output the data with the proper color based on the stream.
|
84
|
+
if (type == :stdout || type == :stderr)
|
85
|
+
@env[:ui].detail data
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
def config
|
93
|
+
provisioner.config
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#-------------------------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Open Technologies, Inc.
|
3
|
+
# All Rights Reserved. Licensed under the Apache 2.0 License.
|
4
|
+
#--------------------------------------------------------------------------
|
5
|
+
|
6
|
+
module VagrantPlugins
|
7
|
+
module VagrantHyperV
|
8
|
+
module Provisioner
|
9
|
+
class Shell
|
10
|
+
attr_reader :provisioner
|
11
|
+
def initialize(env)
|
12
|
+
@env = env
|
13
|
+
@provisioner = env[:provisioner]
|
14
|
+
end
|
15
|
+
|
16
|
+
def provision_for_windows
|
17
|
+
arguments = ""
|
18
|
+
arguments = " #{config.args}" if config.args
|
19
|
+
with_windows_script_file do |path|
|
20
|
+
|
21
|
+
guest_path = if File.extname(config.upload_path) == ""
|
22
|
+
"#{config.upload_path}#{File.extname(path.to_s)}"
|
23
|
+
else
|
24
|
+
config.upload_path
|
25
|
+
end
|
26
|
+
|
27
|
+
response = @env[:machine].provider.driver.upload(path, guest_path)
|
28
|
+
|
29
|
+
command = "powershell.exe #{guest_path} #{arguments}"
|
30
|
+
@env[:machine].provider.driver.run_remote_ps(command) do |type, data|
|
31
|
+
if type == :stdout || type == :stderr
|
32
|
+
@env[:ui].detail data
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def config
|
41
|
+
provisioner.config
|
42
|
+
end
|
43
|
+
|
44
|
+
# This method yields the path to a script to upload and execute
|
45
|
+
# on the remote server. This method will properly clean up the
|
46
|
+
# script file if needed.
|
47
|
+
def with_windows_script_file
|
48
|
+
if config.remote?
|
49
|
+
download_path = @env[:machine].env.tmp_path.join("#{@env[:machine].id}-remote-script#{File.extname(config.path)}")
|
50
|
+
download_path.delete if download_path.file?
|
51
|
+
|
52
|
+
begin
|
53
|
+
Vagrant::Util::Downloader.new(config.path, download_path).download!
|
54
|
+
yield download_path
|
55
|
+
ensure
|
56
|
+
download_path.delete
|
57
|
+
end
|
58
|
+
|
59
|
+
elsif config.path
|
60
|
+
# Just yield the path to that file...
|
61
|
+
yield config.path
|
62
|
+
else
|
63
|
+
# Otherwise we have an inline script, we need to Tempfile it,
|
64
|
+
# and handle it specially...
|
65
|
+
file = Tempfile.new(['vagrant-powershell', '.ps1'])
|
66
|
+
|
67
|
+
begin
|
68
|
+
file.write(config.inline)
|
69
|
+
file.fsync
|
70
|
+
file.close
|
71
|
+
yield file.path
|
72
|
+
ensure
|
73
|
+
file.close
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#-------------------------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Open Technologies, Inc.
|
3
|
+
# All Rights Reserved. Licensed under the Apache 2.0 License.
|
4
|
+
#--------------------------------------------------------------------------
|
5
|
+
param (
|
6
|
+
[string]$guest_ip = $(throw "-guest_ip is required."),
|
7
|
+
[string]$username = $(throw "-guest_username is required."),
|
8
|
+
[string]$password = $(throw "-guest_password is required.")
|
9
|
+
)
|
10
|
+
|
11
|
+
# Include the following modules
|
12
|
+
$presentDir = Split-Path -parent $PSCommandPath
|
13
|
+
. ([System.IO.Path]::Combine($presentDir, "utils\write_messages.ps1"))
|
14
|
+
. ([System.IO.Path]::Combine($presentDir, "utils\create_session.ps1"))
|
15
|
+
|
16
|
+
try {
|
17
|
+
$response = Create-Remote-Session $guest_ip $username $password
|
18
|
+
if (!$response["session"] -and $response["error"]) {
|
19
|
+
Write-Host $response["error"]
|
20
|
+
return
|
21
|
+
}
|
22
|
+
function Remote-Execute() {
|
23
|
+
$winrm_state = ""
|
24
|
+
get-service winrm | ForEach-Object {
|
25
|
+
$winrm_state = $_.status
|
26
|
+
}
|
27
|
+
return "$winrm_state"
|
28
|
+
}
|
29
|
+
$result = Invoke-Command -Session $response["session"] -ScriptBlock ${function:Remote-Execute} -ErrorAction "stop"
|
30
|
+
$resultHash = @{
|
31
|
+
message = "$result"
|
32
|
+
}
|
33
|
+
Write-Output-Message $resultHash
|
34
|
+
} catch {
|
35
|
+
$errortHash = @{
|
36
|
+
type = "PowerShellError"
|
37
|
+
error ="Failed to copy file $_"
|
38
|
+
}
|
39
|
+
Write-Error-Message $errortHash
|
40
|
+
return
|
41
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#-------------------------------------------------------------------------
|
2
|
+
# Copyright (c) Microsoft Open Technologies, Inc.
|
3
|
+
# All Rights Reserved. Licensed under the Apache 2.0 License.
|
4
|
+
#--------------------------------------------------------------------------
|
5
|
+
|
6
|
+
param (
|
7
|
+
[string]$vm_id = $(throw "-vm_id is required."),
|
8
|
+
[string]$path = $(throw "-path is required.")
|
9
|
+
)
|
10
|
+
|
11
|
+
# Include the following modules
|
12
|
+
$presentDir = Split-Path -parent $PSCommandPath
|
13
|
+
. ([System.IO.Path]::Combine($presentDir, "utils\write_messages.ps1"))
|
14
|
+
|
15
|
+
|
16
|
+
# Export the Virtual Machine
|
17
|
+
try {
|
18
|
+
$vm = Get-Vm -Id $vm_id
|
19
|
+
$vm | Export-VM -Path $path -ErrorAction "stop"
|
20
|
+
$name = $vm.name
|
21
|
+
$resultHash = @{
|
22
|
+
name = "$name"
|
23
|
+
}
|
24
|
+
Write-Output-Message $resultHash
|
25
|
+
} catch {
|
26
|
+
$errortHash = @{
|
27
|
+
type = "PowerShellError"
|
28
|
+
error = "Failed to export a VM $_"
|
29
|
+
}
|
30
|
+
Write-Error-Message $errortHash
|
31
|
+
}
|