From 2a060adbf80ab454cb4c322ae5754228b56e67de Mon Sep 17 00:00:00 2001 From: DanHam Date: Fri, 27 Jan 2017 01:32:33 +0000 Subject: [PATCH] Don't use -EncodedCommand with PS as progress stream always leaks to stderr * Setting $ProgressPreference to SilentlyContinue makes no difference when -EncodedCommand is used - any output to the progress stream still appears on stderr. * Delete file containing encode/decode functions since we no longer need them. * Fixes leak of output on progress streams for both normal and elevated commands. * Since we no longer base64 encode, ensure any characters special to XML are correctly escaped in the elevated command. This ensures correct parsing once the command is wrapped within the elevatedTemplates XML based Task Scheduler definition. Fixes #4322 --- provisioner/powershell/elevated.go | 12 +++--- provisioner/powershell/powershell.go | 54 --------------------------- provisioner/powershell/provisioner.go | 50 ++++++++++--------------- 3 files changed, 26 insertions(+), 90 deletions(-) delete mode 100644 provisioner/powershell/powershell.go diff --git a/provisioner/powershell/elevated.go b/provisioner/powershell/elevated.go index d2c23f757..f8adb6cd1 100644 --- a/provisioner/powershell/elevated.go +++ b/provisioner/powershell/elevated.go @@ -5,11 +5,11 @@ import ( ) type elevatedOptions struct { - User string - Password string - TaskName string - TaskDescription string - EncodedCommand string + User string + Password string + TaskName string + TaskDescription string + XMLEscapedCommand string } var elevatedTemplate = template.Must(template.New("ElevatedCommand").Parse(` @@ -53,7 +53,7 @@ $t.XmlText = @' cmd - /c powershell.exe -EncodedCommand {{.EncodedCommand}} > %SYSTEMROOT%\Temp\{{.TaskName}}.out 2>&1 + /c {{.XMLEscapedCommand}} > %SYSTEMROOT%\Temp\{{.TaskName}}.out 2>&1 diff --git a/provisioner/powershell/powershell.go b/provisioner/powershell/powershell.go deleted file mode 100644 index 086e3e554..000000000 --- a/provisioner/powershell/powershell.go +++ /dev/null @@ -1,54 +0,0 @@ -package powershell - -import ( - "encoding/base64" - "encoding/binary" - "unicode/utf16" - "unicode/utf8" - - "golang.org/x/text/encoding/unicode" -) - -func convertUtf8ToUtf16LE(message string) (string, error) { - utf16le := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM) - utfEncoder := utf16le.NewEncoder() - ut16LeEncodedMessage, err := utfEncoder.String(message) - - 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) { - utf16LEEncodedMessage, err := convertUtf8ToUtf16LE(message) - if err != nil { - return "", err - } - - // Base64 encode the command - input := []uint8(utf16LEEncodedMessage) - return base64.StdEncoding.EncodeToString(input), nil -} - -func powershellDecode(messageBase64 string) (retour string, err error) { - messageUtf16LeByteArray, err := base64.StdEncoding.DecodeString(messageBase64) - - if err != nil { - return "", err - } - - message := UTF16BytesToString(messageUtf16LeByteArray, binary.LittleEndian) - - return message, nil -} diff --git a/provisioner/powershell/provisioner.go b/provisioner/powershell/provisioner.go index fd967ce86..1481d6ddc 100644 --- a/provisioner/powershell/provisioner.go +++ b/provisioner/powershell/provisioner.go @@ -5,6 +5,7 @@ package powershell import ( "bufio" "bytes" + "encoding/xml" "errors" "fmt" "io/ioutil" @@ -112,7 +113,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 == "" { @@ -120,11 +121,11 @@ func (p *Provisioner) Prepare(raws ...interface{}) error { } if p.config.ExecuteCommand == "" { - p.config.ExecuteCommand = `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 == "" { - p.config.ElevatedExecuteCommand = `if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'}; . {{.Vars}}; &'{{.Path}}'; exit $LastExitCode` + p.config.ElevatedExecuteCommand = `powershell -executionpolicy bypass "& { if (Test-Path variable:global:ProgressPreference){$ProgressPreference='SilentlyContinue'};. {{.Vars}}; &'{{.Path}}'; exit $LastExitCode }"` } if p.config.Inline != nil && len(p.config.Inline) == 0 { @@ -389,25 +390,8 @@ func (p *Provisioner) createCommandTextNonPrivileged() (command string, err erro return "", fmt.Errorf("Error processing command: %s", err) } - commandText, err := p.generateCommandLineRunner(command) - if err != nil { - return "", fmt.Errorf("Error generating command line runner: %s", 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(command) - if err != nil { - return "", fmt.Errorf("Error encoding command: %s", err) - } - - commandText = "powershell -executionpolicy bypass -encodedCommand " + base64EncodedCommand - - return commandText, nil + // Return the interpolated command + return command, nil } func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { @@ -449,20 +433,26 @@ func (p *Provisioner) createCommandTextPrivileged() (command string, err error) func (p *Provisioner) generateElevatedRunner(command string) (uploadedPath string, err error) { log.Printf("Building elevated command wrapper for: %s", command) - // generate command var buffer bytes.Buffer - base64EncodedCommand, err := powershellEncode(command) + // elevatedTemplate wraps the command in a single quoted XML text + // string so we need to escape characters considered 'special' in XML. + err = xml.EscapeText(&buffer, []byte(command)) if err != nil { - return "", fmt.Errorf("Error encoding command: %s", err) + return "", fmt.Errorf("Error escaping characters special to XML in command %s: %s", command, err) } + escapedCommand := buffer.String() + log.Printf("Command [%s] converted to [%s] for use in XML string", command, escapedCommand) + + buffer.Reset() + // Generate command 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: base64EncodedCommand, + User: p.config.ElevatedUser, + Password: p.config.ElevatedPassword, + TaskDescription: "Packer elevated task", + TaskName: fmt.Sprintf("packer-%s", uuid.TimeOrderedUUID()), + XMLEscapedCommand: escapedCommand, }) if err != nil {