Files
portal-api/app/Http/Services/vCenter/PowerShellService.php
T

271 lines
8.1 KiB
PHP

<?php
namespace App\Http\Services\vCenter;
use Illuminate\Support\Facades\Log;
use phpseclib3\Net\SSH2;
use Exception;
use App\Models\InfraManagement;
use App\Http\Facade\SignRSA;
class PowerShellService
{
protected Client $client;
public function __construct(Client $client)
{
$this->client = $client;
}
private function LoginAuth()
{
try {
// Retrieve SSH/Jumphost credentials from database
$infra = InfraManagement::where('type', 'jumphost')
->where('status', true)
->first();
if (!$infra) {
Log::error('SSH Login Failed: No active jumphost configuration found in InfraManagement.');
return false;
}
$sshHost = $infra->host;
$sshPort = 22;
if (isset($infra->meta_data['port'])) {
$sshPort = $infra->meta_data['port'];
}
$sshUser = $infra->username;
$sshPass = SignRSA::DecryptFunc($infra->password);
$connection = new SSH2($sshHost, $sshPort);
if ($connection->login($sshUser, $sshPass)) {
$connection->setTimeout(0); // No timeout
return $connection;
}
return false;
} catch (Exception $error) {
Log::error('SSH Login Failed: ' . $error->getMessage());
return false;
}
}
public function loginSSH(): bool
{
$conn = $this->LoginAuth();
if ($conn) {
Log::info("✅ Connected to vCenter via SSH successfully");
return true;
}
Log::error("❌ Failed to connect to vCenter via SSH");
return false;
}
public function runPsCommand(string $psCommand): string
{
$auth = $this->LoginAuth();
if (!$auth) {
return "Login_Fail";
}
// Action wrapper
$action = 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass '
. '-Command "' . str_replace('"', '\"', $psCommand) . '"';
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return "Error: " . $e->getMessage();
}
}
public function executePowerCLI(string $cmd)
{
return $this->CommandRSP($cmd);
}
protected function CommandRSP(string $cmd)
{
$auth = $this->LoginAuth();
if (!$auth) {
return "Login_Fail";
}
$esc = fn(string $v) => str_replace("'", "''", $v);
// Get credentials from Client
$server = $esc($this->client->apiUri ?? '');
$user = $esc($this->client->getUsername() ?? '');
$pass = $esc($this->client->getPassword() ?? '');
$psTemplate = <<<'PS'
$ProgressPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
Import-Module VMware.PowerCLI -ErrorAction Stop
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
& {
<CMD>
} | Out-String
PS;
$psScript = strtr($psTemplate, [
'<SERVER>' => $server,
'<USER>' => $user,
'<PASS>' => $pass,
'<CMD>' => $cmd,
]);
$encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8'));
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return "Error: " . $e->getMessage();
}
}
protected function CommandChangePort(string $cmd, bool $expectJson = false)
{
$auth = $this->LoginAuth();
if (!$auth) {
return "Login_Fail";
}
$esc = fn(string $v) => str_replace("'", "''", $v);
$server = $esc($this->client->apiUri ?? '');
$user = $esc($this->client->getUsername() ?? '');
$pass = $esc($this->client->getPassword() ?? '');
$psTemplate = <<<'PS'
$ProgressPreference = 'SilentlyContinue'
$InformationPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
Import-Module VMware.PowerCLI -ErrorAction Stop
Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null
Connect-VIServer -Server '<SERVER>' -User '<USER>' -Password '<PASS>' | Out-Null
& {
<CMD>
}
PS;
$psScript = strtr($psTemplate, [
'<SERVER>' => $server,
'<USER>' => $user,
'<PASS>' => $pass,
'<CMD>' => $cmd,
]);
$encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8'));
$action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded";
try {
$out = $auth->exec($action);
$errOut = $auth->getStdError();
if ($expectJson) {
$trimmed = trim($out);
if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $trimmed, $m)) {
return $m[1];
}
return $trimmed;
}
return $out . ($errOut ? "\n[STDERR]\n" . $errOut : '');
} catch (\Throwable $e) {
return "Error: " . $e->getMessage();
}
}
public function resetGuestPasswordViaPs1(string $vmName, string $guestUser, string $guestPassword, string $newPassword, string $targetUser = 'Administrator'): array|string
{
$psFile = "C:\\CLISave\\AssignIP\\SetGuestPwd.ps1";
$q = fn(string $v) => str_replace("'", "''", $v);
$args = " -VMName '" . $q($vmName) . "'";
$args .= " -GuestUser '" . $q($guestUser) . "'";
$args .= " -GuestPassword '" . $q($guestPassword) . "'";
$args .= " -TargetUser '" . $q($targetUser) . "'";
$args .= " -NewPassword '" . $q($newPassword) . "'";
$cmd = "& '$psFile'$args | Out-String";
$raw = $this->CommandRSP($cmd);
if ($raw === 'Login_Fail' || $raw === false || $raw === null) {
return "Login_Fail";
}
$trim = trim($raw);
$data = json_decode($trim, true);
if (json_last_error() !== JSON_ERROR_NONE) {
if (preg_match('/(\{.*\}|\[.*\])/s', $trim, $m)) {
$data = json_decode($m[1], true);
}
}
return $data ?: $raw;
}
public function changeRDPPortWindows(string $vmName, string $guestUser, string $guestPassword, string $newPort): array|string
{
$psFile = "C:\\CLISave\\AssignIP\\change-rdp-port.ps1";
$q = fn(string $v) => str_replace("'", "''", $v);
$args = " -VMName '" . $q($vmName) . "'";
$args .= " -GuestUser '" . $q($guestUser) . "'";
$args .= " -GuestPassword '" . $q($guestPassword) . "'";
$args .= " -NewPort " . (int) $newPort;
$cmd = "& '$psFile'$args";
$raw = $this->CommandChangePort($cmd, true);
if ($raw === 'Login_Fail' || $raw === false || $raw === null) {
return "Login_Fail";
}
$raw = trim($raw);
if (!preg_match('~(\{(?:[^{}]|(?1))*\}|\[(?:[^\[\]]|(?0))*\])\s*$~s', $raw, $m)) {
if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $raw, $m)) {
$raw = $m[1];
}
} else {
$raw = $m[1];
}
$data = json_decode($raw, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return [
'success' => false,
'error' => 'JSON parsing failed',
'raw_output' => $raw,
'json_error' => json_last_error_msg()
];
}
if (isset($data[0])) {
$data = $data[0];
}
return $data;
}
}