Merge pull request #5872 from DanHam/ps-combined

PowerShell - Combined and updated PR's 5515 and 5376
This commit is contained in:
SwampDragons 2018-02-08 16:33:11 -08:00 committed by GitHub
commit a0c1f4e59d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 387 additions and 91 deletions

View File

@ -34,6 +34,7 @@ func init() {
"amazon-shutdown_behavior": new(FixerAmazonShutdownBehavior),
"amazon-enhanced-networking": new(FixerAmazonEnhancedNetworking),
"docker-email": new(FixerDockerEmail),
"powershell-escapes": new(FixerPowerShellEscapes),
}
FixerOrder = []string{
@ -51,5 +52,6 @@ func init() {
"amazon-shutdown_behavior",
"amazon-enhanced-networking",
"docker-email",
"powershell-escapes",
}
}

View File

@ -0,0 +1,73 @@
package fix
import (
"github.com/mitchellh/mapstructure"
"strings"
)
// FixerPowerShellEscapes removes the PowerShell escape character from user
// environment variables and elevated username and password strings
type FixerPowerShellEscapes struct{}
func (FixerPowerShellEscapes) Fix(input map[string]interface{}) (map[string]interface{}, error) {
type template struct {
Provisioners []interface{}
}
var psUnescape = strings.NewReplacer(
"`$", "$",
"`\"", "\"",
"``", "`",
"`'", "'",
)
// Decode the input into our structure, if we can
var tpl template
if err := mapstructure.WeakDecode(input, &tpl); err != nil {
return nil, err
}
for i, raw := range tpl.Provisioners {
var provisioners map[string]interface{}
if err := mapstructure.Decode(raw, &provisioners); err != nil {
// Ignore errors, could be a non-map
continue
}
if ok := provisioners["type"] == "powershell"; !ok {
continue
}
if _, ok := provisioners["elevated_user"]; ok {
provisioners["elevated_user"] = psUnescape.Replace(provisioners["elevated_user"].(string))
}
if _, ok := provisioners["elevated_password"]; ok {
provisioners["elevated_password"] = psUnescape.Replace(provisioners["elevated_password"].(string))
}
if raw, ok := provisioners["environment_vars"]; ok {
var env_vars []string
if err := mapstructure.Decode(raw, &env_vars); err != nil {
continue
}
env_vars_unescaped := make([]interface{}, len(env_vars))
for j, env_var := range env_vars {
env_vars_unescaped[j] = psUnescape.Replace(env_var)
}
// Replace with unescaped environment variables
provisioners["environment_vars"] = env_vars_unescaped
}
// Write all changes back to template
tpl.Provisioners[i] = provisioners
}
if len(tpl.Provisioners) > 0 {
input["provisioners"] = tpl.Provisioners
}
return input, nil
}
func (FixerPowerShellEscapes) Synopsis() string {
return `Removes PowerShell escapes from user env vars and elevated username and password strings`
}

View File

