github.com/hashicorp/packer@v1.14.3/provisioner/powershell/provisioner.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 //go:generate packer-sdc mapstructure-to-hcl2 -type Config 5 6 // This package implements a provisioner for Packer that executes powershell 7 // scripts within the remote machine. 8 package powershell 9 10 import ( 11 "context" 12 "errors" 13 "fmt" 14 "log" 15 "os" 16 "path/filepath" 17 "sort" 18 "strings" 19 "time" 20 21 "github.com/hashicorp/hcl/v2/hcldec" 22 "github.com/hashicorp/packer-plugin-sdk/guestexec" 23 "github.com/hashicorp/packer-plugin-sdk/multistep/commonsteps" 24 packersdk "github.com/hashicorp/packer-plugin-sdk/packer" 25 "github.com/hashicorp/packer-plugin-sdk/retry" 26 "github.com/hashicorp/packer-plugin-sdk/shell" 27 "github.com/hashicorp/packer-plugin-sdk/template/config" 28 "github.com/hashicorp/packer-plugin-sdk/template/interpolate" 29 "github.com/hashicorp/packer-plugin-sdk/tmp" 30 "github.com/hashicorp/packer-plugin-sdk/uuid" 31 ) 32 33 var psEscape = strings.NewReplacer( 34 "$", "`$", 35 "\"", "`\"", 36 "`", "``", 37 "'", "`'", 38 ) 39 40 // wraps the content in try catch block and exits with a status. 41 const wrapPowershellString string = ` 42 if (Test-Path variable:global:ProgressPreference) { 43 set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue' 44 } 45 {{if .DebugMode}} 46 Set-PsDebug -Trace {{.DebugMode}} 47 {{- end}} 48 $exitCode = 0 49 try { 50 {{.Vars}} 51 {{.Payload}} 52 $exitCode = 0 53 } catch { 54 Write-Error "An error occurred: $_" 55 $exitCode = 1 56 } 57 58 if ($LASTEXITCODE -ne $null -and $LASTEXITCODE -ne 0) { 59 $exitCode = $LASTEXITCODE 60 } 61 exit $exitCode 62 63 ` 64 65 type Config struct { 66 shell.Provisioner `mapstructure:",squash"` 67 68 shell.ProvisionerRemoteSpecific `mapstructure:",squash"` 69 70 // The remote path where the file containing the environment variables 71 // will be uploaded to. This should be set to a writable file that is in a 72 // pre-existing directory. 73 RemoteEnvVarPath string `mapstructure:"remote_env_var_path"` 74 75 // The command used to execute the elevated script. The '{{ .Path }}' 76 // variable should be used to specify where the script goes, {{ .Vars }} 77 // can be used to inject the environment_vars into the environment. 78 ElevatedExecuteCommand string `mapstructure:"elevated_execute_command"` 79 80 // Whether to clean scripts up after executing the provisioner. 81 // Defaults to false. When true any script created by a non-elevated Powershell 82 // provisioner will be removed from the remote machine. Elevated scripts, 83 // along with the scheduled tasks, will always be removed regardless of the 84 // value set for `skip_clean`. 85 SkipClean bool `mapstructure:"skip_clean"` 86 87 // The timeout for retrying to start the process. Until this timeout is 88 // reached, if the provisioner can't start a process, it retries. This 89 // can be set high to allow for reboots. 90 StartRetryTimeout time.Duration `mapstructure:"start_retry_timeout"` 91 92 // This is used in the template generation to format environment variables 93 // inside the `ElevatedExecuteCommand` template. 94 ElevatedEnvVarFormat string `mapstructure:"elevated_env_var_format"` 95 96 // Instructs the communicator to run the remote script as a Windows 97 // scheduled task, effectively elevating the remote user by impersonating 98 // a logged-in user 99 ElevatedUser string `mapstructure:"elevated_user"` 100 ElevatedPassword string `mapstructure:"elevated_password"` 101 102 ExecutionPolicy ExecutionPolicy `mapstructure:"execution_policy"` 103 104 remoteCleanUpScriptPath string 105 106 // If set, sets PowerShell's [PSDebug mode 107 // on](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/set-psdebug?view=powershell-7) 108 // in order to make script debugging easier. For instance, setting the value 109 // to 1 results in adding this to the execute command: 110 // 111 // ``` powershell 112 // Set-PSDebug -Trace 1 113 // ``` 114 DebugMode int `mapstructure:"debug_mode"` 115 116 // A duration of how long to pause after the provisioner 117 PauseAfter time.Duration `mapstructure:"pause_after"` 118 119 // Run pwsh.exe instead of powershell.exe - latest version of powershell. 120 UsePwsh bool `mapstructure:"use_pwsh"` 121 122 ctx interpolate.Context 123 } 124 125 type Provisioner struct { 126 config Config 127 communicator packersdk.Communicator 128 generatedData map[string]interface{} 129 } 130 131 func (p *Provisioner) defaultExecuteCommand() string { 132 133 if p.config.ExecutionPolicy == ExecutionPolicyNone { 134 return `-file {{.Path}}` 135 } 136 137 if p.config.UsePwsh { 138 return fmt.Sprintf(`pwsh -executionpolicy %s -file {{.Path}}`, p.config.ExecutionPolicy) 139 } else { 140 return fmt.Sprintf(`powershell -executionpolicy %s -file {{.Path}}`, p.config.ExecutionPolicy) 141 } 142 143 } 144 145 func (p *Provisioner) defaultScriptCommand() string { 146 baseCmd := `& { if (Test-Path variable:global:ProgressPreference)` + 147 `{set-variable -name variable:global:ProgressPreference -value 'SilentlyContinue'};` 148 149 if p.config.DebugMode != 0 { 150 baseCmd += fmt.Sprintf(`Set-PsDebug -Trace %d;`, p.config.DebugMode) 151 } 152 153 baseCmd += `. {{.Vars}}; &'{{.Path}}'; exit $LastExitCode }` 154 155 if p.config.ExecutionPolicy == ExecutionPolicyNone { 156 return baseCmd 157 } 158 159 if p.config.UsePwsh { 160 return fmt.Sprintf(`pwsh -executionpolicy %s -command "%s"`, p.config.ExecutionPolicy, baseCmd) 161 } else { 162 return fmt.Sprintf(`powershell -executionpolicy %s "%s"`, p.config.ExecutionPolicy, baseCmd) 163 } 164 165 } 166 167 func (p *Provisioner) ConfigSpec() hcldec.ObjectSpec { return p.config.FlatMapstructure().HCL2Spec() } 168 169 func (p *Provisioner) Prepare(raws ...interface{}) error { 170 err := config.Decode(&p.config, &config.DecodeOpts{ 171 PluginType: "powershell", 172 Interpolate: true, 173 InterpolateContext: &p.config.ctx, 174 InterpolateFilter: &interpolate.RenderFilter{ 175 Exclude: []string{ 176 "execute_command", 177 "elevated_execute_command", 178 }, 179 }, 180 DecodeHooks: append(config.DefaultDecodeHookFuncs, StringToExecutionPolicyHook), 181 }, raws...) 182 183 if err != nil { 184 return err 185 } 186 187 if p.config.EnvVarFormat == "" { 188 p.config.EnvVarFormat = `$env:%s="%s"; ` 189 } 190 191 if p.config.ElevatedEnvVarFormat == "" { 192 p.config.ElevatedEnvVarFormat = `$env:%s="%s"; ` 193 } 194 195 if p.config.Inline != nil && len(p.config.Inline) == 0 { 196 p.config.Inline = nil 197 } 198 199 if p.config.StartRetryTimeout == 0 { 200 p.config.StartRetryTimeout = 5 * time.Minute 201 } 202 203 if p.config.RemotePath == "" { 204 uuid := uuid.TimeOrderedUUID() 205 p.config.RemotePath = fmt.Sprintf(`c:/Windows/Temp/script-%s.ps1`, uuid) 206 } 207 208 if p.config.RemoteEnvVarPath == "" { 209 uuid := uuid.TimeOrderedUUID() 210 p.config.RemoteEnvVarPath = fmt.Sprintf(`c:/Windows/Temp/packer-ps-env-vars-%s.ps1`, uuid) 211 } 212 213 if p.config.Scripts == nil { 214 p.config.Scripts = make([]string, 0) 215 } 216 217 if p.config.Vars == nil { 218 p.config.Vars = make([]string, 0) 219 } 220 221 p.config.remoteCleanUpScriptPath = fmt.Sprintf(`c:/Windows/Temp/packer-cleanup-%s.ps1`, uuid.TimeOrderedUUID()) 222 223 var errs error 224 if p.config.Script != "" && len(p.config.Scripts) > 0 { 225 errs = packersdk.MultiErrorAppend(errs, 226 errors.New("Only one of script or scripts can be specified.")) 227 } 228 229 if p.config.ElevatedUser == "" && p.config.ElevatedPassword != "" { 230 errs = packersdk.MultiErrorAppend(errs, 231 errors.New("Must supply an 'elevated_user' if 'elevated_password' provided")) 232 } 233 234 if p.config.Script != "" { 235 p.config.Scripts = []string{p.config.Script} 236 } 237 238 if len(p.config.Scripts) == 0 && p.config.Inline == nil { 239 errs = packersdk.MultiErrorAppend(errs, 240 errors.New("Either a script file or inline script must be specified.")) 241 } else if len(p.config.Scripts) > 0 && p.config.Inline != nil { 242 errs = packersdk.MultiErrorAppend(errs, 243 errors.New("Only a script file or an inline script can be specified, not both.")) 244 } 245 246 if p.config.ExecuteCommand == "" { 247 if p.config.Inline != nil && len(p.config.Scripts) == 0 { 248 p.config.ExecuteCommand = p.defaultExecuteCommand() 249 log.Printf("Using inline default execute command %s", p.config.ExecuteCommand) 250 } else { 251 p.config.ExecuteCommand = p.defaultScriptCommand() 252 log.Printf("Using script default execute command %s", p.config.ExecuteCommand) 253 } 254 255 } 256 257 if p.config.ElevatedExecuteCommand == "" { 258 if p.config.Inline != nil && len(p.config.Scripts) == 0 { 259 p.config.ElevatedExecuteCommand = p.defaultExecuteCommand() 260 log.Printf("Using inline default elevated execute command %s", p.config.ElevatedExecuteCommand) 261 } else { 262 p.config.ElevatedExecuteCommand = p.defaultScriptCommand() 263 log.Printf("Using script default elevated execute command %s", p.config.ElevatedExecuteCommand) 264 } 265 } 266 267 for _, path := range p.config.Scripts { 268 if _, err := os.Stat(path); err != nil { 269 errs = packersdk.MultiErrorAppend(errs, 270 fmt.Errorf("Bad script '%s': %s", path, err)) 271 } 272 } 273 274 // Do a check for bad environment variables, such as '=foo', 'foobar' 275 for _, kv := range p.config.Vars { 276 vs := strings.SplitN(kv, "=", 2) 277 if len(vs) != 2 || vs[0] == "" { 278 errs = packersdk.MultiErrorAppend(errs, 279 fmt.Errorf("Environment variable not in format 'key=value': %s", kv)) 280 } 281 } 282 283 if p.config.ExecutionPolicy > 7 { 284 errs = packersdk.MultiErrorAppend(errs, fmt.Errorf(`Invalid execution `+ 285 `policy provided. Please supply one of: "bypass", "allsigned",`+ 286 ` "default", "remotesigned", "restricted", "undefined", `+ 287 `"unrestricted", "none".`)) 288 } 289 290 if !(p.config.DebugMode >= 0 && p.config.DebugMode <= 2) { 291 errs = packersdk.MultiErrorAppend(errs, fmt.Errorf("%d is an invalid Trace level for `debug_mode`; valid values are 0, 1, and 2", p.config.DebugMode)) 292 } 293 294 if errs != nil { 295 return errs 296 } 297 298 return nil 299 } 300 301 // Takes the inline scripts, adds a wrapper around the inline scripts, concatenates them into a temporary file and 302 // returns a string containing the location of said file. 303 func extractInlineScript(p *Provisioner) (string, error) { 304 temp, err := tmp.File("powershell-provisioner") 305 if err != nil { 306 return "", err 307 } 308 309 defer temp.Close() 310 311 var commandBuilder strings.Builder 312 313 // we concatenate all the inline commands 314 for _, command := range p.config.Inline { 315 log.Printf("Found command: %s", command) 316 if _, err := commandBuilder.WriteString(command + "\n\t"); err != nil { 317 return "", fmt.Errorf("failed to wrap script contents: %w", err) 318 } 319 } 320 321 // injecting all the variables in the string 322 ctxData := p.generatedData 323 ctxData["Vars"] = p.createFlattenedEnvVars(p.config.ElevatedUser != "") 324 ctxData["Payload"] = commandBuilder.String() 325 ctxData["DebugMode"] = p.config.DebugMode 326 p.config.ctx.Data = ctxData 327 328 data, err := interpolate.Render(wrapPowershellString, &p.config.ctx) 329 if err != nil { 330 return "", fmt.Errorf("Error building powershell wrapper: %w", err) 331 } 332 333 log.Printf("Writing PowerShell script to file: %s", temp.Name()) 334 if _, err := temp.WriteString(data); err != nil { 335 return "", fmt.Errorf("Error writing PowerShell script: %w", err) 336 } 337 338 return temp.Name(), nil 339 } 340 341 func (p *Provisioner) Provision(ctx context.Context, ui packersdk.Ui, comm packersdk.Communicator, generatedData map[string]interface{}) error { 342 ui.Say("Provisioning with Powershell...") 343 p.communicator = comm 344 p.generatedData = generatedData 345 346 scripts := make([]string, len(p.config.Scripts)) 347 copy(scripts, p.config.Scripts) 348 349 if p.config.Inline != nil { 350 temp, err := extractInlineScript(p) 351 if err != nil { 352 ui.Error(fmt.Sprintf("Unable to extract inline scripts into a file: %s", err)) 353 } 354 scripts = append(scripts, temp) 355 // Remove temp script containing the inline commands when done 356 defer os.Remove(temp) 357 } 358 359 // every provisioner run will only have one env var script file so lets add it first 360 uploadedScripts := []string{p.config.RemoteEnvVarPath} 361 for _, path := range scripts { 362 ui.Say(fmt.Sprintf("Provisioning with powershell script: %s", path)) 363 364 log.Printf("Opening %s for reading", path) 365 fi, err := os.Stat(path) 366 if err != nil { 367 return fmt.Errorf("Error stating powershell script: %s", err) 368 } 369 if os.IsPathSeparator(p.config.RemotePath[len(p.config.RemotePath)-1]) { 370 // path is a directory 371 p.config.RemotePath += filepath.Base(fi.Name()) 372 } 373 f, err := os.Open(path) 374 if err != nil { 375 return fmt.Errorf("Error opening powershell script: %s", err) 376 } 377 defer f.Close() 378 379 command, err := p.createCommandText() 380 if err != nil { 381 return fmt.Errorf("Error processing command: %s", err) 382 } 383 384 // Upload the file and run the command. Do this in the context of a 385 // single retryable function so that we don't end up with the case 386 // that the upload succeeded, a restart is initiated, and then the 387 // command is executed but the file doesn't exist any longer. 388 var cmd *packersdk.RemoteCmd 389 err = retry.Config{StartTimeout: p.config.StartRetryTimeout}.Run(ctx, func(ctx context.Context) error { 390 if _, err := f.Seek(0, 0); err != nil { 391 return err 392 } 393 if err := comm.Upload(p.config.RemotePath, f, &fi); err != nil { 394 return fmt.Errorf("Error uploading script: %s", err) 395 } 396 397 cmd = &packersdk.RemoteCmd{Command: command} 398 return cmd.RunWithUi(ctx, comm, ui) 399 }) 400 if err != nil { 401 return err 402 } 403 404 // Close the original file since we copied it 405 f.Close() 406 407 // Record every other uploaded script file so we can clean it up later 408 uploadedScripts = append(uploadedScripts, p.config.RemotePath) 409 410 log.Printf("%s returned with exit code %d", p.config.RemotePath, cmd.ExitStatus()) 411 if err := p.config.ValidExitCode(cmd.ExitStatus()); err != nil { 412 return err 413 } 414 } 415 416 if p.config.SkipClean { 417 return nil 418 } 419 420 err := retry.Config{StartTimeout: time.Minute, RetryDelay: func() time.Duration { return 10 * time.Second }}.Run(ctx, func(ctx context.Context) error { 421 command, err := p.createRemoteCleanUpCommand(uploadedScripts) 422 if err != nil { 423 log.Printf("failed to upload the remote cleanup script: %q", err) 424 return err 425 } 426 427 cmd := &packersdk.RemoteCmd{Command: command} 428 return cmd.RunWithUi(ctx, comm, ui) 429 }) 430 if err != nil { 431 log.Printf("remote cleanup script failed to upload; skipping the removal of temporary files: %s; ", strings.Join(uploadedScripts, ",")) 432 } 433 434 if p.config.PauseAfter != 0 { 435 ui.Say(fmt.Sprintf("Pausing %s after this provisioner...", p.config.PauseAfter)) 436 time.Sleep(p.config.PauseAfter) 437 } 438 439 return nil 440 } 441 442 // createRemoteCleanUpCommand will generated a powershell script that will remove remote files; 443 // returning a command that can be executed remotely to do the cleanup. 444 func (p *Provisioner) createRemoteCleanUpCommand(remoteFiles []string) (string, error) { 445 if len(remoteFiles) == 0 { 446 return "", fmt.Errorf("no remoteFiles provided for cleanup") 447 } 448 449 var b strings.Builder 450 // This script should self destruct. 451 remotePath := p.config.remoteCleanUpScriptPath 452 remoteFiles = append(remoteFiles, remotePath) 453 for _, filename := range remoteFiles { 454 fmt.Fprintf(&b, "if (Test-Path %[1]s) {Remove-Item %[1]s}\n", filename) 455 } 456 457 if err := p.communicator.Upload(remotePath, strings.NewReader(b.String()), nil); err != nil { 458 return "", fmt.Errorf("clean up script %q failed to upload: %s", remotePath, err) 459 } 460 461 data := p.generatedData 462 data["Path"] = remotePath 463 data["Vars"] = p.config.RemoteEnvVarPath 464 p.config.ctx.Data = data 465 466 p.config.ctx.Data = data 467 return interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 468 } 469 470 // Environment variables required within the remote environment are uploaded 471 // within a PS script and then enabled by 'dot sourcing' the script 472 // immediately prior to execution of the main command 473 func (p *Provisioner) prepareEnvVars(elevated bool) (err error) { 474 // Collate all required env vars into a plain string with required 475 // formatting applied 476 flattenedEnvVars := p.createFlattenedEnvVars(elevated) 477 // Create a powershell script on the target build fs containing the 478 // flattened env vars 479 err = p.uploadEnvVars(flattenedEnvVars) 480 if err != nil { 481 return err 482 } 483 return 484 } 485 486 func (p *Provisioner) createFlattenedEnvVars(elevated bool) (flattened string) { 487 flattened = "" 488 envVars := make(map[string]string) 489 490 // Always available Packer provided env vars 491 envVars["PACKER_BUILD_NAME"] = p.config.PackerBuildName 492 envVars["PACKER_BUILDER_TYPE"] = p.config.PackerBuilderType 493 494 // expose ip address variables 495 httpAddr := p.generatedData["PackerHTTPAddr"] 496 if httpAddr != nil && httpAddr != commonsteps.HttpAddrNotImplemented { 497 envVars["PACKER_HTTP_ADDR"] = httpAddr.(string) 498 } 499 httpIP := p.generatedData["PackerHTTPIP"] 500 if httpIP != nil && httpIP != commonsteps.HttpIPNotImplemented { 501 envVars["PACKER_HTTP_IP"] = httpIP.(string) 502 } 503 httpPort := p.generatedData["PackerHTTPPort"] 504 if httpPort != nil && httpPort != commonsteps.HttpPortNotImplemented { 505 envVars["PACKER_HTTP_PORT"] = httpPort.(string) 506 } 507 508 // interpolate environment variables 509 p.config.ctx.Data = p.generatedData 510 511 // Split vars into key/value components 512 for _, envVar := range p.config.Vars { 513 envVar, err := interpolate.Render(envVar, &p.config.ctx) 514 if err != nil { 515 return 516 } 517 keyValue := strings.SplitN(envVar, "=", 2) 518 // Escape chars special to PS in each env var value 519 escapedEnvVarValue := psEscape.Replace(keyValue[1]) 520 521 isSensitive := false 522 for _, sensitiveVar := range p.config.PackerSensitiveVars { 523 if strings.EqualFold(sensitiveVar, keyValue[0]) { 524 isSensitive = true 525 break 526 } 527 } 528 529 if escapedEnvVarValue != keyValue[1] && !isSensitive { 530 log.Printf("Env var %s converted to %s after escaping chars special to PS", keyValue[1], 531 escapedEnvVarValue) 532 } 533 envVars[keyValue[0]] = escapedEnvVarValue 534 } 535 536 for k, v := range p.config.Env { 537 envVarName, err := interpolate.Render(k, &p.config.ctx) 538 if err != nil { 539 return 540 } 541 envVarValue, err := interpolate.Render(v, &p.config.ctx) 542 if err != nil { 543 return 544 } 545 envVars[envVarName] = psEscape.Replace(envVarValue) 546 } 547 548 // Create a list of env var keys in sorted order 549 var keys []string 550 for k := range envVars { 551 keys = append(keys, k) 552 } 553 sort.Strings(keys) 554 format := p.config.EnvVarFormat 555 if elevated { 556 format = p.config.ElevatedEnvVarFormat 557 } 558 559 // Re-assemble vars using OS specific format pattern and flatten 560 for _, key := range keys { 561 flattened += fmt.Sprintf(format, key, envVars[key]) 562 } 563 return 564 } 565 566 func (p *Provisioner) uploadEnvVars(flattenedEnvVars string) (err error) { 567 ctx := context.TODO() 568 // Upload all env vars to a powershell script on the target build file 569 // system. Do this in the context of a single retryable function so that 570 // we gracefully handle any errors created by transient conditions such as 571 // a system restart 572 envVarReader := strings.NewReader(flattenedEnvVars) 573 log.Printf("Uploading env vars to %s", p.config.RemoteEnvVarPath) 574 err = retry.Config{StartTimeout: p.config.StartRetryTimeout}.Run(ctx, func(context.Context) error { 575 if err := p.communicator.Upload(p.config.RemoteEnvVarPath, envVarReader, nil); err != nil { 576 return fmt.Errorf("Error uploading ps script containing env vars: %s", err) 577 } 578 return err 579 }) 580 return 581 } 582 583 func (p *Provisioner) createCommandText() (command string, err error) { 584 // Return the interpolated command 585 if p.config.ElevatedUser == "" { 586 return p.createCommandTextNonPrivileged() 587 } else { 588 return p.createCommandTextPrivileged() 589 } 590 } 591 592 func (p *Provisioner) createCommandTextNonPrivileged() (command string, err error) { 593 // Prepare everything needed to enable the required env vars within the 594 // remote environment 595 err = p.prepareEnvVars(false) 596 if err != nil { 597 return "", err 598 } 599 600 ctxData := p.generatedData 601 ctxData["Path"] = p.config.RemotePath 602 ctxData["Vars"] = p.config.RemoteEnvVarPath 603 p.config.ctx.Data = ctxData 604 605 command, err = interpolate.Render(p.config.ExecuteCommand, &p.config.ctx) 606 607 if err != nil { 608 return "", fmt.Errorf("Error processing command: %s", err) 609 } 610 611 // Return the interpolated command 612 return command, nil 613 } 614 615 func (p *Provisioner) createCommandTextPrivileged() (command string, err error) { 616 // Prepare everything needed to enable the required env vars within the 617 // remote environment 618 err = p.prepareEnvVars(true) 619 if err != nil { 620 return "", err 621 } 622 ctxData := p.generatedData 623 ctxData["Path"] = p.config.RemotePath 624 ctxData["Vars"] = p.config.RemoteEnvVarPath 625 p.config.ctx.Data = ctxData 626 627 command, err = interpolate.Render(p.config.ElevatedExecuteCommand, &p.config.ctx) 628 if err != nil { 629 return "", fmt.Errorf("Error processing command: %s", err) 630 } 631 632 command, err = guestexec.GenerateElevatedRunner(command, p) 633 if err != nil { 634 return "", fmt.Errorf("Error generating elevated runner: %s", err) 635 } 636 637 return command, err 638 } 639 640 func (p *Provisioner) Communicator() packersdk.Communicator { 641 return p.communicator 642 } 643 644 func (p *Provisioner) ElevatedUser() string { 645 return p.config.ElevatedUser 646 } 647 648 func (p *Provisioner) ElevatedPassword() string { 649 // Replace ElevatedPassword for winrm users who used this feature 650 p.config.ctx.Data = p.generatedData 651 elevatedPassword, _ := interpolate.Render(p.config.ElevatedPassword, &p.config.ctx) 652 653 return elevatedPassword 654 }