If you use many VKS supervisors, your access tokens expire quickly and you end up logging in to every cluster again. The VCF CLI can handle this for you. The following PowerShell script walks through every saved context and refreshes the tokens automatically.
<#
.SYNOPSIS
Refresh (and if needed re-auth) all VCF CLI contexts non-interactively.
.NOTES
- No TMC special-casing here; treats all contexts the same.
- Fixes the bug where $Args conflicted with PowerShell’s automatic $args.
.PARAMETER Password
SecureString password for this run (no storage).
.PARAMETER PromptForPassword
Prompt for the password securely (no storage).
.PARAMETER SaveCredential
Prompt once and save password securely for reuse (DPAPI file; Credential Manager if available).
.PARAMETER UseSavedCredential
Load previously saved password (DPAPI file or Credential Manager).
.PARAMETER ClearCredential
Remove any stored credential and exit.
.PARAMETER CredTarget
Name for the stored credential (if Credential Manager is used). Default: VCF-SSO.
#>
[CmdletBinding()]
param(
[Parameter(ParameterSetName='Direct', Mandatory=$false)]
[SecureString]$Password,
[Parameter(ParameterSetName='Prompt', Mandatory=$true)]
[switch]$PromptForPassword,
[Parameter(Mandatory=$false)]
[switch]$SaveCredential,
[Parameter(Mandatory=$false)]
[switch]$UseSavedCredential,
[Parameter(Mandatory=$false)]
[switch]$ClearCredential,
[Parameter(Mandatory=$false)]
[string]$CredTarget = 'VCF-SSO'
)
# ---------- Secure storage (DPAPI; optional Windows Credential Manager) ----------
$AppDir = Join-Path $env:APPDATA 'VcfCli'
$CredFile = Join-Path $AppDir 'vcf_sso_cred.xml'
function ConvertTo-PlainText([SecureString]$Secure) {
if (-not $Secure) { return $null }
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($Secure)
try { [Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) }
finally { [Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) }
}
function Save-Password([SecureString]$Secure, [string]$Target) {
if (-not (Test-Path $AppDir)) { [void](New-Item -Type Directory -Path $AppDir -Force) }
$cred = New-Object System.Management.Automation.PSCredential ('vcf', $Secure)
$cred | Export-Clixml -Path $CredFile
$cm = Get-Module -ListAvailable -Name CredentialManager | Select-Object -First 1
if ($cm) {
try {
Import-Module CredentialManager -ErrorAction Stop | Out-Null
$plain = ConvertTo-PlainText $Secure
if ($plain) { New-StoredCredential -Target $Target -UserName 'vcf' -Password $plain -Persist LocalMachine | Out-Null }
} catch { }
}
}
function Load-Password([string]$Target) {
$cm = Get-Module -ListAvailable -Name CredentialManager | Select-Object -First 1
if ($cm) {
try {
Import-Module CredentialManager -ErrorAction Stop | Out-Null
$stored = Get-StoredCredential -Target $Target
if ($stored -and $stored.Password) { return ($stored.Password | ConvertTo-SecureString -AsPlainText -Force) }
} catch { }
}
if (Test-Path $CredFile) {
try {
$cred = Import-Clixml -Path $CredFile
if ($cred -and $cred.Password) { return $cred.Password }
} catch { }
}
return $null
}
function Clear-Password([string]$Target) {
if (Test-Path $CredFile) { Remove-Item $CredFile -Force -ErrorAction SilentlyContinue }
$cm = Get-Module -ListAvailable -Name CredentialManager | Select-Object -First 1
if ($cm) {
try {
Import-Module CredentialManager -ErrorAction Stop | Out-Null
Remove-StoredCredential -Target $Target -ErrorAction SilentlyContinue
} catch { }
}
}
if ($ClearCredential) {
Clear-Password -Target $CredTarget
Write-Host "Stored credential cleared." -ForegroundColor Yellow
return
}
# ---------- Determine password ----------
$SecurePwd = $null
switch ($PSCmdlet.ParameterSetName) {
'Prompt' { $SecurePwd = Read-Host 'Enter VCF password' -AsSecureString }
'Direct' { $SecurePwd = $Password }
default {
if ($UseSavedCredential) {
$SecurePwd = Load-Password -Target $CredTarget
if (-not $SecurePwd) { throw "No saved credential found. Run with -SaveCredential or -PromptForPassword." }
} elseif ($SaveCredential) {
$SecurePwd = Read-Host 'Enter VCF password to save (hidden)' -AsSecureString
Save-Password -Secure $SecurePwd -Target $CredTarget
Write-Host "Credential saved securely." -ForegroundColor Green
} else {
$SecurePwd = Load-Password -Target $CredTarget
if (-not $SecurePwd) { $SecurePwd = Read-Host 'Enter VCF password (not stored)' -AsSecureString }
}
}
}
$PlainPwd = ConvertTo-PlainText $SecurePwd
if (-not $PlainPwd) { throw "No password available." }
# ---------- CLI wrapper (avoid $args conflict; support PS5/PS7) ----------
function Invoke-Vcf {
param([string[]]$ArgList)
# Prefer native invocation with array splatting (PS7+). For PS5, fall back to cmd.exe
if ($PSVersionTable.PSVersion.Major -ge 7) {
$out = & vcf @ArgList 2>&1
} else {
$quoted = $ArgList | ForEach-Object { if ($_ -match '[s"]') { '"' + ($_ -replace '"','"') + '"' } else { $_ } }
$cmd = 'vcf ' + ($quoted -join ' ')
$out = & cmd /c $cmd 2>&1
}
$code = if ($LASTEXITCODE -ne $null) { $LASTEXITCODE } else { 0 }
[pscustomobject]@{ ExitCode = $code; Output = ($out -join "`n") }
}
function Get-VcfContexts {
# Primary attempt
$res = Invoke-Vcf @('context','list')
if ($res.ExitCode -ne 0) { throw "Failed to list contexts:`n$($res.Output)" }
# If we somehow got top-level help, don’t parse it as contexts.
if ($res.Output -match 'Usage:s+vcfb' -or $res.Output -match 'Available command groups:') {
throw "CLI returned help text instead of contexts. Ensure 'vcf context list' works in this shell."
}
$names = @()
foreach ($line in ($res.Output -split "`n")) {
if ($line -match '^s*$' -or $line -match '^s*(NAME|CURRENT|-+)b') { continue }
# Extract the first token (context name), but only if it looks like a context (lowercase/colon/digit/._-)
if ($line -match '^s*(?<name>[a-z0-9][a-z0-9._:-]*)b') {
$n = $Matches['name']
if ($n -ne 'vcf' -and $n -ne 'usage') { $names += $n }
}
}
$names = $names | Sort-Object -Unique
if (-not $names) { throw "No contexts parsed from 'vcf context list'. Raw:`n$($res.Output)" }
return $names
}
function Try-Refresh([string]$ctx) { Invoke-Vcf @('context','refresh', $ctx) }
# ---------- Main ----------
Write-Host "Collecting contexts..." -ForegroundColor Cyan
$contexts = Get-VcfContexts
$env:VCF_CLI_VSPHERE_PASSWORD = $PlainPwd # non-interactive auth for Supervisor/VKS
$failed = @()
try {
Write-Host "Found contexts:" -ForegroundColor Cyan
$contexts | ForEach-Object { Write-Host " - $_" }
foreach ($ctx in $contexts) {
Write-Host "`n[$ctx] Refresh..." -ForegroundColor Yellow
$r = Try-Refresh $ctx
if ($r.ExitCode -eq 0) {
Write-Host "[$ctx] OK (refreshed or token still valid)." -ForegroundColor Green
continue
}
Write-Warning "[$ctx] refresh failed (ExitCode=$($r.ExitCode)). Trying 'use'..."
$u = Invoke-Vcf @('context','use', $ctx)
if ($u.ExitCode -eq 0) {
Write-Host "[$ctx] Re-auth via 'use' OK." -ForegroundColor Green
$r2 = Try-Refresh $ctx
if ($r2.ExitCode -eq 0) { Write-Host "[$ctx] Final refresh OK." -ForegroundColor Green }
} else {
Write-Host "[$ctx] Could not re-auth without an interactive terminal. Check endpoint trust/credentials." -ForegroundColor Red
$failed += $ctx
}
}
}
finally {
$env:VCF_CLI_VSPHERE_PASSWORD = $null
$PlainPwd = $null
}
if ($failed.Count) {
Write-Host "`nManual follow-up required for:" -ForegroundColor Magenta
$failed | ForEach-Object { Write-Host " - $_" }
}



Why this script is handy
- no typing for each cluster
- enter the password once or store it securely for reuse
- cleans up the environment after running
- suitable for lab setups and production installations
Happy lazy logins!