kitchen-hyperv 0.11.0 → 0.12.0
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 +4 -4
- data/kitchen-hyperv.gemspec +1 -1
- data/lib/kitchen/driver/hyperv.rb +111 -2
- data/lib/kitchen/driver/hyperv_version.rb +3 -1
- data/lib/kitchen/driver/powershell.rb +80 -9
- data/support/hyperv.ps1 +264 -233
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d5b1645576764131a29498745a319db7a3e3fa166de15481ec0744acb3dbfe42
|
|
4
|
+
data.tar.gz: d82e688587b75d0156c71f3b79200b76e7f4d6601561d73a7445bd7236cdf005
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c82372e025d904e35a675987e01a1bd4805d653d090bb35814d9345c3437a854b87c49db795646afe019c536ab6f6696dd07de834ac279dc62aed31a3a85f543
|
|
7
|
+
data.tar.gz: 16c10a436d35cd4ca05fbad919ad3026f9eabcf403b8ffca5d55573cca445b15583b989d1c42517762151162d7fb5207006d67acd637791f8ad540ac13379064
|
data/kitchen-hyperv.gemspec
CHANGED
|
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
# Required directly by the PowerShell command encoder. base64 is a bundled
|
|
22
22
|
# gem from Ruby 3.4 on, so it has to be declared rather than assumed.
|
|
23
23
|
spec.add_dependency "base64", "~> 0.2"
|
|
24
|
-
spec.add_dependency "test-kitchen", ">=
|
|
24
|
+
spec.add_dependency "test-kitchen", ">= 3.0", "< 5"
|
|
25
25
|
spec.add_dependency "train", ">= 3.5", "< 4.0"
|
|
26
26
|
spec.add_dependency "train-winrm", ">= 0.2", "< 1.0"
|
|
27
27
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# Author:: Steven Murawski <smurawski@chef.io>
|
|
3
5
|
# Copyright:: Copyright (c) 2020 Chef Software, Inc.
|
|
@@ -20,11 +22,11 @@ require "kitchen"
|
|
|
20
22
|
require "kitchen/driver"
|
|
21
23
|
require_relative "hyperv_version"
|
|
22
24
|
require_relative "powershell"
|
|
23
|
-
require "mixlib/shellout" unless defined?(Mixlib::ShellOut)
|
|
24
25
|
require "fileutils" unless defined?(FileUtils)
|
|
25
26
|
require "json" unless defined?(JSON)
|
|
26
27
|
require "train" unless defined?(Train)
|
|
27
28
|
require "train-winrm" unless defined?(TrainPlugins::WinRM)
|
|
29
|
+
require "time" unless defined?(Time.zone_offset)
|
|
28
30
|
|
|
29
31
|
module Kitchen
|
|
30
32
|
|
|
@@ -77,9 +79,12 @@ module Kitchen
|
|
|
77
79
|
default_config :disable_secureboot, false
|
|
78
80
|
default_config :static_mac_address
|
|
79
81
|
default_config :disk_type do |driver|
|
|
80
|
-
File.extname(driver[:parent_vhd_name])
|
|
82
|
+
File.extname(driver[:parent_vhd_name].to_s)
|
|
81
83
|
end
|
|
82
84
|
|
|
85
|
+
default_config :copy_vm_files
|
|
86
|
+
default_config :dry_run, false
|
|
87
|
+
|
|
83
88
|
default_config :hyperv_server, nil
|
|
84
89
|
default_config :hyperv_username, nil
|
|
85
90
|
default_config :hyperv_password, nil
|
|
@@ -102,6 +107,9 @@ module Kitchen
|
|
|
102
107
|
# @raise [RuntimeError] if validation fails or Hyper-V cannot create the VM
|
|
103
108
|
def create(state)
|
|
104
109
|
@state = state
|
|
110
|
+
# Kitchen::Driver::Base#create runs config[:pre_create_command].
|
|
111
|
+
# Without this the option is silently ignored.
|
|
112
|
+
super
|
|
105
113
|
validate_vm_settings
|
|
106
114
|
create_new_differencing_disk
|
|
107
115
|
create_additional_disks
|
|
@@ -142,8 +150,109 @@ module Kitchen
|
|
|
142
150
|
state.delete(:id)
|
|
143
151
|
end
|
|
144
152
|
|
|
153
|
+
# Report whether Hyper-V still has this instance's virtual machine.
|
|
154
|
+
#
|
|
155
|
+
# Backs `kitchen list --probe`. Deliberately read-only: unlike the check
|
|
156
|
+
# {#create} makes, this never starts a stopped VM.
|
|
157
|
+
#
|
|
158
|
+
# @param state [Hash] the instance state hash
|
|
159
|
+
# @return [Hash] normalized status data for Test Kitchen
|
|
160
|
+
def status(state)
|
|
161
|
+
@state = state
|
|
162
|
+
if state[:id].nil?
|
|
163
|
+
return status_report(
|
|
164
|
+
live: false,
|
|
165
|
+
state: "not_created",
|
|
166
|
+
message: "No virtual machine id recorded for this instance."
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
vm = run_ps vm_status_ps
|
|
171
|
+
if vm.nil? || vm["Id"].nil?
|
|
172
|
+
status_report(live: false, state: "not_created", resource_id: state[:id],
|
|
173
|
+
message: "Hyper-V has no virtual machine with id #{state[:id]}.")
|
|
174
|
+
else
|
|
175
|
+
running = vm["State"].to_s.casecmp?("running")
|
|
176
|
+
status_report(live: running, state: running ? "running" : "stopped",
|
|
177
|
+
resource_id: vm["Id"],
|
|
178
|
+
message: "Hyper-V reports the virtual machine as #{vm["State"]}.")
|
|
179
|
+
end
|
|
180
|
+
rescue => e
|
|
181
|
+
status_report(live: nil, state: "unknown", resource_id: state[:id], message: e.message)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Check for the common reasons this driver cannot build an instance.
|
|
185
|
+
#
|
|
186
|
+
# Backs `kitchen doctor`. Reports every problem it finds rather than
|
|
187
|
+
# stopping at the first, since they are usually related.
|
|
188
|
+
#
|
|
189
|
+
# @param state [Hash] the instance state hash
|
|
190
|
+
# @return [Boolean] true if at least one problem was found
|
|
191
|
+
def doctor(state)
|
|
192
|
+
@state = state
|
|
193
|
+
problems = hyperv_problems + parent_vhd_problems
|
|
194
|
+
problems.each { |problem| warn(problem) }
|
|
195
|
+
!problems.empty?
|
|
196
|
+
end
|
|
197
|
+
|
|
145
198
|
private
|
|
146
199
|
|
|
200
|
+
# Build the status hash Test Kitchen normalizes.
|
|
201
|
+
#
|
|
202
|
+
# @return [Hash]
|
|
203
|
+
# @api private
|
|
204
|
+
def status_report(live:, state:, message:, resource_id: nil)
|
|
205
|
+
{
|
|
206
|
+
live: live,
|
|
207
|
+
state: state,
|
|
208
|
+
source: "driver",
|
|
209
|
+
resource_id: resource_id,
|
|
210
|
+
message: message,
|
|
211
|
+
checked_at: Time.now.utc.iso8601,
|
|
212
|
+
}
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# Problems reaching the Hyper-V host itself.
|
|
216
|
+
#
|
|
217
|
+
# @return [Array<String>]
|
|
218
|
+
# @api private
|
|
219
|
+
def hyperv_problems
|
|
220
|
+
return [] unless run_ps(hyperv_module_ps).nil?
|
|
221
|
+
|
|
222
|
+
["The Hyper-V PowerShell module is not installed on #{hyperv_host_description}."]
|
|
223
|
+
rescue => e
|
|
224
|
+
["Could not run PowerShell on #{hyperv_host_description}: #{e.message}"]
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
# Problems with the parent VHD the instance is cloned from.
|
|
228
|
+
#
|
|
229
|
+
# Only checked locally: the paths refer to the remote host's filesystem
|
|
230
|
+
# when hyperv_server is set, so this machine cannot see them.
|
|
231
|
+
#
|
|
232
|
+
# @return [Array<String>]
|
|
233
|
+
# @api private
|
|
234
|
+
def parent_vhd_problems
|
|
235
|
+
return [] if remote_hyperv
|
|
236
|
+
|
|
237
|
+
problems = []
|
|
238
|
+
unless vhd_folder?
|
|
239
|
+
problems << "parent_vhd_folder #{config[:parent_vhd_folder].inspect} does not exist."
|
|
240
|
+
end
|
|
241
|
+
unless vhd?
|
|
242
|
+
problems << "parent_vhd_name #{config[:parent_vhd_name].inspect} was not found in " \
|
|
243
|
+
"#{config[:parent_vhd_folder].inspect}."
|
|
244
|
+
end
|
|
245
|
+
problems
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# How to refer to the Hyper-V host in a message.
|
|
249
|
+
#
|
|
250
|
+
# @return [String]
|
|
251
|
+
# @api private
|
|
252
|
+
def hyperv_host_description
|
|
253
|
+
remote_hyperv ? config[:hyperv_server] : "this machine"
|
|
254
|
+
end
|
|
255
|
+
|
|
147
256
|
# Check the configuration before anything is created.
|
|
148
257
|
#
|
|
149
258
|
# Also resolves `vm_switch`, which requires a round trip to the host and
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# Author:: Steven Murawski <smurawski@chef.io>
|
|
3
5
|
# Copyright:: Copyright (c) 2015-2020 Chef Software, Inc.
|
|
@@ -24,6 +26,6 @@ module Kitchen
|
|
|
24
26
|
# driver, and therefore without loading test-kitchen.
|
|
25
27
|
#
|
|
26
28
|
# @return [String] a frozen semantic version
|
|
27
|
-
HYPERV_VERSION = "0.
|
|
29
|
+
HYPERV_VERSION = "0.12.0"
|
|
28
30
|
end
|
|
29
31
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
#
|
|
2
4
|
# Author:: Steven Murawski <smurawski@chef.io>
|
|
3
5
|
# Copyright:: Copyright (c) 2020 Chef Software, Inc.
|
|
@@ -16,8 +18,8 @@
|
|
|
16
18
|
# limitations under the License.
|
|
17
19
|
|
|
18
20
|
require "base64" unless defined?(Base64)
|
|
19
|
-
require "mixlib/shellout" unless defined?(Mixlib::ShellOut)
|
|
20
21
|
require "benchmark" unless defined?(Benchmark)
|
|
22
|
+
require "rbconfig/sizeof" unless defined?(RbConfig::SIZEOF)
|
|
21
23
|
require "fileutils" unless defined?(FileUtils)
|
|
22
24
|
require "json" unless defined?(JSON)
|
|
23
25
|
|
|
@@ -41,6 +43,13 @@ module Kitchen
|
|
|
41
43
|
#
|
|
42
44
|
# @see Kitchen::Driver::Hyperv
|
|
43
45
|
module PowerShellScripts
|
|
46
|
+
# Values Windows reports in PROCESSOR_ARCHITECTURE for a 64-bit OS.
|
|
47
|
+
#
|
|
48
|
+
# ARM64 matters for Windows on ARM devices, which run Hyper-V: matching
|
|
49
|
+
# only AMD64 there made both width checks false and sent the driver to
|
|
50
|
+
# the Sysnative path, which does not exist for a native 64-bit process.
|
|
51
|
+
SIXTY_FOUR_BIT_ARCHITECTURES = %w{AMD64 ARM64 IA64}.freeze
|
|
52
|
+
|
|
44
53
|
# Encode a script the way `powershell.exe -encodedcommand` expects it:
|
|
45
54
|
# UTF-16LE, then Base64.
|
|
46
55
|
#
|
|
@@ -52,6 +61,26 @@ module Kitchen
|
|
|
52
61
|
Base64.strict_encode64(encoded_script)
|
|
53
62
|
end
|
|
54
63
|
|
|
64
|
+
# The OS architecture, seeing through WOW64.
|
|
65
|
+
#
|
|
66
|
+
# A 32-bit process on 64-bit Windows reads its own architecture from
|
|
67
|
+
# PROCESSOR_ARCHITECTURE; PROCESSOR_ARCHITEW6432 is what reveals the real
|
|
68
|
+
# one, and is only set in that case.
|
|
69
|
+
#
|
|
70
|
+
# @return [String, nil]
|
|
71
|
+
# @api private
|
|
72
|
+
def os_architecture
|
|
73
|
+
ENV["PROCESSOR_ARCHITEW6432"] || ENV["PROCESSOR_ARCHITECTURE"]
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Pointer width of the running Ruby, in bits.
|
|
77
|
+
#
|
|
78
|
+
# @return [Integer] 32 or 64
|
|
79
|
+
# @api private
|
|
80
|
+
def ruby_architecture_bits
|
|
81
|
+
RbConfig::SIZEOF.fetch("void*", 8) * 8
|
|
82
|
+
end
|
|
83
|
+
|
|
55
84
|
# Whether a 64-bit PowerShell is directly reachable.
|
|
56
85
|
#
|
|
57
86
|
# Always true for a remote host, where the local architecture is
|
|
@@ -59,12 +88,11 @@ module Kitchen
|
|
|
59
88
|
#
|
|
60
89
|
# @return [Boolean]
|
|
61
90
|
# @api private
|
|
62
|
-
def
|
|
91
|
+
def sixty_four_bit?
|
|
63
92
|
return true if remote_hyperv
|
|
64
93
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
os_arch == "AMD64" && ruby_arch == 64
|
|
94
|
+
SIXTY_FOUR_BIT_ARCHITECTURES.include?(os_architecture) &&
|
|
95
|
+
ruby_architecture_bits == 64
|
|
68
96
|
end
|
|
69
97
|
|
|
70
98
|
# Whether both the OS and Ruby are 32-bit, so no WOW64 redirection is in
|
|
@@ -72,10 +100,25 @@ module Kitchen
|
|
|
72
100
|
#
|
|
73
101
|
# @return [Boolean]
|
|
74
102
|
# @api private
|
|
103
|
+
def thirty_two_bit?
|
|
104
|
+
!SIXTY_FOUR_BIT_ARCHITECTURES.include?(os_architecture) &&
|
|
105
|
+
ruby_architecture_bits == 32
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @deprecated Use {#sixty_four_bit?}. Kept because this module is mixed
|
|
109
|
+
# into a published driver class.
|
|
110
|
+
# @return [Boolean]
|
|
111
|
+
# @api private
|
|
112
|
+
def is_64bit?
|
|
113
|
+
sixty_four_bit?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @deprecated Use {#thirty_two_bit?}. Kept because this module is mixed
|
|
117
|
+
# into a published driver class.
|
|
118
|
+
# @return [Boolean]
|
|
119
|
+
# @api private
|
|
75
120
|
def is_32bit?
|
|
76
|
-
|
|
77
|
-
ruby_arch = ["foo"].pack("p").size == 4 ? 32 : 64
|
|
78
|
-
os_arch != "AMD64" && ruby_arch == 32
|
|
121
|
+
thirty_two_bit?
|
|
79
122
|
end
|
|
80
123
|
|
|
81
124
|
# Path to a PowerShell that can see the Hyper-V cmdlets.
|
|
@@ -88,7 +131,7 @@ module Kitchen
|
|
|
88
131
|
# @return [String]
|
|
89
132
|
# @api private
|
|
90
133
|
def powershell_64_bit
|
|
91
|
-
if
|
|
134
|
+
if sixty_four_bit? || thirty_two_bit?
|
|
92
135
|
'c:\windows\system32\windowspowershell\v1.0\powershell.exe'
|
|
93
136
|
else
|
|
94
137
|
'c:\windows\sysnative\windowspowershell\v1.0\powershell.exe'
|
|
@@ -256,6 +299,34 @@ module Kitchen
|
|
|
256
299
|
DETAILS
|
|
257
300
|
end
|
|
258
301
|
|
|
302
|
+
# Script that reads the VM's current power state without changing it.
|
|
303
|
+
#
|
|
304
|
+
# Unlike {#ensure_vm_running_ps}, this never starts a stopped VM, so it is
|
|
305
|
+
# safe for `kitchen list --probe`.
|
|
306
|
+
#
|
|
307
|
+
# @return [String] PowerShell source
|
|
308
|
+
# @api private
|
|
309
|
+
def vm_status_ps
|
|
310
|
+
<<-STATUS
|
|
311
|
+
|
|
312
|
+
Get-VmStatus -Id "#{@state[:id]}" | ConvertTo-Json
|
|
313
|
+
STATUS
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
# Script that reports whether the Hyper-V PowerShell module is installed.
|
|
317
|
+
#
|
|
318
|
+
# @return [String] PowerShell source
|
|
319
|
+
# @api private
|
|
320
|
+
def hyperv_module_ps
|
|
321
|
+
<<-MODULE
|
|
322
|
+
|
|
323
|
+
Get-Module -ListAvailable -Name Hyper-V |
|
|
324
|
+
Select-Object -First 1 |
|
|
325
|
+
ForEach-Object { [pscustomobject]@{ Name = $_.Name; Version = [string]$_.Version } } |
|
|
326
|
+
ConvertTo-Json
|
|
327
|
+
MODULE
|
|
328
|
+
end
|
|
329
|
+
|
|
259
330
|
# Script that forces the VM off and removes it.
|
|
260
331
|
#
|
|
261
332
|
# @return [String] PowerShell source
|
data/support/hyperv.ps1
CHANGED
|
@@ -1,233 +1,264 @@
|
|
|
1
|
-
#requires -Version 2 -Modules Hyper-V
|
|
2
|
-
|
|
3
|
-
#implicitly import hyperv module to avoid powercli cmdlets
|
|
4
|
-
if ((Get-Module -Name 'hyper-v') -ne $null) {
|
|
5
|
-
Remove-Module -Name hyper-v
|
|
6
|
-
Import-Module -Name hyper-v
|
|
7
|
-
}
|
|
8
|
-
else {
|
|
9
|
-
Import-Module -Name hyper-v
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
$ProgressPreference = 'SilentlyContinue'
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
function New-DifferencingDisk {
|
|
16
|
-
[cmdletbinding()]
|
|
17
|
-
param (
|
|
18
|
-
[parameter(Mandatory)]
|
|
19
|
-
[ValidateNotNullOrEmpty()]
|
|
20
|
-
[string[]]$Path,
|
|
21
|
-
[parameter(Mandatory)]
|
|
22
|
-
[ValidateNotNullOrEmpty()]
|
|
23
|
-
[string]$ParentPath
|
|
24
|
-
)
|
|
25
|
-
if (-not (Test-Path $Path)) {
|
|
26
|
-
$null = new-vhd @psboundparameters -Differencing
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function Assert-VmRunning {
|
|
31
|
-
[cmdletbinding()]
|
|
32
|
-
param([string]$Id)
|
|
33
|
-
|
|
34
|
-
if ([string]::IsNullOrEmpty($Id)) {
|
|
35
|
-
$Output = [pscustomobject]@{
|
|
36
|
-
Name = ''
|
|
37
|
-
State = ''
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
$Output = Get-VM -Id $Id |
|
|
42
|
-
ForEach-Object -Process {
|
|
43
|
-
if ($_.State -notlike 'Running') {
|
|
44
|
-
$_ |
|
|
45
|
-
Start-VM -passthru
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
$_
|
|
49
|
-
}
|
|
50
|
-
} |
|
|
51
|
-
Select-Object -Property Name, Id, State
|
|
52
|
-
}
|
|
53
|
-
$Output
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function New-KitchenVM {
|
|
57
|
-
[cmdletbinding()]
|
|
58
|
-
param (
|
|
59
|
-
$Generation = 1,
|
|
60
|
-
$DisableSecureBoot,
|
|
61
|
-
$MemoryStartupBytes,
|
|
62
|
-
$StaticMacAddress,
|
|
63
|
-
$Name,
|
|
64
|
-
$Path,
|
|
65
|
-
$VHDPath,
|
|
66
|
-
$SwitchName,
|
|
67
|
-
$VlanId,
|
|
68
|
-
$ProcessorCount,
|
|
69
|
-
$UseDynamicMemory,
|
|
70
|
-
$DynamicMemoryMinBytes,
|
|
71
|
-
$DynamicMemoryMaxBytes,
|
|
72
|
-
$boot_iso_path,
|
|
73
|
-
$EnableGuestServices,
|
|
74
|
-
$AdditionalDisks
|
|
75
|
-
)
|
|
76
|
-
$null = $psboundparameters.remove('DisableSecureBoot')
|
|
77
|
-
$null = $psboundparameters.remove('ProcessorCount')
|
|
78
|
-
$null = $psboundparameters.remove('StaticMacAddress')
|
|
79
|
-
$null = $psboundparameters.remove('UseDynamicMemory')
|
|
80
|
-
$null = $psboundparameters.remove('DynamicMemoryMinBytes')
|
|
81
|
-
$null = $psboundparameters.remove('DynamicMemoryMaxBytes')
|
|
82
|
-
$null = $psboundparameters.remove('boot_iso_path')
|
|
83
|
-
$null = $psboundparameters.remove('EnableGuestServices')
|
|
84
|
-
$null = $psboundparameters.remove('VlanId')
|
|
85
|
-
$null = $psboundparameters.remove('AdditionalDisks')
|
|
86
|
-
$DisableSecureBoot = [Convert]::ToBoolean($DisableSecureBoot)
|
|
87
|
-
$UseDynamicMemory = [Convert]::ToBoolean($UseDynamicMemory)
|
|
88
|
-
$null = [bool]::TryParse($EnableGuestServices, [ref]$EnableGuestServices)
|
|
89
|
-
|
|
90
|
-
$vm = new-vm @psboundparameters |
|
|
91
|
-
Set-Vm -ProcessorCount $ProcessorCount -passthru
|
|
92
|
-
|
|
93
|
-
if ($UseDynamicMemory) {
|
|
94
|
-
$vm | Set-VMMemory -DynamicMemoryEnabled $true -MinimumBytes $DynamicMemoryMinBytes -MaximumBytes $DynamicMemoryMaxBytes
|
|
95
|
-
}
|
|
96
|
-
else {
|
|
97
|
-
$vm | Set-VMMemory -DynamicMemoryEnabled $false
|
|
98
|
-
}
|
|
99
|
-
if (-not [string]::IsNullOrEmpty($boot_iso_path)) {
|
|
100
|
-
Mount-VMISO -Id $vm.Id -Path $boot_iso_path
|
|
101
|
-
}
|
|
102
|
-
if (-not [string]::IsNullOrEmpty($StaticMacAddress)) {
|
|
103
|
-
Set-VMNetworkAdapter -VMName $vm.VMName -StaticMacAddress $StaticMacAddress
|
|
104
|
-
}
|
|
105
|
-
if ($EnableGuestServices -and (Get-command Enable-VMIntegrationService -ErrorAction SilentlyContinue)) {
|
|
106
|
-
Enable-VMIntegrationService -VM $vm -Name 'Guest Service Interface'
|
|
107
|
-
}
|
|
108
|
-
if (($VlanId -ne $null) -and (Get-command Set-VMNetworkAdapterVlan -ErrorAction SilentlyContinue)) {
|
|
109
|
-
Set-VMNetworkAdapterVlan -VM $vm -Access -VlanId $VlanId
|
|
110
|
-
}
|
|
111
|
-
if ($AdditionalDisks -and (Get-command Add-VMHardDiskDrive -ErrorAction SilentlyContinue)) {
|
|
112
|
-
foreach ($AdditionalDisk in $AdditionalDisks) {
|
|
113
|
-
Add-VMHardDiskDrive -VM $vm -Path $AdditionalDisk
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
if ($DisableSecureBoot -and ($Generation -eq 2) -and (Get-command Set-VMFirmware -ErrorAction SilentlyContinue)) {
|
|
117
|
-
Set-VMFirmware -VM $vm -EnableSecureBoot Off
|
|
118
|
-
}
|
|
119
|
-
if ((Get-Command -Name Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) {
|
|
120
|
-
Set-VM -Name $vm.VMName -AutomaticCheckpointsEnabled $false
|
|
121
|
-
}
|
|
122
|
-
$vm | Start-Vm -passthru |
|
|
123
|
-
foreach {
|
|
124
|
-
$vm = $_
|
|
125
|
-
do {
|
|
126
|
-
start-sleep -seconds 2
|
|
127
|
-
}
|
|
128
|
-
while ($vm.state -notlike 'Running')
|
|
129
|
-
$vm
|
|
130
|
-
} |
|
|
131
|
-
select Name, Id, State
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function Get-VmIP($vm) {
|
|
135
|
-
start-sleep -seconds 10
|
|
136
|
-
$vm.networkadapters.ipaddresses |
|
|
137
|
-
Where-Object {
|
|
138
|
-
$_ -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'
|
|
139
|
-
} |
|
|
140
|
-
Select-Object -First 1
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
Function Set-VMNetworkConfiguration {
|
|
144
|
-
[CmdletBinding()]
|
|
145
|
-
Param (
|
|
146
|
-
[parameter(valuefrompipeline)]
|
|
147
|
-
[object]$NetworkAdapter,
|
|
148
|
-
[String[]]$IPAddress = @(),
|
|
149
|
-
[String[]]$Gateway = @(),
|
|
150
|
-
[String[]]$DNSServers = @(),
|
|
151
|
-
[String[]]$Subnet = @()
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
$vm = Get-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
$
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
$NetworkSettings[0].
|
|
173
|
-
$NetworkSettings[0].
|
|
174
|
-
$NetworkSettings[0].
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
$
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
$
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
1
|
+
#requires -Version 2 -Modules Hyper-V
|
|
2
|
+
|
|
3
|
+
#implicitly import hyperv module to avoid powercli cmdlets
|
|
4
|
+
if ((Get-Module -Name 'hyper-v') -ne $null) {
|
|
5
|
+
Remove-Module -Name hyper-v
|
|
6
|
+
Import-Module -Name hyper-v
|
|
7
|
+
}
|
|
8
|
+
else {
|
|
9
|
+
Import-Module -Name hyper-v
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
$ProgressPreference = 'SilentlyContinue'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
function New-DifferencingDisk {
|
|
16
|
+
[cmdletbinding()]
|
|
17
|
+
param (
|
|
18
|
+
[parameter(Mandatory)]
|
|
19
|
+
[ValidateNotNullOrEmpty()]
|
|
20
|
+
[string[]]$Path,
|
|
21
|
+
[parameter(Mandatory)]
|
|
22
|
+
[ValidateNotNullOrEmpty()]
|
|
23
|
+
[string]$ParentPath
|
|
24
|
+
)
|
|
25
|
+
if (-not (Test-Path $Path)) {
|
|
26
|
+
$null = new-vhd @psboundparameters -Differencing
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Assert-VmRunning {
|
|
31
|
+
[cmdletbinding()]
|
|
32
|
+
param([string]$Id)
|
|
33
|
+
|
|
34
|
+
if ([string]::IsNullOrEmpty($Id)) {
|
|
35
|
+
$Output = [pscustomobject]@{
|
|
36
|
+
Name = ''
|
|
37
|
+
State = ''
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
$Output = Get-VM -Id $Id |
|
|
42
|
+
ForEach-Object -Process {
|
|
43
|
+
if ($_.State -notlike 'Running') {
|
|
44
|
+
$_ |
|
|
45
|
+
Start-VM -passthru
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
$_
|
|
49
|
+
}
|
|
50
|
+
} |
|
|
51
|
+
Select-Object -Property Name, Id, State
|
|
52
|
+
}
|
|
53
|
+
$Output
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function New-KitchenVM {
|
|
57
|
+
[cmdletbinding()]
|
|
58
|
+
param (
|
|
59
|
+
$Generation = 1,
|
|
60
|
+
$DisableSecureBoot,
|
|
61
|
+
$MemoryStartupBytes,
|
|
62
|
+
$StaticMacAddress,
|
|
63
|
+
$Name,
|
|
64
|
+
$Path,
|
|
65
|
+
$VHDPath,
|
|
66
|
+
$SwitchName,
|
|
67
|
+
$VlanId,
|
|
68
|
+
$ProcessorCount,
|
|
69
|
+
$UseDynamicMemory,
|
|
70
|
+
$DynamicMemoryMinBytes,
|
|
71
|
+
$DynamicMemoryMaxBytes,
|
|
72
|
+
$boot_iso_path,
|
|
73
|
+
$EnableGuestServices,
|
|
74
|
+
$AdditionalDisks
|
|
75
|
+
)
|
|
76
|
+
$null = $psboundparameters.remove('DisableSecureBoot')
|
|
77
|
+
$null = $psboundparameters.remove('ProcessorCount')
|
|
78
|
+
$null = $psboundparameters.remove('StaticMacAddress')
|
|
79
|
+
$null = $psboundparameters.remove('UseDynamicMemory')
|
|
80
|
+
$null = $psboundparameters.remove('DynamicMemoryMinBytes')
|
|
81
|
+
$null = $psboundparameters.remove('DynamicMemoryMaxBytes')
|
|
82
|
+
$null = $psboundparameters.remove('boot_iso_path')
|
|
83
|
+
$null = $psboundparameters.remove('EnableGuestServices')
|
|
84
|
+
$null = $psboundparameters.remove('VlanId')
|
|
85
|
+
$null = $psboundparameters.remove('AdditionalDisks')
|
|
86
|
+
$DisableSecureBoot = [Convert]::ToBoolean($DisableSecureBoot)
|
|
87
|
+
$UseDynamicMemory = [Convert]::ToBoolean($UseDynamicMemory)
|
|
88
|
+
$null = [bool]::TryParse($EnableGuestServices, [ref]$EnableGuestServices)
|
|
89
|
+
|
|
90
|
+
$vm = new-vm @psboundparameters |
|
|
91
|
+
Set-Vm -ProcessorCount $ProcessorCount -passthru
|
|
92
|
+
|
|
93
|
+
if ($UseDynamicMemory) {
|
|
94
|
+
$vm | Set-VMMemory -DynamicMemoryEnabled $true -MinimumBytes $DynamicMemoryMinBytes -MaximumBytes $DynamicMemoryMaxBytes
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
$vm | Set-VMMemory -DynamicMemoryEnabled $false
|
|
98
|
+
}
|
|
99
|
+
if (-not [string]::IsNullOrEmpty($boot_iso_path)) {
|
|
100
|
+
Mount-VMISO -Id $vm.Id -Path $boot_iso_path
|
|
101
|
+
}
|
|
102
|
+
if (-not [string]::IsNullOrEmpty($StaticMacAddress)) {
|
|
103
|
+
Set-VMNetworkAdapter -VMName $vm.VMName -StaticMacAddress $StaticMacAddress
|
|
104
|
+
}
|
|
105
|
+
if ($EnableGuestServices -and (Get-command Enable-VMIntegrationService -ErrorAction SilentlyContinue)) {
|
|
106
|
+
Enable-VMIntegrationService -VM $vm -Name 'Guest Service Interface'
|
|
107
|
+
}
|
|
108
|
+
if (($VlanId -ne $null) -and (Get-command Set-VMNetworkAdapterVlan -ErrorAction SilentlyContinue)) {
|
|
109
|
+
Set-VMNetworkAdapterVlan -VM $vm -Access -VlanId $VlanId
|
|
110
|
+
}
|
|
111
|
+
if ($AdditionalDisks -and (Get-command Add-VMHardDiskDrive -ErrorAction SilentlyContinue)) {
|
|
112
|
+
foreach ($AdditionalDisk in $AdditionalDisks) {
|
|
113
|
+
Add-VMHardDiskDrive -VM $vm -Path $AdditionalDisk
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if ($DisableSecureBoot -and ($Generation -eq 2) -and (Get-command Set-VMFirmware -ErrorAction SilentlyContinue)) {
|
|
117
|
+
Set-VMFirmware -VM $vm -EnableSecureBoot Off
|
|
118
|
+
}
|
|
119
|
+
if ((Get-Command -Name Set-Vm).Parameters["AutomaticCheckpointsEnabled"]) {
|
|
120
|
+
Set-VM -Name $vm.VMName -AutomaticCheckpointsEnabled $false
|
|
121
|
+
}
|
|
122
|
+
$vm | Start-Vm -passthru |
|
|
123
|
+
foreach {
|
|
124
|
+
$vm = $_
|
|
125
|
+
do {
|
|
126
|
+
start-sleep -seconds 2
|
|
127
|
+
}
|
|
128
|
+
while ($vm.state -notlike 'Running')
|
|
129
|
+
$vm
|
|
130
|
+
} |
|
|
131
|
+
select Name, Id, State
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function Get-VmIP($vm) {
|
|
135
|
+
start-sleep -seconds 10
|
|
136
|
+
$vm.networkadapters.ipaddresses |
|
|
137
|
+
Where-Object {
|
|
138
|
+
$_ -match '^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$'
|
|
139
|
+
} |
|
|
140
|
+
Select-Object -First 1
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Function Set-VMNetworkConfiguration {
|
|
144
|
+
[CmdletBinding()]
|
|
145
|
+
Param (
|
|
146
|
+
[parameter(valuefrompipeline)]
|
|
147
|
+
[object]$NetworkAdapter,
|
|
148
|
+
[String[]]$IPAddress = @(),
|
|
149
|
+
[String[]]$Gateway = @(),
|
|
150
|
+
[String[]]$DNSServers = @(),
|
|
151
|
+
[String[]]$Subnet = @()
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
$vm = Get-CimInstance -Namespace 'root\virtualization\v2' -ClassName 'Msvm_ComputerSystem' -Filter "ElementName = '$($NetworkAdapter.VMName)'"
|
|
155
|
+
|
|
156
|
+
$VMSettings = Get-CimAssociatedInstance -InputObject $vm -ResultClassName 'Msvm_VirtualSystemSettingData' |
|
|
157
|
+
Where-Object { $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' }
|
|
158
|
+
|
|
159
|
+
$VMNetAdapters = Get-CimAssociatedInstance -InputObject $VMSettings -ResultClassName 'Msvm_SyntheticEthernetPortSettingData'
|
|
160
|
+
|
|
161
|
+
$NetworkSettings = @()
|
|
162
|
+
foreach ($NetAdapter in $VMNetAdapters) {
|
|
163
|
+
if ($NetAdapter.Address -eq $NetworkAdapter.MacAddress) {
|
|
164
|
+
$NetworkSettings += Get-CimAssociatedInstance -InputObject $NetAdapter -ResultClassName 'Msvm_GuestNetworkAdapterConfiguration'
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if ($NetworkSettings.Count -eq 0) {
|
|
169
|
+
throw "No guest network adapter configuration found for MAC address $($NetworkAdapter.MacAddress) on VM $($NetworkAdapter.VMName)."
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
$NetworkSettings[0].IPAddresses = $IPAddress
|
|
173
|
+
$NetworkSettings[0].DefaultGateways = $Gateway
|
|
174
|
+
$NetworkSettings[0].DNSServers = $DNSServers
|
|
175
|
+
$NetworkSettings[0].Subnets = $Subnet
|
|
176
|
+
$NetworkSettings[0].ProtocolIFType = 4096
|
|
177
|
+
$NetworkSettings[0].DHCPEnabled = $false
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
$Service = Get-CimInstance -Namespace 'root\virtualization\v2' -ClassName 'Msvm_VirtualSystemManagementService'
|
|
181
|
+
|
|
182
|
+
# NetworkConfiguration is declared string[] of embedded instances; the CIM
|
|
183
|
+
# layer serializes the CimInstance for us, replacing WMI's GetText(1).
|
|
184
|
+
$setIP = Invoke-CimMethod -InputObject $Service -MethodName 'SetGuestNetworkAdapterConfiguration' -Arguments @{
|
|
185
|
+
ComputerSystem = $vm
|
|
186
|
+
NetworkConfiguration = @($NetworkSettings[0])
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
# 4096 means the method was accepted and is running as a job.
|
|
190
|
+
if ($setIP.ReturnValue -eq 4096) {
|
|
191
|
+
$job = $setIP.Job | Get-CimInstance
|
|
192
|
+
|
|
193
|
+
# 3 = Starting, 4 = Running.
|
|
194
|
+
while ($job.JobState -eq 3 -or $job.JobState -eq 4) {
|
|
195
|
+
Start-Sleep -Seconds 1
|
|
196
|
+
$job = $job | Get-CimInstance
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
# 7 = Completed. Anything else previously emitted the error and carried
|
|
200
|
+
# on, which hid a failed address assignment behind a successful create.
|
|
201
|
+
if ($job.JobState -ne 7) {
|
|
202
|
+
throw "Setting the guest network adapter configuration failed: $($job.ErrorDescription)"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
elseif ($setIP.ReturnValue -ne 0) {
|
|
206
|
+
throw "Setting the guest network adapter configuration failed with return value $($setIP.ReturnValue)."
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
(Get-VM -Id $NetworkAdapter.VmId).NetworkAdapters | Select-Object Name, IPAddresses
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function Get-VmDetail {
|
|
213
|
+
[cmdletbinding()]
|
|
214
|
+
param($Id)
|
|
215
|
+
|
|
216
|
+
Get-VM -Id $Id |
|
|
217
|
+
ForEach-Object {
|
|
218
|
+
$vm = $_
|
|
219
|
+
do {
|
|
220
|
+
Start-Sleep -Seconds 1
|
|
221
|
+
}
|
|
222
|
+
while (-not (Get-VmIP $vm))
|
|
223
|
+
|
|
224
|
+
[pscustomobject]@{
|
|
225
|
+
Name = $vm.name
|
|
226
|
+
Id = $vm.ID
|
|
227
|
+
IpAddress = (Get-VmIP $vm)
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function Get-VmStatus {
|
|
233
|
+
[cmdletbinding()]
|
|
234
|
+
param($Id)
|
|
235
|
+
|
|
236
|
+
Get-VM -Id $Id -ErrorAction SilentlyContinue |
|
|
237
|
+
ForEach-Object {
|
|
238
|
+
[pscustomobject]@{
|
|
239
|
+
Name = $_.Name
|
|
240
|
+
Id = [string]$_.Id
|
|
241
|
+
State = [string]$_.State
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function Get-DefaultVMSwitch {
|
|
247
|
+
[CmdletBinding()]
|
|
248
|
+
param ($Name)
|
|
249
|
+
Get-VMSwitch @PSBoundParameters |
|
|
250
|
+
Select-Object -First 1 |
|
|
251
|
+
Select-Object Name, Id
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function Mount-VMISO {
|
|
255
|
+
[cmdletbinding()]
|
|
256
|
+
param($Id, $Path)
|
|
257
|
+
|
|
258
|
+
if ((Get-VM -Id $Id).Generation -eq 2) {
|
|
259
|
+
Add-VMDvdDrive (Get-VM -Id $Id).Name | Set-VMDvdDrive -VMName (Get-VM -Id $Id).Name -Path $Path
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
Set-VMDvdDrive -VMName (Get-VM -Id $Id).Name -Path $Path
|
|
263
|
+
}
|
|
264
|
+
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: kitchen-hyperv
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Steven Murawski
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-08-
|
|
11
|
+
date: 2026-08-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -30,7 +30,7 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '
|
|
33
|
+
version: '3.0'
|
|
34
34
|
- - "<"
|
|
35
35
|
- !ruby/object:Gem::Version
|
|
36
36
|
version: '5'
|
|
@@ -40,7 +40,7 @@ dependencies:
|
|
|
40
40
|
requirements:
|
|
41
41
|
- - ">="
|
|
42
42
|
- !ruby/object:Gem::Version
|
|
43
|
-
version: '
|
|
43
|
+
version: '3.0'
|
|
44
44
|
- - "<"
|
|
45
45
|
- !ruby/object:Gem::Version
|
|
46
46
|
version: '5'
|