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.
 
    Robocopy flags used:
      /A-:R - Strip read-only attribute from extra files before deletion
      /XJ - Exclude NTFS junction points (handled separately to avoid recursing into them)
      /LOG - Capture robocopy output for diagnostics
 
    It returns an object indicating success and any files that were actively locked.
 
.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 = @()
    $TargetPath  = $TargetPath.TrimEnd('\')

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

        # Execute robocopy mirror to bypass MAX_PATH and bulk-delete standard files.
        # /A-:R strips read-only bits so they don't survive the mirror.
        # /XJ excludes NTFS junction points (handled explicitly below).
        # /LOG captures robocopy output to the temp log for post-mortem diagnostics.
        $roboArgs = "`"$emptyDir`" `"$TargetPath`" /MIR /A-:R /R:0 /W:0 /NP /NFL /NDL /NJH /NJS /XJ /LOG:`"$logFile`""
        Start-Process -FilePath "robocopy.exe" -ArgumentList $roboArgs -Wait -NoNewWindow -ErrorAction Stop

        # Robocopy /XJ skips junction points entirely. Explicitly delete them now,
        # sorted deepest-first so parent junctions are removed after their children.
        if (Test-Path $TargetPath) {
            Get-ChildItem -Path $TargetPath -Recurse -Force -ErrorAction SilentlyContinue |
                Where-Object { $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint } |
                Sort-Object FullName -Descending |
                ForEach-Object {
                    try { [System.IO.Directory]::Delete($_.FullName, $false) } catch {}
                }
        }

        # Final sweep: catch any remaining read-only/hidden files and empty directories
        # that slipped past robocopy (e.g. files locked briefly then released).
        if (Test-Path $TargetPath) {
            Start-Process -FilePath "cmd.exe" -ArgumentList "/c del /f /a /q /s `"$TargetPath\*`" >nul 2>&1" -Wait -NoNewWindow
            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. binary not found in minimal environments)
        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 /a /q /s `"$TargetPath\*`" >nul 2>&1" -Wait -NoNewWindow
            Start-Process -FilePath "cmd.exe" -ArgumentList "/c rmdir /s /q `"$TargetPath`" >nul 2>&1"        -Wait -NoNewWindow
        }
    } finally {
        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

    if ($stillExists) {
        # Gather all files that survived the scorched-earth purge (meaning they are actively locked by the OS)
        $survivors = Get-ChildItem -Path $TargetPath -Recurse -File -Force -ErrorAction SilentlyContinue
        if ($survivors) {
            $lockedItems = $survivors | Select-Object -ExpandProperty FullName
        }
    }

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