Skip to content

Commit

Permalink
Merge pull request #29 from packer-community/elevated_powershell_comm…
Browse files Browse the repository at this point in the history
…ands

Elevated powershell commands support
  • Loading branch information
mefellows committed Mar 22, 2015
2 parents 6976781 + f354585 commit 29bbe8f
Show file tree
Hide file tree
Showing 7 changed files with 406 additions and 120 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,22 @@ Check out these projects for more detailed examples of Windows-centric Packer te
- [joefitzgerald/packer-windows](https://github.com/joefitzgerald/packer-windows)
- [box-cutter/windows-vm](https://github.com/box-cutter/windows-vm)

### Running commands with Elevated privileges

In certain situations, you may need to run commands with elevated privileges even if your `winrm_username` user is an Administrator, for example upgrading system packages like the .NET Framework.
In these cases there are 2 additional keys provided on the `powershell` provisioner that you can supply that will enable this mode: `elevated_user` and `elevated_password`:

```
{
"type": "powershell",
"elevated_user":"vagrant",
"elevated_password":"vagrant",
"inline": [
"choco install netfx-4.5.2-devpack"
]
}
```

### Community
- **IRC**: `#packer-community` on Freenode.
- **Slack**: packer.slack.com
37 changes: 12 additions & 25 deletions communicator/winrm/communicator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ type Communicator struct {
endpoint *winrm.Endpoint
user string
password string
timeout time.Duration
}

type elevatedShellOptions struct {
Command string
User string
Password string
}

// Creates a new packer.Communicator implementation over WinRM.
Expand Down Expand Up @@ -56,15 +49,7 @@ func New(endpoint *winrm.Endpoint, user string, password string, timeout time.Du
}, nil
}

func (c *Communicator) Start(rc *packer.RemoteCmd) (err error) {
return c.runCommand(rc, rc.Command)
}

func (c *Communicator) StartElevated(cmd *packer.RemoteCmd) (err error) {
panic("not implemented")
}

func (c *Communicator) runCommand(rc *packer.RemoteCmd, command string, arguments ...string) (err error) {
func (c *Communicator) Start(rc *packer.RemoteCmd) error {
log.Printf("starting remote command: %s", rc.Command)

// Create a new shell process on the guest
Expand All @@ -80,22 +65,23 @@ func (c *Communicator) runCommand(rc *packer.RemoteCmd, command string, argument
return err
}

cmd, err := shell.Execute(command, arguments...)
cmd, err := shell.Execute(rc.Command)
if err != nil {
return err
}

go func(shell *winrm.Shell, cmd *winrm.Command, rc *packer.RemoteCmd) {
defer shell.Close()
go runCommand(shell, cmd, rc)
return nil
}

go io.Copy(rc.Stdout, cmd.Stdout)
go io.Copy(rc.Stderr, cmd.Stderr)
func runCommand(shell *winrm.Shell, cmd *winrm.Command, rc *packer.RemoteCmd) {
defer shell.Close()

cmd.Wait()
rc.SetExited(cmd.ExitCode())
}(shell, cmd, rc)
go io.Copy(rc.Stdout, cmd.Stdout)
go io.Copy(rc.Stderr, cmd.Stderr)

return nil
cmd.Wait()
rc.SetExited(cmd.ExitCode())
}

func (c *Communicator) Upload(dst string, input io.Reader, ignored *os.FileInfo) error {
Expand Down Expand Up @@ -125,6 +111,7 @@ func (c *Communicator) newCopyClient() (*winrmcp.Winrmcp, error) {
User: c.user,
Password: c.password,
},
OperationTimeout: time.Minute * 5,
MaxOperationsPerShell: 15, // lowest common denominator
})
}
35 changes: 0 additions & 35 deletions communicator/winrm/communicator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,41 +54,6 @@ func TestStart(t *testing.T) {
}
}

