1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 22:10:55 +01:00

Execute commands under Powershell on Windows for Harbormaster

Summary:
Resolves T5831.  This modifies the Drydock SSH interface to execute commands under Powershell when the target host platform is Windows.  Powershell is far more featured than cmd.exe, and more closely resembles a UNIX shell.

Currently Powershell outputs stderr as an XML blob on a line, and while this code currently doesn't use that, it will allow us in the future (planned next week) to redirect that output to the stderr log instead of having it all merged in with stdout under cmd (where there is no way to distinguish it).

Test Plan:
Ran various native commands and PowerShell commands from a Harbormaster build, including things like:

```
Write-Host ("my test" + ${build.id})
```

and saw:

```
my test679
```

in the output.

Reviewers: epriestley, #blessed_reviewers

Reviewed By: epriestley, #blessed_reviewers

Subscribers: epriestley, Korvin

Maniphest Tasks: T5831

Differential Revision: https://secure.phabricator.com/D10248
This commit is contained in:
James Rhodes 2014-08-13 12:48:52 +10:00
parent 8ef1ea63dd
commit ca8f7cdaa5
2 changed files with 63 additions and 16 deletions

View file

@ -36,18 +36,44 @@ final class DrydockSSHCommandInterface extends DrydockCommandInterface {
$argv = func_get_args();
// This assumes there's a UNIX shell living at the other
// end of the connection, which isn't the case for Windows machines.
if ($this->getConfig('platform') !== 'windows') {
$argv = $this->applyWorkingDirectoryToArgv($argv);
}
$full_command = call_user_func_array('csprintf', $argv);
if ($this->getConfig('platform') === 'windows') {
// On Windows platforms we need to execute cmd.exe explicitly since
// most commands are not really executables.
$full_command = 'C:\\Windows\\system32\\cmd.exe /C '.$full_command;
// Handle Windows by executing the command under PowerShell.
$command = id(new PhutilCommandString($argv))
->setEscapingMode(PhutilCommandString::MODE_POWERSHELL);
$change_directory = '';
if ($this->getWorkingDirectory() !== null) {
$change_directory .= 'cd '.$this->getWorkingDirectory();
}
$script = <<<EOF
$change_directory
$command
if (\$LastExitCode -ne 0) {
exit \$LastExitCode
}
EOF;
// When Microsoft says "Unicode" they don't mean UTF-8.
$script = mb_convert_encoding($script, 'UTF-16LE');
$script = base64_encode($script);
$powershell =
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
$powershell .=
' -ExecutionPolicy Bypass'.
' -NonInteractive'.
' -InputFormat Text'.
' -OutputFormat Text'.
' -EncodedCommand '.$script;
$full_command = $powershell;
} else {
// Handle UNIX by executing under the native shell.
$argv = $this->applyWorkingDirectoryToArgv($argv);
$full_command = call_user_func_array('csprintf', $argv);
}
$command_timeout = '';

View file

@ -3,6 +3,8 @@
final class HarbormasterCommandBuildStepImplementation
extends HarbormasterBuildStepImplementation {
private $platform;
public function getName() {
return pht('Run Command');
}
@ -18,6 +20,18 @@ final class HarbormasterCommandBuildStepImplementation
$this->formatSettingForDescription('hostartifact'));
}
public function escapeCommand($pattern, array $args) {
array_unshift($args, $pattern);
$mode = PhutilCommandString::MODE_DEFAULT;
if ($this->platform == 'windows') {
$mode = PhutilCommandString::MODE_POWERSHELL;
}
return id(new PhutilCommandString($args))
->setEscapingMode($mode);
}
public function execute(
HarbormasterBuild $build,
HarbormasterBuildTarget $build_target) {
@ -25,15 +39,19 @@ final class HarbormasterCommandBuildStepImplementation
$settings = $this->getSettings();
$variables = $build_target->getVariables();
$command = $this->mergeVariables(
'vcsprintf',
$settings['command'],
$variables);
$artifact = $build->loadArtifact($settings['hostartifact']);
$lease = $artifact->loadDrydockLease();
$this->platform = $lease->getAttribute('platform');
$command = $this->mergeVariables(
array($this, 'escapeCommand'),
$settings['command'],
$variables);
$this->platform = null;
$interface = $lease->getInterface('command');
$future = $interface->getExecFuture('%C', $command);
@ -88,6 +106,9 @@ final class HarbormasterCommandBuildStepImplementation
'name' => pht('Command'),
'type' => 'text',
'required' => true,
'caption' => pht(
'Under Windows, this is executed under PowerShell.'.
'Under UNIX, this is executed using the user\'s shell.'),
),
'hostartifact' => array(
'name' => pht('Host'),