Public/Start-SACInteractive.ps1
|
function Start-SACInteractive { <# .SYNOPSIS Interactive CLI main menu for the Surgical Autodesk Cleaner toolkit. .DESCRIPTION Presents a top-level action menu that surfaces all SAC tools: Surgical Cleanup, Master Purge, User Profile Reset, Licensing Reset, Pre-Flight Scan, and Profile Backup Restore. Supports Out-ConsoleGridView on PowerShell 7+ with automatic fallback to a native console menu. Includes a conditional "View Attention Items" menu option that surfaces critical failure logs from the last run for immediate review. #> [CmdletBinding()] param( [switch]$ScanOnly ) # State for remote targeting if ($null -eq $script:SACTarget) { $script:SACTarget = [PSCustomObject]@{ ComputerName = "localhost" Credential = $null IsRemote = $false } } # Shared helpers (Test-SACRemoteSession and Invoke-SACPause) are imported from the Private folder. # ------------------------------------------------------------------------- # Shared helper: multi-select list (GridView on PS7+, text fallback) # ------------------------------------------------------------------------- function Invoke-SACSelection { param ( [Parameter(Mandatory=$true)][array]$Items, [string]$Title ) if ($Items.Count -eq 0) { return @() } if ($PSVersionTable.PSVersion.Major -ge 7 -and -not (Test-SACRemoteSession)) { if (-not (Get-Command Out-ConsoleGridView -ErrorAction SilentlyContinue)) { try { Import-Module Microsoft.PowerShell.ConsoleGuiTools -ErrorAction Stop } catch { try { Write-Host "Installing Microsoft.PowerShell.ConsoleGuiTools for enhanced UI..." -ForegroundColor Cyan Install-Module Microsoft.PowerShell.ConsoleGuiTools -Force -Scope CurrentUser -ErrorAction Stop Import-Module Microsoft.PowerShell.ConsoleGuiTools -ErrorAction Stop } catch {} } } if (Get-Command Out-ConsoleGridView -ErrorAction SilentlyContinue) { try { $selected = $Items | Out-ConsoleGridView -Title "$Title (CTRL-A = Select All | SPACE = Toggle | ENTER = Confirm)" -OutputMode Multiple -ErrorAction Stop if ($selected) { return @($selected) } return @() } catch { Write-Host "`n[!] UI rendering unavailable. Falling back to text menu..." -ForegroundColor Yellow } } } Write-Host "`n--- $Title ---" -ForegroundColor Cyan Write-Host "0. ALL ITEMS" -ForegroundColor Yellow for ($i = 0; $i -lt $Items.Count; $i++) { Write-Host "$($i+1). $($Items[$i])" } $inputStr = Read-Host "`nEnter numbers to select (comma-separated, or 0 for all)" if ($inputStr -match '0') { return $Items } $selected = @() $indices = $inputStr -split ',' | ForEach-Object { $_.Trim() } foreach ($idx in $indices) { if ($idx -match '^\d+$' -and [int]$idx -gt 0 -and [int]$idx -le $Items.Count) { $selected += $Items[[int]$idx - 1] } } return $selected } # ------------------------------------------------------------------------- # Discover installed Autodesk products for header context # ------------------------------------------------------------------------- function Get-InstalledAutodeskSummary { # Known primary/parent product keywords - these float to the top $PrimaryProducts = @( 'AutoCAD','Revit','Civil 3D','Inventor','Navisworks','3ds Max','Maya', 'Fusion','Vault','ReCap','Advance Steel','BIM 360','InfraWorks' ) $registryBlock = { Get-ItemProperty -Path @( 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' ) -ErrorAction SilentlyContinue | Where-Object { $_.Publisher -match 'Autodesk' -or $_.DisplayName -match 'Autodesk' } | Where-Object { $_.DisplayName -match '\b20\d{2}\b' } | Select-Object -ExpandProperty DisplayName -Unique } $all = if ($script:SACTarget.IsRemote) { Invoke-Command -ComputerName $script:SACTarget.ComputerName -Credential $script:SACTarget.Credential -ScriptBlock $registryBlock -ErrorAction SilentlyContinue } else { & $registryBlock } if (-not $all) { return @() } # Score: 0 = primary product, 1 = everything else $sorted = $all | Sort-Object { $name = $_ $isPrimary = ($PrimaryProducts | Where-Object { $name -match [regex]::Escape($_) }).Count -gt 0 if ($isPrimary) { 0 } else { 1 } }, { $_ } # secondary sort: alphabetical within each group return $sorted } function Invoke-SurgicalCleanupFlow { param ([bool]$ScanOnly = $false, [switch]$BuildScript) Write-Host "`nScanning registry for installed Autodesk products..." -ForegroundColor Cyan $discoveryBlock = { Get-ItemProperty -Path @( 'HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' ) -ErrorAction SilentlyContinue | Where-Object { $_.Publisher -match 'Autodesk' -or $_.DisplayName -match 'Autodesk' } } $UninstallKeys = if ($script:SACTarget.IsRemote) { Invoke-Command -ComputerName $script:SACTarget.ComputerName -Credential $script:SACTarget.Credential -ScriptBlock $discoveryBlock -ErrorAction SilentlyContinue } else { & $discoveryBlock } if (-not $UninstallKeys) { Write-Host "No Autodesk products found on this system." -ForegroundColor Yellow return } $FoundProducts = @() foreach ($app in $UninstallKeys) { if ($app.DisplayName -match '\b(20\d{2})\b') { $year = $matches[1] $FoundProducts += [PSCustomObject]@{ DisplayName = $app.DisplayName Year = [int]$year } } } if ($FoundProducts.Count -eq 0) { Write-Host "No versioned Autodesk products found." -ForegroundColor Yellow return } $AvailableYears = $FoundProducts.Year | Select-Object -Unique | Sort-Object $AllAppNames = @() foreach ($fp in $FoundProducts) { $name = $fp.DisplayName -replace '\s*\b20\d{2}\b.*', '' -replace '^Autodesk\s+', '' $name = $name.Trim() if ([string]::IsNullOrWhiteSpace($name)) { $name = "Autodesk Core" } if ($AllAppNames -notcontains $name) { $AllAppNames += $name } } $AllAppNames = $AllAppNames | Sort-Object Write-Host "`nHow would you like to target?" -ForegroundColor Cyan Write-Host " [1] By Product" Write-Host " [2] By Year" Write-Host " [3] By Product + Year (recommended)" $modeInput = Read-Host "Select [1-3]" if ([string]::IsNullOrWhiteSpace($modeInput) -or $modeInput -notin @("1","2","3")) { $modeInput = "3" } $SelectedProducts = @() $SelectedYears = @() if ($modeInput -eq "1") { $SelectedProducts = Invoke-SACSelection -Items $AllAppNames -Title "Select Products" if (-not $SelectedProducts) { Write-Host "No products selected." -ForegroundColor Red; return } $SelectedYears = $AvailableYears } elseif ($modeInput -eq "2") { $SelectedYears = Invoke-SACSelection -Items $AvailableYears -Title "Select Years" if (-not $SelectedYears) { Write-Host "No years selected." -ForegroundColor Red; return } $SelectedProducts = $AllAppNames } else { $SelectedYears = Invoke-SACSelection -Items $AvailableYears -Title "Step 1 of 2 - Select Target Years" if (-not $SelectedYears) { Write-Host "No years selected." -ForegroundColor Red; return } $FilteredProducts = @() foreach ($fp in ($FoundProducts | Where-Object { $_.Year -in $SelectedYears })) { $name = $fp.DisplayName -replace '\s*\b20\d{2}\b.*', '' -replace '^Autodesk\s+', '' $name = $name.Trim() if ([string]::IsNullOrWhiteSpace($name)) { $name = "Autodesk Core" } if ($FilteredProducts -notcontains $name) { $FilteredProducts += $name } } $FilteredProducts = $FilteredProducts | Sort-Object $SelectedProducts = Invoke-SACSelection -Items $FilteredProducts -Title "Step 2 of 2 - Select Products for $($SelectedYears -join ', ')" if (-not $SelectedProducts) { Write-Host "No products selected." -ForegroundColor Red; return } } if ($ScanOnly) { Write-Host "`nBuilding pre-flight scan report..." -ForegroundColor Cyan $Report = @() foreach ($product in $SelectedProducts) { foreach ($year in $SelectedYears) { $PackageName = "*$product*$year*" $keys = $UninstallKeys | Where-Object { $_.DisplayName -like $PackageName } foreach ($app in $keys) { $actionType = if ($app.PSChildName -match '^{.*}$' -and $app.UninstallString -match '^MsiExec\.exe') { "MSI Uninstall" } else { "Custom Uninstall" } $Report += [PSCustomObject]@{ Action="$actionType"; ComponentType="Application" TargetProduct=$product; TargetYear=$year DisplayName=$app.DisplayName; Detail=$app.UninstallString } } } } # Process discovery (remote-aware) $processDiscoveryBlock = { $ProcessesToKill = @("acad*","AcEventSync*","AcQMod*","revit*","3dsmax*","maya*","inventor*","roamer*","navisworks*","recap*","dwgviewr*") $results = @() foreach ($procPattern in $ProcessesToKill) { Get-Process -Name $procPattern -ErrorAction SilentlyContinue | ForEach-Object { $results += [PSCustomObject]@{ Name = $_.ProcessName; Id = $_.Id } } } return $results } $FoundProcesses = if ($script:SACTarget.IsRemote) { Invoke-Command -ComputerName $script:SACTarget.ComputerName -Credential $script:SACTarget.Credential -ScriptBlock $processDiscoveryBlock -ErrorAction SilentlyContinue } else { & $processDiscoveryBlock } foreach ($p in $FoundProcesses) { $Report += [PSCustomObject]@{ Action="Terminate Process"; ComponentType="Active Process" TargetProduct="Global"; TargetYear="Global" DisplayName="$($p.Name).exe"; Detail="PID: $($p.Id)" } } # File system discovery (remote-aware) $fsDiscoveryBlock = { param($prods, $years) $SafePathsToSearch = @( "$($env:ProgramFiles)\Autodesk","$(${env:ProgramFiles(x86)})\Autodesk", "$($env:ProgramData)\Autodesk","$($env:PUBLIC)\Documents\Autodesk", "C:\Users\*\AppData\Local\Autodesk","C:\Users\*\AppData\Roaming\Autodesk" ) $results = @() foreach ($product in $prods) { foreach ($year in $years) { foreach ($basePath in $SafePathsToSearch) { Get-ChildItem -Path $basePath -Filter "*$product*$year*" -Directory -ErrorAction SilentlyContinue | ForEach-Object { $results += [PSCustomObject]@{ Name = $_.Name; FullName = $_.FullName; Product = $product; Year = $year } } } } } return $results } $FoundDirs = if ($script:SACTarget.IsRemote) { Invoke-Command -ComputerName $script:SACTarget.ComputerName -Credential $script:SACTarget.Credential -ScriptBlock $fsDiscoveryBlock -ArgumentList $SelectedProducts, $SelectedYears -ErrorAction SilentlyContinue } else { & $fsDiscoveryBlock $SelectedProducts $SelectedYears } foreach ($d in $FoundDirs) { $Report += [PSCustomObject]@{ Action="Purge Directory"; ComponentType="Orphaned Folder" TargetProduct=$d.Product; TargetYear=$d.Year DisplayName=$d.Name; Detail=$d.FullName } } if ($Report.Count -gt 0) { $Report = $Report | Select-Object -Unique * $OutPath = "$([Environment]::GetFolderPath('Desktop'))\Autodesk_ScanReport_$(Get-Date -Format 'yyyyMMdd_HHmmss').csv" $Report | Export-Csv -Path $OutPath -NoTypeInformation -Force Write-Host "Scan Complete! $($Report.Count) actions identified." -ForegroundColor Green Write-Host "Report saved to: $OutPath`n" -ForegroundColor Green } else { Write-Host "Scan Complete! No matching components would be removed.`n" -ForegroundColor Yellow } } elseif ($BuildScript) { # ... (BuildScript logic stays largely the same for now, as it generates a local script) # ... (omitted for brevity in this replace call, will keep it if it fits) Write-Host "`nGenerating Deployment Script..." -ForegroundColor Cyan $OutPath = "$([Environment]::GetFolderPath('Desktop'))\SAC_Deployment_$(Get-Date -Format 'yyyyMMdd_HHmmss').ps1" $prodString = ($SelectedProducts | ForEach-Object { "`"$_`"" }) -join ", " $yearString = $SelectedYears -join ", " $command = "Start-SACCleanup -TargetProducts $prodString -TargetYears $yearString -Silent" $scriptContent = @" <# .SYNOPSIS Automated Surgical Autodesk Cleanup Deployment .DESCRIPTION This script is designed to safely and silently remove specific versions of Autodesk products from this workstation. It will automatically download and utilize the SurgicalAutodeskCleaner module from the PowerShell Gallery. Targeted Products: $($SelectedProducts -join ', ') Targeted Years: $($SelectedYears -join ', ') .NOTES Generated by SAC Script Builder on $(Get-Date) #> [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 # Ensure module is installed if (-not (Get-Module -ListAvailable -Name SurgicalAutodeskCleaner)) { Write-Host "Installing SurgicalAutodeskCleaner from PSGallery..." Install-Module -Name SurgicalAutodeskCleaner -Force -AcceptLicense -AllowClobber -Scope CurrentUser } Import-Module SurgicalAutodeskCleaner -Force # Execute silent cleanup Write-Host "Beginning silent Autodesk cleanup..." $command "@ $scriptContent | Out-File -FilePath $OutPath -Encoding utf8 Write-Host "`nDeployment Script Generated!" -ForegroundColor Green Write-Host "Saved to: $OutPath`n" -ForegroundColor Green Write-Host "Raw Command:" -ForegroundColor Cyan Write-Host " $command`n" -ForegroundColor Gray } else { if ($script:SACTarget.IsRemote) { Write-Host "`nDispatching Surgical Cleanup to $($script:SACTarget.ComputerName)..." -ForegroundColor Cyan $prodArgs = ($SelectedProducts | ForEach-Object { "`"$_`"" }) -join "," $yearArgs = $SelectedYears -join "," $remoteCmd = "Start-SACCleanup -TargetProducts $prodArgs -TargetYears $yearArgs -Silent" Invoke-SACRemote -ComputerName $script:SACTarget.ComputerName -Credential $script:SACTarget.Credential -Command $remoteCmd -AutoInstall } else { Write-Host "`nExecuting Surgical Cleanup..." -ForegroundColor Cyan Start-SACCleanup -TargetProducts $SelectedProducts -TargetYears $SelectedYears } } } # ------------------------------------------------------------------------- # Main menu loop # ------------------------------------------------------------------------- $installedProducts = Get-InstalledAutodeskSummary if ($ScanOnly) { Invoke-SurgicalCleanupFlow -ScanOnly $true return } while ($true) { if (-not (Test-SACRemoteSession)) { Clear-Host } else { Write-Host "`n--- SAC Main Menu ---`n" -ForegroundColor Cyan } # Box-drawing characters generated at runtime via [char] casts. # Source file stays pure 7-bit ASCII - no BOM or encoding dependency. $TL = [string][char]0x2554 # top-left corner $TR = [string][char]0x2557 # top-right corner $BL = [string][char]0x255A # bottom-left corner $BR = [string][char]0x255D # bottom-right corner $ML = [string][char]0x2560 # mid-left tee $MR = [string][char]0x2563 # mid-right tee $H = [string][char]0x2550 # horizontal double line $V = [string][char]0x2551 # vertical double line # Fixed inner width (between the vertical borders) = 80 chars $W = 80 function Write-BoxLine { param( [string]$Text = "", [string]$Color = "Cyan", [string]$BorderColor = "DarkCyan" ) $inner = " $Text".PadRight($W) Write-Host $V -ForegroundColor $BorderColor -NoNewline Write-Host $inner -ForegroundColor $Color -NoNewline Write-Host $V -ForegroundColor $BorderColor } $border = $H * $W $borderLine = $V + (" " * $W) + $V # Top border + title $title = " SURGICAL AUTODESK CLEANER v2.3.3-beta" $titlePad = $title.PadLeft(($W + $title.Length) / 2) Write-Host "$TL$border$TR" -ForegroundColor DarkCyan Write-BoxLine -Text $titlePad -Color "Cyan" Write-Host "$ML$border$MR" -ForegroundColor DarkCyan # Detected products if ($installedProducts -and $installedProducts.Count -gt 0) { Write-BoxLine -Text "Detected Products:" -Color "DarkCyan" foreach ($p in ($installedProducts | Select-Object -First 5)) { $truncated = if ($p.Length -gt ($W - 4)) { $p.Substring(0, $W - 7) + "..." } else { $p } Write-BoxLine -Text " $truncated" -Color "Gray" } if ($installedProducts.Count -gt 5) { Write-BoxLine -Text " ...and $($installedProducts.Count - 5) more" -Color "DarkGray" } } else { Write-BoxLine -Text " No versioned Autodesk products detected." -Color "Yellow" } # Last-run status badge if ($script:SACLastRunStatus) { $st = $script:SACLastRunStatus Write-Host "$ML$border$MR" -ForegroundColor DarkCyan if ($st.Criticals -gt 0) { Write-BoxLine -Text " [!] Last Run ($($st.Operation)): Attention Required" -Color "Red" -BorderColor "DarkCyan" Write-BoxLine -Text " [$($st.Criticals)] critical item(s) failed" -Color "Red" -BorderColor "DarkCyan" Write-BoxLine -Text " [$($st.Warnings)] minor notice(s) logged" -Color "DarkGray" -BorderColor "DarkCyan" Write-BoxLine -Text " Log: $($st.LogDir)" -Color "DarkGray" -BorderColor "DarkCyan" } elseif ($st.Warnings -gt 0) { Write-BoxLine -Text " [~] Last Run ($($st.Operation)): Attention Items" -Color "DarkYellow" -BorderColor "DarkCyan" Write-BoxLine -Text " [$($st.Warnings)] minor notice(s) logged" -Color "DarkYellow" -BorderColor "DarkCyan" Write-BoxLine -Text " Log: $($st.LogDir)" -Color "DarkGray" -BorderColor "DarkCyan" } else { Write-BoxLine -Text " [OK] Last Run ($($st.Operation)): Completed successfully" -Color "Green" -BorderColor "DarkCyan" Write-BoxLine -Text " Time elapsed: $($st.Elapsed)" -Color "Green" -BorderColor "DarkCyan" Write-BoxLine -Text " Log: $($st.LogDir)" -Color "DarkGray" -BorderColor "DarkCyan" } } # Menu options Write-Host "$ML$border$MR" -ForegroundColor DarkCyan Write-Host $borderLine -ForegroundColor DarkCyan Write-BoxLine -Text " [1] Surgical Cleanup Targeted uninstall by product/year" Write-BoxLine -Text " [2] Master Purge Scorched-earth full system removal" Write-BoxLine -Text " [3] Reset User Profile Rename/clear per-user AppData and reg" Write-BoxLine -Text " [4] Reset Licensing Wipe CLM, token cache and FlexNet" Write-BoxLine -Text " [5] Pre-Flight Scan Simulate cleanup, export CSV report" Write-BoxLine -Text " [6] Build Cleanup Script Build an SAC script to run later" Write-BoxLine -Text " [7] Restore User Profile List/restore SAC backup folders" if ($script:SACLastRunStatus.AttentionItems -and (Test-Path $script:SACLastRunStatus.AttentionItems)) { $vColor = if ($script:SACLastRunStatus.Criticals -gt 0) { "Red" } else { "Yellow" } Write-BoxLine -Text " [V] View Attention Items Open logs for items requiring attention" -Color $vColor } Write-Host $borderLine -ForegroundColor DarkCyan $targetStr = if ($script:SACTarget.IsRemote) { "$($script:SACTarget.ComputerName) ($($script:SACTarget.Credential.UserName))" } else { "Local Machine" } Write-BoxLine -Text " [T] Target Remote Machine Currently: $targetStr" -Color "Yellow" Write-BoxLine -Text " [Q] Quit" if ($PSVersionTable.PSVersion.Major -lt 7) { Write-Host $borderLine -ForegroundColor DarkCyan Write-BoxLine -Text " Notice: Interactive mode is best experienced in PowerShell 7 (Core)+" -Color "DarkGray" } Write-Host $borderLine -ForegroundColor DarkCyan Write-Host "$BL$border$BR" -ForegroundColor DarkCyan Write-Host "" $choice = Read-Host " Select an option" switch ($choice.Trim().ToUpper()) { "1" { Invoke-SurgicalCleanupFlow -ScanOnly $false Invoke-SACPause } "2" { Write-Host "`n *** MASTER PURGE - This will remove ALL Autodesk software ***" -ForegroundColor Red Write-Host " This action is irreversible. All products, services, and" -ForegroundColor Yellow Write-Host " registry data will be forcefully removed from this machine.`n" -ForegroundColor Yellow $confirm = Read-Host " Type 'PURGE' to confirm" if ($confirm -eq "PURGE") { if ($script:SACTarget.IsRemote) { Write-Host "`n Dispatching Master Purge to $($script:SACTarget.ComputerName)..." -ForegroundColor Cyan Invoke-SACRemote -ComputerName $script:SACTarget.ComputerName -Credential $script:SACTarget.Credential -Command "Start-SACPurge -Silent" -AutoInstall } else { Start-SACPurge } } else { Write-Host " Master Purge cancelled." -ForegroundColor Yellow } Invoke-SACPause } "3" { Write-Host "`n Roaming folders will be RENAMED (backed up) by default." -ForegroundColor Cyan Write-Host " Local cache folders will be DELETED outright.`n" -ForegroundColor Cyan $delRoaming = $false $delChoice = Read-Host " Delete Roaming instead of rename? (y/N)" if ($delChoice.Trim().ToLower() -eq "y") { $delRoaming = $true } if ($script:SACTarget.IsRemote) { Write-Host "`n Dispatching Profile Reset to $($script:SACTarget.ComputerName)..." -ForegroundColor Cyan $flags = if ($delRoaming) { "-DeleteRoaming -Silent" } else { "-Silent" } Invoke-SACRemote -ComputerName $script:SACTarget.ComputerName -Credential $script:SACTarget.Credential -Command "Reset-SACUserProfile $flags" -AutoInstall } else { if ($delRoaming) { Reset-SACUserProfile -DeleteRoaming } else { Reset-SACUserProfile } } Invoke-SACPause } "4" { Write-Host "`n FlexNet (adsk* stubs) removal is optional.`n" -ForegroundColor Cyan $flexChoice = Read-Host " Also remove Autodesk FlexNet stubs? (y/N)" $includeFlex = ($flexChoice.Trim().ToLower() -eq "y") if ($script:SACTarget.IsRemote) { Write-Host "`n Dispatching Licensing Reset to $($script:SACTarget.ComputerName)..." -ForegroundColor Cyan $flags = if ($includeFlex) { "-IncludeFlexNet -Silent" } else { "-Silent" } Invoke-SACRemote -ComputerName $script:SACTarget.ComputerName -Credential $script:SACTarget.Credential -Command "Reset-SACLicensing $flags" -AutoInstall } else { if ($includeFlex) { Reset-SACLicensing -IncludeFlexNet } else { Reset-SACLicensing } } Invoke-SACPause } "5" { Invoke-SurgicalCleanupFlow -ScanOnly $true Invoke-SACPause } "6" { Invoke-SurgicalCleanupFlow -BuildScript Invoke-SACPause } "7" { Restore-SACUserProfile -List Write-Host "" $restoreChoice = Read-Host " Restore a specific backup? Enter full path (or press Enter to return)" if (-not [string]::IsNullOrWhiteSpace($restoreChoice)) { Restore-SACUserProfile -Restore -BackupPath $restoreChoice.Trim() } Invoke-SACPause } "V" { if ($script:SACLastRunStatus.AttentionItems -and (Test-Path $script:SACLastRunStatus.AttentionItems)) { Write-Host "`nOpening Attention Items log in Notepad..." -ForegroundColor Cyan Start-Process "notepad.exe" -ArgumentList "`"$($script:SACLastRunStatus.AttentionItems)`"" } else { Write-Host "`nNo attention items found or log file missing." -ForegroundColor Yellow Start-Sleep -Seconds 1 } } "T" { $comp = Read-Host "`n Enter remote computer name (or 'local' to reset)" if ($comp.Trim().ToLower() -eq "local" -or [string]::IsNullOrWhiteSpace($comp)) { $script:SACTarget = [PSCustomObject]@{ ComputerName = "localhost"; Credential = $null; IsRemote = $false } Write-Host " Target reset to local machine." -ForegroundColor Green } else { $res = Connect-SACTarget -ComputerName $comp.Trim() if ($res.Connected) { $script:SACTarget = [PSCustomObject]@{ ComputerName = $res.ComputerName; Credential = $res.Credential; IsRemote = ($res.ComputerName -ne "localhost") } # Re-scan installed products for the new target $installedProducts = Get-InstalledAutodeskSummary } } Start-Sleep -Seconds 1 } "Q" { Write-Host "`nExiting Surgical Autodesk Cleaner. Goodbye.`n" -ForegroundColor Cyan return } default { Write-Host "`n Invalid option. Please select 1-7 or Q." -ForegroundColor Yellow Start-Sleep -Seconds 1 } } } } |