diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -769,7 +769,9 @@ 'DrydockResourceViewController' => 'applications/drydock/controller/DrydockResourceViewController.php', 'DrydockSFTPFilesystemInterface' => 'applications/drydock/interface/filesystem/DrydockSFTPFilesystemInterface.php', 'DrydockSSHCommandInterface' => 'applications/drydock/interface/command/DrydockSSHCommandInterface.php', + 'DrydockSetupCheckWinRM' => 'applications/drydock/check/DrydockSetupCheckWinRM.php', 'DrydockWebrootInterface' => 'applications/drydock/interface/webroot/DrydockWebrootInterface.php', + 'DrydockWinRMCommandInterface' => 'applications/drydock/interface/command/DrydockWinRMCommandInterface.php', 'DrydockWorkingCopyBlueprintImplementation' => 'applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php', 'FeedConduitAPIMethod' => 'applications/feed/conduit/FeedConduitAPIMethod.php', 'FeedPublishConduitAPIMethod' => 'applications/feed/conduit/FeedPublishConduitAPIMethod.php', @@ -4171,7 +4173,9 @@ 'DrydockResourceViewController' => 'DrydockResourceController', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', 'DrydockSSHCommandInterface' => 'DrydockCommandInterface', + 'DrydockSetupCheckWinRM' => 'PhabricatorSetupCheck', 'DrydockWebrootInterface' => 'DrydockInterface', + 'DrydockWinRMCommandInterface' => 'DrydockCommandInterface', 'DrydockWorkingCopyBlueprintImplementation' => 'DrydockBlueprintImplementation', 'FeedConduitAPIMethod' => 'ConduitAPIMethod', 'FeedPublishConduitAPIMethod' => 'FeedConduitAPIMethod', diff --git a/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php --- a/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockPreallocatedHostBlueprintImplementation.php @@ -96,7 +96,12 @@ switch ($type) { case 'command': - return id(new DrydockSSHCommandInterface()) + $interface = new DrydockSSHCommandInterface(); + if ($resource->getAttribute('platform') === 'windows') { + $interface = new DrydockWinRMCommandInterface(); + } + + return $interface ->setConfiguration(array( 'host' => $resource->getAttribute('host'), 'port' => $resource->getAttribute('port'), diff --git a/src/applications/drydock/check/DrydockSetupCheckWinRM.php b/src/applications/drydock/check/DrydockSetupCheckWinRM.php new file mode 100644 --- /dev/null +++ b/src/applications/drydock/check/DrydockSetupCheckWinRM.php @@ -0,0 +1,41 @@ + 'https://github.com/masterzen/winrm'), + 'https://github.com/masterzen/winrm'), + phutil_tag('tt', array(), 'PATH')); + + $message = pht( + 'You only need this binary if you are leasing Windows hosts in '. + 'Drydock or Harbormaster. If you don\'t need to run commands on '. + 'Windows machines, you can safely ignore this message.'); + + $this->newIssue('bin.winrm') + ->setShortName(pht("'%s' Missing", 'winrm')) + ->setName(pht("Missing '%s' Binary", 'winrm')) + ->setSummary( + pht("The '%s' binary could not be located or executed.", 'winrm')) + ->setMessage(pht("%s\n\n%s", $preamble, $message)); + } + + } + +} diff --git a/src/applications/drydock/interface/command/DrydockWinRMCommandInterface.php b/src/applications/drydock/interface/command/DrydockWinRMCommandInterface.php new file mode 100644 --- /dev/null +++ b/src/applications/drydock/interface/command/DrydockWinRMCommandInterface.php @@ -0,0 +1,69 @@ +passphraseWinRMPassword !== null) { + return; + } + + $credential = id(new PassphraseCredentialQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withIDs(array($this->getConfig('credential'))) + ->needSecrets(true) + ->executeOne(); + + if ($credential->getProvidesType() !== + PassphrasePasswordCredentialType::PROVIDES_TYPE) { + throw new Exception('Only password credentials are supported.'); + } + + $this->passphraseWinRMPassword = PassphrasePasswordKey::loadFromPHID( + $credential->getPHID(), + PhabricatorUser::getOmnipotentUser()); + } + + public function getExecFuture($command) { + $this->openCredentialsIfNotOpen(); + + $argv = func_get_args(); + + $change_directory = ''; + if ($this->getWorkingDirectory() !== null) { + $change_directory .= 'cd '.$this->getWorkingDirectory().' & '; + } + + // Encode the command to run under Powershell. + $command = id(new PhutilCommandString($argv)) + ->setEscapingMode(PhutilCommandString::MODE_POWERSHELL); + + // When Microsoft says "Unicode" they don't mean UTF-8. + $command = mb_convert_encoding($command, 'UTF-16LE'); + $command = base64_encode($command); + + $powershell = + 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe'; + $powershell .= + ' -ExecutionPolicy Bypass'. + ' -NonInteractive'. + ' -InputFormat Text'. + ' -OutputFormat Text'. + ' -EncodedCommand '.$command; + + return new ExecFuture( + 'winrm '. + '-hostname=%s '. + '-username=%P '. + '-password=%P '. + '-port=%s '. + '%s', + $this->getConfig('host'), + $this->passphraseWinRMPassword->getUsernameEnvelope(), + $this->passphraseWinRMPassword->getPasswordEnvelope(), + $this->getConfig('port'), + $change_directory.$powershell); + } +}