func TestStartElevated(t *testing.T) {
// This test hits an already running Windows VM
// You can comment this line out temporarily during development
t.Skip()

comm, err := New(&winrm.Endpoint{"localhost", 5985}, "vagrant", "vagrant", time.Duration(1)*time.Minute)
if err != nil {
t.Fatalf("error connecting to WinRM: %s", err)
}

var cmd packer.RemoteCmd
var outWriter, errWriter bytes.Buffer

cmd.Command = "dir"
cmd.Stdout = &outWriter
cmd.Stderr = &errWriter

err = comm.StartElevated(&cmd)
if err != nil {
t.Fatalf("error starting cmd: %s", err)
}
cmd.Wait()

fmt.Println(outWriter.String())
fmt.Println(errWriter.String())

if err != nil {
t.Fatalf("error running cmd: %s", err)
}

if cmd.ExitStatus != 0 {
t.Fatalf("exit status was non-zero: %d", cmd.ExitStatus)
}
}

func TestUpload(t *testing.T) {
// This test hits an already running Windows VM
// You can comment this line out temporarily during development
Expand Down
87 changes: 87 additions & 0 deletions provisioner/powershell/elevated.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package powershell

import (
"text/template"
)

type elevatedOptions struct {
User string
Password string
TaskName string
TaskDescription string
EncodedCommand string
}

var elevatedTemplate = template.Must(template.New("ElevatedCommand").Parse(`
$name = "{{.TaskName}}"
$log = "$env:TEMP\$name.out"
$s = New-Object -ComObject "Schedule.Service"
$s.Connect()
$t = $s.NewTask($null)
$t.XmlText = @'
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
<RegistrationInfo>
<Description>{{.TaskDescription}}</Description>
</RegistrationInfo>
<Principals>
<Principal id="Author">
<UserId>{{.User}}</UserId>
<LogonType>Password</LogonType>
<RunLevel>HighestAvailable</RunLevel>
</Principal>
</Principals>
<Settings>
<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>
<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>
<AllowHardTerminate>true</AllowHardTerminate>
<StartWhenAvailable>false</StartWhenAvailable>
<RunOnlyIfNetworkAvailable>false</RunOnlyIfNetworkAvailable>
<IdleSettings>
<StopOnIdleEnd>true</StopOnIdleEnd>
<RestartOnIdle>false</RestartOnIdle>
</IdleSettings>
<AllowStartOnDemand>true</AllowStartOnDemand>
<Enabled>true</Enabled>
<Hidden>false</Hidden>
<RunOnlyIfIdle>false</RunOnlyIfIdle>
<WakeToRun>false</WakeToRun>
<ExecutionTimeLimit>PT2H</ExecutionTimeLimit>
<Priority>4</Priority>
</Settings>
<Actions Context="Author">
<Exec>
<Command>cmd</Command>
<Arguments>/c powershell.exe -EncodedCommand {{.EncodedCommand}} &gt; %TEMP%\{{.TaskName}}.out 2&gt;&amp;1</Arguments>
</Exec>
</Actions>
</Task>
'@
$f = $s.GetFolder("\")
$f.RegisterTaskDefinition($name, $t, 6, "{{.User}}", "{{.Password}}", 1, $null) | Out-Null
$t = $f.GetTask("\$name")
$t.Run($null) | Out-Null
$timeout = 10
$sec = 0
while ((!($t.state -eq 4)) -and ($sec -lt $timeout)) {
Start-Sleep -s 1
$sec++
}
function SlurpOutput($l) {
if (Test-Path $log) {
Get-Content $log | select -skip $l | ForEach {
$l += 1
Write-Host "$_"
}
}
return $l
}
$line = 0
do {
Start-Sleep -m 100
$line = SlurpOutput $line
} while (!($t.state -eq 3))
$result = $t.LastTaskResult
[System.Runtime.Interopservices.Marshal]::ReleaseComObject($s) | Out-Null
exit $result`))
17 changes: 17 additions & 0 deletions provisioner/powershell/powershell.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package powershell

import (
"encoding/base64"
)

func powershellEncode(buffer []byte) string {
// 2 byte chars to make PowerShell happy
wideCmd := ""
for _, b := range buffer {
wideCmd += string(b) + "\x00"
}

// Base64 encode the command
input := []uint8(wideCmd)
return base64.StdEncoding.EncodeToString(input)
}
Loading

0 comments on commit 29bbe8f

Please sign in to comment.