@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+61 -14
+36 -10
src/applications/drydock/interface/command/DrydockSSHCommandInterface.php
··· 36 36 37 37 $argv = func_get_args(); 38 38 39 - // This assumes there's a UNIX shell living at the other 40 - // end of the connection, which isn't the case for Windows machines. 41 - if ($this->getConfig('platform') !== 'windows') { 42 - $argv = $this->applyWorkingDirectoryToArgv($argv); 43 - } 39 + if ($this->getConfig('platform') === 'windows') { 40 + // Handle Windows by executing the command under PowerShell. 41 + $command = id(new PhutilCommandString($argv)) 42 + ->setEscapingMode(PhutilCommandString::MODE_POWERSHELL); 44 43 45 - $full_command = call_user_func_array('csprintf', $argv); 44 + $change_directory = ''; 45 + if ($this->getWorkingDirectory() !== null) { 46 + $change_directory .= 'cd '.$this->getWorkingDirectory(); 47 + } 46 48 47 - if ($this->getConfig('platform') === 'windows') { 48 - // On Windows platforms we need to execute cmd.exe explicitly since 49 - // most commands are not really executables. 50 - $full_command = 'C:\\Windows\\system32\\cmd.exe /C '.$full_command; 49 + $script = <<<EOF 50 + $change_directory 51 + $command 52 + if (\$LastExitCode -ne 0) { 53 + exit \$LastExitCode 54 + } 55 + EOF; 56 + 57 + // When Microsoft says "Unicode" they don't mean UTF-8. 58 + $script = mb_convert_encoding($script, 'UTF-16LE'); 59 + 60 + $script = base64_encode($script); 61 + 62 + $powershell = 63 + 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; 64 + $powershell .= 65 + ' -ExecutionPolicy Bypass'. 66 + ' -NonInteractive'. 67 + ' -InputFormat Text'. 68 + ' -OutputFormat Text'. 69 + ' -EncodedCommand '.$script; 70 + 71 + $full_command = $powershell; 72 + } else { 73 + // Handle UNIX by executing under the native shell. 74 + $argv = $this->applyWorkingDirectoryToArgv($argv); 75 + 76 + $full_command = call_user_func_array('csprintf', $argv); 51 77 } 52 78 53 79 $command_timeout = '';
+25 -4
src/applications/harbormaster/step/HarbormasterCommandBuildStepImplementation.php
··· 3 3 final class HarbormasterCommandBuildStepImplementation 4 4 extends HarbormasterBuildStepImplementation { 5 5 6 + private $platform; 7 + 6 8 public function getName() { 7 9 return pht('Run Command'); 8 10 } ··· 18 20 $this->formatSettingForDescription('hostartifact')); 19 21 } 20 22 23 + public function escapeCommand($pattern, array $args) { 24 + array_unshift($args, $pattern); 25 + 26 + $mode = PhutilCommandString::MODE_DEFAULT; 27 + if ($this->platform == 'windows') { 28 + $mode = PhutilCommandString::MODE_POWERSHELL; 29 + } 30 + 31 + return id(new PhutilCommandString($args)) 32 + ->setEscapingMode($mode); 33 + } 34 + 21 35 public function execute( 22 36 HarbormasterBuild $build, 23 37 HarbormasterBuildTarget $build_target) { 24 38 25 39 $settings = $this->getSettings(); 26 40 $variables = $build_target->getVariables(); 41 + 42 + $artifact = $build->loadArtifact($settings['hostartifact']); 43 + 44 + $lease = $artifact->loadDrydockLease(); 45 + 46 + $this->platform = $lease->getAttribute('platform'); 27 47 28 48 $command = $this->mergeVariables( 29 - 'vcsprintf', 49 + array($this, 'escapeCommand'), 30 50 $settings['command'], 31 51 $variables); 32 52 33 - $artifact = $build->loadArtifact($settings['hostartifact']); 34 - 35 - $lease = $artifact->loadDrydockLease(); 53 + $this->platform = null; 36 54 37 55 $interface = $lease->getInterface('command'); 38 56 ··· 88 106 'name' => pht('Command'), 89 107 'type' => 'text', 90 108 'required' => true, 109 + 'caption' => pht( 110 + 'Under Windows, this is executed under PowerShell.'. 111 + 'Under UNIX, this is executed using the user\'s shell.'), 91 112 ), 92 113 'hostartifact' => array( 93 114 'name' => pht('Host'),