TSG

March 31, 2026 · View on GitHub

Update service can be frequently terminated due to exceeding the maximum configured memory limit. If the service is terminated in this way during preparation, preparation may be unable to complete successfully.

Symptoms

One symptom observed is that update service is continuously terminated in the middle of every update preparation attempt, while update service is waiting for the health check action plan to complete.

Issue Validation

To confirm the scenario that you are encountering is the issue documented in this article, confirm you are seeing the following behavior(s)

PowerShell script

Update service process termination can be detected in the Windows Event logs by examining events in Event Viewer or through PowerShell on the cluster node that is the owner of the Azure Stack HCI Update Service Cluster Group.

Find the current update service owner node:

Get-ClusterGroup "Azure Stack HCI Update Service Cluster Group"

Sample output:

Name                                         OwnerNode State
----                                         --------- -----
Azure Stack HCI Update Service Cluster Group v-Host1   Online

Run the following commands to find events indicating that the update service was terminated due to exceeding the memory limit:

Get-WinEvent -LogName Application | where ProviderName -eq "AzureStack Agent Lifecycle Agent" | where Message -match "Azure Stack HCI Update Service: Memory Error Limit Reached"

Sample output. Note the TimeCreated value to determine that these are recent, relevant events. If the event times are not recent they are likely not relevant:

   ProviderName: AzureStack Agent Lifecycle Agent

TimeCreated                      Id LevelDisplayName Message
-----------                      -- ---------------- -------
10/10/2025 6:46:46 PM             1 Error            Azure Stack HCI Update Service: Memory Error Limit Reached, terminating process. Current private memory usage = 4294967384 bytes, memory limit = 4294967296 bytes.

The same events can be viewed in Event Viewer, in the Windows Logs > Application log.

Cause

There is a bug found in 2509 which is a memory leak in a serializer used by the update service cache. This leak can be exacerbated by a large number of health check result objects. These objects are retained even for installed updates and are reported in the corresponding update object returned by Get-SolutionUpdate.

Mitigation Details

Remove health check result files for installed updates

Health check result files are stored in the C:\ClusterStorage\Infrastructure_1\Shares\SU1_Infrastructure_1> cd .\Updates\HealthCheck\ directory on the infrastructure volume. Within that directory, there will be a subdirectory named System and additional subdirectories containing the names of installed updates like (for example) Solution11.2508.1001.51 or SBE4.1.2508.1. Directories that correspond to updates that are already installed can be cleaned up.

The following command can be used to clean up all the directories. Save this script on one of the cluster nodes and run.

$ErrorActionPreference = "Stop"

Write-Host "Checking for installed updates."
$installedUpdates = Get-SolutionUpdate | where State -eq Installed
$resourceIds = @( $installedUpdates | % { $_.ResourceId.Split('/')[-1] } )
Write-Host "Found $($installedUpdates.Count) installed updates: $($resourceIds -join ", ")"

$removedHC = $false

if ($resourceIds.Count -gt 0)
{
    foreach ($resourceId in $resourceIds)
    {
        $healthCheckPath = Join-Path "C:\ClusterStorage\Infrastructure_1\Shares\SU1_Infrastructure_1\Updates\HealthCheck" $resourceId
        if (Test-Path $healthCheckPath)
        {
            Write-Host "Removing health check directory for $resourceId"
            $removedHC = $true
            Remove-Item -Path $healthCheckPath -Recurse -Force -Verbose
        }
        else
        {
            Write-Host "No health check found for $resourceId at $healthCheckPath"
        }
    }
}

$systemHealthPath = Join-Path "C:\ClusterStorage\Infrastructure_1\Shares\SU1_Infrastructure_1\Updates\HealthCheck" "System"
if (Test-Path $systemHealthPath)
{
    $oldSystemChecks = Get-ChildItem $systemHealthPath | sort LastWriteTime | select -SkipLast 10
    if ($oldSystemChecks)
    {
        Write-Host "Removing $($oldSystemChecks.Count) old system health check results."
        $removedHC = $true
        $oldSystemChecks | Remove-Item -Force -Verbose
    }
    else
    {
        Write-Host "No stale health checks found at $systemHealthPath"
    }
}

if ($removedHC)
{
    Write-Host "Some health checks were removed, so clearing update service cache."
    $clientCert = ls Cert:\LocalMachine\My\ | ? Subject -match "URP" | sort NotAfter | select -last 1
    $updateEndpoint = "https://$(Get-ClusterGroup "Azure Stack HCI Update Service Cluster Group" | % OwnerNode | % Name).$($env:USERDNSDOMAIN):4900"
    $clearCacheResult = Invoke-WebRequest -Certificate $clientCert -UseBasicParsing -Uri "$updateEndpoint/caches/Update" -Method "Delete"
    if ([int]::TryParse($clearCacheResult.Content, [ref]$null))
    {
        Write-Host "Removed $($clearCacheResult.Content) updates from the cache."
    }
    else
    {
        Write-Host "Result from clearing the cache: $($clearCacheResult | Out-String)"
    }
}

