Public/Update-EntityBaselineGroup.ps1

function Update-EntityBaselineGroup {
    <#
    .SYNOPSIS
        Remediates a host against a baseline group.
 
        With thanks to Lyuboslav Asenov @ VMWare for providing assistance with new Update Manager API.
 
    .DESCRIPTION
        Makes a call to the VC Integrity API to remediate a host or cluster against a baseline group.
 
    .PARAMETER baselineGroupName
        Name of the baseline group to remediate against.
 
    .PARAMETER entity
        Entity object to remediate against, either a host or a cluster.
 
    .PARAMETER HostRemediationConfig
        Optional. Host remediation configuration object. If this is not specified, the default Update Manager host remediation settings are used.
        To override the default configuration at remediation runtime, generate a custom configuration object using the Update-HostRemediationConfig function.
 
    .INPUTS
        PSObject An entity object for either a cluster or a host.
        Must be of type VMware.VimAutomation.ViCore.Impl.V1.Inventory.ClusterImpl or VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl
 
    .OUTPUTS
        None.
 
    .EXAMPLE
        $VMHost = Get-VMHost -name "esxi01"
        Update-EntityBaselineGroup -BaselineGroupName "Sample Baseline Group" -Entity $VMHost
 
        Remediates host esxi01 against baseline group Sample Baseline Group.
 
    .EXAMPLE
        $Cluster = Get-Cluster -name "vSAN"
        Update-EntityBaselineGroup -BaselineGroupName "Sample Baseline Group" -Entity $Cluster
 
        Remediates cluster vSAN against baseline group Sample Baseline Group.
 
    .EXAMPLE
        $vmHosts | Update-EntityBaselineGroup -baselineGroupName "Test-BaselineGroup01" -Verbose
 
        Remediates all hosts in $vmHosts, one at a time, to baseline group Test-Baselinegroup01
 
    .LINK
        https://github.com/TheDotSource/VUMXtra
 
    .NOTES
        01 13/11/18 Initial version. A McNair
        02 23/12/19 Tidied up synopsis and added verbose output. A McNair
                              Added pipeline input for entity.
        03 22/12/22 Reworked for PowerCLI 12.7 and new API. A McNair
                              Added support for clusters.
                              Added option to override default host remediation settings.
    #>


    [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="Medium")]
    Param
    (
        [Parameter(Mandatory=$true,ValueFromPipeline=$false)]
        [String]$baselineGroupName,
        [Parameter(Mandatory=$true,ValueFromPipeline=$true)]
        [ValidateScript({($_.GetType().toString() -eq "VMware.VimAutomation.ViCore.Impl.V1.Inventory.ClusterImpl") -or ($_.GetType().toString() -eq "VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl")})]
        [PSObject]$entity,
        [Parameter(Mandatory=$false,ValueFromPipeline=$false)]
        [IntegrityApi.HostRemediationScheduleOption]$HostRemediationConfig
    )

    begin {

        Write-Verbose ("Function start.")

        ## Get a VUM service connection object
        try {
            $vumCon = Connect-VUM -ErrorAction stop
            Write-Verbose ("Got VUM connection.")
        } # try
        catch {
            throw ("Failed to connect to VUM instance. " + $_.Exception.Message)
        } # catch


        $reqType = New-Object IntegrityApi.GetBaselineGroupInfoRequestType
        $reqType._this = $vumCon.vumServiceContent.RetrieveVcIntegrityContentResponse.returnval.baselineGroupManager

        ## Verify that the baseline group exists
        for ($i=0; $i -le 100; $i++) {

            $reqType.id = $i

            try {
                $svcRefVum = New-Object IntegrityApi.GetBaselineGroupInfoRequest($reqType) -ErrorAction Stop
                $result = $vumCon.vumWebService.GetBaselineGroupInfo($svcRefVum)

                ## When baseline is found break out of loop to continue function
                if (($result.GetBaselineGroupInfoResponse1).name -eq $baselineGroupName) {

                    $baselineGroup  = $result.GetBaselineGroupInfoResponse1
                    Break

                } # if
            } # try
            catch {
                throw ("Failed to query for baseline group. " + $_.Exception.message)
            } # catch

        } # for


        ## Check we have a baseline group to work with
        if (!$baselineGroup) {
            throw ("The specified baseline group was not found on this VUM instance.")
        } # if
        else {
            Write-Verbose ("Baseline group " + $baselineGroup.name + " was found, ID " + $baselineGroup.key)
        } # else

    } # begin

    process {

        Write-Verbose ("Processing entity " + $entity.name)

        ## Initialise array for leaf entities
        $leafEntities = @()


        ## Things work a little differently depending on cluster or host target entity
        switch ($entity) {

            {$_.GetType().toString() -eq "VMware.VimAutomation.ViCore.Impl.V1.Inventory.VMHostImpl"} {

                ## Leaf entity is the target host
                $leafTypeValue = $entity.id.split("-",2)
                $leafEntity = New-Object IntegrityApi.ManagedObjectReference
                $leafEntity.type = $leafTypeValue[0]
                $leafEntity.Value = $leafTypeValue[1]
                $leafEntities += $leafEntity

                ## Parent entity is the cluster this host belongs to.
                $parentTypeValue = $entity.ParentId.split("-",2)
                $parentEntity = New-Object IntegrityApi.ManagedObjectReference
                $parentEntity.type = $parentTypeValue[0]
                $parentEntity.Value = $parentTypeValue[1]

                ## Specify an entity that we want to check compliance on
                $complianceEntity = $leafEntity

            } # host

            {$_.GetType().toString() -eq "VMware.VimAutomation.ViCore.Impl.V1.Inventory.ClusterImpl"} {

                ## Get all hosts belonging to this cluster
                Write-Verbose ("Entity type is cluster. Getting hosts in this cluster.")

                try {
                    $vmHosts = $entity | Get-vmHost -ErrorAction Stop
                    Write-Verbose ("Got " + $vmHosts.count + " hosts.")
                } # try
                catch {
                    throw ("Failed to get hosts from tagret cluster. " + $_.Exception.Message)
                } # catch


                ## Leaf objects are hosts in this cluster. Create an array of these leaf entities
                foreach ($vmHost in $vmHosts) {

                    $leafTypeValue = $vmHost.id.split("-",2)
                    $leafEntity = New-Object IntegrityApi.ManagedObjectReference
                    $leafEntity.type = $leafTypeValue[0]
                    $leafEntity.Value = $leafTypeValue[1]

                    $leafEntities += $leafEntity

                } # foreach

                ## Parent entity is the target cluster
                $parentTypeValue = $entity.id.split("-",2)
                $parentEntity = New-Object IntegrityApi.ManagedObjectReference
                $parentEntity.type = $parentTypeValue[0]
                $parentEntity.Value = $parentTypeValue[1]

                ## Specify an entity that we want to check compliance on
                $complianceEntity = $parentEntity

            } # cluster

        } # switch


        ## Initiate a scan of the host or cluster
        try {
            Test-Compliance -Entity $entity -ErrorAction Stop | Out-Null
        } # try
        catch {
            throw ("Compliance scan failed on entity. " + $_.Exception.Message)
        } # catch


        ## Query compliance status for specified baseline group
        try {

            $reqType = New-Object IntegrityApi.QueryBaselineGroupComplianceStatusRequestType
            $reqType._this = $vumCon.vumServiceContent.RetrieveVcIntegrityContentResponse.returnval.complianceStatusManager
            $reqType.entity = $complianceEntity

            $svcRefVum = New-Object IntegrityApi.QueryBaselineGroupComplianceStatusRequest($reqType)
            $complianceStatus = ($vumCon.vumWebService.QueryBaselineGroupComplianceStatus($svcRefVum)).QueryBaselineGroupComplianceStatusResponse1 | Where-Object {$_.key -eq $baselineGroup.key}

            Write-Verbose ("Obtained entity compliance status.")
        } # try
        catch {
            throw ("Failed to query compliance status of entity. " + $_.Exception.Message)
        } # catch

        ## Check if this entity is compliant with baseline group or not
        if ($complianceStatus.status -eq "Compliant") {
            Write-Verbose ("Entity is already compliant with baseline group. No further action is required.")
            Break
        } # if


        ## Phase 1 remediation
        ## Initialise IntegrityApi.HostRemediationScheduleOption object and configure
        ## Check is a config object has been supplied, or if we need to pull the configuration from Update Manager defaults.
        if ($HostRemediationConfig) {

            Write-Verbose ("A host remediation configuration object has been specified and will be applied to this remediation.")
        } # if
        else {

            Write-Verbose ("Using Update Manager default host remediation configuration.")

            try {
                $HostRemediationConfig = Get-HostRemediationConfig -ErrorAction Stop
                Write-Verbose ("Successfully queried Update Manager for host remediation settings.")
            } # try
            catch {
                throw ("Failed to query Update Manager for host remediation settings. " + $_.Exception.Message)
            } # catch

        } # else


        ## Initialise IntegrityApi.HostUpgradeOptionManagerOptions object and configure
        $hostUpgradeOptions = New-Object IntegrityApi.HostUpgradeOptionManagerOptions
        $hostUpgradeOptions.ignore3rdPartyModules = $false
        $hostUpgradeOptions.ignore3rdPartyModulesSpecified = $true
        Write-Verbose ("Host upgrade options set.")


        ## Phase 2 remediation
        ## Initialise IntegrityApi.VcIntegrityRemediateOption object, consuming phase 1 objects as input
        ## Initialise IntegrityApi.UpdateManagerBaselineGroupUnit which specifies what baseline group and baselines we want to use
        $remediateOption = New-Object IntegrityApi.VcIntegrityRemediateOption
        $remediateOption.hostScheduler = $HostRemediationConfig
        $remediateOption.hostUpgradeOptions = $hostUpgradeOptions
        $baselineGroupUnit = New-Object IntegrityApi.UpdateManagerBaselineGroupUnit
        $baselineGroupUnit.baselinegroup = $baselineGroup.Key
        Write-Verbose ("Remediation option and group unit objects configured.")


        ## Phase 3 remediation
        ## Initialise IntegrityApi.UpdateManagerRemediationSpec object which consumes phase 2 objects as input
        $remediationSpec = New-Object IntegrityApi.UpdateManagerRemediationSpec
        $remediationSpec.baselineGroupUnit = $baselineGroupUnit
        $remediationSpec.option = $remediateOption
        Write-Verbose ("Remediation spec configured.")


        ## Phase 4 remediation
        ## The phase 3 object is the completed object we send to the API with Leaf and Entity objects to the UpdateManager
        ## The API will kick back a job object we can monitor for progress
        try {

            ## Apply shouldProcess
            if ($PSCmdlet.ShouldProcess($entity.name)) {

                $updateManager = $vumCon.vumServiceContent.RetrieveVcIntegrityContentResponse.returnval.updateManager

                $reqType = New-Object IntegrityApi.RemediateRequestType
                $reqType._this = $updateManager
                $reqType.entity = $parentEntity
                $reqType.leafEntity = $leafEntities
                $reqType.spec = $remediationSpec

                $mofTask = ($vumCon.vumWebService.Remediate_Task($reqType)).Remediate_TaskResponse.returnval

            } # if

            Write-Verbose ("Remediation task started.")

        } # try
        catch {
            throw ("Failed to start remediation task. " + $_.Exception.Message)
        } # catch


        ## Wait 5 seconds to give task a chance to start
        Start-Sleep 5

        ## Wait for remedaition job to complete
        try {
            $jobStatus = Get-Task -id ("Task-" + $MofTask.value) -ErrorAction Stop
        } # try
        catch {
            throw ("Failed to get task object for task " + $MofTask.value)
        } # catch

        Write-Verbose ("Waiting for task to complete.")

        while ($jobStatus.State -eq "Running") {

            Write-Progress -Activity ("Applying Baseline Group to entity " + $entity.name) -Status ($jobStatus.PercentComplete.ToString() + " percent complete.") -PercentComplete $jobStatus.PercentComplete

            Write-Verbose ("Current task status is " + $jobStatus.State)

            Start-Sleep 10

            try {
                $jobStatus = Get-Task -id ("Task-" + $MofTask.value) -ErrorAction Stop
            } # try
            catch {
                throw ("Failed to get task object for task " + $MofTask.value)
            } # catch

        } # while


        Write-Verbose ("Task completed, verifying result.")


        ## Check the job did not fail
        if ($JobStatus.state -eq "Error") {
            throw ("Remediation task failed.")
        }

        Write-Verbose ("Task completed successfully.")
        Write-Verbose ("Completed entity " + $entity.name)

    } # process


    end {

        Write-Verbose ("All entities completed.")

        ## Logoff session
        try {
            $reqType = New-Object IntegrityApi.VciLogoutRequestType -ErrorAction Stop
            $reqType._this = $vumCon.vumServiceContent.RetrieveVcIntegrityContentResponse.returnval.sessionManager
            $svcRefVum = New-Object IntegrityApi.VciLogoutRequest($reqType)
            $vumCon.vumWebService.VciLogout($svcRefVum) | Out-Null

            Write-Verbose ("Disconnected from VUM API.")
        } # try
        catch {
            Write-Warning ("Failed to disconnect from VUM API.")
        } # catch


        Write-Verbose ("Function completed.")

    } # end


} # function