Module: Kitchen::Driver::PowerShellScripts
- Included in:
- Hyperv
- Defined in:
- lib/kitchen/driver/powershell.rb
Overview
PowerShell generation and execution for Hyperv.
Every method here is either a script generator -- a *_ps method
returning PowerShell source -- or part of the pipeline that runs one:
#run_ps wraps the script so it dot-sources support/hyperv.ps1,
#encode_command encodes it for powershell.exe -encodedcommand, and
#execute_command runs it over the Train connection and parses the JSON
that comes back.
Encoding sidesteps every layer of quoting between Ruby and PowerShell, which matters because these scripts embed Windows paths and user-supplied strings.
The module reads config, instance and @state from the driver it is
mixed into, so it is not usable standalone.
Constant Summary collapse
- SIXTY_FOUR_BIT_ARCHITECTURES =
Values Windows reports in PROCESSOR_ARCHITECTURE for a 64-bit OS.
ARM64 matters for Windows on ARM devices, which run Hyper-V: matching only AMD64 there made both width checks false and sent the driver to the Sysnative path, which does not exist for a native 64-bit process.
%w{AMD64 ARM64 IA64}.freeze
Instance Method Summary collapse
-
#additional_disks ⇒ String?
private
The
AdditionalDisksentry spliced into #new_vm_ps. -
#copy_vm_file_ps(source, dest) ⇒ String
private
Script that copies a file or directory into the running guest.
-
#delete_vm_ps ⇒ String
private
Script that forces the VM off and removes it.
-
#encode_command(script) ⇒ String
private
Encode a script the way
powershell.exe -encodedcommandexpects it: UTF-16LE, then Base64. -
#ensure_vm_running_ps ⇒ String
private
Script that confirms the VM exists and starts it if it is stopped.
-
#execute_command(cmd, options = {}) ⇒ Hash, ...
private
Run a prepared command line and parse its output.
-
#hyperv_module_ps ⇒ String
private
Script that reports whether the Hyper-V PowerShell module is installed.
-
#is_32bit? ⇒ Boolean
deprecated
private
Deprecated.
Use #thirty_two_bit?. Kept because this module is mixed into a published driver class.
-
#is_64bit? ⇒ Boolean
deprecated
private
Deprecated.
Use #sixty_four_bit?. Kept because this module is mixed into a published driver class.
-
#mount_vm_iso ⇒ String
private
Script that attaches the configured ISO to the VM's DVD drive.
-
#new_additional_disk_ps(disk_path, disk_size) ⇒ String
private
Script that creates one additional data disk.
-
#new_differencing_disk_ps ⇒ String
private
Script that clones the parent VHD into this instance's differencing disk.
-
#new_vm_ps ⇒ String
private
Script that creates the VM from the current configuration.
-
#os_architecture ⇒ String?
private
The OS architecture, seeing through WOW64.
-
#powershell_64_bit ⇒ String
private
Path to a PowerShell that can see the Hyper-V cmdlets.
-
#resize_vhd ⇒ String
private
Script that grows the parent VHD to the configured size.
-
#ruby_architecture_bits ⇒ Integer
private
Pointer width of the running Ruby, in bits.
-
#run_ps(cmd, options = {}) ⇒ Hash, ...
private
Run a PowerShell script on the Hyper-V host.
-
#sanitize_stdout(stdout) ⇒ String
private
Strip the interactive prompt lines PowerShell interleaves with output, which would otherwise make the result invalid JSON.
-
#set_vm_ipaddress_ps ⇒ String
private
Script that assigns the VM a static address once its adapter is up.
-
#set_vm_note ⇒ String
private
Script that writes the configured note onto the VM.
-
#sixty_four_bit? ⇒ Boolean
private
Whether a 64-bit PowerShell is directly reachable.
-
#thirty_two_bit? ⇒ Boolean
private
Whether both the OS and Ruby are 32-bit, so no WOW64 redirection is in play.
-
#vm_default_switch_ps ⇒ String
private
Script that resolves the virtual switch to attach the VM to.
-
#vm_details_ps ⇒ String
private
Script that reads the VM's name, id and IP address.
-
#vm_status_ps ⇒ String
private
Script that reads the VM's current power state without changing it.
-
#wrap_command(script) ⇒ String
private
Turn a script into a full
powershell.execommand line.
Instance Method Details
#additional_disks ⇒ String?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The AdditionalDisks entry spliced into #new_vm_ps.
Reads the paths Hyperv#create_additional_disks recorded, so it is only meaningful after that has run.
282 283 284 285 286 287 288 |
# File 'lib/kitchen/driver/powershell.rb', line 282 def additional_disks return if config[:additional_disks].nil? <<-EOH AdditionalDisks = @("#{@additional_disk_objects.join('","')}") EOH end |
#copy_vm_file_ps(source, dest) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that copies a file or directory into the running guest.
Enables the guest service interface first if it is off, and walks a
directory source file by file since Copy-VMFile handles only files.
412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 |
# File 'lib/kitchen/driver/powershell.rb', line 412 def copy_vm_file_ps(source, dest) <<-FILECOPY Function CopyFile ($VM, [string]$SourcePath, [string]$DestPath) { $p = @{ CreateFullPath = $true ; FileSource = 'Host'; Force = $true } $VM | Copy-VMFile -SourcePath $SourcePath -DestinationPath $DestPath @p } $sourceLocation = '#{source}' $destinationLocation = '#{dest}' $vmId = '#{@state[:id]}' If (Test-Path $sourceLocation) { $vm = Get-VM -ID $vmId $service = 'Guest Service Interface' If ((Get-VMIntegrationService -Name $service -VM $vm).Enabled -ne $true) { Enable-VMIntegrationService -Name $service -VM $vm Start-Sleep -Seconds 3 } If ((Get-Item $sourceLocation) -is [System.IO.DirectoryInfo]) { ForEach ($item in (Get-ChildItem -Path $sourceLocation -File)) { $destFullPath = (Join-Path $destinationLocation $item.Name) CopyFile $vm $item.FullName $destFullPath } } Else { CopyFile $vm $sourceLocation $destinationLocation } } else { Write-Error "Source file path does not exist: $sourceLocation" } FILECOPY end |
#delete_vm_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that forces the VM off and removes it.
334 335 336 337 338 339 340 341 |
# File 'lib/kitchen/driver/powershell.rb', line 334 def delete_vm_ps <<-REMOVE $null = Get-VM -ID "#{@state[:id]}" | Stop-VM -Force -TurnOff -PassThru | Remove-VM -Force REMOVE end |
#encode_command(script) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Encode a script the way powershell.exe -encodedcommand expects it:
UTF-16LE, then Base64.
59 60 61 62 |
# File 'lib/kitchen/driver/powershell.rb', line 59 def encode_command(script) encoded_script = script.encode("UTF-16LE", "UTF-8") Base64.strict_encode64(encoded_script) end |
#ensure_vm_running_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that confirms the VM exists and starts it if it is stopped.
238 239 240 241 242 243 |
# File 'lib/kitchen/driver/powershell.rb', line 238 def ensure_vm_running_ps <<-RUNNING Assert-VmRunning -ID "#{@state[:id]}" | ConvertTo-Json RUNNING end |
#execute_command(cmd, options = {}) ⇒ Hash, ...
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Run a prepared command line and parse its output.
184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
# File 'lib/kitchen/driver/powershell.rb', line 184 def execute_command(cmd, = {}) debug("#Command BEGIN (#{cmd})") sh = nil bm = Benchmark.measure do sh = connection.run_command(cmd, ) end debug("Command END #{Util.duration(bm.total)}") raise "Failed: #{sh.stderr}" if sh.exit_status != 0 stdout = sanitize_stdout(sh.stdout) JSON.parse(stdout) if stdout.length > 2 end |
#hyperv_module_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that reports whether the Hyper-V PowerShell module is installed.
320 321 322 323 324 325 326 327 328 |
# File 'lib/kitchen/driver/powershell.rb', line 320 def hyperv_module_ps <<-MODULE Get-Module -ListAvailable -Name Hyper-V | Select-Object -First 1 | ForEach-Object { [pscustomobject]@{ Name = $_.Name; Version = [string]$_.Version } } | ConvertTo-Json MODULE end |
#is_32bit? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Use #thirty_two_bit?. Kept because this module is mixed into a published driver class.
120 121 122 |
# File 'lib/kitchen/driver/powershell.rb', line 120 def is_32bit? thirty_two_bit? end |
#is_64bit? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Use #sixty_four_bit?. Kept because this module is mixed into a published driver class.
112 113 114 |
# File 'lib/kitchen/driver/powershell.rb', line 112 def is_64bit? sixty_four_bit? end |
#mount_vm_iso ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that attaches the configured ISO to the VM's DVD drive.
377 378 379 380 381 |
# File 'lib/kitchen/driver/powershell.rb', line 377 def mount_vm_iso <<-MOUNTISO mount-vmiso -id "#{@state[:id]}" -Path #{config[:iso_path]} MOUNTISO end |
#new_additional_disk_ps(disk_path, disk_size) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that creates one additional data disk.
227 228 229 230 231 232 |
# File 'lib/kitchen/driver/powershell.rb', line 227 def new_additional_disk_ps(disk_path, disk_size) <<-ADDDISK New-VHD -Path "#{disk_path}" -SizeBytes #{disk_size}GB | Out-Null ADDDISK end |
#new_differencing_disk_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that clones the parent VHD into this instance's differencing disk.
214 215 216 217 218 219 |
# File 'lib/kitchen/driver/powershell.rb', line 214 def new_differencing_disk_ps <<-DIFF New-DifferencingDisk -Path "#{differencing_disk_path}" -ParentPath "#{parent_vhd_path}" DIFF end |
#new_vm_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that creates the VM from the current configuration.
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/kitchen/driver/powershell.rb', line 249 def new_vm_ps <<-NEWVM $NewVMParams = @{ Generation = #{config[:vm_generation]} DisableSecureBoot = "#{config[:disable_secureboot]}" MemoryStartupBytes = #{config[:memory_startup_bytes]} StaticMacAddress = "#{config[:static_mac_address]}" Name = "#{instance.name}" Path = "#{kitchen_vm_path}" VHDPath = "#{differencing_disk_path}" SwitchName = "#{config[:vm_switch]}" VlanId = #{config[:vm_vlan_id] || "$null"} ProcessorCount = #{config[:processor_count]} UseDynamicMemory = "#{config[:dynamic_memory]}" DynamicMemoryMinBytes = #{config[:dynamic_memory_min_bytes]} DynamicMemoryMaxBytes = #{config[:dynamic_memory_max_bytes]} boot_iso_path = "#{boot_iso_path}" EnableGuestServices = "#{config[:enable_guest_services]}" #{additional_disks} } New-KitchenVM @NewVMParams | ConvertTo-Json NEWVM end |
#os_architecture ⇒ String?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
The OS architecture, seeing through WOW64.
A 32-bit process on 64-bit Windows reads its own architecture from PROCESSOR_ARCHITECTURE; PROCESSOR_ARCHITEW6432 is what reveals the real one, and is only set in that case.
72 73 74 |
# File 'lib/kitchen/driver/powershell.rb', line 72 def os_architecture ENV["PROCESSOR_ARCHITEW6432"] || ENV["PROCESSOR_ARCHITECTURE"] end |
#powershell_64_bit ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Path to a PowerShell that can see the Hyper-V cmdlets.
When a 32-bit Ruby runs on 64-bit Windows the WOW64 filesystem
redirector rewrites system32 to SysWOW64, which would launch a
32-bit PowerShell with no Hyper-V module. sysnative is the virtual
path that escapes redirection.
133 134 135 136 137 138 139 |
# File 'lib/kitchen/driver/powershell.rb', line 133 def powershell_64_bit if sixty_four_bit? || thirty_two_bit? 'c:\windows\system32\windowspowershell\v1.0\powershell.exe' else 'c:\windows\sysnative\windowspowershell\v1.0\powershell.exe' end end |
#resize_vhd ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that grows the parent VHD to the configured size.
387 388 389 390 391 |
# File 'lib/kitchen/driver/powershell.rb', line 387 def resize_vhd <<-VMNOTE Resize-VHD -Path "#{parent_vhd_path}" -SizeBytes #{config[:resize_vhd]} VMNOTE end |
#ruby_architecture_bits ⇒ Integer
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Pointer width of the running Ruby, in bits.
80 81 82 |
# File 'lib/kitchen/driver/powershell.rb', line 80 def ruby_architecture_bits RbConfig::SIZEOF.fetch("void*", 8) * 8 end |
#run_ps(cmd, options = {}) ⇒ Hash, ...
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Run a PowerShell script on the Hyper-V host.
With dry_run set the script is echoed rather than executed, which is
the quickest way to see exactly what the driver would have run.
168 169 170 171 172 173 174 |
# File 'lib/kitchen/driver/powershell.rb', line 168 def run_ps(cmd, = {}) cmd = "echo #{cmd}" if config[:dry_run] debug("Preparing to run: ") debug(" #{cmd}") wrapped_command = wrap_command cmd execute_command wrapped_command, end |
#sanitize_stdout(stdout) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Strip the interactive prompt lines PowerShell interleaves with output, which would otherwise make the result invalid JSON.
205 206 207 |
# File 'lib/kitchen/driver/powershell.rb', line 205 def sanitize_stdout(stdout) stdout.split("\n").select { |s| !s.start_with?("PS") }.join("\n") end |
#set_vm_ipaddress_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that assigns the VM a static address once its adapter is up.
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 |
# File 'lib/kitchen/driver/powershell.rb', line 347 def set_vm_ipaddress_ps <<-VMIP while ((Get-VM -id "#{@state[:id]}").NetworkAdapters[0].Status -ne 'Ok'){ start-sleep 10 } (Get-VM -id "#{@state[:id]}").NetworkAdapters | Set-VMNetworkConfiguration -ipaddress "#{config[:ip_address]}" ` -subnet "#{config[:subnet]}" ` -gateway "#{config[:gateway]}" ` -dnsservers #{ruby_array_to_ps_array(config[:dns_servers])} | ConvertTo-Json VMIP end |
#set_vm_note ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that writes the configured note onto the VM.
397 398 399 400 401 |
# File 'lib/kitchen/driver/powershell.rb', line 397 def set_vm_note <<-VMNOTE Set-VM -Name (Get-VM | Where-Object{ $_.ID -eq "#{@state[:id]}"}).Name -Note "#{config[:vm_note]}" VMNOTE end |
#sixty_four_bit? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether a 64-bit PowerShell is directly reachable.
Always true for a remote host, where the local architecture is irrelevant.
91 92 93 94 95 96 |
# File 'lib/kitchen/driver/powershell.rb', line 91 def sixty_four_bit? return true if remote_hyperv SIXTY_FOUR_BIT_ARCHITECTURES.include?(os_architecture) && ruby_architecture_bits == 64 end |
#thirty_two_bit? ⇒ Boolean
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Whether both the OS and Ruby are 32-bit, so no WOW64 redirection is in play.
103 104 105 106 |
# File 'lib/kitchen/driver/powershell.rb', line 103 def thirty_two_bit? !SIXTY_FOUR_BIT_ARCHITECTURES.include?(os_architecture) && ruby_architecture_bits == 32 end |
#vm_default_switch_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that resolves the virtual switch to attach the VM to.
367 368 369 370 371 |
# File 'lib/kitchen/driver/powershell.rb', line 367 def vm_default_switch_ps <<-VMSWITCH Get-DefaultVMSwitch "#{config[:vm_switch]}" | ConvertTo-Json VMSWITCH end |
#vm_details_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Report if VM has no IP address instead of silently waiting forever
Script that reads the VM's name, id and IP address.
295 296 297 298 299 300 |
# File 'lib/kitchen/driver/powershell.rb', line 295 def vm_details_ps <<-DETAILS Get-VmDetail -id "#{@state[:id]}" | ConvertTo-Json DETAILS end |
#vm_status_ps ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Script that reads the VM's current power state without changing it.
Unlike #ensure_vm_running_ps, this never starts a stopped VM, so it is
safe for kitchen list --probe.
309 310 311 312 313 314 |
# File 'lib/kitchen/driver/powershell.rb', line 309 def vm_status_ps <<-STATUS Get-VmStatus -Id "#{@state[:id]}" | ConvertTo-Json STATUS end |
#wrap_command(script) ⇒ String
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Turn a script into a full powershell.exe command line.
Prepends a dot-source of the support script so the helper functions are defined, then encodes the result.
149 150 151 152 153 154 155 |
# File 'lib/kitchen/driver/powershell.rb', line 149 def wrap_command(script) debug("Loading functions from #{base_script_path}") new_script = [ ". #{base_script_path}", "#{script}" ].join(";\n") debug("Wrapped script: #{new_script}") "#{powershell_64_bit} -noprofile -executionpolicy bypass" \ " -encodedcommand #{encode_command new_script} -outputformat Text" end |