Public/Reset-SACLicensing.ps1

function Reset-SACLicensing {
<#
.SYNOPSIS
    Wipes Autodesk licensing data to force a clean re-authentication.
.DESCRIPTION
    Clears the Autodesk CLM (Central Licensing Manager), AdskLicensing
    service data, per-user SSO token cache, and optionally Autodesk-specific
    FlexNet stubs. Restarts the AdskLicensing service after the wipe.

    This resolves common issues like:
      - "Autodesk keeps asking for activation"
      - "License not found" after account changes
      - Stuck multi-seat license reservations

.PARAMETER SkipServiceRestart
    Switch. Do not restart the AdskLicensing service after clearing data.

.PARAMETER IncludeFlexNet
    Switch. Also remove Autodesk-specific files (adsk*) from C:\ProgramData\FLEXnet\.
    Off by default since FLEXnet is a shared directory used by other software.

.PARAMETER Silent
    Switch. Bypasses the confirmation prompt for RMM/headless execution.

.EXAMPLE
    # Interactive: full licensing reset with service restart
    Reset-SACLicensing

.EXAMPLE
    # RMM: silent reset including FlexNet stubs
    Reset-SACLicensing -IncludeFlexNet -Silent
#>

    [CmdletBinding()]
    param (
        [switch]$SkipServiceRestart,
        [switch]$IncludeFlexNet,
        [switch]$Silent
    )

    $StopWatch = [System.Diagnostics.Stopwatch]::StartNew()
    $script:SACFailures = @()

    $ToDate    = Get-Date -Format 'yyyyMMdd_HHmmss'
    $LogDir    = "C:\temp\AutodeskLicenseReset_$ToDate"
    New-Item -ItemType Directory -Path $LogDir -Force -ErrorAction SilentlyContinue | Out-Null
    $DebugLog  = "$LogDir\LicenseResetDebug.log"
    $TranscriptLog = "$LogDir\LicenseResetTranscript.log"
    Start-Transcript -Path $TranscriptLog -Append -Force | Out-Null

    function Write-Msg {
        param ([string]$Message, [ValidateSet("Info","Success","Warning","Error")][string]$Type = "Info")
        $ts  = "[$(Get-Date -Format 'HH:mm:ss')]"
        $clr = @{ Info="Cyan"; Success="Green"; Warning="Yellow"; Error="Red" }
        Write-Host "$ts $Message" -ForegroundColor $clr[$Type]
    }

    function Write-QuietLog {
        param ([string]$Message)
        Add-Content -Path $DebugLog -Value "[$(Get-Date -Format 'HH:mm:ss')] [DEBUG] $Message"
    }

    function Test-Interactive {
        return [Environment]::UserInteractive -and -not $Silent -and ($host.Name -eq "ConsoleHost" -or $host.Name -match "ISE|VS Code")
    }

    function Remove-TargetPath {
        param ([string]$Path, [string]$Label)
        if (Test-Path $Path) {
            try {
                Remove-Item $Path -Recurse -Force -ErrorAction Stop
                Write-Msg "Cleared: $Label" "Success"
                return "OK"
            } catch {
                Write-QuietLog "Failed to remove $Path : $($_.Exception.Message)"
                $script:SACFailures += [PSCustomObject]@{ Component=$Label; Reason=$_.Exception.Message }
                return "FAILED"
            }
        } else {
            Write-QuietLog "Not found (skipped): $Path"
            return "NOT FOUND"
        }
    }

    Clear-Host
    Write-Msg "==========================================" "Info"
    Write-Msg " SAC LICENSING RESET INITIALIZED"           "Info"
    Write-Msg " Debug Log: $DebugLog"                    "Info"
    Write-Msg "==========================================" "Info"

    if (Test-Interactive) {
        Write-Host "`nThis will wipe all Autodesk licensing tokens and force re-authentication." -ForegroundColor Cyan
        if ($IncludeFlexNet) {
            Write-Host "FlexNet Autodesk stubs will also be removed." -ForegroundColor Yellow
        }
        Write-Host ""
        $resp = Read-Host "Type 'YES' to proceed"
        if ($resp -ne "YES") {
            Write-Msg "Aborted by user." "Warning"
            Stop-Transcript | Out-Null
            return
        }
    } else {
        Write-Msg "Running in silent/non-interactive mode." "Info"
    }

    # --- Stop licensing service before wipe ---
    $LicService = Get-Service -Name "AdskLicensing" -ErrorAction SilentlyContinue
    if ($LicService -and $LicService.Status -eq "Running") {
        Write-Msg "Stopping AdskLicensing service..." "Info"
        try {
            Stop-Service -Name "AdskLicensing" -Force -ErrorAction Stop
            Write-Msg "AdskLicensing service stopped." "Success"
        } catch {
            Write-QuietLog "Failed to stop AdskLicensing: $($_.Exception.Message)"
            Write-Msg "Warning: Could not stop AdskLicensing service. Files may be locked." "Warning"
        }
    }

    # --- System-wide licensing paths ---
    $SystemTargets = @(
        @{ Path = "$($env:ProgramData)\Autodesk\CLM";            Label = "ProgramData CLM" },
        @{ Path = "$($env:ProgramData)\Autodesk\AdskLicensing";  Label = "ProgramData AdskLicensing" },
        @{ Path = "HKLM:\SOFTWARE\Autodesk\CLM";                 Label = "Registry HKLM CLM" },
        @{ Path = "HKLM:\SOFTWARE\Wow6432Node\Autodesk\CLM";     Label = "Registry HKLM CLM (WOW64)" }
    )

    foreach ($target in $SystemTargets) {
        Remove-TargetPath -Path $target.Path -Label $target.Label | Out-Null
    }

    # --- Per-user licensing token paths ---
    $UserProfiles = Get-ChildItem -Path "C:\Users" -Directory -ErrorAction SilentlyContinue |
        Where-Object { $_.Name -notmatch "^(Public|Default|Default User|All Users)$" }

    foreach ($profile in $UserProfiles) {
        $userName = $profile.Name

        $UserTargets = @(
            @{ Path = "$($profile.FullName)\AppData\Roaming\Autodesk\CLM";            Label = "[$userName] Roaming CLM cache" },
            @{ Path = "$($profile.FullName)\AppData\Local\Autodesk\Web Services";     Label = "[$userName] Local Web Services (SSO tokens)" },
            @{ Path = "$($profile.FullName)\AppData\Local\Autodesk\Autodesk Desktop App"; Label = "[$userName] Desktop App manager cache" }
        )

        foreach ($target in $UserTargets) {
            Remove-TargetPath -Path $target.Path -Label $target.Label | Out-Null
        }
    }

    # --- Optional: FlexNet Autodesk stubs ---
    if ($IncludeFlexNet) {
        Write-Msg "Scanning FLEXnet for Autodesk-specific stubs..." "Info"
        $FlexNetDir = "C:\ProgramData\FLEXnet"
        if (Test-Path $FlexNetDir) {
            $adskFiles = Get-ChildItem -Path $FlexNetDir -File -ErrorAction SilentlyContinue |
                Where-Object { $_.Name -match "^adsk" }
            foreach ($f in $adskFiles) {
                try {
                    Remove-Item $f.FullName -Force -ErrorAction Stop
                    Write-Msg "Removed FLEXnet stub: $($f.Name)" "Success"
                } catch {
                    Write-QuietLog "Failed to remove FLEXnet file $($f.FullName): $($_.Exception.Message)"
                    $script:SACFailures += [PSCustomObject]@{ Component="FLEXnet: $($f.Name)"; Reason=$_.Exception.Message }
                }
            }
            if ($adskFiles.Count -eq 0) {
                Write-Msg "No Autodesk FLEXnet stubs found." "Info"
            }
        } else {
            Write-Msg "FLEXnet directory not found — skipping." "Info"
        }
    }

    # --- Restart licensing service ---
    if (-not $SkipServiceRestart) {
        $LicService = Get-Service -Name "AdskLicensing" -ErrorAction SilentlyContinue
        if ($LicService) {
            Write-Msg "Restarting AdskLicensing service..." "Info"
            try {
                Start-Service -Name "AdskLicensing" -ErrorAction Stop
                Write-Msg "AdskLicensing service restarted successfully." "Success"
            } catch {
                Write-QuietLog "Failed to restart AdskLicensing: $($_.Exception.Message)"
                Write-Msg "AdskLicensing could not be restarted. You may need to reboot." "Warning"
            }
        } else {
            Write-Msg "AdskLicensing service not present on this machine — skipping restart." "Info"
        }
    }

    $StopWatch.Stop()
    $ElapsedTime = "{0:mm} min {0:ss} sec" -f $StopWatch.Elapsed

    Write-Msg "==========================================" "Info"
    Write-Msg " LICENSING RESET COMPLETED in $ElapsedTime" "Success"
    Write-Msg "==========================================" "Info"

    if ($script:SACFailures.Count -gt 0) {
        Write-Host "`n[!] FAILURES DETECTED:" -ForegroundColor Red
        foreach ($fail in $script:SACFailures) {
            Write-Host " - $($fail.Component)" -ForegroundColor Yellow
            Write-Host " Reason: $($fail.Reason)" -ForegroundColor DarkGray
        }
        Write-Host "`nPlease review the Debug Log: $DebugLog`n" -ForegroundColor Red
    } else {
        Write-Host "`n[*] All licensing data cleared. Autodesk will prompt for re-authentication on next launch.`n" -ForegroundColor Green
    }

    Stop-Transcript | Out-Null
}