Remove failed Update action plan instances

Delete all failed Update action plans except for the last failed one.

Import-Module ECEClient -DisableNameChecking
$failedUpdates = Get-ActionPlanInstances | ? { $_.Status -eq "Failed" -and $_.ActionPlanName -match "MAS Update" } | sort LastModifiedDateTime -Descending | select -Skip 1
$instanceIDs = $failedUpdates.InstanceID

$eceClient = Create-ECEClusterServiceClient
$deleteActionPlanInstanceDescription = New-Object Microsoft.AzureStack.Solution.Deploy.EnterpriseCloudEngine.Controllers.Models.DeleteActionPlanInstanceDescription

foreach ($actionPlanInstanceID in $instanceIDs) {
    # remove old instance
    $deleteActionPlanInstanceDescription.ActionPlanInstanceID = $actionPlanInstanceID
    $eceClient.DeleteActionPlanInstance($deleteActionPlanInstanceDescription).Wait()
}

(Last Resort) Increase update service memory limit

If the problem is still occurring, the final thing to do is to temporarily increase the configured memory limit for the update service.

This can be done by changing the configured value for the memory limit in the update service agent manifest on each node and restarting the ALM agent. To do so, set the expected values at the top of this script in MB (existing suggested values are warning: 5.5GB and error: 6GB) and run this script on every cluster node.

Note

The maximum value allowed by ALM resource governance is 8GB (8192 MB). If the configured error limit exceeds this, the next Solution update will fail at patching the update service. If the error limit is configured higher than 8GB temporarily, it must be reset down to 8192 or lower before taking the next Solution update.

$nodes = Get-ClusterNode | % Name

Write-Host "Running script block on nodes: $($nodes -join ', ')"
Invoke-Command -ComputerName $nodes -ScriptBlock {
    $ErrorActionPreference = "Stop"

    # Values in MB
    $memoryWarningLimit = 5632
    $memoryErrorLimit = 6144

    if ($memoryErrorLimit -lt 4096)
    {
        throw "[$($env:ComputerName)] Current memory limit is 4096. If this TSG is being used, the suggested memory limit should be higher."
    }

    if ($memoryErrorLimit -lt $memoryWarningLimit)
    {
        throw "[$($env:ComputerName)] Warning limit ($memoryWarningLimit) must be lower than the Error limit $($memoryErrorLimit)"
    }

    $updateAgentManifestFileName = ls "C:\Agents\AgentManifests\*Update Service*"
    if ($updateAgentManifestFileName.Count -gt 1)
    {
        throw "[$($env:ComputerName)] Unexpectedly found more than one update service agent manifest in C:\Agents"
    }

    if (-not $updateAgentManifestFileName)
    {
        throw "[$($env:ComputerName)] Unable to find the update agent manifest at C:\Agents\AgentManifests"
    }

    Write-Host "[$($env:ComputerName)] Found update agent manifest at $($updateAgentManifestFileName.FullName)"
    $updateAgentManifest = Get-Content $updateAgentManifestFileName.FullName -Raw | ConvertFrom-Json

    if ($updateAgentManifest.WindowsServiceInstallationParameters.ResourceGovernanceConfiguration.WarningMemoryLimitMB -ne $memoryWarningLimit -or `
        $updateAgentManifest.WindowsServiceInstallationParameters.ResourceGovernanceConfiguration.ErrorMemoryLimitMB -ne $memoryErrorLimit)
    {
        Write-Host "[$($env:ComputerName)] Setting Warning limit to $($memoryWarningLimit)MB and Error limit to $($memoryErrorLimit)MB"
        $updateAgentManifest.WindowsServiceInstallationParameters.ResourceGovernanceConfiguration.WarningMemoryLimitMB = $memoryWarningLimit
        $updateAgentManifest.WindowsServiceInstallationParameters.ResourceGovernanceConfiguration.ErrorMemoryLimitMB = $memoryErrorLimit

        Write-Host "[$($env:ComputerName)] Saving edited agent manifest back to $($updateAgentManifestFileName.FullName)"
        $updateAgentManifest | ConvertTo-Json -Depth 10 | Set-Content -Path $updateAgentManifestFileName.FullName

        Write-Host "[$($env:ComputerName)] Restarting ALM agent to pick up new config."
        Get-Service "AzureStack Agent Lifecycle Agent" | Restart-Service

        $updateService = Get-Service "Azure Stack HCI Update Service"
        if ($updateService.Status -eq "Running")
        {
            Write-Host "[$($env:ComputerName)] Restarting update service to be monitored with new memory limit values."
            $updateService | Restart-Service
        }
        else
        {
            Write-Host "[$($env:ComputerName)] Update service is not primary on this node. Current status: $($updateService.Status)"
        }
    }
    else
    {
        Write-Host "[$($env:ComputerName)] No changes needed. Existing limits are already set. Warning limit $($memoryWarningLimit)MB and Error limit $($memoryErrorLimit)MB"
    }
}