#!/usr/bin/env pwsh # ----------------------------------------------------------------------------------- # vCenter + Posh-ACME Script (Fully Automated, Idempotent, Verbose Logging) # ----------------------------------------------------------------------------------- . /opt/idssys/nodemgmt/conf/powerwall/settings.ps1 $global:helpme = $null $global:responseBody = $null function Show-Failure { param([System.Management.Automation.ErrorRecord]$ErrorRecord) $global:responseBody = $ErrorRecord.Exception.Message $global:helpme = $global:responseBody Write-Host "----------------------------------------" -ForegroundColor Red Write-Host "Status: A system exception was caught." -ForegroundColor Red Write-Host $global:responseBody -ForegroundColor Red Write-Host "The request/response body has been saved to `$global:helpme" -ForegroundColor Red Write-Host "----------------------------------------" -ForegroundColor Red exit 1 } # ---------------------------- # PowerCLI # ---------------------------- if (-not (Get-Module -ListAvailable -Name VMware.PowerCLI)) { Install-Module -Name VMware.PowerCLI -Force -Scope AllUsers } Import-Module VMware.PowerCLI -ErrorAction Stop Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Confirm:$false | Out-Null # ---------------------------- # Connect to vCenter # ---------------------------- try { Write-Host "Connecting to vCenter at $VCENTERHOST ..." -ForegroundColor Cyan $vCenterConn = Connect-VIServer -Server $VCENTERHOST -User $VCENTERUSER -Password $VCENTERPASS -Force Write-Host "Connected to vCenter API. Session established." -ForegroundColor Green } catch { Show-Failure -ErrorRecord $_ } # ---------------------------- # Retrieve VM list (optional) # ---------------------------- try { $vms = Get-VM Write-Host "Retrieved VM list:" -ForegroundColor Cyan $vms | ForEach-Object { Write-Host " - $($_.Name)" } } catch { Write-Host "Unable to retrieve VM list, continuing..." -ForegroundColor Yellow } # ---------------------------- # Posh-ACME # ---------------------------- if (-not (Get-Module -ListAvailable -Name Posh-ACME)) { Install-Module -Name Posh-ACME -Force -Scope AllUsers } Import-Module Posh-ACME -ErrorAction Stop # ---------------------------- # Determine if ACME request is needed # ---------------------------- $certSuccess = $false $existingCert = Get-PAOrder -Domain $VCENTERHOST -ErrorAction SilentlyContinue | Sort-Object Created -Descending | Select-Object -First 1 if ($existingCert -and ($existingCert.Cert.NotAfter -gt (Get-Date).AddDays(30))) { Write-Host "Existing certificate valid >30 days (expires $($existingCert.Cert.NotAfter)), skipping ACME request." -ForegroundColor Green $certFolder = $existingCert.CertFolder $certPath = Join-Path $certFolder "cert.pem" $keyPath = Join-Path $certFolder "privkey.pem" $chainPath = Join-Path $certFolder "chain.pem" $certSuccess = $true } else { if ($PDNSAPI -is [string]) { $securePDNSAPI = ConvertTo-SecureString $PDNSAPI -AsPlainText -Force } else { $securePDNSAPI = $PDNSAPI } $pArgs = @{ PowerDNSApiHost = $WDNSHOST PowerDNSApiKey = $securePDNSAPI PowerDNSUseTLS = $true PowerDNSPort = 443 PowerDNSServerName = 'localhost' } try { Write-Host "Requesting certificate via Posh-ACME..." -ForegroundColor Cyan New-PACertificate -Domain $VCENTERHOST -DnsPlugin PowerDNS -PluginArgs $pArgs -Contact $ACMEEMAIL -AcceptTOS -Verbose -Force -DnsSleep 15 $paOrder = Get-PAOrder | Sort-Object Created -Descending | Select-Object -First 1 $certFolder = $paOrder.CertFolder $certPath = Join-Path $certFolder "cert.pem" $keyPath = Join-Path $certFolder "privkey.pem" $chainPath = Join-Path $certFolder "chain.pem" $certSuccess = $true } catch { Write-Host "ACME request failed: $($_.Exception.Message)" -ForegroundColor Yellow $global:helpme = $_.Exception.Message } } # ---------------------------- # Validate certificate files # ---------------------------- if ($certSuccess) { foreach ($f in @($certPath, $keyPath, $chainPath)) { if (-not (Test-Path $f)) { Write-Host "Certificate file missing: $f" -ForegroundColor Yellow $certSuccess = $false } } } # ---------------------------- # Check if vCenter already has this cert # ---------------------------- $updateNeeded = $true if ($certSuccess) { try { $sessionHeaders = @{ 'vmware-api-session-id' = $vCenterConn.ExtensionData.Content.SessionManager.SessionId } $vcenterCertUri = "https://$VCENTERHOST/rest/vcenter/certificate-management/vcenter/tls" $vCenterCert = Invoke-RestMethod -Uri $vcenterCertUri -Method Get -SkipCertificateCheck -Headers $sessionHeaders -ErrorAction Stop $currentCertPem = $vCenterCert.certificate | Out-String $newCertPem = Get-Content -Path $certPath -Raw if ($currentCertPem -eq $newCertPem) { Write-Host "vCenter already has the latest certificate applied. Skipping upload/apply." -ForegroundColor Green $updateNeeded = $false } } catch { Write-Host "Unable to verify current vCenter certificate, will attempt update." -ForegroundColor Yellow } } # ---------------------------- # Upload & apply if needed # ---------------------------- if ($certSuccess -and $updateNeeded) { try { $body = @{ cert = Get-Content -Path $certPath -Raw key = Get-Content -Path $keyPath -Raw chain = Get-Content -Path $chainPath -Raw } Write-Host "Uploading TLS certificate to vCenter..." -ForegroundColor Cyan Invoke-RestMethod -Uri $vcenterCertUri -Method Post -Body ($body | ConvertTo-Json -Compress) -ContentType 'application/json' -Headers $sessionHeaders -SkipCertificateCheck Write-Host "Certificate uploaded successfully." -ForegroundColor Green $uriApply = "https://$VCENTERHOST/rest/vcenter/certificate-management/vcenter/tls?action=apply" Write-Host "Applying TLS certificate to vCenter..." -ForegroundColor Cyan Invoke-RestMethod -Uri $uriApply -Method Post -Headers $sessionHeaders -SkipCertificateCheck Write-Host "Certificate applied successfully." -ForegroundColor Green } catch { Write-Host "Certificate upload/apply failed: $($_.Exception.Message)" -ForegroundColor Red $global:helpme = $_.Exception.Message } } elseif ($certSuccess -and -not $updateNeeded) { Write-Host "Certificate already current, no changes applied." -ForegroundColor Green } # ---------------------------- # Automatic vpxd restart via REST # ---------------------------- $maxRetries = 20 $retryCount = 0 $restartSucceeded = $false while ($retryCount -lt $maxRetries -and -not $restartSucceeded) { try { $healthUri = "https://$VCENTERHOST/rest/appliance/health/system" Write-Host "Checking vCenter REST health endpoint..." -ForegroundColor Cyan Invoke-RestMethod -Uri $healthUri -Method Get -SkipCertificateCheck -ErrorAction Stop $restartUri = "https://$VCENTERHOST/rest/appliance/system/services/vpxd?action=restart" Write-Host "Requesting vpxd service restart via REST..." -ForegroundColor Cyan Invoke-RestMethod -Uri $restartUri -Method Post -SkipCertificateCheck -ErrorAction Stop Write-Host "vpxd service restart requested successfully." -ForegroundColor Green $restartSucceeded = $true } catch { Write-Host "vpxd REST endpoint not ready, retrying 15 seconds... (Attempt $($retryCount+1)/$maxRetries)" -ForegroundColor Yellow Start-Sleep -Seconds 15 $retryCount++ } } if (-not $restartSucceeded) { Write-Host "Automatic vpxd restart failed after $maxRetries attempts." -ForegroundColor Red Write-Host "Please restart manually via SSH:" -ForegroundColor Red Write-Host "ssh root@$VCENTERHOST 'service-control --stop vpxd; service-control --start vpxd'" -ForegroundColor Red } Write-Host "" Write-Host "Script completed. Check `$global:helpme for any error details." -ForegroundColor Green