Private/Invoke-SACRobocopyPurge.ps1

<#
.SYNOPSIS
    Utilizes Robocopy to perform an ultra-fast, scorched-earth directory purge.

.DESCRIPTION
    PowerShell's native Remove-Item is susceptible to MAX_PATH (260 char) limitations
    and can halt prematurely on locked files. This helper creates a temporary empty
    directory and uses robocopy /MIR to mirror the empty state to the target directory.
    This forces Windows to bypass path limits and rapidly delete all contents.
    It returns an array of any files that were actively locked and could not be purged.

.PARAMETER TargetPath
    The absolute path of the directory to be purged.

.EXAMPLE
    $results = Invoke-SACRobocopyPurge -TargetPath "C:\ProgramData\Autodesk"
    if (-not $results.Success) {
        Write-Warning "Locked files: $($results.LockedItems -join ', ')"
    }
#>

function Invoke-SACRobocopyPurge {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [string]$TargetPath
    )

    if (-not (Test-Path $TargetPath)) {
        return [PSCustomObject]@{ Success = $true; LockedItems = @() }
    }

    $emptyDir = Join-Path $env:TEMP ([Guid]::NewGuid().ToString())
    $logFile = Join-Path $env:TEMP ("RCLog_" + [Guid]::NewGuid().ToString() + ".log")
    $lockedItems = @()

    try {
        # Create the temporary empty directory
        New-Item -ItemType Directory -Path $emptyDir -Force | Out-Null

        # Execute robocopy mirror
        # /MIR: Mirror a directory tree (equivalent to /E plus /PURGE)
        # /R:0: 0 retries on failed files (skip locked immediately)
        # /W:0: 0 wait time between retries
        # /NP: No progress (reduces log noise)
        # /UNILOG: Output log as Unicode to handle special characters
        $roboArgs = "`"$emptyDir`" `"$TargetPath`" /MIR /R:0 /W:0 /NP /UNILOG:`"$logFile`""
        $process = Start-Process -FilePath "robocopy.exe" -ArgumentList $roboArgs -Wait -NoNewWindow -PassThru

        # Parse log for failures (e.g. ERROR 32 Sharing Violation)
        if (Test-Path $logFile) {
            # UNILOG writes UTF-16 LE
            $logContent = Get-Content -Path $logFile -Encoding Unicode -ErrorAction SilentlyContinue
            if ($logContent) {
                foreach ($line in $logContent) {
                    # Typical error format:
                    # 2026/05/13 10:10:10 ERROR 32 (0x00000020) Deleting File C:\Target\locked.dll
                    if ($line -match 'ERROR \d+ .* Deleting (?:File|Dir) (.+)') {
                        $lockedItems += $matches[1].Trim()
                    }
                }
            }
        }

        # Robocopy /MIR empties the folder but leaves the top-level directory intact.
        # Use native rmdir to destroy the root target directory.
        if (Test-Path $TargetPath) {
            Start-Process -FilePath "cmd.exe" -ArgumentList "/c rmdir /s /q `"$TargetPath`" >nul 2>&1" -Wait -NoNewWindow
        }

    } catch {
        # Fallback if Robocopy fails to execute (e.g., missing from PATH)
        Write-Warning "Robocopy purge exception ($($_.Exception.Message)). Falling back to cmd.exe deletion..."
        if (Test-Path $TargetPath) {
            Start-Process -FilePath "cmd.exe" -ArgumentList "/c del /f /s /q `"$TargetPath\*`" >nul 2>&1" -Wait -NoNewWindow
            Start-Process -FilePath "cmd.exe" -ArgumentList "/c rmdir /s /q `"$TargetPath`" >nul 2>&1" -Wait -NoNewWindow
        }
    } finally {
        # Cleanup temp items
        if (Test-Path $emptyDir) { Remove-Item -Path $emptyDir -Force -Recurse -ErrorAction SilentlyContinue }
        if (Test-Path $logFile) { Remove-Item -Path $logFile -Force -ErrorAction SilentlyContinue }
    }

    $stillExists = Test-Path $TargetPath
    return [PSCustomObject]@{
        Success = (-not $stillExists)
        LockedItems = $lockedItems
    }
}