Public/Retry/TransientErrorStrategies/New-FileLockRetryStrategy.ps1

<#
.NOTES
    Dot-sourced by Infrastructure.Common.psm1. The public surface is
    New-FileLockRetryStrategy; Test-FileLockException is a file-private
    helper kept alongside the factory so the classification policy lives
    next to its sole consumer.
#>


# ---------------------------------------------------------------------------
# Test-FileLockException (private)
# Returns $true when the failure looks like another process holds a
# handle on the target file. The canonical case is Hyper-V's VMMS
# service releasing VHDX handles asynchronously after Remove-VM:
# Remove-Item then throws System.IO.IOException
# ("The process cannot access the file ... because it is being used by
# another process") for a few seconds.
#
# The helper walks the InnerException chain because higher-level
# PowerShell errors often wrap the IOException several layers deep.
#
# UnauthorizedAccessException is intentionally NOT matched: it signals
# a permissions problem that will not resolve on its own, so retrying
# would just stall the caller before the real error surfaces.
# ---------------------------------------------------------------------------

function Test-FileLockException {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [System.Management.Automation.ErrorRecord] $ErrorRecord
    )

    $ex = $ErrorRecord.Exception
    while ($null -ne $ex) {
        if ($ex -is [System.IO.IOException]) {
            return $true
        }
        $ex = $ex.InnerException
    }

    return $false
}

function New-FileLockRetryStrategy {
    <#
    .SYNOPSIS
        Builds a retry strategy hashtable that matches file-lock failures
        (System.IO.IOException anywhere in the exception chain).
 
    .DESCRIPTION
        Returned shape is the standard retry-strategy contract consumed by
        Invoke-WithRetry:
 
            @{
                Name = 'FileLock'
                ShouldRetry = { param($err) <bool> }
            }
 
        Designed for the Hyper-V VMMS handle-release case where
        Remove-Item briefly fails with IOException after Remove-VM
        completes. UnauthorizedAccessException is not matched - it is
        a permissions problem, not a transient lock.
 
    .EXAMPLE
        Invoke-WithRetry `
            -ScriptBlock { Remove-Item -Path $vhdxPath -Force -ErrorAction Stop } `
            -RetryStrategy (New-FileLockRetryStrategy) `
            -MaxAttempts 5
    #>

    [CmdletBinding()]
    param()

    return @{
        Name        = 'FileLock'
        ShouldRetry = {
            param([System.Management.Automation.ErrorRecord] $ErrorRecord)
            Test-FileLockException -ErrorRecord $ErrorRecord
        }
    }
}