Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Elevated powershell commands #29

Merged
merged 7 commits into from
Mar 22, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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