Private/Checks/Esx/Test-VcfEsxLockdownStatus.ps1
|
# Copyright (c) 2026 Broadcom. All Rights Reserved. # Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc. # and/or its subsidiaries. # # ============================================================================= # # SOFTWARE LICENSE AGREEMENT # # Copyright (c) CA, Inc. All rights reserved. # # You are hereby granted a non-exclusive, worldwide, royalty-free license # under CA, Inc.'s copyrights to use, copy, modify, and distribute this # software in source code or binary form for use in connection with CA, Inc. # products. # # This copyright notice shall be included in all copies or substantial # portions of the software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. # # ============================================================================= function Test-VcfEsxLockdownStatus { <# .SYNOPSIS Reports lockdown mode status for every ESX host across all vCenters managed by SDDC Manager, flagging hosts that are missing the VCF service account SDDC Manager needs to update them while locked down. .DESCRIPTION Retrieves ESX host inventory for each connected vCenter domain using Get-VcfCheckVMHostInventory and checks the ExtensionData.Config.LockdownMode API property (HostConfigInfo.lockdownMode). ESX can be upgraded while Lockdown Mode is enabled as long as the host's VCF service account ('svc-vcf-<host_shortname>') is present in that host's Lockdown Mode Exception Users list - see https://knowledge.broadcom.com/external/article/435394/esxi-update-fails-with-error-upgrade-is.html. For each host with Lockdown Mode enabled ('lockdownNormal' or 'lockdownStrict'), queries that list via Get-VcfCheckHostLockdownExceptionUsers and reports the host Fail if its service account is absent. Hosts with Lockdown Mode disabled have no such requirement and always Pass. Rolls up results at the cluster level: if all hosts in a cluster share the same lockdown mode and service-account status, shows a single aggregated row ("X hosts in the cluster have an identical lockdown mode"); otherwise expands to show each host individually. Builds a per-vCenter table containing Cluster, Hostname, Status, ServiceAccount, and Result. Returns 'Fail' for a vCenter domain if any host has Lockdown Mode enabled without its VCF service account authorized in the Exception Users list, since SDDC Manager cannot update that host - the Detail field then names every affected account/host pair directly, rather than requiring the reader to cross-reference the Rows table to find which host needs fixing. Returns an 'Error' status for a domain only if an execution failure occurs (e.g., unable to retrieve vCenter FQDNs, connect to a vCenter, or fetch inventory). Iterates through all vCenters managed by SDDC Manager and delegates output generation to New-VcfCheckPerDomainResults to return results grouped per vCenter domain. .PARAMETER Context The VcfCheck.Context object. Must already be connected to SDDC Manager. .PARAMETER DisplayName Optional friendly name for the check, used when returning top-level error results. .OUTPUTS [PSObject[]] Array of per-domain result objects generated by New-VcfCheckPerDomainResults. #> [CmdletBinding()] [OutputType([PSObject[]])] Param ( [Parameter(Mandatory = $true)] [PSObject]$Context, [Parameter(Mandatory = $false)] [String]$DisplayName = '' ) $startedAt = Get-Date $checkId = 'esx_lockdown_status' $catalogEntry = (Get-VcfCheckCatalog)[$checkId] $displayName = if ([String]::IsNullOrEmpty($DisplayName)) { $catalogEntry.displayName } else { $DisplayName } try { $vcenterFqdns = Get-VcfCheckAllVCenterFqdns -Context $Context } catch { return New-VcfCheckResult -CheckId $checkId -Status Error ` -Exception $_.Exception.Message -StartedAt $startedAt -CompletedAt (Get-Date) -DisplayName $displayName } $outcomes = foreach ($vcenterFqdn in $vcenterFqdns) { $iterationStartedAt = Get-Date $outcome = try { Connect-VcfCheckVCenter -Context $Context -Fqdn $vcenterFqdn $hosts = @(Get-VcfCheckVMHostInventory -Server $vcenterFqdn) $hostIndex = 0 $missingAccounts = [System.Collections.Generic.List[String]]::new() $hostsByCluster = @($hosts | ForEach-Object { $hostIndex++ Write-VcfCheckSubProgress -Context $Context -Current $hostIndex -Total $hosts.Count -Label $_.Name $lockdownEnabled = $_.ExtensionData.Config.LockdownMode -ne 'lockdownDisabled' $serviceAccount = 'N/A' if ($lockdownEnabled) { $expectedAccount = "svc-vcf-$($_.Name.Split('.')[0])" $exceptionUsers = @(Get-VcfCheckHostLockdownExceptionUsers -VMHost $_ -Server $vcenterFqdn) $serviceAccount = if ($exceptionUsers -icontains $expectedAccount) { 'Present' } else { 'Missing' } if ($serviceAccount -eq 'Missing') { $missingAccounts.Add("`"$expectedAccount`" is missing from the Exception Users list on `"$($_.Name)`"") } } [PSCustomObject]@{ Cluster = $_.Parent.Name Hostname = $_.Name Status = if ($lockdownEnabled) { 'Enabled' } else { 'Disabled' } ServiceAccount = $serviceAccount Result = if ($serviceAccount -eq 'Missing') { 'Fail' } else { 'Pass' } } } | Group-Object -Property Cluster) $rows = @() foreach ($clusterGroup in $hostsByCluster) { $clusterName = $clusterGroup.Name $hostsInCluster = @($clusterGroup.Group) $uniqueStates = @($hostsInCluster | ForEach-Object { "$($_.Status)|$($_.ServiceAccount)" } | Select-Object -Unique) if ($uniqueStates.Count -eq 1) { $hostCount = $hostsInCluster.Count $rows += [PSCustomObject]@{ Cluster = $clusterName Hostname = "All $hostCount host$(if ($hostCount -ne 1) { 's' }) in the cluster have an identical lockdown mode" Status = $hostsInCluster[0].Status ServiceAccount = $hostsInCluster[0].ServiceAccount Result = $hostsInCluster[0].Result } } else { $rows += @($hostsInCluster | Sort-Object -Property Hostname) } } $rows = @($rows | Sort-Object -Property Cluster) $detail = $null if ($missingAccounts.Count -gt 0) { $detail = $missingAccounts -join '; ' } [PSCustomObject]@{ VCenterFqdn = $vcenterFqdn Status = if (@($rows | Where-Object { $_.Result -eq 'Fail' }).Count -gt 0) { 'Fail' } else { 'Pass' } Detail = $detail Rows = $rows } } catch { [PSCustomObject]@{ VCenterFqdn = $vcenterFqdn Status = 'Error' Detail = $_.Exception.Message Rows = @() } } $outcome | Add-Member -NotePropertyName StartedAt -NotePropertyValue $iterationStartedAt -Force $outcome | Add-Member -NotePropertyName CompletedAt -NotePropertyValue (Get-Date) -Force $outcome } return New-VcfCheckPerDomainResults -Context $Context -PerVCenterOutcome $outcomes -CheckId $checkId ` -StartedAt $startedAt } |