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.
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.
-
#is_32bit? ⇒ Boolean
private
Whether both the OS and Ruby are 32-bit, so no WOW64 redirection is in play.
-
#is_64bit? ⇒ Boolean
private
Whether a 64-bit PowerShell is directly reachable.
-
#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.
-
#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.
-
#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.
-
#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.
-
#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.
239 240 241 242 243 244 245 |
# File 'lib/kitchen/driver/powershell.rb', line 239 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.
341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/kitchen/driver/powershell.rb', line 341 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.
263 264 265 266 267 268 269 270 |
# File 'lib/kitchen/driver/powershell.rb', line 263 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.
50 51 52 53 |
# File 'lib/kitchen/driver/powershell.rb', line 50 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.
195 196 197 198 199 200 |
# File 'lib/kitchen/driver/powershell.rb', line 195 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.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
# File 'lib/kitchen/driver/powershell.rb', line 141 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 |
#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.
Whether both the OS and Ruby are 32-bit, so no WOW64 redirection is in play.
75 76 77 78 79 |
# File 'lib/kitchen/driver/powershell.rb', line 75 def is_32bit? os_arch = ENV["PROCESSOR_ARCHITEW6432"] || ENV["PROCESSOR_ARCHITECTURE"] ruby_arch = ["foo"].pack("p").size == 4 ? 32 : 64 os_arch != "AMD64" && ruby_arch == 32 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.
Whether a 64-bit PowerShell is directly reachable.
Always true for a remote host, where the local architecture is irrelevant.
62 63 64 65 66 67 68 |
# File 'lib/kitchen/driver/powershell.rb', line 62 def is_64bit? return true if remote_hyperv os_arch = ENV["PROCESSOR_ARCHITEW6432"] || ENV["PROCESSOR_ARCHITECTURE"] ruby_arch = ["foo"].pack("p").size == 4 ? 32 : 64 os_arch == "AMD64" && ruby_arch == 64 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.
306 307 308 309 310 |
# File 'lib/kitchen/driver/powershell.rb', line 306 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.
184 185 186 187 188 189 |
# File 'lib/kitchen/driver/powershell.rb', line 184 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.
171 172 173 174 175 176 |
# File 'lib/kitchen/driver/powershell.rb', line 171 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.
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 |
# File 'lib/kitchen/driver/powershell.rb', line 206 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 |
#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.
90 91 92 93 94 95 96 |
# File 'lib/kitchen/driver/powershell.rb', line 90 def powershell_64_bit if is_64bit? || is_32bit? '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.
316 317 318 319 320 |
# File 'lib/kitchen/driver/powershell.rb', line 316 def resize_vhd <<-VMNOTE Resize-VHD -Path "#{parent_vhd_path}" -SizeBytes #{config[:resize_vhd]} VMNOTE 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.
125 126 127 128 129 130 131 |
# File 'lib/kitchen/driver/powershell.rb', line 125 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.
162 163 164 |
# File 'lib/kitchen/driver/powershell.rb', line 162 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.
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 |
# File 'lib/kitchen/driver/powershell.rb', line 276 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.
326 327 328 329 330 |
# File 'lib/kitchen/driver/powershell.rb', line 326 def set_vm_note <<-VMNOTE Set-VM -Name (Get-VM | Where-Object{ $_.ID -eq "#{@state[:id]}"}).Name -Note "#{config[:vm_note]}" VMNOTE 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.
296 297 298 299 300 |
# File 'lib/kitchen/driver/powershell.rb', line 296 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.
252 253 254 255 256 257 |
# File 'lib/kitchen/driver/powershell.rb', line 252 def vm_details_ps <<-DETAILS Get-VmDetail -id "#{@state[:id]}" | ConvertTo-Json DETAILS 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.
106 107 108 109 110 111 112 |
# File 'lib/kitchen/driver/powershell.rb', line 106 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 |