vagrant-windows-hyperv 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|