From 5b7adb646495f14e97908e64e6d402f272288ed5 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 12:36:07 +0100 Subject: [PATCH 001/113] Port of https://github.com/pbolduc/packer-hyperv/ Remove steps that are windows specific --- builder/hyperv/common/artifact.go | 65 +++ builder/hyperv/common/artifact_test.go | 43 ++ builder/hyperv/common/config_test.go | 11 + builder/hyperv/common/driver.go | 34 ++ builder/hyperv/common/driver_ps_4.go | 142 ++++++ builder/hyperv/common/output_config.go | 28 ++ builder/hyperv/common/output_config_test.go | 45 ++ builder/hyperv/common/ssh.go | 51 +++ builder/hyperv/common/ssh_config.go | 29 ++ builder/hyperv/common/step_configure_ip.go | 83 ++++ builder/hyperv/common/step_configure_vlan.go | 54 +++ .../common/step_create_external_switch.go | 107 +++++ builder/hyperv/common/step_create_switch.go | 83 ++++ builder/hyperv/common/step_create_tempdir.go | 55 +++ builder/hyperv/common/step_create_vm.go | 67 +++ builder/hyperv/common/step_disable_vlan.go | 41 ++ .../common/step_enable_integration_service.go | 39 ++ .../common/step_execute_online_activation.go | 62 +++ .../step_execute_online_activation_full.go | 90 ++++ builder/hyperv/common/step_export_vm.go | 74 +++ builder/hyperv/common/step_mount_dvddrive.go | 59 +++ .../hyperv/common/step_mount_floppydrive.go | 189 ++++++++ .../common/step_mount_integration_services.go | 142 ++++++ builder/hyperv/common/step_output_dir.go | 71 +++ .../common/step_polling_installation.go | 114 +++++ builder/hyperv/common/step_reboot_vm.go | 45 ++ builder/hyperv/common/step_shutdown.go | 141 ++++++ builder/hyperv/common/step_sleep.go | 32 ++ builder/hyperv/common/step_start_vm.go | 48 ++ .../hyperv/common/step_unmount_dvddrive.go | 38 ++ .../hyperv/common/step_unmount_floppydrive.go | 39 ++ .../step_unmount_integration_services.go | 52 +++ .../step_upgrade_integration_services.go | 110 +++++ .../step_wait_for_install_to_complete.go | 130 ++++++ builder/hyperv/iso/builder.go | 391 ++++++++++++++++ .../build-and-deploy.sh | 3 + plugin/packer-builder-hyperv-iso/main.go | 19 + plugin/packer-builder-hyperv-iso/main_test.go | 5 + powershell/hyperv/hyperv.go | 433 ++++++++++++++++++ powershell/powershell.go | 255 +++++++++++ powershell/powershell_test.go | 72 +++ powershell/scriptbuilder.go | 30 ++ 42 files changed, 3621 insertions(+) create mode 100644 builder/hyperv/common/artifact.go create mode 100644 builder/hyperv/common/artifact_test.go create mode 100644 builder/hyperv/common/config_test.go create mode 100644 builder/hyperv/common/driver.go create mode 100644 builder/hyperv/common/driver_ps_4.go create mode 100644 builder/hyperv/common/output_config.go create mode 100644 builder/hyperv/common/output_config_test.go create mode 100644 builder/hyperv/common/ssh.go create mode 100644 builder/hyperv/common/ssh_config.go create mode 100644 builder/hyperv/common/step_configure_ip.go create mode 100644 builder/hyperv/common/step_configure_vlan.go create mode 100644 builder/hyperv/common/step_create_external_switch.go create mode 100644 builder/hyperv/common/step_create_switch.go create mode 100644 builder/hyperv/common/step_create_tempdir.go create mode 100644 builder/hyperv/common/step_create_vm.go create mode 100644 builder/hyperv/common/step_disable_vlan.go create mode 100644 builder/hyperv/common/step_enable_integration_service.go create mode 100644 builder/hyperv/common/step_execute_online_activation.go create mode 100644 builder/hyperv/common/step_execute_online_activation_full.go create mode 100644 builder/hyperv/common/step_export_vm.go create mode 100644 builder/hyperv/common/step_mount_dvddrive.go create mode 100644 builder/hyperv/common/step_mount_floppydrive.go create mode 100644 builder/hyperv/common/step_mount_integration_services.go create mode 100644 builder/hyperv/common/step_output_dir.go create mode 100644 builder/hyperv/common/step_polling_installation.go create mode 100644 builder/hyperv/common/step_reboot_vm.go create mode 100644 builder/hyperv/common/step_shutdown.go create mode 100644 builder/hyperv/common/step_sleep.go create mode 100644 builder/hyperv/common/step_start_vm.go create mode 100644 builder/hyperv/common/step_unmount_dvddrive.go create mode 100644 builder/hyperv/common/step_unmount_floppydrive.go create mode 100644 builder/hyperv/common/step_unmount_integration_services.go create mode 100644 builder/hyperv/common/step_upgrade_integration_services.go create mode 100644 builder/hyperv/common/step_wait_for_install_to_complete.go create mode 100644 builder/hyperv/iso/builder.go create mode 100644 plugin/packer-builder-hyperv-iso/build-and-deploy.sh create mode 100644 plugin/packer-builder-hyperv-iso/main.go create mode 100644 plugin/packer-builder-hyperv-iso/main_test.go create mode 100644 powershell/hyperv/hyperv.go create mode 100644 powershell/powershell.go create mode 100644 powershell/powershell_test.go create mode 100644 powershell/scriptbuilder.go diff --git a/builder/hyperv/common/artifact.go b/builder/hyperv/common/artifact.go new file mode 100644 index 00000000000..de7f94ca72c --- /dev/null +++ b/builder/hyperv/common/artifact.go @@ -0,0 +1,65 @@ +package common + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/mitchellh/packer/packer" +) + +// This is the common builder ID to all of these artifacts. +const BuilderId = "mitchellh.hyperv" + +// Artifact is the result of running the hyperv builder, namely a set +// of files associated with the resulting machine. +type artifact struct { + dir string + f []string +} + +// NewArtifact returns a hyperv artifact containing the files +// in the given directory. +func NewArtifact(dir string) (packer.Artifact, error) { + files := make([]string, 0, 5) + visit := func(path string, info os.FileInfo, err error) error { + if !info.IsDir() { + files = append(files, path) + } + + return err + } + + if err := filepath.Walk(dir, visit); err != nil { + return nil, err + } + + return &artifact{ + dir: dir, + f: files, + }, nil +} + +func (*artifact) BuilderId() string { + return BuilderId +} + +func (a *artifact) Files() []string { + return a.f +} + +func (*artifact) Id() string { + return "VM" +} + +func (a *artifact) String() string { + return fmt.Sprintf("VM files in directory: %s", a.dir) +} + +func (a *artifact) State(name string) interface{} { + return nil +} + +func (a *artifact) Destroy() error { + return os.RemoveAll(a.dir) +} diff --git a/builder/hyperv/common/artifact_test.go b/builder/hyperv/common/artifact_test.go new file mode 100644 index 00000000000..f9ddc5dbfc8 --- /dev/null +++ b/builder/hyperv/common/artifact_test.go @@ -0,0 +1,43 @@ +package common + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/mitchellh/packer/packer" +) + +func TestArtifact_impl(t *testing.T) { + var _ packer.Artifact = new(artifact) +} + +func TestNewArtifact(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + err = ioutil.WriteFile(filepath.Join(td, "a"), []byte("foo"), 0644) + if err != nil { + t.Fatalf("err: %s", err) + } + + if err := os.Mkdir(filepath.Join(td, "b"), 0755); err != nil { + t.Fatalf("err: %s", err) + } + + a, err := NewArtifact(td) + if err != nil { + t.Fatalf("err: %s", err) + } + + if a.BuilderId() != BuilderId { + t.Fatalf("bad: %#v", a.BuilderId()) + } + if len(a.Files()) != 1 { + t.Fatalf("should length 1: %d", len(a.Files())) + } +} diff --git a/builder/hyperv/common/config_test.go b/builder/hyperv/common/config_test.go new file mode 100644 index 00000000000..eeeda864a3d --- /dev/null +++ b/builder/hyperv/common/config_test.go @@ -0,0 +1,11 @@ +package common + +import ( + "testing" + + "github.com/mitchellh/packer/template/interpolate" +) + +func testConfigTemplate(t *testing.T) *interpolate.Context { + return &interpolate.Context{} +} diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go new file mode 100644 index 00000000000..2c9f570b4c5 --- /dev/null +++ b/builder/hyperv/common/driver.go @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +// A driver is able to talk to HyperV and perform certain +// operations with it. Some of the operations on here may seem overly +// specific, but they were built specifically in mind to handle features +// of the HyperV builder for Packer, and to abstract differences in +// versions out of the builder steps, so sometimes the methods are +// extremely specific. +type Driver interface { + + // Checks if the VM named is running. + IsRunning(string) (bool, error) + + // Start starts a VM specified by the name given. + Start(string) error + + // Stop stops a VM specified by the name given. + Stop(string) error + + // Verify checks to make sure that this driver should function + // properly. If there is any indication the driver can't function, + // this will return an error. + Verify() error + + // Finds the MAC address of the NIC nic0 + Mac(string) (string, error) + + // Finds the IP address of a VM connected that uses DHCP by its MAC address + IpAddress(string) (string, error) +} diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go new file mode 100644 index 00000000000..a5c2abd6f91 --- /dev/null +++ b/builder/hyperv/common/driver_ps_4.go @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" + "log" + "runtime" + "strconv" + "strings" +) + +type HypervPS4Driver struct { +} + +func NewHypervPS4Driver() (Driver, error) { + appliesTo := "Applies to Windows 8.1, Windows PowerShell 4.0, Windows Server 2012 R2 only" + + // Check this is Windows + if runtime.GOOS != "windows" { + err := fmt.Errorf("%s", appliesTo) + return nil, err + } + + ps4Driver := &HypervPS4Driver{} + + if err := ps4Driver.Verify(); err != nil { + return nil, err + } + + return ps4Driver, nil +} + +func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) { + return hyperv.IsRunning(vmName) +} + +// Start starts a VM specified by the name given. +func (d *HypervPS4Driver) Start(vmName string) error { + return hyperv.Start(vmName) +} + +// Stop stops a VM specified by the name given. +func (d *HypervPS4Driver) Stop(vmName string) error { + return hyperv.TurnOff(vmName) +} + +func (d *HypervPS4Driver) Verify() error { + + if err := d.verifyPSVersion(); err != nil { + return err + } + + if err := d.verifyPSHypervModule(); err != nil { + return err + } + + if err := d.verifyElevatedMode(); err != nil { + return err + } + + return nil +} + +// Get mac address for VM. +func (d *HypervPS4Driver) Mac(vmName string) (string, error) { + return hyperv.Mac(vmName) +} + +// Get ip address for mac address. +func (d *HypervPS4Driver) IpAddress(mac string) (string, error) { + return hyperv.IpAddress(mac) +} + +func (d *HypervPS4Driver) verifyPSVersion() error { + + log.Printf("Enter method: %s", "verifyPSVersion") + // check PS is available and is of proper version + versionCmd := "$host.version.Major" + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(versionCmd) + if err != nil { + return err + } + + versionOutput := strings.TrimSpace(string(cmdOut)) + log.Printf("%s output: %s", versionCmd, versionOutput) + + ver, err := strconv.ParseInt(versionOutput, 10, 32) + + if err != nil { + return err + } + + if ver < 4 { + err := fmt.Errorf("%s", "Windows PowerShell version 4.0 or higher is expected") + return err + } + + return nil +} + +func (d *HypervPS4Driver) verifyPSHypervModule() error { + + log.Printf("Enter method: %s", "verifyPSHypervModule") + + versionCmd := "function foo(){try{ $commands = Get-Command -Module Hyper-V;if($commands.Length -eq 0){return $false} }catch{return $false}; return $true} foo" + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(versionCmd) + if err != nil { + return err + } + + res := strings.TrimSpace(string(cmdOut)) + + if res == "False" { + err := fmt.Errorf("%s", "PS Hyper-V module is not loaded. Make sure Hyper-V feature is on.") + return err + } + + return nil +} + +func (d *HypervPS4Driver) verifyElevatedMode() error { + + log.Printf("Enter method: %s", "verifyElevatedMode") + + isAdmin, _ := powershell.IsCurrentUserAnAdministrator() + + if !isAdmin { + err := fmt.Errorf("%s", "Please restart your shell in elevated mode") + return err + } + + return nil +} diff --git a/builder/hyperv/common/output_config.go b/builder/hyperv/common/output_config.go new file mode 100644 index 00000000000..40b10e6e552 --- /dev/null +++ b/builder/hyperv/common/output_config.go @@ -0,0 +1,28 @@ +package common + +import ( + "fmt" + "os" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/template/interpolate" +) + +type OutputConfig struct { + OutputDir string `mapstructure:"output_directory"` +} + +func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig) []error { + if c.OutputDir == "" { + c.OutputDir = fmt.Sprintf("output-%s", pc.PackerBuildName) + } + + var errs []error + if !pc.PackerForce { + if _, err := os.Stat(c.OutputDir); err == nil { + errs = append(errs, fmt.Errorf( + "Output directory '%s' already exists. It must not exist.", c.OutputDir)) + } + } + + return errs +} diff --git a/builder/hyperv/common/output_config_test.go b/builder/hyperv/common/output_config_test.go new file mode 100644 index 00000000000..a4d8e7999a9 --- /dev/null +++ b/builder/hyperv/common/output_config_test.go @@ -0,0 +1,45 @@ +package common + +import ( + "github.com/mitchellh/packer/common" + "io/ioutil" + "os" + "testing" +) + +func TestOutputConfigPrepare(t *testing.T) { + c := new(OutputConfig) + if c.OutputDir != "" { + t.Fatalf("what: %s", c.OutputDir) + } + + pc := &common.PackerConfig{PackerBuildName: "foo"} + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if c.OutputDir == "" { + t.Fatal("should have output dir") + } +} + +func TestOutputConfigPrepare_exists(t *testing.T) { + td, err := ioutil.TempDir("", "packer") + if err != nil { + t.Fatalf("err: %s", err) + } + defer os.RemoveAll(td) + + c := new(OutputConfig) + c.OutputDir = td + + pc := &common.PackerConfig{ + PackerBuildName: "foo", + PackerForce: false, + } + errs := c.Prepare(testConfigTemplate(t), pc) + if len(errs) != 0 { + t.Fatal("should not have errors") + } +} diff --git a/builder/hyperv/common/ssh.go b/builder/hyperv/common/ssh.go new file mode 100644 index 00000000000..f61047dd496 --- /dev/null +++ b/builder/hyperv/common/ssh.go @@ -0,0 +1,51 @@ +package common + +import ( + "github.com/mitchellh/multistep" + commonssh "github.com/mitchellh/packer/common/ssh" + packerssh "github.com/mitchellh/packer/communicator/ssh" + "golang.org/x/crypto/ssh" +) + +func CommHost(state multistep.StateBag) (string, error) { + vmName := state.Get("vmName").(string) + driver := state.Get("driver").(Driver) + + mac, err := driver.Mac(vmName) + if err != nil { + return "", err + } + + ip, err := driver.IpAddress(mac) + if err != nil { + return "", err + } + + return ip, nil +} + +func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) { + return func(state multistep.StateBag) (*ssh.ClientConfig, error) { + auth := []ssh.AuthMethod{ + ssh.Password(config.Comm.SSHPassword), + ssh.KeyboardInteractive( + packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)), + } + + if config.SSHKeyPath != "" { + signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey) + if err != nil { + return nil, err + } + + auth = append(auth, ssh.PublicKeys(signer)) + } + + return &ssh.ClientConfig{ + User: config.Comm.SSHUsername, + Auth: auth, + }, nil + } +} + + diff --git a/builder/hyperv/common/ssh_config.go b/builder/hyperv/common/ssh_config.go new file mode 100644 index 00000000000..bea164b0683 --- /dev/null +++ b/builder/hyperv/common/ssh_config.go @@ -0,0 +1,29 @@ +package common + +import ( + "time" + + "github.com/mitchellh/packer/helper/communicator" + "github.com/mitchellh/packer/template/interpolate" +) + +type SSHConfig struct { + Comm communicator.Config `mapstructure:",squash"` + + // These are deprecated, but we keep them around for BC + // TODO(@mitchellh): remove + SSHKeyPath string `mapstructure:"ssh_key_path"` + SSHWaitTimeout time.Duration `mapstructure:"ssh_wait_timeout"` +} + +func (c *SSHConfig) Prepare(ctx *interpolate.Context) []error { + // TODO: backwards compatibility, write fixer instead + if c.SSHKeyPath != "" { + c.Comm.SSHPrivateKey = c.SSHKeyPath + } + if c.SSHWaitTimeout != 0 { + c.Comm.SSHTimeout = c.SSHWaitTimeout + } + + return c.Comm.Prepare(ctx) +} diff --git a/builder/hyperv/common/step_configure_ip.go b/builder/hyperv/common/step_configure_ip.go new file mode 100644 index 00000000000..ea35818e9b6 --- /dev/null +++ b/builder/hyperv/common/step_configure_ip.go @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "time" + "log" + powershell "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +type StepConfigureIp struct { +} + +func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { +// driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error configuring ip address: %s" + vmName := state.Get("vmName").(string) + + ui.Say("Configuring ip address...") + + count := 60 + var duration time.Duration = 1 + sleepTime := time.Minute * duration + var ip string + + for count != 0 { + cmdOut, err := hyperv.GetVirtualMachineNetworkAdapterAddress(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ip = strings.TrimSpace(string(cmdOut)) + + if ip != "False" { + break; + } + + log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration))) + time.Sleep(sleepTime) + count-- + } + + if(count == 0){ + err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("ip address is " + ip) + + hostName, err := powershell.GetHostName(ip); + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("hostname is " + hostName) + + state.Put("ip", ip) + state.Put("hostname", hostName) + + return multistep.ActionContinue +} + +func (s *StepConfigureIp) Cleanup(state multistep.StateBag) { + // do nothing +} + diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go new file mode 100644 index 00000000000..508b43e9879 --- /dev/null +++ b/builder/hyperv/common/step_configure_vlan.go @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +const( + vlanId = "1724" +) + +type StepConfigureVlan struct { +} + +func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { + //config := state.Get("config").(*config) + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error configuring vlan: %s" + vmName := state.Get("vmName").(string) + switchName := state.Get("SwitchName").(string) + + ui.Say("Configuring vlan...") + + err := hyperv.SetNetworkAdapterVlanId(switchName, vlanId) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + err = hyperv.SetVirtualMachineVlanId(vmName, vlanId) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepConfigureVlan) Cleanup(state multistep.StateBag) { + //do nothing +} diff --git a/builder/hyperv/common/step_create_external_switch.go b/builder/hyperv/common/step_create_external_switch.go new file mode 100644 index 00000000000..1685d069e5c --- /dev/null +++ b/builder/hyperv/common/step_create_external_switch.go @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "code.google.com/p/go-uuid/uuid" + "github.com/mitchellh/packer/powershell/hyperv" +) + +// This step creates switch for VM. +// +// Produces: +// SwitchName string - The name of the Switch +type StepCreateExternalSwitch struct { + SwitchName string + oldSwitchName string +} + +func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + vmName := state.Get("vmName").(string) + errorMsg := "Error createing external switch: %s" + var err error + + ui.Say("Creating external switch...") + + packerExternalSwitchName := "paes_" + uuid.New() + + err = hyperv.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) + if err != nil { + err := fmt.Errorf("Error creating switch: %s", err) + state.Put(errorMsg, err) + ui.Error(err.Error()) + s.SwitchName = ""; + return multistep.ActionHalt + } + + switchName, err := hyperv.GetVirtualMachineSwitchName(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if len(switchName) == 0 { + err := fmt.Errorf(errorMsg, err) + state.Put("error", "Can't get the VM switch name") + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("External switch name is: '" + switchName + "'") + + if(switchName != packerExternalSwitchName){ + s.SwitchName = "" + } else { + s.SwitchName = packerExternalSwitchName + s.oldSwitchName = state.Get("SwitchName").(string) + } + + // Set the final name in the state bag so others can use it + state.Put("SwitchName", switchName) + + return multistep.ActionContinue +} + +func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) { + if s.SwitchName == "" { + return + } + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Unregistering and deleting external switch...") + + var err error = nil + + errMsg := "Error deleting external switch: %s" + + // connect the vm to the old switch + if s.oldSwitchName == "" { + ui.Error(fmt.Sprintf(errMsg, "the old switch name is empty")) + return + } + + err = hyperv.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, s.oldSwitchName) + if err != nil { + ui.Error(fmt.Sprintf(errMsg, err)) + return + } + + state.Put("SwitchName", s.oldSwitchName) + + err = hyperv.DeleteVirtualSwitch(s.SwitchName) + if err != nil { + ui.Error(fmt.Sprintf(errMsg, err)) + } +} diff --git a/builder/hyperv/common/step_create_switch.go b/builder/hyperv/common/step_create_switch.go new file mode 100644 index 00000000000..5620e3f9383 --- /dev/null +++ b/builder/hyperv/common/step_create_switch.go @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +const ( + SwitchTypeInternal = "Internal" + SwitchTypePrivate = "Private" + DefaultSwitchType = SwitchTypeInternal +) + +// This step creates switch for VM. +// +// Produces: +// SwitchName string - The name of the Switch +type StepCreateSwitch struct { + // Specifies the name of the switch to be created. + SwitchName string + // Specifies the type of the switch to be created. Allowed values are Internal and Private. To create an External + // virtual switch, specify either the NetAdapterInterfaceDescription or the NetAdapterName parameter, which + // implicitly set the type of the virtual switch to External. + SwitchType string + // Specifies the name of the network adapter to be bound to the switch to be created. + NetAdapterName string + // Specifies the interface description of the network adapter to be bound to the switch to be created. + NetAdapterInterfaceDescription string + + createdSwitch bool +} + +func (s *StepCreateSwitch) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + if len(s.SwitchType) == 0 { + s.SwitchType = DefaultSwitchType + } + + ui.Say(fmt.Sprintf("Creating switch '%v' if required...", s.SwitchName)) + + createdSwitch, err := hyperv.CreateVirtualSwitch(s.SwitchName, s.SwitchType) + if err != nil { + err := fmt.Errorf("Error creating switch: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + s.SwitchName = ""; + return multistep.ActionHalt + } + + s.createdSwitch = createdSwitch + + if !s.createdSwitch { + ui.Say(fmt.Sprintf(" switch '%v' already exists. Will not delete on cleanup...", s.SwitchName)) + } + + // Set the final name in the state bag so others can use it + state.Put("SwitchName", s.SwitchName) + + return multistep.ActionContinue +} + +func (s *StepCreateSwitch) Cleanup(state multistep.StateBag) { + if len(s.SwitchName) == 0 || !s.createdSwitch { + return + } + + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Unregistering and deleting switch...") + + err := hyperv.DeleteVirtualSwitch(s.SwitchName) + if err != nil { + ui.Error(fmt.Sprintf("Error deleting switch: %s", err)) + } +} diff --git a/builder/hyperv/common/step_create_tempdir.go b/builder/hyperv/common/step_create_tempdir.go new file mode 100644 index 00000000000..c4682a852ac --- /dev/null +++ b/builder/hyperv/common/step_create_tempdir.go @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "io/ioutil" + "os" +) + +type StepCreateTempDir struct { + dirPath string +} + +func (s *StepCreateTempDir) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + ui.Say("Creating temporary directory...") + + tempDir := os.TempDir() + packerTempDir, err := ioutil.TempDir(tempDir, "packerhv") + if err != nil { + err := fmt.Errorf("Error creating temporary directory: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.dirPath = packerTempDir; + state.Put("packerTempDir", packerTempDir) + +// ui.Say("packerTempDir = '" + packerTempDir + "'") + + return multistep.ActionContinue +} + +func (s *StepCreateTempDir) Cleanup(state multistep.StateBag) { + if s.dirPath == "" { + return + } + + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting temporary directory...") + + err := os.RemoveAll(s.dirPath) + + if err != nil { + ui.Error(fmt.Sprintf("Error deleting temporary directory: %s", err)) + } +} diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go new file mode 100644 index 00000000000..6a172ada880 --- /dev/null +++ b/builder/hyperv/common/step_create_vm.go @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "strconv" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +// This step creates the actual virtual machine. +// +// Produces: +// VMName string - The name of the VM +type StepCreateVM struct { + VMName string + SwitchName string + RamSizeMB uint + DiskSize uint +} + +func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Creating virtual machine...") + + path := state.Get("packerTempDir").(string) + + // convert the MB to bytes + ramBytes := int64(s.RamSizeMB * 1024 * 1024) + diskSizeBytes := int64(s.DiskSize * 1024 * 1024) + + ram := strconv.FormatInt(ramBytes, 10) + diskSize := strconv.FormatInt(diskSizeBytes, 10) + switchName := s.SwitchName + + err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName) + if err != nil { + err := fmt.Errorf("Error creating virtual machine: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Set the final name in the state bag so others can use it + state.Put("vmName", s.VMName) + + return multistep.ActionContinue +} + +func (s *StepCreateVM) Cleanup(state multistep.StateBag) { + if s.VMName == "" { + return + } + + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Unregistering and deleting virtual machine...") + + err := hyperv.DeleteVirtualMachine(s.VMName) + if err != nil { + ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err)) + } +} diff --git a/builder/hyperv/common/step_disable_vlan.go b/builder/hyperv/common/step_disable_vlan.go new file mode 100644 index 00000000000..264affbddb9 --- /dev/null +++ b/builder/hyperv/common/step_disable_vlan.go @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +type StepDisableVlan struct { +} + +func (s *StepDisableVlan) Run(state multistep.StateBag) multistep.StepAction { + //config := state.Get("config").(*config) + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error disabling vlan: %s" + vmName := state.Get("vmName").(string) + switchName := state.Get("SwitchName").(string) + + ui.Say("Disabling vlan...") + + err := hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepDisableVlan) Cleanup(state multistep.StateBag) { + //do nothing +} diff --git a/builder/hyperv/common/step_enable_integration_service.go b/builder/hyperv/common/step_enable_integration_service.go new file mode 100644 index 00000000000..89259d4d88d --- /dev/null +++ b/builder/hyperv/common/step_enable_integration_service.go @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +type StepEnableIntegrationService struct { + name string +} + +func (s *StepEnableIntegrationService) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Enabling Integration Service...") + + vmName := state.Get("vmName").(string) + s.name = "Guest Service Interface" + + err := hyperv.EnableVirtualMachineIntegrationService(vmName, s.name) + + if err != nil { + err := fmt.Errorf("Error enabling Integration Service: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepEnableIntegrationService) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_execute_online_activation.go b/builder/hyperv/common/step_execute_online_activation.go new file mode 100644 index 00000000000..b369934e152 --- /dev/null +++ b/builder/hyperv/common/step_execute_online_activation.go @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "log" +) + +type StepExecuteOnlineActivation struct { +} + +func (s *StepExecuteOnlineActivation) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + comm := state.Get("communicator").(packer.Communicator) + + errorMsg := "Error Executing Online Activation: %s" + + var remoteCmd packer.RemoteCmd + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + var err error + + ui.Say("Executing Online Activation...") + + var blockBuffer bytes.Buffer + blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }") + + remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() + + remoteCmd.Stdout = stdout + remoteCmd.Stderr = stderr + + err = comm.Start(&remoteCmd) + + stderrString := strings.TrimSpace(stderr.String()) + stdoutString := strings.TrimSpace(stdout.String()) + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + if len(stderrString) > 0 { + err = fmt.Errorf(errorMsg, stderrString) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(stdoutString) + + return multistep.ActionContinue +} + +func (s *StepExecuteOnlineActivation) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_execute_online_activation_full.go b/builder/hyperv/common/step_execute_online_activation_full.go new file mode 100644 index 00000000000..6eb91936c30 --- /dev/null +++ b/builder/hyperv/common/step_execute_online_activation_full.go @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "bytes" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "log" +) + +type StepExecuteOnlineActivationFull struct { + Pk string +} + +func (s *StepExecuteOnlineActivationFull) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + comm := state.Get("communicator").(packer.Communicator) + + errorMsg := "Error Executing Online Activation: %s" + + var remoteCmd packer.RemoteCmd + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + var err error + var stderrString string + var stdoutString string + + ui.Say("Executing Online Activation Full version...") + + var blockBuffer bytes.Buffer + blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" /ipk "+ s.Pk +" //nologo }") + + log.Printf("cmd: %s", blockBuffer.String()) + remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() + + remoteCmd.Stdout = stdout + remoteCmd.Stderr = stderr + + err = comm.Start(&remoteCmd) + + stderrString = strings.TrimSpace(stderr.String()) + stdoutString = strings.TrimSpace(stdout.String()) + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + if len(stderrString) > 0 { + err = fmt.Errorf(errorMsg, stderrString) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + +// ui.Say(stdoutString) + +/* + blockBuffer.Reset() + blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }") + + log.Printf("cmd: %s", blockBuffer.String()) + remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() + + err = comm.Start(&remoteCmd) + + stderrString = strings.TrimSpace(stderr.String()) + stdoutString = strings.TrimSpace(stdout.String()) + + log.Printf("stdout: %s", stdoutString) + log.Printf("stderr: %s", stderrString) + + if len(stderrString) > 0 { + err = fmt.Errorf(errorMsg, stderrString) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(stdoutString) +*/ + return multistep.ActionContinue +} + +func (s *StepExecuteOnlineActivationFull) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_export_vm.go b/builder/hyperv/common/step_export_vm.go new file mode 100644 index 00000000000..95076fd1448 --- /dev/null +++ b/builder/hyperv/common/step_export_vm.go @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "path/filepath" + "io/ioutil" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + +const( + vhdDir string = "Virtual Hard Disks" + vmDir string = "Virtual Machines" +) + +type StepExportVm struct { + OutputDir string +} + +func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + var err error + var errorMsg string + + vmName := state.Get("vmName").(string) + tmpPath := state.Get("packerTempDir").(string) + outputPath := s.OutputDir + + // create temp path to export vm + errorMsg = "Error creating temp export path: %s" + vmExportPath , err := ioutil.TempDir(tmpPath, "export") + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("Exporting vm...") + + err = hyperv.ExportVirtualMachine(vmName, vmExportPath) + if err != nil { + errorMsg = "Error exporting vm: %s" + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // copy to output dir + expPath := filepath.Join(vmExportPath,vmName) + + ui.Say("Coping to output dir...") + err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) + if err != nil { + errorMsg = "Error exporting vm: %s" + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepExportVm) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go new file mode 100644 index 00000000000..d5e9b4ef35b --- /dev/null +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +type StepMountDvdDrive struct { + RawSingleISOUrl string + path string +} + +func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error mounting dvd drive: %s" + vmName := state.Get("vmName").(string) + isoPath := s.RawSingleISOUrl + + ui.Say("Mounting dvd drive...") + + err := hyperv.MountDvdDrive(vmName, isoPath) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.path = isoPath + + return multistep.ActionContinue +} + +func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { + if s.path == "" { + return + } + + errorMsg := "Error unmounting dvd drive: %s" + + vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Unmounting dvd drive...") + + err := hyperv.UnmountDvdDrive(vmName) + if err != nil { + ui.Error(fmt.Sprintf(errorMsg, err)) + } +} diff --git a/builder/hyperv/common/step_mount_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go new file mode 100644 index 00000000000..c3005414151 --- /dev/null +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "os" + "strings" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" + "log" + "io" + "io/ioutil" + "path/filepath" +) + + +const( + FloppyFileName = "assets.vfd" +) + + + + +type StepSetUnattendedProductKey struct { + Files []string + ProductKey string +} + +func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if s.ProductKey == "" { + ui.Say("No product key specified...") + return multistep.ActionContinue + } + + index := -1 + for i, value := range s.Files { + if s.caseInsensitiveContains(value, "Autounattend.xml") { + index = i + break + } + } + + ui.Say("Setting product key in Autounattend.xml...") + copyOfAutounattend, err := s.copyAutounattend(s.Files[index]) + if err != nil { + state.Put("error", fmt.Errorf("Error copying Autounattend.xml: %s", err)) + return multistep.ActionHalt + } + + powershell.SetUnattendedProductKey(copyOfAutounattend, s.ProductKey) + s.Files[index] = copyOfAutounattend + return multistep.ActionContinue +} + + +func (s *StepSetUnattendedProductKey) caseInsensitiveContains(str, substr string) bool { + str, substr = strings.ToUpper(str), strings.ToUpper(substr) + return strings.Contains(str, substr) +} + +func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, error) { + tempdir, err := ioutil.TempDir("", "packer") + if err != nil { + return "", err + } + + autounattend := filepath.Join(tempdir, "Autounattend.xml") + f, err := os.Create(autounattend) + if err != nil { + return "", err + } + defer f.Close() + + sourceF, err := os.Open(path) + if err != nil { + return "", err + } + defer sourceF.Close() + + log.Printf("Copying %s to temp location: %s", path, autounattend) + if _, err := io.Copy(f, sourceF); err != nil { + return "", err + } + + return autounattend, nil +} + + +func (s *StepSetUnattendedProductKey) Cleanup(state multistep.StateBag) { +} + + + +type StepMountFloppydrive struct { + floppyPath string +} + +func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepAction { + // Determine if we even have a floppy disk to attach + var floppyPath string + if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { + floppyPath = floppyPathRaw.(string) + } else { + log.Println("No floppy disk, not attaching.") + return multistep.ActionContinue + } + + // Hyper-V is really dumb and can't figure out the format of the file + // without an extension, so we need to add the "vfd" extension to the + // floppy. + floppyPath, err := s.copyFloppy(floppyPath) + if err != nil { + state.Put("error", fmt.Errorf("Error preparing floppy: %s", err)) + return multistep.ActionHalt + } + + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Mounting floppy drive...") + + err = hyperv.MountFloppyDrive(vmName, floppyPath) + if err != nil { + state.Put("error", fmt.Errorf("Error mounting floppy drive: %s", err)) + return multistep.ActionHalt + } + + // Track the path so that we can unregister it from Hyper-V later + s.floppyPath = floppyPath + + return multistep.ActionContinue} + +func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { + if s.floppyPath == "" { + return + } + + errorMsg := "Error unmounting floppy drive: %s" + + vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Unmounting floppy drive (cleanup)...") + + err := hyperv.UnmountFloppyDrive(vmName) + if err != nil { + ui.Error(fmt.Sprintf(errorMsg, err)) + } + + err = os.Remove(s.floppyPath) + + if err != nil { + ui.Error(fmt.Sprintf(errorMsg, err)) + } +} + +func (s *StepMountFloppydrive) copyFloppy(path string) (string, error) { + tempdir, err := ioutil.TempDir("", "packer") + if err != nil { + return "", err + } + + floppyPath := filepath.Join(tempdir, "floppy.vfd") + f, err := os.Create(floppyPath) + if err != nil { + return "", err + } + defer f.Close() + + sourceF, err := os.Open(path) + if err != nil { + return "", err + } + defer sourceF.Close() + + log.Printf("Copying floppy to temp location: %s", floppyPath) + if _, err := io.Copy(f, sourceF); err != nil { + return "", err + } + + return floppyPath, nil +} diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go new file mode 100644 index 00000000000..05eaf6dc895 --- /dev/null +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "log" + "os" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + powershell "github.com/mitchellh/packer/powershell" +) + +type StepMountSecondaryDvdImages struct { + Files [] string + dvdProperties []DvdControllerProperties +} + +type DvdControllerProperties struct { + ControllerNumber string + ControllerLocation string +} + +func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Mounting secondary DVD images...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + dvdProperties, err := s.mountFiles(vmName); + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("Saving DVD properties %s DVDs", len(dvdProperties))) + + state.Put("secondary.dvd.properties", dvdProperties) + + return multistep.ActionContinue +} + +func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { + +} + + +func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdControllerProperties, error) { + + var dvdProperties []DvdControllerProperties + + properties, err := s.addAndMountIntegrationServicesSetupDisk(vmName) + if err != nil { + return dvdProperties, err + } + + dvdProperties = append(dvdProperties, properties) + + for _, value := range s.Files { + properties, err := s.addAndMountDvdDisk(vmName, value) + if err != nil { + return dvdProperties, err + } + + dvdProperties = append(dvdProperties, properties) + } + + return dvdProperties, nil +} + + +func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vmName string) (DvdControllerProperties, error) { + + isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + properties, err := s.addAndMountDvdDisk(vmName, isoPath) + if err != nil { + return properties, err + } + + return properties, nil +} + + + + +func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) { + + var properties DvdControllerProperties + var script powershell.ScriptBuilder + powershell := new(powershell.PowerShellCmd) + + // get the controller number that the OS install disk is mounted on + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber") + controllerNumber, err := powershell.Output(script.String(), vmName) + if err != nil { + return properties, err + } + + script.Reset() + script.WriteLine("param([string]$vmName,[int]$controllerNumber)") + script.WriteLine("Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber") + err = powershell.Run(script.String(), vmName, controllerNumber) + if err != nil { + return properties, err + } + + // we could try to get the controller location and number in one call, but this way we do not + // need to parse the output + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation") + controllerLocation, err := powershell.Output(script.String(), vmName) + if err != nil { + return properties, err + } + + script.Reset() + script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)") + script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") + + err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + return properties, err + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v",isoPath, controllerNumber, controllerLocation)) + + properties.ControllerNumber = controllerNumber + properties.ControllerLocation = controllerLocation + + return properties, nil +} diff --git a/builder/hyperv/common/step_output_dir.go b/builder/hyperv/common/step_output_dir.go new file mode 100644 index 00000000000..602ce265496 --- /dev/null +++ b/builder/hyperv/common/step_output_dir.go @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "log" + "os" + "path/filepath" + "time" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +// StepOutputDir sets up the output directory by creating it if it does +// not exist, deleting it if it does exist and we're forcing, and cleaning +// it up when we're done with it. +type StepOutputDir struct { + Force bool + Path string +} + +func (s *StepOutputDir) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if _, err := os.Stat(s.Path); err == nil && s.Force { + ui.Say("Deleting previous output directory...") + os.RemoveAll(s.Path) + } + + // Create the directory + if err := os.MkdirAll(s.Path, 0755); err != nil { + state.Put("error", err) + return multistep.ActionHalt + } + + // Make sure we can write in the directory + f, err := os.Create(filepath.Join(s.Path, "_packer_perm_check")) + if err != nil { + err = fmt.Errorf("Couldn't write to output directory: %s", err) + state.Put("error", err) + return multistep.ActionHalt + } + f.Close() + os.Remove(f.Name()) + + return multistep.ActionContinue +} + +func (s *StepOutputDir) Cleanup(state multistep.StateBag) { + _, cancelled := state.GetOk(multistep.StateCancelled) + _, halted := state.GetOk(multistep.StateHalted) + + if cancelled || halted { + ui := state.Get("ui").(packer.Ui) + + ui.Say("Deleting output directory...") + for i := 0; i < 5; i++ { + err := os.RemoveAll(s.Path) + if err == nil { + break + } + + log.Printf("Error removing output dir: %s", err) + time.Sleep(2 * time.Second) + } + } +} diff --git a/builder/hyperv/common/step_polling_installation.go b/builder/hyperv/common/step_polling_installation.go new file mode 100644 index 00000000000..c462f3da9cf --- /dev/null +++ b/builder/hyperv/common/step_polling_installation.go @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +// "net" + "log" + "os/exec" + "strings" + "bytes" +) + +const port string = "13000" + +type StepPollingInstalation struct { + step int +} + +func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error polling VM: %s" + vmIp := state.Get("ip").(string) + + ui.Say("Start polling VM to check the installation is complete...") +/* + count := 30 + var minutes time.Duration = 1 + sleepMin := time.Minute * minutes + host := vmIp + ":" + port + + timeoutSec := time.Second * 15 + + for count > 0 { + ui.Say(fmt.Sprintf("Connecting vm (%s)...", host )) + conn, err := net.DialTimeout("tcp", host, timeoutSec) + if err == nil { + ui.Say("Done!") + conn.Close() + break; + } + + log.Println(err) + ui.Say(fmt.Sprintf("Waiting more %v minutes...", uint(minutes))) + time.Sleep(sleepMin) + count-- + } + + if count == 0 { + err := fmt.Errorf(errorMsg, "a signal from vm was not received in a given time period ") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } +*/ + host := "'" + vmIp + "'," + port + + var blockBuffer bytes.Buffer + blockBuffer.WriteString("Invoke-Command -scriptblock {function foo(){try{$client=New-Object System.Net.Sockets.TcpClient(") + blockBuffer.WriteString(host) + blockBuffer.WriteString(") -ErrorAction SilentlyContinue;if($client -eq $null){return $false}}catch{return $false}return $true} foo}") + + count := 60 + var duration time.Duration = 20 + sleepTime := time.Second * duration + + var res string + + for count > 0 { + log.Println(fmt.Sprintf("Connecting vm (%s)...", host )) + cmd := exec.Command("powershell", blockBuffer.String()) + cmdOut, err := cmd.Output() + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + res = strings.TrimSpace(string(cmdOut)) + + if res != "False" { + ui.Say("Signal was received from the VM") + // Sleep before starting provision + time.Sleep(time.Second*30) + break; + } + + log.Println(fmt.Sprintf("Slipping for more %v seconds...", uint(duration))) + time.Sleep(sleepTime) + count-- + } + + if count == 0 { + err := fmt.Errorf(errorMsg, "a signal from vm was not received in a given time period ") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("The installation complete") + + return multistep.ActionContinue +} + +func (s *StepPollingInstalation) Cleanup(state multistep.StateBag) { + +} diff --git a/builder/hyperv/common/step_reboot_vm.go b/builder/hyperv/common/step_reboot_vm.go new file mode 100644 index 00000000000..3053a085597 --- /dev/null +++ b/builder/hyperv/common/step_reboot_vm.go @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" + "github.com/mitchellh/packer/powershell/hyperv" +) + +type StepRebootVm struct { +} + +func (s *StepRebootVm) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error rebooting vm: %s" + vmName := state.Get("vmName").(string) + + ui.Say("Rebooting vm...") + + err := hyperv.RestartVirtualMachine(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("Waiting the VM to complete rebooting (2 minutes)...") + + sleepTime := time.Minute * 2 + time.Sleep(sleepTime) + + return multistep.ActionContinue +} + +func (s *StepRebootVm) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go new file mode 100644 index 00000000000..db80e7c5681 --- /dev/null +++ b/builder/hyperv/common/step_shutdown.go @@ -0,0 +1,141 @@ +package common + +import ( + "bytes" + "errors" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "time" +) + +type ShutdownConfig struct { + ShutdownCommand string `mapstructure:"shutdown_command"` + RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + + ShutdownTimeout time.Duration `` +} + +func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error { + if c.RawShutdownTimeout == "" { + c.RawShutdownTimeout = "5m" + } + + templates := map[string]*string{ + "shutdown_command": &c.ShutdownCommand, + "shutdown_timeout": &c.RawShutdownTimeout, + } + + errs := make([]error, 0) + for n, ptr := range templates { + var err error + *ptr, err = t.Process(*ptr, nil) + if err != nil { + errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + var err error + c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) + } + + return errs +} + +// This step shuts down the machine. It first attempts to do so gracefully, +// but ultimately forcefully shuts it down if that fails. +// +// Uses: +// communicator packer.Communicator +// dir OutputDir +// driver Driver +// ui packer.Ui +// vmx_path string +// +// Produces: +// +type StepShutdown struct { + Command string + Timeout time.Duration +} + +func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { + + comm := state.Get("communicator").(packer.Communicator) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + if s.Command != "" { + ui.Say("Gracefully halting virtual machine...") + log.Printf("Executing shutdown command: %s", s.Command) + + var stdout, stderr bytes.Buffer + cmd := &packer.RemoteCmd{ + Command: s.Command, + Stdout: &stdout, + Stderr: &stderr, + } + if err := comm.Start(cmd); err != nil { + err := fmt.Errorf("Failed to send shutdown command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // Wait for the command to run + cmd.Wait() + + // If the command failed to run, notify the user in some way. + if cmd.ExitStatus != 0 { + state.Put("error", fmt.Errorf( + "Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s", + stdout.String(), stderr.String())) + return multistep.ActionHalt + } + + if stdout.Len() > 0 { + log.Printf("Shutdown stdout: %s", stdout.String()) + } + + if stderr.Len() > 0 { + log.Printf("Shutdown stderr: %s", stderr.String()) + } + + // Wait for the machine to actually shut down + log.Printf("Waiting max %s for shutdown to complete", s.Timeout) + shutdownTimer := time.After(s.Timeout) + for { + running, _ := driver.IsRunning(vmName) + if !running { + break + } + + select { + case <-shutdownTimer: + err := errors.New("Timeout while waiting for machine to shut down.") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + default: + time.Sleep(150 * time.Millisecond) + } + } + } else { + ui.Say("Forcibly halting virtual machine...") + if err := driver.Stop(vmName); err != nil { + err := fmt.Errorf("Error stopping VM: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + log.Println("VM shut down.") + return multistep.ActionContinue +} + +func (s *StepShutdown) Cleanup(state multistep.StateBag) {} diff --git a/builder/hyperv/common/step_sleep.go b/builder/hyperv/common/step_sleep.go new file mode 100644 index 00000000000..2d0f0053a0f --- /dev/null +++ b/builder/hyperv/common/step_sleep.go @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +) + +type StepSleep struct { + Minutes time.Duration + ActionName string +} + +func (s *StepSleep) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if(len(s.ActionName)>0){ + ui.Say(s.ActionName + "! Waiting for "+ fmt.Sprintf("%v",uint(s.Minutes)) + " minutes to let the action to complete...") + } + time.Sleep(time.Minute*s.Minutes); + + return multistep.ActionContinue +} + +func (s *StepSleep) Cleanup(state multistep.StateBag) { + +} diff --git a/builder/hyperv/common/step_start_vm.go b/builder/hyperv/common/step_start_vm.go new file mode 100644 index 00000000000..af1fcda5d21 --- /dev/null +++ b/builder/hyperv/common/step_start_vm.go @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" + "github.com/mitchellh/packer/powershell/hyperv" +) + +type StepStartVm struct { + Reason string + StartUpDelay int +} + +func (s *StepStartVm) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error starting vm: %s" + vmName := state.Get("vmName").(string) + + ui.Say("Starting vm for " + s.Reason + "...") + + err := hyperv.StartVirtualMachine(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if s.StartUpDelay != 0 { + //sleepTime := s.StartUpDelay * time.Second + sleepTime := 60 * time.Second + + ui.Say(fmt.Sprintf(" Waiting %v for vm to start...", sleepTime)) + time.Sleep(sleepTime); + } + + return multistep.ActionContinue +} + +func (s *StepStartVm) Cleanup(state multistep.StateBag) { +} diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go new file mode 100644 index 00000000000..57ffb422d9e --- /dev/null +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +type StepUnmountDvdDrive struct { +} + +func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + vmName := state.Get("vmName").(string) + + ui.Say("Unmounting dvd drive...") + + err := hyperv.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (s *StepUnmountDvdDrive) Cleanup(state multistep.StateBag) { +} diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go new file mode 100644 index 00000000000..ae7813400b5 --- /dev/null +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/powershell/hyperv" +) + + +type StepUnmountFloppyDrive struct { +} + +func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAction { + //driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + errorMsg := "Error Unmounting floppy drive: %s" + vmName := state.Get("vmName").(string) + + ui.Say("Unmounting floppy drive (Run)...") + + err := hyperv.UnmountFloppyDrive(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + } + + return multistep.ActionContinue +} + +func (s *StepUnmountFloppyDrive) Cleanup(state multistep.StateBag) { + // do nothing +} diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go new file mode 100644 index 00000000000..7c40389fe0f --- /dev/null +++ b/builder/hyperv/common/step_unmount_integration_services.go @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "log" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + powershell "github.com/mitchellh/packer/powershell" +) + +type StepUnmountSecondaryDvdImages struct { +} + +func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + ui.Say("Unmounting Integration Services Setup Disk...") + + vmName := state.Get("vmName").(string) + + // todo: should this message say removing the dvd? + + dvdProperties := state.Get("secondary.dvd.properties").([]DvdControllerProperties) + + log.Println(fmt.Sprintf("Found DVD properties %s", len(dvdProperties))) + + for _, dvdProperty := range dvdProperties { + controllerNumber := dvdProperty.ControllerNumber + controllerLocation := dvdProperty.ControllerLocation + + var script powershell.ScriptBuilder + powershell := new(powershell.PowerShellCmd) + + script.WriteLine("param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)") + script.WriteLine("Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") + err := powershell.Run(script.String(), vmName, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + + return multistep.ActionContinue +} + +func (s *StepUnmountSecondaryDvdImages) Cleanup(state multistep.StateBag) { +} diff --git a/builder/hyperv/common/step_upgrade_integration_services.go b/builder/hyperv/common/step_upgrade_integration_services.go new file mode 100644 index 00000000000..ab076e7edf2 --- /dev/null +++ b/builder/hyperv/common/step_upgrade_integration_services.go @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + //"fmt" + "os" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + powershell "github.com/mitchellh/packer/powershell" +) + +type StepUpdateIntegrationServices struct { + Username string + Password string + + newDvdDriveProperties dvdDriveProperties +} + +type dvdDriveProperties struct { + ControllerNumber string + ControllerLocation string +} + +func (s *StepUpdateIntegrationServices) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Mounting Integration Services Setup Disk...") + + _, err := s.mountIntegrationServicesSetupDisk(vmName); + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + // dvdDriveLetter, err := s.getDvdDriveLetter(vmName) + // if err != nil { + // state.Put("error", err) + // ui.Error(err.Error()) + // return multistep.ActionHalt + // } + + // setup := dvdDriveLetter + ":\\support\\"+osArchitecture+"\\setup.exe /quiet /norestart" + + // ui.Say("Run: " + setup) + + return multistep.ActionContinue +} + +func (s *StepUpdateIntegrationServices) Cleanup(state multistep.StateBag) { + vmName := state.Get("vmName").(string) + + var script powershell.ScriptBuilder + script.WriteLine("param([string]$vmName)") + script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $null") + + powershell := new(powershell.PowerShellCmd) + _ = powershell.Run(script.String(), vmName) +} + +func (s *StepUpdateIntegrationServices) mountIntegrationServicesSetupDisk(vmName string) (dvdDriveProperties, error) { + + var dvdProperties dvdDriveProperties + + var script powershell.ScriptBuilder + script.WriteLine("param([string]$vmName)") + script.WriteLine("Add-VMDvdDrive -VMName $vmName") + + powershell := new(powershell.PowerShellCmd) + err := powershell.Run(script.String(), vmName) + if err != nil { + return dvdProperties, err + } + + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation") + controllerLocation, err := powershell.Output(script.String(), vmName) + if err != nil { + return dvdProperties, err + } + + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerNumber") + controllerNumber, err := powershell.Output(script.String(), vmName) + if err != nil { + return dvdProperties, err + } + + isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + + script.Reset() + script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)") + script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") + + err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + return dvdProperties, err + } + + dvdProperties.ControllerNumber = controllerNumber + dvdProperties.ControllerLocation = controllerLocation + + return dvdProperties, err +} diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go new file mode 100644 index 00000000000..22da371c819 --- /dev/null +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "strings" + "strconv" + "time" + powershell "github.com/mitchellh/packer/powershell" +) + +const ( + SleepSeconds = 10 +) + +type StepWaitForPowerOff struct { +} + +func (s *StepWaitForPowerOff) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + ui.Say("Waiting for vm to be powered down...") + + // unless the person has a super fast disk, it should take at least 5 minutes + // for the install and post-install operations to take. Wait 5 minutes to + // avoid hammering on getting VM status via PowerShell + time.Sleep(time.Second * 300); + + var script powershell.ScriptBuilder + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VM -Name $vmName).State -eq [Microsoft.HyperV.PowerShell.VMState]::Off") + isOffScript := script.String() + + for { + powershell := new(powershell.PowerShellCmd) + cmdOut, err := powershell.Output(isOffScript, vmName); + if err != nil { + err := fmt.Errorf("Error checking VM's state: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if cmdOut == "True" { + break + } else { + time.Sleep(time.Second * SleepSeconds); + } + } + + return multistep.ActionContinue +} + +func (s *StepWaitForPowerOff) Cleanup(state multistep.StateBag) { +} + +type StepWaitForInstallToComplete struct { + ExpectedRebootCount uint + ActionName string +} + +func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + if(len(s.ActionName)>0){ + ui.Say(fmt.Sprintf("%v ! Waiting for VM to reboot %v times...",s.ActionName, s.ExpectedRebootCount)) + } + + var rebootCount uint + var lastUptime uint64 + + var script powershell.ScriptBuilder + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VM -Name $vmName).Uptime.TotalSeconds") + + uptimeScript := script.String() + + for rebootCount < s.ExpectedRebootCount { + powershell := new(powershell.PowerShellCmd) + cmdOut, err := powershell.Output(uptimeScript, vmName); + if err != nil { + err := fmt.Errorf("Error checking uptime: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + uptime, _ := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 64) + if uint64(uptime) < lastUptime { + rebootCount++ + ui.Say(fmt.Sprintf("%v -> Detected reboot %v after %v seconds...", s.ActionName, rebootCount, lastUptime)) + } + + lastUptime = uptime + + if (rebootCount < s.ExpectedRebootCount) { + time.Sleep(time.Second * SleepSeconds); + } + } + + + return multistep.ActionContinue +} + +func (s *StepWaitForInstallToComplete) Cleanup(state multistep.StateBag) { + +} + + +type StepWaitForWinRm struct { +} + +func (s *StepWaitForWinRm) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + //vmName := state.Get("vmName").(string) + + ui.Say("Waiting for WinRM to be ready...") + + return multistep.ActionContinue +} + +func (s *StepWaitForWinRm) Cleanup(state multistep.StateBag) { + +} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go new file mode 100644 index 00000000000..60bd5f77f61 --- /dev/null +++ b/builder/hyperv/iso/builder.go @@ -0,0 +1,391 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package iso + +import ( + "code.google.com/p/go-uuid/uuid" + "errors" + "fmt" + "github.com/mitchellh/multistep" + hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" + "github.com/mitchellh/packer/common" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/helper/communicator" + powershell "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" + "log" + "os" + "regexp" + "strings" + "time" +) + +const ( + DefaultDiskSize = 127 * 1024 // 127GB + MinDiskSize = 10 * 1024 // 10GB + MaxDiskSize = 65536 * 1024 // 64TB + + DefaultRamSize = 1024 // 1GB + MinRamSize = 512 // 512MB + MaxRamSize = 32768 // 32GB + + LowRam = 512 // 512MB + + DefaultUsername = "vagrant" + DefaultPassword = "vagrant" +) + +// Builder implements packer.Builder and builds the actual Hyperv +// images. +type Builder struct { + config config + runner multistep.Runner +} + +type config struct { + // The size, in megabytes, of the hard disk to create for the VM. + // By default, this is 130048 (about 127 GB). + DiskSize uint `mapstructure:"disk_size"` + // The size, in megabytes, of the computer memory in the VM. + // By default, this is 1024 (about 1 GB). + RamSizeMB uint `mapstructure:"ram_size_mb"` + // A list of files to place onto a floppy disk that is attached when the + // VM is booted. This is most useful for unattended Windows installs, + // which look for an Autounattend.xml file on removable media. By default, + // no floppy will be attached. All files listed in this setting get + // placed into the root directory of the floppy and the floppy is attached + // as the first floppy device. Currently, no support exists for creating + // sub-directories on the floppy. Wildcard characters (*, ?, and []) + // are allowed. Directory names are also allowed, which will add all + // the files found in the directory to the floppy. + FloppyFiles []string `mapstructure:"floppy_files"` + // + SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` + // The checksum for the OS ISO file. Because ISO files are so large, + // this is required and Packer will verify it prior to booting a virtual + // machine with the ISO attached. The type of the checksum is specified + // with iso_checksum_type, documented below. + ISOChecksum string `mapstructure:"iso_checksum"` + // The type of the checksum specified in iso_checksum. Valid values are + // "none", "md5", "sha1", "sha256", or "sha512" currently. While "none" + // will skip checksumming, this is not recommended since ISO files are + // generally large and corruption does happen from time to time. + ISOChecksumType string `mapstructure:"iso_checksum_type"` + // A URL to the ISO containing the installation image. This URL can be + // either an HTTP URL or a file URL (or path to a file). If this is an + // HTTP URL, Packer will download it and cache it between runs. + RawSingleISOUrl string `mapstructure:"iso_url"` + // Multiple URLs for the ISO to download. Packer will try these in order. + // If anything goes wrong attempting to download or while downloading a + // single URL, it will move on to the next. All URLs must point to the + // same file (same checksum). By default this is empty and iso_url is + // used. Only one of iso_url or iso_urls can be specified. + ISOUrls []string `mapstructure:"iso_urls"` + // This is the name of the new virtual machine. + // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. + VMName string `mapstructure:"vm_name"` + + common.PackerConfig `mapstructure:",squash"` + hypervcommon.OutputConfig `mapstructure:",squash"` + hypervcommon.SSHConfig `mapstructure:",squash"` + hypervcommon.ShutdownConfig `mapstructure:",squash"` + + SwitchName string `mapstructure:"switch_name"` + + Communicator string `mapstructure:"communicator"` + + // The time in seconds to wait for the virtual machine to report an IP address. + // This defaults to 120 seconds. This may have to be increased if your VM takes longer to boot. + IPAddressTimeout time.Duration `mapstructure:"ip_address_timeout"` + + SSHWaitTimeout time.Duration + + tpl *packer.ConfigTemplate +} + +// Prepare processes the build configuration parameters. +func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { + + md, err := common.DecodeConfig(&b.config, raws...) + if err != nil { + return nil, err + } + + b.config.tpl, err = packer.NewConfigTemplate() + if err != nil { + return nil, err + } + + log.Println(fmt.Sprintf("%s: %v", "PackerUserVars", b.config.PackerUserVars)) + + b.config.tpl.UserVars = b.config.PackerUserVars + + // Accumulate any errors and warnings + errs := common.CheckUnusedConfig(md) + errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) + + warnings := make([]string, 0) + + err = b.checkDiskSize() + if err != nil { + errs = packer.MultiErrorAppend(errs, err) + } + + err = b.checkRamSize() + if err != nil { + errs = packer.MultiErrorAppend(errs, err) + } + + if b.config.VMName == "" { + b.config.VMName = fmt.Sprintf("pvm_%s", uuid.New()) + } + + if b.config.SwitchName == "" { + // no switch name, try to get one attached to a online network adapter + onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() + if onlineSwitchName == "" || err != nil { + b.config.SwitchName = fmt.Sprintf("pis_%s", uuid.New()) + } else { + b.config.SwitchName = onlineSwitchName + } + } + + log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) + + if b.config.Communicator == "" { + b.config.Communicator = "ssh" + } else if b.config.Communicator == "ssh" || b.config.Communicator == "winrm" { + // good + } else { + err = errors.New("communicator must be either ssh or winrm") + errs = packer.MultiErrorAppend(errs, err) + } + + // Errors + templates := map[string]*string{ + "iso_url": &b.config.RawSingleISOUrl + } + + for n, ptr := range templates { + var err error + *ptr, err = b.config.tpl.Process(*ptr, nil) + if err != nil { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err)) + } + } + + log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) + log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) + log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) + + if b.config.RawSingleISOUrl == "" { + errs = packer.MultiErrorAppend(errs, errors.New("iso_url: The option can't be missed and a path must be specified.")) + } else if _, err := os.Stat(b.config.RawSingleISOUrl); err != nil { + errs = packer.MultiErrorAppend(errs, errors.New("iso_url: Check the path is correct")) + } + + log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) + + b.config.SSHWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout) + + // Warnings + warning := b.checkHostAvailableMemory() + if warning != "" { + warnings = appendWarnings(warnings, warning) + } + + if b.config.ShutdownCommand == "" { + warnings = append(warnings, + "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ + "will forcibly halt the virtual machine, which may result in data loss.") + } + + if errs != nil && len(errs.Errors) > 0 { + return warnings, errs + } + + return warnings, nil +} + +// Run executes a Packer build and returns a packer.Artifact representing +// a Hyperv appliance. +func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packer.Artifact, error) { + // Create the driver that we'll use to communicate with Hyperv + driver, err := hypervcommon.NewHypervPS4Driver() + if err != nil { + return nil, fmt.Errorf("Failed creating Hyper-V driver: %s", err) + } + + // Set up the state. + state := new(multistep.BasicStateBag) + state.Put("config", &b.config) + state.Put("driver", driver) + state.Put("hook", hook) + state.Put("ui", ui) + + steps := []multistep.Step{ + &hypervcommon.StepCreateTempDir{}, + &hypervcommon.StepOutputDir{ + Force: b.config.PackerForce, + Path: b.config.OutputDir, + }, + &common.StepCreateFloppy{ + Files: b.config.FloppyFiles, + }, + &hypervcommon.StepCreateSwitch{ + SwitchName: b.config.SwitchName, + }, + &hypervcommon.StepCreateVM{ + VMName: b.config.VMName, + SwitchName: b.config.SwitchName, + RamSizeMB: b.config.RamSizeMB, + DiskSize: b.config.DiskSize, + }, + &hypervcommon.StepEnableIntegrationService{}, + + &hypervcommon.StepMountDvdDrive{ + RawSingleISOUrl: b.config.RawSingleISOUrl, + }, + &hypervcommon.StepMountFloppydrive{}, + + &hypervcommon.StepMountSecondaryDvdImages{}, + + + &hypervcommon.StepStartVm{ + Reason: "OS installation", + }, + + // wait for the vm to be powered off + &hypervcommon.StepWaitForPowerOff{}, + + // remove the integration services dvd drive + // after we power down + &hypervcommon.StepUnmountSecondaryDvdImages{}, + + // + &hypervcommon.StepStartVm{ + Reason: "provisioning", + StartUpDelay: 60, + }, + + // configure the communicator ssh, winrm + &communicator.StepConnect{ + Config: &b.config.SSHConfig.Comm, + Host: hypervcommon.CommHost, + SSHConfig: hypervcommon.SSHConfigFunc(b.config.SSHConfig), + SSHPort: hypervcommon.SSHPort, + }, + + // provision requires communicator to be setup + &common.StepProvision{}, + + &hypervcommon.StepUnmountFloppyDrive{}, + &hypervcommon.StepUnmountDvdDrive{}, + + &hypervcommon.StepShutdown{ + Command: b.config.ShutdownCommand, + Timeout: b.config.ShutdownTimeout, + }, + + &hypervcommon.StepExportVm{ + OutputDir: b.config.OutputDir, + }, + + // the clean up actions for each step will be executed reverse order + } + + // Run the steps. + if b.config.PackerDebug { + b.runner = &multistep.DebugRunner{ + Steps: steps, + PauseFn: common.MultistepDebugFn(ui), + } + } else { + b.runner = &multistep.BasicRunner{Steps: steps} + } + b.runner.Run(state) + + // Report any errors. + if rawErr, ok := state.GetOk("error"); ok { + return nil, rawErr.(error) + } + + // If we were interrupted or cancelled, then just exit. + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return nil, errors.New("Build was cancelled.") + } + + if _, ok := state.GetOk(multistep.StateHalted); ok { + return nil, errors.New("Build was halted.") + } + + return hypervcommon.NewArtifact(b.config.OutputDir) +} + +// Cancel. +func (b *Builder) Cancel() { + if b.runner != nil { + log.Println("Cancelling the step runner...") + b.runner.Cancel() + } +} + +func appendWarnings(slice []string, data ...string) []string { + m := len(slice) + n := m + len(data) + if n > cap(slice) { // if necessary, reallocate + // allocate double what's needed, for future growth. + newSlice := make([]string, (n+1)*2) + copy(newSlice, slice) + slice = newSlice + } + slice = slice[0:n] + copy(slice[m:n], data) + return slice +} + +func (b *Builder) checkDiskSize() error { + if b.config.DiskSize == 0 { + b.config.DiskSize = DefaultDiskSize + } + + log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize)) + + if b.config.DiskSize < MinDiskSize { + return fmt.Errorf("disk_size_gb: Windows server requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) + } else if b.config.DiskSize > MaxDiskSize { + return fmt.Errorf("disk_size_gb: Windows server requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) + } + + return nil +} + +func (b *Builder) checkRamSize() error { + if b.config.RamSizeMB == 0 { + b.config.RamSizeMB = DefaultRamSize + } + + log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSizeMB)) + + if b.config.RamSizeMB < MinRamSize { + return fmt.Errorf("ram_size_mb: Windows server requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSizeMB) + } else if b.config.RamSizeMB > MaxRamSize { + return fmt.Errorf("ram_size_mb: Windows server requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSizeMB) + } + + return nil +} + +func (b *Builder) checkHostAvailableMemory() string { + freeMB := powershell.GetHostAvailableMemory() + + if (freeMB - float64(b.config.RamSizeMB)) < LowRam { + return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") + } + + return "" +} \ No newline at end of file diff --git a/plugin/packer-builder-hyperv-iso/build-and-deploy.sh b/plugin/packer-builder-hyperv-iso/build-and-deploy.sh new file mode 100644 index 00000000000..8e0185a4edd --- /dev/null +++ b/plugin/packer-builder-hyperv-iso/build-and-deploy.sh @@ -0,0 +1,3 @@ +go build +cp packer-builder-hyperv-iso.exe ../../../bin/ + diff --git a/plugin/packer-builder-hyperv-iso/main.go b/plugin/packer-builder-hyperv-iso/main.go new file mode 100644 index 00000000000..c7be7a1ed09 --- /dev/null +++ b/plugin/packer-builder-hyperv-iso/main.go @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package main + +import ( + "github.com/mitchellh/packer/builder/hyperv/iso" + "github.com/mitchellh/packer/plugin" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(iso.Builder)) + server.Serve() +} diff --git a/plugin/packer-builder-hyperv-iso/main_test.go b/plugin/packer-builder-hyperv-iso/main_test.go new file mode 100644 index 00000000000..b07e1808032 --- /dev/null +++ b/plugin/packer-builder-hyperv-iso/main_test.go @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package main diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go new file mode 100644 index 00000000000..afddc928b3d --- /dev/null +++ b/powershell/hyperv/hyperv.go @@ -0,0 +1,433 @@ +package hyperv + +import ( + "github.com/mitchellh/packer/powershell" + "strings" +) + +func GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) { + + var script = ` +param([string]$vmName, [int]$addressIndex) +try { + $adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue + $ip = $adapter.IPAddresses[$addressIndex] + if($ip -eq $null) { + return $false + } +} catch { + return $false +} +$ip +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName, "0") + + return cmdOut, err +} + +func MountDvdDrive(vmName string, path string) error { + + var script = ` +param([string]$vmName,[string]$path) +Set-VMDvdDrive -VMName $vmName -Path $path +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path) + return err +} + +func UnmountDvdDrive(vmName string) error { + + var script = ` +param([string]$vmName) +Set-VMDvdDrive -VMName $vmName -Path $null +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func MountFloppyDrive(vmName string, path string) error { + var script = ` +param([string]$vmName, [string]$path) +Set-VMFloppyDiskDrive -VMName $vmName -Path $path +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path) + return err +} + +func UnmountFloppyDrive(vmName string) error { + + var script = ` +param([string]$vmName) +Set-VMFloppyDiskDrive -VMName $vmName -Path $null +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string) error { + + var script = ` +param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName) +$vhdx = $vmName + '.vhdx' +$vhdPath = Join-Path -Path $path -ChildPath $vhdx +New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path, ram, diskSize, switchName) + return err +} + +func DeleteVirtualMachine(vmName string) error { + + var script = ` +param([string]$vmName) +Remove-VM -Name $vmName -Force +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func ExportVirtualMachine(vmName string, path string) error { + + var script = ` +param([string]$vmName, [string]$path) +Export-VM -Name $vmName -Path $path +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path) + return err +} + +func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error { + + var script = ` +param([string]$srcPath, [string]$dstPath, [string]$vhdDirName, [string]$vmDir) +Copy-Item -Path $srcPath/$vhdDirName -Destination $dstPath -recurse +Copy-Item -Path $srcPath/$vmDir -Destination $dstPath +Copy-Item -Path $srcPath/$vmDir/*.xml -Destination $dstPath/$vmDir +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, expPath, outputPath, vhdDir, vmDir) + return err +} + +func CreateVirtualSwitch(switchName string, switchType string) (bool, error) { + + var script = ` +param([string]$switchName,[string]$switchType) +$switches = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue +if ($switches.Count -eq 0) { + New-VMSwitch -Name $switchName -SwitchType $switchType + return $true +} +return $false +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, switchName, switchType) + var created = strings.TrimSpace(cmdOut) == "True" + return created, err +} + +func DeleteVirtualSwitch(switchName string) error { + + var script = ` +param([string]$switchName) +$switch = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue +if ($switch -ne $null) { + $switch | Remove-VMSwitch -Force +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, switchName) + return err +} + +func StartVirtualMachine(vmName string) error { + + var script = ` +param([string]$vmName) +Start-VM -Name $vmName +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func RestartVirtualMachine(vmName string) error { + + var script = ` +param([string]$vmName) +Restart-VM $vmName -Force +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func StopVirtualMachine(vmName string) error { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { + Stop-VM -VM $vm +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error { + + var script = ` +param([string]$vmName,[string]$integrationServiceName) +Enable-VMIntegrationService -VMName $vmName -Name $integrationServiceName +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, integrationServiceName) + return err +} + +func SetNetworkAdapterVlanId(switchName string, vlanId string) error { + + var script = ` +param([string]$networkAdapterName,[string]$vlanId) +Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $networkAdapterName -Access -VlanId $vlanId +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, switchName, vlanId) + return err +} + +func SetVirtualMachineVlanId(vmName string, vlanId string) error { + + var script = ` +param([string]$vmName,[string]$vlanId) +Set-VMNetworkAdapterVlan -VMName $vmName -Access -VlanId $vlanId +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, vlanId) + return err +} + +func GetExternalOnlineVirtualSwitch() (string, error) { + + var script = ` +$adapters = Get-NetAdapter -Physical -ErrorAction SilentlyContinue | Where-Object { $_.Status -eq 'Up' } | Sort-Object -Descending -Property Speed +foreach ($adapter in $adapters) { + $switch = Get-VMSwitch -SwitchType External | Where-Object { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription } + + if ($switch -ne $null) { + $switch.Name + break + } +} +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script) + if err != nil { + return "", err + } + + var switchName = strings.TrimSpace(cmdOut) + return switchName, nil +} + +func CreateExternalVirtualSwitch(vmName string, switchName string) error { + + var script = ` +param([string]$vmName,[string]$switchName) +$switch = $null +$names = @('ethernet','wi-fi','lan') +$adapters = foreach ($name in $names) { + Get-NetAdapter -Physical -Name $name -ErrorAction SilentlyContinue | where status -eq 'up' +} + +foreach ($adapter in $adapters) { + $switch = Get-VMSwitch -SwitchType External | where { $_.NetAdapterInterfaceDescription -eq $adapter.InterfaceDescription } + + if ($switch -eq $null) { + $switch = New-VMSwitch -Name $switchName -NetAdapterName $adapter.Name -AllowManagementOS $true -Notes 'Parent OS, VMs, WiFi' + } + + if ($switch -ne $null) { + break + } +} + +if($switch -ne $null) { + Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch +} else { + Write-Error 'No internet adapters found' +} +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, switchName) + return err +} + +func GetVirtualMachineSwitchName(vmName string) (string, error) { + + var script = ` +param([string]$vmName) +(Get-VMNetworkAdapter -VMName $vmName).SwitchName +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + if err != nil { + return "", err + } + + return strings.TrimSpace(cmdOut), nil +} + +func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName string) error { + + var script = ` +param([string]$vmName,[string]$switchName) +Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter –SwitchName $switchName +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, switchName) + return err +} + +func UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error { + + var script = ` +param([string]$vmName,[string]$switchName) +Set-VMNetworkAdapterVlan -VMName $vmName -Untagged +Set-VMNetworkAdapterVlan -ManagementOS -VMNetworkAdapterName $switchName -Untagged +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, switchName) + return err +} + +func IsRunning(vmName string) (bool, error) { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + var isRunning = strings.TrimSpace(cmdOut) == "True" + return isRunning, err +} + +func Mac(vmName string) (string, error) { + var script = ` +param([string]$vmName, [int]$addressIndex) +try { + $adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue + $mac = $adapter.MacAddress[$addressIndex] + if($mac -eq $null) { + return $false + } +} catch { + return $false +} +$mac +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName, "0") + + return cmdOut, err +} + +func IpAddress(mac string) (string, error) { + var script = ` +param([string]$mac, [int]$addressIndex) +try { + $ip = Get-Vm | %{$_.NetworkAdapters} | ?{$_.MacAddress -eq $mac} | %{$_.IpAddresses[$addressIndex]} + + if($ip -eq $null) { + return $false + } +} catch { + return $false +} +$ip +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, mac, "0") + + return cmdOut, err +} + +func Start(vmName string) error { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { + Start-VM –Name $vmName +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func TurnOff(vmName string) error { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { + Stop-VM -Name $vmName -TurnOff +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + +func ShutDown(vmName string) error { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { + Stop-VM –Name $vmName +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} diff --git a/powershell/powershell.go b/powershell/powershell.go new file mode 100644 index 00000000000..28c3d908b68 --- /dev/null +++ b/powershell/powershell.go @@ -0,0 +1,255 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package powershell + +import ( + "fmt" + "log" + "io" + "os" + "os/exec" + "strings" + "bytes" + "io/ioutil" + "strconv" +) + +const ( + powerShellFalse = "False" + powerShellTrue = "True" +) + +type PowerShellCmd struct { + Stdout io.Writer + Stderr io.Writer +} + +func (ps *PowerShellCmd) Run(fileContents string, params ...string) error { + _, err := ps.Output(fileContents, params...) + return err +} + +// Output runs the PowerShell command and returns its standard output. +func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, error) { + path, err := ps.getPowerShellPath(); + if err != nil { + return "", nil + } + + filename, err := saveScript(fileContents); + if err != nil { + return "", err + } + + debug := os.Getenv("PACKER_POWERSHELL_DEBUG") != "" + verbose := debug || os.Getenv("PACKER_POWERSHELL_VERBOSE") != "" + + if !debug { + defer os.Remove(filename) + } + + args := createArgs(filename, params...) + + if verbose { + log.Printf("Run: %s %s", path, args) + } + + var stdout, stderr bytes.Buffer + command := exec.Command(path, args...) + command.Stdout = &stdout + command.Stderr = &stderr + + err = command.Run() + + if ps.Stdout != nil { + stdout.WriteTo(ps.Stdout) + } + + if ps.Stderr != nil { + stderr.WriteTo(ps.Stderr) + } + + stderrString := strings.TrimSpace(stderr.String()) + + if _, ok := err.(*exec.ExitError); ok { + err = fmt.Errorf("PowerShell error: %s", stderrString) + } + + if len(stderrString) > 0 { + err = fmt.Errorf("PowerShell error: %s", stderrString) + } + + stdoutString := strings.TrimSpace(stdout.String()) + + if verbose && stdoutString != "" { + log.Printf("stdout: %s", stdoutString) + } + + // only write the stderr string if verbose because + // the error string will already be in the err return value. + if verbose && stderrString != "" { + log.Printf("stderr: %s", stderrString) + } + + return stdoutString, err; +} + +func (ps *PowerShellCmd) getPowerShellPath() (string, error) { + path, err := exec.LookPath("powershell") + if err != nil { + log.Fatal("Cannot find PowerShell in the path", err) + return "", err + } + + return path, nil +} + +func saveScript(fileContents string) (string, error) { + file, err := ioutil.TempFile(os.TempDir(), "ps") + if err != nil { + return "", err + } + + _, err = file.Write([]byte(fileContents)) + if err != nil { + return "", err + } + + err = file.Close() + if err != nil { + return "", err + } + + newFilename := file.Name() + ".ps1" + err = os.Rename(file.Name(), newFilename) + if err != nil { + return "", err + } + + return newFilename, nil +} + +func createArgs(filename string, params ...string) []string { + args := make([]string,len(params)+4) + args[0] = "-ExecutionPolicy" + args[1] = "Bypass" + + args[2] = "-File" + args[3] = filename + + for key, value := range params { + args[key+4] = value + } + + return args; +} + +func GetHostAvailableMemory() float64 { + + var script = "(Get-WmiObject Win32_OperatingSystem).FreePhysicalMemory / 1024" + + var ps PowerShellCmd + output, _ := ps.Output(script) + + freeMB, _ := strconv.ParseFloat(output, 64) + + return freeMB +} + + +func GetHostName(ip string) (string, error) { + + var script = ` +param([string]$ip) +try { + $HostName = [System.Net.Dns]::GetHostEntry($ip).HostName + if ($HostName -ne $null) { + $HostName = $HostName.Split('.')[0] + } + $HostName +} catch { } +` + + // + var ps PowerShellCmd + cmdOut, err := ps.Output(script, ip); + if err != nil { + return "", err + } + + return cmdOut, nil +} + +func IsCurrentUserAnAdministrator() (bool, error) { + var script = ` +$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent() +$principal = new-object System.Security.Principal.WindowsPrincipal($identity) +$administratorRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator +return $principal.IsInRole($administratorRole) +` + + var ps PowerShellCmd + cmdOut, err := ps.Output(script); + if err != nil { + return false, err + } + + res := strings.TrimSpace(cmdOut) + return res == powerShellTrue, nil +} + + +func ModuleExists(moduleName string) (bool, error) { + + var script = ` +param([string]$moduleName) +(Get-Module -Name $moduleName) -ne $null +` + var ps PowerShellCmd + cmdOut, err := ps.Output(script) + if err != nil { + return false, err + } + + res := strings.TrimSpace(string(cmdOut)) + + if(res == powerShellFalse){ + err := fmt.Errorf("PowerShell %s module is not loaded. Make sure %s feature is on.", moduleName, moduleName) + return false, err + } + + return true, nil +} + +func SetUnattendedProductKey(path string, productKey string) error { + + var script = ` +param([string]$path,[string]$productKey) + +$unattend = [xml](Get-Content -Path $path) +$ns = @{ un = 'urn:schemas-microsoft-com:unattend' } + +$setupNode = $unattend | + Select-Xml -XPath '//un:settings[@pass = "specialize"]/un:component[@name = "Microsoft-Windows-Shell-Setup"]' -Namespace $ns | + Select-Object -ExpandProperty Node + +$productKeyNode = $setupNode | + Select-Xml -XPath '//un:ProductKey' -Namespace $ns | + Select-Object -ExpandProperty Node + +if ($productKeyNode -eq $null) { + $productKeyNode = $unattend.CreateElement('ProductKey', $ns.un) + [Void]$setupNode.AppendChild($productKeyNode) +} + +$productKeyNode.InnerText = $productKey + +$unattend.Save($path) +` + + var ps PowerShellCmd + err := ps.Run(script, path, productKey) + return err +} diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go new file mode 100644 index 00000000000..9d60e8f6044 --- /dev/null +++ b/powershell/powershell_test.go @@ -0,0 +1,72 @@ + + +package powershell + +import ( + "bytes" + "testing" +) + +func TestOutputScriptBlock(t *testing.T) { + + ps, err := powershell.Command() + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + trueOutput, err := powershell.OutputScriptBlock("$True") + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if trueOutput != "True" { + t.Fatalf("output '%v' is not 'True'", trueOutput) + } + + falseOutput, err := powershell.OutputScriptBlock("$False") + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if falseOutput != "False" { + t.Fatalf("output '%v' is not 'False'", falseOutput) + } +} + +func TestRunScriptBlock(t *testing.T) { + powershell, err := powershell.Command() + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + err = powershell.RunScriptBlock("$True") +} + +func TestVersion(t *testing.T) { + powershell, err := powershell.Command() + version, err := powershell.Version(); + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if (version != 4) { + t.Fatalf("expected version 4") + } +} + +func TestRunFile(t *testing.T) { + powershell, err := powershell.Command() + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + var blockBuffer bytes.Buffer + blockBuffer.WriteString("param([string]$a, [string]$b, [int]$x, [int]$y) $n = $x + $y; Write-Host $a, $b, $n") + + err = powershell.Run(blockBuffer.String(), "a", "b", "5", "10") + + if err != nil { + t.Fatalf("should not have error: %s", err) + } + +} diff --git a/powershell/scriptbuilder.go b/powershell/scriptbuilder.go new file mode 100644 index 00000000000..d51156aa2a0 --- /dev/null +++ b/powershell/scriptbuilder.go @@ -0,0 +1,30 @@ +package powershell + +import ( + "bytes" +) + +type ScriptBuilder struct { + buffer bytes.Buffer +} + +func (b *ScriptBuilder) WriteLine(s string) (n int, err error) { + n, err = b.buffer.WriteString(s); + b.buffer.WriteString("\n") + + return n+1, err +} + +func (b *ScriptBuilder) WriteString(s string) (n int, err error) { + n, err = b.buffer.WriteString(s); + return n, err +} + +func (b *ScriptBuilder) String() string { + return b.buffer.String() +} + +func (b *ScriptBuilder) Reset() { + b.buffer.Reset() +} + From f9c7dca8f91c545f0af6123fcc9d1b7fe9119086 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 14:06:27 +0100 Subject: [PATCH 002/113] Use the same configuration style as existing builders Move builder into correctly named folder --- builder/hyperv/common/floppy_config.go | 19 +++ builder/hyperv/common/floppy_config_test.go | 18 +++ builder/hyperv/common/run_config.go | 29 +++++ builder/hyperv/common/run_config_test.go | 37 ++++++ builder/hyperv/common/shutdown_config.go | 30 +++++ builder/hyperv/common/shutdown_config_test.go | 45 +++++++ builder/hyperv/common/step_shutdown.go | 37 +----- builder/hyperv/iso/builder.go | 111 +++++++++++------- .../build-and-deploy.sh | 0 plugin/builder-hyperv-iso/main.go | 15 +++ .../main_test.go | 0 plugin/packer-builder-hyperv-iso/main.go | 19 --- 12 files changed, 264 insertions(+), 96 deletions(-) create mode 100644 builder/hyperv/common/floppy_config.go create mode 100644 builder/hyperv/common/floppy_config_test.go create mode 100644 builder/hyperv/common/run_config.go create mode 100644 builder/hyperv/common/run_config_test.go create mode 100644 builder/hyperv/common/shutdown_config.go create mode 100644 builder/hyperv/common/shutdown_config_test.go rename plugin/{packer-builder-hyperv-iso => builder-hyperv-iso}/build-and-deploy.sh (100%) create mode 100644 plugin/builder-hyperv-iso/main.go rename plugin/{packer-builder-hyperv-iso => builder-hyperv-iso}/main_test.go (100%) delete mode 100644 plugin/packer-builder-hyperv-iso/main.go diff --git a/builder/hyperv/common/floppy_config.go b/builder/hyperv/common/floppy_config.go new file mode 100644 index 00000000000..d656e103a8f --- /dev/null +++ b/builder/hyperv/common/floppy_config.go @@ -0,0 +1,19 @@ +package common + +import ( + "github.com/mitchellh/packer/template/interpolate" +) + +// FloppyConfig is configuration related to created floppy disks and attaching +// them to a Parallels virtual machine. +type FloppyConfig struct { + FloppyFiles []string `mapstructure:"floppy_files"` +} + +func (c *FloppyConfig) Prepare(ctx *interpolate.Context) []error { + if c.FloppyFiles == nil { + c.FloppyFiles = make([]string, 0) + } + + return nil +} diff --git a/builder/hyperv/common/floppy_config_test.go b/builder/hyperv/common/floppy_config_test.go new file mode 100644 index 00000000000..3e4fc55db3a --- /dev/null +++ b/builder/hyperv/common/floppy_config_test.go @@ -0,0 +1,18 @@ +package common + +import ( + "testing" +) + +func TestFloppyConfigPrepare(t *testing.T) { + c := new(FloppyConfig) + + errs := c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + + if len(c.FloppyFiles) > 0 { + t.Fatal("should not have floppy files") + } +} diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go new file mode 100644 index 00000000000..c755cdafbcf --- /dev/null +++ b/builder/hyperv/common/run_config.go @@ -0,0 +1,29 @@ +package common + +import ( + "fmt" + "time" + + "github.com/mitchellh/packer/template/interpolate" +) + +type RunConfig struct { + Headless bool `mapstructure:"headless"` + RawBootWait string `mapstructure:"boot_wait"` + + BootWait time.Duration `` +} + +func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { + if c.RawBootWait == "" { + c.RawBootWait = "10s" + } + + var err error + c.BootWait, err = time.ParseDuration(c.RawBootWait) + if err != nil { + return []error{fmt.Errorf("Failed parsing boot_wait: %s", err)} + } + + return nil +} diff --git a/builder/hyperv/common/run_config_test.go b/builder/hyperv/common/run_config_test.go new file mode 100644 index 00000000000..8068fe625e3 --- /dev/null +++ b/builder/hyperv/common/run_config_test.go @@ -0,0 +1,37 @@ +package common + +import ( + "testing" +) + +func TestRunConfigPrepare_BootWait(t *testing.T) { + var c *RunConfig + var errs []error + + // Test a default boot_wait + c = new(RunConfig) + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } + + if c.RawBootWait != "10s" { + t.Fatalf("bad value: %s", c.RawBootWait) + } + + // Test with a bad boot_wait + c = new(RunConfig) + c.RawBootWait = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("bad: %#v", errs) + } + + // Test with a good one + c = new(RunConfig) + c.RawBootWait = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("should not have error: %s", errs) + } +} diff --git a/builder/hyperv/common/shutdown_config.go b/builder/hyperv/common/shutdown_config.go new file mode 100644 index 00000000000..83d2224c386 --- /dev/null +++ b/builder/hyperv/common/shutdown_config.go @@ -0,0 +1,30 @@ +package common + +import ( + "fmt" + "time" + + "github.com/mitchellh/packer/template/interpolate" +) + +type ShutdownConfig struct { + ShutdownCommand string `mapstructure:"shutdown_command"` + RawShutdownTimeout string `mapstructure:"shutdown_timeout"` + + ShutdownTimeout time.Duration `` +} + +func (c *ShutdownConfig) Prepare(ctx *interpolate.Context) []error { + if c.RawShutdownTimeout == "" { + c.RawShutdownTimeout = "5m" + } + + var errs []error + var err error + c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) + if err != nil { + errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) + } + + return errs +} diff --git a/builder/hyperv/common/shutdown_config_test.go b/builder/hyperv/common/shutdown_config_test.go new file mode 100644 index 00000000000..5da613a19a2 --- /dev/null +++ b/builder/hyperv/common/shutdown_config_test.go @@ -0,0 +1,45 @@ +package common + +import ( + "testing" + "time" +) + +func testShutdownConfig() *ShutdownConfig { + return &ShutdownConfig{} +} + +func TestShutdownConfigPrepare_ShutdownCommand(t *testing.T) { + var c *ShutdownConfig + var errs []error + + c = testShutdownConfig() + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } +} + +func TestShutdownConfigPrepare_ShutdownTimeout(t *testing.T) { + var c *ShutdownConfig + var errs []error + + // Test with a bad value + c = testShutdownConfig() + c.RawShutdownTimeout = "this is not good" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) == 0 { + t.Fatalf("should have error") + } + + // Test with a good one + c = testShutdownConfig() + c.RawShutdownTimeout = "5s" + errs = c.Prepare(testConfigTemplate(t)) + if len(errs) > 0 { + t.Fatalf("err: %#v", errs) + } + if c.ShutdownTimeout != 5*time.Second { + t.Fatalf("bad: %s", c.ShutdownTimeout) + } +} diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index db80e7c5681..62bbab21f4e 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -10,41 +10,6 @@ import ( "time" ) -type ShutdownConfig struct { - ShutdownCommand string `mapstructure:"shutdown_command"` - RawShutdownTimeout string `mapstructure:"shutdown_timeout"` - - ShutdownTimeout time.Duration `` -} - -func (c *ShutdownConfig) Prepare(t *packer.ConfigTemplate) []error { - if c.RawShutdownTimeout == "" { - c.RawShutdownTimeout = "5m" - } - - templates := map[string]*string{ - "shutdown_command": &c.ShutdownCommand, - "shutdown_timeout": &c.RawShutdownTimeout, - } - - errs := make([]error, 0) - for n, ptr := range templates { - var err error - *ptr, err = t.Process(*ptr, nil) - if err != nil { - errs = append(errs, fmt.Errorf("Error processing %s: %s", n, err)) - } - } - - var err error - c.ShutdownTimeout, err = time.ParseDuration(c.RawShutdownTimeout) - if err != nil { - errs = append(errs, fmt.Errorf("Failed parsing shutdown_timeout: %s", err)) - } - - return errs -} - // This step shuts down the machine. It first attempts to do so gracefully, // but ultimately forcefully shuts it down if that fails. // @@ -104,7 +69,7 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { if stderr.Len() > 0 { log.Printf("Shutdown stderr: %s", stderr.String()) } - + // Wait for the machine to actually shut down log.Printf("Waiting max %s for shutdown to complete", s.Timeout) shutdownTimer := time.After(s.Timeout) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 60bd5f77f61..2e4723a917b 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -11,13 +11,14 @@ import ( "github.com/mitchellh/multistep" hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" "github.com/mitchellh/packer/common" - "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/helper/communicator" + "github.com/mitchellh/packer/helper/config" + "github.com/mitchellh/packer/packer" powershell "github.com/mitchellh/packer/powershell" "github.com/mitchellh/packer/powershell/hyperv" + "github.com/mitchellh/packer/template/interpolate" "log" "os" - "regexp" "strings" "time" ) @@ -40,11 +41,18 @@ const ( // Builder implements packer.Builder and builds the actual Hyperv // images. type Builder struct { - config config + config Config runner multistep.Runner } -type config struct { +type Config struct { + common.PackerConfig `mapstructure:",squash"` + hypervcommon.FloppyConfig `mapstructure:",squash"` + hypervcommon.OutputConfig `mapstructure:",squash"` + hypervcommon.SSHConfig `mapstructure:",squash"` + hypervcommon.ShutdownConfig `mapstructure:",squash"` + hypervcommon.RunConfig `mapstructure:",squash"` + // The size, in megabytes, of the hard disk to create for the VM. // By default, this is 130048 (about 127 GB). DiskSize uint `mapstructure:"disk_size"` @@ -87,11 +95,6 @@ type config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - common.PackerConfig `mapstructure:",squash"` - hypervcommon.OutputConfig `mapstructure:",squash"` - hypervcommon.SSHConfig `mapstructure:",squash"` - hypervcommon.ShutdownConfig `mapstructure:",squash"` - SwitchName string `mapstructure:"switch_name"` Communicator string `mapstructure:"communicator"` @@ -102,32 +105,28 @@ type config struct { SSHWaitTimeout time.Duration - tpl *packer.ConfigTemplate + ctx interpolate.Context } // Prepare processes the build configuration parameters. func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { - - md, err := common.DecodeConfig(&b.config, raws...) - if err != nil { - return nil, err - } - - b.config.tpl, err = packer.NewConfigTemplate() + err := config.Decode(&b.config, &config.DecodeOpts{ + Interpolate: true, + InterpolateFilter: &interpolate.RenderFilter{ + Exclude: []string{}, + }, + }, raws...) if err != nil { return nil, err } - log.Println(fmt.Sprintf("%s: %v", "PackerUserVars", b.config.PackerUserVars)) - - b.config.tpl.UserVars = b.config.PackerUserVars - // Accumulate any errors and warnings - errs := common.CheckUnusedConfig(md) - errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(b.config.tpl, &b.config.PackerConfig)...) - errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(b.config.tpl)...) - errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(b.config.tpl)...) - + var errs *packer.MultiError + errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) + errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) warnings := make([]string, 0) err = b.checkDiskSize() @@ -144,6 +143,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.VMName = fmt.Sprintf("pvm_%s", uuid.New()) } + log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) + if b.config.SwitchName == "" { // no switch name, try to get one attached to a online network adapter onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() @@ -155,6 +156,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) + log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) if b.config.Communicator == "" { b.config.Communicator = "ssh" @@ -165,23 +167,48 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { errs = packer.MultiErrorAppend(errs, err) } + log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) + // Errors - templates := map[string]*string{ - "iso_url": &b.config.RawSingleISOUrl + if b.config.ISOChecksumType == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("The iso_checksum_type must be specified.")) + } else { + b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType) + if b.config.ISOChecksumType != "none" { + if b.config.ISOChecksum == "" { + errs = packer.MultiErrorAppend( + errs, errors.New("Due to large file sizes, an iso_checksum is required")) + } else { + b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum) + } + + if h := common.HashForType(b.config.ISOChecksumType); h == nil { + errs = packer.MultiErrorAppend( + errs, + fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType)) + } + } + } + + if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { + errs = packer.MultiErrorAppend( + errs, errors.New("One of iso_url or iso_urls must be specified.")) + } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { + errs = packer.MultiErrorAppend( + errs, errors.New("Only one of iso_url or iso_urls may be specified.")) + } else if b.config.RawSingleISOUrl != "" { + b.config.ISOUrls = []string{b.config.RawSingleISOUrl} } - for n, ptr := range templates { - var err error - *ptr, err = b.config.tpl.Process(*ptr, nil) + for i, url := range b.config.ISOUrls { + b.config.ISOUrls[i], err = common.DownloadableURL(url) if err != nil { - errs = packer.MultiErrorAppend(errs, fmt.Errorf("Error processing %s: %s", n, err)) + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) } } - log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) - log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) - log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) - if b.config.RawSingleISOUrl == "" { errs = packer.MultiErrorAppend(errs, errors.New("iso_url: The option can't be missed and a path must be specified.")) } else if _, err := os.Stat(b.config.RawSingleISOUrl); err != nil { @@ -190,14 +217,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) - b.config.SSHWaitTimeout, err = time.ParseDuration(b.config.RawSSHWaitTimeout) - // Warnings warning := b.checkHostAvailableMemory() if warning != "" { warnings = appendWarnings(warnings, warning) } + if b.config.ISOChecksumType == "none" { + warnings = append(warnings, + "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ + "a checksum is highly recommended.") + } + if b.config.ShutdownCommand == "" { warnings = append(warnings, "A shutdown_command was not specified. Without a shutdown command, Packer\n"+ @@ -254,7 +285,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountSecondaryDvdImages{}, - &hypervcommon.StepStartVm{ Reason: "OS installation", }, @@ -277,7 +307,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Config: &b.config.SSHConfig.Comm, Host: hypervcommon.CommHost, SSHConfig: hypervcommon.SSHConfigFunc(b.config.SSHConfig), - SSHPort: hypervcommon.SSHPort, }, // provision requires communicator to be setup @@ -388,4 +417,4 @@ func (b *Builder) checkHostAvailableMemory() string { } return "" -} \ No newline at end of file +} diff --git a/plugin/packer-builder-hyperv-iso/build-and-deploy.sh b/plugin/builder-hyperv-iso/build-and-deploy.sh similarity index 100% rename from plugin/packer-builder-hyperv-iso/build-and-deploy.sh rename to plugin/builder-hyperv-iso/build-and-deploy.sh diff --git a/plugin/builder-hyperv-iso/main.go b/plugin/builder-hyperv-iso/main.go new file mode 100644 index 00000000000..66461504945 --- /dev/null +++ b/plugin/builder-hyperv-iso/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/mitchellh/packer/builder/hyperv/iso" + "github.com/mitchellh/packer/packer/plugin" +) + +func main() { + server, err := plugin.Server() + if err != nil { + panic(err) + } + server.RegisterBuilder(new(iso.Builder)) + server.Serve() +} diff --git a/plugin/packer-builder-hyperv-iso/main_test.go b/plugin/builder-hyperv-iso/main_test.go similarity index 100% rename from plugin/packer-builder-hyperv-iso/main_test.go rename to plugin/builder-hyperv-iso/main_test.go diff --git a/plugin/packer-builder-hyperv-iso/main.go b/plugin/packer-builder-hyperv-iso/main.go deleted file mode 100644 index c7be7a1ed09..00000000000 --- a/plugin/packer-builder-hyperv-iso/main.go +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package main - -import ( - "github.com/mitchellh/packer/builder/hyperv/iso" - "github.com/mitchellh/packer/plugin" -) - -func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterBuilder(new(iso.Builder)) - server.Serve() -} From 755eb56649c38fff8e76fd9549c40c3c1659d0a6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 14:32:48 +0100 Subject: [PATCH 003/113] The test is meant to fail if the folder already exists --- builder/hyperv/common/output_config_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/output_config_test.go b/builder/hyperv/common/output_config_test.go index a4d8e7999a9..ebd91eab13d 100644 --- a/builder/hyperv/common/output_config_test.go +++ b/builder/hyperv/common/output_config_test.go @@ -39,7 +39,7 @@ func TestOutputConfigPrepare_exists(t *testing.T) { PackerForce: false, } errs := c.Prepare(testConfigTemplate(t), pc) - if len(errs) != 0 { - t.Fatal("should not have errors") + if len(errs) == 0 { + t.Fatal("should have errors") } } From da1a0f44dd96289624011c9397e496ec8153f031 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 14:59:37 +0100 Subject: [PATCH 004/113] Tests where for an old version of powershell class. Chnaged for the new one --- powershell/powershell_test.go | 48 ++++++++++------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 9d60e8f6044..4aff204916f 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -1,5 +1,3 @@ - - package powershell import ( @@ -7,14 +5,18 @@ import ( "testing" ) -func TestOutputScriptBlock(t *testing.T) { - - ps, err := powershell.Command() +func TestOutput(t *testing.T) { + var ps PowerShellCmd + cmdOut, err := ps.Output("") if err != nil { t.Fatalf("should not have error: %s", err) } - trueOutput, err := powershell.OutputScriptBlock("$True") + if cmdOut != "" { + t.Fatalf("output '%v' is not ''", cmdOut) + } + + trueOutput, err := ps.Output("$True") if err != nil { t.Fatalf("should not have error: %s", err) } @@ -23,7 +25,7 @@ func TestOutputScriptBlock(t *testing.T) { t.Fatalf("output '%v' is not 'True'", trueOutput) } - falseOutput, err := powershell.OutputScriptBlock("$False") + falseOutput, err := ps.Output("$False") if err != nil { t.Fatalf("should not have error: %s", err) } @@ -33,40 +35,18 @@ func TestOutputScriptBlock(t *testing.T) { } } -func TestRunScriptBlock(t *testing.T) { - powershell, err := powershell.Command() - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - err = powershell.RunScriptBlock("$True") -} - -func TestVersion(t *testing.T) { - powershell, err := powershell.Command() - version, err := powershell.Version(); - if err != nil { - t.Fatalf("should not have error: %s", err) - } - - if (version != 4) { - t.Fatalf("expected version 4") - } -} - func TestRunFile(t *testing.T) { - powershell, err := powershell.Command() - if err != nil { - t.Fatalf("should not have error: %s", err) - } - var blockBuffer bytes.Buffer blockBuffer.WriteString("param([string]$a, [string]$b, [int]$x, [int]$y) $n = $x + $y; Write-Host $a, $b, $n") - err = powershell.Run(blockBuffer.String(), "a", "b", "5", "10") + var ps PowerShellCmd + cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") if err != nil { t.Fatalf("should not have error: %s", err) } + if cmdOut != "a b 15" { + t.Fatalf("output '%v' is not 'a b 15'", cmdOut) + } } From d56dabd98bbb3449e21c3c423fdbf75335d788fd Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 15:53:08 +0100 Subject: [PATCH 005/113] Use the convention for default vmname Use the convention for default hdd size Tests added for builder --- builder/hyperv/iso/builder.go | 26 ++- builder/hyperv/iso/builder_test.go | 246 +++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+), 16 deletions(-) create mode 100644 builder/hyperv/iso/builder_test.go diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 2e4723a917b..f9501511e2f 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -5,7 +5,6 @@ package iso import ( - "code.google.com/p/go-uuid/uuid" "errors" "fmt" "github.com/mitchellh/multistep" @@ -18,13 +17,12 @@ import ( "github.com/mitchellh/packer/powershell/hyperv" "github.com/mitchellh/packer/template/interpolate" "log" - "os" "strings" "time" ) const ( - DefaultDiskSize = 127 * 1024 // 127GB + DefaultDiskSize = 40000 // ~40GB MinDiskSize = 10 * 1024 // 10GB MaxDiskSize = 65536 * 1024 // 64TB @@ -71,6 +69,7 @@ type Config struct { FloppyFiles []string `mapstructure:"floppy_files"` // SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` + // The checksum for the OS ISO file. Because ISO files are so large, // this is required and Packer will verify it prior to booting a virtual // machine with the ISO attached. The type of the checksum is specified @@ -91,6 +90,7 @@ type Config struct { // same file (same checksum). By default this is empty and iso_url is // used. Only one of iso_url or iso_urls can be specified. ISOUrls []string `mapstructure:"iso_urls"` + // This is the name of the new virtual machine. // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` @@ -140,7 +140,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.VMName == "" { - b.config.VMName = fmt.Sprintf("pvm_%s", uuid.New()) + b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) } log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) @@ -149,7 +149,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // no switch name, try to get one attached to a online network adapter onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() if onlineSwitchName == "" || err != nil { - b.config.SwitchName = fmt.Sprintf("pis_%s", uuid.New()) + b.config.SwitchName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) } else { b.config.SwitchName = onlineSwitchName } @@ -209,20 +209,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } - if b.config.RawSingleISOUrl == "" { - errs = packer.MultiErrorAppend(errs, errors.New("iso_url: The option can't be missed and a path must be specified.")) - } else if _, err := os.Stat(b.config.RawSingleISOUrl); err != nil { - errs = packer.MultiErrorAppend(errs, errors.New("iso_url: Check the path is correct")) - } - log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) // Warnings - warning := b.checkHostAvailableMemory() - if warning != "" { - warnings = appendWarnings(warnings, warning) - } - if b.config.ISOChecksumType == "none" { warnings = append(warnings, "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ @@ -235,6 +224,11 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { "will forcibly halt the virtual machine, which may result in data loss.") } + warning := b.checkHostAvailableMemory() + if warning != "" { + warnings = appendWarnings(warnings, warning) + } + if errs != nil && len(errs.Errors) > 0 { return warnings, errs } diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go new file mode 100644 index 00000000000..a17851a3e11 --- /dev/null +++ b/builder/hyperv/iso/builder_test.go @@ -0,0 +1,246 @@ +package iso + +import ( + "github.com/mitchellh/packer/packer" + "reflect" + "testing" +) + +func testConfig() map[string]interface{} { + return map[string]interface{}{ + "iso_checksum": "foo", + "iso_checksum_type": "md5", + "iso_url": "http://www.packer.io", + "shutdown_command": "yes", + "ssh_username": "foo", + + packer.BuildNameConfigKey: "foo", + } +} + +func TestBuilder_ImplementsBuilder(t *testing.T) { + var raw interface{} + raw = &Builder{} + if _, ok := raw.(packer.Builder); !ok { + t.Error("Builder must implement builder.") + } +} + +func TestBuilderPrepare_Defaults(t *testing.T) { + var b Builder + config := testConfig() + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.VMName != "packer-foo" { + t.Errorf("bad vm name: %s", b.config.VMName) + } +} + +func TestBuilderPrepare_DiskSize(t *testing.T) { + var b Builder + config := testConfig() + + delete(config, "disk_size") + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("bad err: %s", err) + } + + if b.config.DiskSize != 40000 { + t.Fatalf("bad size: %d", b.config.DiskSize) + } + + config["disk_size"] = 60000 + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.DiskSize != 60000 { + t.Fatalf("bad size: %d", b.config.DiskSize) + } +} + +func TestBuilderPrepare_InvalidKey(t *testing.T) { + var b Builder + config := testConfig() + + // Add a random key + config["i_should_not_be_valid"] = true + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } +} + +func TestBuilderPrepare_ISOChecksum(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum"] = "FOo" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksum != "foo" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksum) + } +} + +func TestBuilderPrepare_ISOChecksumType(t *testing.T) { + var b Builder + config := testConfig() + + // Test bad + config["iso_checksum_type"] = "" + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test good + config["iso_checksum_type"] = "mD5" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksumType != "md5" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } + + // Test unknown + config["iso_checksum_type"] = "fake" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test none + config["iso_checksum_type"] = "none" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) == 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Fatalf("should not have error: %s", err) + } + + if b.config.ISOChecksumType != "none" { + t.Fatalf("should've lowercased: %s", b.config.ISOChecksumType) + } +} + +func TestBuilderPrepare_ISOUrl(t *testing.T) { + var b Builder + config := testConfig() + delete(config, "iso_url") + delete(config, "iso_urls") + + // Test both epty + config["iso_url"] = "" + b = Builder{} + warns, err := b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test iso_url set + config["iso_url"] = "http://www.packer.io" + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected := []string{"http://www.packer.io"} + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } + + // Test both set + config["iso_url"] = "http://www.packer.io" + config["iso_urls"] = []string{"http://www.packer.io"} + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err == nil { + t.Fatal("should have error") + } + + // Test just iso_urls set + delete(config, "iso_url") + config["iso_urls"] = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + + b = Builder{} + warns, err = b.Prepare(config) + if len(warns) > 0 { + t.Fatalf("bad: %#v", warns) + } + if err != nil { + t.Errorf("should not have error: %s", err) + } + + expected = []string{ + "http://www.packer.io", + "http://www.hashicorp.com", + } + if !reflect.DeepEqual(b.config.ISOUrls, expected) { + t.Fatalf("bad: %#v", b.config.ISOUrls) + } +} From f4296f9982a335c144f59745451edd3a317240c6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 21 Jun 2015 19:35:32 +0100 Subject: [PATCH 006/113] Wait until WinRM is available, before continuing with install. This is how other system like VeeWee do it. Add support for number of cpus to use for vm Add support for vm generation --- builder/hyperv/common/step_create_vm.go | 24 ++++++++++---- .../step_wait_for_install_to_complete.go | 17 ---------- builder/hyperv/iso/builder.go | 31 +++++++++++-------- powershell/hyperv/hyperv.go | 20 +++++++++--- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 6a172ada880..1ea4530d803 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -6,10 +6,10 @@ package common import ( "fmt" - "strconv" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/powershell/hyperv" + "strconv" ) // This step creates the actual virtual machine. @@ -17,17 +17,19 @@ import ( // Produces: // VMName string - The name of the VM type StepCreateVM struct { - VMName string + VMName string SwitchName string - RamSizeMB uint - DiskSize uint + RamSizeMB uint + DiskSize uint + Generation uint + Cpu uint } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ui := state.Get("ui").(packer.Ui) ui.Say("Creating virtual machine...") - path := state.Get("packerTempDir").(string) + path := state.Get("packerTempDir").(string) // convert the MB to bytes ramBytes := int64(s.RamSizeMB * 1024 * 1024) @@ -36,8 +38,10 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ram := strconv.FormatInt(ramBytes, 10) diskSize := strconv.FormatInt(diskSizeBytes, 10) switchName := s.SwitchName + generation := strconv.FormatInt(int64(s.Generation), 10) + cpu := strconv.FormatInt(int64(s.Cpu), 10) - err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName) + err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, generation) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) @@ -45,6 +49,14 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + err = hyperv.SetVirtualMachineCpu(s.VMName, cpu) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go index 22da371c819..efd8bd8b704 100644 --- a/builder/hyperv/common/step_wait_for_install_to_complete.go +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -111,20 +111,3 @@ func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.S func (s *StepWaitForInstallToComplete) Cleanup(state multistep.StateBag) { } - - -type StepWaitForWinRm struct { -} - -func (s *StepWaitForWinRm) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - //vmName := state.Get("vmName").(string) - - ui.Say("Waiting for WinRM to be ready...") - - return multistep.ActionContinue -} - -func (s *StepWaitForWinRm) Cleanup(state multistep.StateBag) { - -} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index f9501511e2f..cf7e776a030 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -96,6 +96,8 @@ type Config struct { VMName string `mapstructure:"vm_name"` SwitchName string `mapstructure:"switch_name"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` Communicator string `mapstructure:"communicator"` @@ -155,6 +157,14 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } + if b.config.Cpu < 1 { + b.config.Cpu = 1 + } + + if b.config.Generation != 2 { + b.config.Generation = 1 + } + log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) @@ -269,6 +279,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchName: b.config.SwitchName, RamSizeMB: b.config.RamSizeMB, DiskSize: b.config.DiskSize, + Generation: b.config.Generation, + Cpu: b.config.Cpu, }, &hypervcommon.StepEnableIntegrationService{}, @@ -283,19 +295,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Reason: "OS installation", }, - // wait for the vm to be powered off - &hypervcommon.StepWaitForPowerOff{}, - - // remove the integration services dvd drive - // after we power down - &hypervcommon.StepUnmountSecondaryDvdImages{}, - - // - &hypervcommon.StepStartVm{ - Reason: "provisioning", - StartUpDelay: 60, - }, - // configure the communicator ssh, winrm &communicator.StepConnect{ Config: &b.config.SSHConfig.Comm, @@ -306,6 +305,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // provision requires communicator to be setup &common.StepProvision{}, + // remove the integration services dvd drive + // after we power down + &hypervcommon.StepUnmountSecondaryDvdImages{}, &hypervcommon.StepUnmountFloppyDrive{}, &hypervcommon.StepUnmountDvdDrive{}, @@ -314,6 +316,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Timeout: b.config.ShutdownTimeout, }, + // wait for the vm to be powered off + &hypervcommon.StepWaitForPowerOff{}, + &hypervcommon.StepExportVm{ OutputDir: b.config.OutputDir, }, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index afddc928b3d..250722effda 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -74,17 +74,29 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null return err } -func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string) error { +func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string, generation string) error { var script = ` -param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName) +param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]generation) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $path -ChildPath $vhdx -New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName +New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, ram, diskSize, switchName) + err := ps.Run(script, vmName, path, ram, diskSize, switchName, generation) + return err +} + +func SetVirtualMachineCpu(vmName string, cpu string) error { + + var script = ` +param([string]$vmName, [int]cpu) +Set-VMProcessor -VMName $vmName –Count $cpu +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, cpu) return err } From 9890d7f042da0218323d14781754b995c1bc3020 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 22 Jun 2015 22:26:06 +0100 Subject: [PATCH 007/113] Throw error if we can't get mac or ip address during steps --- builder/hyperv/common/driver_ps_4.go | 25 +++++++++++++++++++++++-- builder/hyperv/common/ssh.go | 24 +++++++++++------------- builder/hyperv/iso/builder.go | 2 +- powershell/hyperv/hyperv.go | 18 +++++++++--------- 4 files changed, 44 insertions(+), 25 deletions(-) diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index a5c2abd6f91..4b014614615 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -68,12 +68,33 @@ func (d *HypervPS4Driver) Verify() error { // Get mac address for VM. func (d *HypervPS4Driver) Mac(vmName string) (string, error) { - return hyperv.Mac(vmName) + res, err := hyperv.Mac(vmName) + + if err != nil { + return res, err + } + + if res == "" { + err := fmt.Errorf("%s", "No mac address.") + return res, err + } + + return res, err } // Get ip address for mac address. func (d *HypervPS4Driver) IpAddress(mac string) (string, error) { - return hyperv.IpAddress(mac) + res, err := hyperv.IpAddress(mac) + + if err != nil { + return res, err + } + + if res == "" { + err := fmt.Errorf("%s", "No ip address.") + return res, err + } + return res, err } func (d *HypervPS4Driver) verifyPSVersion() error { diff --git a/builder/hyperv/common/ssh.go b/builder/hyperv/common/ssh.go index f61047dd496..1bb06bfd250 100644 --- a/builder/hyperv/common/ssh.go +++ b/builder/hyperv/common/ssh.go @@ -3,8 +3,8 @@ package common import ( "github.com/mitchellh/multistep" commonssh "github.com/mitchellh/packer/common/ssh" - packerssh "github.com/mitchellh/packer/communicator/ssh" - "golang.org/x/crypto/ssh" + "github.com/mitchellh/packer/communicator/ssh" + gossh "golang.org/x/crypto/ssh" ) func CommHost(state multistep.StateBag) (string, error) { @@ -24,28 +24,26 @@ func CommHost(state multistep.StateBag) (string, error) { return ip, nil } -func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*ssh.ClientConfig, error) { - return func(state multistep.StateBag) (*ssh.ClientConfig, error) { - auth := []ssh.AuthMethod{ - ssh.Password(config.Comm.SSHPassword), - ssh.KeyboardInteractive( - packerssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)), +func SSHConfigFunc(config *SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { + return func(state multistep.StateBag) (*gossh.ClientConfig, error) { + auth := []gossh.AuthMethod{ + gossh.Password(config.Comm.SSHPassword), + gossh.KeyboardInteractive( + ssh.PasswordKeyboardInteractive(config.Comm.SSHPassword)), } - if config.SSHKeyPath != "" { + if config.Comm.SSHPrivateKey != "" { signer, err := commonssh.FileSigner(config.Comm.SSHPrivateKey) if err != nil { return nil, err } - auth = append(auth, ssh.PublicKeys(signer)) + auth = append(auth, gossh.PublicKeys(signer)) } - return &ssh.ClientConfig{ + return &gossh.ClientConfig{ User: config.Comm.SSHUsername, Auth: auth, }, nil } } - - diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index cf7e776a030..c7d4e640532 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -299,7 +299,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &communicator.StepConnect{ Config: &b.config.SSHConfig.Comm, Host: hypervcommon.CommHost, - SSHConfig: hypervcommon.SSHConfigFunc(b.config.SSHConfig), + SSHConfig: hypervcommon.SSHConfigFunc(&b.config.SSHConfig), }, // provision requires communicator to be setup diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 250722effda..b5e3fc1376b 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -77,7 +77,7 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string, generation string) error { var script = ` -param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]generation) +param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $path -ChildPath $vhdx New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation @@ -91,8 +91,8 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD func SetVirtualMachineCpu(vmName string, cpu string) error { var script = ` -param([string]$vmName, [int]cpu) -Set-VMProcessor -VMName $vmName –Count $cpu +param([string]$vmName, [int]$cpu) +Set-VMProcessor -VMName $vmName -Count $cpu ` var ps powershell.PowerShellCmd @@ -359,15 +359,15 @@ $vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running func Mac(vmName string) (string, error) { var script = ` -param([string]$vmName, [int]$addressIndex) +param([string]$vmName, [int]$adapterIndex) try { $adapter = Get-VMNetworkAdapter -VMName $vmName -ErrorAction SilentlyContinue - $mac = $adapter.MacAddress[$addressIndex] + $mac = $adapter[$adapterIndex].MacAddress if($mac -eq $null) { - return $false + return "" } } catch { - return $false + return "" } $mac ` @@ -385,10 +385,10 @@ try { $ip = Get-Vm | %{$_.NetworkAdapters} | ?{$_.MacAddress -eq $mac} | %{$_.IpAddresses[$addressIndex]} if($ip -eq $null) { - return $false + return "" } } catch { - return $false + return "" } $ip ` From ad03de18525821eab5aa78ca574cc6a0678b7f7b Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 24 Jun 2015 19:52:42 +0100 Subject: [PATCH 008/113] Remove devices after it has been shut down Attempt to stop vm, before deleting it --- builder/hyperv/common/artifact.go | 2 +- .../common/step_unmount_integration_services.go | 5 +++-- .../common/step_wait_for_install_to_complete.go | 5 ----- builder/hyperv/iso/builder.go | 12 ++++++------ powershell/hyperv/hyperv.go | 9 +++++++-- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/builder/hyperv/common/artifact.go b/builder/hyperv/common/artifact.go index de7f94ca72c..2baeb2d62bf 100644 --- a/builder/hyperv/common/artifact.go +++ b/builder/hyperv/common/artifact.go @@ -9,7 +9,7 @@ import ( ) // This is the common builder ID to all of these artifacts. -const BuilderId = "mitchellh.hyperv" +const BuilderId = "MSOpenTech.hyperv" // Artifact is the result of running the hyperv builder, namely a set // of files associated with the resulting machine. diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go index 7c40389fe0f..a905f2a8bf4 100644 --- a/builder/hyperv/common/step_unmount_integration_services.go +++ b/builder/hyperv/common/step_unmount_integration_services.go @@ -6,10 +6,10 @@ package common import ( "fmt" - "log" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" powershell "github.com/mitchellh/packer/powershell" + "log" ) type StepUnmountSecondaryDvdImages struct { @@ -35,6 +35,8 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. powershell := new(powershell.PowerShellCmd) script.WriteLine("param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)") + script.WriteLine("$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") + script.WriteLine("if (!$vmDvdDrive) {throw 'unable to find dvd drive'}") script.WriteLine("Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") err := powershell.Run(script.String(), vmName, controllerNumber, controllerLocation) if err != nil { @@ -44,7 +46,6 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. } } - return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go index efd8bd8b704..c4fafbef0a4 100644 --- a/builder/hyperv/common/step_wait_for_install_to_complete.go +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -26,11 +26,6 @@ func (s *StepWaitForPowerOff) Run(state multistep.StateBag) multistep.StepAction vmName := state.Get("vmName").(string) ui.Say("Waiting for vm to be powered down...") - // unless the person has a super fast disk, it should take at least 5 minutes - // for the install and post-install operations to take. Wait 5 minutes to - // avoid hammering on getting VM status via PowerShell - time.Sleep(time.Second * 300); - var script powershell.ScriptBuilder script.WriteLine("param([string]$vmName)") script.WriteLine("(Get-VM -Name $vmName).State -eq [Microsoft.HyperV.PowerShell.VMState]::Off") diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index c7d4e640532..b330cfc504e 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -305,12 +305,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // provision requires communicator to be setup &common.StepProvision{}, - // remove the integration services dvd drive - // after we power down - &hypervcommon.StepUnmountSecondaryDvdImages{}, - &hypervcommon.StepUnmountFloppyDrive{}, - &hypervcommon.StepUnmountDvdDrive{}, - &hypervcommon.StepShutdown{ Command: b.config.ShutdownCommand, Timeout: b.config.ShutdownTimeout, @@ -319,6 +313,12 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // wait for the vm to be powered off &hypervcommon.StepWaitForPowerOff{}, + // remove the integration services dvd drive + // after we power down + &hypervcommon.StepUnmountSecondaryDvdImages{}, + &hypervcommon.StepUnmountFloppyDrive{}, + &hypervcommon.StepUnmountDvdDrive{}, + &hypervcommon.StepExportVm{ OutputDir: b.config.OutputDir, }, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index b5e3fc1376b..63aa8cff655 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -40,10 +40,9 @@ Set-VMDvdDrive -VMName $vmName -Path $path } func UnmountDvdDrive(vmName string) error { - var script = ` param([string]$vmName) -Set-VMDvdDrive -VMName $vmName -Path $null +Get-VMDvdDrive -VMName $vmName | Set-VMDvdDrive -Path $null ` var ps powershell.PowerShellCmd @@ -104,6 +103,12 @@ func DeleteVirtualMachine(vmName string) error { var script = ` param([string]$vmName) + +$vm = Get-VM -Name $vmName +if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::OffCritical)) { + Stop-VM -VM $vm -TurnOff -Force +} + Remove-VM -Name $vmName -Force ` From edfcdf365e85ca5f265adac565ecffbc1ceed9fb Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sat, 27 Jun 2015 22:36:39 +0100 Subject: [PATCH 009/113] Support for boot_command Setup local http server Add compaction of hard drive GetHostAdapterIpAddress function added for hyperv renamed step step_start_vm to step_run to fall in ine with naming conventions of other builders --- builder/hyperv/common/driver.go | 10 +- builder/hyperv/common/driver_ps_4.go | 24 +- builder/hyperv/common/run_config.go | 34 ++- builder/hyperv/common/step_export_vm.go | 31 ++- builder/hyperv/common/step_http_server.go | 78 ++++++ builder/hyperv/common/step_run.go | 70 ++++++ builder/hyperv/common/step_start_vm.go | 48 ---- .../hyperv/common/step_type_boot_command.go | 211 ++++++++++++++++ builder/hyperv/iso/builder.go | 35 ++- powershell/hyperv/hyperv.go | 231 ++++++++++++++++-- 10 files changed, 680 insertions(+), 92 deletions(-) create mode 100644 builder/hyperv/common/step_http_server.go create mode 100644 builder/hyperv/common/step_run.go delete mode 100644 builder/hyperv/common/step_start_vm.go create mode 100644 builder/hyperv/common/step_type_boot_command.go diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 2c9f570b4c5..2d5044576c5 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -24,11 +24,17 @@ type Driver interface { // Verify checks to make sure that this driver should function // properly. If there is any indication the driver can't function, // this will return an error. - Verify() error - + Verify() error + // Finds the MAC address of the NIC nic0 Mac(string) (string, error) // Finds the IP address of a VM connected that uses DHCP by its MAC address IpAddress(string) (string, error) + + // Finds the IP address of a host adapter connected to switch + GetHostAdapterIpAddressForSwitch(string) (string, error) + + // Type scan codes to virtual keyboard of vm + TypeScanCodes(string, string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 4b014614615..b9371a8c0c4 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -41,12 +41,12 @@ func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) { // Start starts a VM specified by the name given. func (d *HypervPS4Driver) Start(vmName string) error { - return hyperv.Start(vmName) + return hyperv.StartVirtualMachine(vmName) } // Stop stops a VM specified by the name given. func (d *HypervPS4Driver) Stop(vmName string) error { - return hyperv.TurnOff(vmName) + return hyperv.StopVirtualMachine(vmName) } func (d *HypervPS4Driver) Verify() error { @@ -97,6 +97,26 @@ func (d *HypervPS4Driver) IpAddress(mac string) (string, error) { return res, err } +// Finds the IP address of a host adapter connected to switch +func (d *HypervPS4Driver) GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { + res, err := hyperv.GetHostAdapterIpAddressForSwitch(switchName) + + if err != nil { + return res, err + } + + if res == "" { + err := fmt.Errorf("%s", "No ip address.") + return res, err + } + return res, err +} + +// Type scan codes to virtual keyboard of vm +func (d *HypervPS4Driver) TypeScanCodes(vmName string, scanCodes string) error { + return hyperv.TypeScanCodes(vmName, scanCodes) +} + func (d *HypervPS4Driver) verifyPSVersion() error { log.Printf("Enter method: %s", "verifyPSVersion") diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go index c755cdafbcf..4b73ae532f1 100644 --- a/builder/hyperv/common/run_config.go +++ b/builder/hyperv/common/run_config.go @@ -1,16 +1,20 @@ package common import ( + "errors" "fmt" - "time" - "github.com/mitchellh/packer/template/interpolate" + "time" ) type RunConfig struct { Headless bool `mapstructure:"headless"` RawBootWait string `mapstructure:"boot_wait"` + HTTPDir string `mapstructure:"http_directory"` + HTTPPortMin uint `mapstructure:"http_port_min"` + HTTPPortMax uint `mapstructure:"http_port_max"` + BootWait time.Duration `` } @@ -19,11 +23,29 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { c.RawBootWait = "10s" } + if c.HTTPPortMin == 0 { + c.HTTPPortMin = 8000 + } + + if c.HTTPPortMax == 0 { + c.HTTPPortMax = 9000 + } + + var errs []error var err error - c.BootWait, err = time.ParseDuration(c.RawBootWait) - if err != nil { - return []error{fmt.Errorf("Failed parsing boot_wait: %s", err)} + + if c.RawBootWait != "" { + c.BootWait, err = time.ParseDuration(c.RawBootWait) + if err != nil { + errs = append( + errs, fmt.Errorf("Failed parsing boot_wait: %s", err)) + } + } + + if c.HTTPPortMin > c.HTTPPortMax { + errs = append(errs, + errors.New("http_port_min must be less than http_port_max")) } - return nil + return errs } diff --git a/builder/hyperv/common/step_export_vm.go b/builder/hyperv/common/step_export_vm.go index 95076fd1448..d226c25d1aa 100644 --- a/builder/hyperv/common/step_export_vm.go +++ b/builder/hyperv/common/step_export_vm.go @@ -6,20 +6,21 @@ package common import ( "fmt" - "path/filepath" - "io/ioutil" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/powershell/hyperv" + "io/ioutil" + "path/filepath" ) -const( +const ( vhdDir string = "Virtual Hard Disks" - vmDir string = "Virtual Machines" + vmDir string = "Virtual Machines" ) type StepExportVm struct { - OutputDir string + OutputDir string + SkipCompaction bool } func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { @@ -29,12 +30,12 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { var errorMsg string vmName := state.Get("vmName").(string) - tmpPath := state.Get("packerTempDir").(string) + tmpPath := state.Get("packerTempDir").(string) outputPath := s.OutputDir // create temp path to export vm errorMsg = "Error creating temp export path: %s" - vmExportPath , err := ioutil.TempDir(tmpPath, "export") + vmExportPath, err := ioutil.TempDir(tmpPath, "export") if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -54,7 +55,21 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { } // copy to output dir - expPath := filepath.Join(vmExportPath,vmName) + expPath := filepath.Join(vmExportPath, vmName) + + if s.SkipCompaction { + ui.Say("Skipping disk compaction...") + } else { + ui.Say("Compacting disks...") + err = hyperv.CompactDisks(expPath, vhdDir) + if err != nil { + errorMsg = "Error compacting disks: %s" + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } ui.Say("Coping to output dir...") err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) diff --git a/builder/hyperv/common/step_http_server.go b/builder/hyperv/common/step_http_server.go new file mode 100644 index 00000000000..55874992e85 --- /dev/null +++ b/builder/hyperv/common/step_http_server.go @@ -0,0 +1,78 @@ +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "math/rand" + "net" + "net/http" +) + +// This step creates and runs the HTTP server that is serving files from the +// directory specified by the 'http_directory` configuration parameter in the +// template. +// +// Uses: +// ui packer.Ui +// +// Produces: +// http_port int - The port the HTTP server started on. +type StepHTTPServer struct { + HTTPDir string + HTTPPortMin uint + HTTPPortMax uint + + l net.Listener +} + +func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + var httpPort uint = 0 + if s.HTTPDir == "" { + state.Put("http_port", httpPort) + return multistep.ActionContinue + } + + // Find an available TCP port for our HTTP server + var httpAddr string + portRange := int(s.HTTPPortMax - s.HTTPPortMin) + for { + var err error + var offset uint = 0 + + if portRange > 0 { + // Intn will panic if portRange == 0, so we do a check. + offset = uint(rand.Intn(portRange)) + } + + httpPort = offset + s.HTTPPortMin + httpAddr = fmt.Sprintf("0.0.0.0:%d", httpPort) + log.Printf("Trying port: %d", httpPort) + s.l, err = net.Listen("tcp", httpAddr) + if err == nil { + break + } + } + + ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort)) + + // Start the HTTP server and run it in the background + fileServer := http.FileServer(http.Dir(s.HTTPDir)) + server := &http.Server{Addr: httpAddr, Handler: fileServer} + go server.Serve(s.l) + + // Save the address into the state so it can be accessed in the future + state.Put("http_port", httpPort) + + return multistep.ActionContinue +} + +func (s *StepHTTPServer) Cleanup(multistep.StateBag) { + if s.l != nil { + // Close the listener so that the HTTP server stops + s.l.Close() + } +} diff --git a/builder/hyperv/common/step_run.go b/builder/hyperv/common/step_run.go new file mode 100644 index 00000000000..c0271469202 --- /dev/null +++ b/builder/hyperv/common/step_run.go @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "time" +) + +type StepRun struct { + BootWait time.Duration + Headless bool + + vmName string +} + +func (s *StepRun) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Starting the virtual machine...") + + err := driver.Start(vmName) + if err != nil { + err := fmt.Errorf("Error starting vm: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + s.vmName = vmName + + if int64(s.BootWait) > 0 { + ui.Say(fmt.Sprintf("Waiting %s for boot...", s.BootWait)) + wait := time.After(s.BootWait) + WAITLOOP: + for { + select { + case <-wait: + break WAITLOOP + case <-time.After(1 * time.Second): + if _, ok := state.GetOk(multistep.StateCancelled); ok { + return multistep.ActionHalt + } + } + } + } + + return multistep.ActionContinue +} + +func (s *StepRun) Cleanup(state multistep.StateBag) { + if s.vmName == "" { + return + } + + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + + if running, _ := driver.IsRunning(s.vmName); running { + if err := driver.Stop(s.vmName); err != nil { + ui.Error(fmt.Sprintf("Error shutting down VM: %s", err)) + } + } +} diff --git a/builder/hyperv/common/step_start_vm.go b/builder/hyperv/common/step_start_vm.go deleted file mode 100644 index af1fcda5d21..00000000000 --- a/builder/hyperv/common/step_start_vm.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "time" - "github.com/mitchellh/packer/powershell/hyperv" -) - -type StepStartVm struct { - Reason string - StartUpDelay int -} - -func (s *StepStartVm) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - errorMsg := "Error starting vm: %s" - vmName := state.Get("vmName").(string) - - ui.Say("Starting vm for " + s.Reason + "...") - - err := hyperv.StartVirtualMachine(vmName) - if err != nil { - err := fmt.Errorf(errorMsg, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - if s.StartUpDelay != 0 { - //sleepTime := s.StartUpDelay * time.Second - sleepTime := 60 * time.Second - - ui.Say(fmt.Sprintf(" Waiting %v for vm to start...", sleepTime)) - time.Sleep(sleepTime); - } - - return multistep.ActionContinue -} - -func (s *StepStartVm) Cleanup(state multistep.StateBag) { -} diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go new file mode 100644 index 00000000000..8c06982bc1a --- /dev/null +++ b/builder/hyperv/common/step_type_boot_command.go @@ -0,0 +1,211 @@ +package common + +import ( + "fmt" + "log" + "strings" + "unicode" + "unicode/utf8" + + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "github.com/mitchellh/packer/template/interpolate" +) + +type bootCommandTemplateData struct { + HTTPIP string + HTTPPort uint + Name string +} + +// This step "types" the boot command into the VM via the prltype script, built on the +// Parallels Virtualization SDK - Python API. +// +// Uses: +// driver Driver +// http_port int +// ui packer.Ui +// vmName string +// +// Produces: +// +type StepTypeBootCommand struct { + BootCommand []string + SwitchName string + VMName string + Ctx interpolate.Context +} + +func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction { + httpPort := state.Get("http_port").(uint) + ui := state.Get("ui").(packer.Ui) + + driver := state.Get("driver").(Driver) + + hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName) + + if err != nil { + err := fmt.Errorf("Error getting host adapter ip address: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Host IP for the HyperV machine: %s", hostIp)) + + s.Ctx.Data = &bootCommandTemplateData{ + hostIp, + httpPort, + s.VMName, + } + + ui.Say("Typing the boot command...") + scanCodesToSend := []string{} + + for _, command := range s.BootCommand { + command, err := interpolate.Render(command, &s.Ctx) + + if err != nil { + err := fmt.Errorf("Error preparing boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + scanCodesToSend = append(scanCodesToSend, scancodes(command)...) + } + + scanCodesToSendString := strings.Join(scanCodesToSend, " ") + + if err := driver.TypeScanCodes(s.VMName, scanCodesToSendString); err != nil { + err := fmt.Errorf("Error sending boot command: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + return multistep.ActionContinue +} + +func (*StepTypeBootCommand) Cleanup(multistep.StateBag) {} + +func scancodes(message string) []string { + // Scancodes reference: http://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // + // Scancodes represent raw keyboard output and are fed to the VM by the + // VBoxManage controlvm keyboardputscancode program. + // + // Scancodes are recorded here in pairs. The first entry represents + // the key press and the second entry represents the key release and is + // derived from the first by the addition of 0x80. + special := make(map[string][]string) + special[""] = []string{"0e", "8e"} + special[""] = []string{"53", "d3"} + special[""] = []string{"1c", "9c"} + special[""] = []string{"01", "81"} + special[""] = []string{"3b", "bb"} + special[""] = []string{"3c", "bc"} + special[""] = []string{"3d", "bd"} + special[""] = []string{"3e", "be"} + special[""] = []string{"3f", "bf"} + special[""] = []string{"40", "c0"} + special[""] = []string{"41", "c1"} + special[""] = []string{"42", "c2"} + special[""] = []string{"43", "c3"} + special[""] = []string{"44", "c4"} + special[""] = []string{"1c", "9c"} + special[""] = []string{"0f", "8f"} + special[""] = []string{"48", "c8"} + special[""] = []string{"50", "d0"} + special[""] = []string{"4b", "cb"} + special[""] = []string{"4d", "cd"} + special[""] = []string{"39", "b9"} + special[""] = []string{"52", "d2"} + special[""] = []string{"47", "c7"} + special[""] = []string{"4f", "cf"} + special[""] = []string{"49", "c9"} + special[""] = []string{"51", "d1"} + + shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" + + scancodeIndex := make(map[string]uint) + scancodeIndex["1234567890-="] = 0x02 + scancodeIndex["!@#$%^&*()_+"] = 0x02 + scancodeIndex["qwertyuiop[]"] = 0x10 + scancodeIndex["QWERTYUIOP{}"] = 0x10 + scancodeIndex["asdfghjkl;'`"] = 0x1e + scancodeIndex[`ASDFGHJKL:"~`] = 0x1e + scancodeIndex[`\zxcvbnm,./`] = 0x2b + scancodeIndex["|ZXCVBNM<>?"] = 0x2b + scancodeIndex[" "] = 0x39 + + scancodeMap := make(map[rune]uint) + for chars, start := range scancodeIndex { + var i uint = 0 + for len(chars) > 0 { + r, size := utf8.DecodeRuneInString(chars) + chars = chars[size:] + scancodeMap[r] = start + i + i += 1 + } + } + + result := make([]string, 0, len(message)*2) + for len(message) > 0 { + var scancode []string + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 1 second at this point.") + scancode = []string{"wait"} + message = message[len(""):] + } + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 5 seconds at this point.") + scancode = []string{"wait5"} + message = message[len(""):] + } + + if strings.HasPrefix(message, "") { + log.Printf("Special code found, will sleep 10 seconds at this point.") + scancode = []string{"wait10"} + message = message[len(""):] + } + + if scancode == nil { + for specialCode, specialValue := range special { + if strings.HasPrefix(message, specialCode) { + log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) + scancode = specialValue + message = message[len(specialCode):] + break + } + } + } + + if scancode == nil { + r, size := utf8.DecodeRuneInString(message) + message = message[size:] + scancodeInt := scancodeMap[r] + keyShift := unicode.IsUpper(r) || strings.ContainsRune(shiftedChars, r) + + scancode = make([]string, 0, 4) + if keyShift { + scancode = append(scancode, "2a") + } + + scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt)) + + if keyShift { + scancode = append(scancode, "aa") + } + + scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) + log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) + } + + result = append(result, scancode...) + } + + return result +} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index b330cfc504e..89f98ef83b8 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -48,8 +48,8 @@ type Config struct { hypervcommon.FloppyConfig `mapstructure:",squash"` hypervcommon.OutputConfig `mapstructure:",squash"` hypervcommon.SSHConfig `mapstructure:",squash"` - hypervcommon.ShutdownConfig `mapstructure:",squash"` hypervcommon.RunConfig `mapstructure:",squash"` + hypervcommon.ShutdownConfig `mapstructure:",squash"` // The size, in megabytes, of the hard disk to create for the VM. // By default, this is 130048 (about 127 GB). @@ -95,9 +95,10 @@ type Config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - SwitchName string `mapstructure:"switch_name"` - Cpu uint `mapstructure:"cpu"` - Generation uint `mapstructure:"generation"` + BootCommand []string `mapstructure:"boot_command"` + SwitchName string `mapstructure:"switch_name"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` Communicator string `mapstructure:"communicator"` @@ -107,6 +108,8 @@ type Config struct { SSHWaitTimeout time.Duration + SkipCompaction bool `mapstructure:"skip_compaction"` + ctx interpolate.Context } @@ -115,7 +118,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { err := config.Decode(&b.config, &config.DecodeOpts{ Interpolate: true, InterpolateFilter: &interpolate.RenderFilter{ - Exclude: []string{}, + Exclude: []string{ + "boot_command", + }, }, }, raws...) if err != nil { @@ -271,6 +276,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, + &hypervcommon.StepHTTPServer{ + HTTPDir: b.config.HTTPDir, + HTTPPortMin: b.config.HTTPPortMin, + HTTPPortMax: b.config.HTTPPortMax, + }, &hypervcommon.StepCreateSwitch{ SwitchName: b.config.SwitchName, }, @@ -291,8 +301,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountSecondaryDvdImages{}, - &hypervcommon.StepStartVm{ - Reason: "OS installation", + &hypervcommon.StepRun{ + BootWait: b.config.BootWait, + Headless: b.config.Headless, + }, + + &hypervcommon.StepTypeBootCommand{ + BootCommand: b.config.BootCommand, + SwitchName: b.config.SwitchName, + VMName: b.config.VMName, + Ctx: b.config.ctx, }, // configure the communicator ssh, winrm @@ -320,7 +338,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepUnmountDvdDrive{}, &hypervcommon.StepExportVm{ - OutputDir: b.config.OutputDir, + OutputDir: b.config.OutputDir, + SkipCompaction: b.config.SkipCompaction, }, // the clean up actions for each step will be executed reverse order diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 63aa8cff655..1a2c5956a1f 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -5,6 +5,29 @@ import ( "strings" ) +func GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { + var script = ` +param([string]$switchName, [int]$addressIndex) + +$HostVMAdapter = Get-VMNetworkAdapter -ManagementOS -SwitchName $switchName +if ($HostVMAdapter){ + $HostNetAdapter = Get-NetAdapter | ?{ $_.DeviceID -eq $HostVMAdapter.DeviceId } + if ($HostNetAdapter){ + $HostNetAdapterConfiguration = @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE' AND InterfaceIndex=$($HostNetAdapter.ifIndex)") + if ($HostNetAdapterConfiguration){ + return $HostNetAdapterConfiguration.IpAddress[$addressIndex] + } + } +} +return $false +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, switchName, "0") + + return cmdOut, err +} + func GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) { var script = ` @@ -106,7 +129,7 @@ param([string]$vmName) $vm = Get-VM -Name $vmName if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::OffCritical)) { - Stop-VM -VM $vm -TurnOff -Force + Stop-VM -VM $vm -TurnOff -Force -Confirm:$false } Remove-VM -Name $vmName -Force @@ -129,6 +152,19 @@ Export-VM -Name $vmName -Path $path return err } +func CompactDisks(expPath string, vhdDir string) error { + var script = ` +param([string]$srcPath, [string]$vhdDirName) +Get-ChildItem "$srcPath/$vhdDirName" -Filter *.vhd* | %{ + Optimize-VHD -Path $_.FullName -Mode Full +} +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, expPath, vhdDir) + return err +} + func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error { var script = ` @@ -180,7 +216,10 @@ func StartVirtualMachine(vmName string) error { var script = ` param([string]$vmName) -Start-VM -Name $vmName +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { + Start-VM -Name $vmName +} ` var ps powershell.PowerShellCmd @@ -206,7 +245,7 @@ func StopVirtualMachine(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -VM $vm + Stop-VM -VM $vm -Confirm:$false } ` @@ -297,7 +336,7 @@ foreach ($adapter in $adapters) { } if($switch -ne $null) { - Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch + Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -VMSwitch $switch } else { Write-Error 'No internet adapters found' } @@ -327,7 +366,7 @@ func ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName strin var script = ` param([string]$vmName,[string]$switchName) -Get-VMNetworkAdapter –VMName $vmName | Connect-VMNetworkAdapter –SwitchName $switchName +Get-VMNetworkAdapter -VMName $vmName | Connect-VMNetworkAdapter -SwitchName $switchName ` var ps powershell.PowerShellCmd @@ -404,13 +443,13 @@ $ip return cmdOut, err } -func Start(vmName string) error { +func TurnOff(vmName string) error { var script = ` param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue -if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { - Start-VM –Name $vmName +if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { + Stop-VM -Name $vmName -TurnOff -Confirm:$false } ` @@ -419,13 +458,13 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { return err } -func TurnOff(vmName string) error { +func ShutDown(vmName string) error { var script = ` param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -TurnOff + Stop-VM -Name $vmName -Confirm:$false } ` @@ -434,17 +473,173 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { return err } -func ShutDown(vmName string) error { - +func TypeScanCodes(vmName string, scanCodes string) error { var script = ` -param([string]$vmName) -$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue -if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM –Name $vmName -} +param([string]$vmName, [string]$scanCodes) + #Requires -Version 3 + #Requires -RunAsAdministrator + + function Get-VMConsole + { + [CmdletBinding()] + param ( + [Parameter(Mandatory)] + [string] $VMName + ) + + $ErrorActionPreference = "Stop" + + $vm = Get-CimInstance -ComputerName localhost -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1 + if ($vm -eq $null){ + Write-Error ("VirtualMachine({0}) is not found!" -f $VMName) + } + + $vmKeyboard = $vm | Get-CimAssociatedInstance -ResultClassName "Msvm_Keyboard" -ErrorAction Ignore -Verbose:$false + if ($vmKeyboard -eq $null){ + Write-Error ("VirtualMachine({0}) keyboard class is not found!" -f $VMName) + } + + #TODO: It may be better using New-Module -AsCustomObject to return console object? + + #Console object to return + $console = [pscustomobject] @{ + Msvm_ComputerSystem = $vm + Msvm_Keyboard = $vmKeyboard + } + + #Need to import assembly to use System.Windows.Input.Key + Add-Type -AssemblyName WindowsBase + + #region Add Console Members + $console | Add-Member -MemberType ScriptMethod -Name TypeText -Value { + [OutputType([bool])] + param ( + [ValidateNotNullOrEmpty()] + [Parameter(Mandatory)] + [string] $AsciiText + ) + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeText" -Arguments @{ asciiText = $AsciiText } + return (0 -eq $result.ReturnValue) + } + + #Define method:TypeCtrlAltDel + $console | Add-Member -MemberType ScriptMethod -Name TypeCtrlAltDel -Value { + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeCtrlAltDel" + return (0 -eq $result.ReturnValue) + } + + #Define method:TypeKey + $console | Add-Member -MemberType ScriptMethod -Name TypeKey -Value { + [OutputType([bool])] + param ( + [Parameter(Mandatory)] + [Windows.Input.Key] $Key, + [Windows.Input.ModifierKeys] $ModifierKey = [Windows.Input.ModifierKeys]::None + ) + + $keyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey($Key) + + switch ($ModifierKey) + { + ([Windows.Input.ModifierKeys]::Control){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftCtrl)} + ([Windows.Input.ModifierKeys]::Alt){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftAlt)} + ([Windows.Input.ModifierKeys]::Shift){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LeftShift)} + ([Windows.Input.ModifierKeys]::Windows){ $modifierKeyCode = [Windows.Input.KeyInterop]::VirtualKeyFromKey([Windows.Input.Key]::LWin)} + } + + if ($ModifierKey -eq [Windows.Input.ModifierKeys]::None) + { + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode } + } + else + { + $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "PressKey" -Arguments @{ keyCode = $modifierKeyCode } + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeKey" -Arguments @{ keyCode = $keyCode } + $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "ReleaseKey" -Arguments @{ keyCode = $modifierKeyCode } + } + $result = return (0 -eq $result.ReturnValue) + } + + #Define method:Scancodes + $console | Add-Member -MemberType ScriptMethod -Name TypeScancodes -Value { + [OutputType([bool])] + param ( + [Parameter(Mandatory)] + [byte[]] $ScanCodes + ) + $result = $this.Msvm_Keyboard | Invoke-CimMethod -MethodName "TypeScancodes" -Arguments @{ ScanCodes = $ScanCodes } + return (0 -eq $result.ReturnValue) + } + + #Define method:ExecCommand + $console | Add-Member -MemberType ScriptMethod -Name ExecCommand -Value { + param ( + [Parameter(Mandatory)] + [string] $Command + ) + if ([String]::IsNullOrEmpty($Command)){ + return + } + + $console.TypeText($Command) > $null + $console.TypeKey([Windows.Input.Key]::Enter) > $null + #sleep -Milliseconds 100 + } + + #Define method:Dispose + $console | Add-Member -MemberType ScriptMethod -Name Dispose -Value { + $this.Msvm_ComputerSystem.Dispose() + $this.Msvm_Keyboard.Dispose() + } + + + #endregion + + return $console + } + + $vmConsole = Get-VMConsole -VMName $vmName + $scanCodesToSend = '' + $scanCodes.Split(' ') | %{ + $scanCode = $_ + + if ($scanCode.StartsWith('wait')){ + $timeToWait = $scanCode.Substring(4) + if (!$timeToWait){ + $timeToWait = "10" + } + + Start-Sleep -s $timeToWait + + if ($scanCodesToSend){ + $scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"}) + + $scanCodesToSendByteArray | %{ + $vmConsole.TypeScancodes($_) + } + } + + $scanCodesToSend = '' + } else { + if ($scanCodesToSend){ + $scanCodesToSend = "$scanCodesToSend $scanCode" + } else { + $scanCodesToSend = "$scanCode" + } + } + } + if ($scanCodesToSend){ + $scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"}) + + $scanCodesToSendByteArray | %{ + $vmConsole.TypeScancodes($_) + } + } + + ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName) + err := ps.Run(script, vmName, scanCodes) return err } From cff52c738a9ebbb97432e08316339e238ce65e20 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 29 Jun 2015 21:18:25 +0100 Subject: [PATCH 010/113] With generation 2 machine by default a dvd drive is not created. So create a dvd drive for os if it does not exist. Allow secure boot mode to be configured from config. --- builder/hyperv/common/step_create_vm.go | 14 ++++++- builder/hyperv/common/step_mount_dvddrive.go | 33 ++++++++++++++-- builder/hyperv/iso/builder.go | 22 ++++++----- powershell/hyperv/hyperv.go | 40 ++++++++++++++++---- 4 files changed, 88 insertions(+), 21 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 1ea4530d803..6418dbea06e 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -23,6 +23,7 @@ type StepCreateVM struct { DiskSize uint Generation uint Cpu uint + EnabeSecureBoot bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { @@ -40,6 +41,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { switchName := s.SwitchName generation := strconv.FormatInt(int64(s.Generation), 10) cpu := strconv.FormatInt(int64(s.Cpu), 10) + enabeSecureBoot := s.EnabeSecureBoot err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, generation) if err != nil { @@ -48,7 +50,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } - + err = hyperv.SetVirtualMachineCpu(s.VMName, cpu) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) @@ -56,6 +58,16 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } + + if generation == "2" { + err = hyperv.SetSecureBoot(s.VMName, enabeSecureBoot) + if err != nil { + err := fmt.Errorf("Error setting secure boot: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index d5e9b4ef35b..8077dd7d1b2 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -8,13 +8,13 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + powershell "github.com/mitchellh/packer/powershell" "github.com/mitchellh/packer/powershell/hyperv" ) - type StepMountDvdDrive struct { RawSingleISOUrl string - path string + path string } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { @@ -25,9 +25,36 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { vmName := state.Get("vmName").(string) isoPath := s.RawSingleISOUrl + // Check that there is a virtual dvd drive + var script powershell.ScriptBuilder + powershell := new(powershell.PowerShellCmd) + + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber") + controllerNumber, err := powershell.Output(script.String(), vmName) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + if controllerNumber == "" { + // Add a virtual dvd drive as there is none + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("Add-VMDvdDrive -VMName $vmName") + err = powershell.Run(script.String(), vmName) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + ui.Say("Mounting dvd drive...") - err := hyperv.MountDvdDrive(vmName, isoPath) + err = hyperv.MountDvdDrive(vmName, isoPath) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 89f98ef83b8..010b06cc77a 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -95,10 +95,11 @@ type Config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - BootCommand []string `mapstructure:"boot_command"` - SwitchName string `mapstructure:"switch_name"` - Cpu uint `mapstructure:"cpu"` - Generation uint `mapstructure:"generation"` + BootCommand []string `mapstructure:"boot_command"` + SwitchName string `mapstructure:"switch_name"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` + EnableSecureBoot bool `mapstructure:"enable_secure_boot"` Communicator string `mapstructure:"communicator"` @@ -285,12 +286,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchName: b.config.SwitchName, }, &hypervcommon.StepCreateVM{ - VMName: b.config.VMName, - SwitchName: b.config.SwitchName, - RamSizeMB: b.config.RamSizeMB, - DiskSize: b.config.DiskSize, - Generation: b.config.Generation, - Cpu: b.config.Cpu, + VMName: b.config.VMName, + SwitchName: b.config.SwitchName, + RamSizeMB: b.config.RamSizeMB, + DiskSize: b.config.DiskSize, + Generation: b.config.Generation, + Cpu: b.config.Cpu, + EnabeSecureBoot: b.config.EnableSecureBoot, }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 1a2c5956a1f..f1581b4d9d5 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -98,16 +98,27 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string, generation string) error { - var script = ` + if generation == "2" { + var script = ` param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation) $vhdx = $vmName + '.vhdx' $vhdPath = Join-Path -Path $path -ChildPath $vhdx New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation ` - - var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, ram, diskSize, switchName, generation) - return err + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path, ram, diskSize, switchName, generation) + return err + } else { + var script = ` +param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName) +$vhdx = $vmName + '.vhdx' +$vhdPath = Join-Path -Path $path -ChildPath $vhdx +New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path, ram, diskSize, switchName) + return err + } } func SetVirtualMachineCpu(vmName string, cpu string) error { @@ -122,6 +133,23 @@ Set-VMProcessor -VMName $vmName -Count $cpu return err } +func SetSecureBoot(vmName string, enable bool) error { + var script = ` +param([string]$vmName, $enableSecureBoot) +Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBoot +` + + var ps powershell.PowerShellCmd + + enableSecureBoot := "Off" + if enable { + enableSecureBoot = "On" + } + + err := ps.Run(script, vmName, enableSecureBoot) + return err +} + func DeleteVirtualMachine(vmName string) error { var script = ` @@ -635,8 +663,6 @@ param([string]$vmName, [string]$scanCodes) $vmConsole.TypeScancodes($_) } } - - ` var ps powershell.PowerShellCmd From 3d17d50e65fb6ae4de090c3e1f81459315a66579 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 29 Jun 2015 22:06:28 +0100 Subject: [PATCH 011/113] Set the dvd to the first boot device --- builder/hyperv/common/step_mount_dvddrive.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 8077dd7d1b2..56856566ffc 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -44,6 +44,8 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { script.Reset() script.WriteLine("param([string]$vmName)") script.WriteLine("Add-VMDvdDrive -VMName $vmName") + script.WriteLine("$dvdDrive = Get-VMDvdDrive -VMName $vmName | Select-Object -first 1") + script.WriteLine("Set-VMFirmware -VMName $vmName -FirstBootDevice $dvdDrive") err = powershell.Run(script.String(), vmName) if err != nil { state.Put("error", err) From 2b437d9b9a1c6062102cd8b90888ab6e9ead6329 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 30 Jun 2015 16:39:03 +0100 Subject: [PATCH 012/113] When redirecting local ports to hyper visor ports we need to configure WinRM ports as well as SSH ports. --- builder/qemu/builder.go | 3 ++- builder/qemu/ssh.go | 7 ++++++- builder/virtualbox/common/ssh.go | 5 +++++ builder/virtualbox/iso/builder.go | 1 + helper/communicator/step_connect.go | 2 ++ helper/communicator/step_connect_winrm.go | 9 +++++++++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/builder/qemu/builder.go b/builder/qemu/builder.go index 9df90898928..9e957935e6d 100644 --- a/builder/qemu/builder.go +++ b/builder/qemu/builder.go @@ -396,7 +396,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Config: &b.config.Comm, Host: commHost, SSHConfig: sshConfig, - SSHPort: commPort, + SSHPort: SSHPort, + WinRMPort: WinRMPort, }, new(common.StepProvision), new(stepShutdown), diff --git a/builder/qemu/ssh.go b/builder/qemu/ssh.go index 498d3fbe9c1..75264652daa 100644 --- a/builder/qemu/ssh.go +++ b/builder/qemu/ssh.go @@ -11,11 +11,16 @@ func commHost(state multistep.StateBag) (string, error) { return "127.0.0.1", nil } -func commPort(state multistep.StateBag) (int, error) { +func SSHPort(state multistep.StateBag) (int, error) { sshHostPort := state.Get("sshHostPort").(uint) return int(sshHostPort), nil } +func WinRMPort(state multistep.StateBag) (int, error) { + winRMHostPort := state.Get("sshHostPort").(uint) + return int(winRMHostPort), nil +} + func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { config := state.Get("config").(*Config) diff --git a/builder/virtualbox/common/ssh.go b/builder/virtualbox/common/ssh.go index 2584528dd45..a924ba6fa51 100644 --- a/builder/virtualbox/common/ssh.go +++ b/builder/virtualbox/common/ssh.go @@ -16,6 +16,11 @@ func SSHPort(state multistep.StateBag) (int, error) { return int(sshHostPort), nil } +func WinRMPort(state multistep.StateBag) (int, error) { + winRMHostPort := state.Get("sshHostPort").(uint) + return int(winRMHostPort), nil +} + func SSHConfigFunc(config SSHConfig) func(multistep.StateBag) (*gossh.ClientConfig, error) { return func(state multistep.StateBag) (*gossh.ClientConfig, error) { auth := []gossh.AuthMethod{ diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index dae107170aa..e7a131b5ea4 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -277,6 +277,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Host: vboxcommon.CommHost, SSHConfig: vboxcommon.SSHConfigFunc(b.config.SSHConfig), SSHPort: vboxcommon.SSHPort, + WinRMPort: vboxcommon.WinRMPort, }, &vboxcommon.StepUploadVersion{ Path: b.config.VBoxVersionFile, diff --git a/helper/communicator/step_connect.go b/helper/communicator/step_connect.go index 0c1522330ac..fa1963b6117 100644 --- a/helper/communicator/step_connect.go +++ b/helper/communicator/step_connect.go @@ -31,6 +31,7 @@ type StepConnect struct { // WinRMConfig should return the default configuration for // connecting via WinRM. WinRMConfig func(multistep.StateBag) (*WinRMConfig, error) + WinRMPort func(multistep.StateBag) (int, error) // CustomConnect can be set to have custom connectors for specific // types. These take highest precedence so you can also override @@ -53,6 +54,7 @@ func (s *StepConnect) Run(state multistep.StateBag) multistep.StepAction { Config: s.Config, Host: s.Host, WinRMConfig: s.WinRMConfig, + WinRMPort: s.WinRMPort, }, } for k, v := range s.CustomConnect { diff --git a/helper/communicator/step_connect_winrm.go b/helper/communicator/step_connect_winrm.go index bdd0c1499f2..72a2de41470 100644 --- a/helper/communicator/step_connect_winrm.go +++ b/helper/communicator/step_connect_winrm.go @@ -25,6 +25,7 @@ type StepConnectWinRM struct { Config *Config Host func(multistep.StateBag) (string, error) WinRMConfig func(multistep.StateBag) (*WinRMConfig, error) + WinRMPort func(multistep.StateBag) (int, error) } func (s *StepConnectWinRM) Run(state multistep.StateBag) multistep.StepAction { @@ -95,7 +96,15 @@ func (s *StepConnectWinRM) waitForWinRM(state multistep.StateBag, cancel <-chan log.Printf("[DEBUG] Error getting WinRM host: %s", err) continue } + port := s.Config.WinRMPort + if s.WinRMPort != nil { + port, err = s.WinRMPort(state) + if err != nil { + log.Printf("[DEBUG] Error getting WinRM port: %s", err) + continue + } + } user := s.Config.WinRMUser password := s.Config.WinRMPassword From 832e1a6c747490528ce55a789a817919f40331a3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 30 Jun 2015 17:59:08 +0100 Subject: [PATCH 013/113] We should be using uint for sshport when saving in statebag --- builder/virtualbox/common/step_forward_ssh.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builder/virtualbox/common/step_forward_ssh.go b/builder/virtualbox/common/step_forward_ssh.go index 86376c834ed..c0d877f42e8 100644 --- a/builder/virtualbox/common/step_forward_ssh.go +++ b/builder/virtualbox/common/step_forward_ssh.go @@ -33,23 +33,23 @@ func (s *StepForwardSSH) Run(state multistep.StateBag) multistep.StepAction { vmName := state.Get("vmName").(string) guestPort := s.CommConfig.Port() - sshHostPort := guestPort + sshHostPort := uint(guestPort) if !s.SkipNatMapping { log.Printf("Looking for available SSH port between %d and %d", s.HostPortMin, s.HostPortMax) - offset := 0 + offset := uint(0) portRange := int(s.HostPortMax - s.HostPortMin) if portRange > 0 { // Have to check if > 0 to avoid a panic - offset = rand.Intn(portRange) + offset = uint(rand.Intn(portRange)) } for { - sshHostPort = offset + int(s.HostPortMin) - if sshHostPort >= int(s.HostPortMax) { + sshHostPort = offset + s.HostPortMin + if sshHostPort >= s.HostPortMax { offset = 0 - sshHostPort = int(s.HostPortMin) + sshHostPort = s.HostPortMin } log.Printf("Trying port: %d", sshHostPort) l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort)) From e730b401035b1bcce6b533b9790b1d4e402e893b Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 1 Jul 2015 13:44:54 +0100 Subject: [PATCH 014/113] Apply template rendering to VMName --- .../common/step_type_boot_command.go | 2 +- builder/virtualbox/iso/builder.go | 5 ++- builder/virtualbox/iso/step_create_vm.go | 32 ++++++++++++------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/builder/virtualbox/common/step_type_boot_command.go b/builder/virtualbox/common/step_type_boot_command.go index 4d0d3cfff5f..3871e4f27da 100644 --- a/builder/virtualbox/common/step_type_boot_command.go +++ b/builder/virtualbox/common/step_type_boot_command.go @@ -46,7 +46,7 @@ func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction s.Ctx.Data = &bootCommandTemplateData{ "10.0.2.2", httpPort, - s.VMName, + vmName, } ui.Say("Typing the boot command...") diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index e7a131b5ea4..9356c400c63 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -246,7 +246,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe HTTPPortMax: b.config.HTTPPortMax, }, new(vboxcommon.StepSuppressMessages), - new(stepCreateVM), + &StepCreateVM{ + VMName : b.config.VMName, + Ctx : b.config.ctx, + }, new(stepCreateDisk), new(stepAttachISO), &vboxcommon.StepAttachGuestAdditions{ diff --git a/builder/virtualbox/iso/step_create_vm.go b/builder/virtualbox/iso/step_create_vm.go index adee364113c..c7081d8c799 100644 --- a/builder/virtualbox/iso/step_create_vm.go +++ b/builder/virtualbox/iso/step_create_vm.go @@ -6,23 +6,31 @@ import ( vboxcommon "github.com/mitchellh/packer/builder/virtualbox/common" "github.com/mitchellh/packer/packer" "time" + "github.com/mitchellh/packer/template/interpolate" ) // This step creates the actual virtual machine. // // Produces: -// vmName string - The name of the VM -type stepCreateVM struct { - vmName string +// VMName string - The name of the VM +type StepCreateVM struct { + VMName string + Ctx interpolate.Context } -func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { config := state.Get("config").(*Config) driver := state.Get("driver").(vboxcommon.Driver) ui := state.Get("ui").(packer.Ui) - name := config.VMName - + name, err := interpolate.Render(s.VMName, &s.Ctx) + if err != nil { + err := fmt.Errorf("Error preparing vm name: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + commands := make([][]string, 4) commands[0] = []string{ "createvm", "--name", name, @@ -46,19 +54,19 @@ func (s *stepCreateVM) Run(state multistep.StateBag) multistep.StepAction { } // Set the VM name property on the first command - if s.vmName == "" { - s.vmName = name + if s.VMName == "" { + s.VMName = name } } // Set the final name in the state bag so others can use it - state.Put("vmName", s.vmName) + state.Put("vmName", name) return multistep.ActionContinue } -func (s *stepCreateVM) Cleanup(state multistep.StateBag) { - if s.vmName == "" { +func (s *StepCreateVM) Cleanup(state multistep.StateBag) { + if s.VMName == "" { return } @@ -68,7 +76,7 @@ func (s *stepCreateVM) Cleanup(state multistep.StateBag) { ui.Say("Unregistering and deleting virtual machine...") var err error = nil for i := 0; i < 5; i++ { - err = driver.VBoxManage("unregistervm", s.vmName, "--delete") + err = driver.VBoxManage("unregistervm", s.VMName, "--delete") if err == nil { break } From ea7251993156500685d6c7aba51db997f159e360 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 1 Jul 2015 13:46:29 +0100 Subject: [PATCH 015/113] Add a little safety around variables that are passed into provisioner. --- provisioner/powershell/provisioner.go | 5 +++-- provisioner/windows-shell/provisioner.go | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index b31781d8864..f6f4c56c0af 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -346,8 +346,9 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string, e // Split vars into key/value components for _, envVar := range p.config.Vars { keyValue := strings.Split(envVar, "=") - if len(keyValue) != 2 { - err = errors.New("Shell provisioner environment variables must be in key=value format") + + if len(keyValue) != 2 || keyValue[0] == "" { + err = errors.New(fmt.Sprintf("Shell provisioner environment variables must be in key=value format. Currently it is '%s'", envVar)) return } envVars[keyValue[0]] = keyValue[1] diff --git a/provisioner/windows-shell/provisioner.go b/provisioner/windows-shell/provisioner.go index 50c0aaeb18f..c3bc56ec396 100644 --- a/provisioner/windows-shell/provisioner.go +++ b/provisioner/windows-shell/provisioner.go @@ -304,10 +304,10 @@ func (p *Provisioner) createFlattenedEnvVars() (flattened string, err error) { // Split vars into key/value components for _, envVar := range p.config.Vars { keyValue := strings.Split(envVar, "=") - if len(keyValue) != 2 { - err = errors.New("Shell provisioner environment variables must be in key=value format") + if len(keyValue) != 2 || keyValue[0] == "" { + err = errors.New(fmt.Sprintf("Shell provisioner environment variables must be in key=value format. Currently it is '%s'", envVar)) return - } + } envVars[keyValue[0]] = keyValue[1] } // Create a list of env var keys in sorted order From d5a0368cbb707030f954a467a917ea6beddb9db8 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Wed, 1 Jul 2015 17:02:52 +0100 Subject: [PATCH 016/113] Quote powershell so that it does not try to interpret command to be run Get VName out of state. This allows template replacement to be run on vmname --- builder/hyperv/common/step_type_boot_command.go | 7 +++---- builder/hyperv/iso/builder.go | 5 ++--- builder/virtualbox/common/step_type_boot_command.go | 1 - builder/virtualbox/iso/builder.go | 1 - builder/virtualbox/iso/step_create_disk.go | 2 +- builder/virtualbox/ovf/builder.go | 1 - provisioner/powershell/provisioner.go | 4 ++-- provisioner/powershell/provisioner_test.go | 4 ++-- 8 files changed, 10 insertions(+), 15 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index 8c06982bc1a..c801858b727 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -32,15 +32,14 @@ type bootCommandTemplateData struct { type StepTypeBootCommand struct { BootCommand []string SwitchName string - VMName string Ctx interpolate.Context } func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction { httpPort := state.Get("http_port").(uint) ui := state.Get("ui").(packer.Ui) - driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) hostIp, err := driver.GetHostAdapterIpAddressForSwitch(s.SwitchName) @@ -56,7 +55,7 @@ func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction s.Ctx.Data = &bootCommandTemplateData{ hostIp, httpPort, - s.VMName, + vmName, } ui.Say("Typing the boot command...") @@ -77,7 +76,7 @@ func (s *StepTypeBootCommand) Run(state multistep.StateBag) multistep.StepAction scanCodesToSendString := strings.Join(scanCodesToSend, " ") - if err := driver.TypeScanCodes(s.VMName, scanCodesToSendString); err != nil { + if err := driver.TypeScanCodes(vmName, scanCodesToSendString); err != nil { err := fmt.Errorf("Error sending boot command: %s", err) state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 010b06cc77a..e918d3b5ea7 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -148,8 +148,8 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.VMName == "" { - b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) - } + b.config.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", b.config.PackerBuildName) + } log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) @@ -311,7 +311,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepTypeBootCommand{ BootCommand: b.config.BootCommand, SwitchName: b.config.SwitchName, - VMName: b.config.VMName, Ctx: b.config.ctx, }, diff --git a/builder/virtualbox/common/step_type_boot_command.go b/builder/virtualbox/common/step_type_boot_command.go index 3871e4f27da..2f0ce22c090 100644 --- a/builder/virtualbox/common/step_type_boot_command.go +++ b/builder/virtualbox/common/step_type_boot_command.go @@ -33,7 +33,6 @@ type bootCommandTemplateData struct { // type StepTypeBootCommand struct { BootCommand []string - VMName string Ctx interpolate.Context } diff --git a/builder/virtualbox/iso/builder.go b/builder/virtualbox/iso/builder.go index 9356c400c63..7582c37207b 100644 --- a/builder/virtualbox/iso/builder.go +++ b/builder/virtualbox/iso/builder.go @@ -272,7 +272,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &vboxcommon.StepTypeBootCommand{ BootCommand: b.config.BootCommand, - VMName: b.config.VMName, Ctx: b.config.ctx, }, &communicator.StepConnect{ diff --git a/builder/virtualbox/iso/step_create_disk.go b/builder/virtualbox/iso/step_create_disk.go index 22b171f44f9..87015b8468f 100644 --- a/builder/virtualbox/iso/step_create_disk.go +++ b/builder/virtualbox/iso/step_create_disk.go @@ -21,7 +21,7 @@ func (s *stepCreateDisk) Run(state multistep.StateBag) multistep.StepAction { vmName := state.Get("vmName").(string) format := "VDI" - path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", config.VMName, strings.ToLower(format))) + path := filepath.Join(config.OutputDir, fmt.Sprintf("%s.%s", vmName, strings.ToLower(format))) command := []string{ "createhd", diff --git a/builder/virtualbox/ovf/builder.go b/builder/virtualbox/ovf/builder.go index 8b9932d54f8..197093ff779 100644 --- a/builder/virtualbox/ovf/builder.go +++ b/builder/virtualbox/ovf/builder.go @@ -98,7 +98,6 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &vboxcommon.StepTypeBootCommand{ BootCommand: b.config.BootCommand, - VMName: b.config.VMName, Ctx: b.config.ctx, }, &communicator.StepConnect{ diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index f6f4c56c0af..cd10933ea49 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -115,11 +115,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` + p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell "& { {{.Vars}}{{.Path}}; exit $LastExitCode}"` + p.config.ExecuteCommand = `powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.ElevatedExecuteCommand == "" { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 78484c18415..7acf97d8af2 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,8 +75,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != "powershell \"& { {{.Vars}}{{.Path}}; exit $LastExitCode}\"" { - t.Fatalf("Default command should be powershell \"& { {{.Vars}}{{.Path}}; exit $LastExitCode}\", but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != "powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'" { + t.Fatalf("Default command should be powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } if p.config.ElevatedExecuteCommand != "{{.Vars}}{{.Path}}" { From 1f41901b1c7b80ba454a09fbd7c286a1e83fb791 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Jul 2015 17:19:29 +0100 Subject: [PATCH 017/113] Pass in any iso images to add as dvd drives during boot --- builder/hyperv/iso/builder.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e918d3b5ea7..e1b2282f817 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -149,7 +149,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { if b.config.VMName == "" { b.config.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", b.config.PackerBuildName) - } + } log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) @@ -171,6 +171,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { b.config.Generation = 1 } + if b.config.Generation == 2 { + if len(b.config.SecondaryDvdImages) > 0 { + err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.") + errs = packer.MultiErrorAppend(errs, err) + } + } + log.Println(fmt.Sprintf("Using switch %s", b.config.SwitchName)) log.Println(fmt.Sprintf("%s: %v", "SwitchName", b.config.SwitchName)) @@ -301,7 +308,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe }, &hypervcommon.StepMountFloppydrive{}, - &hypervcommon.StepMountSecondaryDvdImages{}, + &hypervcommon.StepMountSecondaryDvdImages{ + Files: b.config.SecondaryDvdImages, + }, &hypervcommon.StepRun{ BootWait: b.config.BootWait, From 70b85fc5b621952727922e46ac5af681fc300ec2 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Jul 2015 18:44:10 +0100 Subject: [PATCH 018/113] Hyperv generation 2 machines use scsi for dvd drives. Allow gen 1 machines to have at least 1 iso image attached --- .../common/step_mount_integration_services.go | 52 ++++++++++++------- builder/hyperv/iso/builder.go | 1 + 2 files changed, 33 insertions(+), 20 deletions(-) diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 05eaf6dc895..66684ea6338 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -6,20 +6,21 @@ package common import ( "fmt" - "log" - "os" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" powershell "github.com/mitchellh/packer/powershell" + "log" + "os" ) type StepMountSecondaryDvdImages struct { - Files [] string + Files []string dvdProperties []DvdControllerProperties + generation uint } type DvdControllerProperties struct { - ControllerNumber string + ControllerNumber string ControllerLocation string } @@ -34,13 +35,13 @@ func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.St // Will Windows assign DVD drives to A: and B: ? // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - dvdProperties, err := s.mountFiles(vmName); + dvdProperties, err := s.mountFiles(vmName) if err != nil { state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - + log.Println(fmt.Sprintf("Saving DVD properties %s DVDs", len(dvdProperties))) state.Put("secondary.dvd.properties", dvdProperties) @@ -52,7 +53,6 @@ func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { } - func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdControllerProperties, error) { var dvdProperties []DvdControllerProperties @@ -76,7 +76,6 @@ func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdController return dvdProperties, nil } - func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vmName string) (DvdControllerProperties, error) { isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" @@ -88,28 +87,41 @@ func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vm return properties, nil } - - - func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) { var properties DvdControllerProperties var script powershell.ScriptBuilder powershell := new(powershell.PowerShellCmd) - // get the controller number that the OS install disk is mounted on - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber") - controllerNumber, err := powershell.Output(script.String(), vmName) - if err != nil { - return properties, err + controllerNumber := "0" + if s.generation < 2 { + // get the controller number that the OS install disk is mounted on + // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives + script.Reset() + script.WriteLine("param([string]$vmName)") + script.WriteLine("$dvdDrives = (Get-VMDvdDrive -VMName $vmName)") + script.WriteLine("$lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber}") + script.WriteLine("if (!$lastControllerNumber) {") + script.WriteLine(" $lastControllerNumber = 0") + script.WriteLine("} elseif (!$lastControllerNumber -or ($dvdDrives | ?{ $_.ControllerNumber -eq $lastControllerNumber} | measure).count -gt 1) {") + script.WriteLine(" $lastControllerNumber += 1") + script.WriteLine("}") + script.WriteLine("$lastControllerNumber") + controllerNumber, err := powershell.Output(script.String(), vmName) + if err != nil { + return properties, err + } + + if controllerNumber != "0" || controllerNumber != "1" { + //There are only 2 ide controllers, try to use the one the hdd is attached too + controllerNumber = "0" + } } script.Reset() script.WriteLine("param([string]$vmName,[int]$controllerNumber)") script.WriteLine("Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber") - err = powershell.Run(script.String(), vmName, controllerNumber) + err := powershell.Run(script.String(), vmName, controllerNumber) if err != nil { return properties, err } @@ -133,7 +145,7 @@ func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath return properties, err } - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v",isoPath, controllerNumber, controllerLocation)) + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) properties.ControllerNumber = controllerNumber properties.ControllerLocation = controllerLocation diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e1b2282f817..4955b733bda 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -310,6 +310,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountSecondaryDvdImages{ Files: b.config.SecondaryDvdImages, + generation: b.config.Generation, }, &hypervcommon.StepRun{ From a5f0aeb65f5bb3fba3dbc990b73df6a6a0ec3df0 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Jul 2015 18:57:42 +0100 Subject: [PATCH 019/113] Need to expose generation as a public property --- builder/hyperv/common/step_mount_integration_services.go | 4 ++-- builder/hyperv/iso/builder.go | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 66684ea6338..5460d52b831 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -15,8 +15,8 @@ import ( type StepMountSecondaryDvdImages struct { Files []string + Generation uint dvdProperties []DvdControllerProperties - generation uint } type DvdControllerProperties struct { @@ -94,7 +94,7 @@ func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath powershell := new(powershell.PowerShellCmd) controllerNumber := "0" - if s.generation < 2 { + if s.Generation < 2 { // get the controller number that the OS install disk is mounted on // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives script.Reset() diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 4955b733bda..e44182fd9bc 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -309,8 +309,8 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountFloppydrive{}, &hypervcommon.StepMountSecondaryDvdImages{ - Files: b.config.SecondaryDvdImages, - generation: b.config.Generation, + Files: b.config.SecondaryDvdImages, + Generation: b.config.Generation, }, &hypervcommon.StepRun{ From ed8c028dce0edcaadc3d56eb1244a560f18c9c9a Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Sun, 12 Jul 2015 19:27:16 +0100 Subject: [PATCH 020/113] Check the floppy files and not the dvd files attached --- builder/hyperv/iso/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e44182fd9bc..edf39385ccb 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -172,7 +172,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.Generation == 2 { - if len(b.config.SecondaryDvdImages) > 0 { + if len(b.config.FloppyFiles) > 0 { err = errors.New("Generation 2 vms don't support floppy drives. Use ISO image instead.") errs = packer.MultiErrorAppend(errs, err) } From cfa99c1c991aca7d2de776b5cf5b6c5f01cb8f6d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Mon, 13 Jul 2015 19:00:08 +0100 Subject: [PATCH 021/113] Remove VDI file if there is a VMDK file. We don't need VDI file as well. --- builder/virtualbox/common/step_export.go | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/builder/virtualbox/common/step_export.go b/builder/virtualbox/common/step_export.go index 56b013d9f87..a6785094b42 100644 --- a/builder/virtualbox/common/step_export.go +++ b/builder/virtualbox/common/step_export.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" "time" + "os" ) // This step cleans up forwarded ports and exports the VM to an OVF. @@ -68,6 +69,28 @@ func (s *StepExport) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + //Drop VDI if there is a VMDK + format := "VMDK" + path := filepath.Join(s.OutputDir, fmt.Sprintf("%s.%s", vmName, strings.ToLower(format))) + + if _, err := os.Stat(path); err == nil { + format = "VDI" + path = filepath.Join(s.OutputDir, fmt.Sprintf("%s.%s", vmName, strings.ToLower(format))) + + if _, err := os.Stat(path); err == nil { + ui.Say(fmt.Sprintf("Removing VDI as VMDK exists... %s", path)) + + err := os.Remove(path) + + if err != nil { + err := fmt.Errorf("Error deleting VDI: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + } + state.Put("exportPath", outputPath) return multistep.ActionContinue From d7b02fd89450e29c7d906a22850bd4b5207a1d5f Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Tue, 14 Jul 2015 07:51:03 +0100 Subject: [PATCH 022/113] No need to remove floppy controller for 2nd generation vms Don't want to be prompted for confirmation from powershell commandlets --- builder/hyperv/common/step_unmount_floppydrive.go | 6 +++++- builder/hyperv/iso/builder.go | 4 +++- powershell/hyperv/hyperv.go | 8 ++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index ae7813400b5..87d1fd4a37a 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -11,14 +11,18 @@ import ( "github.com/mitchellh/packer/powershell/hyperv" ) - type StepUnmountFloppyDrive struct { + Generation uint } func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAction { //driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) + if s.Generation > 1 { + return multistep.ActionContinue + } + errorMsg := "Error Unmounting floppy drive: %s" vmName := state.Get("vmName").(string) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index edf39385ccb..e3556eb6552 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -345,7 +345,9 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // remove the integration services dvd drive // after we power down &hypervcommon.StepUnmountSecondaryDvdImages{}, - &hypervcommon.StepUnmountFloppyDrive{}, + &hypervcommon.StepUnmountFloppyDrive{ + Generation: b.config.Generation, + }, &hypervcommon.StepUnmountDvdDrive{}, &hypervcommon.StepExportVm{ diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index f1581b4d9d5..efd83c684ef 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -160,7 +160,7 @@ if (($vm.State -ne [Microsoft.HyperV.PowerShell.VMState]::Off) -and ($vm.State - Stop-VM -VM $vm -TurnOff -Force -Confirm:$false } -Remove-VM -Name $vmName -Force +Remove-VM -Name $vmName -Force -Confirm:$false ` var ps powershell.PowerShellCmd @@ -231,7 +231,7 @@ func DeleteVirtualSwitch(switchName string) error { param([string]$switchName) $switch = Get-VMSwitch -Name $switchName -ErrorAction SilentlyContinue if ($switch -ne $null) { - $switch | Remove-VMSwitch -Force + $switch | Remove-VMSwitch -Force -Confirm:$false } ` @@ -246,7 +246,7 @@ func StartVirtualMachine(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off) { - Start-VM -Name $vmName + Start-VM -Name $vmName -Confirm:$false } ` @@ -259,7 +259,7 @@ func RestartVirtualMachine(vmName string) error { var script = ` param([string]$vmName) -Restart-VM $vmName -Force +Restart-VM $vmName -Force -Confirm:$false ` var ps powershell.PowerShellCmd From 3e122f441164517b27b08c4d8d2853fff218463d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Thu, 16 Jul 2015 20:48:08 +0100 Subject: [PATCH 023/113] Copy the folder structure correctly --- powershell/hyperv/hyperv.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index efd83c684ef..6a512d1f49e 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -197,9 +197,9 @@ func CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string var script = ` param([string]$srcPath, [string]$dstPath, [string]$vhdDirName, [string]$vmDir) -Copy-Item -Path $srcPath/$vhdDirName -Destination $dstPath -recurse -Copy-Item -Path $srcPath/$vmDir -Destination $dstPath -Copy-Item -Path $srcPath/$vmDir/*.xml -Destination $dstPath/$vmDir +Move-Item -Path $srcPath/*.* -Destination $dstPath +Move-Item -Path $srcPath/$vhdDirName -Destination $dstPath +Move-Item -Path $srcPath/$vmDir -Destination $dstPath ` var ps powershell.PowerShellCmd From 3cee05d30a08c62247a76eb199899a492c70ba15 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Fri, 24 Jul 2015 08:21:23 +0100 Subject: [PATCH 024/113] Use simple naming convention for now --- builder/hyperv/iso/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index e3556eb6552..ef174a58429 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -148,7 +148,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } if b.config.VMName == "" { - b.config.VMName = fmt.Sprintf("packer-%s-{{timestamp}}", b.config.PackerBuildName) + b.config.VMName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) } log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) From 44802e1f3daca1ce7ea2472e9d4d9e82e3e8fbf4 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson Date: Fri, 24 Jul 2015 08:22:27 +0100 Subject: [PATCH 025/113] Shelf check in - start of adding documentation --- .../docs/builders/hyperv-iso.html.markdown | 954 ++++++++++++++++++ .../source/docs/builders/hyperv.html.markdown | 18 + 2 files changed, 972 insertions(+) create mode 100644 website/source/docs/builders/hyperv-iso.html.markdown create mode 100644 website/source/docs/builders/hyperv.html.markdown diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown new file mode 100644 index 00000000000..913e44c05fe --- /dev/null +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -0,0 +1,954 @@ +--- +layout: "docs" +page_title: "HyperV Builder (from an ISO)" +description: |- + The HyperV Packer builder is able to create HyperV virtual machines and export them. +--- + +# HyperV Builder (from an ISO) + +Type: `hyperv-iso` + +The HyperV Packer builder is able to create [HyperV](https://www.microsoft.com/en-us/server-cloud/solutions/virtualization.aspx) +virtual machines and export them, starting from an ISO image. + +The builder builds a virtual machine by creating a new virtual machine +from scratch, booting it, installing an OS, provisioning software within +the OS, then shutting it down. The result of the HyperV builder is a directory +containing all the files necessary to run the virtual machine portably. + +## Basic Example + +Here is a basic example. This example is not functional. It will start the +OS installer but then fail because we don't provide the preseed file for +Ubuntu to self-install. Still, the example serves to show the basic configuration: + +```javascript +{ + "type": "hyperv-iso", + "guest_os_type": "Ubuntu_64", + "iso_url": "http://releases.ubuntu.com/12.04/ubuntu-12.04.5-server-amd64.iso", + "iso_checksum": "769474248a3897f4865817446f9a4a53", + "iso_checksum_type": "md5", + "ssh_username": "packer", + "ssh_password": "packer", + "shutdown_command": "echo 'packer' | sudo -S shutdown -P now" +} +``` + +It is important to add a `shutdown_command`. By default Packer halts the +virtual machine and the file system may not be sync'd. Thus, changes made in a +provisioner might not be saved. + +## Configuration Reference + +There are many configuration options available for the HyperV builder. +They are organized below into two categories: required and optional. Within +each category, the available options are alphabetized and described. + +In addition to the options listed here, a +[communicator](/docs/templates/communicator.html) +can be configured for this builder. + + + + + + + + +### Required: + +* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO + files are so large, this is required and Packer will verify it prior + to booting a virtual machine with the ISO attached. The type of the + checksum is specified with `iso_checksum_type`, documented below. + +* `iso_checksum_type` (string) - The type of the checksum specified in + `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or + "sha512" currently. While "none" will skip checksumming, this is not + recommended since ISO files are generally large and corruption does happen + from time to time. + +* `iso_url` (string) - A URL to the ISO containing the installation image. + This URL can be either an HTTP URL or a file URL (or path to a file). + If this is an HTTP URL, Packer will download it and cache it between + runs. + +### Optional: + +* `boot_command` (array of strings) - This is an array of commands to type + when the virtual machine is first booted. The goal of these commands should + be to type just enough to initialize the operating system installer. Special + keys can be typed as well, and are covered in the section below on the boot + command. If this is not specified, it is assumed the installer will start + itself. + +* `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `cpu` (int) - The number of cpus the virtual machine should use. If this isn't specified, + the default is 1 cpu. + +* `disk_size` (integer) - The size, in megabytes, of the hard disk to create + for the VM. By default, this is 40000 (about 40 GB). + +* `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. + This defaults to false. + +* `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. + +* `generation` (int) - The HyperV generation for the virtual machine. By + default, this is 1. Generation 2 HyperV virtual machines do not support + floppy drives. In this scenario use secondary_iso_images instead. Hard + drives and dvd drives will also be scsi and not ide. + +* `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +* `http_port_min` and `http_port_max` (integer) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + +* `ip_address_timeout` (string) - The time to wait after creating the initial virtual + machine and waiting for an ip address before assuming there is an error in the process. + The value of this should be a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +* `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. + +* `secondary_iso_images` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. + +* `shutdown_command` (string) - The command to use to gracefully shut down the machine once all + the provisioning is done. By default this is an empty string, which tells Packer to just + forcefully shut down the machine unless a shutdown command takes place inside script so this may + safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank + since reboots may fail and specify the final shutdown command in your last script. + +* `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +* `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when + exporting. This defaults to false. + +* `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting + this to an empty string, Packer will try to determine the switch to use by looking for + external switch that is up and running. + +* `vm_name` (string) - This is the name of the virtua machine for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. + +## Boot Command + +The `boot_command` configuration is very important: it specifies the keys +to type when the virtual machine is first booted in order to start the +OS installer. This command is typed after `boot_wait`, which gives the +virtual machine some time to actually load the ISO. + +As documented above, the `boot_command` is an array of strings. The +strings are all typed in sequence. It is an array only to improve readability +within the template. + +The boot command is "typed" character for character over the virtual keyboard +to the machine, simulating a human actually typing the keyboard. There are +a set of special keys available. If these are in your boot command, they +will be replaced by the proper key: + +* `` - Backspace + +* `` - Delete + +* `` and `` - Simulates an actual "enter" or "return" keypress. + +* `` - Simulates pressing the escape key. + +* `` - Simulates pressing the tab key. + +* `` - `` - Simulates pressing a function key. + +* `` `` `` `` - Simulates pressing an arrow key. + +* `` - Simulates pressing the spacebar. + +* `` - Simulates pressing the insert key. + +* `` `` - Simulates pressing the home and end keys. + +* `` `` - Simulates pressing the page up and page down keys. + +* `` `` `` - Adds a 1, 5 or 10 second pause before sending any additional keys. This + is useful if you have to generally wait for the UI to update before typing more. + +In addition to the special keys, each command to type is treated as a +[configuration template](/docs/templates/configuration-templates.html). +The available variables are: + +* `HTTPIP` and `HTTPPort` - The IP and port, respectively of an HTTP server + that is started serving the directory specified by the `http_directory` + configuration parameter. If `http_directory` isn't specified, these will + be blank! + +Example boot command. This is actually a working boot command used to start +an Ubuntu 12.04 installer: + +```text +[ + "", + "/install/vmlinuz noapic ", + "preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg ", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", + "hostname={{ .Name }} ", + "fb=false debconf/frontend=noninteractive ", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", + "keyboard-configuration/variant=USA console-setup/ask_detect=false ", + "initrd=/install/initrd.gz -- " +] +``` + +## Integration Services + +Packer will automatically attach the integration services iso as a dvd drive +for the version of HyperV that is running. + +## Generation 1 vs Generation 2 + +Floppy drives are no longer supported by generation 2 machines. This requires you to +take another approach when dealing with preseed or answer files. Two possible options +are using virtua dvd drives or using the built in web server. + +When dealing with Windows you need to enable UEFI drives for generation 2 virtual machines. + +## Creating iso from directory + +Programs like mkisofs can be used to create an iso from a directory. +There is a [windows version of mkisofs](http://opensourcepack.blogspot.co.uk/p/cdrtools.html). + +Example powershell script. This is an actually working powershell script used to create a Windows answer iso: + +```text +$isoFolder = "answer-iso" +if (test-path $isoFolder){ + remove-item $isoFolder -Force -Recurse +} + +if (test-path windows\windows-2012R2-serverdatacenter-amd64\answer.iso){ + remove-item windows\windows-2012R2-serverdatacenter-amd64\answer.iso -Force +} + +mkdir $isoFolder + +copy windows\windows-2012R2-serverdatacenter-amd64\Autounattend.xml $isoFolder\ +copy windows\windows-2012R2-serverdatacenter-amd64\sysprep-unattend.xml $isoFolder\ +copy windows\common\set-power-config.ps1 $isoFolder\ +copy windows\common\microsoft-updates.ps1 $isoFolder\ +copy windows\common\win-updates.ps1 $isoFolder\ +copy windows\common\run-sysprep.ps1 $isoFolder\ +copy windows\common\run-sysprep.cmd $isoFolder\ + +$textFile = "$isoFolder\Autounattend.xml" + +$c = Get-Content -Encoding UTF8 $textFile + +$c | % { $_ -replace '','','Finish Non UEFI -->' } | % { $_ -replace '' } | % { $_ -replace 'Finish UEFI compatible -->','' } | sc -Path $textFile + +& .\mkisofs.exe -r -iso-level 4 -UDF -o windows\windows-2012R2-serverdatacenter-amd64\answer.iso $isoFolder + +if (test-path $isoFolder){ + remove-item $isoFolder -Force -Recurse +} +``` + + +## Example For Windows Server 2012 R2 Generation 2 + +Packer config: + +```text +{ + "builders": [ + { + "vm_name":"windows2012r2", + "type": "hyperv-iso", + "disk_size": 61440, + "floppy_files": [], + "secondary_iso_images": [ + "./windows/windows-2012R2-serverdatacenter-amd64/answer.iso" + ], + "http_directory": "./windows/common/http/", + "boot_wait": "0s", + "boot_command": [ + "aaa" + ], + "headless": false, + "iso_url": "http://download.microsoft.com/download/6/2/A/62A76ABB-9990-4EFC-A4FE-C7D698DAEB96/9600.16384.WINBLUE_RTM.130821-1623_X64FRE_SERVER_EVAL_EN-US-IRM_SSS_X64FREE_EN-US_DV5.ISO", + "iso_checksum_type": "md5", + "iso_checksum": "458ff91f8abc21b75cb544744bf92e6a", + "communicator":"winrm", + "winrm_username": "vagrant", + "winrm_password": "vagrant", + "winrm_timeout" : "4h", + "shutdown_command": "f:\\run-sysprep.cmd", + "ram_size_mb": 4096, + "cpu": 4, + "generation": 2, + "switch_name":"LAN", + "enable_secure_boot":true + }], + "provisioners": [{ + "type": "powershell", + "elevated_user":"vagrant", + "elevated_password":"vagrant", + "scripts": [ + "./windows/common/install-7zip.ps1", + "./windows/common/install-chef.ps1", + "./windows/common/compile-dotnet-assemblies.ps1", + "./windows/common/cleanup.ps1", + "./windows/common/ultradefrag.ps1", + "./windows/common/sdelete.ps1" + ] + }], + "post-processors": [ + { + "type": "vagrant", + "keep_input_artifact": false, + "output": "{{.Provider}}_windows-2012r2_chef.box" + } + ] +} +``` + +autounattend.xml: + +```text + + + + + + en-US + + en-US + en-US + en-US + en-US + en-US + + + + + + + + Primary + 1 + 350 + + + 2 + Primary + true + + + + + true + NTFS + + 1 + 1 + + + NTFS + + C + 2 + 2 + + + 0 + true + + + + + + + /IMAGE/NAME + Windows Server 2012 R2 SERVERSTANDARD + + + + 0 + 2 + + + + + + + + + + + + OnError + + true + Vagrant + Vagrant + + + + + + + false + + vagrant-2012r2 + Coordinated Universal Time + + + + true + + + false + false + + + true + + + true + + + + + + + + dgBhAGcAcgBhAG4AdABQAGEAcwBzAHcAbwByAGQA + false</PlainText> + </Password> + <Enabled>true</Enabled> + <Username>vagrant</Username> + </AutoLogon> + <FirstLogonCommands> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <Description>Set Execution Policy 64 Bit</Description> + <Order>1</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>C:\Windows\SysWOW64\cmd.exe /c powershell -Command "Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Force"</CommandLine> + <Description>Set Execution Policy 32 Bit</Description> + <Order>2</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm quickconfig -q</CommandLine> + <Description>winrm quickconfig -q</Description> + <Order>3</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm quickconfig -transport:http</CommandLine> + <Description>winrm quickconfig -transport:http</Description> + <Order>4</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config @{MaxTimeoutms="1800000"}</CommandLine> + <Description>Win RM MaxTimoutms</Description> + <Order>5</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxMemoryPerShellMB="300"}</CommandLine> + <Description>Win RM MaxMemoryPerShellMB</Description> + <Order>6</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/service @{AllowUnencrypted="true"}</CommandLine> + <Description>Win RM AllowUnencrypted</Description> + <Order>7</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/service/auth @{Basic="true"}</CommandLine> + <Description>Win RM auth Basic</Description> + <Order>8</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/client/auth @{Basic="true"}</CommandLine> + <Description>Win RM client auth Basic</Description> + <Order>9</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/listener?Address=*+Transport=HTTP @{Port="5985"} </CommandLine> + <Description>Win RM listener Address/Port</Description> + <Order>10</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall set rule group="remote administration" new enable=yes </CommandLine> + <Description>Win RM adv firewall enable</Description> + <Order>11</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="WinRM 5985" protocol=TCP dir=in localport=5985 action=allow</CommandLine> + <Description>Win RM port open</Description> + <Order>12</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="WinRM 5986" protocol=TCP dir=in localport=5986 action=allow</CommandLine> + <Description>Win RM port open</Description> + <Order>13</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c net stop winrm </CommandLine> + <Description>Stop Win RM Service </Description> + <Order>14</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c sc config winrm start= disabled</CommandLine> + <Description>Win RM Autostart</Description> + <Order>15</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v HideFileExt /t REG_DWORD /d 0 /f</CommandLine> + <Order>16</Order> + <Description>Show file extensions in Explorer</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\Console /v QuickEdit /t REG_DWORD /d 1 /f</CommandLine> + <Order>17</Order> + <Description>Enable QuickEdit mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v Start_ShowRun /t REG_DWORD /d 1 /f</CommandLine> + <Order>18</Order> + <Description>Show Run command in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Advanced\ /v StartMenuAdminTools /t REG_DWORD /d 1 /f</CommandLine> + <Order>19</Order> + <Description>Show Administrative Tools in Start Menu</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateFileSizePercent /t REG_DWORD /d 0 /f</CommandLine> + <Order>20</Order> + <Description>Zero Hibernation File</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD HKLM\SYSTEM\CurrentControlSet\Control\Power\ /v HibernateEnabled /t REG_DWORD /d 0 /f</CommandLine> + <Order>21</Order> + <Description>Disable Hibernation Mode</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c wmic useraccount where "name='vagrant'" set PasswordExpires=FALSE</CommandLine> + <Order>22</Order> + <Description>Disable password expiration for vagrant user</Description> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxShellsPerUser="30"}</CommandLine> + <Description>Win RM MaxShellsPerUser</Description> + <Order>23</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c winrm set winrm/config/winrs @{MaxProcessesPerShell="25"}</CommandLine> + <Description>Win RM MaxProcessesPerShell</Description> + <Order>24</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>%SystemRoot%\System32\reg.exe ADD "HKLM\System\CurrentControlSet\Services\Netlogon\Parameters" /v DisablePasswordChange /t REG_DWORD /d 1 /f</CommandLine> + <Description>Turn off computer password</Description> + <Order>25</Order> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c netsh advfirewall firewall add rule name="ICMP Allow incoming V4 echo request" protocol=icmpv4:8,any dir=in action=allow</CommandLine> + <Description>ICMP open for ping</Description> + <Order>26</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <!-- WITH WINDOWS UPDATES --> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\set-power-config.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\set-power-config.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\set-power-config.ps1)</CommandLine> + <Order>97</Order> + <Description>Turn off all power saving and timeouts</Description> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\microsoft-updates.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\microsoft-updates.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\microsoft-updates.ps1)</CommandLine> + <Order>98</Order> + <Description>Enable Microsoft Updates</Description> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <SynchronousCommand wcm:action="add"> + <CommandLine>cmd.exe /c IF EXIST a:\win-updates.ps1 (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File a:\win-updates.ps1) ELSE (C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -File f:\win-updates.ps1)</CommandLine> + <Description>Install Windows Updates</Description> + <Order>100</Order> + <RequiresUserInput>true</RequiresUserInput> + </SynchronousCommand> + <!-- END WITH WINDOWS UPDATES --> + </FirstLogonCommands> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideLocalAccountScreen>true</HideLocalAccountScreen> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + </OOBE> + <UserAccounts> + <AdministratorPassword> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </Password> + <Group>administrators</Group> + <DisplayName>Vagrant</DisplayName> + <Name>vagrant</Name> + <Description>Vagrant User</Description> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <RegisteredOwner /> + <TimeZone>Coordinated Universal Time</TimeZone> + </component> + </settings> + <settings pass="offlineServicing"> + <component name="Microsoft-Windows-LUA-Settings" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <EnableLUA>false</EnableLUA> + </component> + </settings> + <cpi:offlineImage cpi:source="wim:c:/projects/baseboxes/9600.16384.winblue_rtm.130821-1623_x64fre_server_eval_en-us-irm_sss_x64free_en-us_dv5_slipstream/sources/install.wim#Windows Server 2012 R2 SERVERDATACENTER" xmlns:cpi="urn:schemas-microsoft-com:cpi" /> +</unattend> + +``` + +sysprep-unattend.xml: + +```text +<?xml version="1.0" encoding="utf-8"?> +<unattend xmlns="urn:schemas-microsoft-com:unattend"> + <settings pass="generalize"> + <component language="neutral" name="Microsoft-Windows-Security-SPP" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <SkipRearm>1</SkipRearm> + </component> + </settings> + <settings pass="oobeSystem"> +<!-- Setup proxy after sysprep + <component name="Microsoft-Windows-IE-ClientNetworkProtocolImplementation" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <POLICYProxySettingsPerUser>1</POLICYProxySettingsPerUser> + <HKLMProxyEnable>false</HKLMProxyEnable> + <HKLMProxyServer>cache-proxy:3142</HKLMProxyServer> + </component> +Finish proxy after sysprep --> + <component language="neutral" name="Microsoft-Windows-International-Core" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <InputLocale>0809:00000809</InputLocale> + <SystemLocale>en-GB</SystemLocale> + <UILanguage>en-US</UILanguage> + <UILanguageFallback>en-US</UILanguageFallback> + <UserLocale>en-GB</UserLocale> + </component> + <component language="neutral" name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <OOBE> + <HideEULAPage>true</HideEULAPage> + <HideOEMRegistrationScreen>true</HideOEMRegistrationScreen> + <HideOnlineAccountScreens>true</HideOnlineAccountScreens> + <HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE> + <NetworkLocation>Work</NetworkLocation> + <ProtectYourPC>1</ProtectYourPC> + <SkipUserOOBE>true</SkipUserOOBE> + <SkipMachineOOBE>true</SkipMachineOOBE> + </OOBE> + <UserAccounts> + <AdministratorPassword> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </AdministratorPassword> + <LocalAccounts> + <LocalAccount wcm:action="add"> + <Password> + <Value>vagrant</Value> + <PlainText>true</PlainText> + </Password> + <Group>administrators</Group> + <DisplayName>Vagrant</DisplayName> + <Name>vagrant</Name> + <Description>Vagrant User</Description> + </LocalAccount> + </LocalAccounts> + </UserAccounts> + <DisableAutoDaylightTimeSet>true</DisableAutoDaylightTimeSet> + <TimeZone>Coordinated Universal Time</TimeZone> + <VisualEffects> + <SystemDefaultBackgroundColor>2</SystemDefaultBackgroundColor> + </VisualEffects> + </component> + </settings> +</unattend> +``` + +## Example For Ubuntu Vivid Generation 2 + +Packer config: + +```text +{ + "builders": [ + { + "vm_name":"ubuntu-vivid", + "type": "hyperv-iso", + "disk_size": 61440, + "headless": false, + "iso_url": "http://releases.ubuntu.com/15.04/ubuntu-15.04-server-amd64.iso", + "iso_checksum_type": "sha1", + "iso_checksum": "D10248965C2C749DF6BCCE9F2F90F16A2E75E843", + "communicator":"ssh", + "ssh_username": "vagrant", + "ssh_password": "vagrant", + "ssh_timeout" : "4h", + "http_directory": "./linux/ubuntu/http/", + "boot_wait": "5s", + "boot_command": [ + "<esc><esc><enter><wait>", + "/install/vmlinuz ", + "preseed/url=http://{{.HTTPIP}}:{{.HTTPPort}}/preseed.cfg ", + "debian-installer=en_US auto locale=en_US kbd-chooser/method=us ", + "hostname={{.Name}} ", + "fb=false debconf/frontend=noninteractive ", + "keyboard-configuration/modelcode=SKIP keyboard-configuration/layout=USA ", + "keyboard-configuration/variant=USA console-setup/ask_detect=false ", + "initrd=/install/initrd.gz -- <enter>" + ], + "shutdown_command": "echo 'vagrant' | sudo -S -E shutdown -P now", + "ram_size_mb": 4096, + "cpu": 4, + "generation": 1, + "switch_name":"LAN" + }], + "provisioners": [{ + "type": "shell", + "execute_command": "echo 'vagrant' | sudo -S -E sh {{.Path}}", + "scripts": [ + "./linux/ubuntu/update.sh", + "./linux/ubuntu/network.sh", + "./linux/common/vagrant.sh", + "./linux/common/chef.sh", + "./linux/common/motd.sh", + "./linux/ubuntu/cleanup.sh" + ] + }], + "post-processors": [ + { + "type": "vagrant", + "keep_input_artifact": true, + "output": "{{.Provider}}_ubuntu-15.04_chef.box" + } + ] +} +``` + +preseed.cfg: + +```text +## Options to set on the command line +d-i debian-installer/locale string en_US.utf8 +d-i console-setup/ask_detect boolean false +d-i console-setup/layout string us + +d-i netcfg/get_hostname string unassigned-hostname +d-i netcfg/get_domain string unassigned-domain + +d-i time/zone string UTC +d-i clock-setup/utc-auto boolean true +d-i clock-setup/utc boolean true + +d-i kbd-chooser/method select American English + +d-i netcfg/wireless_wep string + +d-i base-installer/kernel/override-image string linux-server + +d-i debconf debconf/frontend select Noninteractive + +d-i pkgsel/install-language-support boolean false +tasksel tasksel/first multiselect standard, ubuntu-server + +d-i partman-auto/method string lvm + +d-i partman-lvm/confirm boolean true +d-i partman-lvm/device_remove_lvm boolean true +d-i partman-auto/choose_recipe select atomic + +d-i partman/confirm_write_new_label boolean true +d-i partman/confirm_nooverwrite boolean true +d-i partman/choose_partition select finish +d-i partman/confirm boolean true + +# Write the changes to disks and configure LVM? +d-i partman-lvm/confirm boolean true +d-i partman-lvm/confirm_nooverwrite boolean true +d-i partman-auto-lvm/guided_size string max + +# Default user +d-i passwd/user-fullname string vagrant +d-i passwd/username string vagrant +d-i passwd/user-password password vagrant +d-i passwd/user-password-again password vagrant +d-i user-setup/encrypt-home boolean false +d-i user-setup/allow-password-weak boolean true + +# Minimum packages (see postinstall.sh) +d-i pkgsel/include string openssh-server ntp + +# Upgrade packages after debootstrap? (none, safe-upgrade, full-upgrade) +# (note: set to none for speed) +d-i pkgsel/upgrade select none + +d-i grub-installer/only_debian boolean true +d-i grub-installer/with_other_os boolean true +d-i finish-install/reboot_in_progress note + +d-i pkgsel/update-policy select none + +choose-mirror-bin mirror/http/proxy string + +#d-i mirror/http/proxy string http://apt-cacher:3142/ +``` \ No newline at end of file diff --git a/website/source/docs/builders/hyperv.html.markdown b/website/source/docs/builders/hyperv.html.markdown new file mode 100644 index 00000000000..79c6fbff1b5 --- /dev/null +++ b/website/source/docs/builders/hyperv.html.markdown @@ -0,0 +1,18 @@ +--- +layout: "docs" +page_title: "HyperV Builder" +description: |- + The HyperV Packer builder is able to create HyperV virtual machines and export them. +--- + +# HyperV Builder + +The HyperV Packer builder is able to create [HyperV](https://www.microsoft.com/en-us/server-cloud/solutions/virtualization.aspx) +virtual machines and export them. + +Packer currently only support building HyperV machines with an iso: + +* [hyperv-iso](/docs/builders/hyperv-iso.html) - Starts from + an ISO file, creates a brand new HyperV VM, installs an OS, + provisions software within the OS, then exports that machine to create + an image. This is best for people who want to start from scratch. \ No newline at end of file From b74825768609af4d22d91909437c95397b3f858b Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Mon, 10 Aug 2015 09:25:52 +0100 Subject: [PATCH 026/113] Use plain text password --- website/source/docs/builders/hyperv-iso.html.markdown | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 913e44c05fe..ceb94613635 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -291,6 +291,7 @@ $textFile = "$isoFolder\Autounattend.xml" $c = Get-Content -Encoding UTF8 $textFile +# Enable UEFI and disable Non EUFI $c | % { $_ -replace '<!-- Start Non UEFI -->','<!-- Start Non UEFI' } | % { $_ -replace '<!-- Finish Non UEFI -->','Finish Non UEFI -->' } | % { $_ -replace '<!-- Start UEFI compatible','<!-- Start UEFI compatible -->' } | % { $_ -replace 'Finish UEFI compatible -->','<!-- Finish UEFI compatible -->' } | sc -Path $textFile & .\mkisofs.exe -r -iso-level 4 -UDF -o windows\windows-2012R2-serverdatacenter-amd64\answer.iso $isoFolder @@ -544,8 +545,8 @@ Finish Setup cache proxy during installation --> <component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64" publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS" xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <AutoLogon> <Password> - <Value>dgBhAGcAcgBhAG4AdABQAGEAcwBzAHcAbwByAGQA</Value> - <PlainText>false</PlainText> + <Value>vagrant</Value> + <PlainText>true</PlainText> </Password> <Enabled>true</Enabled> <Username>vagrant</Username> From 0f47da1e44282d4ca20091fd83a4a004945f811d Mon Sep 17 00:00:00 2001 From: Volodymyr Babchynskyy <vvchik@gmail.com> Date: Fri, 9 Oct 2015 16:54:55 +0300 Subject: [PATCH 027/113] fix vagrant box structure --- post-processor/vagrant/hyperv.go | 38 +++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/post-processor/vagrant/hyperv.go b/post-processor/vagrant/hyperv.go index d5fa0ce5234..2f7dc144059 100644 --- a/post-processor/vagrant/hyperv.go +++ b/post-processor/vagrant/hyperv.go @@ -3,7 +3,9 @@ package vagrant import ( "fmt" "github.com/mitchellh/packer/packer" + "os" "path/filepath" + "strings" ) type HypervProvider struct{} @@ -16,14 +18,48 @@ func (p *HypervProvider) Process(ui packer.Ui, artifact packer.Artifact, dir str // Create the metadata metadata = map[string]interface{}{"provider": "hyperv"} + // ui.Message(fmt.Sprintf("artifacts all: %+v", artifact)) + var outputDir string + + // Vargant requires specific dir structure for hyperv + // hyperv builder creates the structure in the output dir + // we have to keep the structure in a temp dir + // hack little bit but string in artifact usually have output dir + artifactString := artifact.String() + d := strings.Split(artifactString, ": ") + outputDir = d[1] + // ui.Message(fmt.Sprintf("artifact dir from string: %s", outputDir)) + // Copy all of the original contents into the temporary directory for _, path := range artifact.Files() { ui.Message(fmt.Sprintf("Copying: %s", path)) - dstPath := filepath.Join(dir, filepath.Base(path)) + var rel string + + rel, err = filepath.Rel(outputDir, filepath.Dir(path)) + // ui.Message(fmt.Sprintf("rel is: %s", rel)) + + if err != nil { + ui.Message(fmt.Sprintf("err in: %s", rel)) + return + } + + dstDir := filepath.Join(dir, rel) + // ui.Message(fmt.Sprintf("dstdir is: %s", dstDir)) + if _, err = os.Stat(dstDir); err != nil { + if err = os.MkdirAll(dstDir, 0755); err != nil { + ui.Message(fmt.Sprintf("err in creating: %s", dstDir)) + return + } + } + + dstPath := filepath.Join(dstDir, filepath.Base(path)) + if err = CopyContents(dstPath, path); err != nil { + ui.Message(fmt.Sprintf("err in copying: %s to %s", path, dstPath)) return } + ui.Message(fmt.Sprintf("Copyed %s to %s", path, dstPath)) } return From c586f4c1ddb72b2707d7e2cbee39a5a09ccb2ea0 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 12:26:44 +0100 Subject: [PATCH 028/113] Should only default to first controller if its not 1 of the 2 controllers available --- builder/hyperv/common/step_mount_integration_services.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 5460d52b831..5aad1f4abc5 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -112,7 +112,7 @@ func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath return properties, err } - if controllerNumber != "0" || controllerNumber != "1" { + if controllerNumber != "0" && controllerNumber != "1" { //There are only 2 ide controllers, try to use the one the hdd is attached too controllerNumber = "0" } From ae9382a2a2639d8f1b1caaf445803a9d42bc8b9d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 14:01:18 +0100 Subject: [PATCH 029/113] Use correct formatting for printf --- builder/hyperv/common/step_mount_integration_services.go | 2 +- builder/hyperv/common/step_unmount_integration_services.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 5aad1f4abc5..63e775a0d6b 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -42,7 +42,7 @@ func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.St return multistep.ActionHalt } - log.Println(fmt.Sprintf("Saving DVD properties %s DVDs", len(dvdProperties))) + log.Println(fmt.Sprintf("Saving DVD properties %d DVDs", len(dvdProperties))) state.Put("secondary.dvd.properties", dvdProperties) diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go index a905f2a8bf4..d792457825c 100644 --- a/builder/hyperv/common/step_unmount_integration_services.go +++ b/builder/hyperv/common/step_unmount_integration_services.go @@ -25,7 +25,7 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. dvdProperties := state.Get("secondary.dvd.properties").([]DvdControllerProperties) - log.Println(fmt.Sprintf("Found DVD properties %s", len(dvdProperties))) + log.Println(fmt.Sprintf("Found DVD properties %d", len(dvdProperties))) for _, dvdProperty := range dvdProperties { controllerNumber := dvdProperty.ControllerNumber From d33449b36b25511bfa0075fca7a08e1432750590 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 16:10:06 +0100 Subject: [PATCH 030/113] Quoting of powershell commands and associated tests --- provisioner/powershell/provisioner.go | 6 +++--- provisioner/powershell/provisioner_test.go | 24 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index d59a6bf46da..571ac2da63e 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -112,11 +112,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.EnvVarFormat == "" { - p.config.EnvVarFormat = `$env:%s=\"%s\"; ` + p.config.EnvVarFormat = `$env:%s="%s"; ` } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` + p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` } if p.config.ExecuteCommand == "" { @@ -124,7 +124,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `{{.Vars}}{{.Path}}` + p.config.ElevatedExecuteCommand = `powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 7acf97d8af2..e0bee6bccf5 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -58,7 +58,7 @@ func TestProvisioner_Impl(t *testing.T) { func TestProvisionerPrepare_Defaults(t *testing.T) { var p Provisioner config := testConfig() - + err := p.Prepare(config) if err != nil { t.Fatalf("err: %s", err) @@ -79,8 +79,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Fatalf("Default command should be powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != "{{.Vars}}{{.Path}}" { - t.Fatalf("Default command should be powershell {{.Vars}}{{.Path}}, but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != "powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'" { + t.Fatalf("Default command should be powershell powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -96,7 +96,7 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { - t.Fatalf("Default command should be powershell \"{{.Vars}}{{.Path}}\", but got %s", p.config.ElevatedEnvVarFormat) + t.Fatalf("Default command should be powershell '$env:%s=\"%s\"; ', but got %s", p.config.ElevatedEnvVarFormat) } } @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell "& { $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}"` + expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell "& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}"` + expectedCommand = `powershell '& { $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell "& { $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}"` + expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell "& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}"` + expectedCommand := `powershell '& { $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -545,7 +545,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -556,7 +556,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:FOO=\\\"bar\\\"; $env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " { + if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -567,7 +567,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:BAZ=\\\"qux\\\"; $env:FOO=\\\"bar\\\"; $env:PACKER_BUILDER_TYPE=\\\"iso\\\"; $env:PACKER_BUILD_NAME=\\\"vmware\\\"; " { + if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != "powershell \"& { $env:PACKER_BUILDER_TYPE=\\\"\\\"; $env:PACKER_BUILD_NAME=\\\"\\\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}\"" { + if cmd != `powershell '& { $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From f884a355629f55a3fb39e35ed113f0d2eeddd1f2 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 16:29:23 +0100 Subject: [PATCH 031/113] Must escape string formatting for string fmt --- provisioner/powershell/provisioner_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index e0bee6bccf5..9ef9ec3013c 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -96,7 +96,7 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { - t.Fatalf("Default command should be powershell '$env:%s=\"%s\"; ', but got %s", p.config.ElevatedEnvVarFormat) + t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat) } } From 2afd159eb33f9de7e1a89c6945d7b5fafe40bdc6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 17:11:38 +0100 Subject: [PATCH 032/113] Default minimum and maximum sizes were geared towards spinning up windows server instances. Linux instances are far more efficient and make do with lower requirements. Set the minimum sizes to match this. --- builder/hyperv/iso/builder.go | 22 +++++++++++----------- builder/hyperv/iso/builder_test.go | 3 ++- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index ef174a58429..9a18c133f05 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -22,15 +22,15 @@ import ( ) const ( - DefaultDiskSize = 40000 // ~40GB - MinDiskSize = 10 * 1024 // 10GB - MaxDiskSize = 65536 * 1024 // 64TB + DefaultDiskSize = 40 * 1024 // ~40GB + MinDiskSize = 256 // 256MB + MaxDiskSize = 64 * 1024 * 1024 // 64TB - DefaultRamSize = 1024 // 1GB - MinRamSize = 512 // 512MB - MaxRamSize = 32768 // 32GB + DefaultRamSize = 1 * 1024 // 1GB + MinRamSize = 32 // 32MB + MaxRamSize = 32 * 1024 // 32GB - LowRam = 512 // 512MB + LowRam = 384 // 384MB DefaultUsername = "vagrant" DefaultPassword = "vagrant" @@ -416,9 +416,9 @@ func (b *Builder) checkDiskSize() error { log.Println(fmt.Sprintf("%s: %v", "DiskSize", b.config.DiskSize)) if b.config.DiskSize < MinDiskSize { - return fmt.Errorf("disk_size_gb: Windows server requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) + return fmt.Errorf("disk_size_gb: Virtual machine requires disk space >= %v GB, but defined: %v", MinDiskSize, b.config.DiskSize/1024) } else if b.config.DiskSize > MaxDiskSize { - return fmt.Errorf("disk_size_gb: Windows server requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) + return fmt.Errorf("disk_size_gb: Virtual machine requires disk space <= %v GB, but defined: %v", MaxDiskSize, b.config.DiskSize/1024) } return nil @@ -432,9 +432,9 @@ func (b *Builder) checkRamSize() error { log.Println(fmt.Sprintf("%s: %v", "RamSize", b.config.RamSizeMB)) if b.config.RamSizeMB < MinRamSize { - return fmt.Errorf("ram_size_mb: Windows server requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSizeMB) + return fmt.Errorf("ram_size_mb: Virtual machine requires memory size >= %v MB, but defined: %v", MinRamSize, b.config.RamSizeMB) } else if b.config.RamSizeMB > MaxRamSize { - return fmt.Errorf("ram_size_mb: Windows server requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSizeMB) + return fmt.Errorf("ram_size_mb: Virtual machine requires memory size <= %v MB, but defined: %v", MaxRamSize, b.config.RamSizeMB) } return nil diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index a17851a3e11..a1da8402ba4 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -13,7 +13,8 @@ func testConfig() map[string]interface{} { "iso_url": "http://www.packer.io", "shutdown_command": "yes", "ssh_username": "foo", - + "ram_size_mb": 64, + "disk_size": 1024, packer.BuildNameConfigKey: "foo", } } From bff85787cdff666f1209fe7aedaf11e3f0e41ab3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 18 Oct 2015 17:32:47 +0100 Subject: [PATCH 033/113] Fix disk size test --- builder/hyperv/iso/builder_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index a1da8402ba4..5311075d0be 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -14,7 +14,7 @@ func testConfig() map[string]interface{} { "shutdown_command": "yes", "ssh_username": "foo", "ram_size_mb": 64, - "disk_size": 1024, + "disk_size": 256, packer.BuildNameConfigKey: "foo", } } @@ -56,11 +56,11 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { t.Fatalf("bad err: %s", err) } - if b.config.DiskSize != 40000 { + if b.config.DiskSize != 40 * 1024 { t.Fatalf("bad size: %d", b.config.DiskSize) } - config["disk_size"] = 60000 + config["disk_size"] = 256 b = Builder{} warns, err = b.Prepare(config) if len(warns) > 0 { @@ -70,7 +70,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { t.Fatalf("should not have error: %s", err) } - if b.config.DiskSize != 60000 { + if b.config.DiskSize != 256 { t.Fatalf("bad size: %d", b.config.DiskSize) } } From 778298e1e5f83171e35a77bcbf478eea7fbb5ebc Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 25 Oct 2015 22:28:01 +0000 Subject: [PATCH 034/113] If there is only one ip address on a card we need to force it to be an array. Otherwise powershell may treat it as a property. Stop-Vm with force parameter to ensure that powershell prompts will not appear --- powershell/hyperv/hyperv.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 6a512d1f49e..0b5d14869ac 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -15,7 +15,7 @@ if ($HostVMAdapter){ if ($HostNetAdapter){ $HostNetAdapterConfiguration = @(get-wmiobject win32_networkadapterconfiguration -filter "IPEnabled = 'TRUE' AND InterfaceIndex=$($HostNetAdapter.ifIndex)") if ($HostNetAdapterConfiguration){ - return $HostNetAdapterConfiguration.IpAddress[$addressIndex] + return @($HostNetAdapterConfiguration.IpAddress)[$addressIndex] } } } @@ -477,7 +477,7 @@ func TurnOff(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -TurnOff -Confirm:$false + Stop-VM -Name $vmName -TurnOff -Confirm:$false -Force } ` @@ -492,7 +492,7 @@ func ShutDown(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -Confirm:$false + Stop-VM -Name $vmName -Confirm:$false -Force } ` From 2c43946612447ab5ab5f7bb6ab404cab28acda98 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 25 Oct 2015 23:11:01 +0000 Subject: [PATCH 035/113] Incude hyperv as part of the plugins --- command/plugin.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/command/plugin.go b/command/plugin.go index cf213b3ef2f..15c26b9867e 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -31,6 +31,7 @@ import ( filebuilder "github.com/mitchellh/packer/builder/file" fileprovisioner "github.com/mitchellh/packer/provisioner/file" googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" + hypervbuilder "github.com/mitchellh/packer/builder/hyperv" nullbuilder "github.com/mitchellh/packer/builder/null" openstackbuilder "github.com/mitchellh/packer/builder/openstack" parallelsisobuilder "github.com/mitchellh/packer/builder/parallels/iso" @@ -66,6 +67,7 @@ var Builders = map[string]packer.Builder{ "docker": new(dockerbuilder.Builder), "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), + "hyperv": new(hypervbuilder.Builder), "null": new(nullbuilder.Builder), "openstack": new(openstackbuilder.Builder), "parallels-iso": new(parallelsisobuilder.Builder), From 3a67c0b51a5f6e39ad789198fdad0ab6a6ab1306 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 25 Oct 2015 23:15:16 +0000 Subject: [PATCH 036/113] Must point to iso hyperv plugin --- command/plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/plugin.go b/command/plugin.go index 15c26b9867e..5db0f3b9ef5 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -31,7 +31,7 @@ import ( filebuilder "github.com/mitchellh/packer/builder/file" fileprovisioner "github.com/mitchellh/packer/provisioner/file" googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" - hypervbuilder "github.com/mitchellh/packer/builder/hyperv" + hypervbuilder "github.com/mitchellh/packer/builder/hyperv/iso" nullbuilder "github.com/mitchellh/packer/builder/null" openstackbuilder "github.com/mitchellh/packer/builder/openstack" parallelsisobuilder "github.com/mitchellh/packer/builder/parallels/iso" From 101ccd8c669165b1931bcd04f5133f64a176a1a5 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 25 Oct 2015 23:21:27 +0000 Subject: [PATCH 037/113] Needs iso part --- command/plugin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/plugin.go b/command/plugin.go index 5db0f3b9ef5..e451013d117 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -67,7 +67,7 @@ var Builders = map[string]packer.Builder{ "docker": new(dockerbuilder.Builder), "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), - "hyperv": new(hypervbuilder.Builder), + "hyperv-iso": new(hypervbuilder.Builder), "null": new(nullbuilder.Builder), "openstack": new(openstackbuilder.Builder), "parallels-iso": new(parallelsisobuilder.Builder), From 7e2620ef5b64578758d21772bc90e61da737d870 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 27 Oct 2015 00:12:41 +0000 Subject: [PATCH 038/113] Always force turn off vm --- powershell/hyperv/hyperv.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 0b5d14869ac..3cf5d8a386f 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -273,7 +273,7 @@ func StopVirtualMachine(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -VM $vm -Confirm:$false + Stop-VM -VM $vm -Force -Confirm:$false } ` @@ -477,7 +477,7 @@ func TurnOff(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -TurnOff -Confirm:$false -Force + Stop-VM -Name $vmName -TurnOff -Force -Confirm:$false } ` @@ -492,7 +492,7 @@ func ShutDown(vmName string) error { param([string]$vmName) $vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { - Stop-VM -Name $vmName -Confirm:$false -Force + Stop-VM -Name $vmName -Force -Confirm:$false } ` From 52978e89c7a678604b5921c24727ad1187f03d98 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 28 Oct 2015 23:09:31 +0000 Subject: [PATCH 039/113] Seems like we do need to escape the double quote --- provisioner/powershell/provisioner.go | 6 ++--- provisioner/powershell/provisioner_test.go | 28 +++++++++++----------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 571ac2da63e..ad90e56a152 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -112,11 +112,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.EnvVarFormat == "" { - p.config.EnvVarFormat = `$env:%s="%s"; ` + p.config.EnvVarFormat = `$env:%s=\"%s\"; ` } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` + p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` } if p.config.ExecuteCommand == "" { @@ -347,7 +347,7 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string, e // Split vars into key/value components for _, envVar := range p.config.Vars { keyValue := strings.Split(envVar, "=") - + if len(keyValue) != 2 || keyValue[0] == "" { err = errors.New(fmt.Sprintf("Shell provisioner environment variables must be in key=value format. Currently it is '%s'", envVar)) return diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 9ef9ec3013c..e109eb6d5d5 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -58,7 +58,7 @@ func TestProvisioner_Impl(t *testing.T) { func TestProvisionerPrepare_Defaults(t *testing.T) { var p Provisioner config := testConfig() - + err := p.Prepare(config) if err != nil { t.Fatalf("err: %s", err) @@ -95,8 +95,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } } - if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { - t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat) + if p.config.ElevatedEnvVarFormat != `$env:%s=\"%s\"; ` { + t.Fatalf(`Default command should be powershell '$env:%%s=\"%%s\"; ', but got %s`, p.config.ElevatedEnvVarFormat) } } @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& { $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `powershell '& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& { $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -500,7 +500,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -511,7 +511,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " { + if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -522,7 +522,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != "$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; " { + if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -545,7 +545,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -556,7 +556,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { + if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -567,7 +567,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { + if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& { $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { + if cmd != `powershell '& { $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From 2fc2b1d9c87fa0645b3c1900d149bb5509da581b Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 30 Oct 2015 08:23:30 +0000 Subject: [PATCH 040/113] Make use of driver instead of directly referencing hyper Move inline powershell to hyperv --- builder/hyperv/common/driver.go | 60 +++++++ builder/hyperv/common/driver_ps_4.go | 112 +++++++++++++ builder/hyperv/common/step_configure_ip.go | 16 +- builder/hyperv/common/step_configure_vlan.go | 20 +-- .../common/step_create_external_switch.go | 25 ++- builder/hyperv/common/step_create_switch.go | 21 ++- builder/hyperv/common/step_create_vm.go | 39 ++--- builder/hyperv/common/step_disable_vlan.go | 6 +- .../common/step_enable_integration_service.go | 4 +- builder/hyperv/common/step_export_vm.go | 8 +- builder/hyperv/common/step_mount_dvddrive.go | 9 +- .../hyperv/common/step_mount_floppydrive.go | 37 ++--- .../common/step_mount_integration_services.go | 56 +------ .../common/step_polling_installation.go | 41 +---- builder/hyperv/common/step_reboot_vm.go | 5 +- .../hyperv/common/step_unmount_dvddrive.go | 8 +- .../hyperv/common/step_unmount_floppydrive.go | 5 +- .../step_unmount_integration_services.go | 14 +- .../step_wait_for_install_to_complete.go | 44 ++---- powershell/hyperv/hyperv.go | 148 +++++++++++++++++- 20 files changed, 432 insertions(+), 246 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 2d5044576c5..5afa15d7777 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -15,6 +15,12 @@ type Driver interface { // Checks if the VM named is running. IsRunning(string) (bool, error) + // Checks if the VM named is off. + IsOff(string) (bool, error) + + //How long has VM been on + Uptime(vmName string) (uint64, error) + // Start starts a VM specified by the name given. Start(string) error @@ -32,9 +38,63 @@ type Driver interface { // Finds the IP address of a VM connected that uses DHCP by its MAC address IpAddress(string) (string, error) + // Finds the hostname for the ip address + GetHostName(string) (string, error) + // Finds the IP address of a host adapter connected to switch GetHostAdapterIpAddressForSwitch(string) (string, error) // Type scan codes to virtual keyboard of vm TypeScanCodes(string, string) error + + //Get the ip address for network adaptor + GetVirtualMachineNetworkAdapterAddress(string) (string, error) + + //Set the vlan to use for switch + SetNetworkAdapterVlanId(string, string) error + + //Set the vlan to use for machine + SetVirtualMachineVlanId(string, string) error + + UntagVirtualMachineNetworkAdapterVlan(string, string) error + + CreateExternalVirtualSwitch(string, string) error + + GetVirtualMachineSwitchName(string) (string, error) + + ConnectVirtualMachineNetworkAdapterToSwitch(string, string) error + + CreateVirtualSwitch(string, string) (bool, error) + + DeleteVirtualSwitch(string) error + + CreateVirtualMachine(string, string, int64, int64, string, uint) error + + DeleteVirtualMachine(string) error + + SetVirtualMachineCpu(string, uint) error + + SetSecureBoot(string, bool) error + + EnableVirtualMachineIntegrationService(string, string) error + + ExportVirtualMachine(string, string) error + + CompactDisks(string, string) error + + CopyExportedVirtualMachine(string, string, string, string) error + + RestartVirtualMachine(string) error + + CreateDvdDrive(string, uint) (uint, uint, error) + + MountDvdDrive(string, string) error + + MountDvdDriveByLocation(string, string, uint, uint) error + + UnmountDvdDrive(string) error + + DeleteDvdDrive(string, string, string) error + + UnmountFloppyDrive(vmName string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index b9371a8c0c4..5662ce0d848 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -39,6 +39,14 @@ func (d *HypervPS4Driver) IsRunning(vmName string) (bool, error) { return hyperv.IsRunning(vmName) } +func (d *HypervPS4Driver) IsOff(vmName string) (bool, error) { + return hyperv.IsOff(vmName) +} + +func (d *HypervPS4Driver) Uptime(vmName string) (uint64, error) { + return hyperv.Uptime(vmName) +} + // Start starts a VM specified by the name given. func (d *HypervPS4Driver) Start(vmName string) error { return hyperv.StartVirtualMachine(vmName) @@ -97,6 +105,11 @@ func (d *HypervPS4Driver) IpAddress(mac string) (string, error) { return res, err } +// Get host name from ip address +func (d *HypervPS4Driver) GetHostName(ip string) (string, error) { + return powershell.GetHostName(ip) +} + // Finds the IP address of a host adapter connected to switch func (d *HypervPS4Driver) GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { res, err := hyperv.GetHostAdapterIpAddressForSwitch(switchName) @@ -117,6 +130,105 @@ func (d *HypervPS4Driver) TypeScanCodes(vmName string, scanCodes string) error { return hyperv.TypeScanCodes(vmName, scanCodes) } +// Get network adapter address +func (d *HypervPS4Driver) GetVirtualMachineNetworkAdapterAddress(vmName string) (string, error) { + return hyperv.GetVirtualMachineNetworkAdapterAddress(vmName) +} + +//Set the vlan to use for switch +func (d *HypervPS4Driver) SetNetworkAdapterVlanId(switchName string, vlanId string) error { + return hyperv.SetNetworkAdapterVlanId(switchName, vlanId) +} + +//Set the vlan to use for machine +func (d *HypervPS4Driver) SetVirtualMachineVlanId(vmName string, vlanId string) error { + return hyperv.SetVirtualMachineVlanId(vmName, vlanId) +} + +func (d *HypervPS4Driver) UntagVirtualMachineNetworkAdapterVlan(vmName string, switchName string) error { + return hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName) +} + +func (d *HypervPS4Driver) CreateExternalVirtualSwitch(vmName string, switchName string) error { + return hyperv.CreateExternalVirtualSwitch(vmName, switchName) +} + +func (d *HypervPS4Driver) GetVirtualMachineSwitchName(vmName string) (string, error) { + return hyperv.GetVirtualMachineSwitchName(vmName) +} + +func (d *HypervPS4Driver) ConnectVirtualMachineNetworkAdapterToSwitch(vmName string, switchName string) error { + return hyperv.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, switchName) +} + +func (d *HypervPS4Driver) DeleteVirtualSwitch(switchName string) error { + return hyperv.DeleteVirtualSwitch(switchName) +} + +func (d *HypervPS4Driver) CreateVirtualSwitch(switchName string, switchType string) (bool, error) { + return hyperv.CreateVirtualSwitch(switchName, switchType) +} + +func (d *HypervPS4Driver) CreateVirtualMachine(vmName string, path string, ram int64, diskSize int64, switchName string, generation uint) error { + return hyperv.CreateVirtualMachine(vmName, path, ram, diskSize, switchName, generation) +} + +func (d *HypervPS4Driver) DeleteVirtualMachine(vmName string) error { + return hyperv.DeleteVirtualMachine(vmName) +} + +func (d *HypervPS4Driver) SetVirtualMachineCpu(vmName string, cpu uint) error { + return hyperv.SetVirtualMachineCpu(vmName, cpu) +} + +func (d *HypervPS4Driver) SetSecureBoot(vmName string, enable bool) error { + return hyperv.SetSecureBoot(vmName, enable) +} + +func (d *HypervPS4Driver) EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error { + return hyperv.EnableVirtualMachineIntegrationService(vmName, integrationServiceName) +} + +func (d *HypervPS4Driver) ExportVirtualMachine(vmName string, path string) error { + return hyperv.ExportVirtualMachine(vmName, path) +} + +func (d *HypervPS4Driver) CompactDisks(expPath string, vhdDir string) error { + return hyperv.CompactDisks(expPath, vhdDir) +} + +func (d *HypervPS4Driver) CopyExportedVirtualMachine(expPath string, outputPath string, vhdDir string, vmDir string) error { + return hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) +} + +func (d *HypervPS4Driver) RestartVirtualMachine(vmName string) error { + return hyperv.RestartVirtualMachine(vmName) +} + +func (d *HypervPS4Driver) CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { + return hyperv.CreateDvdDrive(vmName, generation) +} + +func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string) error { + return hyperv.MountDvdDrive(vmName, path) +} + +func (d *HypervPS4Driver) MountDvdDriveByLocation(vmName string, path string, controllerNumber uint, controllerLocation uint) error { + return hyperv.MountDvdDriveByLocation(vmName, path, controllerNumber, controllerLocation) +} + +func (d *HypervPS4Driver) UnmountDvdDrive(vmName string) error { + return hyperv.UnmountDvdDrive(vmName) +} + +func (d *HypervPS4Driver) DeleteDvdDrive(vmName string, controllerNumber string, controllerLocation string) error { + return hyperv.DeleteDvdDrive(vmName, controllerNumber, controllerLocation) +} + +func (d *HypervPS4Driver) UnmountFloppyDrive(vmName string) error { + return hyperv.UnmountFloppyDrive(vmName) +} + func (d *HypervPS4Driver) verifyPSVersion() error { log.Printf("Enter method: %s", "verifyPSVersion") diff --git a/builder/hyperv/common/step_configure_ip.go b/builder/hyperv/common/step_configure_ip.go index ea35818e9b6..4396a1b9a24 100644 --- a/builder/hyperv/common/step_configure_ip.go +++ b/builder/hyperv/common/step_configure_ip.go @@ -8,19 +8,16 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "log" "strings" "time" - "log" - powershell "github.com/mitchellh/packer/powershell" - "github.com/mitchellh/packer/powershell/hyperv" ) - type StepConfigureIp struct { } func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { -// driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error configuring ip address: %s" @@ -34,7 +31,7 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { var ip string for count != 0 { - cmdOut, err := hyperv.GetVirtualMachineNetworkAdapterAddress(vmName) + cmdOut, err := driver.GetVirtualMachineNetworkAdapterAddress(vmName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -45,7 +42,7 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { ip = strings.TrimSpace(string(cmdOut)) if ip != "False" { - break; + break } log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration))) @@ -53,7 +50,7 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { count-- } - if(count == 0){ + if count == 0 { err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty") state.Put("error", err) ui.Error(err.Error()) @@ -62,7 +59,7 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { ui.Say("ip address is " + ip) - hostName, err := powershell.GetHostName(ip); + hostName, err := driver.GetHostName(ip) if err != nil { state.Put("error", err) ui.Error(err.Error()) @@ -80,4 +77,3 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { func (s *StepConfigureIp) Cleanup(state multistep.StateBag) { // do nothing } - diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go index 508b43e9879..94f445ddd93 100644 --- a/builder/hyperv/common/step_configure_vlan.go +++ b/builder/hyperv/common/step_configure_vlan.go @@ -8,20 +8,14 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" -) - - -const( - vlanId = "1724" ) type StepConfigureVlan struct { + vlanId string } func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { - //config := state.Get("config").(*config) - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error configuring vlan: %s" @@ -30,7 +24,13 @@ func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Configuring vlan...") - err := hyperv.SetNetworkAdapterVlanId(switchName, vlanId) + vlanId := s.vlanId + + if vlanId == "" { + vlanId = "1724" + } + + err := driver.SetNetworkAdapterVlanId(switchName, vlanId) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -38,7 +38,7 @@ func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = hyperv.SetVirtualMachineVlanId(vmName, vlanId) + err = driver.SetVirtualMachineVlanId(vmName, vlanId) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/common/step_create_external_switch.go b/builder/hyperv/common/step_create_external_switch.go index 1685d069e5c..4ab1c335b58 100644 --- a/builder/hyperv/common/step_create_external_switch.go +++ b/builder/hyperv/common/step_create_external_switch.go @@ -5,11 +5,10 @@ package common import ( + "code.google.com/p/go-uuid/uuid" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "code.google.com/p/go-uuid/uuid" - "github.com/mitchellh/packer/powershell/hyperv" ) // This step creates switch for VM. @@ -17,12 +16,12 @@ import ( // Produces: // SwitchName string - The name of the Switch type StepCreateExternalSwitch struct { - SwitchName string + SwitchName string oldSwitchName string } func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) @@ -33,16 +32,16 @@ func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepA packerExternalSwitchName := "paes_" + uuid.New() - err = hyperv.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) + err = driver.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) if err != nil { err := fmt.Errorf("Error creating switch: %s", err) state.Put(errorMsg, err) ui.Error(err.Error()) - s.SwitchName = ""; + s.SwitchName = "" return multistep.ActionHalt } - - switchName, err := hyperv.GetVirtualMachineSwitchName(vmName) + + switchName, err := driver.GetVirtualMachineSwitchName(vmName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -59,10 +58,10 @@ func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepA ui.Say("External switch name is: '" + switchName + "'") - if(switchName != packerExternalSwitchName){ + if switchName != packerExternalSwitchName { s.SwitchName = "" } else { - s.SwitchName = packerExternalSwitchName + s.SwitchName = packerExternalSwitchName s.oldSwitchName = state.Get("SwitchName").(string) } @@ -76,7 +75,7 @@ func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) { if s.SwitchName == "" { return } - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) @@ -92,7 +91,7 @@ func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) { return } - err = hyperv.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, s.oldSwitchName) + err = driver.ConnectVirtualMachineNetworkAdapterToSwitch(vmName, s.oldSwitchName) if err != nil { ui.Error(fmt.Sprintf(errMsg, err)) return @@ -100,7 +99,7 @@ func (s *StepCreateExternalSwitch) Cleanup(state multistep.StateBag) { state.Put("SwitchName", s.oldSwitchName) - err = hyperv.DeleteVirtualSwitch(s.SwitchName) + err = driver.DeleteVirtualSwitch(s.SwitchName) if err != nil { ui.Error(fmt.Sprintf(errMsg, err)) } diff --git a/builder/hyperv/common/step_create_switch.go b/builder/hyperv/common/step_create_switch.go index 5620e3f9383..b8eb029626b 100644 --- a/builder/hyperv/common/step_create_switch.go +++ b/builder/hyperv/common/step_create_switch.go @@ -8,13 +8,12 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) const ( SwitchTypeInternal = "Internal" - SwitchTypePrivate = "Private" - DefaultSwitchType = SwitchTypeInternal + SwitchTypePrivate = "Private" + DefaultSwitchType = SwitchTypeInternal ) // This step creates switch for VM. @@ -23,21 +22,21 @@ const ( // SwitchName string - The name of the Switch type StepCreateSwitch struct { // Specifies the name of the switch to be created. - SwitchName string + SwitchName string // Specifies the type of the switch to be created. Allowed values are Internal and Private. To create an External // virtual switch, specify either the NetAdapterInterfaceDescription or the NetAdapterName parameter, which // implicitly set the type of the virtual switch to External. - SwitchType string + SwitchType string // Specifies the name of the network adapter to be bound to the switch to be created. NetAdapterName string // Specifies the interface description of the network adapter to be bound to the switch to be created. NetAdapterInterfaceDescription string - createdSwitch bool + createdSwitch bool } func (s *StepCreateSwitch) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) if len(s.SwitchType) == 0 { @@ -46,12 +45,12 @@ func (s *StepCreateSwitch) Run(state multistep.StateBag) multistep.StepAction { ui.Say(fmt.Sprintf("Creating switch '%v' if required...", s.SwitchName)) - createdSwitch, err := hyperv.CreateVirtualSwitch(s.SwitchName, s.SwitchType) + createdSwitch, err := driver.CreateVirtualSwitch(s.SwitchName, s.SwitchType) if err != nil { err := fmt.Errorf("Error creating switch: %s", err) state.Put("error", err) ui.Error(err.Error()) - s.SwitchName = ""; + s.SwitchName = "" return multistep.ActionHalt } @@ -72,11 +71,11 @@ func (s *StepCreateSwitch) Cleanup(state multistep.StateBag) { return } - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Unregistering and deleting switch...") - err := hyperv.DeleteVirtualSwitch(s.SwitchName) + err := driver.DeleteVirtualSwitch(s.SwitchName) if err != nil { ui.Error(fmt.Sprintf("Error deleting switch: %s", err)) } diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 6418dbea06e..6d142cb0b3c 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -8,8 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" - "strconv" ) // This step creates the actual virtual machine. @@ -17,50 +15,47 @@ import ( // Produces: // VMName string - The name of the VM type StepCreateVM struct { - VMName string - SwitchName string - RamSizeMB uint - DiskSize uint - Generation uint - Cpu uint + VMName string + SwitchName string + RamSizeMB uint + DiskSize uint + Generation uint + Cpu uint EnabeSecureBoot bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Creating virtual machine...") path := state.Get("packerTempDir").(string) // convert the MB to bytes - ramBytes := int64(s.RamSizeMB * 1024 * 1024) - diskSizeBytes := int64(s.DiskSize * 1024 * 1024) + ram := int64(s.RamSizeMB * 1024 * 1024) + diskSize := int64(s.DiskSize * 1024 * 1024) - ram := strconv.FormatInt(ramBytes, 10) - diskSize := strconv.FormatInt(diskSizeBytes, 10) switchName := s.SwitchName - generation := strconv.FormatInt(int64(s.Generation), 10) - cpu := strconv.FormatInt(int64(s.Cpu), 10) enabeSecureBoot := s.EnabeSecureBoot - err := hyperv.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, generation) + err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, s.Generation) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - - err = hyperv.SetVirtualMachineCpu(s.VMName, cpu) + + err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - - if generation == "2" { - err = hyperv.SetSecureBoot(s.VMName, enabeSecureBoot) + + if s.Generation == 2 { + err = driver.SetSecureBoot(s.VMName, enabeSecureBoot) if err != nil { err := fmt.Errorf("Error setting secure boot: %s", err) state.Put("error", err) @@ -80,11 +75,11 @@ func (s *StepCreateVM) Cleanup(state multistep.StateBag) { return } - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Unregistering and deleting virtual machine...") - err := hyperv.DeleteVirtualMachine(s.VMName) + err := driver.DeleteVirtualMachine(s.VMName) if err != nil { ui.Error(fmt.Sprintf("Error deleting virtual machine: %s", err)) } diff --git a/builder/hyperv/common/step_disable_vlan.go b/builder/hyperv/common/step_disable_vlan.go index 264affbddb9..631c941d207 100644 --- a/builder/hyperv/common/step_disable_vlan.go +++ b/builder/hyperv/common/step_disable_vlan.go @@ -8,15 +8,13 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepDisableVlan struct { } func (s *StepDisableVlan) Run(state multistep.StateBag) multistep.StepAction { - //config := state.Get("config").(*config) - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error disabling vlan: %s" @@ -25,7 +23,7 @@ func (s *StepDisableVlan) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Disabling vlan...") - err := hyperv.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName) + err := driver.UntagVirtualMachineNetworkAdapterVlan(vmName, switchName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/common/step_enable_integration_service.go b/builder/hyperv/common/step_enable_integration_service.go index 89259d4d88d..05b148232e2 100644 --- a/builder/hyperv/common/step_enable_integration_service.go +++ b/builder/hyperv/common/step_enable_integration_service.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepEnableIntegrationService struct { @@ -16,13 +15,14 @@ type StepEnableIntegrationService struct { } func (s *StepEnableIntegrationService) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Enabling Integration Service...") vmName := state.Get("vmName").(string) s.name = "Guest Service Interface" - err := hyperv.EnableVirtualMachineIntegrationService(vmName, s.name) + err := driver.EnableVirtualMachineIntegrationService(vmName, s.name) if err != nil { err := fmt.Errorf("Error enabling Integration Service: %s", err) diff --git a/builder/hyperv/common/step_export_vm.go b/builder/hyperv/common/step_export_vm.go index d226c25d1aa..2ee10e16c8f 100644 --- a/builder/hyperv/common/step_export_vm.go +++ b/builder/hyperv/common/step_export_vm.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" "io/ioutil" "path/filepath" ) @@ -24,6 +23,7 @@ type StepExportVm struct { } func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) var err error @@ -45,7 +45,7 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Exporting vm...") - err = hyperv.ExportVirtualMachine(vmName, vmExportPath) + err = driver.ExportVirtualMachine(vmName, vmExportPath) if err != nil { errorMsg = "Error exporting vm: %s" err := fmt.Errorf(errorMsg, err) @@ -61,7 +61,7 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Skipping disk compaction...") } else { ui.Say("Compacting disks...") - err = hyperv.CompactDisks(expPath, vhdDir) + err = driver.CompactDisks(expPath, vhdDir) if err != nil { errorMsg = "Error compacting disks: %s" err := fmt.Errorf(errorMsg, err) @@ -72,7 +72,7 @@ func (s *StepExportVm) Run(state multistep.StateBag) multistep.StepAction { } ui.Say("Coping to output dir...") - err = hyperv.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) + err = driver.CopyExportedVirtualMachine(expPath, outputPath, vhdDir, vmDir) if err != nil { errorMsg = "Error exporting vm: %s" err := fmt.Errorf(errorMsg, err) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 56856566ffc..a9129a3787b 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -9,7 +9,6 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" powershell "github.com/mitchellh/packer/powershell" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepMountDvdDrive struct { @@ -18,7 +17,7 @@ type StepMountDvdDrive struct { } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error mounting dvd drive: %s" @@ -56,7 +55,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Mounting dvd drive...") - err = hyperv.MountDvdDrive(vmName, isoPath) + err = driver.MountDvdDrive(vmName, isoPath) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -74,6 +73,8 @@ func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { return } + driver := state.Get("driver").(Driver) + errorMsg := "Error unmounting dvd drive: %s" vmName := state.Get("vmName").(string) @@ -81,7 +82,7 @@ func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { ui.Say("Unmounting dvd drive...") - err := hyperv.UnmountDvdDrive(vmName) + err := driver.UnmountDvdDrive(vmName) if err != nil { ui.Error(fmt.Sprintf(errorMsg, err)) } diff --git a/builder/hyperv/common/step_mount_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go index c3005414151..78f9f091442 100644 --- a/builder/hyperv/common/step_mount_floppydrive.go +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -6,28 +6,24 @@ package common import ( "fmt" - "os" - "strings" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/powershell" "github.com/mitchellh/packer/powershell/hyperv" - "log" "io" "io/ioutil" + "log" + "os" "path/filepath" + "strings" ) - -const( +const ( FloppyFileName = "assets.vfd" ) - - - type StepSetUnattendedProductKey struct { - Files []string + Files []string ProductKey string } @@ -36,15 +32,15 @@ func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.St if s.ProductKey == "" { ui.Say("No product key specified...") - return multistep.ActionContinue + return multistep.ActionContinue } index := -1 for i, value := range s.Files { - if s.caseInsensitiveContains(value, "Autounattend.xml") { - index = i - break - } + if s.caseInsensitiveContains(value, "Autounattend.xml") { + index = i + break + } } ui.Say("Setting product key in Autounattend.xml...") @@ -59,10 +55,9 @@ func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.St return multistep.ActionContinue } - func (s *StepSetUnattendedProductKey) caseInsensitiveContains(str, substr string) bool { - str, substr = strings.ToUpper(str), strings.ToUpper(substr) - return strings.Contains(str, substr) + str, substr = strings.ToUpper(str), strings.ToUpper(substr) + return strings.Contains(str, substr) } func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, error) { @@ -92,12 +87,9 @@ func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, err return autounattend, nil } - func (s *StepSetUnattendedProductKey) Cleanup(state multistep.StateBag) { } - - type StepMountFloppydrive struct { floppyPath string } @@ -119,7 +111,7 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio if err != nil { state.Put("error", fmt.Errorf("Error preparing floppy: %s", err)) return multistep.ActionHalt - } + } ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) @@ -135,7 +127,8 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio // Track the path so that we can unregister it from Hyper-V later s.floppyPath = floppyPath - return multistep.ActionContinue} + return multistep.ActionContinue +} func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { if s.floppyPath == "" { diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go index 63e775a0d6b..3172969163b 100644 --- a/builder/hyperv/common/step_mount_integration_services.go +++ b/builder/hyperv/common/step_mount_integration_services.go @@ -8,9 +8,10 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - powershell "github.com/mitchellh/packer/powershell" + hyperv "github.com/mitchellh/packer/powershell/hyperv" "log" "os" + "strconv" ) type StepMountSecondaryDvdImages struct { @@ -88,67 +89,22 @@ func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vm } func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) { - var properties DvdControllerProperties - var script powershell.ScriptBuilder - powershell := new(powershell.PowerShellCmd) - - controllerNumber := "0" - if s.Generation < 2 { - // get the controller number that the OS install disk is mounted on - // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("$dvdDrives = (Get-VMDvdDrive -VMName $vmName)") - script.WriteLine("$lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber}") - script.WriteLine("if (!$lastControllerNumber) {") - script.WriteLine(" $lastControllerNumber = 0") - script.WriteLine("} elseif (!$lastControllerNumber -or ($dvdDrives | ?{ $_.ControllerNumber -eq $lastControllerNumber} | measure).count -gt 1) {") - script.WriteLine(" $lastControllerNumber += 1") - script.WriteLine("}") - script.WriteLine("$lastControllerNumber") - controllerNumber, err := powershell.Output(script.String(), vmName) - if err != nil { - return properties, err - } - - if controllerNumber != "0" && controllerNumber != "1" { - //There are only 2 ide controllers, try to use the one the hdd is attached too - controllerNumber = "0" - } - } - script.Reset() - script.WriteLine("param([string]$vmName,[int]$controllerNumber)") - script.WriteLine("Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber") - err := powershell.Run(script.String(), vmName, controllerNumber) + controllerNumber, controllerLocation, err := hyperv.CreateDvdDrive(vmName, s.Generation) if err != nil { return properties, err } - // we could try to get the controller location and number in one call, but this way we do not - // need to parse the output - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation") - controllerLocation, err := powershell.Output(script.String(), vmName) - if err != nil { - return properties, err - } + properties.ControllerNumber = strconv.FormatInt(int64(controllerNumber), 10) + properties.ControllerLocation = strconv.FormatInt(int64(controllerLocation), 10) - script.Reset() - script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)") - script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") - - err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation) + err = hyperv.MountDvdDriveByLocation(vmName, isoPath, controllerNumber, controllerLocation) if err != nil { return properties, err } log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) - properties.ControllerNumber = controllerNumber - properties.ControllerLocation = controllerLocation - return properties, nil } diff --git a/builder/hyperv/common/step_polling_installation.go b/builder/hyperv/common/step_polling_installation.go index c462f3da9cf..34577de541f 100644 --- a/builder/hyperv/common/step_polling_installation.go +++ b/builder/hyperv/common/step_polling_installation.go @@ -5,15 +5,14 @@ package common import ( + "bytes" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "time" -// "net" "log" "os/exec" "strings" - "bytes" + "time" ) const port string = "13000" @@ -29,36 +28,6 @@ func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAct vmIp := state.Get("ip").(string) ui.Say("Start polling VM to check the installation is complete...") -/* - count := 30 - var minutes time.Duration = 1 - sleepMin := time.Minute * minutes - host := vmIp + ":" + port - - timeoutSec := time.Second * 15 - - for count > 0 { - ui.Say(fmt.Sprintf("Connecting vm (%s)...", host )) - conn, err := net.DialTimeout("tcp", host, timeoutSec) - if err == nil { - ui.Say("Done!") - conn.Close() - break; - } - - log.Println(err) - ui.Say(fmt.Sprintf("Waiting more %v minutes...", uint(minutes))) - time.Sleep(sleepMin) - count-- - } - - if count == 0 { - err := fmt.Errorf(errorMsg, "a signal from vm was not received in a given time period ") - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } -*/ host := "'" + vmIp + "'," + port var blockBuffer bytes.Buffer @@ -73,7 +42,7 @@ func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAct var res string for count > 0 { - log.Println(fmt.Sprintf("Connecting vm (%s)...", host )) + log.Println(fmt.Sprintf("Connecting vm (%s)...", host)) cmd := exec.Command("powershell", blockBuffer.String()) cmdOut, err := cmd.Output() if err != nil { @@ -88,8 +57,8 @@ func (s *StepPollingInstalation) Run(state multistep.StateBag) multistep.StepAct if res != "False" { ui.Say("Signal was received from the VM") // Sleep before starting provision - time.Sleep(time.Second*30) - break; + time.Sleep(time.Second * 30) + break } log.Println(fmt.Sprintf("Slipping for more %v seconds...", uint(duration))) diff --git a/builder/hyperv/common/step_reboot_vm.go b/builder/hyperv/common/step_reboot_vm.go index 3053a085597..7be0f3517b2 100644 --- a/builder/hyperv/common/step_reboot_vm.go +++ b/builder/hyperv/common/step_reboot_vm.go @@ -9,14 +9,13 @@ import ( "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "time" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepRebootVm struct { } func (s *StepRebootVm) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) errorMsg := "Error rebooting vm: %s" @@ -24,7 +23,7 @@ func (s *StepRebootVm) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Rebooting vm...") - err := hyperv.RestartVirtualMachine(vmName) + err := driver.RestartVirtualMachine(vmName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go index 57ffb422d9e..c5a4f2f84eb 100644 --- a/builder/hyperv/common/step_unmount_dvddrive.go +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -8,22 +8,20 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) - type StepUnmountDvdDrive struct { } func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - + ui.Say("Unmounting dvd drive...") - err := hyperv.UnmountDvdDrive(vmName) + err := driver.UnmountDvdDrive(vmName) if err != nil { err := fmt.Errorf("Error unmounting dvd drive: %s", err) state.Put("error", err) diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index 87d1fd4a37a..21ae1dcc1b3 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell/hyperv" ) type StepUnmountFloppyDrive struct { @@ -16,7 +15,7 @@ type StepUnmountFloppyDrive struct { } func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAction { - //driver := state.Get("driver").(Driver) + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) if s.Generation > 1 { @@ -28,7 +27,7 @@ func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAct ui.Say("Unmounting floppy drive (Run)...") - err := hyperv.UnmountFloppyDrive(vmName) + err := driver.UnmountFloppyDrive(vmName) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go index d792457825c..d2719744bc5 100644 --- a/builder/hyperv/common/step_unmount_integration_services.go +++ b/builder/hyperv/common/step_unmount_integration_services.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - powershell "github.com/mitchellh/packer/powershell" "log" ) @@ -16,6 +15,7 @@ type StepUnmountSecondaryDvdImages struct { } func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) ui.Say("Unmounting Integration Services Setup Disk...") @@ -28,17 +28,7 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. log.Println(fmt.Sprintf("Found DVD properties %d", len(dvdProperties))) for _, dvdProperty := range dvdProperties { - controllerNumber := dvdProperty.ControllerNumber - controllerLocation := dvdProperty.ControllerLocation - - var script powershell.ScriptBuilder - powershell := new(powershell.PowerShellCmd) - - script.WriteLine("param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation)") - script.WriteLine("$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") - script.WriteLine("if (!$vmDvdDrive) {throw 'unable to find dvd drive'}") - script.WriteLine("Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") - err := powershell.Run(script.String(), vmName, controllerNumber, controllerLocation) + err := driver.DeleteDvdDrive(vmName, dvdProperty.ControllerNumber, dvdProperty.ControllerLocation) if err != nil { state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/hyperv/common/step_wait_for_install_to_complete.go b/builder/hyperv/common/step_wait_for_install_to_complete.go index c4fafbef0a4..46fc7e5cd50 100644 --- a/builder/hyperv/common/step_wait_for_install_to_complete.go +++ b/builder/hyperv/common/step_wait_for_install_to_complete.go @@ -8,10 +8,7 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "strings" - "strconv" "time" - powershell "github.com/mitchellh/packer/powershell" ) const ( @@ -22,29 +19,25 @@ type StepWaitForPowerOff struct { } func (s *StepWaitForPowerOff) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) ui.Say("Waiting for vm to be powered down...") - var script powershell.ScriptBuilder - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VM -Name $vmName).State -eq [Microsoft.HyperV.PowerShell.VMState]::Off") - isOffScript := script.String() - for { - powershell := new(powershell.PowerShellCmd) - cmdOut, err := powershell.Output(isOffScript, vmName); + isOff, err := driver.IsOff(vmName) + if err != nil { - err := fmt.Errorf("Error checking VM's state: %s", err) + err := fmt.Errorf("Error checking if vm is off: %s", err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - if cmdOut == "True" { + if isOff { break } else { - time.Sleep(time.Second * SleepSeconds); + time.Sleep(time.Second * SleepSeconds) } } @@ -56,29 +49,24 @@ func (s *StepWaitForPowerOff) Cleanup(state multistep.StateBag) { type StepWaitForInstallToComplete struct { ExpectedRebootCount uint - ActionName string + ActionName string } func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - if(len(s.ActionName)>0){ - ui.Say(fmt.Sprintf("%v ! Waiting for VM to reboot %v times...",s.ActionName, s.ExpectedRebootCount)) + if len(s.ActionName) > 0 { + ui.Say(fmt.Sprintf("%v ! Waiting for VM to reboot %v times...", s.ActionName, s.ExpectedRebootCount)) } var rebootCount uint var lastUptime uint64 - var script powershell.ScriptBuilder - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VM -Name $vmName).Uptime.TotalSeconds") - - uptimeScript := script.String() - for rebootCount < s.ExpectedRebootCount { - powershell := new(powershell.PowerShellCmd) - cmdOut, err := powershell.Output(uptimeScript, vmName); + uptime, err := driver.Uptime(vmName) + if err != nil { err := fmt.Errorf("Error checking uptime: %s", err) state.Put("error", err) @@ -86,20 +74,18 @@ func (s *StepWaitForInstallToComplete) Run(state multistep.StateBag) multistep.S return multistep.ActionHalt } - uptime, _ := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 64) - if uint64(uptime) < lastUptime { + if uptime < lastUptime { rebootCount++ ui.Say(fmt.Sprintf("%v -> Detected reboot %v after %v seconds...", s.ActionName, rebootCount, lastUptime)) } lastUptime = uptime - if (rebootCount < s.ExpectedRebootCount) { - time.Sleep(time.Second * SleepSeconds); + if rebootCount < s.ExpectedRebootCount { + time.Sleep(time.Second * SleepSeconds) } } - return multistep.ActionContinue } diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 3cf5d8a386f..162b048fafc 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -2,6 +2,7 @@ package hyperv import ( "github.com/mitchellh/packer/powershell" + "strconv" "strings" ) @@ -50,6 +51,73 @@ $ip return cmdOut, err } +func CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { + var ps powershell.PowerShellCmd + var script string + var controllerNumber uint + controllerNumber = 0 + if generation < 2 { + // get the controller number that the OS install disk is mounted on + // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives + script = ` +param([string]$vmName) +$dvdDrives = (Get-VMDvdDrive -VMName $vmName) +$lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber} +if (!$lastControllerNumber) { + $lastControllerNumber = 0 +} elseif (!$lastControllerNumber -or ($dvdDrives | ?{ $_.ControllerNumber -eq $lastControllerNumber} | measure).count -gt 1) { + $lastControllerNumber += 1 +} +$lastControllerNumber +` + cmdOut, err := ps.Output(script, vmName) + if err != nil { + return 0, 0, err + } + + controllerNumberTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64) + if err != nil { + return 0, 0, err + } + + controllerNumber = uint(controllerNumberTemp) + + if controllerNumber != 0 && controllerNumber != 1 { + //There are only 2 ide controllers, try to use the one the hdd is attached too + controllerNumber = 0 + } + } + + script = ` +param([string]$vmName,[int]$controllerNumber) +Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber +` + cmdOut, err := ps.Output(script, vmName) + if err != nil { + return controllerNumber, 0, err + } + + // we could try to get the controller location and number in one call, but this way we do not + // need to parse the output + script = ` +param([string]$vmName) +(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation +` + + cmdOut, err = ps.Output(script, vmName) + if err != nil { + return controllerNumber, 0, err + } + + controllerLocationTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64) + if err != nil { + return controllerNumber, 0, err + } + + controllerLocation := uint(controllerLocationTemp) + return controllerNumber, controllerLocation, err +} + func MountDvdDrive(vmName string, path string) error { var script = ` @@ -62,6 +130,18 @@ Set-VMDvdDrive -VMName $vmName -Path $path return err } +func MountDvdDriveByLocation(vmName string, path string, controllerNumber uint, controllerLocation uint) error { + + var script = ` +param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation) +Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, path, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + return err +} + func UnmountDvdDrive(vmName string) error { var script = ` param([string]$vmName) @@ -73,6 +153,19 @@ Get-VMDvdDrive -VMName $vmName | Set-VMDvdDrive -Path $null return err } +func DeleteDvdDrive(vmName string, controllerNumber string, controllerLocation string) error { + var script = ` +param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) +$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +if (!$vmDvdDrive) {throw 'unable to find dvd drive'} +Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, controllerNumber, controllerLocation) + return err +} + func MountFloppyDrive(vmName string, path string) error { var script = ` param([string]$vmName, [string]$path) @@ -96,9 +189,9 @@ Set-VMFloppyDiskDrive -VMName $vmName -Path $null return err } -func CreateVirtualMachine(vmName string, path string, ram string, diskSize string, switchName string, generation string) error { +func CreateVirtualMachine(vmName string, path string, ram int64, diskSize int64, switchName string, generation uint) error { - if generation == "2" { + if generation == 2 { var script = ` param([string]$vmName, [string]$path, [long]$memoryStartupBytes, [long]$newVHDSizeBytes, [string]$switchName, [int]$generation) $vhdx = $vmName + '.vhdx' @@ -106,7 +199,7 @@ $vhdPath = Join-Path -Path $path -ChildPath $vhdx New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName -Generation $generation ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, ram, diskSize, switchName, generation) + err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName, strconv.FormatInt(int64(generation), 10)) return err } else { var script = ` @@ -116,12 +209,12 @@ $vhdPath = Join-Path -Path $path -ChildPath $vhdx New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHDPath $vhdPath -NewVHDSizeBytes $newVHDSizeBytes -SwitchName $switchName ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path, ram, diskSize, switchName) + err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName) return err } } -func SetVirtualMachineCpu(vmName string, cpu string) error { +func SetVirtualMachineCpu(vmName string, cpu uint) error { var script = ` param([string]$vmName, [int]$cpu) @@ -129,7 +222,7 @@ Set-VMProcessor -VMName $vmName -Count $cpu ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, cpu) + err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10)) return err } @@ -425,10 +518,53 @@ $vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running var ps powershell.PowerShellCmd cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return false, err + } + var isRunning = strings.TrimSpace(cmdOut) == "True" return isRunning, err } +func IsOff(vmName string) (bool, error) { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +$vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Off +` + + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return false, err + } + + var isRunning = strings.TrimSpace(cmdOut) == "True" + return isRunning, err +} + +func Uptime(vmName string) (uint64, error) { + + var script = ` +param([string]$vmName) +$vm = Get-VM -Name $vmName -ErrorAction SilentlyContinue +$vm.Uptime.TotalSeconds +` + var ps powershell.PowerShellCmd + cmdOut, err := ps.Output(script, vmName) + + if err != nil { + return 0, err + } + + uptime, err := strconv.ParseUint(strings.TrimSpace(string(cmdOut)), 10, 64) + + return uptime, err +} + func Mac(vmName string) (string, error) { var script = ` param([string]$vmName, [int]$adapterIndex) From edea1549c20a5f7839346e5c966c7fb083b50450 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Fri, 30 Oct 2015 17:19:25 +0000 Subject: [PATCH 041/113] GuestAdditionsMode and GuestAdditionsPath can be set in config. If GuestAdditionsMode == "attach" it will mount the HyperV Integration Services ISO. If GuestAdditionsPath is set, then it will be used as an alternative to where the HyperV Integration Service ISO is. Included the build step to download ISO, so iso_urls works properly now. Online activation should be done via provisioner Installation of integration services should be done via provisioner Cleaned up the way dvd drives are mounted and unmounted (still need to implement feature to find unused drives before adding a new one) Cleaned up the way floppies are mounted and unmounted --- builder/hyperv/common/driver.go | 8 +- builder/hyperv/common/driver_ps_4.go | 10 +- .../common/step_execute_online_activation.go | 62 ---------- .../step_execute_online_activation_full.go | 90 -------------- builder/hyperv/common/step_mount_dvddrive.go | 84 +++++++------ .../hyperv/common/step_mount_floppydrive.go | 92 +++------------ .../common/step_mount_integration_services.go | 110 ------------------ .../hyperv/common/step_unmount_dvddrive.go | 33 ++++-- .../hyperv/common/step_unmount_floppydrive.go | 4 +- .../step_unmount_integration_services.go | 43 ------- .../step_upgrade_integration_services.go | 110 ------------------ builder/hyperv/iso/builder.go | 87 ++++++++++++-- powershell/hyperv/hyperv.go | 17 ++- 13 files changed, 196 insertions(+), 554 deletions(-) delete mode 100644 builder/hyperv/common/step_execute_online_activation.go delete mode 100644 builder/hyperv/common/step_execute_online_activation_full.go delete mode 100644 builder/hyperv/common/step_mount_integration_services.go delete mode 100644 builder/hyperv/common/step_unmount_integration_services.go delete mode 100644 builder/hyperv/common/step_upgrade_integration_services.go diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 5afa15d7777..6ceb37c0f6b 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -91,10 +91,14 @@ type Driver interface { MountDvdDrive(string, string) error MountDvdDriveByLocation(string, string, uint, uint) error + + SetBootDvdDrive(string, uint, uint) error UnmountDvdDrive(string) error - DeleteDvdDrive(string, string, string) error + DeleteDvdDrive(string, uint, uint) error - UnmountFloppyDrive(vmName string) error + MountFloppyDrive(string, string) error + + UnmountFloppyDrive(string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 5662ce0d848..c6d6d4e65b1 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -217,14 +217,22 @@ func (d *HypervPS4Driver) MountDvdDriveByLocation(vmName string, path string, co return hyperv.MountDvdDriveByLocation(vmName, path, controllerNumber, controllerLocation) } +func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { + return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) +} + func (d *HypervPS4Driver) UnmountDvdDrive(vmName string) error { return hyperv.UnmountDvdDrive(vmName) } -func (d *HypervPS4Driver) DeleteDvdDrive(vmName string, controllerNumber string, controllerLocation string) error { +func (d *HypervPS4Driver) DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { return hyperv.DeleteDvdDrive(vmName, controllerNumber, controllerLocation) } +func (d *HypervPS4Driver) MountFloppyDrive(vmName string, path string) error { + return hyperv.MountFloppyDrive(vmName, path) +} + func (d *HypervPS4Driver) UnmountFloppyDrive(vmName string) error { return hyperv.UnmountFloppyDrive(vmName) } diff --git a/builder/hyperv/common/step_execute_online_activation.go b/builder/hyperv/common/step_execute_online_activation.go deleted file mode 100644 index b369934e152..00000000000 --- a/builder/hyperv/common/step_execute_online_activation.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "bytes" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "strings" - "log" -) - -type StepExecuteOnlineActivation struct { -} - -func (s *StepExecuteOnlineActivation) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - comm := state.Get("communicator").(packer.Communicator) - - errorMsg := "Error Executing Online Activation: %s" - - var remoteCmd packer.RemoteCmd - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - var err error - - ui.Say("Executing Online Activation...") - - var blockBuffer bytes.Buffer - blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }") - - remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() - - remoteCmd.Stdout = stdout - remoteCmd.Stderr = stderr - - err = comm.Start(&remoteCmd) - - stderrString := strings.TrimSpace(stderr.String()) - stdoutString := strings.TrimSpace(stdout.String()) - - log.Printf("stdout: %s", stdoutString) - log.Printf("stderr: %s", stderrString) - - if len(stderrString) > 0 { - err = fmt.Errorf(errorMsg, stderrString) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ui.Say(stdoutString) - - return multistep.ActionContinue -} - -func (s *StepExecuteOnlineActivation) Cleanup(state multistep.StateBag) { - // do nothing -} diff --git a/builder/hyperv/common/step_execute_online_activation_full.go b/builder/hyperv/common/step_execute_online_activation_full.go deleted file mode 100644 index 6eb91936c30..00000000000 --- a/builder/hyperv/common/step_execute_online_activation_full.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "bytes" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "strings" - "log" -) - -type StepExecuteOnlineActivationFull struct { - Pk string -} - -func (s *StepExecuteOnlineActivationFull) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - comm := state.Get("communicator").(packer.Communicator) - - errorMsg := "Error Executing Online Activation: %s" - - var remoteCmd packer.RemoteCmd - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - var err error - var stderrString string - var stdoutString string - - ui.Say("Executing Online Activation Full version...") - - var blockBuffer bytes.Buffer - blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" /ipk "+ s.Pk +" //nologo }") - - log.Printf("cmd: %s", blockBuffer.String()) - remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() - - remoteCmd.Stdout = stdout - remoteCmd.Stderr = stderr - - err = comm.Start(&remoteCmd) - - stderrString = strings.TrimSpace(stderr.String()) - stdoutString = strings.TrimSpace(stdout.String()) - - log.Printf("stdout: %s", stdoutString) - log.Printf("stderr: %s", stderrString) - - if len(stderrString) > 0 { - err = fmt.Errorf(errorMsg, stderrString) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - -// ui.Say(stdoutString) - -/* - blockBuffer.Reset() - blockBuffer.WriteString("{ cscript \"$env:SystemRoot/system32/slmgr.vbs\" -ato //nologo }") - - log.Printf("cmd: %s", blockBuffer.String()) - remoteCmd.Command = "-ScriptBlock " + blockBuffer.String() - - err = comm.Start(&remoteCmd) - - stderrString = strings.TrimSpace(stderr.String()) - stdoutString = strings.TrimSpace(stdout.String()) - - log.Printf("stdout: %s", stdoutString) - log.Printf("stderr: %s", stderrString) - - if len(stderrString) > 0 { - err = fmt.Errorf(errorMsg, stderrString) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ui.Say(stdoutString) -*/ - return multistep.ActionContinue -} - -func (s *StepExecuteOnlineActivationFull) Cleanup(state multistep.StateBag) { - // do nothing -} diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index a9129a3787b..f31ef4c2c54 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -8,12 +8,13 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - powershell "github.com/mitchellh/packer/powershell" + "log" ) type StepMountDvdDrive struct { - RawSingleISOUrl string - path string + Generation uint + cleanup bool + dvdProperties DvdControllerProperties } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { @@ -22,39 +23,37 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { errorMsg := "Error mounting dvd drive: %s" vmName := state.Get("vmName").(string) - isoPath := s.RawSingleISOUrl + isoPath := state.Get("iso_path").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? - // Check that there is a virtual dvd drive - var script powershell.ScriptBuilder - powershell := new(powershell.PowerShellCmd) + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName).ControllerNumber") - controllerNumber, err := powershell.Output(script.String(), vmName) + var dvdControllerProperties DvdControllerProperties + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) if err != nil { state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - if controllerNumber == "" { - // Add a virtual dvd drive as there is none - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("Add-VMDvdDrive -VMName $vmName") - script.WriteLine("$dvdDrive = Get-VMDvdDrive -VMName $vmName | Select-Object -first 1") - script.WriteLine("Set-VMFirmware -VMName $vmName -FirstBootDevice $dvdDrive") - err = powershell.Run(script.String(), vmName) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - } - - ui.Say("Mounting dvd drive...") - + dvdControllerProperties.ControllerNumber = controllerNumber + dvdControllerProperties.ControllerLocation = controllerLocation + s.cleanup = true + s.dvdProperties = dvdControllerProperties + + ui.Say("Setting boot drive to os dvd drive %s ...") + err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say(fmt.Sprintf("Mounting os dvd drive %s ...", isoPath)) err = driver.MountDvdDrive(vmName, isoPath) if err != nil { err := fmt.Errorf(errorMsg, err) @@ -62,28 +61,39 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } - - s.path = isoPath + + state.Put("os.dvd.properties", dvdControllerProperties) return multistep.ActionContinue } func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { - if s.path == "" { + if !s.cleanup { return } driver := state.Get("driver").(Driver) - errorMsg := "Error unmounting dvd drive: %s" + errorMsg := "Error unmounting os dvd drive: %s" vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting dvd drive...") - - err := driver.UnmountDvdDrive(vmName) - if err != nil { - ui.Error(fmt.Sprintf(errorMsg, err)) + ui.Say("Clean up os dvd drive...") + + dvdControllerProperties := s.dvdProperties + + if dvdControllerProperties.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting dvd drive: %s", err) + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + if err != nil { + err := fmt.Errorf("Error deleting dvd drive: %s", err) + log.Print(fmt.Sprintf(errorMsg, err)) + } } } diff --git a/builder/hyperv/common/step_mount_floppydrive.go b/builder/hyperv/common/step_mount_floppydrive.go index 78f9f091442..8e8254083f6 100644 --- a/builder/hyperv/common/step_mount_floppydrive.go +++ b/builder/hyperv/common/step_mount_floppydrive.go @@ -8,93 +8,29 @@ import ( "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "github.com/mitchellh/packer/powershell" - "github.com/mitchellh/packer/powershell/hyperv" "io" "io/ioutil" "log" "os" "path/filepath" - "strings" ) const ( FloppyFileName = "assets.vfd" ) -type StepSetUnattendedProductKey struct { - Files []string - ProductKey string -} - -func (s *StepSetUnattendedProductKey) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - if s.ProductKey == "" { - ui.Say("No product key specified...") - return multistep.ActionContinue - } - - index := -1 - for i, value := range s.Files { - if s.caseInsensitiveContains(value, "Autounattend.xml") { - index = i - break - } - } - - ui.Say("Setting product key in Autounattend.xml...") - copyOfAutounattend, err := s.copyAutounattend(s.Files[index]) - if err != nil { - state.Put("error", fmt.Errorf("Error copying Autounattend.xml: %s", err)) - return multistep.ActionHalt - } - - powershell.SetUnattendedProductKey(copyOfAutounattend, s.ProductKey) - s.Files[index] = copyOfAutounattend - return multistep.ActionContinue -} - -func (s *StepSetUnattendedProductKey) caseInsensitiveContains(str, substr string) bool { - str, substr = strings.ToUpper(str), strings.ToUpper(substr) - return strings.Contains(str, substr) -} - -func (s *StepSetUnattendedProductKey) copyAutounattend(path string) (string, error) { - tempdir, err := ioutil.TempDir("", "packer") - if err != nil { - return "", err - } - - autounattend := filepath.Join(tempdir, "Autounattend.xml") - f, err := os.Create(autounattend) - if err != nil { - return "", err - } - defer f.Close() - - sourceF, err := os.Open(path) - if err != nil { - return "", err - } - defer sourceF.Close() - - log.Printf("Copying %s to temp location: %s", path, autounattend) - if _, err := io.Copy(f, sourceF); err != nil { - return "", err - } - - return autounattend, nil -} - -func (s *StepSetUnattendedProductKey) Cleanup(state multistep.StateBag) { -} - type StepMountFloppydrive struct { + Generation uint floppyPath string } func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepAction { + if s.Generation > 1 { + return multistep.ActionContinue + } + + driver := state.Get("driver").(Driver) + // Determine if we even have a floppy disk to attach var floppyPath string if floppyPathRaw, ok := state.GetOk("floppy_path"); ok { @@ -118,7 +54,7 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio ui.Say("Mounting floppy drive...") - err = hyperv.MountFloppyDrive(vmName, floppyPath) + err = driver.MountFloppyDrive(vmName, floppyPath) if err != nil { state.Put("error", fmt.Errorf("Error mounting floppy drive: %s", err)) return multistep.ActionHalt @@ -131,6 +67,10 @@ func (s *StepMountFloppydrive) Run(state multistep.StateBag) multistep.StepActio } func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { + if s.Generation > 1 { + return + } + driver := state.Get("driver").(Driver) if s.floppyPath == "" { return } @@ -140,17 +80,17 @@ func (s *StepMountFloppydrive) Cleanup(state multistep.StateBag) { vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting floppy drive (cleanup)...") + ui.Say("Cleanup floppy drive...") - err := hyperv.UnmountFloppyDrive(vmName) + err := driver.UnmountFloppyDrive(vmName) if err != nil { - ui.Error(fmt.Sprintf(errorMsg, err)) + log.Print(fmt.Sprintf(errorMsg, err)) } err = os.Remove(s.floppyPath) if err != nil { - ui.Error(fmt.Sprintf(errorMsg, err)) + log.Print(fmt.Sprintf(errorMsg, err)) } } diff --git a/builder/hyperv/common/step_mount_integration_services.go b/builder/hyperv/common/step_mount_integration_services.go deleted file mode 100644 index 3172969163b..00000000000 --- a/builder/hyperv/common/step_mount_integration_services.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - hyperv "github.com/mitchellh/packer/powershell/hyperv" - "log" - "os" - "strconv" -) - -type StepMountSecondaryDvdImages struct { - Files []string - Generation uint - dvdProperties []DvdControllerProperties -} - -type DvdControllerProperties struct { - ControllerNumber string - ControllerLocation string -} - -func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - ui.Say("Mounting secondary DVD images...") - - vmName := state.Get("vmName").(string) - - // should be able to mount up to 60 additional iso images using SCSI - // but Windows would only allow a max of 22 due to available drive letters - // Will Windows assign DVD drives to A: and B: ? - - // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - dvdProperties, err := s.mountFiles(vmName) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Println(fmt.Sprintf("Saving DVD properties %d DVDs", len(dvdProperties))) - - state.Put("secondary.dvd.properties", dvdProperties) - - return multistep.ActionContinue -} - -func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { - -} - -func (s *StepMountSecondaryDvdImages) mountFiles(vmName string) ([]DvdControllerProperties, error) { - - var dvdProperties []DvdControllerProperties - - properties, err := s.addAndMountIntegrationServicesSetupDisk(vmName) - if err != nil { - return dvdProperties, err - } - - dvdProperties = append(dvdProperties, properties) - - for _, value := range s.Files { - properties, err := s.addAndMountDvdDisk(vmName, value) - if err != nil { - return dvdProperties, err - } - - dvdProperties = append(dvdProperties, properties) - } - - return dvdProperties, nil -} - -func (s *StepMountSecondaryDvdImages) addAndMountIntegrationServicesSetupDisk(vmName string) (DvdControllerProperties, error) { - - isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" - properties, err := s.addAndMountDvdDisk(vmName, isoPath) - if err != nil { - return properties, err - } - - return properties, nil -} - -func (s *StepMountSecondaryDvdImages) addAndMountDvdDisk(vmName string, isoPath string) (DvdControllerProperties, error) { - var properties DvdControllerProperties - - controllerNumber, controllerLocation, err := hyperv.CreateDvdDrive(vmName, s.Generation) - if err != nil { - return properties, err - } - - properties.ControllerNumber = strconv.FormatInt(int64(controllerNumber), 10) - properties.ControllerLocation = strconv.FormatInt(int64(controllerLocation), 10) - - err = hyperv.MountDvdDriveByLocation(vmName, isoPath, controllerNumber, controllerLocation) - if err != nil { - return properties, err - } - - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) - - return properties, nil -} diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go index c5a4f2f84eb..c0f4d3e1ce1 100644 --- a/builder/hyperv/common/step_unmount_dvddrive.go +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -15,20 +15,31 @@ type StepUnmountDvdDrive struct { func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting dvd drive...") - - err := driver.UnmountDvdDrive(vmName) - if err != nil { - err := fmt.Errorf("Error unmounting dvd drive: %s", err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + ui.Say("Unmounting os dvd drive...") + + dvdController := state.Get("os.dvd.properties").(DvdControllerProperties) + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting os dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + err := fmt.Errorf("Error deleting os dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } - + return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index 21ae1dcc1b3..45546e1d467 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -22,10 +22,10 @@ func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAct return multistep.ActionContinue } - errorMsg := "Error Unmounting floppy drive: %s" vmName := state.Get("vmName").(string) - ui.Say("Unmounting floppy drive (Run)...") + + errorMsg := "Error Unmounting floppy drive: %s" err := driver.UnmountFloppyDrive(vmName) if err != nil { diff --git a/builder/hyperv/common/step_unmount_integration_services.go b/builder/hyperv/common/step_unmount_integration_services.go deleted file mode 100644 index d2719744bc5..00000000000 --- a/builder/hyperv/common/step_unmount_integration_services.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" -) - -type StepUnmountSecondaryDvdImages struct { -} - -func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting Integration Services Setup Disk...") - - vmName := state.Get("vmName").(string) - - // todo: should this message say removing the dvd? - - dvdProperties := state.Get("secondary.dvd.properties").([]DvdControllerProperties) - - log.Println(fmt.Sprintf("Found DVD properties %d", len(dvdProperties))) - - for _, dvdProperty := range dvdProperties { - err := driver.DeleteDvdDrive(vmName, dvdProperty.ControllerNumber, dvdProperty.ControllerLocation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - } - - return multistep.ActionContinue -} - -func (s *StepUnmountSecondaryDvdImages) Cleanup(state multistep.StateBag) { -} diff --git a/builder/hyperv/common/step_upgrade_integration_services.go b/builder/hyperv/common/step_upgrade_integration_services.go deleted file mode 100644 index ab076e7edf2..00000000000 --- a/builder/hyperv/common/step_upgrade_integration_services.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - //"fmt" - "os" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - powershell "github.com/mitchellh/packer/powershell" -) - -type StepUpdateIntegrationServices struct { - Username string - Password string - - newDvdDriveProperties dvdDriveProperties -} - -type dvdDriveProperties struct { - ControllerNumber string - ControllerLocation string -} - -func (s *StepUpdateIntegrationServices) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - vmName := state.Get("vmName").(string) - - ui.Say("Mounting Integration Services Setup Disk...") - - _, err := s.mountIntegrationServicesSetupDisk(vmName); - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - // dvdDriveLetter, err := s.getDvdDriveLetter(vmName) - // if err != nil { - // state.Put("error", err) - // ui.Error(err.Error()) - // return multistep.ActionHalt - // } - - // setup := dvdDriveLetter + ":\\support\\"+osArchitecture+"\\setup.exe /quiet /norestart" - - // ui.Say("Run: " + setup) - - return multistep.ActionContinue -} - -func (s *StepUpdateIntegrationServices) Cleanup(state multistep.StateBag) { - vmName := state.Get("vmName").(string) - - var script powershell.ScriptBuilder - script.WriteLine("param([string]$vmName)") - script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $null") - - powershell := new(powershell.PowerShellCmd) - _ = powershell.Run(script.String(), vmName) -} - -func (s *StepUpdateIntegrationServices) mountIntegrationServicesSetupDisk(vmName string) (dvdDriveProperties, error) { - - var dvdProperties dvdDriveProperties - - var script powershell.ScriptBuilder - script.WriteLine("param([string]$vmName)") - script.WriteLine("Add-VMDvdDrive -VMName $vmName") - - powershell := new(powershell.PowerShellCmd) - err := powershell.Run(script.String(), vmName) - if err != nil { - return dvdProperties, err - } - - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation") - controllerLocation, err := powershell.Output(script.String(), vmName) - if err != nil { - return dvdProperties, err - } - - script.Reset() - script.WriteLine("param([string]$vmName)") - script.WriteLine("(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerNumber") - controllerNumber, err := powershell.Output(script.String(), vmName) - if err != nil { - return dvdProperties, err - } - - isoPath := os.Getenv("WINDIR") + "\\system32\\vmguest.iso" - - script.Reset() - script.WriteLine("param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation)") - script.WriteLine("Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation") - - err = powershell.Run(script.String(), vmName, isoPath, controllerNumber, controllerLocation) - if err != nil { - return dvdProperties, err - } - - dvdProperties.ControllerNumber = controllerNumber - dvdProperties.ControllerLocation = controllerLocation - - return dvdProperties, err -} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 9a18c133f05..40b1a36a75b 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -19,6 +19,7 @@ import ( "log" "strings" "time" + "os" ) const ( @@ -84,12 +85,21 @@ type Config struct { // either an HTTP URL or a file URL (or path to a file). If this is an // HTTP URL, Packer will download it and cache it between runs. RawSingleISOUrl string `mapstructure:"iso_url"` + // Multiple URLs for the ISO to download. Packer will try these in order. // If anything goes wrong attempting to download or while downloading a // single URL, it will move on to the next. All URLs must point to the // same file (same checksum). By default this is empty and iso_url is // used. Only one of iso_url or iso_urls can be specified. ISOUrls []string `mapstructure:"iso_urls"` + + TargetPath string `mapstructure:"iso_target_path"` + + // Should integration services iso be mounted + GuestAdditionsMode string `mapstructure:"guest_additions_mode"` + + // The path to the integration services iso + GuestAdditionsPath string `mapstructure:"guest_additions_path"` // This is the name of the new virtual machine. // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. @@ -234,6 +244,50 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) + if b.config.GuestAdditionsMode == "" { + b.config.GuestAdditionsMode = "attach" + } + + if b.config.GuestAdditionsPath == "" { + b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" + } + + for _, isoPath := range b.config.SecondaryDvdImages { + if _, err := os.Stat(isoPath); os.IsNotExist(err) { + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Secondary Dvd image does not exist: %s", err)) + } + } + } + + numberOfIsos := len(b.config.SecondaryDvdImages) + + if b.config.GuestAdditionsMode == "attach" { + if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { + if err != nil { + errs = packer.MultiErrorAppend( + errs, fmt.Errorf("Guest additions iso does not exist: %s", err)) + } + } + + numberOfIsos = numberOfIsos + 1 + } + + if b.config.Generation < 2 && numberOfIsos > 2 { + if b.config.GuestAdditionsMode == "attach" { + 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, ", "))) + } else { + 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, ", "))) + } + } else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 { + if b.config.GuestAdditionsMode == "attach" { + 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, ", "))) + } else { + 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, ", "))) + } + } + // Warnings if b.config.ISOChecksumType == "none" { warnings = append(warnings, @@ -275,12 +329,21 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state.Put("hook", hook) state.Put("ui", ui) - steps := []multistep.Step{ + steps := []multistep.Step{ &hypervcommon.StepCreateTempDir{}, &hypervcommon.StepOutputDir{ Force: b.config.PackerForce, Path: b.config.OutputDir, }, + &common.StepDownload{ + Checksum: b.config.ISOChecksum, + ChecksumType: b.config.ISOChecksumType, + Description: "ISO", + ResultKey: "iso_path", + Url: b.config.ISOUrls, + Extension: "iso", + TargetPath: b.config.TargetPath, + }, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, @@ -304,12 +367,20 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepEnableIntegrationService{}, &hypervcommon.StepMountDvdDrive{ - RawSingleISOUrl: b.config.RawSingleISOUrl, + Generation: b.config.Generation, + }, + &hypervcommon.StepMountFloppydrive{ + Generation: b.config.Generation, + }, + + &hypervcommon.StepMountGuestAdditions{ + GuestAdditionsMode: b.config.GuestAdditionsMode, + GuestAdditionsPath: b.config.GuestAdditionsPath, + Generation: b.config.Generation, }, - &hypervcommon.StepMountFloppydrive{}, &hypervcommon.StepMountSecondaryDvdImages{ - Files: b.config.SecondaryDvdImages, + IsoPaths: b.config.SecondaryDvdImages, Generation: b.config.Generation, }, @@ -341,15 +412,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // wait for the vm to be powered off &hypervcommon.StepWaitForPowerOff{}, - - // remove the integration services dvd drive + + // remove the secondary dvd images // after we power down &hypervcommon.StepUnmountSecondaryDvdImages{}, + &hypervcommon.StepUnmountGuestAdditions{}, + &hypervcommon.StepUnmountDvdDrive{}, &hypervcommon.StepUnmountFloppyDrive{ Generation: b.config.Generation, }, - &hypervcommon.StepUnmountDvdDrive{}, - &hypervcommon.StepExportVm{ OutputDir: b.config.OutputDir, SkipCompaction: b.config.SkipCompaction, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 162b048fafc..bcba3edf51b 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -153,7 +153,20 @@ Get-VMDvdDrive -VMName $vmName | Set-VMDvdDrive -Path $null return err } -func DeleteDvdDrive(vmName string, controllerNumber string, controllerLocation string) error { +func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { + var script = ` +param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) +$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +if (!$vmDvdDrive) {throw 'unable to find dvd drive'} +Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + return err +} + +func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { var script = ` param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) $vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation @@ -162,7 +175,7 @@ Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Controlle ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, controllerNumber, controllerLocation) + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) return err } From 5c2ecef04b7524b117a4cdd3df953a3fa8a2e5e3 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Fri, 30 Oct 2015 19:47:14 +0000 Subject: [PATCH 042/113] Forgot to check in these files. --- .../common/step_mount_guest_additions.go | 96 +++++++++++++++++ .../common/step_mount_secondary_dvd_images.go | 101 ++++++++++++++++++ .../common/step_unmount_guest_additions.go | 47 ++++++++ .../step_unmount_secondary_dvd_images.go | 49 +++++++++ 4 files changed, 293 insertions(+) create mode 100644 builder/hyperv/common/step_mount_guest_additions.go create mode 100644 builder/hyperv/common/step_mount_secondary_dvd_images.go create mode 100644 builder/hyperv/common/step_unmount_guest_additions.go create mode 100644 builder/hyperv/common/step_unmount_secondary_dvd_images.go diff --git a/builder/hyperv/common/step_mount_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go new file mode 100644 index 00000000000..33c25208246 --- /dev/null +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountGuestAdditions struct { + GuestAdditionsMode string + GuestAdditionsPath string + Generation uint + cleanup bool + dvdProperties DvdControllerProperties +} + +func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if s.GuestAdditionsMode != "attach" { + ui.Say("Skipping mounting Integration Services Setup Disk...") + return multistep.ActionContinue + } + + driver := state.Get("driver").(Driver) + ui.Say("Mounting Integration Services Setup Disk...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + + var dvdControllerProperties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + dvdControllerProperties.ControllerNumber = controllerNumber + dvdControllerProperties.ControllerLocation = controllerLocation + s.cleanup = true + s.dvdProperties = dvdControllerProperties + + ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) + err = driver.MountDvdDriveByLocation(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) + + state.Put("guest.dvd.properties", dvdControllerProperties) + + return multistep.ActionContinue +} + +func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { + if !s.cleanup || s.GuestAdditionsMode != "attach" { + return + } + ui := state.Get("ui").(packer.Ui) + + driver := state.Get("driver").(Driver) + ui.Say("Cleanup Integration Services dvd drive...") + + vmName := state.Get("vmName").(string) + + dvdControllerProperties := s.dvdProperties + + errorMsg := "Error unmounting Integration Services dvd drive: %s" + + if dvdControllerProperties.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } +} diff --git a/builder/hyperv/common/step_mount_secondary_dvd_images.go b/builder/hyperv/common/step_mount_secondary_dvd_images.go new file mode 100644 index 00000000000..5d220b55b5f --- /dev/null +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountSecondaryDvdImages struct { + IsoPaths []string + Generation uint + cleanup bool + dvdProperties []DvdControllerProperties +} + +type DvdControllerProperties struct { + ControllerNumber uint + ControllerLocation uint + Existing bool +} + +func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Mounting secondary DVD images...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + var dvdProperties []DvdControllerProperties + + for _, isoPath := range s.IsoPaths { + var properties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + properties.ControllerNumber = controllerNumber + properties.ControllerLocation = controllerLocation + + s.cleanup = true + dvdProperties = append(dvdProperties, properties) + s.dvdProperties = dvdProperties + + ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) + err = driver.MountDvdDriveByLocation(vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) + } + + state.Put("secondary.dvd.properties", dvdProperties) + + return multistep.ActionContinue +} + +func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { + if (!s.cleanup){ + return + } + + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Clean up secondary dvd drives...") + + vmName := state.Get("vmName").(string) + + errorMsg := "Error unmounting secondary dvd drive: %s" + + for _, dvdControllerProperties := range s.dvdProperties { + + if dvdControllerProperties.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } + } +} diff --git a/builder/hyperv/common/step_unmount_guest_additions.go b/builder/hyperv/common/step_unmount_guest_additions.go new file mode 100644 index 00000000000..d4b50500114 --- /dev/null +++ b/builder/hyperv/common/step_unmount_guest_additions.go @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type StepUnmountGuestAdditions struct { +} + +func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) + ui := state.Get("ui").(packer.Ui) + + ui.Say("Unmounting Integration Services dvd drive...") + + dvdController := state.Get("guest.dvd.properties").(DvdControllerProperties) + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting Integration Services dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + err := fmt.Errorf("Error deleting Integration Services dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + return multistep.ActionContinue +} + +func (s *StepUnmountGuestAdditions) Cleanup(state multistep.StateBag) { +} diff --git a/builder/hyperv/common/step_unmount_secondary_dvd_images.go b/builder/hyperv/common/step_unmount_secondary_dvd_images.go new file mode 100644 index 00000000000..ceb6663b7b5 --- /dev/null +++ b/builder/hyperv/common/step_unmount_secondary_dvd_images.go @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" +) + +type StepUnmountSecondaryDvdImages struct { +} + +func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + + ui.Say("Unmounting secondary dvd drives...") + + dvdControllers := state.Get("secondary.dvd.properties").([]DvdControllerProperties) + + for _, dvdController := range dvdControllers { + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName) + if err != nil { + err := fmt.Errorf("Error unmounting secondary dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + err := fmt.Errorf("Error deleting secondary dvd drive: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + } + + return multistep.ActionContinue +} + +func (s *StepUnmountSecondaryDvdImages) Cleanup(state multistep.StateBag) { +} From d8948d118bbeb93e605a996a64df458bc754de47 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 30 Oct 2015 19:57:27 +0000 Subject: [PATCH 043/113] Include cache in the state bag. --- builder/hyperv/iso/builder.go | 53 ++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 26 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 40b1a36a75b..430499e4807 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -17,18 +17,18 @@ import ( "github.com/mitchellh/packer/powershell/hyperv" "github.com/mitchellh/packer/template/interpolate" "log" + "os" "strings" "time" - "os" ) const ( - DefaultDiskSize = 40 * 1024 // ~40GB - MinDiskSize = 256 // 256MB + DefaultDiskSize = 40 * 1024 // ~40GB + MinDiskSize = 256 // 256MB MaxDiskSize = 64 * 1024 * 1024 // 64TB DefaultRamSize = 1 * 1024 // 1GB - MinRamSize = 32 // 32MB + MinRamSize = 32 // 32MB MaxRamSize = 32 * 1024 // 32GB LowRam = 384 // 384MB @@ -85,21 +85,21 @@ type Config struct { // either an HTTP URL or a file URL (or path to a file). If this is an // HTTP URL, Packer will download it and cache it between runs. RawSingleISOUrl string `mapstructure:"iso_url"` - + // Multiple URLs for the ISO to download. Packer will try these in order. // If anything goes wrong attempting to download or while downloading a // single URL, it will move on to the next. All URLs must point to the // same file (same checksum). By default this is empty and iso_url is // used. Only one of iso_url or iso_urls can be specified. ISOUrls []string `mapstructure:"iso_urls"` - - TargetPath string `mapstructure:"iso_target_path"` - + + TargetPath string `mapstructure:"iso_target_path"` + // Should integration services iso be mounted - GuestAdditionsMode string `mapstructure:"guest_additions_mode"` - + GuestAdditionsMode string `mapstructure:"guest_additions_mode"` + // The path to the integration services iso - GuestAdditionsPath string `mapstructure:"guest_additions_path"` + GuestAdditionsPath string `mapstructure:"guest_additions_path"` // This is the name of the new virtual machine. // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. @@ -251,29 +251,29 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { if b.config.GuestAdditionsPath == "" { b.config.GuestAdditionsPath = os.Getenv("WINDIR") + "\\system32\\vmguest.iso" } - + for _, isoPath := range b.config.SecondaryDvdImages { if _, err := os.Stat(isoPath); os.IsNotExist(err) { - if err != nil { + if err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("Secondary Dvd image does not exist: %s", err)) } } } - + numberOfIsos := len(b.config.SecondaryDvdImages) - + if b.config.GuestAdditionsMode == "attach" { if _, err := os.Stat(b.config.GuestAdditionsPath); os.IsNotExist(err) { - if err != nil { + if err != nil { errs = packer.MultiErrorAppend( errs, fmt.Errorf("Guest additions iso does not exist: %s", err)) } - } - + } + numberOfIsos = numberOfIsos + 1 } - + if b.config.Generation < 2 && numberOfIsos > 2 { if b.config.GuestAdditionsMode == "attach" { 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, ", "))) @@ -282,9 +282,9 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } else if b.config.Generation > 1 && len(b.config.SecondaryDvdImages) > 16 { if b.config.GuestAdditionsMode == "attach" { - 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, ", "))) + 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, ", "))) } else { - 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, ", "))) + 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, ", "))) } } @@ -324,12 +324,13 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Set up the state. state := new(multistep.BasicStateBag) + state.Put("cache", cache) state.Put("config", &b.config) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) - steps := []multistep.Step{ + steps := []multistep.Step{ &hypervcommon.StepCreateTempDir{}, &hypervcommon.StepOutputDir{ Force: b.config.PackerForce, @@ -343,7 +344,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Url: b.config.ISOUrls, Extension: "iso", TargetPath: b.config.TargetPath, - }, + }, &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, @@ -376,11 +377,11 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &hypervcommon.StepMountGuestAdditions{ GuestAdditionsMode: b.config.GuestAdditionsMode, GuestAdditionsPath: b.config.GuestAdditionsPath, - Generation: b.config.Generation, + Generation: b.config.Generation, }, &hypervcommon.StepMountSecondaryDvdImages{ - IsoPaths: b.config.SecondaryDvdImages, + IsoPaths: b.config.SecondaryDvdImages, Generation: b.config.Generation, }, @@ -412,7 +413,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // wait for the vm to be powered off &hypervcommon.StepWaitForPowerOff{}, - + // remove the secondary dvd images // after we power down &hypervcommon.StepUnmountSecondaryDvdImages{}, From 32feb8a005704ed712ce5e18fa8729eb88f07ff5 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 30 Oct 2015 20:13:48 +0000 Subject: [PATCH 044/113] Build agents might not have guest additions --- builder/hyperv/iso/builder_test.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 5311075d0be..5cc04163e93 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -8,13 +8,14 @@ import ( func testConfig() map[string]interface{} { return map[string]interface{}{ - "iso_checksum": "foo", - "iso_checksum_type": "md5", - "iso_url": "http://www.packer.io", - "shutdown_command": "yes", - "ssh_username": "foo", - "ram_size_mb": 64, - "disk_size": 256, + "iso_checksum": "foo", + "iso_checksum_type": "md5", + "iso_url": "http://www.packer.io", + "shutdown_command": "yes", + "ssh_username": "foo", + "ram_size_mb": 64, + "disk_size": 256, + "guest_additions_mode": "none", packer.BuildNameConfigKey: "foo", } } @@ -56,7 +57,7 @@ func TestBuilderPrepare_DiskSize(t *testing.T) { t.Fatalf("bad err: %s", err) } - if b.config.DiskSize != 40 * 1024 { + if b.config.DiskSize != 40*1024 { t.Fatalf("bad size: %d", b.config.DiskSize) } From 3e8fa1b544f814715fc7ab7ee5545505014137a4 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 31 Oct 2015 16:53:58 +0000 Subject: [PATCH 045/113] Setting boot drive message --- builder/hyperv/common/step_mount_dvddrive.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index f31ef4c2c54..f325ada9422 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -13,7 +13,7 @@ import ( type StepMountDvdDrive struct { Generation uint - cleanup bool + cleanup bool dvdProperties DvdControllerProperties } @@ -24,7 +24,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { errorMsg := "Error mounting dvd drive: %s" vmName := state.Get("vmName").(string) isoPath := state.Get("iso_path").(string) - + // should be able to mount up to 60 additional iso images using SCSI // but Windows would only allow a max of 22 due to available drive letters // Will Windows assign DVD drives to A: and B: ? @@ -43,16 +43,16 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { dvdControllerProperties.ControllerLocation = controllerLocation s.cleanup = true s.dvdProperties = dvdControllerProperties - - ui.Say("Setting boot drive to os dvd drive %s ...") + + ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ..."), isoPath) err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt - } - + } + ui.Say(fmt.Sprintf("Mounting os dvd drive %s ...", isoPath)) err = driver.MountDvdDrive(vmName, isoPath) if err != nil { @@ -61,7 +61,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } - + state.Put("os.dvd.properties", dvdControllerProperties) return multistep.ActionContinue @@ -80,9 +80,9 @@ func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { ui := state.Get("ui").(packer.Ui) ui.Say("Clean up os dvd drive...") - + dvdControllerProperties := s.dvdProperties - + if dvdControllerProperties.Existing { err := driver.UnmountDvdDrive(vmName) if err != nil { From 1e9363fff76021247a519314dae294e40f6475f6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 1 Nov 2015 16:00:56 +0000 Subject: [PATCH 046/113] Get rid of mount and unmount that does not specify controller location exactly Handle cleanup if unmount step has not already done so --- builder/hyperv/common/driver.go | 8 ++-- builder/hyperv/common/driver_ps_4.go | 12 ++---- builder/hyperv/common/step_mount_dvddrive.go | 34 ++++++++--------- .../common/step_mount_guest_additions.go | 38 +++++++++---------- .../common/step_mount_secondary_dvd_images.go | 29 +++++++------- .../hyperv/common/step_unmount_dvddrive.go | 16 ++++++-- .../hyperv/common/step_unmount_floppydrive.go | 2 +- .../common/step_unmount_guest_additions.go | 18 +++++++-- .../step_unmount_secondary_dvd_images.go | 18 +++++++-- powershell/hyperv/hyperv.go | 28 +++++--------- 10 files changed, 106 insertions(+), 97 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 6ceb37c0f6b..f0a6d9d8644 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -88,13 +88,11 @@ type Driver interface { CreateDvdDrive(string, uint) (uint, uint, error) - MountDvdDrive(string, string) error - - MountDvdDriveByLocation(string, string, uint, uint) error + MountDvdDrive(string, string, uint, uint) error SetBootDvdDrive(string, uint, uint) error - - UnmountDvdDrive(string) error + + UnmountDvdDrive(string, uint, uint) error DeleteDvdDrive(string, uint, uint) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index c6d6d4e65b1..bc2040811c2 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -209,20 +209,16 @@ func (d *HypervPS4Driver) CreateDvdDrive(vmName string, generation uint) (uint, return hyperv.CreateDvdDrive(vmName, generation) } -func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string) error { - return hyperv.MountDvdDrive(vmName, path) -} - -func (d *HypervPS4Driver) MountDvdDriveByLocation(vmName string, path string, controllerNumber uint, controllerLocation uint) error { - return hyperv.MountDvdDriveByLocation(vmName, path, controllerNumber, controllerLocation) +func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error { + return hyperv.MountDvdDrive(vmName, path, controllerNumber, controllerLocation) } func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) } -func (d *HypervPS4Driver) UnmountDvdDrive(vmName string) error { - return hyperv.UnmountDvdDrive(vmName) +func (d *HypervPS4Driver) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { + return hyperv.UnmountDvdDrive(vmName, controllerNumber, controllerLocation) } func (d *HypervPS4Driver) DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index f325ada9422..75af552ad63 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -13,8 +13,6 @@ import ( type StepMountDvdDrive struct { Generation uint - cleanup bool - dvdProperties DvdControllerProperties } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { @@ -41,10 +39,11 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { dvdControllerProperties.ControllerNumber = controllerNumber dvdControllerProperties.ControllerLocation = controllerLocation - s.cleanup = true - s.dvdProperties = dvdControllerProperties + dvdControllerProperties.Existing = false + + state.Put("os.dvd.properties", dvdControllerProperties) - ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ..."), isoPath) + ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ...", isoPath)) err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) if err != nil { err := fmt.Errorf(errorMsg, err) @@ -54,7 +53,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { } ui.Say(fmt.Sprintf("Mounting os dvd drive %s ...", isoPath)) - err = driver.MountDvdDrive(vmName, isoPath) + err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -62,35 +61,32 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - state.Put("os.dvd.properties", dvdControllerProperties) - return multistep.ActionContinue } -func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { - if !s.cleanup { +func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { + dvdControllerState := state.Get("os.dvd.properties") + + if dvdControllerState == nil { return } - + + dvdController := dvdControllerState.(DvdControllerProperties) driver := state.Get("driver").(Driver) - - errorMsg := "Error unmounting os dvd drive: %s" - vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) + errorMsg := "Error unmounting os dvd drive: %s" ui.Say("Clean up os dvd drive...") - dvdControllerProperties := s.dvdProperties - - if dvdControllerProperties.Existing { - err := driver.UnmountDvdDrive(vmName) + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error unmounting dvd drive: %s", err) log.Print(fmt.Sprintf(errorMsg, err)) } } else { - err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error deleting dvd drive: %s", err) log.Print(fmt.Sprintf(errorMsg, err)) diff --git a/builder/hyperv/common/step_mount_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go index 33c25208246..ee6b69afcac 100644 --- a/builder/hyperv/common/step_mount_guest_additions.go +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -15,8 +15,6 @@ type StepMountGuestAdditions struct { GuestAdditionsMode string GuestAdditionsPath string Generation uint - cleanup bool - dvdProperties DvdControllerProperties } func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { @@ -49,11 +47,11 @@ func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAc dvdControllerProperties.ControllerNumber = controllerNumber dvdControllerProperties.ControllerLocation = controllerLocation - s.cleanup = true - s.dvdProperties = dvdControllerProperties + dvdControllerProperties.Existing = false + state.Put("guest.dvd.properties", dvdControllerProperties) ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) - err = driver.MountDvdDriveByLocation(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) + err = driver.MountDvdDrive(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) if err != nil { state.Put("error", err) ui.Error(err.Error()) @@ -62,35 +60,37 @@ func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAc log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) - state.Put("guest.dvd.properties", dvdControllerProperties) - return multistep.ActionContinue } func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { - if !s.cleanup || s.GuestAdditionsMode != "attach" { + if s.GuestAdditionsMode != "attach" { return } - ui := state.Get("ui").(packer.Ui) - driver := state.Get("driver").(Driver) - ui.Say("Cleanup Integration Services dvd drive...") - - vmName := state.Get("vmName").(string) - - dvdControllerProperties := s.dvdProperties + dvdControllerState := state.Get("guest.dvd.properties") + if dvdControllerState == nil { + return + } + + dvdController := dvdControllerState.(DvdControllerProperties) + ui := state.Get("ui").(packer.Ui) + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) errorMsg := "Error unmounting Integration Services dvd drive: %s" - if dvdControllerProperties.Existing { - err := driver.UnmountDvdDrive(vmName) + ui.Say("Cleanup Integration Services dvd drive...") + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { log.Print(fmt.Sprintf(errorMsg, err)) } } else { - err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { log.Print(fmt.Sprintf(errorMsg, err)) } } -} +} \ No newline at end of file diff --git a/builder/hyperv/common/step_mount_secondary_dvd_images.go b/builder/hyperv/common/step_mount_secondary_dvd_images.go index 5d220b55b5f..548fd4a8aa6 100644 --- a/builder/hyperv/common/step_mount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -14,8 +14,6 @@ import ( type StepMountSecondaryDvdImages struct { IsoPaths []string Generation uint - cleanup bool - dvdProperties []DvdControllerProperties } type DvdControllerProperties struct { @@ -50,13 +48,12 @@ func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.St properties.ControllerNumber = controllerNumber properties.ControllerLocation = controllerLocation - - s.cleanup = true + properties.Existing = false dvdProperties = append(dvdProperties, properties) - s.dvdProperties = dvdProperties + state.Put("secondary.dvd.properties", dvdProperties) ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) - err = driver.MountDvdDriveByLocation(vmName, isoPath, controllerNumber, controllerLocation) + err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) if err != nil { state.Put("error", err) ui.Error(err.Error()) @@ -66,33 +63,33 @@ func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.St log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) } - state.Put("secondary.dvd.properties", dvdProperties) - return multistep.ActionContinue } func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { - if (!s.cleanup){ + dvdControllersState := state.Get("secondary.dvd.properties") + + if dvdControllersState == nil { return } + dvdControllers := dvdControllersState.([]DvdControllerProperties) driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) - ui.Say("Clean up secondary dvd drives...") - vmName := state.Get("vmName").(string) - errorMsg := "Error unmounting secondary dvd drive: %s" + + ui.Say("Clean up secondary dvd drives...") - for _, dvdControllerProperties := range s.dvdProperties { + for _, dvdController := range dvdControllers { - if dvdControllerProperties.Existing { - err := driver.UnmountDvdDrive(vmName) + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { log.Print(fmt.Sprintf(errorMsg, err)) } } else { - err := driver.DeleteDvdDrive(vmName, dvdControllerProperties.ControllerNumber, dvdControllerProperties.ControllerLocation) + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { log.Print(fmt.Sprintf(errorMsg, err)) } diff --git a/builder/hyperv/common/step_unmount_dvddrive.go b/builder/hyperv/common/step_unmount_dvddrive.go index c0f4d3e1ce1..26f327cc87c 100644 --- a/builder/hyperv/common/step_unmount_dvddrive.go +++ b/builder/hyperv/common/step_unmount_dvddrive.go @@ -18,12 +18,19 @@ func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting os dvd drive...") + ui.Say("Unmount/delete os dvd drive...") - dvdController := state.Get("os.dvd.properties").(DvdControllerProperties) + dvdControllerState := state.Get("os.dvd.properties") + + if dvdControllerState == nil { + return multistep.ActionContinue + } + + dvdController := dvdControllerState.(DvdControllerProperties) if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName) + ui.Say(fmt.Sprintf("Unmounting os dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error unmounting os dvd drive: %s", err) state.Put("error", err) @@ -31,6 +38,7 @@ func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction return multistep.ActionHalt } } else { + ui.Say(fmt.Sprintf("Delete os dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error deleting os dvd drive: %s", err) @@ -40,6 +48,8 @@ func (s *StepUnmountDvdDrive) Run(state multistep.StateBag) multistep.StepAction } } + state.Put("os.dvd.properties", nil) + return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_floppydrive.go b/builder/hyperv/common/step_unmount_floppydrive.go index 45546e1d467..5ae92073008 100644 --- a/builder/hyperv/common/step_unmount_floppydrive.go +++ b/builder/hyperv/common/step_unmount_floppydrive.go @@ -23,7 +23,7 @@ func (s *StepUnmountFloppyDrive) Run(state multistep.StateBag) multistep.StepAct } vmName := state.Get("vmName").(string) - ui.Say("Unmounting floppy drive (Run)...") + ui.Say("Unmount/delete floppy drive (Run)...") errorMsg := "Error Unmounting floppy drive: %s" diff --git a/builder/hyperv/common/step_unmount_guest_additions.go b/builder/hyperv/common/step_unmount_guest_additions.go index d4b50500114..56c98791a29 100644 --- a/builder/hyperv/common/step_unmount_guest_additions.go +++ b/builder/hyperv/common/step_unmount_guest_additions.go @@ -18,12 +18,19 @@ func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.Step vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) - ui.Say("Unmounting Integration Services dvd drive...") - - dvdController := state.Get("guest.dvd.properties").(DvdControllerProperties) + ui.Say("Unmount/delete Integration Services dvd drive...") + + dvdControllerState := state.Get("guest.dvd.properties") + + if dvdControllerState == nil { + return multistep.ActionContinue + } + + dvdController := dvdControllerState.(DvdControllerProperties) if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName) + ui.Say(fmt.Sprintf("Unmounting Integration Services dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error unmounting Integration Services dvd drive: %s", err) state.Put("error", err) @@ -31,6 +38,7 @@ func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.Step return multistep.ActionHalt } } else { + ui.Say(fmt.Sprintf("Delete Integration Services dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error deleting Integration Services dvd drive: %s", err) @@ -40,6 +48,8 @@ func (s *StepUnmountGuestAdditions) Run(state multistep.StateBag) multistep.Step } } + state.Put("guest.dvd.properties", nil) + return multistep.ActionContinue } diff --git a/builder/hyperv/common/step_unmount_secondary_dvd_images.go b/builder/hyperv/common/step_unmount_secondary_dvd_images.go index ceb6663b7b5..7c5b27781c9 100644 --- a/builder/hyperv/common/step_unmount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_unmount_secondary_dvd_images.go @@ -18,13 +18,20 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. ui := state.Get("ui").(packer.Ui) vmName := state.Get("vmName").(string) - ui.Say("Unmounting secondary dvd drives...") - - dvdControllers := state.Get("secondary.dvd.properties").([]DvdControllerProperties) + ui.Say("Unmount/delete secondary dvd drives...") + + dvdControllersState := state.Get("secondary.dvd.properties") + + if dvdControllersState == nil { + return multistep.ActionContinue + } + + dvdControllers := dvdControllersState.([]DvdControllerProperties) for _, dvdController := range dvdControllers { if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName) + ui.Say(fmt.Sprintf("Unmounting secondary dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error unmounting secondary dvd drive: %s", err) state.Put("error", err) @@ -32,6 +39,7 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. return multistep.ActionHalt } } else { + ui.Say(fmt.Sprintf("Delete secondary dvd drives controller %d location %d ...", dvdController.ControllerNumber, dvdController.ControllerLocation)) err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) if err != nil { err := fmt.Errorf("Error deleting secondary dvd drive: %s", err) @@ -41,6 +49,8 @@ func (s *StepUnmountSecondaryDvdImages) Run(state multistep.StateBag) multistep. } } } + + state.Put("secondary.dvd.properties", nil) return multistep.ActionContinue } diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index bcba3edf51b..e25ae93131d 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -118,23 +118,13 @@ param([string]$vmName) return controllerNumber, controllerLocation, err } -func MountDvdDrive(vmName string, path string) error { - - var script = ` -param([string]$vmName,[string]$path) -Set-VMDvdDrive -VMName $vmName -Path $path -` - - var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, path) - return err -} - -func MountDvdDriveByLocation(vmName string, path string, controllerNumber uint, controllerLocation uint) error { +func MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error { var script = ` param([string]$vmName,[string]$path,[string]$controllerNumber,[string]$controllerLocation) -Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +if (!$vmDvdDrive) {throw 'unable to find dvd drive'} +Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $path ` var ps powershell.PowerShellCmd @@ -142,14 +132,16 @@ Set-VMDvdDrive -VMName $vmName -Path $path -ControllerNumber $controllerNumber - return err } -func UnmountDvdDrive(vmName string) error { +func UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { var script = ` -param([string]$vmName) -Get-VMDvdDrive -VMName $vmName | Set-VMDvdDrive -Path $null +param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) +$vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation +if (!$vmDvdDrive) {throw 'unable to find dvd drive'} +Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation -Path $null ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName) + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) return err } From dd7734017831aaa77831b375c03b0c2e7ced3e08 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 1 Nov 2015 21:18:48 +0000 Subject: [PATCH 047/113] Useful to know what the exit code is if there is an error --- builder/hyperv/common/step_shutdown.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index 62bbab21f4e..add53adaf89 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -57,18 +57,13 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { // If the command failed to run, notify the user in some way. if cmd.ExitStatus != 0 { state.Put("error", fmt.Errorf( - "Shutdown command has non-zero exit status.\n\nStdout: %s\n\nStderr: %s", - stdout.String(), stderr.String())) + "Shutdown command has non-zero exit status.\n\nExitStatus: %d\n\nStdout: %s\n\nStderr: %s", + cmd.ExitStatus, stdout.String(), stderr.String())) return multistep.ActionHalt } - if stdout.Len() > 0 { - log.Printf("Shutdown stdout: %s", stdout.String()) - } - - if stderr.Len() > 0 { - log.Printf("Shutdown stderr: %s", stderr.String()) - } + log.Printf("Shutdown stdout: %s", stdout.String()) + log.Printf("Shutdown stderr: %s", stderr.String()) // Wait for the machine to actually shut down log.Printf("Waiting max %s for shutdown to complete", s.Timeout) From f643bdd0b40f7661a692e09b3c7af973e31601b1 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Mon, 2 Nov 2015 14:16:21 +0000 Subject: [PATCH 048/113] If we are not getting the exit code assigned then default to it being successful --- builder/hyperv/common/step_shutdown.go | 1 + 1 file changed, 1 insertion(+) diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index add53adaf89..21a1e963d7a 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -40,6 +40,7 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { var stdout, stderr bytes.Buffer cmd := &packer.RemoteCmd{ + ExitStatus: 0, Command: s.Command, Stdout: &stdout, Stderr: &stderr, From 0eeb203032287276f79566e65833a48bd9a029f8 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Mon, 2 Nov 2015 17:23:51 +0000 Subject: [PATCH 049/113] Using Write-Output instead of Write-Host since PS v5 now leaks the host stream to stderr --- provisioner/powershell/elevated.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index 00bc72e4adf..ec79afa3612 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -72,7 +72,7 @@ function SlurpOutput($l) { if (Test-Path $log) { Get-Content $log | select -skip $l | ForEach { $l += 1 - Write-Host "$_" + Write-Output "$_" } } return $l From 81e2e1f54dece90c75d57b3265a4e8d46679ccdd Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Mon, 2 Nov 2015 17:35:38 +0000 Subject: [PATCH 050/113] Ensure that progress stream does not get leaked into stdout --- powershell/powershell_test.go | 2 +- provisioner/powershell/elevated.go | 1 + provisioner/powershell/provisioner.go | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 4aff204916f..0925e3c74cf 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -37,7 +37,7 @@ func TestOutput(t *testing.T) { func TestRunFile(t *testing.T) { var blockBuffer bytes.Buffer - blockBuffer.WriteString("param([string]$a, [string]$b, [int]$x, [int]$y) $n = $x + $y; Write-Host $a, $b, $n") + blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) $ProgressPreference='SilentlyContinue'; $n = $x + $y; Write-Output "$a $b $n";`) var ps PowerShellCmd cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index ec79afa3612..54aec2b4962 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -58,6 +58,7 @@ $t.XmlText = @' </Actions> </Task> '@ +$ProgressPreference='SilentlyContinue'; $f = $s.GetFolder("\") $f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", "{{.Password}}", 1, $null) | Out-Null $t = $f.GetTask("\$name") diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index ad90e56a152..9d23dde9ae0 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -120,11 +120,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -425,7 +425,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte(command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("$ProgressPreference='SilentlyContinue'; " + command + "; exit $LASTEXITCODE")), }) if err != nil { From 3bae6a200dd76cb3265b380fc7554cc082e74c02 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@interxion.com> Date: Mon, 2 Nov 2015 17:45:26 +0000 Subject: [PATCH 051/113] Fix unit tests for not showing progress stream when using powershell --- provisioner/powershell/provisioner_test.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index e109eb6d5d5..e8b0728ebe5 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,11 +75,11 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != "powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'" { + if p.config.ExecuteCommand != "powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'" { t.Fatalf("Default command should be powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != "powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}'" { + if p.config.ElevatedExecuteCommand != "powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'" { t.Fatalf("Default command should be powershell powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) } @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& { $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& { $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& { $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { + if cmd != `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From f0ffb3c6eb70e94b8f0e66a1694de2a9d0061a73 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 2 Nov 2015 19:12:03 +0000 Subject: [PATCH 052/113] wite output will put ouput from function, so we don't want to be getting line from output of function --- provisioner/powershell/elevated.go | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index 54aec2b4962..6678b4b659f 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -69,19 +69,16 @@ while ((!($t.state -eq 4)) -and ($sec -lt $timeout)) { Start-Sleep -s 1 $sec++ } -function SlurpOutput($l) { + +$line = 0 +do { + Start-Sleep -m 100 if (Test-Path $log) { - Get-Content $log | select -skip $l | ForEach { - $l += 1 + Get-Content $log | select -skip $line | ForEach { + $line += 1 Write-Output "$_" } } - return $l -} -$line = 0 -do { - Start-Sleep -m 100 - $line = SlurpOutput $line } while (!($t.state -eq 3)) $result = $t.LastTaskResult [System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null From 7d06f88519cc563c43dd16f6865a0e220f4ef8cf Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 2 Nov 2015 20:05:53 +0000 Subject: [PATCH 053/113] Use correct quotation of variables --- provisioner/powershell/elevated.go | 2 +- provisioner/powershell/provisioner.go | 6 +++--- provisioner/powershell/provisioner_test.go | 18 +++++++++--------- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index 6678b4b659f..cb3336ae291 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -58,7 +58,7 @@ $t.XmlText = @' </Actions> </Task> '@ -$ProgressPreference='SilentlyContinue'; +$ProgressPreference="SilentlyContinue"; $f = $s.GetFolder("\") $f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", "{{.Password}}", 1, $null) | Out-Null $t = $f.GetTask("\$name") diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 9d23dde9ae0..90cde164fdd 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -120,11 +120,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -425,7 +425,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("$ProgressPreference='SilentlyContinue'; " + command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("$ProgressPreference=\"SilentlyContinue\"; " + command + "; exit $LASTEXITCODE")), }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index e8b0728ebe5..87f6680ad03 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != "powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'" { - t.Fatalf("Default command should be powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { + t.Fatalf("Default command should be powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != "powershell '& {$ProgressPreference='SilentlyContinue'; {{.Vars}}{{.Path}}; exit $LastExitCode}'" { - t.Fatalf("Default command should be powershell powershell '& { {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { + t.Fatalf("Default command should be powershell powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& {$ProgressPreference='SilentlyContinue'; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {$ProgressPreference='SilentlyContinue'; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& {$ProgressPreference='SilentlyContinue'; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { + if cmd != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From acb6ed57743c072777f5462e18b0a684b5779434 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 3 Nov 2015 09:41:55 +0000 Subject: [PATCH 054/113] If exit code = 1 and the length of the stderr is 0, then we can assume that the exit code was not set, so assume success --- builder/hyperv/common/step_shutdown.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index 21a1e963d7a..06ddb6f39e0 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -40,7 +40,6 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { var stdout, stderr bytes.Buffer cmd := &packer.RemoteCmd{ - ExitStatus: 0, Command: s.Command, Stdout: &stdout, Stderr: &stderr, @@ -55,16 +54,19 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { // Wait for the command to run cmd.Wait() + stderrString := stderr.String() + stdoutString := stdout.String() + // If the command failed to run, notify the user in some way. - if cmd.ExitStatus != 0 { + if !(cmd.ExitStatus == 0 || (cmd.ExitStatus == 1 && len(stderrString) == 0)) { state.Put("error", fmt.Errorf( - "Shutdown command has non-zero exit status.\n\nExitStatus: %d\n\nStdout: %s\n\nStderr: %s", - cmd.ExitStatus, stdout.String(), stderr.String())) + "Shutdown command has not successful.\n\nExitStatus: %d\n\nStdout: %s\n\nStderr: %s", + cmd.ExitStatus, stdoutString), stderrString)) return multistep.ActionHalt } - log.Printf("Shutdown stdout: %s", stdout.String()) - log.Printf("Shutdown stderr: %s", stderr.String()) + log.Printf("Shutdown stdout: %s", stdoutString) + log.Printf("Shutdown stderr: %s", stderrString) // Wait for the machine to actually shut down log.Printf("Waiting max %s for shutdown to complete", s.Timeout) From afcacaff2dd3df4cdde9045ab5fc0dcdcfbc9b78 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 3 Nov 2015 09:58:45 +0000 Subject: [PATCH 055/113] Accidentally included bracket --- builder/hyperv/common/step_shutdown.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/common/step_shutdown.go b/builder/hyperv/common/step_shutdown.go index 06ddb6f39e0..db60a15176b 100644 --- a/builder/hyperv/common/step_shutdown.go +++ b/builder/hyperv/common/step_shutdown.go @@ -61,7 +61,7 @@ func (s *StepShutdown) Run(state multistep.StateBag) multistep.StepAction { if !(cmd.ExitStatus == 0 || (cmd.ExitStatus == 1 && len(stderrString) == 0)) { state.Put("error", fmt.Errorf( "Shutdown command has not successful.\n\nExitStatus: %d\n\nStdout: %s\n\nStderr: %s", - cmd.ExitStatus, stdoutString), stderrString)) + cmd.ExitStatus, stdoutString, stderrString)) return multistep.ActionHalt } From 990cdc0587549158c4e84788b693e9672db1e73d Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 4 Nov 2015 21:45:37 +0000 Subject: [PATCH 056/113] The default for wait is 1 second --- powershell/hyperv/hyperv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index e25ae93131d..506f1a2c11f 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -775,7 +775,7 @@ param([string]$vmName, [string]$scanCodes) if ($scanCode.StartsWith('wait')){ $timeToWait = $scanCode.Substring(4) if (!$timeToWait){ - $timeToWait = "10" + $timeToWait = "1" } Start-Sleep -s $timeToWait From e7186f70c5362f23679b57d924f7ef80da08683f Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 13:19:37 +0000 Subject: [PATCH 057/113] Fix gen 1 creation of dvd --- powershell/hyperv/hyperv.go | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 506f1a2c11f..3e5e493088c 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -61,7 +61,7 @@ func CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives script = ` param([string]$vmName) -$dvdDrives = (Get-VMDvdDrive -VMName $vmName) +$dvdDrives = @(Get-VMDvdDrive -VMName $vmName) $lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber} if (!$lastControllerNumber) { $lastControllerNumber = 0 @@ -90,21 +90,11 @@ $lastControllerNumber script = ` param([string]$vmName,[int]$controllerNumber) -Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -` - cmdOut, err := ps.Output(script, vmName) - if err != nil { - return controllerNumber, 0, err - } - - // we could try to get the controller location and number in one call, but this way we do not - // need to parse the output - script = ` -param([string]$vmName) -(Get-VMDvdDrive -VMName $vmName | Where-Object {$_.Path -eq $null}).ControllerLocation +$dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Passthru +$dvdController.ControllerLocation ` - cmdOut, err = ps.Output(script, vmName) + cmdOut, err := ps.Output(script, vmName, strconv.FormatInt(int64(controllerNumber), 10)) if err != nil { return controllerNumber, 0, err } From 0a19ccbd16a1ba5a968fc8134c1a0fd029904502 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 13:42:26 +0000 Subject: [PATCH 058/113] Setting boot drive is generation specific --- builder/hyperv/common/driver.go | 8 +++---- builder/hyperv/common/driver_ps_4.go | 4 ++-- builder/hyperv/common/step_mount_dvddrive.go | 14 ++++++------- powershell/hyperv/hyperv.go | 22 ++++++++++++++------ 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index f0a6d9d8644..44fa37c90bf 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -89,14 +89,14 @@ type Driver interface { CreateDvdDrive(string, uint) (uint, uint, error) MountDvdDrive(string, string, uint, uint) error - - SetBootDvdDrive(string, uint, uint) error - + + SetBootDvdDrive(string, uint, uint, uint) error + UnmountDvdDrive(string, uint, uint) error DeleteDvdDrive(string, uint, uint) error MountFloppyDrive(string, string) error - + UnmountFloppyDrive(string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index bc2040811c2..0e6507ede45 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -213,8 +213,8 @@ func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNu return hyperv.MountDvdDrive(vmName, path, controllerNumber, controllerLocation) } -func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { - return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) +func (d *HypervPS4Driver) SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error { + return hyperv.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, generation) } func (d *HypervPS4Driver) UnmountDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 75af552ad63..346af2c9eef 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -12,7 +12,7 @@ import ( ) type StepMountDvdDrive struct { - Generation uint + Generation uint } func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { @@ -40,11 +40,11 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { dvdControllerProperties.ControllerNumber = controllerNumber dvdControllerProperties.ControllerLocation = controllerLocation dvdControllerProperties.Existing = false - + state.Put("os.dvd.properties", dvdControllerProperties) ui.Say(fmt.Sprintf("Setting boot drive to os dvd drive %s ...", isoPath)) - err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation) + err = driver.SetBootDvdDrive(vmName, controllerNumber, controllerLocation, s.Generation) if err != nil { err := fmt.Errorf(errorMsg, err) state.Put("error", err) @@ -64,14 +64,14 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionContinue } -func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { +func (s *StepMountDvdDrive) Cleanup(state multistep.StateBag) { dvdControllerState := state.Get("os.dvd.properties") - + if dvdControllerState == nil { return } - - dvdController := dvdControllerState.(DvdControllerProperties) + + dvdController := dvdControllerState.(DvdControllerProperties) driver := state.Get("driver").(Driver) vmName := state.Get("vmName").(string) ui := state.Get("ui").(packer.Ui) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 3e5e493088c..e26e432cb6d 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -135,17 +135,27 @@ Set-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLo return err } -func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { - var script = ` +func SetBootDvdDrive(vmName string, controllerNumber uint, controllerLocation uint, generation uint) error { + + if generation < 2 { + script := ` +param([string]$vmName) +Set-VMBios -VMName $vmName -StartupOrder @("CD", "IDE","LegacyNetworkAdapter","Floppy") +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err + } else { + script := ` param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) $vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation if (!$vmDvdDrive) {throw 'unable to find dvd drive'} Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive ` - - var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) - return err + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) + return err + } } func DeleteDvdDrive(vmName string, controllerNumber uint, controllerLocation uint) error { From 344153549ced8d561e821841e92ff80813725caa Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 16:20:55 +0000 Subject: [PATCH 059/113] By default a gen 1 vm get a dvd drive. Remove it on creation so that it behaves the same as gen 2. --- powershell/hyperv/hyperv.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index e26e432cb6d..0d86d441c86 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -215,7 +215,12 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD ` var ps powershell.PowerShellCmd err := ps.Run(script, vmName, path, strconv.FormatInt(ram, 10), strconv.FormatInt(diskSize, 10), switchName) - return err + + if err != nil { + return err + } + + return DeleteDvdDrive(vmName, 1, 0) } } From 4bf0a92a1e9445b8b9d90999a0ac7183c2e9080b Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 17:07:40 +0000 Subject: [PATCH 060/113] Echo output when its used --- builder/hyperv/common/step_type_boot_command.go | 10 +++++----- powershell/hyperv/hyperv.go | 3 +++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index c801858b727..e121ebad28b 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -154,19 +154,19 @@ func scancodes(message string) []string { var scancode []string if strings.HasPrefix(message, "<wait>") { - log.Printf("Special code <wait> found, will sleep 1 second at this point.") + //log.Printf("Special code <wait> found, will sleep 1 second at this point.") scancode = []string{"wait"} message = message[len("<wait>"):] } if strings.HasPrefix(message, "<wait5>") { - log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.") + //log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.") scancode = []string{"wait5"} message = message[len("<wait5>"):] } if strings.HasPrefix(message, "<wait10>") { - log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.") + //log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.") scancode = []string{"wait10"} message = message[len("<wait10>"):] } @@ -174,7 +174,7 @@ func scancodes(message string) []string { if scancode == nil { for specialCode, specialValue := range special { if strings.HasPrefix(message, specialCode) { - log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) + //log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) scancode = specialValue message = message[len(specialCode):] break @@ -200,7 +200,7 @@ func scancodes(message string) []string { } scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) - log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) + //log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) } result = append(result, scancode...) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 0d86d441c86..28213605aad 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -783,6 +783,7 @@ param([string]$vmName, [string]$scanCodes) $timeToWait = "1" } + write-host "Special code <wait> found, will sleep $timeToWait second(s) at this point." Start-Sleep -s $timeToWait if ($scanCodesToSend){ @@ -796,8 +797,10 @@ param([string]$vmName, [string]$scanCodes) $scanCodesToSend = '' } else { if ($scanCodesToSend){ + write-host "Sending special code '$scanCodesToSend' '$scanCode'" $scanCodesToSend = "$scanCodesToSend $scanCode" } else { + write-host "Sending char '$scanCode'" $scanCodesToSend = "$scanCode" } } From 16c08fcb3c75d71c9513902a13953d9d84c4a64f Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 17:12:19 +0000 Subject: [PATCH 061/113] removed unused reference --- builder/hyperv/common/step_type_boot_command.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index e121ebad28b..f5cf1d909a3 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -2,7 +2,7 @@ package common import ( "fmt" - "log" + //"log" "strings" "unicode" "unicode/utf8" From a924e41dc14edbac1a1a6daf9d2faeed53275923 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 7 Nov 2015 23:33:47 +0000 Subject: [PATCH 062/113] Need to sleep after sending previous characters, not before --- powershell/hyperv/hyperv.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 28213605aad..67ad79d82a5 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -782,10 +782,7 @@ param([string]$vmName, [string]$scanCodes) if (!$timeToWait){ $timeToWait = "1" } - - write-host "Special code <wait> found, will sleep $timeToWait second(s) at this point." - Start-Sleep -s $timeToWait - + if ($scanCodesToSend){ $scanCodesToSendByteArray = [byte[]]@($scanCodesToSend.Split(' ') | %{"0x$_"}) @@ -794,6 +791,9 @@ param([string]$vmName, [string]$scanCodes) } } + write-host "Special code <wait> found, will sleep $timeToWait second(s) at this point." + Start-Sleep -s $timeToWait + $scanCodesToSend = '' } else { if ($scanCodesToSend){ From 803af742c7502ebba3c4440dd7fccbece9e3ff8a Mon Sep 17 00:00:00 2001 From: Gildas Cherruel <gildas.cherruel@inin.com> Date: Mon, 30 Nov 2015 22:34:35 +0900 Subject: [PATCH 063/113] Remove all Dvd Drives on Generation 1 --- powershell/hyperv/hyperv.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 67ad79d82a5..410678ee4cf 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -171,6 +171,17 @@ Remove-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Controlle return err } +func DeleteAllDvdDrives(vmName string) error { + var script = ` +param([string]$vmName) +Get-VMDvdDrive -VMName $vmName | Remove-VMDvdDrive +` + + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName) + return err +} + func MountFloppyDrive(vmName string, path string) error { var script = ` param([string]$vmName, [string]$path) @@ -220,7 +231,7 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD return err } - return DeleteDvdDrive(vmName, 1, 0) + return DeleteAllDvdDrives(vmName) } } From a6e03aeee4d73998ed13d91a5d902d1b9a2a44c6 Mon Sep 17 00:00:00 2001 From: Gildas Cherruel <gildas.cherruel@inin.com> Date: Tue, 1 Dec 2015 11:20:47 +0900 Subject: [PATCH 064/113] Don't do anything if there are no scanCodes --- powershell/hyperv/hyperv.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 410678ee4cf..6d15c8425cc 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -659,6 +659,9 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { } func TypeScanCodes(vmName string, scanCodes string) error { + if len(scanCodes) == 0 { + return nil + } var script = ` param([string]$vmName, [string]$scanCodes) #Requires -Version 3 From 19a03f2bdc5e2760722ddf714188b29a04b1ea91 Mon Sep 17 00:00:00 2001 From: Gildas Cherruel <gildas.cherruel@inin.com> Date: Tue, 1 Dec 2015 11:41:02 +0900 Subject: [PATCH 065/113] Do not specify ComputerName with Get-CimInstance on Windows 10 --- powershell/hyperv/hyperv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 6d15c8425cc..81d6528b270 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -677,7 +677,7 @@ param([string]$vmName, [string]$scanCodes) $ErrorActionPreference = "Stop" - $vm = Get-CimInstance -ComputerName localhost -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1 + $vm = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1 if ($vm -eq $null){ Write-Error ("VirtualMachine({0}) is not found!" -f $VMName) } From d174dc9f5294ddbab30630f8e86b0d84ed3e8518 Mon Sep 17 00:00:00 2001 From: Gildas Cherruel <gildas.cherruel@inin.com> Date: Tue, 1 Dec 2015 13:44:34 +0900 Subject: [PATCH 066/113] Export of Hyper-V/VMCX machines --- powershell/hyperv/hyperv.go | 92 +++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 81d6528b270..d7f4467aaf5 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -287,6 +287,98 @@ func ExportVirtualMachine(vmName string, path string) error { var script = ` param([string]$vmName, [string]$path) Export-VM -Name $vmName -Path $path + +if (Test-Path -Path ([IO.Path]::Combine($path, $vmName, 'Virtual Machines', '*.VMCX'))) +{ + $vm = Get-VM -Name $vmName + $vm_adapter = Get-VMNetworkAdapter -VM $vm | Select -First 1 + + $config = [xml]@" +<?xml version="1.0" ?> +<configuration> + <properties> + <subtype type="integer">$($vm.Generation - 1)</subtype> + <name type="string">$($vm.Name)</name> + </properties> + <settings> + <processors> + <count type="integer">$($vm.ProcessorCount)</count> + </processors> + <memory> + <bank> + <dynamic_memory_enabled type="bool">$($vm.DynamicMemoryEnabled)</dynamic_memory_enabled> + <limit type="integer">$($vm.MemoryMaximum / 1MB)</limit> + <reservation type="integer">$($vm.MemoryMinimum / 1MB)</reservation> + <size type="integer">$($vm.MemoryStartup / 1MB)</size> + </bank> + </memory> + </settings> + <AltSwitchName type="string">$($vm_adapter.SwitchName)</AltSwitchName> + <boot> + <device0 type="string">Optical</device0> + </boot> + <secure_boot_enabled type="bool">False</secure_boot_enabled> + <notes type="string">$($vm.Notes)</notes> + <vm-controllers/> +</configuration> +"@ + + if ($vm.Generation -eq 1) + { + $vm_controllers = Get-VMIdeController -VM $vm + $controller_type = $config.SelectSingleNode('/configuration/vm-controllers') + # IDE controllers are not stored in a special XML container + } + else + { + $vm_controllers = Get-VMScsiController -VM $vm + $controller_type = $config.CreateElement('scsi') + $controller_type.SetAttribute('ChannelInstanceGuid', 'x') + # SCSI controllers are stored in the scsi XML container + if ((Get-VMFirmware -VM $vm).SecureBoot -eq [Microsoft.HyperV.PowerShell.OnOffState]::On) + { + $config.configuration.secure_boot_enabled.'#text' = 'True' + } + else + { + $config.configuration.secure_boot_enabled.'#text' = 'False' + } + } + + $vm_controllers | ForEach { + $controller = $config.CreateElement('controller' + $_.ControllerNumber) + $_.Drives | ForEach { + $drive = $config.CreateElement('drive' + ($_.DiskNumber + 0)) + $drive_path = $config.CreateElement('pathname') + $drive_path.SetAttribute('type', 'string') + $drive_path.AppendChild($config.CreateTextNode($_.Path)) + $drive_type = $config.CreateElement('type') + $drive_type.SetAttribute('type', 'string') + if ($_ -is [Microsoft.HyperV.PowerShell.HardDiskDrive]) + { + $drive_type.AppendChild($config.CreateTextNode('VHD')) + } + elseif ($_ -is [Microsoft.HyperV.PowerShell.DvdDrive]) + { + $drive_type.AppendChild($config.CreateTextNode('ISO')) + } + else + { + $drive_type.AppendChild($config.CreateTextNode('NONE')) + } + $drive.AppendChild($drive_path) + $drive.AppendChild($drive_type) + $controller.AppendChild($drive) + } + $controller_type.AppendChild($controller) + } + if ($controller_type.Name -ne 'vm-controllers') + { + $config.SelectSingleNode('/configuration/vm-controllers').AppendChild($controller_type) + } + + $config.Save([IO.Path]::Combine($path, $vm.Name, 'Virtual Machines', 'box.xml')) +} ` var ps powershell.PowerShellCmd From 20c15135dc80f34985d53174830e95bb51fd5fbd Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 1 Dec 2015 07:26:15 +0000 Subject: [PATCH 067/113] Do not try to type scancodes if there are none. We haven't figured out how to do this on Windows 10 or Windows 7 and below. This will at least allow other types of VMs to be built. --- powershell/hyperv/hyperv.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 410678ee4cf..2c040a0bf12 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -659,6 +659,10 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { } func TypeScanCodes(vmName string, scanCodes string) error { + if len(scanCodes) == 0 { + return nil + } + var script = ` param([string]$vmName, [string]$scanCodes) #Requires -Version 3 @@ -674,7 +678,7 @@ param([string]$vmName, [string]$scanCodes) $ErrorActionPreference = "Stop" - $vm = Get-CimInstance -ComputerName localhost -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1 + $vm = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_ComputerSystem -ErrorAction Ignore -Verbose:$false | where ElementName -eq $VMName | select -first 1 if ($vm -eq $null){ Write-Error ("VirtualMachine({0}) is not found!" -f $VMName) } From 108eeb6c1db96b9c23d0f56d9065caaec3558193 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 5 Dec 2015 02:31:28 -0800 Subject: [PATCH 068/113] try to get the virtual keyboard a number of different ways. This should hopefully work for Windows 7, Window 10 and Windows Server 2016 --- powershell/hyperv/hyperv.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 2c040a0bf12..41556ba64b1 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -682,8 +682,17 @@ param([string]$vmName, [string]$scanCodes) if ($vm -eq $null){ Write-Error ("VirtualMachine({0}) is not found!" -f $VMName) } - + $vmKeyboard = $vm | Get-CimAssociatedInstance -ResultClassName "Msvm_Keyboard" -ErrorAction Ignore -Verbose:$false + + if ($vmKeyboard -eq $null) { + $vmKeyboard = Get-CimInstance -Namespace "root\virtualization\v2" -ClassName Msvm_Keyboard -ErrorAction Ignore -Verbose:$false | where SystemName -eq $vm.Name | select -first 1 + } + + if ($vmKeyboard -eq $null) { + $vmKeyboard = Get-CimInstance -Namespace "root\virtualization" -ClassName Msvm_Keyboard -ErrorAction Ignore -Verbose:$false | where SystemName -eq $vm.Name | select -first 1 + } + if ($vmKeyboard -eq $null){ Write-Error ("VirtualMachine({0}) keyboard class is not found!" -f $VMName) } From 76378c7710b8390eacf410143764edfcf5dc0602 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 5 Dec 2015 03:03:33 -0800 Subject: [PATCH 069/113] A workaround for bug in Windows 10 and Windows 2016 where you have to specify a path when creating a dvd drive --- builder/hyperv/common/driver.go | 2 +- builder/hyperv/common/driver_ps_4.go | 4 +- builder/hyperv/common/step_mount_dvddrive.go | 2 +- .../common/step_mount_guest_additions.go | 190 ++++++++--------- .../common/step_mount_secondary_dvd_images.go | 196 +++++++++--------- powershell/hyperv/hyperv.go | 9 +- 6 files changed, 202 insertions(+), 201 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 44fa37c90bf..a62e3112b3d 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -86,7 +86,7 @@ type Driver interface { RestartVirtualMachine(string) error - CreateDvdDrive(string, uint) (uint, uint, error) + CreateDvdDrive(string, string, uint) (uint, uint, error) MountDvdDrive(string, string, uint, uint) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 0e6507ede45..15d6c6817d8 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -205,8 +205,8 @@ func (d *HypervPS4Driver) RestartVirtualMachine(vmName string) error { return hyperv.RestartVirtualMachine(vmName) } -func (d *HypervPS4Driver) CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { - return hyperv.CreateDvdDrive(vmName, generation) +func (d *HypervPS4Driver) CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) { + return hyperv.CreateDvdDrive(vmName, isoPath, generation) } func (d *HypervPS4Driver) MountDvdDrive(vmName string, path string, controllerNumber uint, controllerLocation uint) error { diff --git a/builder/hyperv/common/step_mount_dvddrive.go b/builder/hyperv/common/step_mount_dvddrive.go index 346af2c9eef..cff69627e73 100644 --- a/builder/hyperv/common/step_mount_dvddrive.go +++ b/builder/hyperv/common/step_mount_dvddrive.go @@ -30,7 +30,7 @@ func (s *StepMountDvdDrive) Run(state multistep.StateBag) multistep.StepAction { // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) var dvdControllerProperties DvdControllerProperties - controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation) if err != nil { state.Put("error", err) ui.Error(err.Error()) diff --git a/builder/hyperv/common/step_mount_guest_additions.go b/builder/hyperv/common/step_mount_guest_additions.go index ee6b69afcac..dff8b90c360 100644 --- a/builder/hyperv/common/step_mount_guest_additions.go +++ b/builder/hyperv/common/step_mount_guest_additions.go @@ -1,96 +1,96 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" -) - -type StepMountGuestAdditions struct { - GuestAdditionsMode string - GuestAdditionsPath string - Generation uint -} - -func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - if s.GuestAdditionsMode != "attach" { - ui.Say("Skipping mounting Integration Services Setup Disk...") - return multistep.ActionContinue - } - - driver := state.Get("driver").(Driver) - ui.Say("Mounting Integration Services Setup Disk...") - - vmName := state.Get("vmName").(string) - - // should be able to mount up to 60 additional iso images using SCSI - // but Windows would only allow a max of 22 due to available drive letters - // Will Windows assign DVD drives to A: and B: ? - - // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - - var dvdControllerProperties DvdControllerProperties - - controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - dvdControllerProperties.ControllerNumber = controllerNumber - dvdControllerProperties.ControllerLocation = controllerLocation - dvdControllerProperties.Existing = false - state.Put("guest.dvd.properties", dvdControllerProperties) - - ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) - err = driver.MountDvdDrive(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) - - return multistep.ActionContinue -} - -func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { - if s.GuestAdditionsMode != "attach" { - return - } - - dvdControllerState := state.Get("guest.dvd.properties") - - if dvdControllerState == nil { - return - } - - dvdController := dvdControllerState.(DvdControllerProperties) - ui := state.Get("ui").(packer.Ui) - driver := state.Get("driver").(Driver) - vmName := state.Get("vmName").(string) - errorMsg := "Error unmounting Integration Services dvd drive: %s" - - ui.Say("Cleanup Integration Services dvd drive...") - - if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } else { - err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountGuestAdditions struct { + GuestAdditionsMode string + GuestAdditionsPath string + Generation uint +} + +func (s *StepMountGuestAdditions) Run(state multistep.StateBag) multistep.StepAction { + ui := state.Get("ui").(packer.Ui) + + if s.GuestAdditionsMode != "attach" { + ui.Say("Skipping mounting Integration Services Setup Disk...") + return multistep.ActionContinue + } + + driver := state.Get("driver").(Driver) + ui.Say("Mounting Integration Services Setup Disk...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + + var dvdControllerProperties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.GuestAdditionsPath, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + dvdControllerProperties.ControllerNumber = controllerNumber + dvdControllerProperties.ControllerLocation = controllerLocation + dvdControllerProperties.Existing = false + state.Put("guest.dvd.properties", dvdControllerProperties) + + ui.Say(fmt.Sprintf("Mounting Integration Services dvd drive %s ...", s.GuestAdditionsPath)) + err = driver.MountDvdDrive(vmName, s.GuestAdditionsPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", s.GuestAdditionsPath, controllerNumber, controllerLocation)) + + return multistep.ActionContinue +} + +func (s *StepMountGuestAdditions) Cleanup(state multistep.StateBag) { + if s.GuestAdditionsMode != "attach" { + return + } + + dvdControllerState := state.Get("guest.dvd.properties") + + if dvdControllerState == nil { + return + } + + dvdController := dvdControllerState.(DvdControllerProperties) + ui := state.Get("ui").(packer.Ui) + driver := state.Get("driver").(Driver) + vmName := state.Get("vmName").(string) + errorMsg := "Error unmounting Integration Services dvd drive: %s" + + ui.Say("Cleanup Integration Services dvd drive...") + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } } \ No newline at end of file diff --git a/builder/hyperv/common/step_mount_secondary_dvd_images.go b/builder/hyperv/common/step_mount_secondary_dvd_images.go index 548fd4a8aa6..eb5ce25bc31 100644 --- a/builder/hyperv/common/step_mount_secondary_dvd_images.go +++ b/builder/hyperv/common/step_mount_secondary_dvd_images.go @@ -1,98 +1,98 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" -) - -type StepMountSecondaryDvdImages struct { - IsoPaths []string - Generation uint -} - -type DvdControllerProperties struct { - ControllerNumber uint - ControllerLocation uint - Existing bool -} - -func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - ui.Say("Mounting secondary DVD images...") - - vmName := state.Get("vmName").(string) - - // should be able to mount up to 60 additional iso images using SCSI - // but Windows would only allow a max of 22 due to available drive letters - // Will Windows assign DVD drives to A: and B: ? - - // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) - var dvdProperties []DvdControllerProperties - - for _, isoPath := range s.IsoPaths { - var properties DvdControllerProperties - - controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, s.Generation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - properties.ControllerNumber = controllerNumber - properties.ControllerLocation = controllerLocation - properties.Existing = false - dvdProperties = append(dvdProperties, properties) - state.Put("secondary.dvd.properties", dvdProperties) - - ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) - err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) - } - - return multistep.ActionContinue -} - -func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { - dvdControllersState := state.Get("secondary.dvd.properties") - - if dvdControllersState == nil { - return - } - - dvdControllers := dvdControllersState.([]DvdControllerProperties) - driver := state.Get("driver").(Driver) - ui := state.Get("ui").(packer.Ui) - vmName := state.Get("vmName").(string) - errorMsg := "Error unmounting secondary dvd drive: %s" - - ui.Say("Clean up secondary dvd drives...") - - for _, dvdController := range dvdControllers { - - if dvdController.Existing { - err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } else { - err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) - if err != nil { - log.Print(fmt.Sprintf(errorMsg, err)) - } - } - } -} +// Copyright (c) Microsoft Open Technologies, Inc. +// All Rights Reserved. +// Licensed under the Apache License, Version 2.0. +// See License.txt in the project root for license information. +package common + +import ( + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" +) + +type StepMountSecondaryDvdImages struct { + IsoPaths []string + Generation uint +} + +type DvdControllerProperties struct { + ControllerNumber uint + ControllerLocation uint + Existing bool +} + +func (s *StepMountSecondaryDvdImages) Run(state multistep.StateBag) multistep.StepAction { + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + ui.Say("Mounting secondary DVD images...") + + vmName := state.Get("vmName").(string) + + // should be able to mount up to 60 additional iso images using SCSI + // but Windows would only allow a max of 22 due to available drive letters + // Will Windows assign DVD drives to A: and B: ? + + // For IDE, there are only 2 controllers (0,1) with 2 locations each (0,1) + var dvdProperties []DvdControllerProperties + + for _, isoPath := range s.IsoPaths { + var properties DvdControllerProperties + + controllerNumber, controllerLocation, err := driver.CreateDvdDrive(vmName, isoPath, s.Generation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + properties.ControllerNumber = controllerNumber + properties.ControllerLocation = controllerLocation + properties.Existing = false + dvdProperties = append(dvdProperties, properties) + state.Put("secondary.dvd.properties", dvdProperties) + + ui.Say(fmt.Sprintf("Mounting secondary dvd drive %s ...", isoPath)) + err = driver.MountDvdDrive(vmName, isoPath, controllerNumber, controllerLocation) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + log.Println(fmt.Sprintf("ISO %s mounted on DVD controller %v, location %v", isoPath, controllerNumber, controllerLocation)) + } + + return multistep.ActionContinue +} + +func (s *StepMountSecondaryDvdImages) Cleanup(state multistep.StateBag) { + dvdControllersState := state.Get("secondary.dvd.properties") + + if dvdControllersState == nil { + return + } + + dvdControllers := dvdControllersState.([]DvdControllerProperties) + driver := state.Get("driver").(Driver) + ui := state.Get("ui").(packer.Ui) + vmName := state.Get("vmName").(string) + errorMsg := "Error unmounting secondary dvd drive: %s" + + ui.Say("Clean up secondary dvd drives...") + + for _, dvdController := range dvdControllers { + + if dvdController.Existing { + err := driver.UnmountDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } else { + err := driver.DeleteDvdDrive(vmName, dvdController.ControllerNumber, dvdController.ControllerLocation) + if err != nil { + log.Print(fmt.Sprintf(errorMsg, err)) + } + } + } +} diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 41556ba64b1..7a981cb1e3e 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -51,7 +51,7 @@ $ip return cmdOut, err } -func CreateDvdDrive(vmName string, generation uint) (uint, uint, error) { +func CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) { var ps powershell.PowerShellCmd var script string var controllerNumber uint @@ -89,12 +89,13 @@ $lastControllerNumber } script = ` -param([string]$vmName,[int]$controllerNumber) -$dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -Passthru +param([string]$vmName, [string]$isoPath, [int]$controllerNumber) +$dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -path $isoPath -Passthru +Set-VMDvdDrive -path $null $dvdController.ControllerLocation ` - cmdOut, err := ps.Output(script, vmName, strconv.FormatInt(int64(controllerNumber), 10)) + cmdOut, err := ps.Output(script, vmName, isoPath, strconv.FormatInt(int64(controllerNumber), 10)) if err != nil { return controllerNumber, 0, err } From 1aaf54b16f133899c2f642a4b0092e5fd4ed2602 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 5 Dec 2015 03:13:38 -0800 Subject: [PATCH 070/113] Forget to pass in the dvd drive to eject the iso from --- powershell/hyperv/hyperv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 792686e23aa..fd29bf6aca7 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -91,7 +91,7 @@ $lastControllerNumber script = ` param([string]$vmName, [string]$isoPath, [int]$controllerNumber) $dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -path $isoPath -Passthru -Set-VMDvdDrive -path $null +$dvdController | Set-VMDvdDrive -path $null $dvdController.ControllerLocation ` From b02452f9f21110ea434c64b87ff8539e5aef41b6 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 5 Dec 2015 03:41:30 -0800 Subject: [PATCH 071/113] Windows Server 2016 won't let you set the first boot device --- powershell/hyperv/hyperv.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index fd29bf6aca7..c6f4ef68e62 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -151,7 +151,7 @@ Set-VMBios -VMName $vmName -StartupOrder @("CD", "IDE","LegacyNetworkAdapter","F param([string]$vmName,[int]$controllerNumber,[int]$controllerLocation) $vmDvdDrive = Get-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -ControllerLocation $controllerLocation if (!$vmDvdDrive) {throw 'unable to find dvd drive'} -Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive +Set-VMFirmware -VMName $vmName -FirstBootDevice $vmDvdDrive -ErrorAction SilentlyContinue ` var ps powershell.PowerShellCmd err := ps.Run(script, vmName, strconv.FormatInt(int64(controllerNumber), 10), strconv.FormatInt(int64(controllerLocation), 10)) From 1b6223ba990cd2306575abeb882a7241c45707ee Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 25 Jan 2016 22:43:40 +0000 Subject: [PATCH 072/113] http server has moved to common step --- builder/hyperv/common/run_config.go | 18 ------ builder/hyperv/common/step_http_server.go | 78 ----------------------- builder/hyperv/iso/builder.go | 4 +- 3 files changed, 3 insertions(+), 97 deletions(-) delete mode 100644 builder/hyperv/common/step_http_server.go diff --git a/builder/hyperv/common/run_config.go b/builder/hyperv/common/run_config.go index 4b73ae532f1..3ac72471cd5 100644 --- a/builder/hyperv/common/run_config.go +++ b/builder/hyperv/common/run_config.go @@ -1,7 +1,6 @@ package common import ( - "errors" "fmt" "github.com/mitchellh/packer/template/interpolate" "time" @@ -11,10 +10,6 @@ type RunConfig struct { Headless bool `mapstructure:"headless"` RawBootWait string `mapstructure:"boot_wait"` - HTTPDir string `mapstructure:"http_directory"` - HTTPPortMin uint `mapstructure:"http_port_min"` - HTTPPortMax uint `mapstructure:"http_port_max"` - BootWait time.Duration `` } @@ -23,14 +18,6 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { c.RawBootWait = "10s" } - if c.HTTPPortMin == 0 { - c.HTTPPortMin = 8000 - } - - if c.HTTPPortMax == 0 { - c.HTTPPortMax = 9000 - } - var errs []error var err error @@ -42,10 +29,5 @@ func (c *RunConfig) Prepare(ctx *interpolate.Context) []error { } } - if c.HTTPPortMin > c.HTTPPortMax { - errs = append(errs, - errors.New("http_port_min must be less than http_port_max")) - } - return errs } diff --git a/builder/hyperv/common/step_http_server.go b/builder/hyperv/common/step_http_server.go deleted file mode 100644 index 55874992e85..00000000000 --- a/builder/hyperv/common/step_http_server.go +++ /dev/null @@ -1,78 +0,0 @@ -package common - -import ( - "fmt" - "github.com/mitchellh/multistep" - "github.com/mitchellh/packer/packer" - "log" - "math/rand" - "net" - "net/http" -) - -// This step creates and runs the HTTP server that is serving files from the -// directory specified by the 'http_directory` configuration parameter in the -// template. -// -// Uses: -// ui packer.Ui -// -// Produces: -// http_port int - The port the HTTP server started on. -type StepHTTPServer struct { - HTTPDir string - HTTPPortMin uint - HTTPPortMax uint - - l net.Listener -} - -func (s *StepHTTPServer) Run(state multistep.StateBag) multistep.StepAction { - ui := state.Get("ui").(packer.Ui) - - var httpPort uint = 0 - if s.HTTPDir == "" { - state.Put("http_port", httpPort) - return multistep.ActionContinue - } - - // Find an available TCP port for our HTTP server - var httpAddr string - portRange := int(s.HTTPPortMax - s.HTTPPortMin) - for { - var err error - var offset uint = 0 - - if portRange > 0 { - // Intn will panic if portRange == 0, so we do a check. - offset = uint(rand.Intn(portRange)) - } - - httpPort = offset + s.HTTPPortMin - httpAddr = fmt.Sprintf("0.0.0.0:%d", httpPort) - log.Printf("Trying port: %d", httpPort) - s.l, err = net.Listen("tcp", httpAddr) - if err == nil { - break - } - } - - ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort)) - - // Start the HTTP server and run it in the background - fileServer := http.FileServer(http.Dir(s.HTTPDir)) - server := &http.Server{Addr: httpAddr, Handler: fileServer} - go server.Serve(s.l) - - // Save the address into the state so it can be accessed in the future - state.Put("http_port", httpPort) - - return multistep.ActionContinue -} - -func (s *StepHTTPServer) Cleanup(multistep.StateBag) { - if s.l != nil { - // Close the listener so that the HTTP server stops - s.l.Close() - } -} diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 430499e4807..c98e584ca2e 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -46,6 +46,7 @@ type Builder struct { type Config struct { common.PackerConfig `mapstructure:",squash"` + common.HTTPConfig `mapstructure:",squash"` hypervcommon.FloppyConfig `mapstructure:",squash"` hypervcommon.OutputConfig `mapstructure:",squash"` hypervcommon.SSHConfig `mapstructure:",squash"` @@ -141,6 +142,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Accumulate any errors and warnings var errs *packer.MultiError errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) + errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) @@ -348,7 +350,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe &common.StepCreateFloppy{ Files: b.config.FloppyFiles, }, - &hypervcommon.StepHTTPServer{ + &common.StepHTTPServer{ HTTPDir: b.config.HTTPDir, HTTPPortMin: b.config.HTTPPortMin, HTTPPortMax: b.config.HTTPPortMax, From f99222119e9e2c0a7d82057311b626711bff2e70 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 29 Mar 2016 22:56:26 +0100 Subject: [PATCH 073/113] Generate switch name using uuid v1 --- builder/hyperv/common/step_create_external_switch.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/step_create_external_switch.go b/builder/hyperv/common/step_create_external_switch.go index 4ab1c335b58..ab56dc1ed34 100644 --- a/builder/hyperv/common/step_create_external_switch.go +++ b/builder/hyperv/common/step_create_external_switch.go @@ -5,10 +5,11 @@ package common import ( - "code.google.com/p/go-uuid/uuid" "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" + "github.com/twinj/uuid" ) // This step creates switch for VM. @@ -30,7 +31,7 @@ func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepA ui.Say("Creating external switch...") - packerExternalSwitchName := "paes_" + uuid.New() + packerExternalSwitchName := "paes_" + uuid.NewV1().String() err = driver.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) if err != nil { From 02192d127b4816ee4d4a5f59aec2a14ace7daa83 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 29 Mar 2016 23:07:26 +0100 Subject: [PATCH 074/113] Merged completed --- command/plugin.go | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/command/plugin.go b/command/plugin.go index cae06adfa82..9d89f4283f4 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -19,16 +19,9 @@ import ( azurearmbuilder "github.com/mitchellh/packer/builder/azure/arm" digitaloceanbuilder "github.com/mitchellh/packer/builder/digitalocean" dockerbuilder "github.com/mitchellh/packer/builder/docker" - dockerimportpostprocessor "github.com/mitchellh/packer/post-processor/docker-import" - dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push" - dockersavepostprocessor "github.com/mitchellh/packer/post-processor/docker-save" - dockertagpostprocessor "github.com/mitchellh/packer/post-processor/docker-tag" filebuilder "github.com/mitchellh/packer/builder/file" - fileprovisioner "github.com/mitchellh/packer/provisioner/file" googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" hypervbuilder "github.com/mitchellh/packer/builder/hyperv/iso" - filebuilder "github.com/mitchellh/packer/builder/file" - googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" nullbuilder "github.com/mitchellh/packer/builder/null" openstackbuilder "github.com/mitchellh/packer/builder/openstack" parallelsisobuilder "github.com/mitchellh/packer/builder/parallels/iso" @@ -78,17 +71,9 @@ var Builders = map[string]packer.Builder{ "docker": new(dockerbuilder.Builder), "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), + "hyperv-iso": new(hypervbuilder.Builder), "null": new(nullbuilder.Builder), "openstack": new(openstackbuilder.Builder), - "amazon-ebs": new(amazonebsbuilder.Builder), - "amazon-instance": new(amazoninstancebuilder.Builder), - "digitalocean": new(digitaloceanbuilder.Builder), - "docker": new(dockerbuilder.Builder), - "file": new(filebuilder.Builder), - "googlecompute": new(googlecomputebuilder.Builder), - "hyperv-iso": new(hypervbuilder.Builder), - "null": new(nullbuilder.Builder), - "openstack": new(openstackbuilder.Builder), "parallels-iso": new(parallelsisobuilder.Builder), "parallels-pvm": new(parallelspvmbuilder.Builder), "qemu": new(qemubuilder.Builder), From 2a4149e9f9bebfff9306fab3449a8ff761909c12 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 31 Mar 2016 21:35:40 +0100 Subject: [PATCH 075/113] Use built in UUID generator --- builder/hyperv/common/step_create_external_switch.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/builder/hyperv/common/step_create_external_switch.go b/builder/hyperv/common/step_create_external_switch.go index ab56dc1ed34..75249b8b450 100644 --- a/builder/hyperv/common/step_create_external_switch.go +++ b/builder/hyperv/common/step_create_external_switch.go @@ -8,8 +8,8 @@ import ( "fmt" "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/common/uuid" "github.com/mitchellh/packer/packer" - "github.com/twinj/uuid" ) // This step creates switch for VM. @@ -31,7 +31,7 @@ func (s *StepCreateExternalSwitch) Run(state multistep.StateBag) multistep.StepA ui.Say("Creating external switch...") - packerExternalSwitchName := "paes_" + uuid.NewV1().String() + packerExternalSwitchName := "paes_" + uuid.TimeOrderedUUID() err = driver.CreateExternalVirtualSwitch(vmName, packerExternalSwitchName) if err != nil { From 28e08ff451aa5348d95e159b0140748e190c3928 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Apr 2016 09:35:22 +0100 Subject: [PATCH 076/113] Fix parallels test so they run on windows. IsAbs returns false for c:\ --- builder/parallels/common/output_config.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/builder/parallels/common/output_config.go b/builder/parallels/common/output_config.go index 2b7c7c94c3f..00370d372f1 100644 --- a/builder/parallels/common/output_config.go +++ b/builder/parallels/common/output_config.go @@ -3,7 +3,7 @@ package common import ( "fmt" "os" - "path" + "path/filepath" "github.com/mitchellh/packer/common" "github.com/mitchellh/packer/template/interpolate" @@ -19,18 +19,21 @@ func (c *OutputConfig) Prepare(ctx *interpolate.Context, pc *common.PackerConfig } var errs []error + fmt.Println("OutputDirBefore=", c.OutputDir) - if path.IsAbs(c.OutputDir) { - c.OutputDir = path.Clean(c.OutputDir) - } else { - wd, err := os.Getwd() + if !filepath.IsAbs(c.OutputDir) { + outputDir, err := filepath.Abs(c.OutputDir) if err != nil { errs = append(errs, err) + return errs + } else { + c.OutputDir = outputDir } - c.OutputDir = path.Clean(path.Join(wd, c.OutputDir)) } if !pc.PackerForce { + fmt.Println("OutputDirAfter=", c.OutputDir) + if _, err := os.Stat(c.OutputDir); err == nil { errs = append(errs, fmt.Errorf( "Output directory '%s' already exists. It must not exist.", c.OutputDir)) From eef1970a003748d2280f964c1df1eccbbd4be5a9 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Apr 2016 10:51:05 +0100 Subject: [PATCH 077/113] On windows a lot of git clients will convert LF to CRLF. This would be a problem where file contents are compared exactly --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000000..7230f36f4b4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ + +common/test-fixtures/root/* eol=lf \ No newline at end of file From 9b4f019c9cfb5f798821574ae6eaa992b5fc5d7b Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Apr 2016 10:55:02 +0100 Subject: [PATCH 078/113] Should not have added this file to source control --- common/test-fixtures/root/another.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 common/test-fixtures/root/another.txt diff --git a/common/test-fixtures/root/another.txt b/common/test-fixtures/root/another.txt deleted file mode 100644 index 9b24da92a91..00000000000 --- a/common/test-fixtures/root/another.txt +++ /dev/null @@ -1 +0,0 @@ -another From 03dea3826cfd025db38010ec1a4452e6973ad200 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Apr 2016 10:56:15 +0100 Subject: [PATCH 079/113] When dealing with windows the file url format is file:///c:/ --- common/config_test.go | 2 +- common/download_test.go | 10 +++++++++- common/iso_config.go | 9 ++++++++- common/iso_config_test.go | 9 +++++++-- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/common/config_test.go b/common/config_test.go index 92a7316a314..8bd68473919 100644 --- a/common/config_test.go +++ b/common/config_test.go @@ -120,7 +120,7 @@ func TestDownloadableURL_FilePaths(t *testing.T) { }() // Test some cases with and without a schema prefix - for _, prefix := range []string{"", "file://"} { + for _, prefix := range []string{"", filePrefix} { // Nonexistent file _, err = DownloadableURL(prefix + "i/dont/exist") if err != nil { diff --git a/common/download_test.go b/common/download_test.go index 51f6f270c82..497a0564990 100644 --- a/common/download_test.go +++ b/common/download_test.go @@ -8,6 +8,7 @@ import ( "net/http" "net/http/httptest" "os" + "runtime" "testing" ) @@ -56,6 +57,7 @@ func TestDownloadClient_basic(t *testing.T) { Url: ts.URL + "/basic.txt", TargetPath: tf.Name(), }) + path, err := client.Get() if err != nil { t.Fatalf("err: %s", err) @@ -352,7 +354,13 @@ func TestDownloadFileUrl(t *testing.T) { // source_path is a file path and source is a network path sourcePath := fmt.Sprintf("%s/test-fixtures/fileurl/%s", cwd, "cake") - source := fmt.Sprintf("file://" + sourcePath) + + filePrefix := "file://" + if runtime.GOOS == "windows" { + filePrefix += "/" + } + + source := fmt.Sprintf(filePrefix + sourcePath) t.Logf("Trying to download %s", source) config := &DownloadConfig{ diff --git a/common/iso_config.go b/common/iso_config.go index fddea5bb3d2..7e8883af6b1 100644 --- a/common/iso_config.go +++ b/common/iso_config.go @@ -9,6 +9,7 @@ import ( "net/url" "os" "path/filepath" + "runtime" "strings" "github.com/mitchellh/packer/template/interpolate" @@ -81,7 +82,13 @@ func (c *ISOConfig) Prepare(ctx *interpolate.Context) ([]string, []error) { return warnings, errs } case "file": - file, err := os.Open(u.Path) + path := u.Path + + if runtime.GOOS == "windows" && len(path) > 2 && path[0] == '/' && path[2] == ':' { + path = strings.TrimLeft(path, "/") + } + + file, err := os.Open(path) if err != nil { errs = append(errs, err) return warnings, errs diff --git a/common/iso_config_test.go b/common/iso_config_test.go index baaeea2b6ed..0660d2b40c5 100644 --- a/common/iso_config_test.go +++ b/common/iso_config_test.go @@ -4,6 +4,7 @@ import ( "fmt" "io/ioutil" "reflect" + "runtime" "testing" ) @@ -70,7 +71,11 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) { i.ISOChecksum = "" cs_file, _ := ioutil.TempFile("", "packer-test-") ioutil.WriteFile(cs_file.Name(), []byte(cs_bsd_style), 0666) - i.ISOChecksumURL = fmt.Sprintf("file://%s", cs_file.Name()) + filePrefix := "file://" + if runtime.GOOS == "windows" { + filePrefix += "/" + } + i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name()) warns, err = i.Prepare(nil) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) @@ -88,7 +93,7 @@ func TestISOConfigPrepare_ISOChecksumURL(t *testing.T) { i.ISOChecksum = "" cs_file, _ = ioutil.TempFile("", "packer-test-") ioutil.WriteFile(cs_file.Name(), []byte(cs_gnu_style), 0666) - i.ISOChecksumURL = fmt.Sprintf("file://%s", cs_file.Name()) + i.ISOChecksumURL = fmt.Sprintf("%s%s", filePrefix, cs_file.Name()) warns, err = i.Prepare(nil) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) From fc6e5376d1a60c156c5b9c6655143229005b200a Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Apr 2016 11:00:32 +0100 Subject: [PATCH 080/113] added file with correct line endings --- common/test-fixtures/root/another.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 common/test-fixtures/root/another.txt diff --git a/common/test-fixtures/root/another.txt b/common/test-fixtures/root/another.txt new file mode 100644 index 00000000000..9b24da92a91 --- /dev/null +++ b/common/test-fixtures/root/another.txt @@ -0,0 +1 @@ +another From 8c9248e42d047bd4602f9a0d0581c46f47dc6c6b Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Apr 2016 12:36:18 +0100 Subject: [PATCH 081/113] Make Ansible provisioner commands that are bash or shell scripts work with windows --- provisioner/ansible/provisioner.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/provisioner/ansible/provisioner.go b/provisioner/ansible/provisioner.go index e38e4bc08c5..cf84ad341ae 100644 --- a/provisioner/ansible/provisioner.go +++ b/provisioner/ansible/provisioner.go @@ -17,6 +17,7 @@ import ( "os/exec" "path/filepath" "regexp" + "runtime" "strconv" "strings" "sync" @@ -123,6 +124,9 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { if p.config.User == "" { p.config.User = os.Getenv("USER") } + if p.config.User == "" { + p.config.User = os.Getenv("USERNAME") + } if p.config.User == "" { errs = packer.MultiErrorAppend(errs, fmt.Errorf("user: could not determine current user from environment.")) } @@ -134,16 +138,26 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } func (p *Provisioner) getVersion() error { - out, err := exec.Command(p.config.Command, "--version").Output() + var output []byte + var err error + + if runtime.GOOS == "windows" && strings.HasSuffix(p.config.Command, ".sh") { + output, err = exec.Command("sh", p.config.Command, "--version").Output() + } else { + output, err = exec.Command(p.config.Command, "--version").Output() + } + if err != nil { return err } + out := string(output) + versionRe := regexp.MustCompile(`\w (\d+\.\d+[.\d+]*)`) - matches := versionRe.FindStringSubmatch(string(out)) + matches := versionRe.FindStringSubmatch(out) if matches == nil { return fmt.Errorf( - "Could not find %s version in output:\n%s", p.config.Command, string(out)) + "Could not find %s version in output:\n%s", p.config.Command, out) } version := matches[1] From c791d0ddc1b40cf6dfd81586fe5863d7c48cafb1 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 29 Jun 2016 22:53:29 +0100 Subject: [PATCH 082/113] Try to match style of other builders --- builder/hyperv/iso/builder.go | 84 +++---------------- plugin/builder-hyperv-iso/build-and-deploy.sh | 3 - plugin/builder-hyperv-iso/main.go | 15 ---- plugin/builder-hyperv-iso/main_test.go | 5 -- 4 files changed, 12 insertions(+), 95 deletions(-) delete mode 100644 plugin/builder-hyperv-iso/build-and-deploy.sh delete mode 100644 plugin/builder-hyperv-iso/main.go delete mode 100644 plugin/builder-hyperv-iso/main_test.go diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index c98e584ca2e..849e7d90aae 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -47,6 +47,7 @@ type Builder struct { type Config struct { common.PackerConfig `mapstructure:",squash"` common.HTTPConfig `mapstructure:",squash"` + common.ISOConfig `mapstructure:",squash"` hypervcommon.FloppyConfig `mapstructure:",squash"` hypervcommon.OutputConfig `mapstructure:",squash"` hypervcommon.SSHConfig `mapstructure:",squash"` @@ -72,30 +73,6 @@ type Config struct { // SecondaryDvdImages []string `mapstructure:"secondary_iso_images"` - // The checksum for the OS ISO file. Because ISO files are so large, - // this is required and Packer will verify it prior to booting a virtual - // machine with the ISO attached. The type of the checksum is specified - // with iso_checksum_type, documented below. - ISOChecksum string `mapstructure:"iso_checksum"` - // The type of the checksum specified in iso_checksum. Valid values are - // "none", "md5", "sha1", "sha256", or "sha512" currently. While "none" - // will skip checksumming, this is not recommended since ISO files are - // generally large and corruption does happen from time to time. - ISOChecksumType string `mapstructure:"iso_checksum_type"` - // A URL to the ISO containing the installation image. This URL can be - // either an HTTP URL or a file URL (or path to a file). If this is an - // HTTP URL, Packer will download it and cache it between runs. - RawSingleISOUrl string `mapstructure:"iso_url"` - - // Multiple URLs for the ISO to download. Packer will try these in order. - // If anything goes wrong attempting to download or while downloading a - // single URL, it will move on to the next. All URLs must point to the - // same file (same checksum). By default this is empty and iso_url is - // used. Only one of iso_url or iso_urls can be specified. - ISOUrls []string `mapstructure:"iso_urls"` - - TargetPath string `mapstructure:"iso_target_path"` - // Should integration services iso be mounted GuestAdditionsMode string `mapstructure:"guest_additions_mode"` @@ -141,13 +118,18 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { // Accumulate any errors and warnings var errs *packer.MultiError + warnings := make([]string, 0) + + isoWarnings, isoErrs := b.config.ISOConfig.Prepare(&b.config.ctx) + warnings = append(warnings, isoWarnings...) + errs = packer.MultiErrorAppend(errs, isoErrs...) + errs = packer.MultiErrorAppend(errs, b.config.FloppyConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.HTTPConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.RunConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.OutputConfig.Prepare(&b.config.ctx, &b.config.PackerConfig)...) errs = packer.MultiErrorAppend(errs, b.config.SSHConfig.Prepare(&b.config.ctx)...) errs = packer.MultiErrorAppend(errs, b.config.ShutdownConfig.Prepare(&b.config.ctx)...) - warnings := make([]string, 0) err = b.checkDiskSize() if err != nil { @@ -205,47 +187,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "Communicator", b.config.Communicator)) // Errors - if b.config.ISOChecksumType == "" { - errs = packer.MultiErrorAppend( - errs, errors.New("The iso_checksum_type must be specified.")) - } else { - b.config.ISOChecksumType = strings.ToLower(b.config.ISOChecksumType) - if b.config.ISOChecksumType != "none" { - if b.config.ISOChecksum == "" { - errs = packer.MultiErrorAppend( - errs, errors.New("Due to large file sizes, an iso_checksum is required")) - } else { - b.config.ISOChecksum = strings.ToLower(b.config.ISOChecksum) - } - - if h := common.HashForType(b.config.ISOChecksumType); h == nil { - errs = packer.MultiErrorAppend( - errs, - fmt.Errorf("Unsupported checksum type: %s", b.config.ISOChecksumType)) - } - } - } - - if b.config.RawSingleISOUrl == "" && len(b.config.ISOUrls) == 0 { - errs = packer.MultiErrorAppend( - errs, errors.New("One of iso_url or iso_urls must be specified.")) - } else if b.config.RawSingleISOUrl != "" && len(b.config.ISOUrls) > 0 { - errs = packer.MultiErrorAppend( - errs, errors.New("Only one of iso_url or iso_urls may be specified.")) - } else if b.config.RawSingleISOUrl != "" { - b.config.ISOUrls = []string{b.config.RawSingleISOUrl} - } - - for i, url := range b.config.ISOUrls { - b.config.ISOUrls[i], err = common.DownloadableURL(url) - if err != nil { - errs = packer.MultiErrorAppend( - errs, fmt.Errorf("Failed to parse iso_url %d: %s", i+1, err)) - } - } - - log.Println(fmt.Sprintf("%s: %v", "RawSingleISOUrl", b.config.RawSingleISOUrl)) - if b.config.GuestAdditionsMode == "" { b.config.GuestAdditionsMode = "attach" } @@ -291,11 +232,6 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } // Warnings - if b.config.ISOChecksumType == "none" { - warnings = append(warnings, - "A checksum type of 'none' was specified. Since ISO files are so big,\n"+ - "a checksum is highly recommended.") - } if b.config.ShutdownCommand == "" { warnings = append(warnings, @@ -328,6 +264,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe state := new(multistep.BasicStateBag) state.Put("cache", cache) state.Put("config", &b.config) + state.Put("debug", b.config.PackerDebug) state.Put("driver", driver) state.Put("hook", hook) state.Put("ui", ui) @@ -434,13 +371,16 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe // Run the steps. if b.config.PackerDebug { + pauseFn := common.MultistepDebugFn(ui) + state.Put("pauseFn", pauseFn) b.runner = &multistep.DebugRunner{ Steps: steps, - PauseFn: common.MultistepDebugFn(ui), + PauseFn: pauseFn, } } else { b.runner = &multistep.BasicRunner{Steps: steps} } + b.runner.Run(state) // Report any errors. diff --git a/plugin/builder-hyperv-iso/build-and-deploy.sh b/plugin/builder-hyperv-iso/build-and-deploy.sh deleted file mode 100644 index 8e0185a4edd..00000000000 --- a/plugin/builder-hyperv-iso/build-and-deploy.sh +++ /dev/null @@ -1,3 +0,0 @@ -go build -cp packer-builder-hyperv-iso.exe ../../../bin/ - diff --git a/plugin/builder-hyperv-iso/main.go b/plugin/builder-hyperv-iso/main.go deleted file mode 100644 index 66461504945..00000000000 --- a/plugin/builder-hyperv-iso/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -import ( - "github.com/mitchellh/packer/builder/hyperv/iso" - "github.com/mitchellh/packer/packer/plugin" -) - -func main() { - server, err := plugin.Server() - if err != nil { - panic(err) - } - server.RegisterBuilder(new(iso.Builder)) - server.Serve() -} diff --git a/plugin/builder-hyperv-iso/main_test.go b/plugin/builder-hyperv-iso/main_test.go deleted file mode 100644 index b07e1808032..00000000000 --- a/plugin/builder-hyperv-iso/main_test.go +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. -// All Rights Reserved. -// Licensed under the Apache License, Version 2.0. -// See License.txt in the project root for license information. -package main From fe766818734cacccb5486f7fcc03c8923a08aade Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 29 Jun 2016 23:07:27 +0100 Subject: [PATCH 083/113] Make hiding of progress backward compatible with old versions of powershell --- powershell/powershell_test.go | 2 +- provisioner/powershell/elevated.go | 2 +- provisioner/powershell/provisioner.go | 6 +++--- provisioner/powershell/provisioner_test.go | 18 +++++++++--------- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 0925e3c74cf..2dcc360853a 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -37,7 +37,7 @@ func TestOutput(t *testing.T) { func TestRunFile(t *testing.T) { var blockBuffer bytes.Buffer - blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) $ProgressPreference='SilentlyContinue'; $n = $x + $y; Write-Output "$a $b $n";`) + blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $n = $x + $y; Write-Output "$a $b $n";`) var ps PowerShellCmd cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index cb3336ae291..80bdc005a6d 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -58,7 +58,7 @@ $t.XmlText = @' </Actions> </Task> '@ -$ProgressPreference="SilentlyContinue"; +if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"} $f = $s.GetFolder("\") $f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", "{{.Password}}", 1, $null) | Out-Null $t = $f.GetTask("\$name") diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 90cde164fdd..4d8a98e5a13 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -120,11 +120,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -425,7 +425,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("$ProgressPreference=\"SilentlyContinue\"; " + command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LASTEXITCODE")), }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 87f6680ad03..2719a962142 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { + t.Fatalf("Default command should be powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell powershell '& {$ProgressPreference=\"SilentlyContinue\"; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { + t.Fatalf("Default command should be powershell powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -389,7 +389,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -408,7 +408,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -435,7 +435,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -468,7 +468,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` // Should run the command without alteration if comm.StartCmd.Command != expectedCommand { @@ -582,7 +582,7 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& {$ProgressPreference=\"SilentlyContinue\"; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { + if cmd != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { t.Fatalf("Got unexpected non-elevated command: %s", cmd) } From 7452e2fa7a2d28018f8b09f5ccff288f75c047bd Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 30 Jun 2016 06:45:19 +0100 Subject: [PATCH 084/113] Dix duplicate imports --- command/plugin.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/command/plugin.go b/command/plugin.go index f21e1a9c601..9aab127564d 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -21,13 +21,6 @@ import ( dockerbuilder "github.com/mitchellh/packer/builder/docker" filebuilder "github.com/mitchellh/packer/builder/file" googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" - dockerimportpostprocessor "github.com/mitchellh/packer/post-processor/docker-import" - dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push" - dockersavepostprocessor "github.com/mitchellh/packer/post-processor/docker-save" - dockertagpostprocessor "github.com/mitchellh/packer/post-processor/docker-tag" - filebuilder "github.com/mitchellh/packer/builder/file" - fileprovisioner "github.com/mitchellh/packer/provisioner/file" - googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" hypervbuilder "github.com/mitchellh/packer/builder/hyperv/iso" nullbuilder "github.com/mitchellh/packer/builder/null" openstackbuilder "github.com/mitchellh/packer/builder/openstack" @@ -38,6 +31,8 @@ import ( virtualboxovfbuilder "github.com/mitchellh/packer/builder/virtualbox/ovf" vmwareisobuilder "github.com/mitchellh/packer/builder/vmware/iso" vmwarevmxbuilder "github.com/mitchellh/packer/builder/vmware/vmx" + fileprovisioner "github.com/mitchellh/packer/provisioner/file" + amazonimportpostprocessor "github.com/mitchellh/packer/post-processor/amazon-import" artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice" atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas" @@ -51,6 +46,7 @@ import ( vagrantpostprocessor "github.com/mitchellh/packer/post-processor/vagrant" vagrantcloudpostprocessor "github.com/mitchellh/packer/post-processor/vagrant-cloud" vspherepostprocessor "github.com/mitchellh/packer/post-processor/vsphere" + ansibleprovisioner "github.com/mitchellh/packer/provisioner/ansible" ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local" chefclientprovisioner "github.com/mitchellh/packer/provisioner/chef-client" From 560fdb51c914191a3629cba27490e60c075a8104 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 30 Jun 2016 06:48:37 +0100 Subject: [PATCH 085/113] Remove another duplicate import --- command/plugin.go | 1 - 1 file changed, 1 deletion(-) diff --git a/command/plugin.go b/command/plugin.go index 9aab127564d..04e5bfce97a 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -31,7 +31,6 @@ import ( virtualboxovfbuilder "github.com/mitchellh/packer/builder/virtualbox/ovf" vmwareisobuilder "github.com/mitchellh/packer/builder/vmware/iso" vmwarevmxbuilder "github.com/mitchellh/packer/builder/vmware/vmx" - fileprovisioner "github.com/mitchellh/packer/provisioner/file" amazonimportpostprocessor "github.com/mitchellh/packer/post-processor/amazon-import" artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice" From 58db0a710174c1cf227b5a88d9a898b34196f585 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 30 Jun 2016 07:14:42 +0100 Subject: [PATCH 086/113] Skip these tests on platforms that don't have powershell installed --- powershell/powershell_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 2dcc360853a..43943cb6956 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -7,6 +7,13 @@ import ( func TestOutput(t *testing.T) { var ps PowerShellCmd + + powerShellPath, err := ps.getPowerShellPath() + if err != nil { + t.Skipf("powershell not installed: %s", err) + return + } + cmdOut, err := ps.Output("") if err != nil { t.Fatalf("should not have error: %s", err) @@ -40,6 +47,13 @@ func TestRunFile(t *testing.T) { blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $n = $x + $y; Write-Output "$a $b $n";`) var ps PowerShellCmd + + powerShellPath, err := ps.getPowerShellPath() + if err != nil { + t.Skipf("powershell not installed: %s", err) + return + } + cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") if err != nil { From 50bab2ae33c20acd9f2ea4120520603ee05e10d8 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 30 Jun 2016 20:21:57 +0100 Subject: [PATCH 087/113] Don't log fatal message in tests if powershell is not available --- powershell/powershell.go | 62 ++++++++++++++++++++--------------- powershell/powershell_test.go | 27 ++++++++------- 2 files changed, 51 insertions(+), 38 deletions(-) diff --git a/powershell/powershell.go b/powershell/powershell.go index 28c3d908b68..9afd5c0ea93 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -5,20 +5,20 @@ package powershell import ( + "bytes" "fmt" - "log" "io" + "io/ioutil" + "log" "os" "os/exec" - "strings" - "bytes" - "io/ioutil" "strconv" + "strings" ) const ( powerShellFalse = "False" - powerShellTrue = "True" + powerShellTrue = "True" ) type PowerShellCmd struct { @@ -31,14 +31,14 @@ func (ps *PowerShellCmd) Run(fileContents string, params ...string) error { return err } -// Output runs the PowerShell command and returns its standard output. +// Output runs the PowerShell command and returns its standard output. func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, error) { - path, err := ps.getPowerShellPath(); + path, err := ps.getPowerShellPath() if err != nil { - return "", nil + return "", err } - filename, err := saveScript(fileContents); + filename, err := saveScript(fileContents) if err != nil { return "", err } @@ -49,7 +49,7 @@ func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, if !debug { defer os.Remove(filename) } - + args := createArgs(filename, params...) if verbose { @@ -93,13 +93,23 @@ func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, log.Printf("stderr: %s", stderrString) } - return stdoutString, err; + return stdoutString, err } -func (ps *PowerShellCmd) getPowerShellPath() (string, error) { +func (ps *PowerShellCmd) isPowershellAvaiable() (bool, string, error) { path, err := exec.LookPath("powershell") if err != nil { - log.Fatal("Cannot find PowerShell in the path", err) + return false, "", err + } else { + return false, path, err + } +} + +func (ps *PowerShellCmd) getPowerShellPath() (string, error) { + powershellAvailable, path, err := ps.isPowershellAvaiable() + + if !powershellAvailable { + log.Fatal("Cannot find PowerShell in the path") return "", err } @@ -111,7 +121,7 @@ func saveScript(fileContents string) (string, error) { if err != nil { return "", err } - + _, err = file.Write([]byte(fileContents)) if err != nil { return "", err @@ -132,7 +142,7 @@ func saveScript(fileContents string) (string, error) { } func createArgs(filename string, params ...string) []string { - args := make([]string,len(params)+4) + args := make([]string, len(params)+4) args[0] = "-ExecutionPolicy" args[1] = "Bypass" @@ -141,9 +151,9 @@ func createArgs(filename string, params ...string) []string { for key, value := range params { args[key+4] = value - } + } - return args; + return args } func GetHostAvailableMemory() float64 { @@ -158,7 +168,6 @@ func GetHostAvailableMemory() float64 { return freeMB } - func GetHostName(ip string) (string, error) { var script = ` @@ -174,7 +183,7 @@ try { // var ps PowerShellCmd - cmdOut, err := ps.Output(script, ip); + cmdOut, err := ps.Output(script, ip) if err != nil { return "", err } @@ -190,8 +199,8 @@ $administratorRole = [System.Security.Principal.WindowsBuiltInRole]::Administrat return $principal.IsInRole($administratorRole) ` - var ps PowerShellCmd - cmdOut, err := ps.Output(script); + var ps PowerShellCmd + cmdOut, err := ps.Output(script) if err != nil { return false, err } @@ -200,14 +209,13 @@ return $principal.IsInRole($administratorRole) return res == powerShellTrue, nil } - func ModuleExists(moduleName string) (bool, error) { var script = ` param([string]$moduleName) (Get-Module -Name $moduleName) -ne $null ` - var ps PowerShellCmd + var ps PowerShellCmd cmdOut, err := ps.Output(script) if err != nil { return false, err @@ -215,7 +223,7 @@ param([string]$moduleName) res := strings.TrimSpace(string(cmdOut)) - if(res == powerShellFalse){ + if res == powerShellFalse { err := fmt.Errorf("PowerShell %s module is not loaded. Make sure %s feature is on.", moduleName, moduleName) return false, err } @@ -249,7 +257,7 @@ $productKeyNode.InnerText = $productKey $unattend.Save($path) ` - var ps PowerShellCmd - err := ps.Run(script, path, productKey) - return err + var ps PowerShellCmd + err := ps.Run(script, path, productKey) + return err } diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index 43943cb6956..cafa23e497f 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -6,14 +6,18 @@ import ( ) func TestOutput(t *testing.T) { + var ps PowerShellCmd - powerShellPath, err := ps.getPowerShellPath() - if err != nil { - t.Skipf("powershell not installed: %s", err) + powershellAvailable, _, _ := ps.isPowershellAvaiable() + + if !powershellAvailable { + t.Skipf("powershell not installed") return } + return + cmdOut, err := ps.Output("") if err != nil { t.Fatalf("should not have error: %s", err) @@ -43,17 +47,18 @@ func TestOutput(t *testing.T) { } func TestRunFile(t *testing.T) { - var blockBuffer bytes.Buffer - blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $n = $x + $y; Write-Output "$a $b $n";`) - var ps PowerShellCmd - - powerShellPath, err := ps.getPowerShellPath() - if err != nil { - t.Skipf("powershell not installed: %s", err) + + powershellAvailable, _, _ := ps.isPowershellAvaiable() + + if !powershellAvailable { + t.Skipf("powershell not installed") return } - + + var blockBuffer bytes.Buffer + blockBuffer.WriteString(`param([string]$a, [string]$b, [int]$x, [int]$y) if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $n = $x + $y; Write-Output "$a $b $n";`) + cmdOut, err := ps.Output(blockBuffer.String(), "a", "b", "5", "10") if err != nil { From 57438f564f046a45dc31fe4b4590b21b61709d4e Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Fri, 1 Jul 2016 19:31:22 +0100 Subject: [PATCH 088/113] Test should run and pass even when run on an environment that does not have Powershel. --- builder/hyperv/iso/builder.go | 41 ++++++++++++++++++++---------- builder/hyperv/iso/builder_test.go | 4 ++- powershell/powershell.go | 4 +-- powershell/powershell_test.go | 4 +-- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 849e7d90aae..03c1e3f3f17 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -7,6 +7,11 @@ package iso import ( "errors" "fmt" + "log" + "os" + "strings" + "time" + "github.com/mitchellh/multistep" hypervcommon "github.com/mitchellh/packer/builder/hyperv/common" "github.com/mitchellh/packer/common" @@ -16,10 +21,6 @@ import ( powershell "github.com/mitchellh/packer/powershell" "github.com/mitchellh/packer/powershell/hyperv" "github.com/mitchellh/packer/template/interpolate" - "log" - "os" - "strings" - "time" ) const ( @@ -148,13 +149,7 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { log.Println(fmt.Sprintf("%s: %v", "VMName", b.config.VMName)) if b.config.SwitchName == "" { - // no switch name, try to get one attached to a online network adapter - onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() - if onlineSwitchName == "" || err != nil { - b.config.SwitchName = fmt.Sprintf("packer-%s", b.config.PackerBuildName) - } else { - b.config.SwitchName = onlineSwitchName - } + b.config.SwitchName = b.detectSwitchName() } if b.config.Cpu < 1 { @@ -455,11 +450,29 @@ func (b *Builder) checkRamSize() error { } func (b *Builder) checkHostAvailableMemory() string { - freeMB := powershell.GetHostAvailableMemory() + powershellAvailable, _, _ := powershell.IsPowershellAvailable() + + if powershellAvailable { + freeMB := powershell.GetHostAvailableMemory() - if (freeMB - float64(b.config.RamSizeMB)) < LowRam { - return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") + if (freeMB - float64(b.config.RamSizeMB)) < LowRam { + return fmt.Sprintf("Hyper-V might fail to create a VM if there is not enough free memory in the system.") + } } return "" } + +func (b *Builder) detectSwitchName() string { + powershellAvailable, _, _ := powershell.IsPowershellAvailable() + + if powershellAvailable { + // no switch name, try to get one attached to a online network adapter + onlineSwitchName, err := hyperv.GetExternalOnlineVirtualSwitch() + if onlineSwitchName != "" && err == nil { + return onlineSwitchName + } + } + + return fmt.Sprintf("packer-%s", b.config.PackerBuildName) +} diff --git a/builder/hyperv/iso/builder_test.go b/builder/hyperv/iso/builder_test.go index 5cc04163e93..4d8f193a036 100644 --- a/builder/hyperv/iso/builder_test.go +++ b/builder/hyperv/iso/builder_test.go @@ -1,9 +1,10 @@ package iso import ( - "github.com/mitchellh/packer/packer" "reflect" "testing" + + "github.com/mitchellh/packer/packer" ) func testConfig() map[string]interface{} { @@ -31,6 +32,7 @@ func TestBuilder_ImplementsBuilder(t *testing.T) { func TestBuilderPrepare_Defaults(t *testing.T) { var b Builder config := testConfig() + warns, err := b.Prepare(config) if len(warns) > 0 { t.Fatalf("bad: %#v", warns) diff --git a/powershell/powershell.go b/powershell/powershell.go index 9afd5c0ea93..051f03a670e 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -96,7 +96,7 @@ func (ps *PowerShellCmd) Output(fileContents string, params ...string) (string, return stdoutString, err } -func (ps *PowerShellCmd) isPowershellAvaiable() (bool, string, error) { +func IsPowershellAvailable() (bool, string, error) { path, err := exec.LookPath("powershell") if err != nil { return false, "", err @@ -106,7 +106,7 @@ func (ps *PowerShellCmd) isPowershellAvaiable() (bool, string, error) { } func (ps *PowerShellCmd) getPowerShellPath() (string, error) { - powershellAvailable, path, err := ps.isPowershellAvaiable() + powershellAvailable, path, err := IsPowershellAvailable() if !powershellAvailable { log.Fatal("Cannot find PowerShell in the path") diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index cafa23e497f..a8fe3724cff 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -9,7 +9,7 @@ func TestOutput(t *testing.T) { var ps PowerShellCmd - powershellAvailable, _, _ := ps.isPowershellAvaiable() + powershellAvailable, _, _ := IsPowershellAvailable() if !powershellAvailable { t.Skipf("powershell not installed") @@ -49,7 +49,7 @@ func TestOutput(t *testing.T) { func TestRunFile(t *testing.T) { var ps PowerShellCmd - powershellAvailable, _, _ := ps.isPowershellAvaiable() + powershellAvailable, _, _ := IsPowershellAvailable() if !powershellAvailable { t.Skipf("powershell not installed") From 1ff9ea513f00ba54b7bf3f5503c7adca71fce4fd Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 4 Jul 2016 20:03:52 +0100 Subject: [PATCH 089/113] accidentally checked in code that was used for debugging --- powershell/powershell_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/powershell/powershell_test.go b/powershell/powershell_test.go index a8fe3724cff..91c9a83f83c 100644 --- a/powershell/powershell_test.go +++ b/powershell/powershell_test.go @@ -16,8 +16,6 @@ func TestOutput(t *testing.T) { return } - return - cmdOut, err := ps.Output("") if err != nil { t.Fatalf("should not have error: %s", err) From 3241248deb6520278ee5131fb204bb643a073482 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Mon, 4 Jul 2016 23:44:33 +0100 Subject: [PATCH 090/113] Base64 encode powershell to avoid any necessary escaping --- powershell/powershell.go | 2 +- provisioner/powershell/powershell.go | 6 ++ provisioner/powershell/provisioner.go | 38 +++++++---- provisioner/powershell/provisioner_test.go | 77 ++++++++++++++-------- 4 files changed, 83 insertions(+), 40 deletions(-) diff --git a/powershell/powershell.go b/powershell/powershell.go index 051f03a670e..044ec638487 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -101,7 +101,7 @@ func IsPowershellAvailable() (bool, string, error) { if err != nil { return false, "", err } else { - return false, path, err + return true, path, err } } diff --git a/provisioner/powershell/powershell.go b/provisioner/powershell/powershell.go index 1f5a7ffad64..5f339d116be 100644 --- a/provisioner/powershell/powershell.go +++ b/provisioner/powershell/powershell.go @@ -15,3 +15,9 @@ func powershellEncode(buffer []byte) string { input := []uint8(wideCmd) return base64.StdEncoding.EncodeToString(input) } + +func powershellDecode(message string) (retour string) { + base64Text := make([]byte, base64.StdEncoding.DecodedLen(len(message))) + base64.StdEncoding.Decode(base64Text, []byte(message)) + return string(base64Text) +} diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index 4d8a98e5a13..ad7f71b0f06 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -107,24 +107,25 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { }, }, }, raws...) + if err != nil { return err } if p.config.EnvVarFormat == "" { - p.config.EnvVarFormat = `$env:%s=\"%s\"; ` + p.config.EnvVarFormat = `$env:%s="%s"; ` } if p.config.ElevatedEnvVarFormat == "" { - p.config.ElevatedEnvVarFormat = `$env:%s=\"%s\"; ` + p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ExecuteCommand = `{{.Vars}}{{.Path}}` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` + p.config.ElevatedExecuteCommand = `{{.Vars}}{{.Path}}'` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -374,28 +375,41 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string, e } func (p *Provisioner) createCommandText() (command string, err error) { + // Return the interpolated command + if p.config.ElevatedUser == "" { + return p.createCommandTextNonPrivileged() + } else { + return p.createCommandTextPrivileged() + } +} + +func (p *Provisioner) createCommandTextNonPrivileged() (command string, err error) { // Create environment variables to set before executing the command flattenedEnvVars, err := p.createFlattenedEnvVars(false) if err != nil { return "", err } - p.config.ctx.Data = &ExecuteCommandTemplate{ Vars: flattenedEnvVars, Path: p.config.RemotePath, } command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) + if err != nil { return "", fmt.Errorf("Error processing command: %s", err) } - // Return the interpolated command - if p.config.ElevatedUser == "" { - return command, nil - } + encodedCommand := "powershell -executionpolicy bypass -encodedCommand " + powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; "+command+"; exit $LastExitCode")) + + return encodedCommand, err +} +func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { // Can't double escape the env vars, lets create shiny new ones - flattenedEnvVars, err = p.createFlattenedEnvVars(true) + flattenedEnvVars, err := p.createFlattenedEnvVars(true) + if err != nil { + return "", err + } p.config.ctx.Data = &ExecuteCommandTemplate{ Vars: flattenedEnvVars, Path: p.config.RemotePath, @@ -412,7 +426,7 @@ func (p *Provisioner) createCommandText() (command string, err error) { // Return the path to the elevated shell wrapper command = fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path) - return + return command, err } func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) { @@ -425,7 +439,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LASTEXITCODE")), + EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode")), }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index 2719a962142..cb4c26eb32b 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `{{.Vars}}{{.Path}}` { + t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}'` { - t.Fatalf("Default command should be powershell powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; {{.Vars}}{{.Path}}; exit $LastExitCode}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `{{.Vars}}{{.Path}}'` { + t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -95,8 +95,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { } } - if p.config.ElevatedEnvVarFormat != `$env:%s=\"%s\"; ` { - t.Fatalf(`Default command should be powershell '$env:%%s=\"%%s\"; ', but got %s`, p.config.ElevatedEnvVarFormat) + if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` { + t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat) } } @@ -389,11 +389,15 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } envVars := make([]string, 2) @@ -408,11 +412,15 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode}'` + expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded = expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be: %s, got: %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -434,12 +442,15 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error") } - //powershell -Command "$env:PACKER_BUILDER_TYPE=''"; powershell -Command "$env:PACKER_BUILD_NAME='foobuild'"; powershell -Command c:/Windows/Temp/script.ps1 - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -468,11 +479,15 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:BAR=\"BAZ\"; $env:FOO=\"BAR\"; $env:PACKER_BUILDER_TYPE=\"footype\"; $env:PACKER_BUILD_NAME=\"foobuild\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) // Should run the command without alteration - if comm.StartCmd.Command != expectedCommand { - t.Fatalf("Expect command to be %s NOT %s", expectedCommand, comm.StartCmd.Command) + if comm.StartCmd.Command != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } } @@ -500,7 +515,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -511,7 +526,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -522,7 +537,7 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -545,7 +560,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -556,7 +571,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } @@ -567,7 +582,7 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) { if err != nil { t.Fatalf("should not have error creating flattened env vars: %s", err) } - if flattenedEnvVars != `$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ` { + if flattenedEnvVars != `$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; ` { t.Fatalf("unexpected flattened env vars: %s", flattenedEnvVars) } } @@ -582,8 +597,16 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - if cmd != `powershell '& {if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; c:/Windows/Temp/script.ps1; exit $LastExitCode}'` { - t.Fatalf("Got unexpected non-elevated command: %s", cmd) + + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` + expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + + // Should run the command without alteration + if cmd != expectedCommandEncoded { + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) + t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) } // Elevated From 6cb5bb564e79e1f5862e085d38a2cca4980da982 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Wed, 13 Jul 2016 00:28:14 +0100 Subject: [PATCH 091/113] Encode powershell using utf8 Fix a bug in the size of string that was returned when decoding a base64 string Added tests around encoding and decoding powershell scripts. Used [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes('powershell commands')) | clip to generate what base 64 strings should look like --- provisioner/powershell/powershell.go | 33 ++++--- provisioner/powershell/provisioner.go | 31 ++++++- provisioner/powershell/provisioner_test.go | 102 +++++++++++++++++---- 3 files changed, 132 insertions(+), 34 deletions(-) diff --git a/provisioner/powershell/powershell.go b/provisioner/powershell/powershell.go index 5f339d116be..c24e7df5cf8 100644 --- a/provisioner/powershell/powershell.go +++ b/provisioner/powershell/powershell.go @@ -2,22 +2,33 @@ package powershell import ( "encoding/base64" + + "golang.org/x/text/encoding/unicode" ) -func powershellEncode(buffer []byte) string { - // 2 byte chars to make PowerShell happy - wideCmd := "" - for _, b := range buffer { - wideCmd += string(b) + "\x00" +func powershellUtf8(message string) (string, error) { + utf8 := unicode.UTF8 + utfEncoder := utf8.NewEncoder() + utf8EncodedMessage, err := utfEncoder.String(message) + + return utf8EncodedMessage, err +} + +func powershellEncode(message string) (string, error) { + utf8EncodedMessage, err := powershellUtf8(message) + if err != nil { + return "", err } // Base64 encode the command - input := []uint8(wideCmd) - return base64.StdEncoding.EncodeToString(input) + input := []uint8(utf8EncodedMessage) + return base64.StdEncoding.EncodeToString(input), nil } -func powershellDecode(message string) (retour string) { - base64Text := make([]byte, base64.StdEncoding.DecodedLen(len(message))) - base64.StdEncoding.Decode(base64Text, []byte(message)) - return string(base64Text) +func powershellDecode(message string) (retour string, err error) { + data, err := base64.StdEncoding.DecodeString(message) + if err != nil { + return "", err + } + return string(data), nil } diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index ad7f71b0f06..be887e8bc35 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -399,9 +399,25 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return "", fmt.Errorf("Error processing command: %s", err) } - encodedCommand := "powershell -executionpolicy bypass -encodedCommand " + powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; "+command+"; exit $LastExitCode")) + commandText, err := p.generateCommandLineRunner(command) + if err != nil { + return "", fmt.Errorf("Error generating command line runner: %s", err) + } - return encodedCommand, err + return commandText, err +} + +func (p *Provisioner) generateCommandLineRunner(command string) (commandText string, err error) { + log.Printf("Building command line for: %s", command) + + base64EncodedCommand, err := powershellEncode("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode") + if err != nil { + return "", fmt.Errorf("Error encoding command: %s", err) + } + + commandText = "powershell -executionpolicy bypass -encodedCommand " + base64EncodedCommand + + return commandText, nil } func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { @@ -422,6 +438,9 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error) // OK so we need an elevated shell runner to wrap our command, this is going to have its own path // generate the script and update the command runner in the process path, err := p.generateElevatedRunner(command) + if err != nil { + return "", fmt.Errorf("Error generating elevated runner: %s", err) + } // Return the path to the elevated shell wrapper command = fmt.Sprintf("powershell -executionpolicy bypass -file \"%s\"", path) @@ -434,12 +453,18 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin // generate command var buffer bytes.Buffer + + base64EncodedCommand, err := powershellEncode("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode") + if err != nil { + return "", fmt.Errorf("Error encoding command: %s", err) + } + err = elevatedTemplate.Execute(&buffer, elevatedOptions{ User: p.config.ElevatedUser, Password: p.config.ElevatedPassword, TaskDescription: "Packer elevated task", TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), - EncodedCommand: powershellEncode([]byte("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode")), + EncodedCommand: base64EncodedCommand, }) if err != nil { diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index cb4c26eb32b..f1a51e049a6 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -390,14 +390,27 @@ func TestProvisionerProvision_Inline(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandUtf8, err := powershellUtf8(expectedCommand) + if err != nil { + t.Fatal("should not have error when Utf 8 encoding") + } + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iaXNvIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0idm13YXJlIjsgYzovV2luZG93cy9UZW1wL2lubGluZVNjcmlwdC5iYXQ7IGV4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if comm.StartCmd.Command != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + } + + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded:%s, %s, got %s", expectedCommandEncoded, len(expectedCommandUtf8), len(actualCommandDecoded)) } envVars := make([]string, 2) @@ -413,14 +426,27 @@ func TestProvisionerProvision_Inline(t *testing.T) { } expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommandUtf8, err = powershellUtf8(expectedCommand) + if err != nil { + t.Fatal("should not have error when Utf 8 encoding") + } + expectedCommandBase64Encoded = `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6QkFSPSJCQVoiOyAkZW52OkZPTz0iQkFSIjsgJGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJpc28iOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJ2bXdhcmUiOyBjOi9XaW5kb3dzL1RlbXAvaW5saW5lU2NyaXB0LmJhdDsgZXhpdCAkTGFzdEV4aXRDb2Rl` expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded = expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded = expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix = strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err = powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if comm.StartCmd.Command != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + } + + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } } @@ -443,14 +469,27 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandUtf8, err := powershellUtf8(expectedCommand) + if err != nil { + t.Fatal("should not have error when Utf 8 encoding") + } + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iZm9vdHlwZSI7ICRlbnY6UEFDS0VSX0JVSUxEX05BTUU9ImZvb2J1aWxkIjsgYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczE7IGV4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if comm.StartCmd.Command != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + } + + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded: %n, got %n", len(expectedCommandUtf8), len(actualCommandDecoded)) } } @@ -480,14 +519,27 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandUtf8, err := powershellUtf8(expectedCommand) + if err != nil { + t.Fatal("should not have error when Utf 8 encoding") + } + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6QkFSPSJCQVoiOyAkZW52OkZPTz0iQkFSIjsgJGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJmb290eXBlIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iZm9vYnVpbGQiOyBjOi9XaW5kb3dzL1RlbXAvc2NyaXB0LnBzMTsgZXhpdCAkTGFzdEV4aXRDb2Rl` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if comm.StartCmd.Command != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + } + + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } } @@ -599,14 +651,24 @@ func TestProvision_createCommandText(t *testing.T) { cmd, _ := p.createCommandText() expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iIjsgYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczE7IGV4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` - expectedCommandEncoded := expectedCommandPrefix + powershellEncode([]byte(expectedCommand)) + expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded + + actualCommandWithoutPrefix := strings.Replace(cmd, expectedCommandPrefix, "", -1) + + actualCommandDecoded, err := powershellDecode(actualCommandWithoutPrefix) + if err != nil { + t.Fatal("should not have error when base64 decoding") + } // Should run the command without alteration if cmd != expectedCommandEncoded { - actualCommandWithoutPrefix := strings.Replace(comm.StartCmd.Command, expectedCommandPrefix, "", -1) - actualCommandDecoded := powershellDecode(actualCommandWithoutPrefix) - t.Fatalf("Expect command to be: %s, got %s. Expected decoded: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command, expectedCommand, actualCommandDecoded) + t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, cmd) + } + + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } // Elevated From 7fd23d26832fe71542d8643a36b2ce16dfd585a2 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 14 Jul 2016 00:03:07 +0100 Subject: [PATCH 092/113] Pattern for infrastructure changed to quote filename and execute file with ampersand as everything is run in powershell now Handle powershell commands by specifying any extra infrastructure around running scripts inside of ExecuteCommand and ElevatedExecuteCommand --- provisioner/powershell/provisioner.go | 8 +-- provisioner/powershell/provisioner_test.go | 81 ++++++++++------------ 2 files changed, 42 insertions(+), 47 deletions(-) diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index be887e8bc35..ba0e2def3fd 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -121,11 +121,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `{{.Vars}}{{.Path}}` + p.config.ExecuteCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode` } if p.config.ElevatedExecuteCommand == "" { - p.config.ElevatedExecuteCommand = `{{.Vars}}{{.Path}}'` + p.config.ElevatedExecuteCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -410,7 +410,7 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro func (p *Provisioner) generateCommandLineRunner(command string) (commandText string, err error) { log.Printf("Building command line for: %s", command) - base64EncodedCommand, err := powershellEncode("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode") + base64EncodedCommand, err := powershellEncode(command) if err != nil { return "", fmt.Errorf("Error encoding command: %s", err) } @@ -454,7 +454,7 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin // generate command var buffer bytes.Buffer - base64EncodedCommand, err := powershellEncode("if (Test-Path variable:global:ProgressPreference){$ProgressPreference=\"SilentlyContinue\"}; " + command + "; exit $LastExitCode") + base64EncodedCommand, err := powershellEncode(command) if err != nil { return "", fmt.Errorf("Error encoding command: %s", err) } diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index f1a51e049a6..f6e20ecc2dd 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -75,12 +75,12 @@ func TestProvisionerPrepare_Defaults(t *testing.T) { t.Error("expected elevated_password to be empty") } - if p.config.ExecuteCommand != `{{.Vars}}{{.Path}}` { - t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ExecuteCommand) + if p.config.ExecuteCommand != `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode` { + t.Fatalf(`Default command should be "if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode", but got %s`, p.config.ExecuteCommand) } - if p.config.ElevatedExecuteCommand != `{{.Vars}}{{.Path}}'` { - t.Fatalf("Default command should be '{{.Vars}}{{.Path}}', but got %s", p.config.ElevatedExecuteCommand) + if p.config.ElevatedExecuteCommand != `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode` { + t.Fatalf(`Default command should be "if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode", but got %s`, p.config.ElevatedExecuteCommand) } if p.config.ValidExitCodes == nil { @@ -328,7 +328,7 @@ func TestProvisionerProvision_ValidExitCodes(t *testing.T) { delete(config, "inline") // Defaults provided by Packer - config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" + config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" config["inline"] = []string{"whoami"} ui := testUi() p := new(Provisioner) @@ -351,7 +351,7 @@ func TestProvisionerProvision_InvalidExitCodes(t *testing.T) { delete(config, "inline") // Defaults provided by Packer - config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" + config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" config["inline"] = []string{"whoami"} ui := testUi() p := new(Provisioner) @@ -374,7 +374,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { delete(config, "inline") // Defaults provided by Packer - config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" + config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" config["inline"] = []string{"whoami"} ui := testUi() p := new(Provisioner) @@ -389,12 +389,12 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; &'c:/Windows/Temp/inlineScript.ps1';exit $LastExitCode` expectedCommandUtf8, err := powershellUtf8(expectedCommand) if err != nil { t.Fatal("should not have error when Utf 8 encoding") } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iaXNvIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0idm13YXJlIjsgYzovV2luZG93cy9UZW1wL2lubGluZVNjcmlwdC5iYXQ7IGV4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJpc28iOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJ2bXdhcmUiOyAmJ2M6L1dpbmRvd3MvVGVtcC9pbmxpbmVTY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -404,20 +404,19 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded:%s, %s, got %s", expectedCommandEncoded, expectedCommandUtf8, actualCommandDecoded) } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded:%s, %s, got %s", expectedCommandEncoded, len(expectedCommandUtf8), len(actualCommandDecoded)) + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) } envVars := make([]string, 2) envVars[0] = "FOO=BAR" envVars[1] = "BAR=BAZ" config["environment_vars"] = envVars - config["remote_path"] = "c:/Windows/Temp/inlineScript.bat" + config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1" p.Prepare(config) err = p.Provision(ui, comm) @@ -425,12 +424,12 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error") } - expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; c:/Windows/Temp/inlineScript.bat; exit $LastExitCode` + expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; &'c:/Windows/Temp/inlineScript.ps1';exit $LastExitCode` expectedCommandUtf8, err = powershellUtf8(expectedCommand) if err != nil { t.Fatal("should not have error when Utf 8 encoding") } - expectedCommandBase64Encoded = `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6QkFSPSJCQVoiOyAkZW52OkZPTz0iQkFSIjsgJGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJpc28iOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJ2bXdhcmUiOyBjOi9XaW5kb3dzL1RlbXAvaW5saW5lU2NyaXB0LmJhdDsgZXhpdCAkTGFzdEV4aXRDb2Rl` + expectedCommandBase64Encoded = `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpCQVI9IkJBWiI7ICRlbnY6Rk9PPSJCQVIiOyAkZW52OlBBQ0tFUl9CVUlMREVSX1RZUEU9ImlzbyI7ICRlbnY6UEFDS0VSX0JVSUxEX05BTUU9InZtd2FyZSI7ICYnYzovV2luZG93cy9UZW1wL2lubGluZVNjcmlwdC5wczEnO2V4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded = expectedCommandPrefix + expectedCommandBase64Encoded @@ -440,14 +439,13 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) - } - if actualCommandDecoded != expectedCommandUtf8 { t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } + + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) + } } func TestProvisionerProvision_Scripts(t *testing.T) { @@ -468,12 +466,12 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` expectedCommandUtf8, err := powershellUtf8(expectedCommand) if err != nil { t.Fatal("should not have error when Utf 8 encoding") } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iZm9vdHlwZSI7ICRlbnY6UEFDS0VSX0JVSUxEX05BTUU9ImZvb2J1aWxkIjsgYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczE7IGV4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJmb290eXBlIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iZm9vYnVpbGQiOyAmJ2M6L1dpbmRvd3MvVGVtcC9zY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -483,13 +481,12 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) + if actualCommandDecoded != expectedCommandUtf8 { + t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded: %n, got %n", len(expectedCommandUtf8), len(actualCommandDecoded)) + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) } } @@ -518,12 +515,12 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error") } - expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; c:/Windows/Temp/script.ps1; exit $LastExitCode` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` expectedCommandUtf8, err := powershellUtf8(expectedCommand) if err != nil { t.Fatal("should not have error when Utf 8 encoding") } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6QkFSPSJCQVoiOyAkZW52OkZPTz0iQkFSIjsgJGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJmb290eXBlIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iZm9vYnVpbGQiOyBjOi9XaW5kb3dzL1RlbXAvc2NyaXB0LnBzMTsgZXhpdCAkTGFzdEV4aXRDb2Rl` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpCQVI9IkJBWiI7ICRlbnY6Rk9PPSJCQVIiOyAkZW52OlBBQ0tFUl9CVUlMREVSX1RZUEU9ImZvb3R5cGUiOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJmb29idWlsZCI7ICYnYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczEnO2V4aXQgJExhc3RFeGl0Q29kZQ==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -533,14 +530,13 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if comm.StartCmd.Command != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, comm.StartCmd.Command) - } - if actualCommandDecoded != expectedCommandUtf8 { t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) } + + if comm.StartCmd.Command != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, comm.StartCmd.Command) + } } func TestProvisionerProvision_UISlurp(t *testing.T) { @@ -650,8 +646,8 @@ func TestProvision_createCommandText(t *testing.T) { // Non-elevated cmd, _ := p.createCommandText() - expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference="SilentlyContinue"}; $env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; c:/Windows/Temp/script.ps1; exit $LastExitCode` - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSJTaWxlbnRseUNvbnRpbnVlIn07ICRlbnY6UEFDS0VSX0JVSUxERVJfVFlQRT0iIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iIjsgYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczE7IGV4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` + expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSIiOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSIiOyAmJ2M6L1dpbmRvd3MvVGVtcC9zY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -662,15 +658,14 @@ func TestProvision_createCommandText(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - // Should run the command without alteration - if cmd != expectedCommandEncoded { - t.Fatalf("Expect command to be: %s, got %s.", expectedCommandEncoded, cmd) - } - if actualCommandDecoded != expectedCommand { t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } + if cmd != expectedCommandEncoded { + t.Fatalf("Expect command to be: %s, got %s", expectedCommandEncoded, cmd) + } + // Elevated p.config.ElevatedUser = "vagrant" p.config.ElevatedPassword = "vagrant" From 6faed5349847d6552507d41013531feb4c090d18 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 24 Jul 2016 14:44:07 +0100 Subject: [PATCH 093/113] Powershell uses UTF16Le for encodedCommand --- provisioner/powershell/powershell.go | 40 +++++++++++++++------ provisioner/powershell/provisioner_test.go | 42 +++++++--------------- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/provisioner/powershell/powershell.go b/provisioner/powershell/powershell.go index c24e7df5cf8..086e3e5542e 100644 --- a/provisioner/powershell/powershell.go +++ b/provisioner/powershell/powershell.go @@ -2,33 +2,53 @@ package powershell import ( "encoding/base64" + "encoding/binary" + "unicode/utf16" + "unicode/utf8" "golang.org/x/text/encoding/unicode" ) -func powershellUtf8(message string) (string, error) { - utf8 := unicode.UTF8 - utfEncoder := utf8.NewEncoder() - utf8EncodedMessage, err := utfEncoder.String(message) +func convertUtf8ToUtf16LE(message string) (string, error) { + utf16le := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) + utfEncoder := utf16le.NewEncoder() + ut16LeEncodedMessage, err := utfEncoder.String(message) - return utf8EncodedMessage, err + return ut16LeEncodedMessage, err +} + +// UTF16BytesToString converts UTF-16 encoded bytes, in big or little endian byte order, +// to a UTF-8 encoded string. +func UTF16BytesToString(b []byte, o binary.ByteOrder) string { + utf := make([]uint16, (len(b)+(2-1))/2) + for i := 0; i+(2-1) < len(b); i += 2 { + utf[i/2] = o.Uint16(b[i:]) + } + if len(b)/2 < len(utf) { + utf[len(utf)-1] = utf8.RuneError + } + return string(utf16.Decode(utf)) } func powershellEncode(message string) (string, error) { - utf8EncodedMessage, err := powershellUtf8(message) + utf16LEEncodedMessage, err := convertUtf8ToUtf16LE(message) if err != nil { return "", err } // Base64 encode the command - input := []uint8(utf8EncodedMessage) + input := []uint8(utf16LEEncodedMessage) return base64.StdEncoding.EncodeToString(input), nil } -func powershellDecode(message string) (retour string, err error) { - data, err := base64.StdEncoding.DecodeString(message) +func powershellDecode(messageBase64 string) (retour string, err error) { + messageUtf16LeByteArray, err := base64.StdEncoding.DecodeString(messageBase64) + if err != nil { return "", err } - return string(data), nil + + message := UTF16BytesToString(messageUtf16LeByteArray, binary.LittleEndian) + + return message, nil } diff --git a/provisioner/powershell/provisioner_test.go b/provisioner/powershell/provisioner_test.go index f6e20ecc2dd..473fa1d8efa 100644 --- a/provisioner/powershell/provisioner_test.go +++ b/provisioner/powershell/provisioner_test.go @@ -390,11 +390,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; &'c:/Windows/Temp/inlineScript.ps1';exit $LastExitCode` - expectedCommandUtf8, err := powershellUtf8(expectedCommand) - if err != nil { - t.Fatal("should not have error when Utf 8 encoding") - } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJpc28iOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJ2bXdhcmUiOyAmJ2M6L1dpbmRvd3MvVGVtcC9pbmxpbmVTY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiAGkAcwBvACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiAHYAbQB3AGEAcgBlACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAGkAbgBsAGkAbgBlAFMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -404,8 +400,8 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded:%s, %s, got %s", expectedCommandEncoded, expectedCommandUtf8, actualCommandDecoded) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } if comm.StartCmd.Command != expectedCommandEncoded { @@ -425,11 +421,7 @@ func TestProvisionerProvision_Inline(t *testing.T) { } expectedCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; &'c:/Windows/Temp/inlineScript.ps1';exit $LastExitCode` - expectedCommandUtf8, err = powershellUtf8(expectedCommand) - if err != nil { - t.Fatal("should not have error when Utf 8 encoding") - } - expectedCommandBase64Encoded = `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpCQVI9IkJBWiI7ICRlbnY6Rk9PPSJCQVIiOyAkZW52OlBBQ0tFUl9CVUlMREVSX1RZUEU9ImlzbyI7ICRlbnY6UEFDS0VSX0JVSUxEX05BTUU9InZtd2FyZSI7ICYnYzovV2luZG93cy9UZW1wL2lubGluZVNjcmlwdC5wczEnO2V4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommandBase64Encoded = `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AEIAQQBSAD0AIgBCAEEAWgAiADsAIAAkAGUAbgB2ADoARgBPAE8APQAiAEIAQQBSACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABFAFIAXwBUAFkAUABFAD0AIgBpAHMAbwAiADsAIAAkAGUAbgB2ADoAUABBAEMASwBFAFIAXwBCAFUASQBMAEQAXwBOAEEATQBFAD0AIgB2AG0AdwBhAHIAZQAiADsAIAAmACcAYwA6AC8AVwBpAG4AZABvAHcAcwAvAFQAZQBtAHAALwBpAG4AbABpAG4AZQBTAGMAcgBpAHAAdAAuAHAAcwAxACcAOwBlAHgAaQB0ACAAJABMAGEAcwB0AEUAeABpAHQAQwBvAGQAZQA=` expectedCommandPrefix = `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded = expectedCommandPrefix + expectedCommandBase64Encoded @@ -439,8 +431,8 @@ func TestProvisionerProvision_Inline(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } if comm.StartCmd.Command != expectedCommandEncoded { @@ -467,11 +459,7 @@ func TestProvisionerProvision_Scripts(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` - expectedCommandUtf8, err := powershellUtf8(expectedCommand) - if err != nil { - t.Fatal("should not have error when Utf 8 encoding") - } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSJmb290eXBlIjsgJGVudjpQQUNLRVJfQlVJTERfTkFNRT0iZm9vYnVpbGQiOyAmJ2M6L1dpbmRvd3MvVGVtcC9zY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiAGYAbwBvAHQAeQBwAGUAIgA7ACAAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAF8ATgBBAE0ARQA9ACIAZgBvAG8AYgB1AGkAbABkACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAHMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -481,8 +469,8 @@ func TestProvisionerProvision_Scripts(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } if comm.StartCmd.Command != expectedCommandEncoded { @@ -516,11 +504,7 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { } expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:BAR="BAZ"; $env:FOO="BAR"; $env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` - expectedCommandUtf8, err := powershellUtf8(expectedCommand) - if err != nil { - t.Fatal("should not have error when Utf 8 encoding") - } - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpCQVI9IkJBWiI7ICRlbnY6Rk9PPSJCQVIiOyAkZW52OlBBQ0tFUl9CVUlMREVSX1RZUEU9ImZvb3R5cGUiOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSJmb29idWlsZCI7ICYnYzovV2luZG93cy9UZW1wL3NjcmlwdC5wczEnO2V4aXQgJExhc3RFeGl0Q29kZQ==` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AEIAQQBSAD0AIgBCAEEAWgAiADsAIAAkAGUAbgB2ADoARgBPAE8APQAiAEIAQQBSACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABFAFIAXwBUAFkAUABFAD0AIgBmAG8AbwB0AHkAcABlACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiAGYAbwBvAGIAdQBpAGwAZAAiADsAIAAmACcAYwA6AC8AVwBpAG4AZABvAHcAcwAvAFQAZQBtAHAALwBzAGMAcgBpAHAAdAAuAHAAcwAxACcAOwBlAHgAaQB0ACAAJABMAGEAcwB0AEUAeABpAHQAQwBvAGQAZQA=` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded @@ -530,8 +514,8 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) { t.Fatal("should not have error when base64 decoding") } - if actualCommandDecoded != expectedCommandUtf8 { - t.Fatalf("Expected decoded: %s, got %s", expectedCommandUtf8, actualCommandDecoded) + if actualCommandDecoded != expectedCommand { + t.Fatalf("Expected decoded: %s, got %s", expectedCommand, actualCommandDecoded) } if comm.StartCmd.Command != expectedCommandEncoded { @@ -647,7 +631,7 @@ func TestProvision_createCommandText(t *testing.T) { cmd, _ := p.createCommandText() expectedCommand := `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE=""; $env:PACKER_BUILD_NAME=""; &'c:/Windows/Temp/script.ps1';exit $LastExitCode` - expectedCommandBase64Encoded := `aWYgKFRlc3QtUGF0aCB2YXJpYWJsZTpnbG9iYWw6UHJvZ3Jlc3NQcmVmZXJlbmNlKXskUHJvZ3Jlc3NQcmVmZXJlbmNlPSdTaWxlbnRseUNvbnRpbnVlJ307JGVudjpQQUNLRVJfQlVJTERFUl9UWVBFPSIiOyAkZW52OlBBQ0tFUl9CVUlMRF9OQU1FPSIiOyAmJ2M6L1dpbmRvd3MvVGVtcC9zY3JpcHQucHMxJztleGl0ICRMYXN0RXhpdENvZGU=` + expectedCommandBase64Encoded := `aQBmACAAKABUAGUAcwB0AC0AUABhAHQAaAAgAHYAYQByAGkAYQBiAGwAZQA6AGcAbABvAGIAYQBsADoAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAKQB7ACQAUAByAG8AZwByAGUAcwBzAFAAcgBlAGYAZQByAGUAbgBjAGUAPQAnAFMAaQBsAGUAbgB0AGwAeQBDAG8AbgB0AGkAbgB1AGUAJwB9ADsAJABlAG4AdgA6AFAAQQBDAEsARQBSAF8AQgBVAEkATABEAEUAUgBfAFQAWQBQAEUAPQAiACIAOwAgACQAZQBuAHYAOgBQAEEAQwBLAEUAUgBfAEIAVQBJAEwARABfAE4AQQBNAEUAPQAiACIAOwAgACYAJwBjADoALwBXAGkAbgBkAG8AdwBzAC8AVABlAG0AcAAvAHMAYwByAGkAcAB0AC4AcABzADEAJwA7AGUAeABpAHQAIAAkAEwAYQBzAHQARQB4AGkAdABDAG8AZABlAA==` expectedCommandPrefix := `powershell -executionpolicy bypass -encodedCommand ` expectedCommandEncoded := expectedCommandPrefix + expectedCommandBase64Encoded From 01292eb99e37f8370a5cf293a3535301da7232bf Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 24 Jul 2016 18:07:43 +0100 Subject: [PATCH 094/113] Builds in AppVeyor are failing as they do not have enough free memory. Drop the thresh hold a little more. --- builder/hyperv/iso/builder.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 03c1e3f3f17..d294c8cf571 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -32,7 +32,7 @@ const ( MinRamSize = 32 // 32MB MaxRamSize = 32 * 1024 // 32GB - LowRam = 384 // 384MB + LowRam = 256 // 256MB DefaultUsername = "vagrant" DefaultPassword = "vagrant" From 710b057e761fc975274a72738abb4ce7d2f1b742 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sat, 30 Jul 2016 18:42:10 +0100 Subject: [PATCH 095/113] Add type scan support for ctrl, shift, alt. <leftAlt> <leftCtrl> <leftShift> <rightAlt> <rightCtrl> <rightShift> Add type support for combinations of ctr, alt, shift with (only use lower case characters with this): <leftAltOn> <leftCtrlOn> <leftShiftOn> <leftAltOff> <leftCtrlOff> <leftShiftOff> <rightAltOn> <rightCtrlOn> <rightShiftOn> <rightAltOff> <rightCtrlOff> <rightShiftOff> --- .../hyperv/common/step_type_boot_command.go | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index f5cf1d909a3..8468d11154e 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -124,6 +124,12 @@ func scancodes(message string) []string { special["<end>"] = []string{"4f", "cf"} special["<pageUp>"] = []string{"49", "c9"} special["<pageDown>"] = []string{"51", "d1"} + special["<leftAlt>"] = []string{"38", "b8"} + special["<leftCtrl>"] = []string{"1d", "9d"} + special["<leftShift>"] = []string{"2a", "aa"} + special["<rightAlt>"] = []string{"e038", "e0b8"} + special["<rightCtrl>"] = []string{"e01d", "e09d"} + special["<rightShift>"] = []string{"36", "b6"} shiftedChars := "~!@#$%^&*()_+{}|:\"<>?" @@ -153,6 +159,66 @@ func scancodes(message string) []string { for len(message) > 0 { var scancode []string + if strings.HasPrefix(message, "<leftAltOn>") { + scancode = []string{"38"} + message = message[len("<leftAltOn>"):] + } + + if strings.HasPrefix(message, "<leftCtrlOn>") { + scancode = []string{"1d"} + message = message[len("<leftCtrlOn>"):] + } + + if strings.HasPrefix(message, "<leftShiftOn>") { + scancode = []string{"2a"} + message = message[len("<leftShiftOn>"):] + } + + if strings.HasPrefix(message, "<leftAltOff>") { + scancode = []string{"b8"} + message = message[len("<leftAltOff>"):] + } + + if strings.HasPrefix(message, "<leftCtrlOff>") { + scancode = []string{"9d"} + message = message[len("<leftCtrlOff>"):] + } + + if strings.HasPrefix(message, "<leftShiftOff>") { + scancode = []string{"aa"} + message = message[len("<leftShiftOff>"):] + } + + if strings.HasPrefix(message, "<rightAltOn>") { + scancode = []string{"e038"} + message = message[len("<rightAltOn>"):] + } + + if strings.HasPrefix(message, "<rightCtrlOn>") { + scancode = []string{"e01d"} + message = message[len("<rightCtrlOn>"):] + } + + if strings.HasPrefix(message, "<rightShiftOn>") { + scancode = []string{"36"} + message = message[len("<rightShiftOn>"):] + } + + if strings.HasPrefix(message, "<rightAltOff>") { + scancode = []string{"e0b8"} + message = message[len("<rightAltOff>"):] + } + + if strings.HasPrefix(message, "<rightCtrlOff>") { + scancode = []string{"e09d"} + message = message[len("<rightCtrlOff>"):] + } + + if strings.HasPrefix(message, "<rightShiftOff>") { + scancode = []string{"b6"} + message = message[len("<rightShiftOff>"):] + } + if strings.HasPrefix(message, "<wait>") { //log.Printf("Special code <wait> found, will sleep 1 second at this point.") scancode = []string{"wait"} From d7fd3bf5797971040045f5614f5e4e1457fe4abf Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 31 Jul 2016 16:23:40 +0100 Subject: [PATCH 096/113] Dvd drive creation will auto select the controller number and controller location. This fixes the bug of first generation machines not being able to add more then 1 dvd drive. --- powershell/hyperv/hyperv.go | 63 +++++++++++++------------------------ 1 file changed, 22 insertions(+), 41 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index c6f4ef68e62..94dd63d463e 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -1,9 +1,11 @@ package hyperv import ( - "github.com/mitchellh/packer/powershell" + "errors" "strconv" "strings" + + "github.com/mitchellh/packer/powershell" ) func GetHostAdapterIpAddressForSwitch(switchName string) (string, error) { @@ -54,58 +56,37 @@ $ip func CreateDvdDrive(vmName string, isoPath string, generation uint) (uint, uint, error) { var ps powershell.PowerShellCmd var script string - var controllerNumber uint - controllerNumber = 0 - if generation < 2 { - // get the controller number that the OS install disk is mounted on - // generation 1 requires dvd to be added to ide controller, generation 2 uses scsi for dvd drives - script = ` -param([string]$vmName) -$dvdDrives = @(Get-VMDvdDrive -VMName $vmName) -$lastControllerNumber = $dvdDrives | Sort-Object ControllerNumber | Select-Object -Last 1 | %{$_.ControllerNumber} -if (!$lastControllerNumber) { - $lastControllerNumber = 0 -} elseif (!$lastControllerNumber -or ($dvdDrives | ?{ $_.ControllerNumber -eq $lastControllerNumber} | measure).count -gt 1) { - $lastControllerNumber += 1 -} -$lastControllerNumber -` - cmdOut, err := ps.Output(script, vmName) - if err != nil { - return 0, 0, err - } - - controllerNumberTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64) - if err != nil { - return 0, 0, err - } - - controllerNumber = uint(controllerNumberTemp) - - if controllerNumber != 0 && controllerNumber != 1 { - //There are only 2 ide controllers, try to use the one the hdd is attached too - controllerNumber = 0 - } - } script = ` -param([string]$vmName, [string]$isoPath, [int]$controllerNumber) -$dvdController = Add-VMDvdDrive -VMName $vmName -ControllerNumber $controllerNumber -path $isoPath -Passthru +param([string]$vmName, [string]$isoPath) +$dvdController = Add-VMDvdDrive -VMName $vmName -path $isoPath -Passthru $dvdController | Set-VMDvdDrive -path $null -$dvdController.ControllerLocation +$result = "$($dvdController.ControllerNumber),$($dvdController.ControllerLocation)" +$result ` - cmdOut, err := ps.Output(script, vmName, isoPath, strconv.FormatInt(int64(controllerNumber), 10)) + cmdOut, err := ps.Output(script, vmName, isoPath) if err != nil { - return controllerNumber, 0, err + return 0, 0, err + } + + cmdOutArray := strings.Split(cmdOut, ",") + if len(cmdOutArray) != 2 { + return 0, 0, errors.New("Did not return controller number and controller location") } - controllerLocationTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOut), 10, 64) + controllerNumberTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOutArray[0]), 10, 64) if err != nil { - return controllerNumber, 0, err + return 0, 0, err } + controllerNumber := uint(controllerNumberTemp) + controllerLocationTemp, err := strconv.ParseUint(strings.TrimSpace(cmdOutArray[1]), 10, 64) + if err != nil { + return controllerNumber, 0, err + } controllerLocation := uint(controllerLocationTemp) + return controllerNumber, controllerLocation, err } From db90cf4ebff86949c4a78b11f89f8d3aab6f181c Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 31 Jul 2016 19:18:11 +0100 Subject: [PATCH 097/113] Add special key modifiers to log output when used. Update documentation to include new key modifiers. --- .../hyperv/common/step_type_boot_command.go | 12 + .../docs/builders/hyperv-iso.html.markdown | 277 ++++++++++-------- 2 files changed, 161 insertions(+), 128 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index 8468d11154e..03083b9eb23 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -162,61 +162,73 @@ func scancodes(message string) []string { if strings.HasPrefix(message, "<leftAltOn>") { scancode = []string{"38"} message = message[len("<leftAltOn>"):] + log.Printf("Special code '<leftAltOn>' found, replacing with: 38") } if strings.HasPrefix(message, "<leftCtrlOn>") { scancode = []string{"1d"} message = message[len("<leftCtrlOn>"):] + log.Printf("Special code '<leftCtrlOn>' found, replacing with: 1d") } if strings.HasPrefix(message, "<leftShiftOn>") { scancode = []string{"2a"} message = message[len("<leftShiftOn>"):] + log.Printf("Special code '<leftShiftOn>' found, replacing with: 2a") } if strings.HasPrefix(message, "<leftAltOff>") { scancode = []string{"b8"} message = message[len("<leftAltOff>"):] + log.Printf("Special code '<leftAltOff>' found, replacing with: b8") } if strings.HasPrefix(message, "<leftCtrlOff>") { scancode = []string{"9d"} message = message[len("<leftCtrlOff>"):] + log.Printf("Special code '<leftCtrlOff>' found, replacing with: 9d") } if strings.HasPrefix(message, "<leftShiftOff>") { scancode = []string{"aa"} message = message[len("<leftShiftOff>"):] + log.Printf("Special code '<leftShiftOff>' found, replacing with: aa") } if strings.HasPrefix(message, "<rightAltOn>") { scancode = []string{"e038"} message = message[len("<rightAltOn>"):] + log.Printf("Special code '<rightAltOn>' found, replacing with: e038") } if strings.HasPrefix(message, "<rightCtrlOn>") { scancode = []string{"e01d"} message = message[len("<rightCtrlOn>"):] + log.Printf("Special code '<rightCtrlOn>' found, replacing with: e01d") } if strings.HasPrefix(message, "<rightShiftOn>") { scancode = []string{"36"} message = message[len("<rightShiftOn>"):] + log.Printf("Special code '<rightShiftOn>' found, replacing with: 36") } if strings.HasPrefix(message, "<rightAltOff>") { scancode = []string{"e0b8"} message = message[len("<rightAltOff>"):] + log.Printf("Special code '<rightAltOff>' found, replacing with: e0b8") } if strings.HasPrefix(message, "<rightCtrlOff>") { scancode = []string{"e09d"} message = message[len("<rightCtrlOff>"):] + log.Printf("Special code '<rightCtrlOff>' found, replacing with: e09d") } if strings.HasPrefix(message, "<rightShiftOff>") { scancode = []string{"b6"} message = message[len("<rightShiftOff>"):] + log.Printf("Special code '<rightShiftOff>' found, replacing with: b6") } if strings.HasPrefix(message, "<wait>") { diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index ceb94613635..2d030d7c309 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -1,8 +1,8 @@ --- -layout: "docs" -page_title: "HyperV Builder (from an ISO)" description: |- The HyperV Packer builder is able to create HyperV virtual machines and export them. +layout: "docs" +page_title: "HyperV Builder (from an ISO)" --- # HyperV Builder (from an ISO) @@ -59,126 +59,126 @@ can be configured for this builder. ### Required: -* `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO - files are so large, this is required and Packer will verify it prior - to booting a virtual machine with the ISO attached. The type of the - checksum is specified with `iso_checksum_type`, documented below. +- `iso_checksum` (string) - The checksum for the OS ISO file. Because ISO + files are so large, this is required and Packer will verify it prior + to booting a virtual machine with the ISO attached. The type of the + checksum is specified with `iso_checksum_type`, documented below. -* `iso_checksum_type` (string) - The type of the checksum specified in - `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or - "sha512" currently. While "none" will skip checksumming, this is not - recommended since ISO files are generally large and corruption does happen - from time to time. +- `iso_checksum_type` (string) - The type of the checksum specified in + `iso_checksum`. Valid values are "none", "md5", "sha1", "sha256", or + "sha512" currently. While "none" will skip checksumming, this is not + recommended since ISO files are generally large and corruption does happen + from time to time. -* `iso_url` (string) - A URL to the ISO containing the installation image. - This URL can be either an HTTP URL or a file URL (or path to a file). - If this is an HTTP URL, Packer will download it and cache it between - runs. +- `iso_url` (string) - A URL to the ISO containing the installation image. + This URL can be either an HTTP URL or a file URL (or path to a file). + If this is an HTTP URL, Packer will download it and cache it between + runs. ### Optional: -* `boot_command` (array of strings) - This is an array of commands to type - when the virtual machine is first booted. The goal of these commands should - be to type just enough to initialize the operating system installer. Special - keys can be typed as well, and are covered in the section below on the boot - command. If this is not specified, it is assumed the installer will start - itself. - -* `boot_wait` (string) - The time to wait after booting the initial virtual - machine before typing the `boot_command`. The value of this should be - a duration. Examples are "5s" and "1m30s" which will cause Packer to wait - five seconds and one minute 30 seconds, respectively. If this isn't specified, - the default is 10 seconds. - -* `cpu` (int) - The number of cpus the virtual machine should use. If this isn't specified, - the default is 1 cpu. - -* `disk_size` (integer) - The size, in megabytes, of the hard disk to create - for the VM. By default, this is 40000 (about 40 GB). - -* `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. - This defaults to false. - -* `floppy_files` (array of strings) - A list of files to place onto a floppy - disk that is attached when the VM is booted. This is most useful - for unattended Windows installs, which look for an `Autounattend.xml` file - on removable media. By default, no floppy will be attached. All files - listed in this setting get placed into the root directory of the floppy - and the floppy is attached as the first floppy device. Currently, no - support exists for creating sub-directories on the floppy. Wildcard - characters (*, ?, and []) are allowed. Directory names are also allowed, - which will add all the files found in the directory to the floppy. - -* `generation` (int) - The HyperV generation for the virtual machine. By - default, this is 1. Generation 2 HyperV virtual machines do not support - floppy drives. In this scenario use secondary_iso_images instead. Hard - drives and dvd drives will also be scsi and not ide. - -* `http_directory` (string) - Path to a directory to serve using an HTTP - server. The files in this directory will be available over HTTP that will - be requestable from the virtual machine. This is useful for hosting - kickstart files and so on. By default this is "", which means no HTTP - server will be started. The address and port of the HTTP server will be - available as variables in `boot_command`. This is covered in more detail - below. - -* `http_port_min` and `http_port_max` (integer) - These are the minimum and - maximum port to use for the HTTP server started to serve the `http_directory`. - Because Packer often runs in parallel, Packer will choose a randomly available - port in this range to run the HTTP server. If you want to force the HTTP - server to be on one port, make this minimum and maximum port the same. - By default the values are 8000 and 9000, respectively. - -* `ip_address_timeout` (string) - The time to wait after creating the initial virtual - machine and waiting for an ip address before assuming there is an error in the process. - The value of this should be a duration. Examples are "5s" and "1m30s" which will cause Packer to wait - five seconds and one minute 30 seconds, respectively. If this isn't specified, - the default is 10 seconds. - -* `iso_urls` (array of strings) - Multiple URLs for the ISO to download. - Packer will try these in order. If anything goes wrong attempting to download - or while downloading a single URL, it will move on to the next. All URLs - must point to the same file (same checksum). By default this is empty - and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. - -* `output_directory` (string) - This is the path to the directory where the - resulting virtual machine will be created. This may be relative or absolute. - If relative, the path is relative to the working directory when `packer` - is executed. This directory must not exist or be empty prior to running the builder. - By default this is "output-BUILDNAME" where "BUILDNAME" is the name - of the build. +- `boot_command` (array of strings) - This is an array of commands to type + when the virtual machine is first booted. The goal of these commands should + be to type just enough to initialize the operating system installer. Special + keys can be typed as well, and are covered in the section below on the boot + command. If this is not specified, it is assumed the installer will start + itself. + +- `boot_wait` (string) - The time to wait after booting the initial virtual + machine before typing the `boot_command`. The value of this should be + a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +- `cpu` (int) - The number of cpus the virtual machine should use. If this isn't specified, + the default is 1 cpu. + +- `disk_size` (integer) - The size, in megabytes, of the hard disk to create + for the VM. By default, this is 40000 (about 40 GB). + +- `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. + This defaults to false. + +- `floppy_files` (array of strings) - A list of files to place onto a floppy + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. + +- `generation` (int) - The HyperV generation for the virtual machine. By + default, this is 1. Generation 2 HyperV virtual machines do not support + floppy drives. In this scenario use secondary_iso_images instead. Hard + drives and dvd drives will also be scsi and not ide. + +- `http_directory` (string) - Path to a directory to serve using an HTTP + server. The files in this directory will be available over HTTP that will + be requestable from the virtual machine. This is useful for hosting + kickstart files and so on. By default this is "", which means no HTTP + server will be started. The address and port of the HTTP server will be + available as variables in `boot_command`. This is covered in more detail + below. + +- `http_port_min` and `http_port_max` (integer) - These are the minimum and + maximum port to use for the HTTP server started to serve the `http_directory`. + Because Packer often runs in parallel, Packer will choose a randomly available + port in this range to run the HTTP server. If you want to force the HTTP + server to be on one port, make this minimum and maximum port the same. + By default the values are 8000 and 9000, respectively. + +- `ip_address_timeout` (string) - The time to wait after creating the initial virtual + machine and waiting for an ip address before assuming there is an error in the process. + The value of this should be a duration. Examples are "5s" and "1m30s" which will cause Packer to wait + five seconds and one minute 30 seconds, respectively. If this isn't specified, + the default is 10 seconds. + +- `iso_urls` (array of strings) - Multiple URLs for the ISO to download. + Packer will try these in order. If anything goes wrong attempting to download + or while downloading a single URL, it will move on to the next. All URLs + must point to the same file (same checksum). By default this is empty + and `iso_url` is used. Only one of `iso_url` or `iso_urls` can be specified. + +- `output_directory` (string) - This is the path to the directory where the + resulting virtual machine will be created. This may be relative or absolute. + If relative, the path is relative to the working directory when `packer` + is executed. This directory must not exist or be empty prior to running the builder. + By default this is "output-BUILDNAME" where "BUILDNAME" is the name + of the build. * `secondary_iso_images` (array of strings) - A list of files to place onto a floppy - disk that is attached when the VM is booted. This is most useful - for unattended Windows installs, which look for an `Autounattend.xml` file - on removable media. By default, no floppy will be attached. All files - listed in this setting get placed into the root directory of the floppy - and the floppy is attached as the first floppy device. Currently, no - support exists for creating sub-directories on the floppy. Wildcard - characters (*, ?, and []) are allowed. Directory names are also allowed, - which will add all the files found in the directory to the floppy. - -* `shutdown_command` (string) - The command to use to gracefully shut down the machine once all - the provisioning is done. By default this is an empty string, which tells Packer to just - forcefully shut down the machine unless a shutdown command takes place inside script so this may - safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank - since reboots may fail and specify the final shutdown command in your last script. - -* `shutdown_timeout` (string) - The amount of time to wait after executing - the `shutdown_command` for the virtual machine to actually shut down. - If it doesn't shut down in this time, it is an error. By default, the timeout - is "5m", or five minutes. - -* `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when - exporting. This defaults to false. - -* `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting - this to an empty string, Packer will try to determine the switch to use by looking for - external switch that is up and running. - -* `vm_name` (string) - This is the name of the virtua machine for the new virtual - machine, without the file extension. By default this is "packer-BUILDNAME", - where "BUILDNAME" is the name of the build. + disk that is attached when the VM is booted. This is most useful + for unattended Windows installs, which look for an `Autounattend.xml` file + on removable media. By default, no floppy will be attached. All files + listed in this setting get placed into the root directory of the floppy + and the floppy is attached as the first floppy device. Currently, no + support exists for creating sub-directories on the floppy. Wildcard + characters (*, ?, and []) are allowed. Directory names are also allowed, + which will add all the files found in the directory to the floppy. + +- `shutdown_command` (string) - The command to use to gracefully shut down the machine once all + the provisioning is done. By default this is an empty string, which tells Packer to just + forcefully shut down the machine unless a shutdown command takes place inside script so this may + safely be omitted. If one or more scripts require a reboot it is suggested to leave this blank + since reboots may fail and specify the final shutdown command in your last script. + +- `shutdown_timeout` (string) - The amount of time to wait after executing + the `shutdown_command` for the virtual machine to actually shut down. + If it doesn't shut down in this time, it is an error. By default, the timeout + is "5m", or five minutes. + +- `skip_compaction` (bool) - If true skip compacting the hard disk for virtual machine when + exporting. This defaults to false. + +- `switch_name` (string) - The name of the switch to connect the virtual machine to. Be defaulting + this to an empty string, Packer will try to determine the switch to use by looking for + external switch that is up and running. + +- `vm_name` (string) - This is the name of the virtua machine for the new virtual + machine, without the file extension. By default this is "packer-BUILDNAME", + where "BUILDNAME" is the name of the build. ## Boot Command @@ -196,30 +196,51 @@ to the machine, simulating a human actually typing the keyboard. There are a set of special keys available. If these are in your boot command, they will be replaced by the proper key: -* `<bs>` - Backspace +- `<bs>` - Backspace + +- `<del>` - Delete + +- `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress. + +- `<esc>` - Simulates pressing the escape key. + +- `<tab>` - Simulates pressing the tab key. + +- `<f1>` - `<f12>` - Simulates pressing a function key. + +- `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key. + +- `<spacebar>` - Simulates pressing the spacebar. + +- `<insert>` - Simulates pressing the insert key. + +- `<home>` `<end>` - Simulates pressing the home and end keys. + +- `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys. -* `<del>` - Delete +- `<leftAlt>` `<rightAlt>` - Simulates pressing the alt key. -* `<enter>` and `<return>` - Simulates an actual "enter" or "return" keypress. +- `<leftCtrl>` `<rightCtrl>` - Simulates pressing the ctrl key. -* `<esc>` - Simulates pressing the escape key. +- `<leftShift>` `<rightShift>` - Simulates pressing the shift key. -* `<tab>` - Simulates pressing the tab key. +- `<leftAltOn>` `<rightAltOn>` - Simulates pressing and holding the alt key. -* `<f1>` - `<f12>` - Simulates pressing a function key. +- `<leftCtrlOn>` `<rightCtrlOn>` - Simulates pressing and holding the ctrl key. -* `<up>` `<down>` `<left>` `<right>` - Simulates pressing an arrow key. +- `<leftShiftOn>` `<rightShiftOn>` - Simulates pressing and holding the shift key. -* `<spacebar>` - Simulates pressing the spacebar. +- `<leftAltOff>` `<rightAltOff>` - Simulates releasing a held alt key. -* `<insert>` - Simulates pressing the insert key. +- `<leftCtrlOff>` `<rightCtrlOff>` - Simulates releasing a held ctrl key. -* `<home>` `<end>` - Simulates pressing the home and end keys. +- `<leftShiftOff>` `<rightShiftOff>` - Simulates releasing a held shift key. -* `<pageUp>` `<pageDown>` - Simulates pressing the page up and page down keys. +- `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before + sending any additional keys. This is useful if you have to generally wait + for the UI to update before typing more. -* `<wait>` `<wait5>` `<wait10>` - Adds a 1, 5 or 10 second pause before sending any additional keys. This - is useful if you have to generally wait for the UI to update before typing more. +When using modifier keys `ctrl`, `alt`, `shift` ensure that you release them, otherwise they will be held down until the machine reboots. Use lowercase characters as well inside modifiers. For example: to simulate ctrl+c use `<leftCtrlOn>c<leftCtrlOff>`. In addition to the special keys, each command to type is treated as a [configuration template](/docs/templates/configuration-templates.html). From 483dfd8d17c0929cd6ff88cbf4935ebca32f1139 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 31 Jul 2016 19:50:32 +0100 Subject: [PATCH 098/113] Add special key logging back --- builder/hyperv/common/step_type_boot_command.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/builder/hyperv/common/step_type_boot_command.go b/builder/hyperv/common/step_type_boot_command.go index 03083b9eb23..e9a1bc4a44b 100644 --- a/builder/hyperv/common/step_type_boot_command.go +++ b/builder/hyperv/common/step_type_boot_command.go @@ -2,7 +2,7 @@ package common import ( "fmt" - //"log" + "log" "strings" "unicode" "unicode/utf8" @@ -232,19 +232,19 @@ func scancodes(message string) []string { } if strings.HasPrefix(message, "<wait>") { - //log.Printf("Special code <wait> found, will sleep 1 second at this point.") + log.Printf("Special code <wait> found, will sleep 1 second at this point.") scancode = []string{"wait"} message = message[len("<wait>"):] } if strings.HasPrefix(message, "<wait5>") { - //log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.") + log.Printf("Special code <wait5> found, will sleep 5 seconds at this point.") scancode = []string{"wait5"} message = message[len("<wait5>"):] } if strings.HasPrefix(message, "<wait10>") { - //log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.") + log.Printf("Special code <wait10> found, will sleep 10 seconds at this point.") scancode = []string{"wait10"} message = message[len("<wait10>"):] } @@ -252,7 +252,7 @@ func scancodes(message string) []string { if scancode == nil { for specialCode, specialValue := range special { if strings.HasPrefix(message, specialCode) { - //log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) + log.Printf("Special code '%s' found, replacing with: %s", specialCode, specialValue) scancode = specialValue message = message[len(specialCode):] break @@ -278,7 +278,7 @@ func scancodes(message string) []string { } scancode = append(scancode, fmt.Sprintf("%02x", scancodeInt+0x80)) - //log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) + log.Printf("Sending char '%c', code '%v', shift %v", r, scancode, keyShift) } result = append(result, scancode...) From 67a2db05b6fe03ec85b585b1821680fc7dde5af2 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Tue, 2 Aug 2016 21:41:32 +0100 Subject: [PATCH 099/113] Enable-VMIntegrationService cmdlet has a language dependent value for -Name option. So avoid using it, as it will break on machines with different languages. --- powershell/hyperv/hyperv.go | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 94dd63d463e..90a067b088a 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -472,13 +472,31 @@ if ($vm.State -eq [Microsoft.HyperV.PowerShell.VMState]::Running) { func EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error { + integrationServiceId := "" + switch integrationServiceName { + case "Time Synchronization": + integrationServiceId = "2497F4DE-E9FA-4204-80E4-4B75C46419C0" + case "Heartbeat": + integrationServiceId = "84EAAE65-2F2E-45F5-9BB5-0E857DC8EB47" + case "Key-Value Pair Exchange": + integrationServiceId = "2A34B1C2-FD73-4043-8A5B-DD2159BC743F" + case "Shutdown": + integrationServiceId = "9F8233AC-BE49-4C79-8EE3-E7E1985B2077" + case "VSS": + integrationServiceId = "5CED1297-4598-4915-A5FC-AD21BB4D02A4" + case "Guest Service Interface": + integrationServiceId = "6C09BB55-D683-4DA0-8931-C9BF705F6480" + default: + panic("unrecognized Integration Service Name") + } + var script = ` -param([string]$vmName,[string]$integrationServiceName) -Enable-VMIntegrationService -VMName $vmName -Name $integrationServiceName +param([string]$vmName,[string]$integrationServiceId) +Get-VMIntegrationService -VmName $vmName | ?{$_.Id -match $integrationServiceId} | Enable-VMIntegrationService ` var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, integrationServiceName) + err := ps.Run(script, vmName, integrationServiceId) return err } From 5e7ede49d9dd244ad0accee0db11f60ff171498d Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 18:03:30 -0700 Subject: [PATCH 100/113] s/EnabeSecureBoot/EnableSecureBoot/g --- builder/hyperv/common/step_create_vm.go | 6 +++--- builder/hyperv/iso/builder.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index 6d142cb0b3c..a23c393c99f 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -21,7 +21,7 @@ type StepCreateVM struct { DiskSize uint Generation uint Cpu uint - EnabeSecureBoot bool + EnableSecureBoot bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { @@ -36,7 +36,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { diskSize := int64(s.DiskSize * 1024 * 1024) switchName := s.SwitchName - enabeSecureBoot := s.EnabeSecureBoot + enableSecureBoot := s.EnableSecureBoot err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, s.Generation) if err != nil { @@ -55,7 +55,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { } if s.Generation == 2 { - err = driver.SetSecureBoot(s.VMName, enabeSecureBoot) + err = driver.SetSecureBoot(s.VMName, enableSecureBoot) if err != nil { err := fmt.Errorf("Error setting secure boot: %s", err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index d294c8cf571..ac3fbae8578 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -297,7 +297,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe DiskSize: b.config.DiskSize, Generation: b.config.Generation, Cpu: b.config.Cpu, - EnabeSecureBoot: b.config.EnableSecureBoot, + EnableSecureBoot: b.config.EnableSecureBoot, }, &hypervcommon.StepEnableIntegrationService{}, From 9126e529c1c031b4fcc6d01604b901261d4604cc Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 20:14:36 -0700 Subject: [PATCH 101/113] Adding nested virtualization support --- builder/hyperv/common/driver.go | 2 +- builder/hyperv/common/driver_ps_4.go | 4 ++-- builder/hyperv/common/step_create_vm.go | 3 ++- builder/hyperv/iso/builder.go | 2 ++ powershell/hyperv/hyperv.go | 6 +++--- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index a62e3112b3d..8cfe675829b 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -72,7 +72,7 @@ type Driver interface { DeleteVirtualMachine(string) error - SetVirtualMachineCpu(string, uint) error + SetVirtualMachineCpu(string, uint, bool) error SetSecureBoot(string, bool) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 15d6c6817d8..df1355c5908 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -177,8 +177,8 @@ func (d *HypervPS4Driver) DeleteVirtualMachine(vmName string) error { return hyperv.DeleteVirtualMachine(vmName) } -func (d *HypervPS4Driver) SetVirtualMachineCpu(vmName string, cpu uint) error { - return hyperv.SetVirtualMachineCpu(vmName, cpu) +func (d *HypervPS4Driver) SetVirtualMachineCpu(vmName string, cpu uint, exposeVirtualizationExtensions bool) error { + return hyperv.SetVirtualMachineCpu(vmName, cpu, exposeVirtualizationExtensions) } func (d *HypervPS4Driver) SetSecureBoot(vmName string, enable bool) error { diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index a23c393c99f..e679429205a 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -22,6 +22,7 @@ type StepCreateVM struct { Generation uint Cpu uint EnableSecureBoot bool + ExposeVirtualizationExtensions bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { @@ -46,7 +47,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu) + err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu, s.ExposeVirtualizationExtensions) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index ac3fbae8578..92d19242454 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -89,6 +89,7 @@ type Config struct { Cpu uint `mapstructure:"cpu"` Generation uint `mapstructure:"generation"` EnableSecureBoot bool `mapstructure:"enable_secure_boot"` + EnableVirtualizationExtensions `mapstructure:"enable_virtualization_extensions` Communicator string `mapstructure:"communicator"` @@ -298,6 +299,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Generation: b.config.Generation, Cpu: b.config.Cpu, EnableSecureBoot: b.config.EnableSecureBoot, + EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 90a067b088a..76a2726221e 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -217,11 +217,11 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD } } -func SetVirtualMachineCpu(vmName string, cpu uint) error { +func SetVirtualMachineCpu(vmName string, cpu uint, exposeVirtualizationExtensions bool) error { var script = ` -param([string]$vmName, [int]$cpu) -Set-VMProcessor -VMName $vmName -Count $cpu +param([string]$vmName, [int]$cpu, [bool]$exposeVirtualizationExtensions) +Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $exposeVirtualizationExtensions ` var ps powershell.PowerShellCmd From 7ad92b8c2552921105e6fdae61ca3504ea7892e5 Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 23:09:33 -0700 Subject: [PATCH 102/113] Adding support for nested Hyper-V --- builder/hyperv/common/step_create_vm.go | 4 ++-- builder/hyperv/iso/builder.go | 4 ++-- powershell/hyperv/hyperv.go | 9 ++++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index e679429205a..c03636f3008 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -22,7 +22,7 @@ type StepCreateVM struct { Generation uint Cpu uint EnableSecureBoot bool - ExposeVirtualizationExtensions bool + EnableVirtualizationExtensions bool } func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { @@ -47,7 +47,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu, s.ExposeVirtualizationExtensions) + err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu, s.EnableVirtualizationExtensions) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) state.Put("error", err) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 92d19242454..274dca2ea1f 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -89,7 +89,7 @@ type Config struct { Cpu uint `mapstructure:"cpu"` Generation uint `mapstructure:"generation"` EnableSecureBoot bool `mapstructure:"enable_secure_boot"` - EnableVirtualizationExtensions `mapstructure:"enable_virtualization_extensions` + EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` Communicator string `mapstructure:"communicator"` @@ -299,7 +299,7 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe Generation: b.config.Generation, Cpu: b.config.Cpu, EnableSecureBoot: b.config.EnableSecureBoot, - EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions + EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions, }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 76a2726221e..489699d4f3c 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -217,15 +217,18 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD } } -func SetVirtualMachineCpu(vmName string, cpu uint, exposeVirtualizationExtensions bool) error { +func SetVirtualMachineCpu(vmName string, cpu uint, enableVirtualizationExtensions bool) error { var script = ` param([string]$vmName, [int]$cpu, [bool]$exposeVirtualizationExtensions) Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $exposeVirtualizationExtensions ` - + exposeVirtualizationExtensionsString := "$false" + if enableVirtualizationExtensions { + exposeVirtualizationExtensionsString = "$true" + } var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10)) + err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10), exposeVirtualizationExtensionsString) return err } From c6da09e4bb088a91227d30c398d6cd524d2b772f Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 23:10:28 -0700 Subject: [PATCH 103/113] Adding support for nested Hyper-V --- powershell/hyperv/hyperv.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 489699d4f3c..5631fdf8c54 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -223,9 +223,9 @@ func SetVirtualMachineCpu(vmName string, cpu uint, enableVirtualizationExtension param([string]$vmName, [int]$cpu, [bool]$exposeVirtualizationExtensions) Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $exposeVirtualizationExtensions ` - exposeVirtualizationExtensionsString := "$false" + exposeVirtualizationExtensionsString := "$False" if enableVirtualizationExtensions { - exposeVirtualizationExtensionsString = "$true" + exposeVirtualizationExtensionsString = "$True" } var ps powershell.PowerShellCmd err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10), exposeVirtualizationExtensionsString) From 51a515c39566fa90ab0e1bc90e49933a642cb612 Mon Sep 17 00:00:00 2001 From: Patrick Lang <patrick.lang@hotmail.com> Date: Fri, 5 Aug 2016 23:59:16 -0700 Subject: [PATCH 104/113] Fix type casting --- powershell/hyperv/hyperv.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 5631fdf8c54..2aa2e7e3e88 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -220,12 +220,13 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD func SetVirtualMachineCpu(vmName string, cpu uint, enableVirtualizationExtensions bool) error { var script = ` -param([string]$vmName, [int]$cpu, [bool]$exposeVirtualizationExtensions) -Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $exposeVirtualizationExtensions +param([string]$vmName, [int]$cpu, [string]$exposeVirtualizationExtensions) +$nested = [System.Boolean]::Parse($exposeVirtualizationExtensions) +Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $nested ` - exposeVirtualizationExtensionsString := "$False" + exposeVirtualizationExtensionsString := "False" if enableVirtualizationExtensions { - exposeVirtualizationExtensionsString = "$True" + exposeVirtualizationExtensionsString = "True" } var ps powershell.PowerShellCmd err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10), exposeVirtualizationExtensionsString) From 51a48457c5d70e69eadf264342b500dfa0c24125 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 7 Aug 2016 12:26:27 +0100 Subject: [PATCH 105/113] Add support for mac spoofing and dynamic memory. To enable nested virtualization, mac spoofing, no dynamic memory and at least 4gb of ram should be set for the vm. Set warning if this has not been done. Detected Virtualization Extensions are supported by the machine your are running on, as it only works for Windows 10 and Windows Server 2016 onwards. --- builder/hyperv/common/driver.go | 10 +++- builder/hyperv/common/driver_ps_4.go | 25 +++++++--- builder/hyperv/common/step_create_vm.go | 57 ++++++++++++++++----- builder/hyperv/iso/builder.go | 65 ++++++++++++++++++------ powershell/hyperv/hyperv.go | 66 ++++++++++++++++++++----- powershell/powershell.go | 17 +++++++ 6 files changed, 192 insertions(+), 48 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 8cfe675829b..35df38cb385 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -72,9 +72,15 @@ type Driver interface { DeleteVirtualMachine(string) error - SetVirtualMachineCpu(string, uint, bool) error + SetVirtualMachineCpuCount(string, uint) error - SetSecureBoot(string, bool) error + SetVirtualMachineMacSpoofing(string, bool) error + + SetVirtualMachineDynamicMemory(string, bool) error + + SetVirtualMachineSecureBoot(string, bool) error + + SetVirtualMachineVirtualizationExtensions(string, bool) error EnableVirtualMachineIntegrationService(string, string) error diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index df1355c5908..1fa5220348b 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -6,12 +6,13 @@ package common import ( "fmt" - "github.com/mitchellh/packer/powershell" - "github.com/mitchellh/packer/powershell/hyperv" "log" "runtime" "strconv" "strings" + + "github.com/mitchellh/packer/powershell" + "github.com/mitchellh/packer/powershell/hyperv" ) type HypervPS4Driver struct { @@ -177,12 +178,24 @@ func (d *HypervPS4Driver) DeleteVirtualMachine(vmName string) error { return hyperv.DeleteVirtualMachine(vmName) } -func (d *HypervPS4Driver) SetVirtualMachineCpu(vmName string, cpu uint, exposeVirtualizationExtensions bool) error { - return hyperv.SetVirtualMachineCpu(vmName, cpu, exposeVirtualizationExtensions) +func (d *HypervPS4Driver) SetVirtualMachineCpuCount(vmName string, cpu uint) error { + return hyperv.SetVirtualMachineCpuCount(vmName, cpu) +} + +func (d *HypervPS4Driver) SetVirtualMachineMacSpoofing(vmName string, enable bool) error { + return hyperv.SetVirtualMachineMacSpoofing(vmName, enable) +} + +func (d *HypervPS4Driver) SetVirtualMachineDynamicMemory(vmName string, enable bool) error { + return hyperv.SetVirtualMachineDynamicMemory(vmName, enable) +} + +func (d *HypervPS4Driver) SetVirtualMachineSecureBoot(vmName string, enable bool) error { + return hyperv.SetVirtualMachineSecureBoot(vmName, enable) } -func (d *HypervPS4Driver) SetSecureBoot(vmName string, enable bool) error { - return hyperv.SetSecureBoot(vmName, enable) +func (d *HypervPS4Driver) SetVirtualMachineVirtualizationExtensions(vmName string, enable bool) error { + return hyperv.SetVirtualMachineVirtualizationExtensions(vmName, enable) } func (d *HypervPS4Driver) EnableVirtualMachineIntegrationService(vmName string, integrationServiceName string) error { diff --git a/builder/hyperv/common/step_create_vm.go b/builder/hyperv/common/step_create_vm.go index c03636f3008..54a5a7103b5 100644 --- a/builder/hyperv/common/step_create_vm.go +++ b/builder/hyperv/common/step_create_vm.go @@ -6,6 +6,7 @@ package common import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" ) @@ -15,13 +16,15 @@ import ( // Produces: // VMName string - The name of the VM type StepCreateVM struct { - VMName string - SwitchName string - RamSizeMB uint - DiskSize uint - Generation uint - Cpu uint - EnableSecureBoot bool + VMName string + SwitchName string + RamSizeMB uint + DiskSize uint + Generation uint + Cpu uint + EnableMacSpoofing bool + EnableDynamicMemory bool + EnableSecureBoot bool EnableVirtualizationExtensions bool } @@ -36,10 +39,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { ram := int64(s.RamSizeMB * 1024 * 1024) diskSize := int64(s.DiskSize * 1024 * 1024) - switchName := s.SwitchName - enableSecureBoot := s.EnableSecureBoot - - err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, switchName, s.Generation) + err := driver.CreateVirtualMachine(s.VMName, path, ram, diskSize, s.SwitchName, s.Generation) if err != nil { err := fmt.Errorf("Error creating virtual machine: %s", err) state.Put("error", err) @@ -47,7 +47,7 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - err = driver.SetVirtualMachineCpu(s.VMName, s.Cpu, s.EnableVirtualizationExtensions) + err = driver.SetVirtualMachineCpuCount(s.VMName, s.Cpu) if err != nil { err := fmt.Errorf("Error creating setting virtual machine cpu: %s", err) state.Put("error", err) @@ -55,8 +55,28 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } + if s.EnableDynamicMemory { + err = driver.SetVirtualMachineDynamicMemory(s.VMName, s.EnableDynamicMemory) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine dynamic memory: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + + if s.EnableMacSpoofing { + err = driver.SetVirtualMachineMacSpoofing(s.VMName, s.EnableMacSpoofing) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine mac spoofing: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + if s.Generation == 2 { - err = driver.SetSecureBoot(s.VMName, enableSecureBoot) + err = driver.SetVirtualMachineSecureBoot(s.VMName, s.EnableSecureBoot) if err != nil { err := fmt.Errorf("Error setting secure boot: %s", err) state.Put("error", err) @@ -65,6 +85,17 @@ func (s *StepCreateVM) Run(state multistep.StateBag) multistep.StepAction { } } + if s.EnableVirtualizationExtensions { + //This is only supported on Windows 10 and Windows Server 2016 onwards + err = driver.SetVirtualMachineVirtualizationExtensions(s.VMName, s.EnableVirtualizationExtensions) + if err != nil { + err := fmt.Errorf("Error creating setting virtual machine virtualization extensions: %s", err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + } + // Set the final name in the state bag so others can use it state.Put("vmName", s.VMName) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 274dca2ea1f..0772e24ba5f 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -28,9 +28,10 @@ const ( MinDiskSize = 256 // 256MB MaxDiskSize = 64 * 1024 * 1024 // 64TB - DefaultRamSize = 1 * 1024 // 1GB - MinRamSize = 32 // 32MB - MaxRamSize = 32 * 1024 // 32GB + DefaultRamSize = 1 * 1024 // 1GB + MinRamSize = 32 // 32MB + MaxRamSize = 32 * 1024 // 32GB + MinNestedVirtualizationRamSize = 4 * 1024 // 4GB LowRam = 256 // 256MB @@ -84,12 +85,14 @@ type Config struct { // By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. VMName string `mapstructure:"vm_name"` - BootCommand []string `mapstructure:"boot_command"` - SwitchName string `mapstructure:"switch_name"` - Cpu uint `mapstructure:"cpu"` - Generation uint `mapstructure:"generation"` - EnableSecureBoot bool `mapstructure:"enable_secure_boot"` - EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` + BootCommand []string `mapstructure:"boot_command"` + SwitchName string `mapstructure:"switch_name"` + Cpu uint `mapstructure:"cpu"` + Generation uint `mapstructure:"generation"` + EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` + EnableDynamicMemory bool `mapstructure:"enable_dynamic_memory"` + EnableSecureBoot bool `mapstructure:"enable_secure_boot"` + EnableVirtualizationExtensions bool `mapstructure:"enable_virtualization_extensions"` Communicator string `mapstructure:"communicator"` @@ -227,6 +230,17 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } + if b.config.EnableVirtualizationExtensions { + hasVirtualMachineVirtualizationExtensions, err := powershell.HasVirtualMachineVirtualizationExtensions() + if err != nil { + errs = packer.MultiErrorAppend(errs, fmt.Errorf("Failed detecting virtual machine virtualization extensions support: %s", err)) + } else { + if !hasVirtualMachineVirtualizationExtensions { + 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.")) + } + } + } + // Warnings if b.config.ShutdownCommand == "" { @@ -240,6 +254,23 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { warnings = appendWarnings(warnings, warning) } + if b.config.EnableVirtualizationExtensions { + if b.config.EnableDynamicMemory { + warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, dynamic memory should not be allowed.") + warnings = appendWarnings(warnings, warning) + } + + if !b.config.EnableMacSpoofing { + warning = fmt.Sprintf("For nested virtualization, when virtualization extension is enabled, mac spoofing should be allowed.") + warnings = appendWarnings(warnings, warning) + } + + if b.config.RamSizeMB < MinNestedVirtualizationRamSize { + 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.") + warnings = appendWarnings(warnings, warning) + } + } + if errs != nil && len(errs.Errors) > 0 { return warnings, errs } @@ -292,13 +323,15 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe SwitchName: b.config.SwitchName, }, &hypervcommon.StepCreateVM{ - VMName: b.config.VMName, - SwitchName: b.config.SwitchName, - RamSizeMB: b.config.RamSizeMB, - DiskSize: b.config.DiskSize, - Generation: b.config.Generation, - Cpu: b.config.Cpu, - EnableSecureBoot: b.config.EnableSecureBoot, + VMName: b.config.VMName, + SwitchName: b.config.SwitchName, + RamSizeMB: b.config.RamSizeMB, + DiskSize: b.config.DiskSize, + Generation: b.config.Generation, + Cpu: b.config.Cpu, + EnableMacSpoofing: b.config.EnableMacSpoofing, + EnableDynamicMemory: b.config.EnableDynamicMemory, + EnableSecureBoot: b.config.EnableSecureBoot, EnableVirtualizationExtensions: b.config.EnableVirtualizationExtensions, }, &hypervcommon.StepEnableIntegrationService{}, diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index 2aa2e7e3e88..b0a8d7fc8c7 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -217,23 +217,67 @@ New-VM -Name $vmName -Path $path -MemoryStartupBytes $memoryStartupBytes -NewVHD } } -func SetVirtualMachineCpu(vmName string, cpu uint, enableVirtualizationExtensions bool) error { +func SetVirtualMachineCpuCount(vmName string, cpu uint) error { var script = ` -param([string]$vmName, [int]$cpu, [string]$exposeVirtualizationExtensions) -$nested = [System.Boolean]::Parse($exposeVirtualizationExtensions) -Set-VMProcessor -VMName $vmName -Count $cpu -exposeVirtualizationExtensions $nested +param([string]$vmName, [int]$cpu) +Set-VMProcessor -VMName $vmName -Count $cpu +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10)) + return err +} + +func SetVirtualMachineVirtualizationExtensions(vmName string, enableVirtualizationExtensions bool) error { + + var script = ` +param([string]$vmName, [string]$exposeVirtualizationExtensionsString) +$exposeVirtualizationExtensions = [System.Boolean]::Parse($exposeVirtualizationExtensionsString) +Set-VMProcessor -VMName $vmName -ExposeVirtualizationExtensions $exposeVirtualizationExtensions ` exposeVirtualizationExtensionsString := "False" if enableVirtualizationExtensions { exposeVirtualizationExtensionsString = "True" - } + } + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, exposeVirtualizationExtensionsString) + return err +} + +func SetVirtualMachineDynamicMemory(vmName string, enableDynamicMemory bool) error { + + var script = ` +param([string]$vmName, [string]$enableDynamicMemoryString) +$enableDynamicMemory = [System.Boolean]::Parse($enableDynamicMemoryString) +Set-VMMemory -VMName $vmName -DynamicMemoryEnabled $enableDynamicMemory +` + enableDynamicMemoryString := "False" + if enableDynamicMemory { + enableDynamicMemoryString = "True" + } var ps powershell.PowerShellCmd - err := ps.Run(script, vmName, strconv.FormatInt(int64(cpu), 10), exposeVirtualizationExtensionsString) + err := ps.Run(script, vmName, enableDynamicMemoryString) + return err +} + +func SetVirtualMachineMacSpoofing(vmName string, enableMacSpoofing bool) error { + var script = ` +param([string]$vmName, $enableMacSpoofing) +Set-VMNetworkAdapter -VMName $vmName -MacAddressSpoofing $enableMacSpoofing +` + + var ps powershell.PowerShellCmd + + enableMacSpoofingString := "Off" + if enableMacSpoofing { + enableMacSpoofingString = "On" + } + + err := ps.Run(script, vmName, enableMacSpoofingString) return err } -func SetSecureBoot(vmName string, enable bool) error { +func SetVirtualMachineSecureBoot(vmName string, enableSecureBoot bool) error { var script = ` param([string]$vmName, $enableSecureBoot) Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBoot @@ -241,12 +285,12 @@ Set-VMFirmware -VMName $vmName -EnableSecureBoot $enableSecureBoot var ps powershell.PowerShellCmd - enableSecureBoot := "Off" - if enable { - enableSecureBoot = "On" + enableSecureBootString := "Off" + if enableSecureBoot { + enableSecureBootString = "On" } - err := ps.Run(script, vmName, enableSecureBoot) + err := ps.Run(script, vmName, enableSecureBootString) return err } diff --git a/powershell/powershell.go b/powershell/powershell.go index 044ec638487..93ba91150bc 100644 --- a/powershell/powershell.go +++ b/powershell/powershell.go @@ -231,6 +231,23 @@ param([string]$moduleName) return true, nil } +func HasVirtualMachineVirtualizationExtensions() (bool, error) { + + var script = ` +(GET-Command Set-VMProcessor).parameters.keys -contains "ExposeVirtualizationExtensions" +` + + var ps PowerShellCmd + cmdOut, err := ps.Output(script) + + if err != nil { + return false, err + } + + var hasVirtualMachineVirtualizationExtensions = strings.TrimSpace(cmdOut) == "True" + return hasVirtualMachineVirtualizationExtensions, err +} + func SetUnattendedProductKey(path string, productKey string) error { var script = ` From 01611e40bbe23674a2f6f04d50a2b86ed0667918 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Sun, 7 Aug 2016 12:32:11 +0100 Subject: [PATCH 106/113] Add documentation for: enable_mac_spoofing, enable_dynamic_memory and enable_virtualization_extensions --- website/source/docs/builders/hyperv-iso.html.markdown | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 2d030d7c309..0148c518de8 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -96,9 +96,19 @@ can be configured for this builder. - `disk_size` (integer) - The size, in megabytes, of the hard disk to create for the VM. By default, this is 40000 (about 40 GB). +- `enable_mac_spoofing` (bool) - If true enable mac spoofing for virtual machine. + This defaults to false. + +- `enable_dynamic_memory` (bool) - If true enable dynamic memory for virtual machine. + This defaults to false. + - `enable_secure_boot` (bool) - If true enable secure boot for virtual machine. This defaults to false. +- `enable_virtualization_extensions` (bool) - If true enable virtualization extensions for virtual machine. + This defaults to false. For nested virtualization you need to enable mac spoofing, disable dynamic memory + and have at least 4GB of RAM for virtual machine. + - `floppy_files` (array of strings) - A list of files to place onto a floppy disk that is attached when the VM is booted. This is most useful for unattended Windows installs, which look for an `Autounattend.xml` file From 5cfa97dbc7646b2c711586b75bc8d3961838989e Mon Sep 17 00:00:00 2001 From: James Johnson <james.johnson@equifax.com> Date: Wed, 21 Sep 2016 18:31:06 +0000 Subject: [PATCH 107/113] Enable VlanID --- builder/hyperv/common/step_configure_vlan.go | 7 ++++--- builder/hyperv/iso/builder.go | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go index 94f445ddd93..cd0b3416928 100644 --- a/builder/hyperv/common/step_configure_vlan.go +++ b/builder/hyperv/common/step_configure_vlan.go @@ -11,7 +11,7 @@ import ( ) type StepConfigureVlan struct { - vlanId string + VlanID string } func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { @@ -24,10 +24,11 @@ func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { ui.Say("Configuring vlan...") - vlanId := s.vlanId + vlanId := s.VlanID if vlanId == "" { - vlanId = "1724" + // If no vlan ID is specified, do not enable Virtual LAN Identification + return multistep.ActionContinue } err := driver.SetNetworkAdapterVlanId(switchName, vlanId) diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 0772e24ba5f..162e3c915b5 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -87,6 +87,7 @@ type Config struct { BootCommand []string `mapstructure:"boot_command"` SwitchName string `mapstructure:"switch_name"` + VlandID string `mapstructure:"vlan_id"` Cpu uint `mapstructure:"cpu"` Generation uint `mapstructure:"generation"` EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` @@ -353,6 +354,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe IsoPaths: b.config.SecondaryDvdImages, Generation: b.config.Generation, }, + + &hypervcommon.StepConfigureVlan{ + VlanID: b.config.VlandID, + }, &hypervcommon.StepRun{ BootWait: b.config.BootWait, From 4fe13eea7a859969fb6c59fdfdb25a19b3a26439 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 29 Sep 2016 17:56:35 +0100 Subject: [PATCH 108/113] Fix dodgy merge --- builder/qemu/ssh.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/builder/qemu/ssh.go b/builder/qemu/ssh.go index 75264652daa..498d3fbe9c1 100644 --- a/builder/qemu/ssh.go +++ b/builder/qemu/ssh.go @@ -11,16 +11,11 @@ func commHost(state multistep.StateBag) (string, error) { return "127.0.0.1", nil } -func SSHPort(state multistep.StateBag) (int, error) { +func commPort(state multistep.StateBag) (int, error) { sshHostPort := state.Get("sshHostPort").(uint) return int(sshHostPort), nil } -func WinRMPort(state multistep.StateBag) (int, error) { - winRMHostPort := state.Get("sshHostPort").(uint) - return int(winRMHostPort), nil -} - func sshConfig(state multistep.StateBag) (*gossh.ClientConfig, error) { config := state.Get("config").(*Config) From 4d70b68f3ee600159d33759d1db8e2c70d89a0de Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 29 Sep 2016 18:27:13 +0100 Subject: [PATCH 109/113] Remove duplicate plugin --- command/plugin.go | 131 ++++++++++++++++++++++------------------------ 1 file changed, 63 insertions(+), 68 deletions(-) diff --git a/command/plugin.go b/command/plugin.go index 62e9dc85afd..31a78329652 100644 --- a/command/plugin.go +++ b/command/plugin.go @@ -15,51 +15,50 @@ import ( amazonchrootbuilder "github.com/mitchellh/packer/builder/amazon/chroot" amazonebsbuilder "github.com/mitchellh/packer/builder/amazon/ebs" - amazonimportpostprocessor "github.com/mitchellh/packer/post-processor/amazon-import" amazoninstancebuilder "github.com/mitchellh/packer/builder/amazon/instance" - ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local" - ansibleprovisioner "github.com/mitchellh/packer/provisioner/ansible" - artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice" - atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas" azurearmbuilder "github.com/mitchellh/packer/builder/azure/arm" - checksumpostprocessor "github.com/mitchellh/packer/post-processor/checksum" - chefclientprovisioner "github.com/mitchellh/packer/provisioner/chef-client" - chefsoloprovisioner "github.com/mitchellh/packer/provisioner/chef-solo" - compresspostprocessor "github.com/mitchellh/packer/post-processor/compress" digitaloceanbuilder "github.com/mitchellh/packer/builder/digitalocean" dockerbuilder "github.com/mitchellh/packer/builder/docker" - dockerimportpostprocessor "github.com/mitchellh/packer/post-processor/docker-import" - dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push" - dockersavepostprocessor "github.com/mitchellh/packer/post-processor/docker-save" - dockertagpostprocessor "github.com/mitchellh/packer/post-processor/docker-tag" filebuilder "github.com/mitchellh/packer/builder/file" - fileprovisioner "github.com/mitchellh/packer/provisioner/file" googlecomputebuilder "github.com/mitchellh/packer/builder/googlecompute" - googlecomputeexportpostprocessor "github.com/mitchellh/packer/post-processor/googlecompute-export" hypervbuilder "github.com/mitchellh/packer/builder/hyperv/iso" - manifestpostprocessor "github.com/mitchellh/packer/post-processor/manifest" nullbuilder "github.com/mitchellh/packer/builder/null" openstackbuilder "github.com/mitchellh/packer/builder/openstack" parallelsisobuilder "github.com/mitchellh/packer/builder/parallels/iso" parallelspvmbuilder "github.com/mitchellh/packer/builder/parallels/pvm" - powershellprovisioner "github.com/mitchellh/packer/provisioner/powershell" - puppetmasterlessprovisioner "github.com/mitchellh/packer/provisioner/puppet-masterless" - puppetserverprovisioner "github.com/mitchellh/packer/provisioner/puppet-server" qemubuilder "github.com/mitchellh/packer/builder/qemu" - saltmasterlessprovisioner "github.com/mitchellh/packer/provisioner/salt-masterless" - shelllocalpostprocessor "github.com/mitchellh/packer/post-processor/shell-local" - shelllocalprovisioner "github.com/mitchellh/packer/provisioner/shell-local" - shellprovisioner "github.com/mitchellh/packer/provisioner/shell" - vagrantcloudpostprocessor "github.com/mitchellh/packer/post-processor/vagrant-cloud" - vagrantpostprocessor "github.com/mitchellh/packer/post-processor/vagrant" virtualboxisobuilder "github.com/mitchellh/packer/builder/virtualbox/iso" virtualboxovfbuilder "github.com/mitchellh/packer/builder/virtualbox/ovf" vmwareisobuilder "github.com/mitchellh/packer/builder/vmware/iso" vmwarevmxbuilder "github.com/mitchellh/packer/builder/vmware/vmx" + amazonimportpostprocessor "github.com/mitchellh/packer/post-processor/amazon-import" + artificepostprocessor "github.com/mitchellh/packer/post-processor/artifice" + atlaspostprocessor "github.com/mitchellh/packer/post-processor/atlas" + checksumpostprocessor "github.com/mitchellh/packer/post-processor/checksum" + compresspostprocessor "github.com/mitchellh/packer/post-processor/compress" + dockerimportpostprocessor "github.com/mitchellh/packer/post-processor/docker-import" + dockerpushpostprocessor "github.com/mitchellh/packer/post-processor/docker-push" + dockersavepostprocessor "github.com/mitchellh/packer/post-processor/docker-save" + dockertagpostprocessor "github.com/mitchellh/packer/post-processor/docker-tag" + googlecomputeexportpostprocessor "github.com/mitchellh/packer/post-processor/googlecompute-export" + manifestpostprocessor "github.com/mitchellh/packer/post-processor/manifest" + shelllocalpostprocessor "github.com/mitchellh/packer/post-processor/shell-local" + vagrantpostprocessor "github.com/mitchellh/packer/post-processor/vagrant" + vagrantcloudpostprocessor "github.com/mitchellh/packer/post-processor/vagrant-cloud" vspherepostprocessor "github.com/mitchellh/packer/post-processor/vsphere" + ansibleprovisioner "github.com/mitchellh/packer/provisioner/ansible" + ansiblelocalprovisioner "github.com/mitchellh/packer/provisioner/ansible-local" + chefclientprovisioner "github.com/mitchellh/packer/provisioner/chef-client" + chefsoloprovisioner "github.com/mitchellh/packer/provisioner/chef-solo" + fileprovisioner "github.com/mitchellh/packer/provisioner/file" + powershellprovisioner "github.com/mitchellh/packer/provisioner/powershell" + puppetmasterlessprovisioner "github.com/mitchellh/packer/provisioner/puppet-masterless" + puppetserverprovisioner "github.com/mitchellh/packer/provisioner/puppet-server" + saltmasterlessprovisioner "github.com/mitchellh/packer/provisioner/salt-masterless" + shellprovisioner "github.com/mitchellh/packer/provisioner/shell" + shelllocalprovisioner "github.com/mitchellh/packer/provisioner/shell-local" windowsrestartprovisioner "github.com/mitchellh/packer/provisioner/windows-restart" windowsshellprovisioner "github.com/mitchellh/packer/provisioner/windows-shell" - ) type PluginCommand struct { @@ -68,63 +67,59 @@ type PluginCommand struct { var Builders = map[string]packer.Builder{ "amazon-chroot": new(amazonchrootbuilder.Builder), - "amazon-ebs": new(amazonebsbuilder.Builder), - "amazon-instance": new(amazoninstancebuilder.Builder), - "azure-arm": new(azurearmbuilder.Builder), - "digitalocean": new(digitaloceanbuilder.Builder), - "docker": new(dockerbuilder.Builder), - "file": new(filebuilder.Builder), + "amazon-ebs": new(amazonebsbuilder.Builder), + "amazon-instance": new(amazoninstancebuilder.Builder), + "azure-arm": new(azurearmbuilder.Builder), + "digitalocean": new(digitaloceanbuilder.Builder), + "docker": new(dockerbuilder.Builder), + "file": new(filebuilder.Builder), "googlecompute": new(googlecomputebuilder.Builder), "hyperv-iso": new(hypervbuilder.Builder), + "null": new(nullbuilder.Builder), "openstack": new(openstackbuilder.Builder), - "null": new(nullbuilder.Builder), - "openstack": new(openstackbuilder.Builder), "parallels-iso": new(parallelsisobuilder.Builder), "parallels-pvm": new(parallelspvmbuilder.Builder), - "qemu": new(qemubuilder.Builder), - "virtualbox-iso": new(virtualboxisobuilder.Builder), - "virtualbox-ovf": new(virtualboxovfbuilder.Builder), - "vmware-iso": new(vmwareisobuilder.Builder), - "vmware-vmx": new(vmwarevmxbuilder.Builder), + "qemu": new(qemubuilder.Builder), + "virtualbox-iso": new(virtualboxisobuilder.Builder), + "virtualbox-ovf": new(virtualboxovfbuilder.Builder), + "vmware-iso": new(vmwareisobuilder.Builder), + "vmware-vmx": new(vmwarevmxbuilder.Builder), } - var Provisioners = map[string]packer.Provisioner{ - "ansible": new(ansibleprovisioner.Provisioner), - "ansible-local": new(ansiblelocalprovisioner.Provisioner), - "chef-client": new(chefclientprovisioner.Provisioner), - "chef-solo": new(chefsoloprovisioner.Provisioner), - "file": new(fileprovisioner.Provisioner), - "powershell": new(powershellprovisioner.Provisioner), - "puppet-masterless": new(puppetmasterlessprovisioner.Provisioner), - "puppet-server": new(puppetserverprovisioner.Provisioner), + "ansible": new(ansibleprovisioner.Provisioner), + "ansible-local": new(ansiblelocalprovisioner.Provisioner), + "chef-client": new(chefclientprovisioner.Provisioner), + "chef-solo": new(chefsoloprovisioner.Provisioner), + "file": new(fileprovisioner.Provisioner), + "powershell": new(powershellprovisioner.Provisioner), + "puppet-masterless": new(puppetmasterlessprovisioner.Provisioner), + "puppet-server": new(puppetserverprovisioner.Provisioner), "salt-masterless": new(saltmasterlessprovisioner.Provisioner), - "shell": new(shellprovisioner.Provisioner), - "shell-local": new(shelllocalprovisioner.Provisioner), + "shell": new(shellprovisioner.Provisioner), + "shell-local": new(shelllocalprovisioner.Provisioner), "windows-restart": new(windowsrestartprovisioner.Provisioner), - "windows-shell": new(windowsshellprovisioner.Provisioner), + "windows-shell": new(windowsshellprovisioner.Provisioner), } - var PostProcessors = map[string]packer.PostProcessor{ - "amazon-import": new(amazonimportpostprocessor.PostProcessor), - "artifice": new(artificepostprocessor.PostProcessor), - "atlas": new(atlaspostprocessor.PostProcessor), - "checksum": new(checksumpostprocessor.PostProcessor), - "compress": new(compresspostprocessor.PostProcessor), - "docker-import": new(dockerimportpostprocessor.PostProcessor), - "docker-push": new(dockerpushpostprocessor.PostProcessor), - "docker-save": new(dockersavepostprocessor.PostProcessor), - "docker-tag": new(dockertagpostprocessor.PostProcessor), - "googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor), - "manifest": new(manifestpostprocessor.PostProcessor), - "shell-local": new(shelllocalpostprocessor.PostProcessor), - "vagrant": new(vagrantpostprocessor.PostProcessor), - "vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor), - "vsphere": new(vspherepostprocessor.PostProcessor), + "amazon-import": new(amazonimportpostprocessor.PostProcessor), + "artifice": new(artificepostprocessor.PostProcessor), + "atlas": new(atlaspostprocessor.PostProcessor), + "checksum": new(checksumpostprocessor.PostProcessor), + "compress": new(compresspostprocessor.PostProcessor), + "docker-import": new(dockerimportpostprocessor.PostProcessor), + "docker-push": new(dockerpushpostprocessor.PostProcessor), + "docker-save": new(dockersavepostprocessor.PostProcessor), + "docker-tag": new(dockertagpostprocessor.PostProcessor), + "googlecompute-export": new(googlecomputeexportpostprocessor.PostProcessor), + "manifest": new(manifestpostprocessor.PostProcessor), + "shell-local": new(shelllocalpostprocessor.PostProcessor), + "vagrant": new(vagrantpostprocessor.PostProcessor), + "vagrant-cloud": new(vagrantcloudpostprocessor.PostProcessor), + "vsphere": new(vspherepostprocessor.PostProcessor), } - var pluginRegexp = regexp.MustCompile("packer-(builder|post-processor|provisioner)-(.+)") func (c *PluginCommand) Run(args []string) int { From 002094b42e9b6c6be697d29fcb0be9ca7b0f2771 Mon Sep 17 00:00:00 2001 From: Taliesin Sisson <taliesins@yahoo.com> Date: Thu, 29 Sep 2016 19:37:07 +0100 Subject: [PATCH 110/113] Added the ability to independently configure switch vlan. This will people to leave the switch in trunk mode and set a vlan for the vm. --- builder/hyperv/common/step_configure_vlan.go | 41 ++++++++++--------- builder/hyperv/iso/builder.go | 15 +++++-- .../docs/builders/hyperv-iso.html.markdown | 10 ++++- 3 files changed, 42 insertions(+), 24 deletions(-) diff --git a/builder/hyperv/common/step_configure_vlan.go b/builder/hyperv/common/step_configure_vlan.go index cd0b3416928..bfd24b49034 100644 --- a/builder/hyperv/common/step_configure_vlan.go +++ b/builder/hyperv/common/step_configure_vlan.go @@ -6,12 +6,14 @@ package common import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" ) type StepConfigureVlan struct { - VlanID string + VlanId string + SwitchVlanId string } func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { @@ -21,30 +23,29 @@ func (s *StepConfigureVlan) Run(state multistep.StateBag) multistep.StepAction { errorMsg := "Error configuring vlan: %s" vmName := state.Get("vmName").(string) switchName := state.Get("SwitchName").(string) + vlanId := s.VlanId + switchVlanId := s.SwitchVlanId ui.Say("Configuring vlan...") - vlanId := s.VlanID - - if vlanId == "" { - // If no vlan ID is specified, do not enable Virtual LAN Identification - return multistep.ActionContinue - } - - err := driver.SetNetworkAdapterVlanId(switchName, vlanId) - if err != nil { - err := fmt.Errorf(errorMsg, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + if switchVlanId != "" { + err := driver.SetNetworkAdapterVlanId(switchName, vlanId) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } - err = driver.SetVirtualMachineVlanId(vmName, vlanId) - if err != nil { - err := fmt.Errorf(errorMsg, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt + if vlanId != "" { + err := driver.SetVirtualMachineVlanId(vmName, vlanId) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } } return multistep.ActionContinue diff --git a/builder/hyperv/iso/builder.go b/builder/hyperv/iso/builder.go index 162e3c915b5..ac390eba102 100644 --- a/builder/hyperv/iso/builder.go +++ b/builder/hyperv/iso/builder.go @@ -87,7 +87,8 @@ type Config struct { BootCommand []string `mapstructure:"boot_command"` SwitchName string `mapstructure:"switch_name"` - VlandID string `mapstructure:"vlan_id"` + SwitchVlanId string `mapstructure:"switch_vlan_id"` + VlanId string `mapstructure:"vlan_id"` Cpu uint `mapstructure:"cpu"` Generation uint `mapstructure:"generation"` EnableMacSpoofing bool `mapstructure:"enable_mac_spoofing"` @@ -272,6 +273,13 @@ func (b *Builder) Prepare(raws ...interface{}) ([]string, error) { } } + if b.config.SwitchVlanId != "" { + if b.config.SwitchVlanId != b.config.VlanId { + 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.") + warnings = appendWarnings(warnings, warning) + } + } + if errs != nil && len(errs.Errors) > 0 { return warnings, errs } @@ -354,9 +362,10 @@ func (b *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (packe IsoPaths: b.config.SecondaryDvdImages, Generation: b.config.Generation, }, - + &hypervcommon.StepConfigureVlan{ - VlanID: b.config.VlandID, + VlanId: b.config.VlanId, + SwitchVlanId: b.config.SwitchVlanId, }, &hypervcommon.StepRun{ diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 0148c518de8..062f6bd7988 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -186,7 +186,15 @@ can be configured for this builder. this to an empty string, Packer will try to determine the switch to use by looking for external switch that is up and running. -- `vm_name` (string) - This is the name of the virtua machine for the new virtual +- `switch_vlan_id` (string) - This is the vlan of the virtual switch's network card. + By default none is set. If none is set then a vlan is not set on the switch's network card. + If this value is set it should match the vlan specified in by `vlan_id`. + +- `vlan_id` (string) - This is the vlan of the virtual machine's network card for the new virtual + machine. By default none is set. If none is set then vlans are not set on the virtual machine's + network card. + +- `vm_name` (string) - This is the name of the virtual machine for the new virtual machine, without the file extension. By default this is "packer-BUILDNAME", where "BUILDNAME" is the name of the build. From 6ed04568c3661e86b6fd829a7ab05b21c93d385b Mon Sep 17 00:00:00 2001 From: gaelcolas <gaelcolas@users.noreply.github.com> Date: Wed, 5 Oct 2016 14:34:41 +0100 Subject: [PATCH 111/113] SetVmIp on HyperV VM as per kitchen-hyperv --- builder/hyperv/common/driver.go | 2 + builder/hyperv/common/driver_ps_4.go | 4 + builder/hyperv/common/step_configure_ip.go | 105 +++++++++++---------- powershell/hyperv/hyperv.go | 63 +++++++++++++ 4 files changed, 126 insertions(+), 48 deletions(-) diff --git a/builder/hyperv/common/driver.go b/builder/hyperv/common/driver.go index 35df38cb385..038c855e642 100644 --- a/builder/hyperv/common/driver.go +++ b/builder/hyperv/common/driver.go @@ -105,4 +105,6 @@ type Driver interface { MountFloppyDrive(string, string) error UnmountFloppyDrive(string) error + + SetVirtualMachineIPNetworkConfiguration(string, string, string, string, string) error } diff --git a/builder/hyperv/common/driver_ps_4.go b/builder/hyperv/common/driver_ps_4.go index 1fa5220348b..6ece080d2b6 100644 --- a/builder/hyperv/common/driver_ps_4.go +++ b/builder/hyperv/common/driver_ps_4.go @@ -246,6 +246,10 @@ func (d *HypervPS4Driver) UnmountFloppyDrive(vmName string) error { return hyperv.UnmountFloppyDrive(vmName) } +func (d *HypervPS4Driver) SetVirtualMachineIPNetworkConfiguration(vmName string, IPAddress string, Gateway string, DNSServer string, Subnet string) error { + return hyperv.SetVirtualMachineIPNetworkConfiguration(vmName, IPAddress, Gateway, DNSServer, Subnet) +} + func (d *HypervPS4Driver) verifyPSVersion() error { log.Printf("Enter method: %s", "verifyPSVersion") diff --git a/builder/hyperv/common/step_configure_ip.go b/builder/hyperv/common/step_configure_ip.go index 4396a1b9a24..cf0a6c08083 100644 --- a/builder/hyperv/common/step_configure_ip.go +++ b/builder/hyperv/common/step_configure_ip.go @@ -6,17 +6,19 @@ package common import ( "fmt" + "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" - "log" - "strings" - "time" ) -type StepConfigureIp struct { +type StepConfigureIP struct { + IPAddress string + Gateway string + DNSServer string + Subnet string } -func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { +func (s *StepConfigureIP) Run(state multistep.StateBag) multistep.StepAction { driver := state.Get("driver").(Driver) ui := state.Get("ui").(packer.Ui) @@ -24,56 +26,63 @@ func (s *StepConfigureIp) Run(state multistep.StateBag) multistep.StepAction { vmName := state.Get("vmName").(string) ui.Say("Configuring ip address...") - - count := 60 - var duration time.Duration = 1 - sleepTime := time.Minute * duration - var ip string - - for count != 0 { - cmdOut, err := driver.GetVirtualMachineNetworkAdapterAddress(vmName) - if err != nil { - err := fmt.Errorf(errorMsg, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ip = strings.TrimSpace(string(cmdOut)) - - if ip != "False" { - break - } - - log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration))) - time.Sleep(sleepTime) - count-- - } - - if count == 0 { - err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty") - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ui.Say("ip address is " + ip) - - hostName, err := driver.GetHostName(ip) + err := driver.SetVirtualMachineIPNetworkConfiguration(vmName, s.IPAddress, s.Gateway, s.DNSServer, s.Subnet) if err != nil { + err := fmt.Errorf(errorMsg, err) state.Put("error", err) ui.Error(err.Error()) return multistep.ActionHalt } - - ui.Say("hostname is " + hostName) - - state.Put("ip", ip) - state.Put("hostname", hostName) - + /* + count := 60 + var duration time.Duration = 1 + sleepTime := time.Minute * duration + var ip string + + for count != 0 { + cmdOut, err := driver.GetVirtualMachineNetworkAdapterAddress(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ip = strings.TrimSpace(string(cmdOut)) + + if ip != "False" { + break + } + + log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration))) + time.Sleep(sleepTime) + count-- + } + + if count == 0 { + err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("ip address is " + ip) + + hostName, err := driver.GetHostName(ip) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("hostname is " + hostName) + + state.Put("ip", ip) + state.Put("hostname", hostName) + */ return multistep.ActionContinue } -func (s *StepConfigureIp) Cleanup(state multistep.StateBag) { +func (s *StepConfigureIP) Cleanup(state multistep.StateBag) { // do nothing } diff --git a/powershell/hyperv/hyperv.go b/powershell/hyperv/hyperv.go index b0a8d7fc8c7..eeae4d727cf 100644 --- a/powershell/hyperv/hyperv.go +++ b/powershell/hyperv/hyperv.go @@ -981,3 +981,66 @@ param([string]$vmName, [string]$scanCodes) err := ps.Run(script, vmName, scanCodes) return err } + +func SetVirtualMachineIPNetworkConfiguration(vmName string, IPAddress string, Gateway string, DNSServer string, Subnet string) error { + + var script = ` +[CmdletBinding()] + Param ( + + [string]$vmName, + [String[]]$IPAddress = @(), + [String[]]$Gateway = @(), + [String[]]$DNSServers = @(), + [String[]]$Subnet = @() + ) + $NetworkAdapter = Get-VMNetworkAdapter -VMName $vmName | Select -first 1 + $vm = Get-WmiObject -Namespace 'root\virtualization\v2' -Class 'Msvm_ComputerSystem' | Where-Object { + $_.ElementName -eq $NetworkAdapter.VMName + } + $VMSettings = $vm.GetRelated('Msvm_VirtualSystemSettingData') | Where-Object { + $_.VirtualSystemType -eq 'Microsoft:Hyper-V:System:Realized' + } + $VMNetAdapters = $VMSettings.GetRelated('Msvm_SyntheticEthernetPortSettingData') + + $NetworkSettings = @() + foreach ($NetAdapter in $VMNetAdapters) + { + if ($NetAdapter.Address -eq $NetworkAdapter.MacAddress) + { + $NetworkSettings = $NetworkSettings + $NetAdapter.GetRelated('Msvm_GuestNetworkAdapterConfiguration') + } + } + + $NetworkSettings[0].IPAddresses = $IPAddress + $NetworkSettings[0].DefaultGateways = $Gateway + $NetworkSettings[0].DNSServers = $DNSServers + $NetworkSettings[0].Subnets = $Subnet + $NetworkSettings[0].ProtocolIFType = 4096 + $NetworkSettings[0].DHCPEnabled = $false + + + $Service = Get-WmiObject -Class 'Msvm_VirtualSystemManagementService' -Namespace 'root\virtualization\v2' + $setIP = $Service.SetGuestNetworkAdapterConfiguration($vm, $NetworkSettings[0].GetText(1)) + + if ($setIP.ReturnValue -eq 4096) + { + $job = [WMI]$setIP.job + + while ($job.JobState -eq 3 -or $job.JobState -eq 4) + { + Start-Sleep 1 + $job = [WMI]$setIP.job + } + + if ($job.JobState -ne 7) + { + $job.GetError() + } + } + (Get-VM -Id $NetworkAdapter.VmId).NetworkAdapter | Select-Object Name, IpAddress +` + var ps powershell.PowerShellCmd + err := ps.Run(script, vmName, IPAddress, Gateway, DNSServer, Subnet) + return err +} From 1a6e37a7f03d528b89b272fd8fdc83bd7690d74b Mon Sep 17 00:00:00 2001 From: gaelcolas <gaelcolas@users.noreply.github.com> Date: Wed, 5 Oct 2016 15:08:15 +0100 Subject: [PATCH 112/113] reverting comment-out setpConfigureIP part --- builder/hyperv/common/step_configure_ip.go | 97 +++++++++++----------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/builder/hyperv/common/step_configure_ip.go b/builder/hyperv/common/step_configure_ip.go index cf0a6c08083..96ea5300b59 100644 --- a/builder/hyperv/common/step_configure_ip.go +++ b/builder/hyperv/common/step_configure_ip.go @@ -6,6 +6,9 @@ package common import ( "fmt" + "log" + "strings" + "time" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" @@ -33,53 +36,53 @@ func (s *StepConfigureIP) Run(state multistep.StateBag) multistep.StepAction { ui.Error(err.Error()) return multistep.ActionHalt } - /* - count := 60 - var duration time.Duration = 1 - sleepTime := time.Minute * duration - var ip string - - for count != 0 { - cmdOut, err := driver.GetVirtualMachineNetworkAdapterAddress(vmName) - if err != nil { - err := fmt.Errorf(errorMsg, err) - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ip = strings.TrimSpace(string(cmdOut)) - - if ip != "False" { - break - } - - log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration))) - time.Sleep(sleepTime) - count-- - } - - if count == 0 { - err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty") - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ui.Say("ip address is " + ip) - - hostName, err := driver.GetHostName(ip) - if err != nil { - state.Put("error", err) - ui.Error(err.Error()) - return multistep.ActionHalt - } - - ui.Say("hostname is " + hostName) - - state.Put("ip", ip) - state.Put("hostname", hostName) - */ + + count := 60 + var duration time.Duration = 1 + sleepTime := time.Minute * duration + var ip string + + for count != 0 { + cmdOut, err := driver.GetVirtualMachineNetworkAdapterAddress(vmName) + if err != nil { + err := fmt.Errorf(errorMsg, err) + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ip = strings.TrimSpace(string(cmdOut)) + + if ip != "False" { + break + } + + log.Println(fmt.Sprintf("Waiting for another %v minutes...", uint(duration))) + time.Sleep(sleepTime) + count-- + } + + if count == 0 { + err := fmt.Errorf(errorMsg, "IP address assigned to the adapter is empty") + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("ip address is " + ip) + + hostName, err := driver.GetHostName(ip) + if err != nil { + state.Put("error", err) + ui.Error(err.Error()) + return multistep.ActionHalt + } + + ui.Say("hostname is " + hostName) + + state.Put("ip", ip) + state.Put("hostname", hostName) + return multistep.ActionContinue } From fcf895ed0cdd996dfb6b014bc088d31bcd47bc9e Mon Sep 17 00:00:00 2001 From: gaelcolas <gaelcolas@users.noreply.github.com> Date: Wed, 5 Oct 2016 16:37:55 +0100 Subject: [PATCH 113/113] Adding documentation to set ip support. --- website/source/docs/builders/hyperv-iso.html.markdown | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/source/docs/builders/hyperv-iso.html.markdown b/website/source/docs/builders/hyperv-iso.html.markdown index 062f6bd7988..621ee74952a 100644 --- a/website/source/docs/builders/hyperv-iso.html.markdown +++ b/website/source/docs/builders/hyperv-iso.html.markdown @@ -145,6 +145,16 @@ can be configured for this builder. five seconds and one minute 30 seconds, respectively. If this isn't specified, the default is 10 seconds. +- `ip_address` (string) - Static IP address to be set to the VM. This is particulary useful when you + don't have a DHCP server available, like when you are using a NAT switch in HyperV. + +- `subnet_mask` (string) - Subnet mask of the IP address set for the virtual machine. + +- `gateway` (string) - Default Gateway of the virtual machine + +- `dns_server` (string) - DNS server IP address for name resolution by the VM, need to be + accessible from the virtual network. + - `iso_urls` (array of strings) - Multiple URLs for the ISO to download. Packer will try these in order. If anything goes wrong attempting to download or while downloading a single URL, it will move on to the next. All URLs