OfficeScrubC2R-Utilities.psm1
# OfficeScrubC2R-Utilities.psm1 # PowerShell utilities with C# inline code for performance #region Script Variables $script:SCRIPT_VERSION = "2.19" $script:SCRIPT_NAME = "OfficeScrubC2R" # Error codes (matching VBS) $script:ERROR_SUCCESS = 0 $script:ERROR_FAIL = 1 $script:ERROR_REBOOT_REQUIRED = 2 $script:ERROR_USERCANCEL = 4 $script:ERROR_STAGE1 = 8 $script:ERROR_STAGE2 = 16 $script:ERROR_INCOMPLETE = 32 $script:ERROR_DCAF_FAILURE = 64 $script:ERROR_ELEVATION_USERDECLINED = 128 $script:ERROR_ELEVATION = 256 $script:ERROR_SCRIPTINIT = 512 $script:ERROR_RELAUNCH = 1024 $script:ERROR_UNKNOWN = 2048 # Registry constants $script:REG_ARP = "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\" # Global state $script:ErrorCode = 0 $script:RebootRequired = $false $script:IsElevated = $false $script:Is64Bit = $false $script:OSInfo = "" $script:LogStream = $null $script:LogDir = "" $script:ScrubDir = "" # Dictionaries $script:InstalledSku = @{} $script:C2RSuite = @{} $script:KeepSku = @{} $script:KeepFolder = @{} $script:DelInUse = @{} # Environment paths $script:ProgramFiles = "" $script:ProgramFilesX86 = "" $script:CommonProgramFiles = "" $script:CommonProgramFilesX86 = "" $script:ProgramData = "" $script:AppData = "" $script:LocalAppData = "" $script:AllUsersProfile = "" $script:WinDir = "" $script:Temp = "" $script:WICacheDir = "" # Native orchestrator $script:Orchestrator = $null #endregion #region C# Type Loading function Initialize-NativeTypes { # Try to load the pre-compiled DLL first $dllPath = Join-Path $PSScriptRoot "OfficeScrubNative.dll" if (Test-Path $dllPath) { try { Add-Type -Path $dllPath -ErrorAction Stop Write-Verbose "Native C# types loaded from DLL successfully" return } catch { if ($_.Exception.Message -notlike "*already exists*") { Write-Warning "Failed to load DLL, falling back to source compilation: $_" } else { Write-Verbose "Native C# types already loaded" return } } } # Fallback: compile from source $csharpPath = Join-Path $PSScriptRoot "OfficeScrubC2R-Native.cs" if (-not (Test-Path $csharpPath)) { throw "Neither DLL nor C# source file found. Expected: $dllPath or $csharpPath" } Write-Verbose "Compiling C# source (DLL not found)" $csharpCode = Get-Content $csharpPath -Raw try { # For Windows PowerShell 5.1 (.NET Framework), use simpler assembly references # For PowerShell 7+ (.NET Core), we would need more specific assemblies if ($PSVersionTable.PSVersion.Major -ge 6) { # PowerShell 7+ (.NET Core) $assemblies = @( "System", "System.Core", "System.Collections", "System.Linq", "System.Management", "System.Threading.Thread", "System.ComponentModel.Primitives", "System.Diagnostics.Process", "System.IO.FileSystem", "System.Runtime", "Microsoft.CSharp", "Microsoft.Win32.Registry", "mscorlib", "netstandard" ) } else { # Windows PowerShell 5.1 (.NET Framework) - much simpler! $assemblies = @( "System", "System.Core", "System.Management", "Microsoft.CSharp" ) } Add-Type -TypeDefinition $csharpCode -Language CSharp ` -ReferencedAssemblies $assemblies -ErrorAction Stop Write-Verbose "Native C# types loaded from source successfully" } catch { if ($_.Exception.Message -notlike "*already exists*") { throw "Failed to load C# types: $_" } } } #endregion #region Environment Initialization function Initialize-Environment { [CmdletBinding()] param() # Load C# types Initialize-NativeTypes # Initialize orchestrator $script:Is64Bit = [Environment]::Is64BitOperatingSystem $script:Orchestrator = New-Object OfficeScrubNative.OfficeScrubOrchestrator($script:Is64Bit) # Set environment paths $script:ProgramFiles = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFiles) $script:CommonProgramFiles = [Environment]::GetFolderPath([Environment+SpecialFolder]::CommonProgramFiles) $script:ProgramData = [Environment]::GetFolderPath([Environment+SpecialFolder]::CommonApplicationData) $script:AppData = [Environment]::GetFolderPath([Environment+SpecialFolder]::ApplicationData) $script:LocalAppData = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData) $script:AllUsersProfile = [Environment]::GetFolderPath([Environment+SpecialFolder]::CommonDesktopDirectory) | Split-Path -Parent $script:WinDir = $env:windir $script:Temp = [System.IO.Path]::GetTempPath() if ($script:Is64Bit) { $script:ProgramFilesX86 = ${env:ProgramFiles(x86)} $script:CommonProgramFilesX86 = ${env:CommonProgramFiles(x86)} } else { $script:ProgramFilesX86 = $script:ProgramFiles $script:CommonProgramFilesX86 = $script:CommonProgramFiles } $script:WICacheDir = Join-Path $script:WinDir "Installer" $script:ScrubDir = Join-Path $script:Temp $script:SCRIPT_NAME if (-not (Test-Path $script:ScrubDir)) { New-Item -Path $script:ScrubDir -ItemType Directory -Force | Out-Null } $script:LogDir = $script:ScrubDir } function Get-SystemInfo { [CmdletBinding()] param() $os = Get-CimInstance -ClassName Win32_OperatingSystem $cs = Get-CimInstance -ClassName Win32_ComputerSystem $script:OSInfo = "{0}, Version: {1}, Architecture: {2}" -f ` $os.Caption, $os.Version, $cs.SystemType $script:Is64Bit = $cs.SystemType -like "*64*" } function Test-IsElevated { $identity = [Security.Principal.WindowsIdentity]::GetCurrent() $principal = New-Object Security.Principal.WindowsPrincipal($identity) return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) } #endregion #region Logging Functions function Initialize-Log { [CmdletBinding()] param( [string]$LogPath = $script:LogDir ) $computerName = $env:COMPUTERNAME $timestamp = Get-Date -Format "yyyyMMddHHmmss" $logFile = Join-Path $LogPath ("{0}_{1}_ScrubLog.txt" -f $computerName, $timestamp) try { $script:LogStream = [System.IO.StreamWriter]::new($logFile, $false, [System.Text.Encoding]::UTF8) $script:LogStream.AutoFlush = $true Write-LogHeader "Microsoft Customer Support Services - Office C2R Removal Utility" Write-Log ("Version: {0}" -f $script:SCRIPT_VERSION) Write-Log ("64-bit OS: {0}" -f $script:Is64Bit) Write-Log ("Removal start: {0}" -f (Get-Date)) Write-Log ("OS Details: {0}" -f $script:OSInfo) Write-Log "" } catch { Write-Warning "Failed to initialize log: $_" } } function Write-LogHeader { [CmdletBinding()] param([string]$Message) $separator = "=" * $Message.Length $output = "`r`n{0}`r`n{1}" -f $Message, $separator if (-not $script:Quiet) { Write-Host $output -ForegroundColor Cyan } if ($script:LogStream) { $script:LogStream.WriteLine("") $script:LogStream.WriteLine($output) } } function Write-LogSubHeader { [CmdletBinding()] param([string]$Message) $separator = "-" * $Message.Length $output = "`r`n{0}`r`n{1}" -f $Message, $separator if (-not $script:Quiet) { Write-Host $output -ForegroundColor Yellow } if ($script:LogStream) { $script:LogStream.WriteLine("") $script:LogStream.WriteLine($output) } } function Write-Log { [CmdletBinding()] param([string]$Message) $timestamp = Get-Date -Format "HH:mm:ss" $output = " {0}: {1}" -f $timestamp, $Message if (-not $script:Quiet) { Write-Host $output } if ($script:LogStream) { $script:LogStream.WriteLine($output) } } function Write-LogOnly { [CmdletBinding()] param([string]$Message) if ($script:LogStream) { $timestamp = Get-Date -Format "HH:mm:ss" $script:LogStream.WriteLine(" {0}: {1}" -f $timestamp, $Message) } } function Close-Log { if ($script:LogStream) { $script:LogStream.Flush() $script:LogStream.Close() $script:LogStream.Dispose() $script:LogStream = $null } } #endregion #region Error Handling function Set-ErrorCode { [CmdletBinding()] param([int]$ErrorBit) $script:ErrorCode = $script:ErrorCode -bor $ErrorBit # Cascade critical errors to FAIL bit $criticalErrors = $script:ERROR_DCAF_FAILURE -bor $script:ERROR_STAGE2 -bor $script:ERROR_ELEVATION_USERDECLINED -bor $script:ERROR_ELEVATION -bor $script:ERROR_SCRIPTINIT if ($script:ErrorCode -band $criticalErrors) { $script:ErrorCode = $script:ErrorCode -bor $script:ERROR_FAIL } } function Clear-ErrorCode { [CmdletBinding()] param([int]$ErrorBit) $script:ErrorCode = $script:ErrorCode -band (-bnot $ErrorBit) # Clear FAIL bit if clearing critical errors $clearableErrors = $script:ERROR_ELEVATION_USERDECLINED -bor $script:ERROR_ELEVATION -bor $script:ERROR_SCRIPTINIT if ($ErrorBit -band $clearableErrors) { $script:ErrorCode = $script:ErrorCode -band (-bnot $script:ERROR_FAIL) } } function Set-ReturnValue { [CmdletBinding()] param([int]$Value) $retValFile = Join-Path $script:ScrubDir "ScrubRetValFile.txt" try { [System.IO.File]::WriteAllText($retValFile, $Value.ToString()) } catch { Write-LogOnly "Failed to write return value file: $_" } } #endregion #region Registry Operations (using C# helpers) function Get-RegistryValue { [CmdletBinding()] param( [OfficeScrubNative.RegistryHiveType]$Hive, [string]$SubKey, [string]$ValueName, [object]$DefaultValue = $null ) return $script:Orchestrator.Registry.GetValue($Hive, $SubKey, $ValueName, $DefaultValue) } function Set-RegistryValue { [CmdletBinding()] param( [OfficeScrubNative.RegistryHiveType]$Hive, [string]$SubKey, [string]$ValueName, [object]$Value, [Microsoft.Win32.RegistryValueKind]$Kind = [Microsoft.Win32.RegistryValueKind]::String ) return $script:Orchestrator.Registry.SetValue($Hive, $SubKey, $ValueName, $Value, $Kind) } function Remove-RegistryKey { [CmdletBinding()] param( [OfficeScrubNative.RegistryHiveType]$Hive, [string]$SubKey, [switch]$Recursive = $true ) if (-not $script:DetectOnly) { Write-LogOnly "Delete registry key: $($Hive)\$SubKey" return $script:Orchestrator.Registry.DeleteKey($Hive, $SubKey, $Recursive) } else { Write-LogOnly "Preview mode. Would delete registry key: $($Hive)\$SubKey" return $false } } function Remove-RegistryValue { [CmdletBinding()] param( [OfficeScrubNative.RegistryHiveType]$Hive, [string]$SubKey, [string]$ValueName ) if (-not $script:DetectOnly) { Write-LogOnly "Delete registry value: $($Hive)\$SubKey -> $ValueName" return $script:Orchestrator.Registry.DeleteValue($Hive, $SubKey, $ValueName) } else { Write-LogOnly "Preview mode. Would delete registry value: $($Hive)\$SubKey -> $ValueName" return $false } } function Get-RegistryKeys { [CmdletBinding()] param( [OfficeScrubNative.RegistryHiveType]$Hive, [string]$SubKey ) return $script:Orchestrator.Registry.EnumerateKeys($Hive, $SubKey) } function Get-RegistryValues { [CmdletBinding()] param( [OfficeScrubNative.RegistryHiveType]$Hive, [string]$SubKey ) return $script:Orchestrator.Registry.EnumerateValues($Hive, $SubKey) } function Test-RegistryKeyExists { [CmdletBinding()] param( [OfficeScrubNative.RegistryHiveType]$Hive, [string]$SubKey ) return $script:Orchestrator.Registry.KeyExists($Hive, $SubKey) } #endregion #region File Operations (using C# helpers) function Remove-FolderRecursive { [CmdletBinding()] param( [string]$Path, [switch]$Force ) if (-not (Test-Path $Path)) { return $true } if (-not $script:DetectOnly) { Write-LogOnly "Delete folder: $Path" $result = $script:Orchestrator.Files.DeleteDirectory($Path, $true, $true) if (-not $result) { Write-Log "Failed to delete folder, scheduled for reboot: $Path" $script:RebootRequired = $true Set-ErrorCode $script:ERROR_REBOOT_REQUIRED } return $result } else { Write-LogOnly "Preview mode. Would delete folder: $Path" return $false } } function Remove-FileForced { [CmdletBinding()] param( [string]$Path, [switch]$ScheduleOnFail ) if (-not (Test-Path $Path)) { return $true } if (-not $script:DetectOnly) { Write-LogOnly "Delete file: $Path" $result = $script:Orchestrator.Files.DeleteFile($Path, $ScheduleOnFail) if (-not $result -and $ScheduleOnFail) { Write-Log "Failed to delete file, scheduled for reboot: $Path" $script:RebootRequired = $true Set-ErrorCode $script:ERROR_REBOOT_REQUIRED } return $result } else { Write-LogOnly "Preview mode. Would delete file: $Path" return $false } } function Add-PendingFileDelete { [CmdletBinding()] param([string]$Path) $script:Orchestrator.Registry.AddPendingFileRenameOperation($Path) $script:DelInUse[$Path] = $Path $script:RebootRequired = $true Set-ErrorCode $script:ERROR_REBOOT_REQUIRED } #endregion #region Process Operations function Stop-OfficeProcesses { [CmdletBinding()] param([switch]$Force) Write-LogSubHeader "Stopping Office processes" $processes = [OfficeScrubNative.OfficeConstants]::OFFICE_PROCESSES $terminated = $script:Orchestrator.Processes.TerminateProcesses($processes, 10000) if ($terminated.Count -gt 0) { Write-Log ("Terminated {0} Office process(es)" -f $terminated.Count) Start-Sleep -Seconds 2 } } function Test-ProcessRunning { [CmdletBinding()] param([string]$ProcessName) return $script:Orchestrator.Processes.IsProcessRunning($ProcessName) } #endregion #region Product Detection function Get-InstalledOfficeProducts { [CmdletBinding()] param() Write-LogSubHeader "Detect installed products" $products = @{} $script:C2RSuite = @{} # O15 Configuration Write-LogOnly "Check for O15 C2R products" $o15Products = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\15.0\ClickToRun\Configuration" ` -ValueName "ProductReleaseIds" if ($o15Products) { foreach ($prod in ($o15Products -split ',')) { Write-LogOnly "Found O15 C2R product in Configuration: $prod" $version = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\15.0\ClickToRun\ProductReleaseIDs\Active\culture" ` -ValueName "x-none" $products[$prod.ToLower()] = $version $script:C2RSuite[$prod] = "$prod - $version" } } # O15 PropertyBag $o15PropBag = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\15.0\ClickToRun\propertyBag" ` -ValueName "productreleaseid" if ($o15PropBag) { foreach ($prod in ($o15PropBag -split ',')) { Write-LogOnly "Found O15 C2R product in PropertyBag: $prod" if (-not $products.ContainsKey($prod.ToLower())) { $products[$prod.ToLower()] = "15.0" $script:C2RSuite[$prod] = "$prod - 15.0" } } } # Office C2R (QR8+) Write-LogOnly "Check for Office C2R products (>=QR8)" $activeConfig = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs" ` -ValueName "ActiveConfiguration" if ($activeConfig) { $configKeys = Get-RegistryKeys -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs\$activeConfig" foreach ($key in $configKeys) { if ($key -notin @("culture", "stream")) { $prod = $key if ($prod -like "*.*") { $prod = $prod.Substring(0, $prod.IndexOf('.')) } Write-LogOnly "Found Office C2R product in Configuration: $prod" $version = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs\$activeConfig\$key\x-none" ` -ValueName "Version" if (-not $products.ContainsKey($prod.ToLower())) { $products[$prod.ToLower()] = $version $script:C2RSuite[$prod] = "$prod - $version" } } } } # Office C2R (QR7) Write-LogOnly "Check for Office C2R products (QR7)" $qr7Products = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\ClickToRun\Configuration" ` -ValueName "ProductReleaseIds" if ($qr7Products) { foreach ($prod in ($qr7Products -split ',')) { Write-LogOnly "Found Office C2R product in Configuration: $prod" if (-not $products.ContainsKey($prod.ToLower())) { $version = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs\Active\culture" ` -ValueName "x-none" $products[$prod.ToLower()] = $version $script:C2RSuite[$prod] = "$prod - $version" } } } # O16 Configuration (QR6) Write-LogOnly "Check for O16 C2R products (QR6)" $o16Products = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\16.0\ClickToRun\Configuration" ` -ValueName "ProductReleaseIds" if ($o16Products) { foreach ($prod in ($o16Products -split ',')) { Write-LogOnly "Found O16 (QR6) C2R product in Configuration: $prod" if (-not $products.ContainsKey($prod.ToLower())) { $version = Get-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Office\16.0\ClickToRun\ProductReleaseIDs\culture" ` -ValueName "x-none" $products[$prod.ToLower()] = $version $script:C2RSuite[$prod] = "$prod - $version" } } } # ARP Check Write-LogOnly "Check ARP for Office C2R products" $arpKeys = Get-RegistryKeys -Hive LocalMachine -SubKey $script:REG_ARP foreach ($arpKey in $arpKeys) { $uninstallString = Get-RegistryValue -Hive LocalMachine ` -SubKey "$($script:REG_ARP)$arpKey" ` -ValueName "UninstallString" if ($uninstallString -and (($uninstallString -like "*Microsoft Office 1*") -or ($uninstallString -like "*OfficeClickToRun.exe*"))) { $displayVersion = Get-RegistryValue -Hive LocalMachine ` -SubKey "$($script:REG_ARP)$arpKey" ` -ValueName "DisplayVersion" # Extract product ID from uninstall string if ($uninstallString -match "productstoremove=([^\s]+)") { $prod = $matches[1] -replace "_.*", "" -replace "\.1.*", "" Write-LogOnly "Found C2R product in ARP: $prod" if (-not $products.ContainsKey($prod.ToLower())) { $products[$prod.ToLower()] = $displayVersion $script:C2RSuite[$arpKey] = "$prod - $displayVersion" } } } } $script:InstalledSku = $products return $products } function Test-IsC2R { [CmdletBinding()] param([string]$Path) if ($null -eq $script:Orchestrator) { Write-Warning "Orchestrator not initialized. Call Initialize-Environment first." return $false } return $script:Orchestrator.IsC2RPath($Path) } function Test-ProductInScope { [CmdletBinding()] param([string]$ProductCode) if ($null -eq $script:Orchestrator) { Write-Warning "Orchestrator not initialized. Call Initialize-Environment first." return $false } return $script:Orchestrator.IsInScope($ProductCode) } #endregion #region Service Operations function Remove-Service { [CmdletBinding()] param([string]$ServiceName) Write-Log "Attempting to delete service: $ServiceName" if (-not $script:DetectOnly) { $result = $script:Orchestrator.Services.DeleteService($ServiceName) if ($result) { Write-Log "Successfully deleted service: $ServiceName" } else { Write-Log "Failed to delete service: $ServiceName" } return $result } else { Write-Log "Preview mode. Would delete service: $ServiceName" return $false } } #endregion #region License/SPP Operations function Clear-OfficeLicenses { [CmdletBinding()] param() if ($script:KeepLicense) { Write-Log "Skipping license cleanup (KeepLicense flag set)" return } Write-LogSubHeader "Cleaning Office licenses" # Clean OSPP Write-Log "Cleaning OSPP licenses..." $osVersion = [Environment]::OSVersion.Version $versionNT = $osVersion.Major * 100 + $osVersion.Minor if (-not $script:DetectOnly) { $script:Orchestrator.License.CleanOSPP($versionNT) } # Clean VNext license cache Write-Log "Cleaning VNext license cache..." if (-not $script:DetectOnly) { $script:Orchestrator.License.ClearVNextLicenseCache($script:LocalAppData) } } #endregion #region Windows Installer Cleanup function Clear-WindowsInstallerMetadata { [CmdletBinding()] param() Write-LogSubHeader "Cleaning Windows Installer metadata" $shouldDelete = { param([string]$guid) return Test-ProductInScope $guid } if (-not $script:DetectOnly) { Write-Log "Cleaning UpgradeCodes..." $script:Orchestrator.WindowsInstaller.CleanupUpgradeCodes($shouldDelete) Write-Log "Cleaning Products..." $script:Orchestrator.WindowsInstaller.CleanupProducts($shouldDelete) Write-Log "Cleaning Components..." $script:Orchestrator.WindowsInstaller.CleanupComponents($shouldDelete) Write-Log "Cleaning Published Components..." $script:Orchestrator.WindowsInstaller.CleanupPublishedComponents($shouldDelete) } else { Write-Log "Preview mode. Would clean Windows Installer metadata" } } #endregion #region TypeLib Cleanup function Clear-TypeLibRegistrations { [CmdletBinding()] param() Write-LogSubHeader "Cleaning TypeLib registrations" if (-not $script:DetectOnly) { $script:Orchestrator.TypeLib.CleanupKnownTypeLibs() } else { Write-Log "Preview mode. Would clean TypeLib registrations" } } #endregion #region Shell Integration function Clear-ShellIntegration { [CmdletBinding()] param() Write-LogSubHeader "Cleaning shell integration" # Protocol Handlers Remove-RegistryKey -Hive LocalMachine -SubKey "SOFTWARE\Classes\Protocols\Handler\osf" # Context Menu Handlers $contextMenuHandlers = @( "SOFTWARE\Classes\CLSID\{573FFD05-2805-47C2-BCE0-5F19512BEB8D}", "SOFTWARE\Classes\CLSID\{8BA85C75-763B-4103-94EB-9470F12FE0F7}", "SOFTWARE\Classes\CLSID\{CD55129A-B1A1-438E-A9AA-ABA463DBD3BF}", "SOFTWARE\Classes\CLSID\{D0498E0A-45B7-42AE-A9AA-ABA463DBD3BF}", "SOFTWARE\Classes\CLSID\{E768CD3B-BDDC-436D-9C13-E1B39CA257B1}" ) foreach ($key in $contextMenuHandlers) { Remove-RegistryKey -Hive LocalMachine -SubKey $key } # Groove ShellIconOverlayIdentifiers $overlayIdentifiers = @( "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 1 (ErrorConflict)", "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 2 (SyncInProgress)", "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 3 (InSync)", "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 1 (ErrorConflict)", "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 2 (SyncInProgress)", "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 3 (InSync)" ) foreach ($key in $overlayIdentifiers) { Remove-RegistryKey -Hive LocalMachine -SubKey $key } # Shell Extensions $shellExtensions = @( "{B28AA736-876B-46DA-B3A8-84C5E30BA492}", "{8B02D659-EBBB-43D7-9BBA-52CF22C5B025}", "{0875DCB6-C686-4243-9432-ADCCF0B9F2D7}", "{42042206-2D85-11D3-8CFF-005004838597}", "{993BE281-6695-4BA5-8A2A-7AACBFAAB69E}", "{C41662BB-1FA0-4CE0-8DC5-9B7F8279FF97}", "{506F4668-F13E-4AA1-BB04-B43203AB3CC0}", "{D66DC78C-4F61-447F-942B-3FB6980118CF}", "{46137B78-0EC3-426D-8B89-FF7C3A458B5E}", "{8BA85C75-763B-4103-94EB-9470F12FE0F7}", "{CD55129A-B1A1-438E-A9AA-ABA463DBD3BF}", "{D0498E0A-45B7-42AE-A9AA-ABA463DBD3BF}", "{E768CD3B-BDDC-436D-9C13-E1B39CA257B1}" ) foreach ($guid in $shellExtensions) { Remove-RegistryValue -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved" ` -ValueName $guid } # BHO (Browser Helper Objects) $bhoKeys = @( "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{31D09BA0-12F5-4CCE-BE8A-2923E76605DA}", "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{B4F3A835-0E21-4959-BA22-42B3008E02FF}", "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{D0498E0A-45B7-42AE-A9AA-ABA463DBD3BF}", "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{31D09BA0-12F5-4CCE-BE8A-2923E76605DA}", "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{B4F3A835-0E21-4959-BA22-42B3008E02FF}", "SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{D0498E0A-45B7-42AE-A9AA-ABA463DBD3BF}" ) foreach ($key in $bhoKeys) { Remove-RegistryKey -Hive LocalMachine -SubKey $key } # OneNote Namespace Extension Remove-RegistryKey -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{0875DCB6-C686-4243-9432-ADCCF0B9F2D7}" # Web Sites Remove-RegistryKey -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\Namespace\{B28AA736-876B-46DA-B3A8-84C5E30BA492}" Remove-RegistryKey -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\NetworkNeighborhood\Namespace\{46137B78-0EC3-426D-8B89-FF7C3A458B5E}" # VolumeCaches Remove-RegistryKey -Hive LocalMachine ` -SubKey "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Microsoft Office Temp Files" # Restart Explorer to release locks if (-not $script:DetectOnly) { Write-Log "Restarting Explorer..." $script:Orchestrator.Shell.RestartExplorer() } } function Clear-Shortcuts { [CmdletBinding()] param( [string]$RootPath, [switch]$Delete, [switch]$Unpin ) if ($script:SkipSD) { return } Write-LogSubHeader "Cleaning shortcuts in: $RootPath" $shortcuts = Get-ChildItem -Path $RootPath -Filter "*.lnk" -Recurse -ErrorAction SilentlyContinue foreach ($shortcut in $shortcuts) { try { $shell = New-Object -ComObject WScript.Shell $link = $shell.CreateShortcut($shortcut.FullName) $shouldDelete = $false # Check if target is C2R-related if (Test-IsC2R $link.TargetPath) { $shouldDelete = $true } # Check if target contains a product GUID elseif ($link.TargetPath -match '\{[A-F0-9-]{36}\}') { $guid = $matches[0] if (Test-ProductInScope $guid) { $shouldDelete = $true } } if ($shouldDelete) { if ($Unpin) { Write-LogOnly "Unpinning shortcut: $($shortcut.FullName)" $script:Orchestrator.Shell.UnpinFromTaskbar($shortcut.FullName) $script:Orchestrator.Shell.UnpinFromStartMenu($shortcut.FullName) } if ($Delete) { Write-LogOnly "Deleting shortcut: $($shortcut.FullName)" if (-not $script:DetectOnly) { Remove-Item -Path $shortcut.FullName -Force -ErrorAction SilentlyContinue } } } [System.Runtime.Interopservices.Marshal]::ReleaseComObject($shell) | Out-Null } catch { Write-LogOnly "Error processing shortcut $($shortcut.FullName): $_" } } } #endregion #region Export Module Members Export-ModuleMember -Function @( 'Initialize-Environment', 'Get-SystemInfo', 'Test-IsElevated', 'Initialize-Log', 'Write-LogHeader', 'Write-LogSubHeader', 'Write-Log', 'Write-LogOnly', 'Close-Log', 'Set-ErrorCode', 'Clear-ErrorCode', 'Set-ReturnValue', 'Get-RegistryValue', 'Set-RegistryValue', 'Remove-RegistryKey', 'Remove-RegistryValue', 'Get-RegistryKeys', 'Get-RegistryValues', 'Test-RegistryKeyExists', 'Remove-FolderRecursive', 'Remove-FileForced', 'Add-PendingFileDelete', 'Stop-OfficeProcesses', 'Test-ProcessRunning', 'Get-InstalledOfficeProducts', 'Test-IsC2R', 'Test-ProductInScope', 'Remove-Service', 'Clear-OfficeLicenses', 'Clear-WindowsInstallerMetadata', 'Clear-TypeLibRegistrations', 'Clear-ShellIntegration', 'Clear-Shortcuts' ) Export-ModuleMember -Variable @( 'SCRIPT_VERSION', 'SCRIPT_NAME', 'ERROR_SUCCESS', 'ERROR_FAIL', 'ERROR_REBOOT_REQUIRED', 'ERROR_USERCANCEL', 'ERROR_STAGE1', 'ERROR_STAGE2', 'ERROR_INCOMPLETE', 'ERROR_DCAF_FAILURE', 'ERROR_ELEVATION_USERDECLINED', 'ERROR_ELEVATION', 'ERROR_SCRIPTINIT', 'ERROR_RELAUNCH', 'ERROR_UNKNOWN', 'REG_ARP', 'ErrorCode', 'RebootRequired', 'IsElevated', 'Is64Bit', 'OSInfo', 'LogDir', 'ScrubDir', 'InstalledSku', 'C2RSuite', 'KeepSku', 'KeepFolder', 'DelInUse', 'ProgramFiles', 'ProgramFilesX86', 'CommonProgramFiles', 'CommonProgramFilesX86', 'ProgramData', 'AppData', 'LocalAppData', 'AllUsersProfile', 'WinDir', 'Temp', 'WICacheDir', 'Orchestrator' ) #endregion |