@ -24,6 +24,13 @@ import (
var retryableSleep = 2 * time.Second
var psEscape = strings.NewReplacer(
"$", "`$",
"\"", "`\"",
"`", "``",
"'", "`'",
)
type Config struct {
common.PackerConfig `mapstructure:",squash"`
@ -113,7 +120,7 @@ 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 == "" {
@ -121,7 +128,7 @@ func (p *Provisioner) Prepare(raws ...interface{}) error {
}
if p.config.ExecuteCommand == "" {
p.config.ExecuteCommand = `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode }"`
p.config.ExecuteCommand = `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}';exit $LastExitCode }"`
}
if p.config.ElevatedExecuteCommand == "" {
@ -331,6 +338,19 @@ func (p *Provisioner) retryable(f func() error) error {
}
}
// Enviroment variables required within the remote environment are uploaded within a PS script and
// then enabled by 'dot sourcing' the script immediately prior to execution of the main command
func (p *Provisioner) prepareEnvVars(elevated bool) (envVarPath string, err error) {
// Collate all required env vars into a plain string with required formatting applied
flattenedEnvVars := p.createFlattenedEnvVars(elevated)
// Create a powershell script on the target build fs containing the flattened env vars
envVarPath, err = p.uploadEnvVars(flattenedEnvVars)
if err != nil {
return "", err
}
return
}
func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) {
flattened = ""
envVars := make(map[string]string)
@ -346,7 +366,13 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) {
// Split vars into key/value components
for _, envVar := range p.config.Vars {
keyValue := strings.SplitN(envVar, "=", 2)
envVars[keyValue[0]] = keyValue[1]
// Escape chars special to PS in each env var value
escapedEnvVarValue := psEscape.Replace(keyValue[1])
if escapedEnvVarValue != keyValue[1] {
log.Printf("Env var %s converted to %s after escaping chars special to PS", keyValue[1],
escapedEnvVarValue)
}
envVars[keyValue[0]] = escapedEnvVarValue
}
// Create a list of env var keys in sorted order
@ -367,6 +393,19 @@ func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) {
return
}
func (p *Provisioner) uploadEnvVars(flattenedEnvVars string) (envVarPath string, err error) {
// Upload all env vars to a powershell script on the target build file system
envVarReader := strings.NewReader(flattenedEnvVars)
uuid := uuid.TimeOrderedUUID()
envVarPath = fmt.Sprintf(`${env:SYSTEMROOT}\Temp\packer-env-vars-%s.ps1`, uuid)
log.Printf("Uploading env vars to %s", envVarPath)
err = p.communicator.Upload(envVarPath, envVarReader, nil)
if err != nil {
return "", fmt.Errorf("Error uploading ps script containing env vars: %s", err)
}
return
}
func (p *Provisioner) createCommandText() (command string, err error) {
// Return the interpolated command
if p.config.ElevatedUser == "" {
@ -377,12 +416,15 @@ func (p *Provisioner) createCommandText() (command string, err error) {
}
func (p *Provisioner) createCommandTextNonPrivileged() (command string, err error) {
// Create environment variables to set before executing the command
flattenedEnvVars := p.createFlattenedEnvVars(false)
// Prepare everything needed to enable the required env vars within the remote environment
envVarPath, err := p.prepareEnvVars(false)
if err != nil {
return "", err
}
p.config.ctx.Data = &ExecuteCommandTemplate{
Vars: flattenedEnvVars,
Path: p.config.RemotePath,
Vars: envVarPath,
}
command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx)
@ -395,17 +437,10 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro
}
func (p *Provisioner) createCommandTextPrivileged() (command string, err error) {
// Can't double escape the env vars, lets create shiny new ones
flattenedEnvVars := p.createFlattenedEnvVars(true)
// Need to create a mini ps1 script containing all of the environment variables we want;
// we'll be dot-sourcing this later
envVarReader := strings.NewReader(flattenedEnvVars)
uuid := uuid.TimeOrderedUUID()
envVarPath := fmt.Sprintf(`${env:SYSTEMROOT}\Temp\packer-env-vars-%s.ps1`, uuid)
log.Printf("Uploading env vars to %s", envVarPath)
err = p.communicator.Upload(envVarPath, envVarReader, nil)
// Prepare everything needed to enable the required env vars within the remote environment
envVarPath, err := p.prepareEnvVars(true)
if err != nil {
return "", fmt.Errorf("Error preparing elevated powershell script: %s", err)
return "", err
}
p.config.ctx.Data = &ExecuteCommandTemplate{
@ -458,13 +493,26 @@ func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath strin
}
escapedCommand := buffer.String()
log.Printf("Command [%s] converted to [%s] for use in XML string", command, escapedCommand)
buffer.Reset()
// Escape chars special to PowerShell in the ElevatedUser string
escapedElevatedUser := psEscape.Replace(p.config.ElevatedUser)
if escapedElevatedUser != p.config.ElevatedUser {
log.Printf("Elevated user %s converted to %s after escaping chars special to PowerShell",
p.config.ElevatedUser, escapedElevatedUser)
}
// Escape chars special to PowerShell in the ElevatedPassword string
escapedElevatedPassword := psEscape.Replace(p.config.ElevatedPassword)
if escapedElevatedPassword != p.config.ElevatedPassword {
log.Printf("Elevated password %s converted to %s after escaping chars special to PowerShell",
p.config.ElevatedPassword, escapedElevatedPassword)
}
// Generate command
err = elevatedTemplate.Execute(&buffer, elevatedOptions{
User: p.config.ElevatedUser,
Password: p.config.ElevatedPassword,
User: escapedElevatedUser,
Password: escapedElevatedPassword,
TaskName: taskName,
TaskDescription: "Packer elevated task",
LogFile: logFile,

View File

@ -79,8 +79,8 @@ func TestProvisionerPrepare_Defaults(t *testing.T) {
t.Error("expected elevated_password to be empty")
}
if p.config.ExecuteCommand != `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode }"` {
t.Fatalf(`Default command should be 'powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode }"', but got '%s'`, p.config.ExecuteCommand)
if p.config.ExecuteCommand != `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}';exit $LastExitCode }"` {
t.Fatalf(`Default command should be 'powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}';exit $LastExitCode }"', but got '%s'`, p.config.ExecuteCommand)
}
if p.config.ElevatedExecuteCommand != `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit $LastExitCode }"` {
@ -403,7 +403,7 @@ func TestProvisionerProvision_Inline(t *testing.T) {
ui := testUi()
p := new(Provisioner)
// Defaults provided by Packer
// Defaults provided by Packer - env vars should not appear in cmd
p.config.PackerBuildName = "vmware"
p.config.PackerBuilderType = "iso"
comm := new(packer.MockCommunicator)
@ -413,11 +413,14 @@ func TestProvisionerProvision_Inline(t *testing.T) {
t.Fatal("should not have error")
}
expectedCommand := `powershell -executionpolicy bypass "& { 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 }"`
if comm.StartCmd.Command != expectedCommand {
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
cmd := comm.StartCmd.Command
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}\\Temp\\packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/inlineScript.ps1';exit \$LastExitCode }"`)
matched := re.MatchString(cmd)
if !matched {
t.Fatalf("Got unexpected command: %s", cmd)
}
// User supplied env vars should not change things
envVars := make([]string, 2)
envVars[0] = "FOO=BAR"
envVars[1] = "BAR=BAZ"
@ -430,9 +433,11 @@ func TestProvisionerProvision_Inline(t *testing.T) {
t.Fatal("should not have error")
}
expectedCommand = `powershell -executionpolicy bypass "& { 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 }"`
if comm.StartCmd.Command != expectedCommand {
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
cmd = comm.StartCmd.Command
re = regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}\\Temp\\packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/inlineScript.ps1';exit \$LastExitCode }"`)
matched = re.MatchString(cmd)
if !matched {
t.Fatalf("Got unexpected command: %s", cmd)
}
}
@ -455,11 +460,12 @@ func TestProvisionerProvision_Scripts(t *testing.T) {
t.Fatal("should not have error")
}
expectedCommand := `powershell -executionpolicy bypass "& { 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 }"`
if comm.StartCmd.Command != expectedCommand {
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
cmd := comm.StartCmd.Command
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}\\Temp\\packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
matched := re.MatchString(cmd)
if !matched {
t.Fatalf("Got unexpected command: %s", cmd)
}
}
func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) {
@ -488,9 +494,11 @@ func TestProvisionerProvision_ScriptsWithEnvVars(t *testing.T) {
t.Fatal("should not have error")
}
expectedCommand := `powershell -executionpolicy bypass "& { 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 }"`
if comm.StartCmd.Command != expectedCommand {
t.Fatalf("Expect command to be: %s, got %s", expectedCommand, comm.StartCmd.Command)
cmd := comm.StartCmd.Command
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}\\Temp\\packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
matched := re.MatchString(cmd)
if !matched {
t.Fatalf("Got unexpected command: %s", cmd)
}
}
@ -510,6 +518,12 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) {
{"FOO=bar", "BAZ=qux"}, // Multiple user env vars
{"FOO=bar=baz"}, // User env var with value containing equals
{"FOO==bar"}, // User env var with value starting with equals
// Test escaping of characters special to PowerShell
{"FOO=bar$baz"}, // User env var with value containing dollar
{"FOO=bar\"baz"}, // User env var with value containing a double quote
{"FOO=bar'baz"}, // User env var with value containing a single quote
{"FOO=bar`baz"}, // User env var with value containing a backtick
}
expected := []string{
`$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
@ -517,6 +531,10 @@ func TestProvisioner_createFlattenedElevatedEnvVars_windows(t *testing.T) {
`$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
`$env:FOO="bar=baz"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
`$env:FOO="=bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
"$env:FOO=\"bar`$baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
"$env:FOO=\"bar`\"baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
"$env:FOO=\"bar`'baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
"$env:FOO=\"bar``baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
}
p := new(Provisioner)
@ -545,13 +563,22 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) {
{"FOO=bar", "BAZ=qux"}, // Multiple user env vars
{"FOO=bar=baz"}, // User env var with value containing equals
{"FOO==bar"}, // User env var with value starting with equals
// Test escaping of characters special to PowerShell
{"FOO=bar$baz"}, // User env var with value containing dollar
{"FOO=bar\"baz"}, // User env var with value containing a double quote
{"FOO=bar'baz"}, // User env var with value containing a single quote
{"FOO=bar`baz"}, // User env var with value containing a backtick
}
expected := []string{
`$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; `,
`$env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; `,
`$env:BAZ=\"qux\"; $env:FOO=\"bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; `,
`$env:FOO=\"bar=baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; `,
`$env:FOO=\"=bar\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; `,
`$env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
`$env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
`$env:BAZ="qux"; $env:FOO="bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
`$env:FOO="bar=baz"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
`$env:FOO="=bar"; $env:PACKER_BUILDER_TYPE="iso"; $env:PACKER_BUILD_NAME="vmware"; `,
"$env:FOO=\"bar`$baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
"$env:FOO=\"bar`\"baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
"$env:FOO=\"bar`'baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
"$env:FOO=\"bar``baz\"; $env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; ",
}
p := new(Provisioner)
@ -571,7 +598,6 @@ func TestProvisioner_createFlattenedEnvVars_windows(t *testing.T) {
}
func TestProvision_createCommandText(t *testing.T) {
config := testConfig()
config["remote_path"] = "c:/Windows/Temp/script.ps1"
p := new(Provisioner)
@ -586,22 +612,46 @@ func TestProvision_createCommandText(t *testing.T) {
// Non-elevated
cmd, _ := p.createCommandText()
expectedCommand := `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};$env:PACKER_BUILDER_TYPE=\"iso\"; $env:PACKER_BUILD_NAME=\"vmware\"; &'c:/Windows/Temp/script.ps1';exit $LastExitCode }"`
if cmd != expectedCommand {
t.Fatalf("Expected Non-elevated command: %s, got %s", expectedCommand, cmd)
re := regexp.MustCompile(`powershell -executionpolicy bypass "& { if \(Test-Path variable:global:ProgressPreference\){\$ProgressPreference='SilentlyContinue'};\. \${env:SYSTEMROOT}\\Temp\\packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1; &'c:/Windows/Temp/script.ps1';exit \$LastExitCode }"`)
matched := re.MatchString(cmd)
if !matched {
t.Fatalf("Got unexpected command: %s", cmd)
}
// Elevated
p.config.ElevatedUser = "vagrant"
p.config.ElevatedPassword = "vagrant"
cmd, _ = p.createCommandText()
matched, _ := regexp.MatchString("powershell -executionpolicy bypass -file \"%TEMP%(.{1})packer-elevated-shell.*", cmd)
re = regexp.MustCompile(`powershell -executionpolicy bypass -file "%TEMP%\\packer-elevated-shell-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1"`)
matched = re.MatchString(cmd)
if !matched {
t.Fatalf("Got unexpected elevated command: %s", cmd)
}
}
func TestProvision_uploadEnvVars(t *testing.T) {
p := new(Provisioner)
comm := new(packer.MockCommunicator)
p.communicator = comm
flattenedEnvVars := `$env:PACKER_BUILDER_TYPE="footype"; $env:PACKER_BUILD_NAME="foobuild";`
envVarPath, err := p.uploadEnvVars(flattenedEnvVars)
if err != nil {
t.Fatalf("Did not expect error: %s", err.Error())
}
if comm.UploadCalled != true {
t.Fatalf("Failed to upload env var file")
}
re := regexp.MustCompile(`\${env:SYSTEMROOT}\\Temp\\packer-env-vars-[[:alnum:]]{8}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{4}-[[:alnum:]]{12}\.ps1`)
matched := re.MatchString(envVarPath)
if !matched {
t.Fatalf("Got unexpected path for env var file: %s", envVarPath)
}
}
func TestProvision_generateElevatedShellRunner(t *testing.T) {
// Non-elevated

View File

@ -1,8 +1,8 @@
---
description: |
The shell Packer provisioner provisions machines built by Packer using shell
scripts. Shell provisioning is the easiest way to get software installed and
configured on a machine.
The PowerShell Packer provisioner runs PowerShell scripts on Windows
machines.
It assumes that the communicator in use is WinRM.
layout: docs
page_title: 'PowerShell - Provisioners'
sidebar_current: 'docs-provisioners-powershell'
@ -29,20 +29,21 @@ The example below is fully functional.
## Configuration Reference
The reference of available configuration options is listed below. The only
required element is either "inline" or "script". Every other option is optional.
required element is either "inline" or "script". Every other option is
optional.
Exactly *one* of the following is required:
- `inline` (array of strings) - This is an array of commands to execute. The
commands are concatenated by newlines and turned into a single file, so they
are all executed within the same context. This allows you to change
commands are concatenated by newlines and turned into a single file, so
they are all executed within the same context. This allows you to change
directories in one command and use something in the directory in the next
and so on. Inline scripts are the easiest way to pull off simple tasks
within the machine.
- `script` (string) - The path to a script to upload and execute in
the machine. This path can be absolute or relative. If it is relative, it is
relative to the working directory when Packer is executed.
the machine. This path can be absolute or relative. If it is relative, it
is relative to the working directory when Packer is executed.
- `scripts` (array of strings) - An array of scripts to execute. The scripts
will be uploaded and executed in the order specified. Each script is
@ -51,12 +52,12 @@ Exactly *one* of the following is required:
Optional parameters:
- `binary` (boolean) - If true, specifies that the script(s) are binary files,
and Packer should therefore not convert Windows line endings to Unix line
endings (if there are any). By default this is false.
- `binary` (boolean) - If true, specifies that the script(s) are binary
files, and Packer should therefore not convert Windows line endings to Unix
line endings (if there are any). By default this is false.
- `elevated_execute_command` (string) - The command to use to execute the elevated
script. By default this is as follows:
- `elevated_execute_command` (string) - The command to use to execute the
elevated script. By default this is as follows:
``` powershell
powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit $LastExitCode }"
@ -65,32 +66,34 @@ Optional parameters:
The value of this is treated as [configuration
template](/docs/templates/engine.html). There are two
available variables: `Path`, which is the path to the script to run, and
`Vars`, which is the location of a temp file containing the list of `environment_vars`, if configured.
`Vars`, which is the location of a temp file containing the list of
`environment_vars`, if configured.
- `environment_vars` (array of strings) - An array of key/value pairs to
inject prior to the execute\_command. The format should be `key=value`.
Packer injects some environmental variables by default into the environment,
as well, which are covered in the section below.
Packer injects some environmental variables by default into the
environment, as well, which are covered in the section below.
- `execute_command` (string) - The command to use to execute the script. By
default this is as follows:
``` powershell
powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};{{.Vars}}&'{{.Path}}';exit $LastExitCode }"
powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit $LastExitCode }"
```
The value of this is treated as [configuration
template](/docs/templates/engine.html). There are two
available variables: `Path`, which is the path to the script to run, and
`Vars`, which is the list of `environment_vars`, if configured.
`Vars`, which is the location of a temp file containing the list of
`environment_vars`, if configured.
- `elevated_user` and `elevated_password` (string) - If specified, the
PowerShell script will be run with elevated privileges using the given
Windows user.
- `remote_path` (string) - The path where the script will be uploaded to in
the machine. This defaults to "c:/Windows/Temp/script.ps1". This value must be a
writable location and any parent directories must already exist.
the machine. This defaults to "c:/Windows/Temp/script.ps1". This value must
be a writable location and any parent directories must already exist.
- `start_retry_timeout` (string) - The amount of time to attempt to *start*
the remote process. By default this is "5m" or 5 minutes. This setting
@ -111,9 +114,10 @@ commonly useful environmental variables:
This is most useful when Packer is making multiple builds and you want to
distinguish them slightly from a common provisioning script.
- `PACKER_BUILDER_TYPE` is the type of the builder that was used to create the
machine that the script is running on. This is useful if you want to run
only certain parts of the script on systems built with certain builders.
- `PACKER_BUILDER_TYPE` is the type of the builder that was used to create
the machine that the script is running on. This is useful if you want to
run only certain parts of the script on systems built with certain
builders.
- `PACKER_HTTP_ADDR` If using a builder that provides an http server for file
transfer (such as hyperv, parallels, qemu, virtualbox, and vmware), this
@ -121,3 +125,101 @@ commonly useful environmental variables:
download large files over http. This may be useful if you're experiencing
slower speeds using the default file provisioner. A file provisioner using
the `winrm` communicator may experience these types of difficulties.
## Packer's Handling of Characters Special to PowerShell
The escape character in PowerShell is the `backtick`, also sometimes
referred to as the `grave accent`. When, and when not, to escape characters
special to PowerShell is probably best demonstrated with a series of examples.
### When To Escape...
Users need to deal with escaping characters special to PowerShell when they
appear *directly* in commands used in the `inline` PowerShell provisioner and
when they appear *directly* in the users own scripts.
Note that where double quotes appear within double quotes, the addition of
a backslash escape is required for the JSON template to be parsed correctly.
``` json
"provisioners": [
{
"type": "powershell",
"inline": [
"Write-Host \"A literal dollar `$ must be escaped\"",
"Write-Host \"A literal backtick `` must be escaped\"",
"Write-Host \"Here `\"double quotes`\" must be escaped\"",
"Write-Host \"Here `'single quotes`' don`'t really need to be\"",
"Write-Host \"escaped... but it doesn`'t hurt to do so.\"",
]
},
```
The above snippet should result in the following output on the Packer console:
```
==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with powershell script: /var/folders/15/d0f7gdg13rnd1cxp7tgmr55c0000gn/T/packer-powershell-provisioner508190439
amazon-ebs: A literal dollar $ must be escaped
amazon-ebs: A literal backtick ` must be escaped
amazon-ebs: Here "double quotes" must be escaped
amazon-ebs: Here 'single quotes' don't really need to be
amazon-ebs: escaped... but it doesn't hurt to do so.
```
### When Not To Escape...
Special characters appearing in user environment variable values and in the
`elevated_user` and `elevated_password` fields will be automatically
dealt with for the user. There is no need to use escapes in these instances.
``` json
{
"variables": {
"psvar": "My$tring"
},
...
"provisioners": [
{
"type": "powershell",
"elevated_user": "Administrator",
"elevated_password": "Super$3cr3t!",
"inline": "Write-Output \"The dollar in the elevated_password is interpreted correctly\""
},
{
"type": "powershell",
"environment_vars": [
"VAR1=A$Dollar",
"VAR2=A`Backtick",
"VAR3=A'SingleQuote",
"VAR4=A\"DoubleQuote",
"VAR5={{user `psvar`}}"
],
"inline": [
"Write-Output \"In the following examples the special character is interpreted correctly:\"",
"Write-Output \"The dollar in VAR1: $Env:VAR1\"",
"Write-Output \"The backtick in VAR2: $Env:VAR2\"",
"Write-Output \"The single quote in VAR3: $Env:VAR3\"",
"Write-Output \"The double quote in VAR4: $Env:VAR4\"",
"Write-Output \"The dollar in VAR5 (expanded from a user var): $Env:VAR5\""
]
}
]
...
}
```
The above snippet should result in the following output on the Packer console:
```
==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with powershell script: /var/folders/15/d0f7gdg13rnd1cxp7tgmr55c0000gn/T/packer-powershell-provisioner961728919
amazon-ebs: The dollar in the elevated_password is interpreted correctly
==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with powershell script: /var/folders/15/d0f7gdg13rnd1cxp7tgmr55c0000gn/T/packer-powershell-provisioner142826554
amazon-ebs: In the following examples the special character is interpreted correctly:
amazon-ebs: The dollar in VAR1: A$Dollar
amazon-ebs: The backtick in VAR2: A`Backtick
amazon-ebs: The single quote in VAR3: A'SingleQuote
amazon-ebs: The double quote in VAR4: A"DoubleQuote
amazon-ebs: The dollar in VAR5 (expanded from a user var): My$tring
```

View File

@ -448,14 +448,19 @@ variables we will set in our build template; copy the contents into your own
`sample_script.ps1` and provide the path to it in your build template:
```powershell
Write-Host "PACKER_BUILD_NAME is automatically set for you, " -NoNewline
Write-Host "or you can set it in your builder variables; " -NoNewline
Write-Host "PACKER_BUILD_NAME is an env var Packer automatically sets for you."
Write-Host "...or you can set it in your builder variables."
Write-Host "The default for this builder is:" $Env:PACKER_BUILD_NAME
Write-Host "Use backticks as the escape character when required in powershell:"
Write-Host "The PowerShell provisioner will automatically escape characters"
Write-Host "considered special to PowerShell when it encounters them in"
Write-Host "your environment variables or in the PowerShell elevated"
Write-Host "username/password fields."
Write-Host "For example, VAR1 from our config is:" $Env:VAR1
Write-Host "Likewise, VAR2 is:" $Env:VAR2
Write-Host "Finally, VAR3 is:" $Env:VAR3
Write-Host "VAR3 is:" $Env:VAR3
Write-Host "Finally, VAR4 is:" $Env:VAR4
Write-Host "None of the special characters needed escaping in the template"
```
Finally, we need to create the actual [build template](
@ -514,7 +519,12 @@ customize and control the build process:
{
"type": "powershell",
"environment_vars": ["DEVOPS_LIFE_IMPROVER=PACKER"],
"inline": "Write-Host \"HELLO NEW USER; WELCOME TO $Env:DEVOPS_LIFE_IMPROVER\""
"inline": [
"Write-Host \"HELLO NEW USER; WELCOME TO $Env:DEVOPS_LIFE_IMPROVER\"",
"Write-Host \"You need to use backtick escapes when using\"",
"Write-Host \"characters such as DOLLAR`$ directly in a command\"",
"Write-Host \"or in your own scripts.\""
]
},
{
"type": "windows-restart"
@ -523,9 +533,10 @@ customize and control the build process:
"script": "./sample_script.ps1",
"type": "powershell",
"environment_vars": [
"VAR1=A`$Dollar",
"VAR2=A``Backtick",
"VAR3=A`'SingleQuote"
"VAR1=A$Dollar",
"VAR2=A`Backtick",
"VAR3=A'SingleQuote",
"VAR4=A\"DoubleQuote"
]
}
]
@ -550,39 +561,49 @@ You should see output like this:
```
amazon-ebs output will be in this color.
==> amazon-ebs: Prevalidating AMI Name: packer-demo-1507933843
amazon-ebs: Found Image ID: ami-23d93c59
==> amazon-ebs: Creating temporary keypair: packer_59e13e94-203a-1bca-5327-bebf0d5ad15a
==> amazon-ebs: Creating temporary security group for this instance: packer_59e13ea9-3220-8dab-29c0-ed7f71e221a1
==> amazon-ebs: Prevalidating AMI Name: packer-demo-1518111383
amazon-ebs: Found Image ID: ami-013e197b
==> amazon-ebs: Creating temporary keypair: packer_5a7c8a97-f27f-6708-cc3c-6ab9b4688b13
==> amazon-ebs: Creating temporary security group for this instance: packer_5a7c8ab5-444c-13f2-0aa1-18d124cdb975
==> amazon-ebs: Authorizing access to port 5985 from 0.0.0.0/0 in the temporary security group...
==> amazon-ebs: Launching a source AWS instance...
==> amazon-ebs: Adding tags to source instance
amazon-ebs: Adding tag: "Name": "Packer Builder"
amazon-ebs: Instance ID: i-0349406ac85f02166
==> amazon-ebs: Waiting for instance (i-0349406ac85f02166) to become ready...
amazon-ebs: Instance ID: i-0c8c808a3b945782a
==> amazon-ebs: Waiting for instance (i-0c8c808a3b945782a) to become ready...
==> amazon-ebs: Skipping waiting for password since WinRM password set...
==> amazon-ebs: Waiting for WinRM to become available...
amazon-ebs: WinRM connected.
==> amazon-ebs: Connected to WinRM!
==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with powershell script: /var/folders/15/d0f7gdg13rnd1cxp7tgmr55c0000gn/T/packer-powershell-provisioner175214995
==> amazon-ebs: Provisioning with powershell script: /var/folders/15/d0f7gdg13rnd1cxp7tgmr55c0000gn/T/packer-powershell-provisioner943573503
amazon-ebs: HELLO NEW USER; WELCOME TO PACKER
amazon-ebs: You need to use backtick escapes when using
amazon-ebs: characters such as DOLLAR$ directly in a command
amazon-ebs: or in your own scripts.
==> amazon-ebs: Restarting Machine
==> amazon-ebs: Waiting for machine to restart...
amazon-ebs: WIN-TEM0TDL751M restarted.
amazon-ebs: WIN-NI8N45RPJ23 restarted.
==> amazon-ebs: Machine successfully restarted, moving on
==> amazon-ebs: Provisioning with Powershell...
==> amazon-ebs: Provisioning with powershell script: ./sample_script.ps1
amazon-ebs: PACKER_BUILD_NAME is automatically set for you, or you can set it in your builder variables; The default for this builder is: amazon-ebs
amazon-ebs: Use backticks as the escape character when required in powershell:
amazon-ebs: PACKER_BUILD_NAME is an env var Packer automatically sets for you.
amazon-ebs: ...or you can set it in your builder variables.
amazon-ebs: The default for this builder is: amazon-ebs
amazon-ebs: The PowerShell provisioner will automatically escape characters
amazon-ebs: considered special to PowerShell when it encounters them in
amazon-ebs: your environment variables or in the PowerShell elevated
amazon-ebs: username/password fields.
amazon-ebs: For example, VAR1 from our config is: A$Dollar
amazon-ebs: Likewise, VAR2 is: A`Backtick
amazon-ebs: Finally, VAR3 is: A'SingleQuote
amazon-ebs: VAR3 is: A'SingleQuote
amazon-ebs: Finally, VAR4 is: A"DoubleQuote
amazon-ebs: None of the special characters needed escaping in the template
==> amazon-ebs: Stopping the source instance...
amazon-ebs: Stopping instance, attempt 1
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: packer-demo-1507933843
amazon-ebs: AMI: ami-100fc56a
==> amazon-ebs: Creating the AMI: packer-demo-1518111383
amazon-ebs: AMI: ami-f0060c8a
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
@ -593,7 +614,7 @@ Build 'amazon-ebs' finished.
==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
us-east-1: ami-100fc56a
us-east-1: ami-f0060c8a
```
And if you navigate to your EC2 dashboard you should see your shiny new AMI