github.com/hashicorp/packer@v1.14.3/provisioner/powershell/provisioner_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package powershell
     5  
     6  import (
     7  	"bytes"
     8  	"context"
     9  	"fmt"
    10  	"os"
    11  	"regexp"
    12  	"strings"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/hashicorp/packer-plugin-sdk/common"
    17  	"github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps"
    18  	packersdk "github.com/hashicorp/packer-plugin-sdk/packer"
    19  	"github.com/stretchr/testify/assert"
    20  )
    21  
    22  func TestProvisionerPrepare_extractScript(t *testing.T) {
    23  	config := testConfig()
    24  	p := new(Provisioner)
    25  	_ = p.Prepare(config)
    26  	p.generatedData = generatedData()
    27  	file, err := extractInlineScript(p)
    28  	defer os.Remove(file)
    29  	if err != nil {
    30  		t.Fatalf("Should not be error: %s", err)
    31  	}
    32  	t.Logf("File: %s", file)
    33  	if strings.Index(file, os.TempDir()) != 0 {
    34  		t.Fatalf("Temp file should reside in %s. File location: %s", os.TempDir(), file)
    35  	}
    36  
    37  	// File contents should contain 2 lines concatenated by newlines: foo\nbar
    38  	readFile, err := os.ReadFile(file)
    39  	expectedContents := "if (Test-Path variable:global:ProgressPreference) {\n         set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'\n         }\n         \n         $exitCode = 0\n         try {\n         $env:PACKER_BUILDER_TYPE=\"\"; $env:PACKER_BUILD_NAME=\"\"; \n         foo\n         bar\n         \n         $exitCode = 0\n         } catch {\n         Write-Error \"An error occurred: $_\"\n         $exitCode = 1\n         }\n         \n         if ($LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0) {\n         $exitCode = $LASTEXITCODE\n         }\n         exit $exitCode"
    40  	normalizedExpectedContent := normalizeWhiteSpace(expectedContents)
    41  	if err != nil {
    42  		t.Fatalf("Should not be error: %s", err)
    43  	}
    44  	s := string(readFile[:])
    45  	normalizedString := normalizeWhiteSpace(s)
    46  	if normalizedString != normalizedExpectedContent {
    47  		t.Fatalf("Expected generated inlineScript to equal '%s', got '%s'", normalizedExpectedContent, normalizedString)
    48  	}
    49  }
    50  
    51  func TestProvisioner_Impl(t *testing.T) {
    52  	var raw interface{}
    53  	raw = &Provisioner{}
    54  	if _, ok := raw.(packersdk.Provisioner); !ok {
    55  		t.Fatalf("must be a Provisioner")
    56  	}
    57  }
    58  
    59  func TestProvisionerPrepare_Defaults(t *testing.T) {
    60  	var p Provisioner
    61  	config := testConfig()
    62  
    63  	err := p.Prepare(config)
    64  	if err != nil {
    65  		t.Fatalf("err: %s", err)
    66  	}
    67  
    68  	matched, _ := regexp.MatchString("c:/Windows/Temp/script-.*.ps1", p.config.RemotePath)
    69  	if !matched {
    70  		t.Errorf("unexpected remote path: %s", p.config.RemotePath)
    71  	}
    72  
    73  	if p.config.ElevatedUser != "" {
    74  		t.Error("expected elevated_user to be empty")
    75  	}
    76  	if p.config.ElevatedPassword != "" {
    77  		t.Error("expected elevated_password to be empty")
    78  	}
    79  
    80  	if p.config.ExecuteCommand != `powershell -executionpolicy bypass -file {{.Path}}` {
    81  		t.Fatalf(`Default command should be 'powershell -executionpolicy bypass -file {{.Path}}', but got '%s'`, p.config.ExecuteCommand)
    82  	}
    83  
    84  	if p.config.ElevatedExecuteCommand != `powershell -executionpolicy bypass -file {{.Path}}` {
    85  		t.Fatalf(`Default command should be 'powershell -executionpolicy bypass -file {{.Path}}', but got '%s'`, p.config.ElevatedExecuteCommand)
    86  	}
    87  
    88  	if p.config.ElevatedEnvVarFormat != `$env:%s="%s"; ` {
    89  		t.Fatalf(`Default command should be powershell '$env:%%s="%%s"; ', but got %s`, p.config.ElevatedEnvVarFormat)
    90  	}
    91  }
    92  
    93  func TestProvisionerPrepare_Config(t *testing.T) {
    94  	config := testConfig()
    95  	config["elevated_user"] = "{{user `user`}}"
    96  	config["elevated_password"] = "{{user `password`}}"
    97  	config[common.UserVariablesConfigKey] = map[string]string{
    98  		"user":     "myusername",
    99  		"password": "mypassword",
   100  	}
   101  
   102  	var p Provisioner
   103  	err := p.Prepare(config)
   104  	if err != nil {
   105  		t.Fatalf("err: %s", err)
   106  	}
   107  
   108  	if p.config.ElevatedUser != "myusername" {
   109  		t.Fatalf("Expected 'myusername' for key `elevated_user`: %s", p.config.ElevatedUser)
   110  	}
   111  	if p.config.ElevatedPassword != "mypassword" {
   112  		t.Fatalf("Expected 'mypassword' for key `elevated_password`: %s", p.config.ElevatedPassword)
   113  	}
   114  }
   115  
   116  func TestProvisionerPrepare_DebugMode(t *testing.T) {
   117  	config := testConfig()
   118  	config["debug_mode"] = 1
   119  
   120  	var p Provisioner
   121  	err := p.Prepare(config)
   122  	if err != nil {
   123  		t.Fatalf("err: %s", err)
   124  	}
   125  
   126  	command := `powershell -executionpolicy bypass -file {{.Path}}`
   127  	if p.config.ExecuteCommand != command {
   128  		t.Fatalf(`Expected command should be '%s' but got '%s'`, command, p.config.ExecuteCommand)
   129  	}
   130  }
   131  
   132  func TestProvisionerPrepare_InvalidDebugMode(t *testing.T) {
   133  	config := testConfig()
   134  	config["debug_mode"] = -1
   135  
   136  	var p Provisioner
   137  	err := p.Prepare(config)
   138  	if err == nil {
   139  		t.Fatalf("should have error")
   140  	}
   141  
   142  	message := "invalid Trace level for `debug_mode`; valid values are 0, 1, and 2"
   143  	if !strings.Contains(err.Error(), message) {
   144  		t.Fatalf("expected Prepare() error %q to contain %q", err.Error(), message)
   145  	}
   146  }
   147  
   148  func TestProvisionerPrepare_InvalidKey(t *testing.T) {
   149  	var p Provisioner
   150  	config := testConfig()
   151  
   152  	// Add a random key
   153  	config["i_should_not_be_valid"] = true
   154  	err := p.Prepare(config)
   155  	if err == nil {
   156  		t.Fatal("should have error")
   157  	}
   158  }
   159  
   160  func TestProvisionerPrepare_Elevated(t *testing.T) {
   161  	var p Provisioner
   162  	config := testConfig()
   163  
   164  	// Add a random key
   165  	config["elevated_user"] = "vagrant"
   166  	err := p.Prepare(config)
   167  
   168  	if err != nil {
   169  		t.Fatal("should not have error")
   170  	}
   171  
   172  	config["elevated_password"] = "vagrant"
   173  	err = p.Prepare(config)
   174  
   175  	if err != nil {
   176  		t.Fatal("should not have error")
   177  	}
   178  }
   179  
   180  func TestProvisionerPrepare_Script(t *testing.T) {
   181  	config := testConfig()
   182  	delete(config, "inline")
   183  
   184  	config["script"] = "/this/should/not/exist"
   185  	p := new(Provisioner)
   186  	err := p.Prepare(config)
   187  	if err == nil {
   188  		t.Fatal("should have error")
   189  	}
   190  
   191  	// Test with a good one
   192  	tf, err := os.CreateTemp("", "packer")
   193  	if err != nil {
   194  		t.Fatalf("error tempfile: %s", err)
   195  	}
   196  	defer os.Remove(tf.Name())
   197  	defer tf.Close()
   198  
   199  	config["script"] = tf.Name()
   200  	p = new(Provisioner)
   201  	err = p.Prepare(config)
   202  	if err != nil {
   203  		t.Fatalf("should not have error: %s", err)
   204  	}
   205  }
   206  
   207  func TestProvisionerPrepare_ScriptAndInline(t *testing.T) {
   208  	var p Provisioner
   209  	config := testConfig()
   210  
   211  	delete(config, "inline")
   212  	delete(config, "script")
   213  	err := p.Prepare(config)
   214  	if err == nil {
   215  		t.Fatal("should have error")
   216  	}
   217  
   218  	// Test with both
   219  	tf, err := os.CreateTemp("", "packer")
   220  	if err != nil {
   221  		t.Fatalf("error tempfile: %s", err)
   222  	}
   223  	defer os.Remove(tf.Name())
   224  	defer tf.Close()
   225  
   226  	config["inline"] = []interface{}{"foo"}
   227  	config["script"] = tf.Name()
   228  	err = p.Prepare(config)
   229  	if err == nil {
   230  		t.Fatal("should have error")
   231  	}
   232  }
   233  
   234  func TestProvisionerPrepare_ScriptAndScripts(t *testing.T) {
   235  	var p Provisioner
   236  	config := testConfig()
   237  
   238  	// Test with both
   239  	tf, err := os.CreateTemp("", "packer")
   240  	if err != nil {
   241  		t.Fatalf("error tempfile: %s", err)
   242  	}
   243  	defer os.Remove(tf.Name())
   244  	defer tf.Close()
   245  
   246  	config["inline"] = []interface{}{"foo"}
   247  	config["scripts"] = []string{tf.Name()}
   248  	err = p.Prepare(config)
   249  	if err == nil {
   250  		t.Fatal("should have error")
   251  	}
   252  }
   253  
   254  func TestProvisionerPrepare_Scripts(t *testing.T) {
   255  	config := testConfig()
   256  	delete(config, "inline")
   257  
   258  	config["scripts"] = []string{}
   259  	p := new(Provisioner)
   260  	err := p.Prepare(config)
   261  	if err == nil {
   262  		t.Fatal("should have error")
   263  	}
   264  
   265  	// Test with a good one
   266  	tf, err := os.CreateTemp("", "packer")
   267  	if err != nil {
   268  		t.Fatalf("error tempfile: %s", err)
   269  	}
   270  	defer os.Remove(tf.Name())
   271  	defer tf.Close()
   272  
   273  	config["scripts"] = []string{tf.Name()}
   274  	p = new(Provisioner)
   275  	err = p.Prepare(config)
   276  	if err != nil {
   277  		t.Fatalf("should not have error: %s", err)
   278  	}
   279  }
   280  
   281  func TestProvisionerPrepare_Pwsh(t *testing.T) {
   282  
   283  	config := testConfig()
   284  
   285  	config["use_pwsh"] = true
   286  
   287  	p := new(Provisioner)
   288  	err := p.Prepare(config)
   289  
   290  	if err != nil {
   291  		t.Fatalf("Should not be error: %s", err)
   292  	}
   293  
   294  	if !p.config.UsePwsh {
   295  		t.Fatalf("Expected 'pwsh' to be: true")
   296  	}
   297  }
   298  
   299  func TestProvisionerPrepare_EnvironmentVars(t *testing.T) {
   300  	config := testConfig()
   301  
   302  	// Test with a bad case
   303  	config["environment_vars"] = []string{"badvar", "good=var"}
   304  	p := new(Provisioner)
   305  	err := p.Prepare(config)
   306  	if err == nil {
   307  		t.Fatal("should have error")
   308  	}
   309  
   310  	// Test with a trickier case
   311  	config["environment_vars"] = []string{"=bad"}
   312  	p = new(Provisioner)
   313  	err = p.Prepare(config)
   314  	if err == nil {
   315  		t.Fatal("should have error")
   316  	}
   317  
   318  	// Test with a good case
   319  	// Note: baz= is a real env variable, just empty
   320  	config["environment_vars"] = []string{"FOO=bar", "baz="}
   321  	p = new(Provisioner)
   322  	err = p.Prepare(config)
   323  	if err != nil {
   324  		t.Fatalf("should not have error: %s", err)
   325  	}
   326  
   327  	// Test when the env variable value contains an equals sign
   328  	config["environment_vars"] = []string{"good=withequals=true"}
   329  	p = new(Provisioner)
   330  	err = p.Prepare(config)
   331  	if err != nil {
   332  		t.Fatalf("should not have error: %s", err)
   333  	}
   334  
   335  	// Test when the env variable value starts with an equals sign
   336  	config["environment_vars"] = []string{"good==true"}
   337  	p = new(Provisioner)
   338  	err = p.Prepare(config)
   339  	if err != nil {
   340  		t.Fatalf("should not have error: %s", err)
   341  	}
   342  
   343  }
   344  
   345  func TestProvisionerQuote_EnvironmentVars(t *testing.T) {
   346  	config := testConfig()
   347  
   348  	config["environment_vars"] = []string{
   349  		"keyone=valueone",
   350  		"keytwo=value\ntwo",
   351  		"keythree='valuethree'",
   352  		"keyfour='value\nfour'",
   353  		"keyfive='value=five'",
   354  		"keysix='=six'",
   355  	}
   356  
   357  	expected := []string{
   358  		"keyone=valueone",
   359  		"keytwo=value\ntwo",
   360  		"keythree='valuethree'",
   361  		"keyfour='value\nfour'",
   362  		"keyfive='value=five'",
   363  		"keysix='=six'",
   364  	}
   365  
   366  	p := new(Provisioner)
   367  	p.Prepare(config)
   368  
   369  	for i, expectedValue := range expected {
   370  		if p.config.Vars[i] != expectedValue {
   371  			t.Fatalf("%s should be equal to %s", p.config.Vars[i], expectedValue)
   372  		}
   373  	}
   374  }
   375  
   376  func testUi() *packersdk.BasicUi {
   377  	return &packersdk.BasicUi{
   378  		Reader:      new(bytes.Buffer),
   379  		Writer:      new(bytes.Buffer),
   380  		ErrorWriter: new(bytes.Buffer),
   381  	}
   382  }
   383  
   384  func TestProvisionerProvision_ValidExitCodes(t *testing.T) {
   385  	config := testConfig()
   386  	delete(config, "inline")
   387  
   388  	// Defaults provided by Packer
   389  	config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1"
   390  	config["inline"] = []string{"whoami"}
   391  	ui := testUi()
   392  	p := new(Provisioner)
   393  
   394  	// Defaults provided by Packer
   395  	p.config.PackerBuildName = "vmware"
   396  	p.config.PackerBuilderType = "iso"
   397  	p.config.ValidExitCodes = []int{0, 200}
   398  	comm := new(packersdk.MockCommunicator)
   399  	comm.StartExitStatus = 200
   400  	p.Prepare(config)
   401  	err := p.Provision(context.Background(), ui, comm, generatedData())
   402  	if err != nil {
   403  		t.Fatal("should not have error")
   404  	}
   405  }
   406  
   407  func TestProvisionerProvision_PauseAfter(t *testing.T) {
   408  	config := testConfig()
   409  	delete(config, "inline")
   410  
   411  	// Defaults provided by Packer
   412  	config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1"
   413  	config["inline"] = []string{"whoami"}
   414  	ui := testUi()
   415  	p := new(Provisioner)
   416  
   417  	// Defaults provided by Packer
   418  	p.config.PackerBuildName = "vmware"
   419  	p.config.PackerBuilderType = "iso"
   420  	p.config.ValidExitCodes = []int{0, 200}
   421  	pause_amount := time.Second
   422  	p.config.PauseAfter = pause_amount
   423  	comm := new(packersdk.MockCommunicator)
   424  	comm.StartExitStatus = 200
   425  	err := p.Prepare(config)
   426  	if err != nil {
   427  		t.Fatalf("Prepar failed: %s", err)
   428  	}
   429  
   430  	start_time := time.Now()
   431  	err = p.Provision(context.Background(), ui, comm, generatedData())
   432  	end_time := time.Now()
   433  
   434  	if err != nil {
   435  		t.Fatal("should not have error")
   436  	}
   437  
   438  	if end_time.Sub(start_time) < pause_amount {
   439  		t.Fatal("Didn't wait pause_amount")
   440  	}
   441  }
   442  
   443  func TestProvisionerProvision_InvalidExitCodes(t *testing.T) {
   444  	config := testConfig()
   445  	delete(config, "inline")
   446  
   447  	// Defaults provided by Packer
   448  	config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1"
   449  	config["inline"] = []string{"whoami"}
   450  	ui := testUi()
   451  	p := new(Provisioner)
   452  
   453  	// Defaults provided by Packer
   454  	p.config.PackerBuildName = "vmware"
   455  	p.config.PackerBuilderType = "iso"
   456  	p.config.ValidExitCodes = []int{0, 200}
   457  	comm := new(packersdk.MockCommunicator)
   458  	comm.StartExitStatus = 201 // Invalid!
   459  	p.Prepare(config)
   460  	err := p.Provision(context.Background(), ui, comm, generatedData())
   461  	if err == nil {
   462  		t.Fatal("should have error")
   463  	}
   464  }
   465  
   466  func TestProvisionerProvision_Inline(t *testing.T) {
   467  	// skip_clean is set to true otherwise the last command executed by the provisioner is the cleanup.
   468  	config := testConfigWithSkipClean()
   469  	delete(config, "inline")
   470  
   471  	// Defaults provided by Packer
   472  	config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1"
   473  	config["inline"] = []string{"whoami"}
   474  	ui := testUi()
   475  	p := new(Provisioner)
   476  
   477  	// Defaults provided by Packer - env vars should not appear in cmd
   478  	p.config.PackerBuildName = "vmware"
   479  	p.config.PackerBuilderType = "iso"
   480  	comm := new(packersdk.MockCommunicator)
   481  	_ = p.Prepare(config)
   482  
   483  	err := p.Provision(context.Background(), ui, comm, generatedData())
   484  	if err != nil {
   485  		t.Fatal("should not have error")
   486  	}
   487  
   488  	cmd := comm.StartCmd.Command
   489  	re := regexp.MustCompile(`powershell -executionpolicy bypass -file c:/Windows/Temp/inlineScript.ps1`)
   490  
   491  	matched := re.MatchString(cmd)
   492  	if !matched {
   493  		t.Fatalf("Got unexpected command: %s", cmd)
   494  	}
   495  
   496  	// User supplied env vars should not change things
   497  	envVars := make([]string, 2)
   498  	envVars[0] = "FOO=BAR"
   499  	envVars[1] = "BAR=BAZ"
   500  	config["environment_vars"] = envVars
   501  	config["remote_path"] = "c:/Windows/Temp/inlineScript.ps1"
   502  
   503  	p.Prepare(config)
   504  	err = p.Provision(context.Background(), ui, comm, generatedData())
   505  	if err != nil {
   506  		t.Fatal("should not have error")
   507  	}
   508  
   509  	cmd = comm.StartCmd.Command
   510  	re = regexp.MustCompile(`powershell -executionpolicy bypass -file c:/Windows/Temp/inlineScript.ps1`)
   511  	matched = re.MatchString(cmd)
   512  	if !matched {
   513  		t.Fatalf("Got unexpected command: %s", cmd)
   514  	}
   515  }
   516  
   517  func TestProvisionerProvision_Scripts(t *testing.T) {
   518  	tempFile, _ := os.CreateTemp("", "packer")
   519  	defer os.Remove(tempFile.Name())
   520  	defer tempFile.Close()
   521  
   522  	// skip_clean is set to true otherwise the last command executed by the provisioner is the cleanup.
   523  	config := testConfigWithSkipClean()
   524  	delete(config, "inline")
   525  	config["scripts"] = []string{tempFile.Name()}
   526  	config["packer_build_name"] = "foobuild"
   527  	config["packer_builder_type"] = "footype"
   528  	config["remote_path"] = "c:/Windows/Temp/script.ps1"
   529  	ui := testUi()
   530  
   531  	p := new(Provisioner)
   532  	comm := new(packersdk.MockCommunicator)
   533  	p.Prepare(config)
   534  	err := p.Provision(context.Background(), ui, comm, generatedData())
   535  	if err != nil {
   536  		t.Fatal("should not have error")
   537  	}
   538  
   539  	cmd := comm.StartCmd.Command
   540  	re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1'; exit \$LastExitCode }"`)
   541  	matched := re.MatchString(cmd)
   542  	if !matched {
   543  		t.Fatalf("Got unexpected command: %s", cmd)
   544  	}
   545  }
   546  
   547  func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) {
   548  	tempFile, _ := os.CreateTemp("", "packer")
   549  	ui := testUi()
   550  	defer os.Remove(tempFile.Name())
   551  	defer tempFile.Close()
   552  
   553  	// skip_clean is set to true otherwise the last command executed by the provisioner is the cleanup.
   554  	config := testConfigWithSkipClean()
   555  	delete(config, "inline")
   556  
   557  	config["scripts"] = []string{tempFile.Name()}
   558  	config["packer_build_name"] = "foobuild"
   559  	config["packer_builder_type"] = "footype"
   560  
   561  	// Env vars - currently should not effect them
   562  	envVars := make([]string, 2)
   563  	envVars[0] = "FOO=BAR"
   564  	envVars[1] = "BAR=BAZ"
   565  	config["environment_vars"] = envVars
   566  	config["remote_path"] = "c:/Windows/Temp/script.ps1"
   567  
   568  	p := new(Provisioner)
   569  	comm := new(packersdk.MockCommunicator)
   570  	p.Prepare(config)
   571  	err := p.Provision(context.Background(), ui, comm, generatedData())
   572  	if err != nil {
   573  		t.Fatal("should not have error")
   574  	}
   575  
   576  	cmd := comm.StartCmd.Command
   577  	re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1'; exit \$LastExitCode }"`)
   578  	matched := re.MatchString(cmd)
   579  	if !matched {
   580  		t.Fatalf("Got unexpected command: %s", cmd)
   581  	}
   582  }
   583  
   584  func TestProvisionerProvision_SkipClean(t *testing.T) {
   585  	tempFile, _ := os.CreateTemp("", "packer")
   586  	defer func() {
   587  		tempFile.Close()
   588  		os.Remove(tempFile.Name())
   589  	}()
   590  
   591  	config := map[string]interface{}{
   592  		"scripts":     []string{tempFile.Name()},
   593  		"remote_path": "c:/Windows/Temp/script.ps1",
   594  	}
   595  
   596  	tt := []struct {
   597  		SkipClean                bool
   598  		LastExecutedCommandRegex string
   599  	}{
   600  		{
   601  			SkipClean:                true,
   602  			LastExecutedCommandRegex: `powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1'; exit \$LastExitCode }"`,
   603  		},
   604  		{
   605  			SkipClean:                false,
   606  			LastExecutedCommandRegex: `powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};\. c:/Windows/Temp/packer-ps-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/packer-cleanup-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1'; exit \$LastExitCode }"`,
   607  		},
   608  	}
   609  
   610  	for _, tc := range tt {
   611  		tc := tc
   612  		p := new(Provisioner)
   613  		ui := testUi()
   614  		comm := new(packersdk.MockCommunicator)
   615  
   616  		config["skip_clean"] = tc.SkipClean
   617  		if err := p.Prepare(config); err != nil {
   618  			t.Fatalf("failed to prepare config when SkipClean is %t: %s", tc.SkipClean, err)
   619  		}
   620  		err := p.Provision(context.Background(), ui, comm, generatedData())
   621  		if err != nil {
   622  			t.Fatal("should not have error")
   623  		}
   624  
   625  		// When SkipClean is false the last executed command should be the clean up command;
   626  		// otherwise it will be the execution command for the provisioning script.
   627  		cmd := comm.StartCmd.Command
   628  		re := regexp.MustCompile(tc.LastExecutedCommandRegex)
   629  		matched := re.MatchString(cmd)
   630  		if !matched {
   631  			t.Fatalf(`Got unexpected command when SkipClean is %t: %s`, tc.SkipClean, cmd)
   632  		}
   633  	}
   634  }
   635  
   636  func TestProvisionerProvision_UploadFails(t *testing.T) {
   637  	config := testConfig()
   638  	ui := testUi()
   639  
   640  	p := new(Provisioner)
   641  	comm := new(packersdk.ScriptUploadErrorMockCommunicator)
   642  	p.Prepare(config)
   643  	p.config.StartRetryTimeout = 1 * time.Second
   644  	err := p.Provision(context.Background(), ui, comm, generatedData())
   645  	if !strings.Contains(err.Error(), packersdk.ScriptUploadErrorMockCommunicatorError.Error()) {
   646  		t.Fatalf("expected Provision() error %q to contain %q",
   647  			err.Error(),
   648  			packersdk.ScriptUploadErrorMockCommunicatorError.Error())
   649  	}
   650  }
   651  
   652  func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) {
   653  	var flattenedEnvVars string
   654  	config := testConfig()
   655  
   656  	userEnvVarTests := [][]string{
   657  		{},                     // No user env var
   658  		{"FOO=bar"},            // Single user env var
   659  		{"FOO=bar", "BAZ=qux"}, // Multiple user env vars
   660  		{"FOO=bar=baz"},        // User env var with value containing equals
   661  		{"FOO==bar"},           // User env var with value starting with equals
   662  		// Test escaping of characters special to PowerShell
   663  		{"FOO=bar$baz"},  // User env var with value containing dollar
   664  		{"FOO=bar\"baz"}, // User env var with value containing a double quote
   665  		{"FOO=bar'baz"},  // User env var with value containing a single quote
   666  		{"FOO=bar`baz"},  // User env var with value containing a backtick
   667  
   668  	}
   669  	userEnvVarmapTests := []map[string]string{
   670  		{},
   671  		{
   672  			"BAR": "foo",
   673  		},
   674  		{
   675  			"BAR": "foo",
   676  			"YAR": "yaa",
   677  		},
   678  		{
   679  			"BAR": "foo=yaa",
   680  		},
   681  		{
   682  			"BAR": "=foo",
   683  		},
   684  		{
   685  			"BAR": "foo$yaa",
   686  		},
   687  		{
   688  			"BAR": "foo\"yaa",
   689  		},
   690  		{
   691  			"BAR": "foo'yaa",
   692  		},
   693  		{
   694  			"BAR": "foo`yaa",
   695  		},
   696  	}
   697  	expected := []string{
   698  		`$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
   699  		`$env:BAR="foo"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
   700  		`$env:BAR="foo"; $env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; $env:YAR="yaa"; `,
   701  		`$env:BAR="foo=yaa"; $env:FOO="bar=baz"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
   702  		`$env:BAR="=foo"; $env:FOO="=bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
   703  		"$env:BAR=\"foo`$yaa\"; $env:FOO=\"bar`$baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
   704  		"$env:BAR=\"foo`\"yaa\"; $env:FOO=\"bar`\"baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
   705  		"$env:BAR=\"foo`'yaa\"; $env:FOO=\"bar`'baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
   706  		"$env:BAR=\"foo``yaa\"; $env:FOO=\"bar``baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
   707  	}
   708  
   709  	p := new(Provisioner)
   710  	p.generatedData = generatedData()
   711  	p.Prepare(config)
   712  
   713  	// Defaults provided by Packer
   714  	p.config.PackerBuildName = "vmware"
   715  	p.config.PackerBuilderType = "iso"
   716  
   717  	for i, expectedValue := range expected {
   718  		p.config.Vars = userEnvVarTests[i]
   719  		p.config.Env = userEnvVarmapTests[i]
   720  		flattenedEnvVars = p.createFlattenedEnvVars(true)
   721  		if flattenedEnvVars != expectedValue {
   722  			t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars)
   723  		}
   724  	}
   725  }
   726  
   727  func TestProvisionerCorrectlyInterpolatesValidExitCodes(t *testing.T) {
   728  	type testCases struct {
   729  		Input    interface{}
   730  		Expected []int
   731  	}
   732  	validExitCodeTests := []testCases{
   733  		{"0", []int{0}},
   734  		{[]string{"0"}, []int{0}},
   735  		{[]int{0, 12345}, []int{0, 12345}},
   736  		{[]string{"0", "12345"}, []int{0, 12345}},
   737  		{"0,12345", []int{0, 12345}},
   738  	}
   739  
   740  	for _, tc := range validExitCodeTests {
   741  		p := new(Provisioner)
   742  		config := testConfig()
   743  		config["valid_exit_codes"] = tc.Input
   744  		err := p.Prepare(config)
   745  
   746  		if err != nil {
   747  			t.Fatalf("Shouldn't have had error interpolating exit codes")
   748  		}
   749  		assert.ElementsMatchf(t, p.config.ValidExitCodes, tc.Expected,
   750  			fmt.Sprintf("expected exit codes to be: %#v, got %#v.", p.config.ValidExitCodes, tc.Expected))
   751  	}
   752  }
   753  
   754  func TestProvisionerCorrectlyInterpolatesExecutionPolicy(t *testing.T) {
   755  	type testCases struct {
   756  		Input       interface{}
   757  		Expected    ExecutionPolicy
   758  		ErrExpected bool
   759  	}
   760  	tests := []testCases{
   761  		{
   762  			Input:       "bypass",
   763  			Expected:    ExecutionPolicy(0),
   764  			ErrExpected: false,
   765  		},
   766  		{
   767  			Input:       "allsigned",
   768  			Expected:    ExecutionPolicy(1),
   769  			ErrExpected: false,
   770  		},
   771  		{
   772  			Input:       "default",
   773  			Expected:    ExecutionPolicy(2),
   774  			ErrExpected: false,
   775  		},
   776  		{
   777  			Input:       "remotesigned",
   778  			Expected:    ExecutionPolicy(3),
   779  			ErrExpected: false,
   780  		},
   781  		{
   782  			Input:       "restricted",
   783  			Expected:    ExecutionPolicy(4),
   784  			ErrExpected: false,
   785  		},
   786  		{
   787  			Input:       "undefined",
   788  			Expected:    ExecutionPolicy(5),
   789  			ErrExpected: false,
   790  		},
   791  		{
   792  			Input:       "unrestricted",
   793  			Expected:    ExecutionPolicy(6),
   794  			ErrExpected: false,
   795  		},
   796  		{
   797  			Input:       "none",
   798  			Expected:    ExecutionPolicy(7),
   799  			ErrExpected: false,
   800  		},
   801  		{
   802  			Input:       "0", // User can supply a valid number for policy, too
   803  			Expected:    0,
   804  			ErrExpected: false,
   805  		},
   806  		{
   807  			Input:       "invalid",
   808  			Expected:    0,
   809  			ErrExpected: true,
   810  		},
   811  		{
   812  			Input:       "100", // If number is invalid policy, reject.
   813  			Expected:    100,
   814  			ErrExpected: true,
   815  		},
   816  	}
   817  
   818  	for _, tc := range tests {
   819  		p := new(Provisioner)
   820  		config := testConfig()
   821  		config["execution_policy"] = tc.Input
   822  		err := p.Prepare(config)
   823  
   824  		if (err != nil) != tc.ErrExpected {
   825  			t.Fatalf("Either err was expected, or shouldn't have happened: %#v", tc)
   826  		}
   827  		if err == nil {
   828  			assert.Equal(t, p.config.ExecutionPolicy, tc.Expected,
   829  				fmt.Sprintf("expected %#v, got %#v.", p.config.ExecutionPolicy, tc.Expected))
   830  		}
   831  	}
   832  }
   833  
   834  func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) {
   835  	var flattenedEnvVars string
   836  	config := testConfig()
   837  
   838  	userEnvVarTests := [][]string{
   839  		{},                     // No user env var
   840  		{"FOO=bar"},            // Single user env var
   841  		{"FOO=bar", "BAZ=qux"}, // Multiple user env vars
   842  		{"FOO=bar=baz"},        // User env var with value containing equals
   843  		{"FOO==bar"},           // User env var with value starting with equals
   844  		// Test escaping of characters special to PowerShell
   845  		{"FOO=bar$baz"},  // User env var with value containing dollar
   846  		{"FOO=bar\"baz"}, // User env var with value containing a double quote
   847  		{"FOO=bar'baz"},  // User env var with value containing a single quote
   848  		{"FOO=bar`baz"},  // User env var with value containing a backtick
   849  	}
   850  	userEnvVarmapTests := []map[string]string{
   851  		{},
   852  		{
   853  			"BAR": "foo",
   854  		},
   855  		{
   856  			"BAR": "foo",
   857  			"YAR": "yaa",
   858  		},
   859  		{
   860  			"BAR": "foo=yaa",
   861  		},
   862  		{
   863  			"BAR": "=foo",
   864  		},
   865  		{
   866  			"BAR": "foo$yaa",
   867  		},
   868  		{
   869  			"BAR": "foo\"yaa",
   870  		},
   871  		{
   872  			"BAR": "foo'yaa",
   873  		},
   874  		{
   875  			"BAR": "foo`yaa",
   876  		},
   877  	}
   878  	expected := []string{
   879  		`$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
   880  		`$env:BAR="foo"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
   881  		`$env:BAR="foo"; $env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; $env:YAR="yaa"; `,
   882  		`$env:BAR="foo=yaa"; $env:FOO="bar=baz"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
   883  		`$env:BAR="=foo"; $env:FOO="=bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
   884  		"$env:BAR=\"foo`$yaa\"; $env:FOO=\"bar`$baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
   885  		"$env:BAR=\"foo`\"yaa\"; $env:FOO=\"bar`\"baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
   886  		"$env:BAR=\"foo`'yaa\"; $env:FOO=\"bar`'baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
   887  		"$env:BAR=\"foo``yaa\"; $env:FOO=\"bar``baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
   888  	}
   889  
   890  	p := new(Provisioner)
   891  	p.generatedData = generatedData()
   892  	p.Prepare(config)
   893  
   894  	// Defaults provided by Packer
   895  	p.config.PackerBuildName = "vmware"
   896  	p.config.PackerBuilderType = "iso"
   897  
   898  	for i, expectedValue := range expected {
   899  		p.config.Vars = userEnvVarTests[i]
   900  		p.config.Env = userEnvVarmapTests[i]
   901  		flattenedEnvVars = p.createFlattenedEnvVars(false)
   902  		if flattenedEnvVars != expectedValue {
   903  			t.Fatalf("expected flattened env vars to be: %s, got %s.", expectedValue, flattenedEnvVars)
   904  		}
   905  	}
   906  }
   907  
   908  func TestProvision_createCommandText(t *testing.T) {
   909  	config := testConfig()
   910  	config["remote_path"] = "c:/Windows/Temp/script.ps1"
   911  	p := new(Provisioner)
   912  	comm := new(packersdk.MockCommunicator)
   913  	p.communicator = comm
   914  	_ = p.Prepare(config)
   915  
   916  	// Defaults provided by Packer
   917  	p.config.PackerBuildName = "vmware"
   918  	p.config.PackerBuilderType = "iso"
   919  
   920  	// Non-elevated
   921  	p.generatedData = make(map[string]interface{})
   922  	cmd, _ := p.createCommandText()
   923  
   924  	re := regexp.MustCompile(`powershell -executionpolicy bypass -file c:/Windows/Temp/script.ps1`)
   925  	matched := re.MatchString(cmd)
   926  	if !matched {
   927  		t.Fatalf("Got unexpected command: %s", cmd)
   928  	}
   929  
   930  	// Elevated
   931  	p.config.ElevatedUser = "vagrant"
   932  	p.config.ElevatedPassword = "vagrant"
   933  	cmd, _ = p.createCommandText()
   934  	re = regexp.MustCompile(`powershell -executionpolicy bypass -file "C:/Windows/Temp/packer-elevated-shell-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1"`)
   935  	matched = re.MatchString(cmd)
   936  	if !matched {
   937  		t.Fatalf("Got unexpected elevated command: %s", cmd)
   938  	}
   939  }
   940  
   941  func TestProvision_createCommandTextNoneExecutionPolicy(t *testing.T) {
   942  	config := testConfig()
   943  	config["remote_path"] = "c:/Windows/Temp/script.ps1"
   944  	p := new(Provisioner)
   945  
   946  	comm := new(packersdk.MockCommunicator)
   947  	p.communicator = comm
   948  	config["execution_policy"] = ExecutionPolicyNone
   949  	_ = p.Prepare(config)
   950  
   951  	// Non-elevated
   952  	p.generatedData = make(map[string]interface{})
   953  
   954  	cmd, _ := p.createCommandText()
   955  	re := regexp.MustCompile(`-file c:/Windows/Temp/script.ps1`)
   956  	matched := re.MatchString(cmd)
   957  	if !matched {
   958  		t.Fatalf("Got unexpected command: %s", cmd)
   959  	}
   960  
   961  }
   962  
   963  func TestProvision_uploadEnvVars(t *testing.T) {
   964  	p := new(Provisioner)
   965  	comm := new(packersdk.MockCommunicator)
   966  	p.communicator = comm
   967  
   968  	flattenedEnvVars := `$env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild";`
   969  
   970  	err := p.uploadEnvVars(flattenedEnvVars)
   971  	if err != nil {
   972  		t.Fatalf("Did not expect error: %s", err.Error())
   973  	}
   974  
   975  	if comm.UploadCalled != true {
   976  		t.Fatalf("Failed to upload env var file")
   977  	}
   978  }
   979  
   980  func TestCancel(t *testing.T) {
   981  	// Don't actually call Cancel() as it performs an os.Exit(0)
   982  	// which kills the 'go test' tool
   983  }
   984  
   985  func testConfig() map[string]interface{} {
   986  	return map[string]interface{}{
   987  		"inline": []interface{}{"foo", "bar"},
   988  	}
   989  }
   990  
   991  func testConfigWithSkipClean() map[string]interface{} {
   992  	return map[string]interface{}{
   993  		"inline":     []interface{}{"foo", "bar"},
   994  		"skip_clean": true,
   995  	}
   996  }
   997  
   998  func generatedData() map[string]interface{} {
   999  	return map[string]interface{}{
  1000  		"PackerHTTPAddr": commonsteps.HttpAddrNotImplemented,
  1001  		"PackerHTTPIP":   commonsteps.HttpIPNotImplemented,
  1002  		"PackerHTTPPort": commonsteps.HttpPortNotImplemented,
  1003  	}
  1004  }
  1005  
  1006  func normalizeWhiteSpace(s string) string {
  1007  	// Replace multiple spaces/tabs with a single space
  1008  	re := regexp.MustCompile(`[\t ]+`)
  1009  	s = re.ReplaceAllString(s, " ")
  1010  
  1011  	// Trim leading/trailing spaces and newlines
  1012  	s = strings.TrimSpace(s)
  1013  
  1014  	// Normalize line breaks (remove excessive empty lines)
  1015  	s = strings.ReplaceAll(s, "\r\n", "\n") // Convert Windows line endings to Unix
  1016  	s = strings.ReplaceAll(s, "\r", "\n")   // Convert old Mac line endings to Unix
  1017  
  1018  	return s
  1019  }