271 lines
8.1 KiB
PHP
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;
|
|
}
|
|
}
|