github.com/StackPointCloud/packer@v0.10.2-0.20180716202532-b28098e0f79b/builder/hyperv/iso/builder.go (about) 1 package iso 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "path/filepath" 9 "strings" 10 11 hypervcommon "github.com/hashicorp/packer/builder/hyperv/common" 12 "github.com/hashicorp/packer/common" 13 "github.com/hashicorp/packer/common/bootcommand" 14 powershell "github.com/hashicorp/packer/common/powershell" 15 "github.com/hashicorp/packer/common/powershell/hyperv" 16 "github.com/hashicorp/packer/helper/communicator" 17 "github.com/hashicorp/packer/helper/config" 18 "github.com/hashicorp/packer/helper/multistep" 19 "github.com/hashicorp/packer/packer" 20 "github.com/hashicorp/packer/template/interpolate" 21 ) 22 23 const ( 24 DefaultDiskSize = 40 * 1024 // ~40GB 25 MinDiskSize = 256 // 256MB 26 MaxDiskSize = 64 * 1024 * 1024 // 64TB 27 MaxVHDSize = 2040 * 1024 // 2040GB 28 29 DefaultDiskBlockSize = 32 // 32MB 30 MinDiskBlockSize = 1 // 1MB 31 MaxDiskBlockSize = 256 // 256MB 32 33 DefaultRamSize = 1 * 1024 // 1GB 34 MinRamSize = 32 // 32MB 35 MaxRamSize = 32 * 1024 // 32GB 36 MinNestedVirtualizationRamSize = 4 * 1024 // 4GB 37 38 LowRam = 256 // 256MB 39 40 DefaultUsername = "" 41 DefaultPassword = "" 42 ) 43 44 // Builder implements packer.Builder and builds the actual Hyperv 45 // images. 46 type Builder struct { 47 config Config 48 runner multistep.Runner 49 } 50 51 type Config struct { 52 common.PackerConfig `mapstructure:",squash"` 53 common.HTTPConfig `mapstructure:",squash"` 54 common.ISOConfig `mapstructure:",squash"` 55 common.FloppyConfig `mapstructure:",squash"` 56 bootcommand.BootConfig `mapstructure:",squash"` 57 hypervcommon.OutputConfig `mapstructure:",squash"` 58 hypervcommon.SSHConfig `mapstructure:",squash"` 59 hypervcommon.ShutdownConfig `mapstructure:",squash"` 60 61 // The size, in megabytes, of the hard disk to create for the VM. 62 // By default, this is 130048 (about 127 GB). 63 DiskSize uint `mapstructure:"disk_size"` 64 65 // The size, in megabytes, of the block size used to create the hard disk. 66 // By default, this is 32768 (about 32 MB) 67 DiskBlockSize uint `mapstructure:"disk_block_size"` 68 69 // The size, in megabytes, of the computer memory in the VM. 70 // By default, this is 1024 (about 1 GB). 71 RamSize uint `mapstructure:"ram_size"` 72 73 // 74 SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` 75 76 // Should integration services iso be mounted 77 GuestAdditionsMode string `mapstructure:"guest_additions_mode"` 78 79 // The path to the integration services iso 80 GuestAdditionsPath string `mapstructure:"guest_additions_path"` 81 82 // This is the name of the new virtual machine. 83 // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. 84 VMName string `mapstructure:"vm_name"` 85 86 SwitchName string `mapstructure:"switch_name"` 87 SwitchVlanId string `mapstructure:"switch_vlan_id"` 88 MacAddress string `mapstructure:"mac_address"` 89 VlanId string `mapstructure:"vlan_id"` 90 Cpu uint `mapstructure:"cpu"` 91 Generation uint `mapstructure:"generation"` 92 EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` 93 EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` 94 EnableSecureBoot bool `mapstructure:"enable_secure_boot"` 95 SecureBootTemplate string `mapstructure:"secure_boot_template"` 96 EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` 97 TempPath string `mapstructure:"temp_path"` 98 99 // A separate path can be used for storing the VM's disk image. The purpose is to enable 100 // reading and writing to take place on different physical disks (read from VHD temp path 101 // write to regular temp path while exporting the VM) to eliminate a single-disk bottleneck. 102 VhdTempPath string `mapstructure:"vhd_temp_path"` 103 104 Communicator string `mapstructure:"communicator"` 105 106 AdditionalDiskSize []uint `mapstructure:"disk_additional_size"` 107 108 SkipCompaction bool `mapstructure:"skip_compaction"` 109 110 SkipExport bool `mapstructure:"skip_export"` 111 112 // Use differencing disk 113 DifferencingDisk bool `mapstructure:"differencing_disk"` 114 115 // Create the VM with a Fixed VHD format disk instead of Dynamic VHDX 116 FixedVHD bool `mapstructure:"use_fixed_vhd_format"` 117 118 Headless bool `mapstructure:"headless"` 119 120 ctx interpolate.Context 121 } 122 123 // Prepare processes the build configuration parameters. 124 func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { 125 err := config.Decode(&b.config, &config.DecodeOpts{ 126 Interpolate: true, 127 InterpolateContext: &b.config.ctx, 128 InterpolateFilter: &interpolate.RenderFilter{ 129 Exclude: []string{ 130 "boot_command", 131 }, 132 }, 133 }, raws...) 134 if err != nil { 135 return nil, err 136 } 137 138 // Accumulate any errors and warnings 139 var errs *packer.MultiError 140 warnings := make([]string, 0) 141 142 isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) 143 warnings = append(warnings, isoWarnings...) 144 errs = packer.MultiErrorAppend(errs, isoErrs...) 145 146 errs = packer.MultiErrorAppend(errs, b.config.BootConfig.Prepare(&b.config.ctx)...) 147 errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) 148 errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) 149 errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) 150 errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) 151 errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) 152 153 if len(b.config.ISOConfig.ISOUrls) < 1 || (strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhd" && strings.ToLower(filepath.Ext(b.config.ISOConfig.ISOUrls[0])) != ".vhdx") { 154 //We only create a new hard drive if an existing one to copy from does not exist 155 err = b.checkDiskSize() 156 if err != nil { 157 errs = packer.MultiErrorAppend(errs, err) 158 } 159 } 160 161 err = b.checkDiskBlockSize() 162 if err != nil { 163 errs = packer.MultiErrorAppend(errs, err) 164 } 165 166 err = b.checkRamSize() 167 if err != nil { 168 errs = packer.MultiErrorAppend(errs, err) 169 } 170 171 if b.config.VMName == "" { 172 b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) 173 } 174 175 log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) 176 177 if b.config.SwitchName == "" { 178 b.config.SwitchName = b.detectSwitchName() 179 } 180 181 if b.config.Cpu < 1 { 182 b.config.Cpu = 1 183 } 184 185 if b.config.Generation < 1 || b.config.Generation > 2 { 186 b.config.Generation = 1 187 } 188 189 if b.config.Generation == 2 { 190 if len(b.config.FloppyFiles) > 0 || len(b.config.FloppyDirectories) > 0 { 191 err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.") 192 errs = packer.MultiErrorAppend(errs, err) 193 } 194 } 195 196 if len(b.config.AdditionalDiskSize) > 64 { 197 err = errors.New("VM's currently support a maximum of 64 additional SCSI attached disks.") 198 errs = packer.MultiErrorAppend(errs, err) 199 } 200 201 log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) 202 log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) 203 204 // Errors 205 206 if b.config.GuestAdditionsMode == "" { 207 if b.config.GuestAdditionsPath != "" { 208 b.config.GuestAdditionsMode = "attach" 209 } else { 210 b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" 211 212 if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { 213 if err != nil { 214 b.config.GuestAdditionsPath = "" 215 b.config.GuestAdditionsMode = "none" 216 } else { 217 b.config.GuestAdditionsMode = "attach" 218 } 219 } 220 } 221 } 222 223 if b.config.GuestAdditionsPath == "" && b.config.GuestAdditionsMode == "attach" { 224 b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" 225 226 if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { 227 if err != nil { 228 b.config.GuestAdditionsPath = "" 229 } 230 } 231 } 232 233 for _, isoPath := range b.config.SecondaryDvdImages { 234 if _, err := os.Stat(isoPath); os.IsNotExist(err) { 235 if err != nil { 236 errs = packer.MultiErrorAppend( 237 errs, fmt.Errorf("Secondary Dvd image does not exist: %s", err)) 238 } 239 } 240 } 241 242 numberOfIsos := len(b.config.SecondaryDvdImages) 243 244 if b.config.GuestAdditionsMode == "attach" { 245 if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { 246 if err != nil { 247 errs = packer.MultiErrorAppend( 248 errs, fmt.Errorf("Guest additions iso does not exist: %s", err)) 249 } 250 } 251 252 numberOfIsos = numberOfIsos + 1 253 } 254 255 if b.config.Generation < 2 && numberOfIsos > 2 { 256 if b.config.GuestAdditionsMode == "attach" { 257 errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) 258 } else { 259 errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are only 2 ide controllers available, so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) 260 } 261 } else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 { 262 if b.config.GuestAdditionsMode == "attach" { 263 errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support guest additions and these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) 264 } else { 265 errs = packer.MultiErrorAppend(errs, fmt.Errorf("There are not enough drive letters available for scsi (limited to 16), so we can't support these secondary dvds: %s", strings.Join(b.config.SecondaryDvdImages, ", "))) 266 } 267 } 268 269 if b.config.EnableVirtualizationExtensions { 270 hasVirtualMachineVirtualizationExtensions, err := powershell.HasVirtualMachineVirtualizationExtensions() 271 if err != nil { 272 errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization extensions support: %s", err)) 273 } else { 274 if !hasVirtualMachineVirtualizationExtensions { 275 errs = packer.MultiErrorAppend(errs, fmt.Errorf("This version of Hyper-V does not support virtual machine virtualization extension. Please use Windows 10 or Windows Server 2016 or newer.")) 276 } 277 } 278 } 279 280 if b.config.Generation > 1 && b.config.FixedVHD { 281 err = errors.New("Fixed VHD disks are only supported on Generation 1 virtual machines.") 282 errs = packer.MultiErrorAppend(errs, err) 283 } 284 285 if !b.config.SkipCompaction && b.config.FixedVHD { 286 err = errors.New("Fixed VHD disks do not support compaction.") 287 errs = packer.MultiErrorAppend(errs, err) 288 } 289 290 if b.config.DifferencingDisk && b.config.FixedVHD { 291 err = errors.New("Fixed VHD disks are not supported with differencing disks.") 292 errs = packer.MultiErrorAppend(errs, err) 293 } 294 295 // Warnings 296 297 if b.config.ShutdownCommand == "" { 298 warnings = append(warnings, 299 "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ 300 "will forcibly halt the virtual machine, which may result in data loss.") 301 } 302 303 warning := b.checkHostAvailableMemory() 304 if warning != "" { 305 warnings = appendWarnings(warnings, warning) 306 } 307 308 if b.config.EnableVirtualizationExtensions { 309 if b.config.EnableDynamicMemory { 310 warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, dynamic memory should not be allowed.") 311 warnings = appendWarnings(warnings, warning) 312 } 313 314 if !b.config.EnableMacSpoofing { 315 warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, mac spoofing should be allowed.") 316 warnings = appendWarnings(warnings, warning) 317 } 318 319 if b.config.RamSize < MinNestedVirtualizationRamSize { 320 warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, there should be 4GB or more memory set for the vm, otherwise Hyper-V may fail to start any nested VMs.") 321 warnings = appendWarnings(warnings, warning) 322 } 323 } 324 325 if b.config.SwitchVlanId != "" { 326 if b.config.SwitchVlanId != b.config.VlanId { 327 warning = fmt.Sprintf("Switch network adaptor vlan should match virtual machine network adaptor vlan. The switch will not be able to see traffic from the VM.") 328 warnings = appendWarnings(warnings, warning) 329 } 330 } 331 332 if errs != nil && len(errs.Errors) > 0 { 333 return warnings, errs 334 } 335 336 return warnings, nil 337 } 338 339 // Run executes a Packer build and returns a packer.Artifact representing 340 // a Hyperv appliance. 341 func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { 342 // Create the driver that we'll use to communicate with Hyperv 343 driver, err := hypervcommon.NewHypervPS4Driver() 344 if err != nil { 345 return nil, fmt.Errorf("Failed creating Hyper-V driver: %s", err) 346 } 347 348 // Set up the state. 349 state := new(multistep.BasicStateBag) 350 state.Put("cache", cache) 351 state.Put("config", &b.config) 352 state.Put("debug", b.config.PackerDebug) 353 state.Put("driver", driver) 354 state.Put("hook", hook) 355 state.Put("ui", ui) 356 357 steps := []multistep.Step{ 358 &hypervcommon.StepCreateTempDir{ 359 TempPath: b.config.TempPath, 360 VhdTempPath: b.config.VhdTempPath, 361 }, 362 &hypervcommon.StepOutputDir{ 363 Force: b.config.PackerForce, 364 Path: b.config.OutputDir, 365 }, 366 &common.StepDownload{ 367 Checksum: b.config.ISOChecksum, 368 ChecksumType: b.config.ISOChecksumType, 369 Description: "ISO", 370 ResultKey: "iso_path", 371 Url: b.config.ISOUrls, 372 Extension: b.config.TargetExtension, 373 TargetPath: b.config.TargetPath, 374 }, 375 &common.StepCreateFloppy{ 376 Files: b.config.FloppyConfig.FloppyFiles, 377 Directories: b.config.FloppyConfig.FloppyDirectories, 378 }, 379 &common.StepHTTPServer{ 380 HTTPDir: b.config.HTTPDir, 381 HTTPPortMin: b.config.HTTPPortMin, 382 HTTPPortMax: b.config.HTTPPortMax, 383 }, 384 &hypervcommon.StepCreateSwitch{ 385 SwitchName: b.config.SwitchName, 386 }, 387 &hypervcommon.StepCreateVM{ 388 VMName: b.config.VMName, 389 SwitchName: b.config.SwitchName, 390 RamSize: b.config.RamSize, 391 DiskSize: b.config.DiskSize, 392 DiskBlockSize: b.config.DiskBlockSize, 393 Generation: b.config.Generation, 394 Cpu: b.config.Cpu, 395 EnableMacSpoofing: b.config.EnableMacSpoofing, 396 EnableDynamicMemory: b.config.EnableDynamicMemory, 397 EnableSecureBoot: b.config.EnableSecureBoot, 398 SecureBootTemplate: b.config.SecureBootTemplate, 399 EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions, 400 AdditionalDiskSize: b.config.AdditionalDiskSize, 401 DifferencingDisk: b.config.DifferencingDisk, 402 SkipExport: b.config.SkipExport, 403 OutputDir: b.config.OutputDir, 404 MacAddress: b.config.MacAddress, 405 FixedVHD: b.config.FixedVHD, 406 }, 407 &hypervcommon.StepEnableIntegrationService{}, 408 409 &hypervcommon.StepMountDvdDrive{ 410 Generation: b.config.Generation, 411 }, 412 &hypervcommon.StepMountFloppydrive{ 413 Generation: b.config.Generation, 414 }, 415 416 &hypervcommon.StepMountGuestAdditions{ 417 GuestAdditionsMode: b.config.GuestAdditionsMode, 418 GuestAdditionsPath: b.config.GuestAdditionsPath, 419 Generation: b.config.Generation, 420 }, 421 422 &hypervcommon.StepMountSecondaryDvdImages{ 423 IsoPaths: b.config.SecondaryDvdImages, 424 Generation: b.config.Generation, 425 }, 426 427 &hypervcommon.StepConfigureVlan{ 428 VlanId: b.config.VlanId, 429 SwitchVlanId: b.config.SwitchVlanId, 430 }, 431 432 &hypervcommon.StepRun{ 433 Headless: b.config.Headless, 434 }, 435 436 &hypervcommon.StepTypeBootCommand{ 437 BootCommand: b.config.FlatBootCommand(), 438 BootWait: b.config.BootWait, 439 SwitchName: b.config.SwitchName, 440 Ctx: b.config.ctx, 441 }, 442 443 // configure the communicator ssh, winrm 444 &communicator.StepConnect{ 445 Config: &b.config.SSHConfig.Comm, 446 Host: hypervcommon.CommHost, 447 SSHConfig: hypervcommon.SSHConfigFunc(&b.config.SSHConfig), 448 }, 449 450 // provision requires communicator to be setup 451 &common.StepProvision{}, 452 453 &hypervcommon.StepShutdown{ 454 Command: b.config.ShutdownCommand, 455 Timeout: b.config.ShutdownTimeout, 456 }, 457 458 // wait for the vm to be powered off 459 &hypervcommon.StepWaitForPowerOff{}, 460 461 // remove the secondary dvd images 462 // after we power down 463 &hypervcommon.StepUnmountSecondaryDvdImages{}, 464 &hypervcommon.StepUnmountGuestAdditions{}, 465 &hypervcommon.StepUnmountDvdDrive{}, 466 &hypervcommon.StepUnmountFloppyDrive{ 467 Generation: b.config.Generation, 468 }, 469 &hypervcommon.StepExportVm{ 470 OutputDir: b.config.OutputDir, 471 SkipCompaction: b.config.SkipCompaction, 472 SkipExport: b.config.SkipExport, 473 }, 474 475 // the clean up actions for each step will be executed reverse order 476 } 477 478 // Run the steps. 479 b.runner = common.NewRunner(steps, b.config.PackerConfig, ui) 480 b.runner.Run(state) 481 482 // Report any errors. 483 if rawErr, ok := state.GetOk("error"); ok { 484 return nil, rawErr.(error) 485 } 486 487 // If we were interrupted or cancelled, then just exit. 488 if _, ok := state.GetOk(multistep.StateCancelled); ok { 489 return nil, errors.New("Build was cancelled.") 490 } 491 492 if _, ok := state.GetOk(multistep.StateHalted); ok { 493 return nil, errors.New("Build was halted.") 494 } 495 496 return hypervcommon.NewArtifact(b.config.OutputDir) 497 } 498 499 // Cancel. 500 func (b *Builder) Cancel() { 501 if b.runner != nil { 502 log.Println("Cancelling the step runner...") 503 b.runner.Cancel() 504 } 505 } 506 507 func appendWarnings(slice []string, data ...string) []string { 508 m := len(slice) 509 n := m + len(data) 510 if n > cap(slice) { // if necessary, reallocate 511 // allocate double what's needed, for future growth. 512 newSlice := make([]string, (n+1)*2) 513 copy(newSlice, slice) 514 slice = newSlice 515 } 516 slice = slice[0:n] 517 copy(slice[m:n], data) 518 return slice 519 } 520 521 func (b *Builder) checkDiskSize() error { 522 if b.config.DiskSize == 0 { 523 b.config.DiskSize = DefaultDiskSize 524 } 525 526 log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize)) 527 528 if b.config.DiskSize < MinDiskSize { 529 return fmt.Errorf("disk_size: Virtual machine requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) 530 } else if b.config.DiskSize > MaxDiskSize && !b.config.FixedVHD { 531 return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) 532 } else if b.config.DiskSize > MaxVHDSize && b.config.FixedVHD { 533 return fmt.Errorf("disk_size: Virtual machine requires disk space <= %v GB, but defined: %v", MaxVHDSize/1024, b.config.DiskSize/1024) 534 } 535 536 return nil 537 } 538 539 func (b *Builder) checkDiskBlockSize() error { 540 if b.config.DiskBlockSize == 0 { 541 b.config.DiskBlockSize = DefaultDiskBlockSize 542 } 543 544 log.Println(fmt.Sprintf("%s: %v", "DiskBlockSize", b.config.DiskBlockSize)) 545 546 if b.config.DiskBlockSize < MinDiskBlockSize { 547 return fmt.Errorf("disk_block_size: Virtual machine requires disk block size >= %v MB, but defined: %v", MinDiskBlockSize, b.config.DiskBlockSize) 548 } else if b.config.DiskBlockSize > MaxDiskBlockSize { 549 return fmt.Errorf("disk_block_size: Virtual machine requires disk block size <= %v MB, but defined: %v", MaxDiskBlockSize, b.config.DiskBlockSize) 550 } 551 552 return nil 553 } 554 555 func (b *Builder) checkRamSize() error { 556 if b.config.RamSize == 0 { 557 b.config.RamSize = DefaultRamSize 558 } 559 560 log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSize)) 561 562 if b.config.RamSize < MinRamSize { 563 return fmt.Errorf("ram_size: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSize) 564 } else if b.config.RamSize > MaxRamSize { 565 return fmt.Errorf("ram_size: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSize) 566 } 567 568 return nil 569 } 570 571 func (b *Builder) checkHostAvailableMemory() string { 572 powershellAvailable, _, _ := powershell.IsPowershellAvailable() 573 574 if powershellAvailable { 575 freeMB := powershell.GetHostAvailableMemory() 576 577 if (freeMB - float64(b.config.RamSize)) < LowRam { 578 return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") 579 } 580 } 581 582 return "" 583 } 584 585 func (b *Builder) detectSwitchName() string { 586 powershellAvailable, _, _ := powershell.IsPowershellAvailable() 587 588 if powershellAvailable { 589 // no switch name, try to get one attached to a online network adapter 590 onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() 591 if onlineSwitchName != "" && err == nil { 592 return onlineSwitchName 593 } 594 } 595 596 return fmt.Sprintf("packer-%s", b.config.PackerBuildName) 597 }