diff --git a/.direct_conversion.cs b/.direct_conversion.cs new file mode 100644 index 0000000..e29ce48 --- /dev/null +++ b/.direct_conversion.cs @@ -0,0 +1,5206 @@ + +using System; +using System.IO; +using System.Diagnostics; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using System.Management; +using System.Security.Principal; +using System.Security.AccessControl; + +/// +/// OffScrubC2R +/// +/// Author: Microsoft Customer Support Services +/// Copyright (c) 2014 - 2016 Microsoft Corporation +/// +/// Script to remove Office Click To Run (C2R) products +/// when a standard uninstall is no longer possible. +/// +/// Scope: Office 2013, 2016, and O365 C2R products +/// /// +/// Declaration of constants +/// +public static class Constants +{ + public const string SCRIPTVERSION = "1.0.0"; + public const string SCRIPTFILE = "OffScrubC2R.vbs"; + public const string SCRIPTNAME = "OffScrubC2R"; + public const string RETVALFILE = "ScrubRetValFile.txt"; + public const string ONAME = "Office C2R / O365"; + public const int HKCR = 0x80000000; + public const int HKCU = 0x80000001; + public const int HKLM = 0x80000002; + public const int HKU = 0x80000003; + public const int PRODLEN = 13; + public const int SQUISHED = 20; + public const int COMPRESSED = 32; + public const string REG_ARP = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"; + public const int VB_YES = 6; + public const int VB_NO = 7; + + public const int ERROR_SUCCESS = 0; + public const int ERROR_FAIL = 1; + public const int ERROR_REBOOT_REQUIRED = 2; + public const int ERROR_USERCANCEL = 4; + public const int ERROR_STAGE1 = 8; + public const int ERROR_STAGE2 = 16; + public const int ERROR_INCOMPLETE = 32; + public const int ERROR_DCAF_FAILURE = 64; + public const int ERROR_ELEVATION_USERDECLINED = 128; + public const int ERROR_ELEVATION = 256; + public const int ERROR_SCRIPTINIT = 512; + public const int ERROR_RELAUNCH = 1024; + public const int ERROR_UNKNOWN = 2048; + public const int ERROR_ALL = 4095; + public const int ERROR_USER_ABORT = -1073741510; + public const int ERROR_SUCCESS_CONFIG_COMPLETE = 1728; + public const int ERROR_SUCCESS_REBOOT_REQUIRED = 3010; +} + +/// +/// Declaration of variables +/// + +/// Object-type variables + object oFso, oMsi, oReg, oWShell, oWmiLocal, oShellApp; + object ComputerItem, Key, Item, LogStream, TmpKey; + +/// Array + object[] arrVersion; + +/// Dictionaries (using Dictionary as a generic replacement) + Dictionary dicKeepLis, dicApps, dicKeepFolder, dicDelRegKey, dicKeepReg, dicSC; + Dictionary dicInstalledSku, dicRemoveSku, dicKeepSku, dicC2RSuite, dicDelInUse, dicDelFolder; + +/// Strings + string sAppData, sScrubDir, sProgramFiles, sProgramFilesX86, sCommonProgramFiles; + string sAllusersProfile, sOSVersion, sWinDir, sWICacheDir, sCommonProgramFilesX86; + string sProgramData, sPackageFolder, sLocalAppData, sOInstallRoot, sSkuRemoveList; + string sOSinfo, sDefault, sTemp, sTmp, sCmd, sLogDir, sProfilesDirectory, sArpUninstallCmd; + string sRetVal, sScriptDir, sPackageGuid, sValue, sActiveConfiguration, sNotepad; + +/// Integers + int iVersionNT, iError, iProcCloseCnt; + +/// Booleans + bool f64, fLogInitialized, fNoCancel, fRemoveOse, fDetectOnly, fQuiet, fForce; + bool fC2R, fRemoveAll, fRebootRequired, fRerun, fSetRunOnce, fTestRerun; + bool fIsElevated, fNoElevate, fUserConsent, fCScript, fReturnErrorOrSuccess; + bool fClearTaskBand, fSkipSD, fUnpinMode, fKeepLicense, fOffline, fForceArpUninstall; + +/// Named pipe and file system helper variables + string pipename; + object pipeStream; /// type depends on implementation, e.g., NamedPipeServerStream + object fs; /// type depends on use, e.g., FileStream, FileSystemInfo, etc. +/// + +// ------------------------------------------------------------------------------- +// main +// +// Main section of script +// ------------------------------------------------------------------------------- + +public void main() +{ + // Initialize required settings and objects + Initialize(); + + // Call the command line parser + ParseCmdLine(); + + // ----------------------------- + // Stage # 0 - Basic detection + // ----------------------------- + LogH("Stage # 0 \"Basic detection\""); + LogY("stage0"); + + // Ensure integrity of WI metadata which could otherwise break used APIs + LogH1($"Ensure Windows Installer metadata integrity ({DateTime.Now:T})"); + EnsureValidWIMetadata(HKCU, @"Software\Classes\Installer\Products", COMPRESSED); + EnsureValidWIMetadata(HKCR, @"Installer\Products", COMPRESSED); + EnsureValidWIMetadata(HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products", COMPRESSED); + EnsureValidWIMetadata(HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components", COMPRESSED); + EnsureValidWIMetadata(HKCR, @"Installer\Components", COMPRESSED); + + // Build a list with installed/registered Office products + FindInstalledOProducts(); + if (dicC2RSuite.Count > 0) + { + Log("Registered ARP product(s) found:"); + foreach (var key in dicC2RSuite.Keys) + { + Log($" - {key} - {dicC2RSuite[key]}"); + } + // Optionally: foreach (var val in dicC2RSuite.Values) Log($" - {val}"); + } + else + { + Log("No registered product(s) found"); + } + + // Locate the C2R %PackageFolder% and the PackageGuid + sPackageFolder = ""; + if (RegReadValue(HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun", "PackageFolder", out sValue, "REG_SZ")) + { + sPackageFolder = sValue; + } + else if (RegReadValue(HKLM, @"SOFTWARE\Microsoft\Office\16.0\ClickToRun", "PackageFolder", out sValue, "REG_SZ")) + { + sPackageFolder = sValue; + } + else if (RegReadValue(HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun", "PackageFolder", out sValue, "REG_SZ")) + { + sPackageFolder = sValue; + } + + // If sPackageFolder is invalid, set it to the C2R registry reference string + if (string.IsNullOrEmpty(sPackageFolder)) + { + var pf = ExpandEnv("%programfiles%"); + var pf86 = ExpandEnv("%programfiles(x86)%"); + + if (FolderExists($"{pf}\\Microsoft Office 15")) + { + sPackageFolder = $"{pf}\\Microsoft Office 15"; + } + else if (FolderExists($"{pf}\\Microsoft Office 16")) + { + sPackageFolder = $"{pf}\\Microsoft Office 16"; + } + else if (FolderExists($"{pf}\\Microsoft Office\\PackageManifests")) + { + sPackageFolder = $"{pf}\\Microsoft Office"; + } + else if (FolderExists($"{pf86}\\Microsoft Office\\PackageManifests")) + { + sPackageFolder = $"{pf86}\\Microsoft Office"; + } + } + + sPackageGuid = ""; + if (RegReadValue(HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun", "PackageGUID", out sValue, "REG_SZ")) + { + sPackageGuid = sValue; + } + else if (RegReadValue(HKLM, @"SOFTWARE\Microsoft\Office\16.0\ClickToRun", "PackageGUID", out sValue, "REG_SZ")) + { + sPackageGuid = sValue; + } + else if (RegReadValue(HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun", "PackageGUID", out sValue, "REG_SZ")) + { + sPackageGuid = sValue; + } + + // Init complete. Reset the return value + ClearError(ERROR_SCRIPTINIT); + + // ----------------------- + // Stage # 1 - Uninstall | + // ----------------------- + LogH($"Stage # 1 \"Uninstall\""); + LogY("stage1"); + + // Clean OSPP + LogH1("Clean OSPP"); + if (!fKeepLicense) + { + CleanOSPP(); + } + + // Clean vNext + LogH1("Clean vNext Licenses"); + if (!fKeepLicense) + { + ClearVNextLicCache(); + } + + // End all running Office applications + LogH1("End running processes"); + // LogY("stage2"); + if (dicKeepSku.Count == 0) + { + ClearShellIntegrationReg(); + } + CloseOfficeApps(); + + // Remove scheduled tasks which might interfere with uninstall + if (!fDetectOnly) + { + DelSchtasks(); + } + + // Unpin shortcuts + // Need to unpin as long as the shortcuts are still valid! + LogH1("Clean shortcuts"); + // LogY("stage3"); + CleanShortcuts(sAllusersProfile, true, true); + CleanShortcuts(sProfilesDirectory, true, true); + + // Uninstall + LogH1($"Remove {ONAME}"); + Uninstall(); + + // --------------------- + // Stage # 2 - CleanUp | + // --------------------- + LogH($"Stage # 2 \"CleanUp\""); + LogY("stage4"); + + // Cleanup registry data + RegWipe(); + + // Cleanup files + FileWipe(); + + // For test purposes only! + if (fTestRerun) + { + LogH2("Enforcing 'Rerun' mode for test purposes"); + fRebootRequired = true; + SetError(ERROR_REBOOT_REQUIRED); + Rerun(); + } + + // Ensure Explorer runs + RestoreExplorer(); + + // ------------------ + // Stage #3 - Exit + // ------------------ + ExitScript(); + + // Returncode and reboot handler + void ExitScript() + { + // Update cached error and quit + if ((iError & (ERROR_FAIL | ERROR_INCOMPLETE)) == 0) + { + RegDeleteValue(HKCU, @"SOFTWARE\Microsoft\Office\15.0\CleanC2R", "Rerun", false); + } + SetRetVal(iError); + + // log result + if ((iError & ERROR_INCOMPLETE) != 0) + { + LogH2($"Removal result: {iError} - INCOMPLETE. Uninstall requires a system reboot to complete."); + } + else + { + string sTmp = " - SUCCESS"; + if ((iError & ERROR_USERCANCEL) != 0) sTmp = " - USER CANCELED"; + if ((iError & ERROR_FAIL) != 0) sTmp = " - FAIL"; + LogH2($"Removal result: {iError}{sTmp}"); + } + + if ((iError & ERROR_FAIL) != 0) + { + if ((iError & ERROR_REBOOT_REQUIRED) != 0) Log(" - Reboot required"); + if ((iError & ERROR_USERCANCEL) != 0) Log(" - User cancel"); + if ((iError & ERROR_STAGE1) != 0) Log(" - Msiexec failed"); + if ((iError & ERROR_STAGE2) != 0) Log(" - Cleanup failed"); + if ((iError & ERROR_INCOMPLETE) != 0) Log(" - Removal incomplete. Rerun after reboot needed"); + if ((iError & ERROR_DCAF_FAILURE) != 0) Log(" - Second attempt cleanup still incomplete"); + if ((iError & ERROR_ELEVATION_USERDECLINED) != 0) Log(" - User declined elevation"); + if ((iError & ERROR_ELEVATION) != 0) Log(" - Elevation failed"); + if ((iError & ERROR_SCRIPTINIT) != 0) Log(" - Initialization error"); + if ((iError & ERROR_RELAUNCH) != 0) Log(" - Unhandled error during relaunch attempt"); + if ((iError & ERROR_UNKNOWN) != 0) Log(" - Unknown error"); + // ERROR_USER_ABORT is only valid for the temporary cached error file + // if ((iError & ERROR_USER_ABORT) != 0) Log(" - Process terminated by user"); + } + + LogH2("Removal end."); + LogY("stage5"); + + // Check if we need to show a simplified return code + // 0 = Success, Non Zero = Error + if (fReturnErrorOrSuccess) + { + bool fOverallSuccess = true; + if ((iError & ERROR_USERCANCEL) != 0) fOverallSuccess = false; + if ((iError & ERROR_STAGE2) != 0) fOverallSuccess = false; + if ((iError & ERROR_DCAF_FAILURE) != 0) fOverallSuccess = false; + if ((iError & ERROR_ELEVATION_USERDECLINED) != 0) fOverallSuccess = false; + if ((iError & ERROR_ELEVATION) != 0) fOverallSuccess = false; + if ((iError & ERROR_SCRIPTINIT) != 0) fOverallSuccess = false; + if ((iError & ERROR_RELAUNCH) != 0) fOverallSuccess = false; + if ((iError & ERROR_UNKNOWN) != 0) fOverallSuccess = false; + + string sTmp = "ReturnErrorOrSuccess switch has been set. The current value return code translates to: "; + if (fOverallSuccess) + { + iError = ERROR_SUCCESS; + LogY("result:stage5:true"); + Log($"{sTmp}SUCCESS"); + } + else + { + LogY("result:stage5:false"); + Log($"{sTmp}ERROR"); + } + } + + // Reboot handling + if (fRebootRequired) + { + LogY("reboot"); + string sPrompt = "In order to complete uninstall, a system reboot is necessary. Would you like to reboot now?"; + if (!fQuiet) + { + // Use modern message box and system reboot call + var dr = System.Windows.Forms.MessageBox.Show( + sPrompt, + SCRIPTNAME + " - Reboot Required", + System.Windows.Forms.MessageBoxButtons.YesNo, + System.Windows.Forms.MessageBoxIcon.Question + ); + if (dr == System.Windows.Forms.DialogResult.Yes) + { + try + { + // Reboot the system using WMI + var scope = new System.Management.ManagementScope(@"\\.\root\cimv2"); + var osQuery = new System.Management.ObjectQuery("SELECT * FROM Win32_OperatingSystem"); + using (var mos = new System.Management.ManagementObjectSearcher(scope, osQuery)) + { + foreach (System.Management.ManagementObject os in mos.Get()) + { + os.InvokeMethod("Reboot", null); + } + } + } + catch (Exception ex) + { + Log($"Failed to reboot system: {ex.Message}"); + } + } + } + } + + LogY("ok"); + + // Exit application with return code + Environment.Exit(iError); + } +} + +//------------------------------------------------------------------------------- +// Initialize +// +// Configure defaults and initialize all required objects +//------------------------------------------------------------------------------- +public void Initialize() +{ + // set variable defaults + //---------------------- + iError = Constants.ERROR_SUCCESS; + iProcCloseCnt = 0; + sLogDir = ""; + sPackageFolder = ""; + sArpUninstallCmd = ""; + f64 = false; + fCScript = false; + fLogInitialized = false; + fNoCancel = false; + fRemoveOse = false; + fDetectOnly = false; + fQuiet = true; + fForce = false; + fC2R = true; + fRebootRequired = false; + fRerun = false; + fTestRerun = false; + fIsElevated = false; + fNoElevate = false; + fSetRunOnce = false; + fUserConsent = false; + fReturnErrorOrSuccess = false; + fSkipSD = false; + fClearTaskBand = false; + fUnpinMode = false; + fKeepLicense = false; + fOffline = false; + fForceArpUninstall = false; + + // create required objects + //------------------------ + InitObjects(); + + // get environment path values + //---------------------------- + sAppData = ExpandEnv("%appdata%"); + sLocalAppData = ExpandEnv("%localappdata%"); + sTemp = ExpandEnv("%temp%"); + sAllusersProfile = ExpandEnv("%allusersprofile%"); + if (RegReadValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList", "ProfilesDirectory", out sValue, "REG_EXPAND_SZ")) + { + sProfilesDirectory = sValue; + } + if (!FolderExists(sProfilesDirectory)) + { + sProfilesDirectory = Path.GetDirectoryName(ExpandEnv("%userprofile%")); + } + sProgramFiles = ExpandEnv("%programfiles%"); + //sProgramFilesX86 = deferred. Depends on operating system architecture check + sCommonProgramFiles = ExpandEnv("%commonprogramfiles%"); + //sCommonProgramFilesX86 = deferred. Depends on operating system architecture check + sProgramData = ExpandEnv("%programdata%"); + sWinDir = ExpandEnv("%windir%"); + //sPackageFolder = deferred + sWICacheDir = $"{sWinDir}\\Installer"; + sScrubDir = $"{sTemp}\\{Constants.SCRIPTNAME}"; + sScriptDir = System.Reflection.Assembly.GetExecutingAssembly().Location; + sScriptDir = sScriptDir.Substring(0, sScriptDir.LastIndexOf("\\") + 1); + sNotepad = $"{sWinDir}\\notepad.exe"; + + // check if called to unpin a shortcut + var args = Environment.GetCommandLineArgs(); + if (args.Length > 1) + { + if (args[1] == "UNPINSC" && args.Length > 2) + { + Unpin(args[2]); + Environment.Exit(0); + } + } + + // ensure 64 bit host if needed + if (System.Reflection.Assembly.GetExecutingAssembly().Location.ToLower().Contains("syswow64")) + { + RelaunchAs64Host(); + } + + // create the temp folder + //----------------------- + if (!Directory.Exists(sScrubDir)) + { + Directory.CreateDirectory(sScrubDir); + } + + // set the default logging directory + //---------------------------------- + sLogDir = sScrubDir; + + // detect bitness of the operating system + //---------------------------------------- + var scope = new System.Management.ManagementScope(@"\\.\root\cimv2"); + var query = new System.Management.ObjectQuery("SELECT * FROM Win32_ComputerSystem"); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query)) + { + foreach (System.Management.ManagementObject item in searcher.Get()) + { + var systemType = item["SystemType"]?.ToString() ?? ""; + f64 = systemType.Substring(0, Math.Min(3, systemType.Length)).Contains("64"); + } + } + if (f64) + { + sProgramFilesX86 = ExpandEnv("%programfiles(x86)%"); + } + if (f64) + { + sCommonProgramFilesX86 = ExpandEnv("%CommonProgramFiles(x86)%"); + } + + // update error flag + //------------------ + SetError(Constants.ERROR_SCRIPTINIT); + + // get Win32_OperatingSystem details + //---------------------------------- + query = new System.Management.ObjectQuery("SELECT * FROM Win32_OperatingSystem"); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query)) + { + foreach (System.Management.ManagementObject item in searcher.Get()) + { + sOSinfo = (sOSinfo ?? "") + (item["Caption"]?.ToString() ?? ""); + sOSinfo += item["OtherTypeDescription"]?.ToString() ?? ""; + sOSinfo += $", SP {item["ServicePackMajorVersion"]}"; + sOSinfo += $", Version: {item["Version"]}"; + sOSVersion = item["Version"]?.ToString() ?? ""; + sOSinfo += $", Codepage: {item["CodeSet"]}"; + sOSinfo += $", Country Code: {item["CountryCode"]}"; + sOSinfo += $", Language: {item["OSLanguage"]}"; + } + } + + // get VersionNT number + //--------------------- + if (!string.IsNullOrEmpty(sOSVersion)) + { + var delimiter = sOSVersion.Contains(".") ? "." : (sOSVersion.Contains(",") ? "," : " "); + var arrVersion = sOSVersion.Split(new[] { delimiter }, StringSplitOptions.None); + if (arrVersion.Length >= 2 && int.TryParse(arrVersion[0], out int major) && int.TryParse(arrVersion[1], out int minor)) + { + iVersionNT = major * 100 + minor; + } + } + + // ensure sufficient registry permissions + //-------------------------------------- + fIsElevated = CheckRegPermissions(); + if (!fIsElevated && !fNoElevate) + { + // try to relaunch elevated + RelaunchElevated(); + + // can't relaunch. Exit out + SetError(Constants.ERROR_ELEVATION); + var exeName = System.Reflection.Assembly.GetExecutingAssembly().Location; + if (exeName.Length > 0 && char.ToUpper(exeName[exeName.LastIndexOf("\\") + 1]) == 'C') + { + if (!fLogInitialized) CreateLog(); + Log("Error: Insufficient registry access permissions - exiting"); + } + SetRetVal(iError); + //Environment.Exit(iError); + ExitScript(); + } + + // clear error flags + //------------------ + ClearError(Constants.ERROR_ELEVATION); + ClearError(Constants.ERROR_SCRIPTINIT); + + // ensure CScript as engine + //------------------------ + var fullName = System.Reflection.Assembly.GetExecutingAssembly().Location; + fCScript = fullName.Length > 0 && char.ToUpper(fullName[fullName.LastIndexOf("\\") + 1]) == 'C'; + if (!fCScript && !fQuiet) + { + RelaunchAsCScript(); + } + + // set retval for file based logic + //-------------------------------- + // value needs to be kept on 'user abort' + SetRetVal(Constants.ERROR_USER_ABORT); + + // create dictionary objects + //-------------------------- + dicInstalledSku = new Dictionary(); + dicRemoveSku = new Dictionary(); + dicKeepSku = new Dictionary(); + dicKeepLis = new Dictionary(); + dicKeepFolder = new Dictionary(); + dicApps = new Dictionary(); + dicDelRegKey = new Dictionary(); + dicKeepReg = new Dictionary(); + dicC2RSuite = new Dictionary(); + dicDelInUse = new Dictionary(); + dicDelFolder = new Dictionary(); + dicSC = new Dictionary(); + + // add initial known .exe files that need to be closed + //---------------------------------------------------- + dicApps.Add("appvshnotify.exe", "appvshnotify.exe"); + dicApps.Add("integratedoffice.exe", "integratedoffice.exe"); + dicApps.Add("integrator.exe", "integrator.exe"); + dicApps.Add("firstrun.exe", "firstrun.exe"); + //Adding setup.exe to the hard list of processes that are shut down will potentially break wrappers that invoke OffScrub + //dicApps.Add("setup.exe", "setup.exe"); + dicApps.Add("communicator.exe", "communicator.exe"); + dicApps.Add("msosync.exe", "msosync.exe"); + dicApps.Add("OneNoteM.exe", "OneNoteM.exe"); + dicApps.Add("iexplore.exe", "iexplore.exe"); + dicApps.Add("mavinject32.exe", "mavinject32.exe"); + dicApps.Add("werfault.exe", "werfault.exe"); + dicApps.Add("perfboost.exe", "perfboost.exe"); + dicApps.Add("roamingoffice.exe", "roamingoffice.exe"); + // SP1 additions / changes + dicApps.Add("officeclicktorun.exe", "officeclicktorun.exe"); + dicApps.Add("officeondemand.exe", "officeondemand.exe"); + dicApps.Add("OfficeC2RClient.exe", "OfficeC2RClient.exe"); +} + +//------------------------------------------------------------------------------- +// InitObjects +// +// Initialize global objects +//------------------------------------------------------------------------------- +public void InitObjects() +{ + oWmiLocal = new System.Management.ManagementScope(@"\\.\root\cimv2"); + oWShell = new System.Diagnostics.Process(); + oShellApp = new object(); // Shell.Application - may need COM interop + oFso = new object(); // FileSystemObject - may need COM interop or use System.IO + oMsi = new object(); // WindowsInstaller.Installer - may need COM interop + oReg = new System.Management.ManagementScope(@"\\.\root\default:StdRegProv"); +} + +//------------------------------------------------------------------------------- +// FreeObjects +// +// Free initialized global objects +//------------------------------------------------------------------------------- +public void FreeObjects() +{ + if (oWmiLocal is IDisposable disposable1) disposable1.Dispose(); + if (oWShell is IDisposable disposable2) disposable2.Dispose(); + oShellApp = null; + oFso = null; + oMsi = null; + if (oReg is IDisposable disposable3) disposable3.Dispose(); +} + +//------------------------------------------------------------------------------- +// ParseCmdLine +// +// Command line parser +//------------------------------------------------------------------------------- +public void ParseCmdLine() +{ + string[] arrArguments; + string sArguments = ""; + string sArg0; + + var args = Environment.GetCommandLineArgs(); + int iArgCnt = args.Length - 1; // Subtract 1 because args[0] is the executable path + if (iArgCnt > 0) + { + if (args[1] == "UAC") + { + if (args.Length == 2) iArgCnt = 0; + } + } + if (iArgCnt == 0) + { + var scriptName = Path.GetFileName(System.Reflection.Assembly.GetExecutingAssembly().Location); + switch (scriptName.ToUpper()) + { + default: + //Create the log + CreateLog(); + FindInstalledOProducts(); + sDefault = "ALL"; + arrArguments = sDefault.Trim().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + if (arrArguments.Length == 0) arrArguments = new string[0]; + break; + } + } + else + { + arrArguments = new string[iArgCnt]; + for (int iCnt = 0; iCnt < iArgCnt; iCnt++) + { + arrArguments[iCnt] = args[iCnt + 1].ToUpper(); + sArguments = sArguments + arrArguments[iCnt] + " "; + } + } + + // hardcode to full removal + sArg0 = "ALL"; + + switch (sArg0.ToUpper()) + { + case "?": + ShowSyntax(); + break; + case "ALL": + fRemoveAll = true; + fRemoveOse = false; + break; + case "C2R": + fC2R = true; + fRemoveAll = false; + fRemoveOse = false; + break; + default: + fRemoveAll = false; + fRemoveOse = false; + sSkuRemoveList = sArg0; + break; + } + + for (int iCnt = 0; iCnt < arrArguments.Length; iCnt++) + { + switch (arrArguments[iCnt]) + { + case "?": + case "/?": + case "-?": + ShowSyntax(); + break; + + case "/DETECTONLY": + case "/PREVIEW": + fDetectOnly = true; + break; + + case "/FORCEARPUNINSTALL": + fForceArpUninstall = true; + break; + + case "/KL": + case "/KEEPLICENSE": + fKeepLicense = true; + break; + + case "/L": + case "/LOG": + fLogInitialized = false; + if (arrArguments.Length > iCnt + 1) + { + if (Directory.Exists(arrArguments[iCnt + 1])) + { + sLogDir = arrArguments[iCnt + 1]; + } + else + { + try + { + Directory.CreateDirectory(arrArguments[iCnt + 1]); + sLogDir = arrArguments[iCnt + 1]; + } + catch + { + sLogDir = sScrubDir; + } + } + } + break; + + case "/N": + case "/NOCANCEL": + fNoCancel = true; + break; + + case "/NE": + case "/NOELEVATE": + fNoElevate = true; + break; + + case "/OFFLINE": + case "/FORCEOFFLINE": + fOffline = true; + break; + + case "/O": + case "/OSE": + fRemoveOse = true; + break; + + case "/Q": + case "/QUIET": + fQuiet = true; + break; + + case "/RETERRORSUCCESS": + case "/RETURNERRORORSUCCESS": + case "/REOS": + fReturnErrorOrSuccess = true; + break; + + case "/S": + case "/SKIPSD": + case "/SKIPSHORTCUTDETECTION": + fSkipSD = true; + break; + + // for test purposes only! + case "/TR": + case "/TESTRERUN": + fTestRerun = true; + break; + } + } + if (!fLogInitialized) CreateLog(); + LogH2($"Arguments: {sArguments}\r\n"); +} + +//------------------------------------------------------------------------------- +// ShowSyntax +// +// Show the expected syntax for the script usage +//------------------------------------------------------------------------------- +public void ShowSyntax() +{ + Console.WriteLine($"\r\n{Constants.SCRIPTFILE} V {Constants.SCRIPTVERSION}\r\n" + + "Copyright (c) Microsoft Corporation. All Rights Reserved\r\n\r\n" + + $"{Constants.SCRIPTFILE} - Remove {Constants.ONAME}\r\n" + + "when a regular uninstall is no longer possible\r\n\r\n" + + $"Usage:\t{Constants.SCRIPTFILE}\r\n\r\n" + + "\t/? ' Displays this help\r\n" + + "\t/Log [LogfolderPath] ' Custom folder for log files\r\n" + + "\t/SkipSD ' Skips the ShortcutDetection in local profiles\r\n" + + "\t/NoCancel ' Setup.exe and Msiexec.exe have no Cancel button\r\n" + + "\t/Quiet ' Script, Setup.exe and Msiexec.exe run quiet with no UI\r\n" + + "\t/ReturnErrorOrSuccess ' Returns 0 for a successful removal. Non-Zero if not.\r\n"); + Environment.Exit(0); +} + +//------------------------------------------------------------------------------- +// FindInstalledOProducts +// +// Office configuration products are listed with their configuration product +// name in the "Uninstall" key. +//------------------------------------------------------------------------------- +public void FindInstalledOProducts() +{ + string ArpItem, prod, cult; + string sCurKey, sValue, sConfigName, sCulture, sDisplayVersion, sVersionFallback; + string sUninstallString, sProd; + int iLeft, iRight; + string[] arrKeys, arrProducts, arrCultures; + bool fSystemComponent0, fDisplayVersion, fUninstallString; + + const string REG_ARP = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\"; + const string REG_O15RPROPERTYBAG = @"SOFTWARE\Microsoft\Office\15.0\ClickToRun\propertyBag\"; + const string REG_O15C2RCONFIGURATION = @"SOFTWARE\Microsoft\Office\15.0\ClickToRun\Configuration\"; + const string REG_O15C2RPRODUCTIDS = @"SOFTWARE\Microsoft\Office\15.0\ClickToRun\ProductReleaseIDs\Active\"; + const string REG_O16C2RCONFIGURATION = @"SOFTWARE\Microsoft\Office\16.0\ClickToRun\Configuration\"; + const string REG_O16C2RPRODUCTIDS = @"SOFTWARE\Microsoft\Office\16.0\ClickToRun\ProductReleaseIDs\Active\"; + const string REG_C2RCONFIGURATION = @"SOFTWARE\Microsoft\Office\ClickToRun\Configuration\"; + const string REG_C2RPRODUCTIDS = @"SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs\"; + + if (dicInstalledSku.Count > 0) return; //Already done from command line parser + + fDisplayVersion = false; + + // identify C2R products + LogH1("Detect installed products "); + + LogOnly("Check for O15 C2R products"); + // Check O15 Configuration key + if (RegReadValue(Constants.HKLM, REG_O15C2RCONFIGURATION, "ProductReleaseIds", out sValue, "REG_SZ")) + { + arrProducts = sValue.Split(','); + if (RegReadValue(Constants.HKLM, REG_O15C2RPRODUCTIDS + "culture", "x-none", out sVersionFallback, "REG_SZ")) + { + fDisplayVersion = true; + // get version from active with fallback on configuration + foreach (string prodItem in arrProducts) + { + prod = prodItem; + LogOnly($"Found O15 C2R product in Configuration: {prod}"); + // update product dictionary + if (!dicInstalledSku.ContainsKey(prod.ToLower())) + { + LogOnly($"add new product to dictionary: {prod.ToLower()}"); + dicInstalledSku.Add(prod.ToLower(), sVersionFallback); + } + } + } + } + + // Check O15 PropertyBag key + if (RegReadValue(Constants.HKLM, REG_O15RPROPERTYBAG, "productreleaseid", out sValue, "REG_SZ")) + { + arrProducts = sValue.Split(','); + if (RegReadValue(Constants.HKLM, REG_O15C2RPRODUCTIDS + "culture", "x-none", out sVersionFallback, "REG_SZ")) + { + fDisplayVersion = true; + foreach (string prodItem in arrProducts) + { + prod = prodItem; + LogOnly($"Found O15 C2R product in PropertyBag: {prod}"); + // update product dictionary + if (!dicInstalledSku.ContainsKey(prod.ToLower())) + { + LogOnly($"add new product to dictionary: {prod.ToLower()}"); + dicInstalledSku.Add(prod.ToLower(), sVersionFallback); + } + } + } + } + + //O16 section + LogOnly("Check for Office C2R products (>=QR8)"); + // Check Office Configuration key + if (RegReadValue(Constants.HKLM, REG_C2RPRODUCTIDS, "ActiveConfiguration", out sActiveConfiguration, "REG_SZ")) + { + // Get DisplayVersion + //Try QR8 logic first + fDisplayVersion = RegReadValue(Constants.HKLM, REG_C2RPRODUCTIDS + sActiveConfiguration + "\\culture", "x-none", out sVersionFallback, "REG_SZ"); + if (RegEnumKey(Constants.HKLM, REG_C2RPRODUCTIDS + sActiveConfiguration + "\\culture", out arrCultures)) + { + foreach (string cultItem in arrCultures) + { + cult = cultItem; + if (cult.ToLower().Contains("x-none")) + { + fDisplayVersion = RegReadValue(Constants.HKLM, REG_C2RPRODUCTIDS + sActiveConfiguration + "\\culture\\" + cult, "Version", out sVersionFallback, "REG_SZ"); + } + } + } + // Update product dic + if (RegEnumKey(Constants.HKLM, REG_C2RPRODUCTIDS + sActiveConfiguration, out arrProducts)) + { + foreach (string prodItem in arrProducts) + { + prod = prodItem; + sProd = prod.ToLower(); + int dotIndex = sProd.IndexOf("."); + if (dotIndex > 0) sProd = sProd.Substring(0, dotIndex); + switch (sProd) + { + case "culture": + case "stream": + break; + default: + LogOnly($"Found Office C2R product in Configuration: {prod}"); + // update product dictionary + if (!dicInstalledSku.ContainsKey(sProd)) + { + LogOnly($"add new product to dictionary: {sProd}"); + if (RegReadValue(Constants.HKLM, REG_C2RPRODUCTIDS + sActiveConfiguration + "\\" + prod + "\\x-none", "Version", out sDisplayVersion, "REG_SZ")) + { + dicInstalledSku.Add(sProd, sDisplayVersion); + } + else + { + dicInstalledSku.Add(sProd, sVersionFallback); + } + } + break; + } + } + } + } + + LogOnly("Check for Office C2R products (QR7)"); + // Check Office Configuration key + if (RegReadValue(Constants.HKLM, REG_C2RCONFIGURATION, "ProductReleaseIds", out sValue, "REG_SZ")) + { + arrProducts = sValue.Split(','); + if (!fDisplayVersion) fDisplayVersion = RegReadValue(Constants.HKLM, REG_C2RPRODUCTIDS + "Active\\culture", "x-none", out sVersionFallback, "REG_SZ"); + if (fDisplayVersion) + { + foreach (string prodItem in arrProducts) + { + prod = prodItem; + LogOnly($"Found Office C2R product in Configuration: {prod}"); + // update version tracking + if (!dicInstalledSku.ContainsKey(prod.ToLower())) + { + LogOnly($"add new product to dictionary: {prod.ToLower()}"); + dicInstalledSku.Add(prod.ToLower(), sVersionFallback); + } + } + } + } + + LogOnly("Check for O16 C2R products (QR6)"); + // Check O16 Configuration key + if (RegReadValue(Constants.HKLM, REG_O16C2RCONFIGURATION, "ProductReleaseIds", out sValue, "REG_SZ")) + { + arrProducts = sValue.Split(','); + if (!fDisplayVersion) fDisplayVersion = RegReadValue(Constants.HKLM, REG_O16C2RPRODUCTIDS + "culture", "x-none", out sVersionFallback, "REG_SZ"); + if (fDisplayVersion) + { + foreach (string prodItem in arrProducts) + { + prod = prodItem; + LogOnly($"Found O16 (QR6) C2R product in Configuration: {prod}"); + // update product dictionary + if (!dicInstalledSku.ContainsKey(prod.ToLower())) + { + LogOnly($"add new product to dictionary: {prod}"); + dicInstalledSku.Add(prod.ToLower(), sVersionFallback); + } + } + } + } + + LogOnly("Check ARP for Office C2R products"); + // ARP + if (RegEnumKey(Constants.HKLM, REG_ARP, out arrKeys) && arrKeys != null) + { + foreach (string arpItem in arrKeys) + { + ArpItem = arpItem; + // filter on Office C2R products + sCurKey = REG_ARP + ArpItem + "\\"; + fUninstallString = RegReadValue(Constants.HKLM, sCurKey, "UninstallString", out sValue, "REG_SZ"); + if (fUninstallString && (sValue.ToUpper().Contains("MICROSOFT OFFICE 1") || sValue.ToUpper().Contains("OFFICECLICKTORUN.EXE"))) + { + //cache UninstallString for uninstall Fallback + if (sValue.ToUpper().Contains("OFFICECLICKTORUN.EXE")) sArpUninstallCmd = sValue; + //get Version + fDisplayVersion = RegReadValue(Constants.HKLM, sCurKey, "DisplayVersion", out sDisplayVersion, "REG_SZ"); + //extract the productreleaseid + sValue = sValue.Trim(); + int lastSpace = sValue.LastIndexOf(" "); + prod = lastSpace >= 0 ? sValue.Substring(lastSpace).Trim() : sValue; + prod = prod.Replace("productstoremove=", ""); + int underscoreIndex = prod.IndexOf("_"); + if (underscoreIndex > 0) prod = prod.Substring(0, underscoreIndex); + int dot1Index = prod.IndexOf(".1"); + if (dot1Index > 0) prod = prod.Substring(0, dot1Index); + LogOnly($"Found C2R product in ARP: {prod}"); + if (!dicInstalledSku.ContainsKey(prod.ToLower())) + { + LogOnly($"add new product to dictionary: {prod}"); + dicInstalledSku.Add(prod.ToLower(), sDisplayVersion); + } + // categorize the SKU as C2R + if (!dicC2RSuite.ContainsKey(ArpItem)) dicC2RSuite.Add(ArpItem, $"{prod} - {sDisplayVersion}"); + } + else + { + //Legacy logic keep for compat reasons + sValue = ""; + sDisplayVersion = ""; + string sValueTemp; + bool hasSystemComponent = RegReadValue(Constants.HKLM, sCurKey, "SystemComponent", out sValueTemp, "REG_DWORD"); + fSystemComponent0 = !(hasSystemComponent && sValueTemp == "1"); + fDisplayVersion = RegReadValue(Constants.HKLM, sCurKey, "DisplayVersion", out sValue, "REG_SZ"); + if (fDisplayVersion) + { + sDisplayVersion = sValue; + if (sValue.Length > 1) + { + try + { + if (int.TryParse(sValue.Substring(0, 2), out int versionNum)) + { + fDisplayVersion = (versionNum > 14); + } + } + catch { } + } + else + { + fDisplayVersion = false; + } + } + fUninstallString = RegReadValue(Constants.HKLM, sCurKey, "UninstallString", out sUninstallString, "REG_SZ"); + + // filter on C2R configuration SKU + if (fUninstallString && (sUninstallString.ToUpper().Contains("MICROSOFT OFFICE 1") || sUninstallString.ToUpper().Contains("OFFICECLICKTORUN.EXE"))) + { + // Extract the ProductReleaseID + if (sUninstallString.Contains("productstoremove=")) + { + int lastSpaceIdx = sUninstallString.LastIndexOf(" "); + sConfigName = lastSpaceIdx >= 0 ? sUninstallString.Substring(lastSpaceIdx).Trim() : sUninstallString; + sConfigName = sConfigName.Replace("productstoremove=", ""); + int underscoreIdx = sConfigName.IndexOf("_"); + if (underscoreIdx > 0) sConfigName = sConfigName.Substring(0, underscoreIdx); + } + else + { + iLeft = ArpItem.IndexOf(" - ") + 2; + iRight = ArpItem.IndexOf(" - ", iLeft); + if (iRight > 0) + { + sConfigName = ArpItem.Substring(iLeft, iRight - iLeft).Trim(); + sCulture = ArpItem.Substring(iRight + 3); + } + else + { + sConfigName = ArpItem.Substring(0, iLeft - 3).Trim(); + sCulture = ArpItem.Substring(iLeft); + } + sConfigName = sConfigName.Replace("Microsoft", "").Replace("Office", "") + .Replace("Professional", "Pro").Replace("Standard", "Std") + .Replace("(Technical Preview)", "").Replace("15", "").Replace("16", "") + .Replace("2013", "").Replace("2016", "").Replace(" ", "") + .Replace("Project", "Prj").Replace("Visio", "Vis"); + } + if (!dicInstalledSku.ContainsKey(sConfigName.ToLower())) + { + LogOnly($"add new product to dictionary (ARP Legacy): {sConfigName}"); + dicInstalledSku.Add(sConfigName.ToLower(), sDisplayVersion); + } + // categorize the SKU as C2R + if (!dicC2RSuite.ContainsKey(ArpItem)) dicC2RSuite.Add(ArpItem, $"{sConfigName} - {sDisplayVersion}"); + } + else if (fDisplayVersion && (ArpItem.ToUpper().Contains("OFFICE15.") || ArpItem.ToUpper().Contains("OFFICE16."))) + { + // classic .msi install SKU + iLeft = ArpItem.IndexOf(".") + 1; + iRight = ArpItem.IndexOf("-", iLeft); + sConfigName = iRight > 0 ? ArpItem.Substring(iLeft, iRight - iLeft) : ArpItem.Substring(iLeft); + sCulture = ""; + if (!dicKeepSku.ContainsKey(ArpItem)) dicKeepSku.Add(ArpItem, $"{sConfigName} - {sDisplayVersion}"); + } + + // Other products + if (InScope(ArpItem)) + { + string midValue = ArpItem.Length > 14 ? ArpItem.Substring(10, 4) : ""; + switch (midValue) + { + case "007E": + case "008F": + case "008C": + case "00DD": + sConfigName = "Habanero"; + RegReadValue(Constants.HKLM, sCurKey, "DisplayName", out sConfigName, "REG_SZ"); + if (!dicInstalledSku.ContainsKey(ArpItem.ToLower())) + { + LogOnly($"add new product to dictionary (ARP Integraton Components): {ArpItem}"); + dicInstalledSku.Add(ArpItem.ToLower(), sDisplayVersion); + } + if (!dicC2RSuite.ContainsKey(ArpItem)) dicC2RSuite.Add(ArpItem, $"{sConfigName} - {sDisplayVersion}"); + break; + case "24E1": + case "237A": + sConfigName = "MSOIDLOGIN"; + if (!dicInstalledSku.ContainsKey(ArpItem.ToLower())) + { + LogOnly($"add new product to dictionary (ARP MSOIDLogin): {ArpItem}"); + dicInstalledSku.Add(ArpItem.ToLower(), sDisplayVersion); + } + if (!dicC2RSuite.ContainsKey(ArpItem)) dicC2RSuite.Add(ArpItem, $"{sConfigName} - {sDisplayVersion}"); + break; + default: + if (!dicInstalledSku.ContainsKey(ArpItem.ToLower())) + { + LogOnly($"add new product to dictionary (ARP other): {ArpItem}"); + dicInstalledSku.Add(ArpItem.ToLower(), sDisplayVersion); + } + break; + } + } + // End legacy logic + } + } + } +} + +//------------------------------------------------------------------------------- +// EnsureValidWIMetadata +// +// Ensures that only valid metadata entries exist to avoid API failures. +// Invalid entries will be removed +//------------------------------------------------------------------------------- +public void EnsureValidWIMetadata(int hDefKey, string sKey, int iValidLength) +{ + string[] arrKeys; + + if (sKey.Length > 1) + { + if (sKey.EndsWith("\\")) sKey = sKey.Substring(0, sKey.Length - 1); + } + + if (RegEnumKey(hDefKey, sKey, out arrKeys)) + { + foreach (string subKey in arrKeys) + { + if (subKey.Length != iValidLength) + { + RegDeleteKey(hDefKey, $"{sKey}\\{subKey}\\"); + } + } + } +} + +//------------------------------------------------------------------------------- +// CleanOSPP +// +// Clean out licenses from the Office Software Protection Platform +//------------------------------------------------------------------------------- +public void CleanOSPP() +{ + const string OfficeAppId = "0ff1ce15-a989-479d-af46-f275c6370663"; //Office 2013 + + string sCleanOSPP = "x64\\CleanOSPP.exe"; + if (!f64) sCleanOSPP = "x86\\CleanOSPP.exe"; + if (File.Exists(sScriptDir + sCleanOSPP)) + { + string sCmd = sScriptDir + sCleanOSPP; + Log($" Running: {sCmd}"); + try + { + if (!fDetectOnly) + { + var process = System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo + { + FileName = sCmd, + UseShellExecute = false, + CreateNoWindow = true + }); + process?.WaitForExit(); + int sRetVal = process?.ExitCode ?? 0; + Log($" Return value: {sRetVal}"); + } + } + catch { } + return; + } + + try + { + if (!(dicC2RSuite.Count > 0 || dicKeepSku.Count > 0)) + { + Log("Skip CleanOSPP"); + return; + } + + // Initialize the software protection platform object with a filter on Office 2013 products + var scope = new System.Management.ManagementScope(@"\\.\root\cimv2"); + string queryString; + if (iVersionNT > 601) + { + queryString = $"SELECT ID, ApplicationId, PartialProductKey, Name, ProductKeyID FROM SoftwareLicensingProduct WHERE ApplicationId = '{OfficeAppId}' AND PartialProductKey <> NULL"; + } + else + { + queryString = $"SELECT ID, ApplicationId, PartialProductKey, Name, ProductKeyID FROM OfficeSoftwareProtectionProduct WHERE ApplicationId = '{OfficeAppId}' AND PartialProductKey <> NULL"; + } + var query = new System.Management.ObjectQuery(queryString); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query)) + { + // Remove all licenses + foreach (System.Management.ManagementObject pi in searcher.Get()) + { + if (pi != null) + { + try + { + pi.InvokeMethod("UninstallProductKey", new object[] { pi["ProductKeyID"] }); + } + catch { } + } + } + } + } + catch { } +} + +//------------------------------------------------------------------------------- +// ClearVNextLicCache +// +// clear local license cache for vNext +//------------------------------------------------------------------------------- +public void ClearVNextLicCache() +{ + string sLocalAppData = ExpandEnv("%localappdata%"); + DeleteFolder($"{sLocalAppData}\\Microsoft\\Office\\Licenses"); +} + + +//------------------------------------------------------------------------------- +// DelSchtasks +// +// Delete known scheduled tasks. +//------------------------------------------------------------------------------- +public void DelSchtasks() +{ + if ((iError & Constants.ERROR_USERCANCEL) != 0) return; + + LogH1("Remove scheduled tasks"); + + LogOnly("FF_INTEGRATEDstreamSchedule"); + RunCommand("SCHTASKS /Delete /TN FF_INTEGRATEDstreamSchedule /F", false); + System.Threading.Thread.Sleep(500); + + LogOnly("FF_INTEGRATEDUPDATEDETECTION"); + RunCommand("SCHTASKS /Delete /TN FF_INTEGRATEDUPDATEDETECTION /F", false); + System.Threading.Thread.Sleep(500); + + LogOnly("C2RAppVLoggingStart"); + RunCommand("SCHTASKS /Delete /TN C2RAppVLoggingStart /F", false); + System.Threading.Thread.Sleep(500); + + LogOnly("Office 15 Subscription Heartbeat"); + string sCmd = "SCHTASKS /Delete /TN \"" + "Office 15 Subscription Heartbeat" + "\" /F"; + RunCommand(sCmd, false); + System.Threading.Thread.Sleep(500); + + LogOnly("Microsoft Office 15 Sync Maintenance"); + sCmd = "SCHTASKS /Delete /TN \"" + "Microsoft Office 15 Sync Maintenance for {d068b555-9700-40b8-992c-f866287b06c1}" + "\" /F"; + RunCommand(sCmd, false); + System.Threading.Thread.Sleep(500); + + LogOnly("OfficeInventoryAgentFallBack"); + sCmd = "SCHTASKS /Delete /TN \"" + "\\Microsoft\\Office\\OfficeInventoryAgentFallBack" + "\" /F"; + RunCommand(sCmd, false); + System.Threading.Thread.Sleep(500); + + LogOnly("OfficeTelemetryAgentFallBack"); + sCmd = "SCHTASKS /Delete /TN \"" + "\\Microsoft\\Office\\OfficeTelemetryAgentFallBack" + "\" /F"; + RunCommand(sCmd, false); + System.Threading.Thread.Sleep(500); + + LogOnly("OfficeInventoryAgentLogOn"); + sCmd = "SCHTASKS /Delete /TN \"" + "\\Microsoft\\Office\\OfficeInventoryAgentLogOn" + "\" /F"; + RunCommand(sCmd, false); + + LogOnly("OfficeTelemetryAgentLogOn"); + sCmd = "SCHTASKS /Delete /TN \"" + "\\Microsoft\\Office\\OfficeTelemetryAgentLogOn" + "\" /F"; + RunCommand(sCmd, false); + + LogOnly("Office Background Streaming"); + sCmd = "SCHTASKS /Delete /TN \"" + "Office Background Streaming" + "\" /F"; + RunCommand(sCmd, false); + System.Threading.Thread.Sleep(500); + + LogOnly("Office Automatic Updates"); + sCmd = "SCHTASKS /Delete /TN \"" + "\\Microsoft\\Office\\Office Automatic Updates" + "\" /F"; + RunCommand(sCmd, false); + System.Threading.Thread.Sleep(500); + + LogOnly("Office ClickToRun Service Monitor"); + sCmd = "SCHTASKS /Delete /TN \"" + "\\Microsoft\\Office\\Office ClickToRun Service Monitor" + "\" /F"; + RunCommand(sCmd, false); + System.Threading.Thread.Sleep(500); + + LogOnly("Office Subscription Maintenance"); + sCmd = "SCHTASKS /Delete /TN \"" + "Office Subscription Maintenance" + "\" /F"; + RunCommand(sCmd, false); + System.Threading.Thread.Sleep(500); +} + +//------------------------------------------------------------------------------- +// CloseOfficeApps +// +// End all running instances of applications that will be removed. +//------------------------------------------------------------------------------- +public void CloseOfficeApps() +{ + bool fWait = false; + iProcCloseCnt = iProcCloseCnt + 1; + if (fRerun) return; + + try + { + if (!fUserConsent) + { + // detect processes to allow a user warning + string sUserWarn = "Please save all open documents and close all Office, IE and Windows Explorer applications before proceeding.\r\n" + + "When you click OK this removal process will terminate all running Office, IE and Windows Explorer processes and applications.\r\n\r\n" + + "Click \"Cancel\" to end this removal now."; + var scope = new System.Management.ManagementScope(@"\\.\root\cimv2"); + + foreach (string app in dicApps.Keys) + { + string sAppName = app.Replace(".", "%."); + var query = new System.Management.ObjectQuery($"SELECT * FROM Win32_Process WHERE Name LIKE '{sAppName}'"); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query)) + { + foreach (System.Management.ManagementObject process in searcher.Get()) + { + string processName = process["Name"]?.ToString() ?? ""; + if (!sUserWarn.Contains(processName)) + { + sUserWarn += "\r\n - " + processName; + } + } + } + } + + var query2 = new System.Management.ObjectQuery("SELECT * FROM Win32_Process"); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query2)) + { + foreach (System.Management.ManagementObject process in searcher.Get()) + { + string executablePath = process["ExecutablePath"]?.ToString() ?? ""; + if (!string.IsNullOrEmpty(executablePath) && IsC2R(executablePath)) + { + string processName = process["Name"]?.ToString() ?? ""; + if (!sUserWarn.Contains(processName)) + { + sUserWarn += "\r\n - " + processName; + } + } + } + } + + if (sUserWarn.Contains(" - ") && !fQuiet) + { + var result = MessageBox.Show( + sUserWarn, + "Save your unsaved work now!", + MessageBoxButtons.OKCancel, + MessageBoxIcon.Warning + ); + if (result == DialogResult.Cancel) + { + SetError(Constants.ERROR_USERCANCEL); + ExitScript(); + return; + } + else + { + fUserConsent = true; + } + } + } + + // end known processes first + scope = new System.Management.ManagementScope(@"\\.\root\cimv2"); + foreach (string app in dicApps.Keys) + { + string sAppName = app.Replace(".", "%."); + var query = new System.Management.ObjectQuery($"SELECT * FROM Win32_Process WHERE Name LIKE '{sAppName}'"); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query)) + { + foreach (System.Management.ManagementObject process in searcher.Get()) + { + string processName = process["Name"]?.ToString() ?? ""; + try + { + process.InvokeMethod("Terminate", null); + uint iRet = 0; + Log($"End process '{processName}' returned: {iRet}"); + fWait = true; + } + catch (Exception ex) + { + Log($"Error terminating process {processName}: {ex.Message}"); + } + } + } + } + + // end running applications + var query3 = new System.Management.ObjectQuery("SELECT * FROM Win32_Process"); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query3)) + { + foreach (System.Management.ManagementObject process in searcher.Get()) + { + string executablePath = process["ExecutablePath"]?.ToString() ?? ""; + if (!string.IsNullOrEmpty(executablePath) && IsC2R(executablePath)) + { + string processName = process["Name"]?.ToString() ?? ""; + try + { + process.InvokeMethod("Terminate", null); + uint iRet = 0; + Log($"End process '{processName}' returned: {iRet}"); + fWait = true; + } + catch (Exception ex) + { + Log($"Error terminating process {processName}: {ex.Message}"); + } + } + } + } + + if (fWait) System.Threading.Thread.Sleep(5000); + } + catch (Exception ex) + { + Log($"Error in CloseOfficeApps: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// Uninstall +// +// Identify and invoke default uninstall command for a regular uninstall. +//------------------------------------------------------------------------------- +public void Uninstall() +{ + if ((iError & Constants.ERROR_USERCANCEL) != 0) return; + + try + { + int hDefKey; + string sSubKeyName, sValue, Name, sPkgFld, sPkgGuid, sUninstallCmd, sMsiProp, sCmd; + string[] arrNames, arrTypes; + int sReturn; + + // check if OSE service is *installed, *not disabled, *running under System context. + LogH2("Check state of OSE service"); + var scope = new System.Management.ManagementScope(@"\\.\root\cimv2"); + var query = new System.Management.ObjectQuery("SELECT * FROM Win32_Service WHERE Name LIKE 'ose%'"); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query)) + { + foreach (System.Management.ManagementObject srvc in searcher.Get()) + { + try + { + if (srvc["StartMode"]?.ToString() == "Disabled") + { + uint result = (uint)srvc.InvokeMethod("ChangeStartMode", new object[] { "Manual" }); + if (result != 0) + { + Log("Conflict detected: OSE service is disabled"); + } + } + if (srvc["StartName"]?.ToString() != "LocalSystem") + { + uint result = (uint)srvc.InvokeMethod("Change", new object[] { null, null, null, null, null, null, "LocalSystem", "" }); + if (result == 0) + { + Log("Conflict detected: OSE service not running as LocalSystem"); + } + } + } + catch { } + } + } + + if (dicC2RSuite.Count == 0) + { + Log("No uninstallable C2R items registered in Uninstall"); + } + + // call odt based uninstall + UninstallOfficeC2R(); + + // remove the published component registration for C2R packages + LogH2("Remove published component registration for C2R packages"); + // delete the manifest files + for (int i = 1; i <= 4; i++) + { + switch (i) + { + case 1: + RegReadValue(Constants.HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun", "PackageFolder", out sPkgFld, "REG_SZ"); + RegReadValue(Constants.HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun", "PackageGUID", out sPkgGuid, "REG_SZ"); + break; + case 2: + RegReadValue(Constants.HKLM, @"SOFTWARE\Microsoft\Office\16.0\ClickToRun", "PackageFolder", out sPkgFld, "REG_SZ"); + RegReadValue(Constants.HKLM, @"SOFTWARE\Microsoft\Office\16.0\ClickToRun", "PackageGUID", out sPkgGuid, "REG_SZ"); + break; + case 3: + RegReadValue(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun", "PackageFolder", out sPkgFld, "REG_SZ"); + RegReadValue(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun", "PackageGUID", out sPkgGuid, "REG_SZ"); + break; + case 4: + sPkgFld = sPackageFolder; + sPkgGuid = sPackageGuid; + break; + } + if (!string.IsNullOrEmpty(sPkgFld) && Directory.Exists(Path.Combine(sPkgFld, "root", "Integration"))) + { + sCmd = $"cmd.exe /c del \"{sPkgFld}\\root\\Integration\\C2RManifest*.xml\""; + Log($" Run: {sCmd}"); + if (!fDetectOnly) sReturn = RunCommand(sCmd, true); + Log($" Return value: {sReturn}"); + string integratorPath = Path.Combine(sPkgFld, "root", "Integration", "integrator.exe"); + if (File.Exists(integratorPath)) + { + sCmd = $"\"{integratorPath}\" /U /Extension PackageRoot=\"{sPkgFld}\\root\" PackageGUID={sPkgGuid}"; + Log($" Run: {sCmd}"); + if (!fDetectOnly) sReturn = RunCommand(sCmd, true); + Log($" Return value: {sReturn}"); + sCmd = $"\"{integratorPath}\" /U"; + Log($" Run: {sCmd}"); + if (!fDetectOnly) sReturn = RunCommand(sCmd, true); + Log($" Return value: {sReturn}"); + } + string programDataIntegrator = Path.Combine(sProgramData, "Microsoft", "ClickToRun", $"{{{sPkgGuid}}}", "integrator.exe"); + if (File.Exists(programDataIntegrator)) + { + sCmd = $"\"{programDataIntegrator}\" /U /Extension PackageRoot=\"{sPkgFld}\\root\" PackageGUID={sPkgGuid}"; + Log($" Run: {sCmd}"); + if (!fDetectOnly) sReturn = RunCommand(sCmd, true); + Log($" Return value: {sReturn}"); + } + } + } + + // delete potential blocking registry keys for msiexec based tasks + LogH2("Remove C2R and App-V registry data"); + foreach (string sku in dicC2RSuite.Keys) + { + // remove the ARP entry + RegDeleteKey(Constants.HKLM, Constants.REG_ARP + sku); + } + RegDeleteKey(Constants.HKCU, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun"); + RegDeleteKey(Constants.HKCU, @"SOFTWARE\Microsoft\Office\16.0\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\16.0\ClickToRun"); + RegDeleteKey(Constants.HKCU, @"SOFTWARE\Microsoft\Office\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun"); + + // AppV keys + hDefKey = Constants.HKCU; + sSubKeyName = @"SOFTWARE\Microsoft\AppV\ISV"; + do + { + LogOnly($"Scanning key: {sSubKeyName}"); + if (RegEnumValues(hDefKey, sSubKeyName, out arrNames, out arrTypes)) + { + foreach (string nameItem in arrNames) + { + Name = nameItem; + if (IsC2R(Name)) RegDeleteValue(hDefKey, sSubKeyName, Name, false); + } + } + if (hDefKey == Constants.HKLM) break; + hDefKey = Constants.HKLM; + } while (true); + + hDefKey = Constants.HKCU; + sSubKeyName = @"SOFTWARE\Microsoft\AppVISV"; + do + { + LogOnly($"Scanning key: {sSubKeyName}"); + if (RegEnumValues(hDefKey, sSubKeyName, out arrNames, out arrTypes)) + { + foreach (string nameItem in arrNames) + { + Name = nameItem; + if (IsC2R(Name)) RegDeleteValue(hDefKey, sSubKeyName, Name, false); + } + } + if (hDefKey == Constants.HKLM) break; + hDefKey = Constants.HKLM; + } while (true); + + // msiexec based uninstall + sMsiProp = " REBOOT=ReallySuppress NOREMOVESPAWN=True"; + LogH2("Detect Msi based products"); + // Note: oMsi.Products requires COM interop for WindowsInstaller.Installer + // For now, we'll enumerate MSI products from registry + try + { + string[] arrKeys; + if (RegEnumKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products", out arrKeys)) + { + foreach (string prod in arrKeys) + { + if (CheckDelete(prod)) + { + Log($"Call msiexec.exe to remove {prod}"); + sUninstallCmd = $"msiexec.exe /x{prod}{sMsiProp}"; + if (fQuiet) + { + sUninstallCmd += " /q"; + } + else + { + sUninstallCmd += " /qb-!"; + } + sUninstallCmd += $" /l*v \"{Path.Combine(sLogDir, $"Uninstall_{prod}.log")}\""; + CloseOfficeApps(); + LogOnly($"Call msiexec with '{sUninstallCmd}'"); + if (!fDetectOnly) + { + sReturn = RunCommand(sUninstallCmd, true); + Log($"msiexec returned: {SetupRetVal(sReturn)} ({sReturn})\r\n"); + fRebootRequired = fRebootRequired || (sReturn == 3010); + if (fRebootRequired) SetError(Constants.ERROR_REBOOT_REQUIRED); + switch (sReturn) + { + case Constants.ERROR_SUCCESS: + case Constants.ERROR_SUCCESS_CONFIG_COMPLETE: + case Constants.ERROR_SUCCESS_REBOOT_REQUIRED: + //success no action required + break; + default: + SetError(Constants.ERROR_STAGE1); + break; + } + } + } + else + { + LogOnly($"Skip out of scope product: {prod}"); + } + } + } + } + catch (Exception ex) + { + Log($"Error enumerating MSI products: {ex.Message}"); + } + + if (!fDetectOnly) RunCommand("cmd.exe /c net stop msiserver", false); + } + catch (Exception ex) + { + Log($"Error in Uninstall: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// BuildRemoveXml +// +//------------------------------------------------------------------------------- +public void BuildRemoveXml() +{ + try + { + LogOnly("BuildRemoveXml"); + string sConfigRemoveAllXml; + if (fQuiet) + { + sConfigRemoveAllXml = "\r\n" + + " \r\n" + + " \r\n" + + ""; + } + else + { + sConfigRemoveAllXml = "\r\n" + + " \r\n" + + ""; + } + + // write out the config.xml + string configPath = Path.Combine(sScrubDir, "RemoveAll.xml"); + File.WriteAllText(configPath, sConfigRemoveAllXml); + LogOnly($"RemoveAll.xml:\r\n{sConfigRemoveAllXml}"); + } + catch (Exception ex) + { + Log($"Error building RemoveAll.xml: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// HttpDownloadFile +// +// Copy a file from a url to a local path using HttpClient +//------------------------------------------------------------------------------- +public bool HttpDownloadFile(string sUrl, string sLocalPath) +{ + try + { + Log($"Download {sUrl} to {sLocalPath}"); + + using (var httpClient = new System.Net.Http.HttpClient()) + { + var response = httpClient.GetAsync(sUrl).Result; + if (response.IsSuccessStatusCode) + { + var fileBytes = response.Content.ReadAsByteArrayAsync().Result; + File.WriteAllBytes(sLocalPath, fileBytes); + } + else + { + Log($"Download failed with status code: {response.StatusCode}"); + return false; + } + } + + bool fileExists = File.Exists(sLocalPath); + Log($"Check download success. {sLocalPath} exists: {fileExists}"); + return fileExists; + } + catch (Exception ex) + { + Log($"Error downloading file: {ex.Message}"); + return false; + } +} + + +//------------------------------------------------------------------------------- +// UninstallOfficeC2R +// +// Uninstall all of Office C2R through ODT +//------------------------------------------------------------------------------- +public void UninstallOfficeC2R() +{ + try + { + string sCmd, sODTFullPath = "", sKey, sFolder, sLeft, sRight; + int iVerODT = 0; + bool fCanUseOdtUninstall = false; + + if (RegValExists(Constants.HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun\ProductReleaseIDs\Active\culture", "x-none")) + { + iVerODT = 15; + } + if (RegValExists(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs\Active\culture", "x-none") || + RegValExists(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs", "ActiveConfiguration")) + { + iVerODT = 16; + } + + if (RegValExists(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs\Active\culture", "x-none") || + RegValExists(Constants.HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun\ProductReleaseIDs\Active\culture", "x-none") || + RegValExists(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun\ProductReleaseIDs", "ActiveConfiguration")) + { + LogH($"ODT Uninstall C2R {iVerODT}.0"); + + if (!fForceArpUninstall) + { + //build the remove.xml + BuildRemoveXml(); + + //verify ODT is available + string odtPath = Path.Combine(sScriptDir, $"ODT{iVerODT}", "setup.exe"); + if (File.Exists(odtPath)) + { + sODTFullPath = odtPath; + fCanUseOdtUninstall = true; + } + else + { + //ODT not available. Try to download + if (!fOffline) + { + if (iVerODT == 15) + { + string downloadPath = Path.Combine(sScrubDir, "officedeploymenttool.exe"); + if (HttpDownloadFile("https://download.microsoft.com/download/6/2/3/6230F7A2-D8A9-478B-AC5C-57091B632FCF/officedeploymenttool_x86_5031-1000.exe", downloadPath)) + { + //Extract + sCmd = $"\"{downloadPath}\" /quiet /extract:\"{sScrubDir}\""; + Log($"Run silent extract: {sCmd}"); + if (!fDetectOnly) + { + int sReturn = RunCommand(sCmd, true); + } + sODTFullPath = Path.Combine(sScrubDir, "setup.exe"); + if (File.Exists(sODTFullPath)) fCanUseOdtUninstall = true; + } + } + else + { + string setupPath = Path.Combine(sScrubDir, "setup.exe"); + if (HttpDownloadFile("http://officecdn.microsoft.com/pr/wsus/setup.exe", setupPath)) + { + sODTFullPath = setupPath; + fCanUseOdtUninstall = true; + } + } + } + } + + Log($"Can use ODT based uninstall: {fCanUseOdtUninstall}"); + + if (fCanUseOdtUninstall) + { + //build uninstall command + string xmlPath = Path.Combine(sScrubDir, "RemoveAll.xml"); + sCmd = $"\"{sODTFullPath}\" /configure \"{xmlPath}\""; + Log($"run uninstall: {sCmd}"); + if (!fDetectOnly) + { + int sReturn = RunCommand(sCmd, true); + Log($"ODT uninstall for OfficeC2R returned with value: {sReturn}"); + } + } + else + { + //Can't use ODT for uninstall attempt. Use unified ARP uninstall command + if (!string.IsNullOrEmpty(sArpUninstallCmd)) + { + sArpUninstallCmd = sArpUninstallCmd.Trim(); + int productIndex = sArpUninstallCmd.IndexOf(" productstoremove="); + if (productIndex > 0) + { + sLeft = sArpUninstallCmd.Substring(0, productIndex); + sRight = sArpUninstallCmd.Substring(productIndex); + int spaceIndex = sRight.IndexOf(" "); + if (spaceIndex > 0) sRight = sRight.Substring(spaceIndex); + else sRight = ""; + } + else + { + sLeft = sArpUninstallCmd; + sRight = ""; + } + sCmd = $"{sLeft}productstoremove=\"AllProducts\"{sRight}"; + if (fQuiet) sCmd += " displaylevel=\"false\""; + Log($"run uninstall: {sCmd}"); + if (!fDetectOnly) + { + int sReturn = RunCommand(sCmd, true); + Log($"ARP uninstall for OfficeC2R returned with value: {sReturn}"); + } + } + } + } + else + { + Log("Skip ODT switch is active"); + } + } + else + { + Log("Uninstall for OfficeC2R not required"); + } + + //Log uninstall success + Log("Log uninstall success"); + + sKey = @"SOFTWARE\Microsoft\Office\ClickToRun"; + Log($"HKLM\\{sKey} still exists: {RegKeyExists(Constants.HKLM, sKey)}"); + + sFolder = $"\"{ExpandEnv("%programfiles%")}\\Microsoft Office\\root\""; + Log($"{sFolder} still exists: {Directory.Exists(ExpandEnv("%programfiles%") + "\\Microsoft Office\\root")}"); + + if (f64) + { + sFolder = $"\"{ExpandEnv("%programfiles(x86)%")}\\Microsoft Office\\root\""; + Log($"{sFolder} exists: {Directory.Exists(ExpandEnv("%programfiles(x86)%") + "\\Microsoft Office\\root")}"); + } + } + catch (Exception ex) + { + Log($"Error in UninstallOfficeC2R: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// RunCommand +// +// Helper method to run a command and return exit code +//------------------------------------------------------------------------------- +private int RunCommand(string command, bool waitForExit) +{ + try + { + ProcessStartInfo processInfo; + + // Check if command already starts with cmd.exe + if (command.StartsWith("cmd.exe", StringComparison.OrdinalIgnoreCase)) + { + // Command already includes cmd.exe, parse it + var parts = command.Split(new[] { ' ' }, 2, StringSplitOptions.RemoveEmptyEntries); + processInfo = new ProcessStartInfo + { + FileName = parts[0], + Arguments = parts.Length > 1 ? parts[1] : "", + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + } + else + { + // Wrap in cmd.exe /c + processInfo = new ProcessStartInfo + { + FileName = "cmd.exe", + Arguments = "/c " + command, + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + } + + using (var process = Process.Start(processInfo)) + { + if (waitForExit && process != null) + { + process.WaitForExit(); + return process.ExitCode; + } + return 0; + } + } + catch (Exception ex) + { + Log($"Error running command '{command}': {ex.Message}"); + return -1; + } +} + +//------------------------------------------------------------------------------- +// RegWipe +// +// Removal of left behind registry data +//------------------------------------------------------------------------------- +public void Regwipe() +{ + if ((iError & Constants.ERROR_USERCANCEL) != 0) return; + + try + { + int hDefKey; + string item, name, value, sGuid, sSubKeyName, sValue; + int i; + string[] arrKeys, arrNames, arrTypes, arrTestNames, arrTestTypes; + string[] arrMultiSzValues, arrMultiSzNewValues; + bool fDelReg; + + LogH1("Registry CleanUp"); + + //Moved to earlier timing to avoid reboot needs + //if (dicKeepSku.Count == 0) ClearShellIntegrationReg(); + + CloseOfficeApps(); + + // Note: ARP entries have already been cleared in uninstall stage + + // HKCU Registration + RegDeleteKey(Constants.HKCU, @"Software\Microsoft\Office\15.0\Registration"); + RegDeleteKey(Constants.HKCU, @"Software\Microsoft\Office\16.0\Registration"); + RegDeleteKey(Constants.HKCU, @"Software\Microsoft\Office\Registration"); + + // C2R specifics + // AppV key "SOFTWARE\Microsoft\AppV" has already been cleared in uninstall stage + + // Virtual InstallRoot + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\15.0\Common\InstallRoot\Virtual"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\16.0\Common\InstallRoot\Virtual"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\Common\InstallRoot\Virtual"); + + // Mapi Search reg + //O15 + if (dicKeepSku.Count == 0) RegDeleteKey(Constants.HKLM, @"SOFTWARE\Classes\CLSID\{2027FC3B-CF9D-4ec7-A823-38BA308625CC}"); + //O16 + //{F8E61EDD-EA25-484e-AC8A-7447F2AAE2A9} + + // C2R keys + RegDeleteKey(Constants.HKCU, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\15.0\ClickToRunStore"); + RegDeleteKey(Constants.HKCU, @"SOFTWARE\Microsoft\Office\16.0\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\16.0\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\16.0\ClickToRunStore"); + RegDeleteKey(Constants.HKCU, @"SOFTWARE\Microsoft\Office\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRun"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Office\ClickToRunStore"); + + // Office key in HKLM + if (dicKeepSku.Count == 0) + { + //double calls to ensure Wow6432 gets cleared out as well + RegDeleteKey(Constants.HKLM, @"Software\Microsoft\Office\15.0"); + RegDeleteKey(Constants.HKLM, @"Software\Microsoft\Office\15.0"); + RegDeleteKey(Constants.HKLM, @"Software\Microsoft\Office\16.0"); + RegDeleteKey(Constants.HKLM, @"Software\Microsoft\Office\16.0"); + } + ClearOfficeHKLM(@"SOFTWARE\Microsoft\Office"); + + // Run key + sSubKeyName = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run"; + if (RegEnumValues(Constants.HKLM, sSubKeyName, out arrNames, out arrTypes)) + { + foreach (string nameItem in arrNames) + { + name = nameItem; + if (RegReadValue(Constants.HKLM, sSubKeyName, name, out sValue, "REG_SZ")) + { + if (IsC2R(sValue)) RegDeleteValue(Constants.HKLM, sSubKeyName, name, false); + } + } + } + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", "Lync15", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Run", "Lync16", false); + + // ARP + // Note: configuration entries have already been removed + // as part of the 'Uninstall' stage + if (RegEnumKey(Constants.HKLM, Constants.REG_ARP, out arrKeys)) + { + foreach (string itemKey in arrKeys) + { + item = itemKey; + if (item.Length > 37) + { + sGuid = item.Substring(0, 38).ToUpper(); + if (CheckDelete(sGuid)) RegDeleteKey(Constants.HKLM, Constants.REG_ARP + item + "\\"); + } + } + } + + // UpgradeCodes, WI config, WI global config + LogH2("Scan Windows Installer metadata for removeable UpgradeCodes"); + for (int iLoopCnt = 1; iLoopCnt <= 5; iLoopCnt++) + { + switch (iLoopCnt) + { + case 1: + sSubKeyName = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes\"; + hDefKey = Constants.HKLM; + break; + case 2: + sSubKeyName = @"Installer\UpgradeCodes\"; + hDefKey = Constants.HKCR; + break; + case 3: + sSubKeyName = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Products\"; + hDefKey = Constants.HKLM; + break; + case 4: + sSubKeyName = @"Installer\Features\"; + hDefKey = Constants.HKCR; + break; + case 5: + sSubKeyName = @"Installer\Products\"; + hDefKey = Constants.HKCR; + break; + } + if (RegEnumKey(hDefKey, sSubKeyName, out arrKeys)) + { + foreach (string itemKey in arrKeys) + { + item = itemKey; + // ensure the expected length for a compressed GUID + if (item.Length == 32) + { + // expand the GUID + sGuid = GetExpandedGuid(item); + // check if it's an Office key + if (CheckDelete(sGuid)) + { + if (iLoopCnt < 3) + { + // enum all entries + if (RegEnumValues(hDefKey, sSubKeyName + item, out arrNames, out arrTypes)) + { + if (arrNames != null) + { + // delete entries within removal scope + foreach (string nameItem in arrNames) + { + name = nameItem; + if (name.Length == 32) + { + sGuid = GetExpandedGuid(name); + if (CheckDelete(sGuid)) RegDeleteValue(hDefKey, sSubKeyName + item + "\\", name, true); + } + else + { + // invalid data -> delete the value + RegDeleteValue(hDefKey, sSubKeyName + item + "\\", name, true); + } + } + } + // if all entries were removed - delete the key + if (!RegEnumValues(hDefKey, sSubKeyName + item, out arrTestNames, out arrTestTypes)) + { + RegDeleteKey(hDefKey, sSubKeyName + item + "\\"); + } + } + } + else //iLoopCnt >= 3 + { + RegDeleteKey(hDefKey, sSubKeyName + item + "\\"); + } + } + } + } + } + } + + // Components in Global + LogH2("Scan Windows Installer Global Components metadata"); + sSubKeyName = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UserData\S-1-5-18\Components\"; + hDefKey = Constants.HKLM; + if (RegEnumKey(hDefKey, sSubKeyName, out arrKeys)) + { + foreach (string itemKey in arrKeys) + { + item = itemKey; + // ensure the expected length for a compressed GUID + if (item.Length == 32) + { + if (RegEnumValues(hDefKey, sSubKeyName + item, out arrNames, out arrTypes)) + { + foreach (string nameItem in arrNames) + { + name = nameItem; + if (name.Length == 32) + { + sGuid = GetExpandedGuid(name); + if (CheckDelete(sGuid)) + { + RegDeleteValue(hDefKey, sSubKeyName + item + "\\", name, false); + // if all entries were removed - delete the key + if (!RegEnumValues(hDefKey, sSubKeyName + item, out arrTestNames, out arrTestTypes)) + { + RegDeleteKey(hDefKey, sSubKeyName + item + "\\"); + } + } + } + } + } + } + } + } + + // Published Components + LogH2("Scanning Windows Installer Published Components metadata"); + sSubKeyName = @"Installer\Components\"; + hDefKey = Constants.HKCR; + if (RegEnumKey(hDefKey, sSubKeyName, out arrKeys)) + { + foreach (string itemKey in arrKeys) + { + item = itemKey; + // ensure the expected length for a compressed GUID + if (item.Length == 32) + { + if (RegEnumValues(hDefKey, sSubKeyName + item, out arrNames, out arrTypes)) + { + foreach (string nameItem in arrNames) + { + name = nameItem; + if (RegReadValue(hDefKey, sSubKeyName + item, name, out sValue, "REG_MULTI_SZ")) + { + arrMultiSzValues = sValue.Split('\r'); + if (arrMultiSzValues != null) + { + var newValuesList = new List(); + fDelReg = false; + foreach (string valueItem in arrMultiSzValues) + { + value = valueItem; + if (value.Length > 19) + { + sGuid = ""; + if (GetDecodedGuid(value.Substring(0, Math.Min(Constants.SQUISHED, value.Length)), out sGuid)) + { + if (CheckDelete(sGuid)) + { + fDelReg = true; + } + else + { + newValuesList.Add(value); + } + } + } + } + if (newValuesList.Count > 0) + { + arrMultiSzNewValues = newValuesList.ToArray(); + if (arrMultiSzValues.Length != newValuesList.Count) + { + // Update registry with new values - requires COM interop or registry API + // For now, just delete if all should be removed + if (newValuesList.Count == 0) + { + RegDeleteValue(hDefKey, sSubKeyName + item + "\\", name, true); + if (!RegEnumValues(hDefKey, sSubKeyName + item, out arrTestNames, out arrTestTypes)) + { + RegDeleteKey(hDefKey, sSubKeyName + item + "\\"); + } + } + } + } + else + { + if (fDelReg) + { + RegDeleteValue(hDefKey, sSubKeyName + item + "\\", name, true); + // if all entries were removed - delete the key + if (!RegEnumValues(hDefKey, sSubKeyName + item, out arrTestNames, out arrTestTypes)) + { + RegDeleteKey(hDefKey, sSubKeyName + item + "\\"); + } + } + } + } + } + } + } + } + } + } + + // Known Typelib Registration + LogH2("Scanning known Office TypeLibs registration"); + RegWipeTypeLib(); + } + catch (Exception ex) + { + Log($"Error in Regwipe: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// ClearShellIntegrationReg +// +// Delete registry items that may cause Explorer / Windows Shell to have a lock +// on files +//------------------------------------------------------------------------------- +public void ClearShellIntegrationReg() +{ + try + { + // Protocol Handlers + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Classes\Protocols\Handler\osf"); + + // Context Menu Handlers + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Classes\CLSID\{573FFD05-2805-47C2-BCE0-5F19512BEB8D}"); + //RegDeleteKey(Constants.HKLM, @"SOFTWARE\Classes\CLSID\{4693FF15-B962-420A-9E5D-176F7D4B8321}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Classes\CLSID\{8BA85C75-763B-4103-94EB-9470F12FE0F7}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Classes\CLSID\{CD55129A-B1A1-438E-A425-CEBC7DC684EE}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Classes\CLSID\{D0498E0A-45B7-42AE-A9AA-ABA463DBD3BF}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Classes\CLSID\{E768CD3B-BDDC-436D-9C13-E1B39CA257B1}"); + + // Groove ShellIconOverlayIdentifiers + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 1 (ErrorConflict)"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 2 (SyncInProgress)"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 3 (InSync)"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 1 (ErrorConflict)"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 2 (SyncInProgress)"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\ShellIconOverlayIdentifiers\Microsoft SPFS Icon Overlay 3 (InSync)"); + + // Shell extensions + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{B28AA736-876B-46DA-B3A8-84C5E30BA492}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{8B02D659-EBBB-43D7-9BBA-52CF22C5B025}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{0875DCB6-C686-4243-9432-ADCCF0B9F2D7}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{42042206-2D85-11D3-8CFF-005004838597}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{993BE281-6695-4BA5-8A2A-7AACBFAAB69E}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{C41662BB-1FA0-4CE0-8DC5-9B7F8279FF97}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{506F4668-F13E-4AA1-BB04-B43203AB3CC0}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{D66DC78C-4F61-447F-942B-3FB6980118CF}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{46137B78-0EC3-426D-8B89-FF7C3A458B5E}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{8BA85C75-763B-4103-94EB-9470F12FE0F7}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{CD55129A-B1A1-438E-A425-CEBC7DC684EE}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{D0498E0A-45B7-42AE-A9AA-ABA463DBD3BF}", false); + RegDeleteValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved\", "{E768CD3B-BDDC-436D-9C13-E1B39CA257B1}", false); + + // BHO + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{31D09BA0-12F5-4CCE-BE8A-2923E76605DA}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{B4F3A835-0E21-4959-BA22-42B3008E02FF}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{D0498E0A-45B7-42AE-A9AA-ABA463DBD3BF}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{31D09BA0-12F5-4CCE-BE8A-2923E76605DA}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{B4F3A835-0E21-4959-BA22-42B3008E02FF}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{D0498E0A-45B7-42AE-A9AA-ABA463DBD3BF}"); + + // OneNote Namespace Extension for Desktop + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{0875DCB6-C686-4243-9432-ADCCF0B9F2D7}"); + + // Web Sites + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\Namespace\{B28AA736-876B-46DA-B3A8-84C5E30BA492}"); + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\NetworkNeighborhood\Namespace\{46137B78-0EC3-426D-8B89-FF7C3A458B5E}"); + + // VolumeCaches + RegDeleteKey(Constants.HKLM, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\VolumeCaches\Microsoft Office Temp Files"); + + RestoreExplorer(); + FreeObjects(); + System.Threading.Thread.Sleep(500); + InitObjects(); + } + catch (Exception ex) + { + Log($"Error in ClearShellIntegrationReg: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// RegWipeTypeLib +// +// Clear out left behind Typelib registrations +//------------------------------------------------------------------------------- +//Clean out known typelib registration +public void RegWipeTypeLib() +{ + try + { + string sKey, sTLKey, sTLVerKey; + string sTypeLibs, tl, k, sValue, sFilePath; + string[] arrTypeLibs, arrKeys, arrKeys2; + bool fClearTL, fCanDelete; + + sTypeLibs = "{000204EF-0000-0000-C000-000000000046};{000204EF-0000-0000-C000-000000000046};{00020802-0000-0000-C000-000000000046};{00020813-0000-0000-C000-000000000046};{00020905-0000-0000-C000-000000000046};{0002123C-0000-0000-C000-000000000046};{00024517-0000-0000-C000-000000000046};{0002E157-0000-0000-C000-000000000046};{00062FFF-0000-0000-C000-000000000046};{0006F062-0000-0000-C000-000000000046};{0006F080-0000-0000-C000-000000000046};{012F24C1-35B0-11D0-BF2D-0000E8D0D146};{06CA6721-CB57-449E-8097-E65B9F543A1A};{07B06096-5687-4D13-9E32-12B4259C9813};{0A2F2FC4-26E1-457B-83EC-671B8FC4C86D};{0AF7F3BE-8EA9-4816-889E-3ED22871FE05};{0D452EE1-E08F-101A-852E-02608C4D0BB4};{0EA692EE-BB50-4E3C-AEF0-356D91732725};{1F8E79BA-9268-4889-ADF3-6D2AABB3C32C};{2374F0B1-3220-4c71-B702-AF799F31ABB4};{238AA1AC-786F-4C17-BAAB-253670B449B9};{28DD2950-2D4A-42B5-ABBF-500AA42E7EC1};{2A59CA0A-4F1B-44DF-A216-CB2C831E5870};{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52};{2DF8D04C-5BFA-101B-BDE5-00AA0044DE52};{2F7FC181-292B-11D2-A795-DFAA798E9148};{3120BA9F-4FC8-4A4F-AE1E-02114F421D0A};{31411197-A502-11D2-BBCA-00C04F8EC294};{3B514091-5A69-4650-87A3-607C4004C8F2};{47730B06-C23C-4FCA-8E86-42A6A1BC74F4};{49C40DDF-1B04-4868-B3B5-E49F120E4BFA};{4AC9E1DA-5BAD-4AC7-86E3-24F4CDCECA28};{4AFFC9A0-5F99-101B-AF4E-00AA003F0F07};{4D95030A-A3A9-4C38-ACA8-D323A2267698};{55A108B0-73BB-43db-8C03-1BEF4E3D2FE4};{56D04F5D-964F-4DBF-8D23-B97989E53418};{5B87B6F0-17C8-11D0-AD41-00A0C90DC8D9};{66CDD37F-D313-4E81-8C31-4198F3E42C3C};{6911FD67-B842-4E78-80C3-2D48597C2ED0};{698BB59C-38F1-4CEF-92F9-7E3986E708D3};{6DDCE504-C0DC-4398-8BDB-11545AAA33EF};{6EFF1177-6974-4ED1-99AB-82905F931B87};{73720002-33A0-11E4-9B9A-00155D152105};{759EF423-2E8F-4200-ADF0-5B6177224BEE};{76F6F3F5-9937-11D2-93BB-00105A994D2C};{773F1B9A-35B9-4E95-83A0-A210F2DE3B37};{7D868ACD-1A5D-4A47-A247-F39741353012};{7E36E7CB-14FB-4F9E-B597-693CE6305ADC};{831FDD16-0C5C-11D2-A9FC-0000F8754DA1};{8404DD0E-7A27-4399-B1D9-6492B7DD7F7F};{8405D0DF-9FDD-4829-AEAD-8E2B0A18FEA4};{859D8CF5-7ADE-4DAB-8F7D-AF171643B934};{8E47F3A2-81A4-468E-A401-E1DEBBAE2D8D};{91493440-5A91-11CF-8700-00AA0060263B};{9A8120F2-2782-47DF-9B62-54F672075EA1};{9B7C3E2E-25D5-4898-9D85-71CEA8B2B6DD};{9B92EB61-CBC1-11D3-8C2D-00A0CC37B591};{9D58B963-654A-4625-86AC-345062F53232};{9DCE1FC0-58D3-471B-B069-653CE02DCE88};{A4D51C5D-F8BF-46CC-92CC-2B34D2D89716};{A717753E-C3A6-4650-9F60-472EB56A7061};{AA53E405-C36D-478A-BBFF-F359DF962E6D};{AAB9C2AA-6036-4AE1-A41C-A40AB7F39520};{AB54A09E-1604-4438-9AC7-04BE3E6B0320};{AC0714F2-3D04-11D1-AE7D-00A0C90F26F4};{AC2DE821-36A2-11CF-8053-00AA006009FA};{B30CDC65-4456-4FAA-93E3-F8A79E21891C};{B8812619-BDB3-11D0-B19E-00A0C91E29D8};{B9164592-D558-4EE7-8B41-F1C9F66D683A};{B9AA1F11-F480-4054-A84E-B5D9277E40A8};{BA35B84E-A623-471B-8B09-6D72DD072F25};{BDEADE33-C265-11D0-BCED-00A0C90AB50F};{BDEADEF0-C265-11D0-BCED-00A0C90AB50F};{BDEADEF0-C265-11D0-BCED-00A0C90AB50F};{C04E4E5E-89E6-43C0-92BD-D3F2C7FBA5C4};{C3D19104-7A67-4EB0-B459-D5B2E734D430};{C78F486B-F679-4af5-9166-4E4D7EA1CEFC};{CA973FCA-E9C3-4B24-B864-7218FC1DA7BA};{CBA4EBC4-0C04-468d-9F69-EF3FEED03236};{CBBC4772-C9A4-4FE8-B34B-5EFBD68F8E27};{CD2194AA-11BE-4EFD-97A6-74C39C6508FF};{E0B12BAE-FC67-446C-AAE8-4FA1F00153A7};{E985809A-84A6-4F35-86D6-9B52119AB9D7};{ECD5307E-4419-43CF-8BDA-C9946AC375CF};{EDCD5812-6A06-43C3-AFAC-46EF5D14E22C};{EDCD5812-6A06-43C3-AFAC-46EF5D14E22C};{EDCD5812-6A06-43C3-AFAC-46EF5D14E22C};{EDDCFF16-3AEE-4883-BD91-0F3978640DFB};{EE9CFA8C-F997-4221-BE2F-85A5F603218F};{F2A7EE29-8BF6-4a6d-83F1-098E366C709C};{F3685D71-1FC6-4CBD-B244-E60D8C89990B}"; + arrTypeLibs = sTypeLibs.Split(';'); + sTLKey = @"Software\Classes\TypeLib\"; + + //iterate all known typelibs + foreach (string tlItem in arrTypeLibs) + { + tl = tlItem; + fClearTL = false; + sKey = sTLKey + tl; + if (RegKeyExists(Constants.HKLM, sKey)) + { + //enum subkeys + LogOnly($"Found registration for typelib {tl}"); + if (RegEnumKey(Constants.HKLM, sKey, out arrKeys)) + { + foreach (string kItem in arrKeys) + { + k = kItem; + sTLVerKey = sKey + "\\" + k; + fCanDelete = RegEnumKey(Constants.HKLM, sTLVerKey, out arrKeys2); + if (RegReadValue(Constants.HKLM, sTLVerKey + "\\0\\Win32\\", "", out sValue, "REG_SZ")) + { + LogOnly($"Found key HKLM\\{sTLVerKey}\\0\\Win32\\"); + //get the safe filepath + int lastDot = sValue.LastIndexOf("."); + sFilePath = lastDot >= 0 ? sValue.Substring(0, Math.Min(lastDot + 3, sValue.Length)) : sValue; + LogOnly($"Found filepath: {sValue} - using filepath: {sFilePath}"); + if (File.Exists(sFilePath)) + { + fCanDelete = false; + fClearTL = false; + LogOnly("File target still in use. TypeLib registration will persisted."); + } + else + { + fClearTL = fCanDelete; + LogOnly("File target not found. Flagging for delete"); + } + } + if (RegReadValue(Constants.HKLM, sTLVerKey + "\\9\\Win32\\", "", out sValue, "REG_SZ")) + { + LogOnly($"Found key HKLM\\{sTLVerKey}\\9\\Win32\\"); + //get the safe filepath + int lastDot = sValue.LastIndexOf("."); + sFilePath = lastDot >= 0 ? sValue.Substring(0, Math.Min(lastDot + 3, sValue.Length)) : sValue; + LogOnly($"Found filepath: {sValue} - using filepath: {sFilePath}"); + if (File.Exists(sFilePath)) + { + fCanDelete = false; + fClearTL = false; + LogOnly("File target still in use. TypeLib registration will persisted."); + } + else + { + fClearTL = fCanDelete; + LogOnly("File target not found. Flagging for delete"); + } + } + if (RegReadValue(Constants.HKLM, sTLVerKey + "\\0\\Win64\\", "", out sValue, "REG_SZ")) + { + LogOnly($"Found key HKLM\\{sTLVerKey}\\0\\Win64\\"); + //get the safe filepath + int lastDot = sValue.LastIndexOf("."); + sFilePath = lastDot >= 0 ? sValue.Substring(0, Math.Min(lastDot + 3, sValue.Length)) : sValue; + LogOnly($"Found filepath: {sValue} - using filepath: {sFilePath}"); + if (File.Exists(sFilePath)) + { + fCanDelete = false; + fClearTL = false; + LogOnly("File target still in use. TypeLib registration will persisted."); + } + else + { + fClearTL = fCanDelete; + LogOnly("File target not found. Flagging for delete"); + } + } + if (RegReadValue(Constants.HKLM, sTLVerKey + "\\9\\Win64\\", "", out sValue, "REG_SZ")) + { + LogOnly($"Found key HKLM\\{sTLVerKey}\\9\\Win64\\"); + //get the safe filepath + int lastDot = sValue.LastIndexOf("."); + sFilePath = lastDot >= 0 ? sValue.Substring(0, Math.Min(lastDot + 3, sValue.Length)) : sValue; + LogOnly($"Found filepath: {sValue} - using filepath: {sFilePath}"); + if (File.Exists(sFilePath)) + { + fCanDelete = false; + fClearTL = false; + LogOnly("File target still in use. TypeLib registration will persisted."); + } + else + { + fClearTL = fCanDelete; + LogOnly("File target not found. Flagging for delete"); + } + } + //remove the key if no valid usage references were found + if (fCanDelete) + { + LogOnly($"Removing version registration: HKLM\\{sTLVerKey}"); + RegDeleteKey(Constants.HKLM, sTLVerKey); + } + } + } + //Re-evaluate if there are subkeys left to determine if the whole typelib reg should be removed + if (!RegEnumKey(Constants.HKLM, sKey, out arrKeys)) + { + LogOnly("TypeLib registration obsolete - removing registration key"); + RegDeleteKey(Constants.HKLM, sKey); + } + } + } + } + catch (Exception ex) + { + Log($"Error in RegWipeTypeLib: {ex.Message}"); + } +} + + +//------------------------------------------------------------------------------- +// FileWipe +// +// Removal of left behind services, files and shortcuts +//------------------------------------------------------------------------------- +public void FileWipe() +{ + if ((iError & Constants.ERROR_USERCANCEL) != 0) return; + + try + { + bool fDelFolders; + + LogH1("File Cleanup"); + + fDelFolders = false; + CloseOfficeApps(); + DelSchtasks(); + + LogH1("Delete Services"); + // remove the OfficeSvc service + LogH2("Delete OfficeSvc service"); + DeleteService("OfficeSvc"); + + // SP1 addition / change + // remove the ClickToRunSvc service + LogH2("Delete ClickToRunSvc service"); + DeleteService("ClickToRunSvc"); + + // adding additional processes for termination + if (!dicApps.ContainsKey("explorer.exe")) dicApps.Add("explorer.exe", "explorer.exe"); + if (!dicApps.ContainsKey("msiexec.exe")) dicApps.Add("msiexec.exe", "msiexec.exe"); + if (!dicApps.ContainsKey("ose.exe")) dicApps.Add("ose.exe", "ose.exe"); + + if (fC2R) + { + LogH1("Delete Files and Folders"); + // delete C2R package files + LogH2("Delete C2R package files"); + if (FolderExists(sProgramFiles + "\\Microsoft Office 15") || + FolderExists(sProgramFiles + "\\Microsoft Office 16") || + FolderExists(ExpandEnv("%programfiles%") + "\\Microsoft Office\\PackageManifests") || + FolderExists(ExpandEnv("%programfiles(x86)%") + "\\Microsoft Office\\PackageManifests")) + { + fDelFolders = true; + Log(" Attention: Now closing Explorer.exe for file delete operations"); + Log(" Explorer will automatically restart."); + System.Threading.Thread.Sleep(2000); + CloseOfficeApps(); + } + // delete Office folders + LogH2("Delete Office folders"); + DeleteFolder(sProgramFiles + "\\Microsoft Office 15"); + DeleteFolder(sProgramFiles + "\\Microsoft Office 16"); + if (f64) + { + DeleteFolder(sCommonProgramFilesX86 + "\\Microsoft Office 15"); + DeleteFolder(sCommonProgramFilesX86 + "\\Microsoft Office 16"); + } + if (fDelFolders) + { + DeleteFolder(sProgramFiles + "\\Microsoft Office\\PackageManifests"); + DeleteFolder(sProgramFiles + "\\Microsoft Office\\PackageSunrisePolicies"); + DeleteFolder(sProgramFiles + "\\Microsoft Office\\root"); + DeleteFile(sProgramFiles + "\\Microsoft Office\\AppXManifest.xml"); + DeleteFile(sProgramFiles + "\\Microsoft Office\\FileSystemMetadata.xml"); + if (dicKeepSku.Count == 0) + { + DeleteFolder(sProgramFiles + "\\Microsoft Office\\Office16"); + DeleteFolder(sProgramFiles + "\\Microsoft Office\\Office15"); + } + if (f64) + { + DeleteFolder(sProgramFilesX86 + "\\Microsoft Office\\PackageManifests"); + DeleteFolder(sProgramFilesX86 + "\\Microsoft Office\\PackageSunrisePolicies"); + DeleteFolder(sProgramFilesX86 + "\\Microsoft Office\\root"); + DeleteFile(sProgramFilesX86 + "\\Microsoft Office\\AppXManifest.xml"); + DeleteFile(sProgramFilesX86 + "\\Microsoft Office\\FileSystemMetadata.xml"); + if (dicKeepSku.Count == 0) + { + DeleteFolder(sProgramFilesX86 + "\\Microsoft Office\\Office16"); + DeleteFolder(sProgramFilesX86 + "\\Microsoft Office\\Office15"); + } + } + } + + DeleteFolder(sProgramData + "\\Microsoft\\ClickToRun"); + DeleteFolder(sCommonProgramFiles + "\\microsoft shared\\ClickToRun"); + DeleteFolder(sProgramData + "\\Microsoft\\office\\FFPackageLocker"); + DeleteFolder(sProgramData + "\\Microsoft\\office\\ClickToRunPackageLocker"); + if (File.Exists(sProgramData + "\\Microsoft\\office\\FFPackageLocker")) DeleteFile(sProgramData + "\\Microsoft\\office\\FFPackageLocker"); + if (File.Exists(sProgramData + "\\Microsoft\\office\\FFStatePBLocker")) DeleteFile(sProgramData + "\\Microsoft\\office\\FFStatePBLocker"); + if (dicKeepSku.Count == 0) DeleteFolder(sProgramData + "\\Microsoft\\office\\Heartbeat"); + DeleteFolder(ExpandEnv("%userprofile%") + "\\Microsoft Office"); + DeleteFolder(ExpandEnv("%userprofile%") + "\\Microsoft Office 15"); + DeleteFolder(ExpandEnv("%userprofile%") + "\\Microsoft Office 16"); + } + + // restore explorer.exe if needed + RestoreExplorer(); + + // delete shortcuts + LogH2("Search and delete shortcuts"); + CleanShortcuts(sAllusersProfile, true, false); + CleanShortcuts(sProfilesDirectory, true, false); + + // delete empty folder structures + if (dicDelFolder.Count > 0) + { + LogH2("Remove empty folders"); + DeleteEmptyFolders(); + } + + // add the collected files in use for delete on reboot + if (dicDelInUse.Count > 0) ScheduleDeleteEx(); + + LogH2("File Cleanup complete"); + } + catch (Exception ex) + { + Log($"Error in FileWipe: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// CleanShortcuts +// +// Recursively search all profile folders for Office shortcuts in scope +//------------------------------------------------------------------------------- +public void CleanShortcuts(string sFolder, bool fDelete, bool fUnPin) +{ + if (fSkipSD) return; + + try + { + DirectoryInfo oFolder = new DirectoryInfo(sFolder); + // exclude system protected link folders + if ((oFolder.Attributes & FileAttributes.System) != 0) return; + + foreach (DirectoryInfo fld in oFolder.GetDirectories()) + { + try + { + CleanShortcuts(fld.FullName, fDelete, fUnPin); + } + catch (Exception ex) + { + CheckError($"CleanShortcuts: \t{sFolder}"); + Log($"Error processing folder {fld.FullName}: {ex.Message}"); + } + } + + foreach (FileInfo file in oFolder.GetFiles()) + { + try + { + string filePath = file.FullName; + if (filePath.ToLower().EndsWith(".lnk") && !filePath.ToLower().Contains("recentplaces")) + { + bool fDeleteSC = false; + LogOnly($" check file: {filePath}"); + // Use IWshRuntimeLibrary for shortcut handling - requires COM reference + // For now, we'll use a simplified approach + try + { + // Note: This requires COM interop for IWshShortcut + // Using dynamic to handle COM object + dynamic sc = null; // Would need: oWShell.CreateShortcut(filePath) + string targetPath = ""; // Would get from sc.TargetPath + + // Simplified check - would need actual COM interop implementation + // For now, checking file existence and path patterns + if (!string.IsNullOrEmpty(targetPath)) + { + if (targetPath.Contains("{")) + { + //Handle Windows Installer shortcuts + int braceIndex = targetPath.IndexOf("{"); + if (targetPath.Length >= braceIndex + 38) + { + string guid = targetPath.Substring(braceIndex, 38); + if (CheckDelete(guid)) fDeleteSC = true; + } + } + else + { + //Handle regular shortcuts + if (IsC2R(targetPath)) fDeleteSC = true; + if (!File.Exists(targetPath)) + { + // Shortcut target does not exist + if (IsC2R(targetPath)) + { + LogOnly($"remove Office shortcut with non-existent target: {filePath} - {targetPath}"); + fDeleteSC = true; + } + } + } + } + else + { + // If we can't read shortcut, check if path suggests Office + if (IsC2R(filePath)) fDeleteSC = true; + } + + if (fDeleteSC) + { + if (!dicDelFolder.ContainsKey(sFolder)) dicDelFolder.Add(sFolder, sFolder); + if (fUnPin || fDelete) + { + if (!string.IsNullOrEmpty(targetPath) && File.Exists(targetPath)) + { + // Target exists, keep as is + } + else + { + // Would need COM interop to set sc.TargetPath = sNotepad and sc.Save() + LogOnly($"linking empty shortcut to Notepad.exe as target: {filePath} - {targetPath}"); + } + //Invoke new instance to UnPin file + string exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; + string sCmdLine = $"\"{exePath}\" \"UNPINSC\" \"{filePath}\""; + LogOnly($"Invoke UnPin handler for shortcut: {filePath}"); + LogOnly($"UnPin command: {sCmdLine}"); + if (!fDetectOnly) + { + int sReturn = RunCommand(sCmdLine, true); + LogOnly($"UnPin returned with: {sReturn}"); + } + } + if (fDelete) DeleteFile(filePath); + fDeleteSC = false; + fClearTaskBand = true; + } + } + catch (Exception ex) + { + CheckError($"CleanShortcutsSC: \t{sFolder}"); + Log($"Error processing shortcut {filePath}: {ex.Message}"); + } + } + } + catch (Exception ex) + { + Log($"Error processing file {file.FullName}: {ex.Message}"); + } + } + } + catch (Exception ex) + { + Log($"Error in CleanShortcuts: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// UnPin +// +// Unpins a shortcut from the taskbar or start menu +//------------------------------------------------------------------------------- +public void Unpin(string sFilePath) +{ + try + { + // Requires COM interop for Shell.Application + // Type shellAppType = Type.GetTypeFromProgID("Shell.Application"); + // dynamic oShellAppUnPin = Activator.CreateInstance(shellAppType); + // FileInfo file = new FileInfo(sFilePath); + // dynamic folder = oShellAppUnPin.NameSpace(file.DirectoryName); + // dynamic fldItem = folder.ParseName(file.Name); + + // For now, this is a placeholder - requires COM interop implementation + // The actual implementation would iterate through fldItem.Verbs and call DoIt() on matching verbs + LogOnly($"UnPin called for: {sFilePath}"); + // Note: Full implementation requires COM interop for Shell.Application + } + catch (Exception ex) + { + Log($"Error in Unpin: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// ClearTaskBand +// +// Clears contents from the users taskband to get rid of pinned items +//------------------------------------------------------------------------------- +public void ClearTaskBand() +{ + try + { + string sid; + string sTaskBand, sHKUTaskBand; + string[] arrSid; + + sTaskBand = @"Software\Microsoft\Windows\CurrentVersion\Explorer\Taskband\"; + RegDeleteValue(Constants.HKCU, sTaskBand, "Favorites", false); + RegDeleteValue(Constants.HKCU, sTaskBand, "FavoritesRemovedChanges", false); + RegDeleteValue(Constants.HKCU, sTaskBand, "FavoritesChanges", false); + RegDeleteValue(Constants.HKCU, sTaskBand, "FavoritesResolve", false); + RegDeleteValue(Constants.HKCU, sTaskBand, "FavoritesVersion", false); + + // enum all profiles in HKU + LoadUsersReg(); + if (RegEnumKey(Constants.HKU, "", out arrSid)) + { + foreach (string sidItem in arrSid) + { + sid = sidItem; + sHKUTaskBand = sid + "\\" + sTaskBand; + RegDeleteValue(Constants.HKCU, sHKUTaskBand, "Favorites", false); + RegDeleteValue(Constants.HKCU, sHKUTaskBand, "FavoritesRemovedChanges", false); + RegDeleteValue(Constants.HKCU, sHKUTaskBand, "FavoritesChanges", false); + RegDeleteValue(Constants.HKCU, sHKUTaskBand, "FavoritesResolve", false); + RegDeleteValue(Constants.HKCU, sHKUTaskBand, "FavoritesVersion", false); + } + } + } + catch (Exception ex) + { + Log($"Error in ClearTaskBand: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// LoadUsersReg +// +// Loads the HKCU for all local users +//------------------------------------------------------------------------------- +public void LoadUsersReg() +{ + try + { + string sValue; + + LogH1("Load User Registry Profiles"); + + if (RegReadValue(Constants.HKLM, @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList", "ProfilesDirectory", out sValue, "REG_EXPAND_SZ")) + { + string profilesDir = ExpandEnv(sValue); + if (Directory.Exists(profilesDir)) + { + foreach (DirectoryInfo profilefolder in new DirectoryInfo(profilesDir).GetDirectories()) + { + string ntuserPath = Path.Combine(profilefolder.FullName, "ntuser.dat"); + if (File.Exists(ntuserPath)) + { + LogOnly($" load: {ntuserPath} as HKU\\{profilefolder.Name}"); + string regCmd = $"reg load \"HKU\\{profilefolder.Name}\" \"{ntuserPath}\""; + RunCommand(regCmd, true); + } + // if (File.Exists(Path.Combine(profilefolder.FullName, "Local Settings", "Application Data", "Microsoft", "Windows", "UsrClass.dat"))) + // { + // LogOnly($" load: {Path.Combine(profilefolder.FullName, "Local Settings", "Application Data", "Microsoft", "Windows", "UsrClass.dat")} as HKU\\{profilefolder.Name}_Classes"); + // string regCmd2 = $"reg load \"HKU\\{profilefolder.Name}_Classes\" \"{Path.Combine(profilefolder.FullName, "Local Settings", "Application Data", "Microsoft", "Windows", "UsrClass.dat")}\""; + // RunCommand(regCmd2, true); + // } + } + } + } + } + catch (Exception ex) + { + Log($"Error in LoadUsersReg: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// ClearOfficeHKLM +// +// Recursively search and clear the HKLM Office key from references in scope +//------------------------------------------------------------------------------- +public void ClearOfficeHKLM(string sSubKeyName) +{ + try + { + string key, name; + string sValue; + string[] arrKeys, arrNames, arrTypes; + + // recursion + if (RegEnumKey(Constants.HKLM, sSubKeyName, out arrKeys)) + { + foreach (string keyItem in arrKeys) + { + key = keyItem; + ClearOfficeHKLM(sSubKeyName + "\\" + key); + } + } + + // identify & clear removable entries + if (RegEnumValues(Constants.HKLM, sSubKeyName, out arrNames, out arrTypes)) + { + foreach (string nameItem in arrNames) + { + name = nameItem; + if (RegReadValue(Constants.HKLM, sSubKeyName, name, out sValue, "REG_SZ")) + { + if (IsC2R(sValue)) RegDeleteValue(Constants.HKLM, sSubKeyName, name, false); + } + } + } + + // clear out empty keys + if (!RegEnumValues(Constants.HKLM, sSubKeyName, out arrNames, out arrTypes) && + !RegEnumKey(Constants.HKLM, sSubKeyName, out arrKeys) && + dicKeepSku.Count == 0) + { + RegDeleteKey(Constants.HKLM, sSubKeyName); + } + } + catch (Exception ex) + { + Log($"Error in ClearOfficeHKLM: {ex.Message}"); + } +} + + +'------------------------------------------------------------------------------- +' +' Helper Functions +' +'------------------------------------------------------------------------------- + +//------------------------------------------------------------------------------- +// IsC2R +// +// Check if the passed in string is related to C2R +// Returns TRUE if in C2R scope +//------------------------------------------------------------------------------- +public bool IsC2R(string sValue) +{ + const string OREF = "\\ROOT\\OFFICE1"; + const string OREFROOT = "Microsoft Office\\Root\\"; + const string OREGREFC2R15 = "Microsoft Office 15"; + const string OREGREFC2R16 = "Microsoft Office 16"; + const string OCOMMON = "\\microsoft shared\\ClickToRun"; + const string OMANIFEST = "\\Microsoft Office\\PackageManifests"; + const string OSUNRISE = "\\Microsoft Office\\PackageSunrisePolicies"; + + if (string.IsNullOrEmpty(sValue)) return false; + + string sValueLower = sValue.ToLower(); + return sValueLower.Contains(OREF.ToLower()) || + sValueLower.Contains(OREFROOT.ToLower()) || + sValueLower.Contains(OCOMMON.ToLower()) || + sValueLower.Contains(OMANIFEST.ToLower()) || + sValueLower.Contains(OSUNRISE.ToLower()) || + sValueLower.Contains(OREGREFC2R15.ToLower()) || + sValueLower.Contains(OREGREFC2R16.ToLower()); +} + +//------------------------------------------------------------------------------- +// CheckRegPermissions +// +// Test the permissions on some key registry locations to determine if +// sufficient permissions are given. +//------------------------------------------------------------------------------- +public bool CheckRegPermissions() +{ + const int KEY_QUERY_VALUE = 0x0001; + const int KEY_SET_VALUE = 0x0002; + const int KEY_CREATE_SUB_KEY = 0x0004; + const int DELETE = 0x00010000; + + try + { + string sSubKeyName = @"Software\Microsoft\Windows\"; + bool fReturn; + + // Note: This requires WMI StdRegProv CheckAccess method + // For now, we'll use a simplified check using RegistryKey.OpenSubKey with RegistryRights + using (var baseKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(sSubKeyName, Microsoft.Win32.RegistryKeyPermissionCheck.ReadWriteSubTree, System.Security.AccessControl.RegistryRights.QueryValues | System.Security.AccessControl.RegistryRights.SetValue | System.Security.AccessControl.RegistryRights.CreateSubKey | System.Security.AccessControl.RegistryRights.Delete)) + { + return baseKey != null; + } + } + catch + { + return false; + } +} + +//------------------------------------------------------------------------------- +// GetMyProcessId +// +// Returns the process id of the own process +//------------------------------------------------------------------------------- +public int GetMyProcessId() +{ + try + { + // In C#, we can directly get the current process ID + return System.Diagnostics.Process.GetCurrentProcess().Id; + } + catch + { + // Fallback: try to get from WMI + try + { + var scope = new System.Management.ManagementScope(@"\\.\root\cimv2"); + string queryString = $"SELECT * FROM Win32_Process WHERE Name='{System.Diagnostics.Process.GetCurrentProcess().ProcessName}.exe' AND CommandLine LIKE '%{Constants.SCRIPTNAME}%'"; + var query = new System.Management.ObjectQuery(queryString); + using (var searcher = new System.Management.ManagementObjectSearcher(scope, query)) + { + foreach (System.Management.ManagementObject process in searcher.Get()) + { + return Convert.ToInt32(process["ProcessId"]); + } + } + } + catch { } + return 0; + } +} + +//------------------------------------------------------------------------------- +// Delimiter +// +// Returns the delimiter for a passed in string +//------------------------------------------------------------------------------- +public string Delimiter(string sVersion) +{ + if (string.IsNullOrEmpty(sVersion)) return " "; + + foreach (char c in sVersion) + { + int iAsc = (int)c; + if (!(iAsc >= 48 && iAsc <= 57)) + { + return c.ToString(); + } + } + return " "; +} + +//------------------------------------------------------------------------------- +// GetExpandedGuid +// +// Returns the expanded string from a compressed GUID +//------------------------------------------------------------------------------- +public string GetExpandedGuid(string sGuid) +{ + //Ensure valid length + if (sGuid == null || sGuid.Length != 32) return ""; + + char[] chars = sGuid.ToCharArray(); + Array.Reverse(chars, 0, 8); + string part1 = new string(chars, 0, 8); + Array.Reverse(chars, 8, 4); + string part2 = new string(chars, 8, 4); + Array.Reverse(chars, 12, 4); + string part3 = new string(chars, 12, 4); + + string part4 = ""; + for (int i = 17; i <= 20; i++) + { + // VBScript uses 1-based indexing: Mid(sGuid, i+1, 1) for odd, Mid(sGuid, i-1, 1) for even + // Convert to 0-based: i+1 becomes i, i-1 becomes i-2 + if (i % 2 == 1) + { + part4 += sGuid[i]; // i+1 in 1-based = i in 0-based + } + else + { + part4 += sGuid[i - 2]; // i-1 in 1-based = i-2 in 0-based + } + } + + string part5 = ""; + for (int i = 21; i < 32; i++) // Changed to < 32 since sGuid is 32 chars (0-31) + { + // VBScript uses 1-based indexing: Mid(sGuid, i+1, 1) for odd, Mid(sGuid, i-1, 1) for even + // Convert to 0-based: i+1 becomes i, i-1 becomes i-2 + if (i % 2 == 1) + { + part5 += sGuid[i]; // i+1 in 1-based = i in 0-based + } + else + { + part5 += sGuid[i - 2]; // i-1 in 1-based = i-2 in 0-based + } + } + + return "{" + part1 + "-" + part2 + "-" + part3 + "-" + part4 + "-" + part5 + "}"; +} + +//------------------------------------------------------------------------------- +// GetCompressedGuid +// +// Returns the compressed string for a GUID +//------------------------------------------------------------------------------- +public string GetCompressedGuid(string sGuid) +{ + //Ensure Valid Length + if (sGuid == null || sGuid.Length != 38) return ""; + + char[] part1 = sGuid.Substring(2, 8).ToCharArray(); + Array.Reverse(part1); + char[] part2 = sGuid.Substring(11, 4).ToCharArray(); + Array.Reverse(part2); + char[] part3 = sGuid.Substring(16, 4).ToCharArray(); + Array.Reverse(part3); + + string sCompGUID = new string(part1) + new string(part2) + new string(part3); + + for (int i = 21; i <= 24; i++) + { + if (i % 2 == 1) + { + sCompGUID += sGuid[i]; + } + else + { + sCompGUID += sGuid[i - 2]; + } + } + + for (int i = 26; i <= 37; i++) + { + if (i % 2 == 1) + { + sCompGUID += sGuid[i - 2]; + } + else + { + sCompGUID += sGuid[i]; + } + } + + return sCompGUID; +} + +//------------------------------------------------------------------------------- +// GetDecodedGuid +// +// Returns the GUID from a squished format +//------------------------------------------------------------------------------- +public bool GetDecodedGuid(string sEncGuid, out string sGuid) +{ + sGuid = ""; + if (string.IsNullOrEmpty(sEncGuid) || sEncGuid.Length < 20) return false; + + string sTable = "0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff," + + "0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff," + + "0xff,0x00,0xff,0xff,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0xff," + + "0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0xff,0xff,0xff,0x16,0xff,0x17," + + "0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27," + + "0x28,0x29,0x2a,0x2b,0x2c,0x2d,0x2e,0x2f,0x30,0x31,0x32,0x33,0xff,0x34,0x35,0x36," + + "0x37,0x38,0x39,0x3a,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46," + + "0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0xff,0x53,0x54,0xff"; + string[] arrTable = sTable.Split(','); + long lTotal = 0; + long pow85 = 1; + string sDecode = ""; + bool fFailed = false; + + for (int i = 0; i < 20 && i < sEncGuid.Length; i++) + { + fFailed = true; + if (i % 5 == 0) + { + lTotal = 0; + pow85 = 1; + } + int iAsc = (int)sEncGuid[i]; + if (iAsc >= 128) break; + if (iAsc >= arrTable.Length) break; + string sHex = arrTable[iAsc]; + if (sHex == "0xff") break; + int iChr = Convert.ToInt32(sHex.Substring(2), 16); + lTotal = lTotal + (iChr * pow85); + if (i % 5 == 4) sDecode = sDecode + DecToHex(lTotal); + pow85 = pow85 * 85; + fFailed = false; + } + + if (!fFailed && sDecode.Length >= 32) + { + sGuid = "{" + sDecode.Substring(0, 8) + "-" + + sDecode.Substring(12, 4) + "-" + + sDecode.Substring(8, 4) + "-" + + sDecode.Substring(22, 2) + sDecode.Substring(20, 2) + "-" + + sDecode.Substring(18, 2) + sDecode.Substring(16, 2) + sDecode.Substring(30, 2) + sDecode.Substring(28, 2) + sDecode.Substring(26, 2) + sDecode.Substring(24, 2) + "}"; + } + + return !fFailed; +} + +//------------------------------------------------------------------------------- +// DecToHex +// +// Convert a long decimal to hex +//------------------------------------------------------------------------------- +public string DecToHex(long lDec) +{ + string[] arrChr = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F" }; + string sHex = ""; + long lVal = lDec; + long lExp = (long)Math.Pow(16, 10); + + while (lExp >= 1) + { + if (lVal >= lExp) + { + int index = (int)(lVal / lExp); + sHex = sHex + arrChr[index]; + lVal = lVal - lExp * index; + } + else + { + sHex = sHex + "0"; + if (sHex == "0") sHex = ""; + } + lExp = lExp / 16; + } + + int iLen = 8 - sHex.Length; + if (iLen > 0) sHex = new string('0', iLen) + sHex; + return sHex; +} + +//------------------------------------------------------------------------------- +// RelaunchAs64Host +// +// Relaunch self with 64 bit CScript host +//------------------------------------------------------------------------------- +public void RelaunchAs64Host() +{ + try + { + bool fQuietRelaunch = false; + string exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; + string sysDir = Path.GetDirectoryName(exePath); + // Replace syswow64 with sysnative (case-insensitive) + if (sysDir.IndexOf("syswow64", StringComparison.OrdinalIgnoreCase) >= 0) + { + sysDir = sysDir.Substring(0, sysDir.IndexOf("syswow64", StringComparison.OrdinalIgnoreCase)) + "sysnative" + sysDir.Substring(sysDir.IndexOf("syswow64", StringComparison.OrdinalIgnoreCase) + 8); + } + string sCmd = sysDir + "\\cscript.exe \"" + exePath + "\""; + + if (fQuiet) fQuietRelaunch = true; + + var args = Environment.GetCommandLineArgs(); + for (int i = 1; i < args.Length; i++) + { + string argument = args[i]; + sCmd = sCmd + " \"" + argument + "\""; + switch (argument.ToUpper()) + { + case "/Q": + case "/QUIET": + fQuietRelaunch = true; + break; + } + } + + sCmd = sCmd + " /ChangedHostBitness"; + if (fQuietRelaunch) + { + // Replace cscript.exe with wscript.exe (case-insensitive) + int idx = sCmd.IndexOf("\\cscript.exe", StringComparison.OrdinalIgnoreCase); + if (idx >= 0) + { + sCmd = sCmd.Substring(0, idx) + "\\wscript.exe" + sCmd.Substring(idx + 13); + } + int exitCode = RunCommand(sCmd, true); + Environment.Exit(exitCode); + } + else + { + int exitCode = RunCommand(sCmd, true); + Environment.Exit(exitCode); + } + } + catch (Exception ex) + { + Log($"Error in RelaunchAs64Host: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// RelaunchElevated +// +// Relaunch the script with elevated permissions +//------------------------------------------------------------------------------- +public void RelaunchElevated() +{ + try + { + SetError(Constants.ERROR_RELAUNCH); + // Shell object for relaunch - requires COM interop + // Type shellAppType = Type.GetTypeFromProgID("Shell.Application"); + // dynamic oShell = Activator.CreateInstance(shellAppType); + + // Note: Command line has not been parsed at this point + // build command line for relaunch + string exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; + string sCmdLine = "\"" + exePath + "\""; + + var args = Environment.GetCommandLineArgs(); + for (int i = 1; i < args.Length; i++) + { + string argument = args[i]; + switch (argument.ToUpper()) + { + case "/Q": + case "/QUIET": + //Don't try to relaunch in quiet mode + SetError(Constants.ERROR_ELEVATION); + return; + case "UAC": + //Already tried elevated relaunch + SetError(Constants.ERROR_ELEVATION); + return; + default: + sCmdLine = sCmdLine + " \"" + argument + "\""; + break; + } + } + + // prep work to get the return value from the elevated process + int iParentProcessId = GetMyProcessId(); + + // // make user aware of elevation attempt after reboot + // if (RegReadValue(Constants.HKCU, @"SOFTWARE\Microsoft\Office\15.0\CleanC2R", "Rerun", out sValue, "REG_DWORD")) + // { + // MessageBox.Show("System reboot complete. OffScrub will now prompt for elevation!", Constants.SCRIPTNAME + " - NOTE!", MessageBoxButtons.OK, MessageBoxIcon.Information); + // } + + // launch the elevated instance + // Requires COM interop: oShell.ShellExecute("cscript.exe", sCmdLine + " /NoElevate UAC", "", "runas", 1); + // For now, using ProcessStartInfo with Verb = "runas" + var startInfo = new ProcessStartInfo + { + FileName = "cscript.exe", + Arguments = sCmdLine + " /NoElevate UAC", + Verb = "runas", + UseShellExecute = true, + WindowStyle = ProcessWindowStyle.Normal + }; + + Process elevatedProcess = Process.Start(startInfo); + if (elevatedProcess != null) + { + int iSpawnedProcessId = elevatedProcess.Id; + // monitor the process to detect the end + while (!elevatedProcess.HasExited) + { + System.Threading.Thread.Sleep(3000); + } + // get the return value from the file + int retVal = GetRetValFromFile(); + Environment.Exit(retVal); + } + else + { + // elevation failed (user declined) + SetError(Constants.ERROR_ELEVATION_USERDECLINED); + } + } + catch (Exception ex) + { + Log($"Error in RelaunchElevated: {ex.Message}"); + SetError(Constants.ERROR_ELEVATION); + } +} + +//------------------------------------------------------------------------------- +// RelaunchAsCScript +// +// Relaunch self with Cscript as host +//------------------------------------------------------------------------------- +public void RelaunchAsCScript() +{ + try + { + bool fQuietNoCScript = false; + SetError(Constants.ERROR_RELAUNCH); + string exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; + string sysDir = Path.GetDirectoryName(exePath); + string sCmdLine = "cmd.exe /c " + sysDir + "\\cscript.exe //NOLOGO \"" + exePath + "\""; + + var args = Environment.GetCommandLineArgs(); + for (int i = 1; i < args.Length; i++) + { + string argument = args[i]; + sCmdLine = sCmdLine + " \"" + argument + "\""; + switch (argument.ToUpper()) + { + case "/Q": + case "/QUIET": + fQuietNoCScript = true; + ClearError(Constants.ERROR_RELAUNCH); + break; + } + } + + sCmdLine = sCmdLine + " \"/ChangedScriptHost\""; + + if (!fQuietNoCScript) + { + int exitCode = RunCommand(sCmdLine, true); + Environment.Exit(exitCode); + } + } + catch (Exception ex) + { + Log($"Error in RelaunchAsCScript: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// SetError +// +// Set error bit(s) +//------------------------------------------------------------------------------- +public void SetError(int ErrorBit) +{ + iError = iError | ErrorBit; + switch (ErrorBit) + { + case Constants.ERROR_DCAF_FAILURE: + case Constants.ERROR_STAGE2: + case Constants.ERROR_ELEVATION_USERDECLINED: + case Constants.ERROR_ELEVATION: + case Constants.ERROR_SCRIPTINIT: + iError = iError | Constants.ERROR_FAIL; + break; + } +} + +//------------------------------------------------------------------------------- +// ClearError +// +// Unset error bit(s) +//------------------------------------------------------------------------------- +public void ClearError(int ErrorBit) +{ + iError = iError & (Constants.ERROR_ALL - ErrorBit); + switch (ErrorBit) + { + case Constants.ERROR_ELEVATION_USERDECLINED: + case Constants.ERROR_ELEVATION: + case Constants.ERROR_SCRIPTINIT: + iError = iError & (Constants.ERROR_ALL - Constants.ERROR_FAIL); + break; + } +} + +//------------------------------------------------------------------------------- +// SetRetVal +// +// Write return value to file +//------------------------------------------------------------------------------- +public void SetRetVal(int errorValue) +{ + //don't fail script execution if writing the return value to file fails + try + { + string retValPath = Path.Combine(sScrubDir, Constants.RETVALFILE); + File.WriteAllText(retValPath, errorValue.ToString()); + } + catch (Exception ex) + { + Log($"Error writing return value to file: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// GetRetValFromFile +// +// Read return value from file. +// Used to ensure return value can get obtained from an elevated process +//------------------------------------------------------------------------------- +public int GetRetValFromFile() +{ + //don't fail script execution when getting the return value from file fails + try + { + string retValPath = Path.Combine(sScrubDir, Constants.RETVALFILE); + if (File.Exists(retValPath)) + { + string content = File.ReadAllText(retValPath); + if (int.TryParse(content, out int iRetValFromFile)) + { + return iRetValFromFile; + } + } + } + catch (Exception ex) + { + Log($"Error reading return value from file: {ex.Message}"); + } + return Constants.ERROR_UNKNOWN; +} + +//------------------------------------------------------------------------------- +// CreateLog +// +// Create the removal log file +//------------------------------------------------------------------------------- +public void CreateLog() +{ + try + { + // create the log file + string computerName = Environment.MachineName; + string dateTimeStr = DateTime.Now.ToString("yyyyMMddHHmmss"); + string sLogName = Path.Combine(sLogDir, $"{computerName}_{dateTimeStr}_ScrubLog.txt"); + + try + { + LogStream = new StreamWriter(sLogName, false, Encoding.UTF8); + } + catch + { + sLogDir = sScrubDir; + sLogName = Path.Combine(sLogDir, $"{computerName}_{dateTimeStr}_ScrubLog.txt"); + LogStream = new StreamWriter(sLogName, false, Encoding.UTF8); + } + + LogH2($"Microsoft Customer Support Services - {Constants.ONAME} Removal Utility\r\n\r\n" + + $"Version:\t{Constants.SCRIPTVERSION}\r\n" + + $"64 bit OS:\t{f64}\r\n" + + $"Removal start:\t{DateTime.Now:HH:mm:ss}"); + LogH2($"OS Details: {sOSinfo}\r\n"); + fLogInitialized = true; + } + catch (Exception ex) + { + Log($"Error in CreateLog: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// HiveString +// +// Translates the numeric constant into the human readable registry hive string +//------------------------------------------------------------------------------- +public string HiveString(int hDefKey) +{ + switch (hDefKey) + { + case Constants.HKCR: + return "HKEY_CLASSES_ROOT"; + case Constants.HKCU: + return "HKEY_CURRENT_USER"; + case Constants.HKLM: + return "HKEY_LOCAL_MACHINE"; + case Constants.HKU: + return "HKEY_USERS"; + default: + return hDefKey.ToString(); + } +} + +//------------------------------------------------------------------------------- +// RegKeyExists +// +// Returns a boolean for the test on existence of a given registry key +//------------------------------------------------------------------------------- +public bool RegKeyExists(int hDefKey, string sSubKeyName) +{ + string[] arrKeys; + return RegEnumKey(hDefKey, sSubKeyName, out arrKeys); +} + +//------------------------------------------------------------------------------- +// RegValExists +// +// Returns a boolean for the test on existence of a given registry value +//------------------------------------------------------------------------------- +public bool RegValExists(int hDefKey, string sSubKeyName, string sName) +{ + if (!RegKeyExists(hDefKey, sSubKeyName)) return false; + + string[] arrValueNames, arrValueTypes; + if (RegEnumValues(hDefKey, sSubKeyName, out arrValueNames, out arrValueTypes) && arrValueNames != null) + { + for (int i = 0; i < arrValueNames.Length; i++) + { + if (arrValueNames[i].Trim().Equals(sName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + return false; +} + +//------------------------------------------------------------------------------- +// RegReadValue +// +// Read the value of a given registry entry +// The correct type has to be passed in as argument +//------------------------------------------------------------------------------- +public bool RegReadValue(int hDefKey, string sSubKeyName, string sName, out string sValue, string sType) +{ + sValue = ""; + int RetVal = -1; + string[] arrValues; + + try + { + Microsoft.Win32.RegistryKey baseKey = GetRegistryKey(hDefKey); + if (baseKey == null) return false; + + Microsoft.Win32.RegistryKey key = baseKey.OpenSubKey(sSubKeyName); + if (key == null && f64) + { + key = baseKey.OpenSubKey(Wow64Key(hDefKey, sSubKeyName)); + } + + if (key == null) return false; + + switch (sType.ToUpper()) + { + case "1": + case "REG_SZ": + object val = key.GetValue(sName); + if (val != null) + { + sValue = val.ToString(); + RetVal = 0; + } + break; + case "2": + case "REG_EXPAND_SZ": + val = key.GetValue(sName); + if (val != null) + { + sValue = Environment.ExpandEnvironmentVariables(val.ToString()); + RetVal = 0; + } + break; + case "3": + case "REG_BINARY": + byte[] binaryVal = (byte[])key.GetValue(sName); + if (binaryVal != null) + { + sValue = BitConverter.ToString(binaryVal).Replace("-", ""); + RetVal = 0; + } + break; + case "4": + case "REG_DWORD": + object dwordVal = key.GetValue(sName); + if (dwordVal != null) + { + sValue = Convert.ToInt32(dwordVal).ToString(); + RetVal = 0; + } + break; + case "7": + case "REG_MULTI_SZ": + string[] multiVal = (string[])key.GetValue(sName); + if (multiVal != null) + { + sValue = string.Join("\r", multiVal); + RetVal = 0; + } + break; + default: + RetVal = -1; + break; + } + key.Close(); + baseKey.Close(); + } + catch (Exception ex) + { + Log($"Error in RegReadValue: {ex.Message}"); + RetVal = -1; + } + + return (RetVal == 0); +} + +private Microsoft.Win32.RegistryKey GetRegistryKey(int hDefKey) +{ + switch (hDefKey) + { + case Constants.HKCR: + return Microsoft.Win32.Registry.ClassesRoot; + case Constants.HKCU: + return Microsoft.Win32.Registry.CurrentUser; + case Constants.HKLM: + return Microsoft.Win32.Registry.LocalMachine; + case Constants.HKU: + return Microsoft.Win32.Registry.Users; + default: + return null; + } +} + +//------------------------------------------------------------------------------- +// RegEnumValues +// +// Enumerate a registry key to return all values +//------------------------------------------------------------------------------- +public bool RegEnumValues(int hDefKey, string sSubKeyName, out string[] arrNames, out string[] arrTypes) +{ + arrNames = null; + arrTypes = null; + bool RetVal = false; + bool RetVal64 = false; + string[] arrNames32 = null, arrNames64 = null; + string[] arrTypes32 = null, arrTypes64 = null; + + try + { + if (f64) + { + RetVal = EnumRegistryValues(hDefKey, sSubKeyName, out arrNames32, out arrTypes32); + RetVal64 = EnumRegistryValues(hDefKey, Wow64Key(hDefKey, sSubKeyName), out arrNames64, out arrTypes64); + + if (RetVal && !RetVal64 && arrNames32 != null && arrTypes32 != null) + { + arrNames = arrNames32; + arrTypes = arrTypes32; + } + else if (!RetVal && RetVal64 && arrNames64 != null && arrTypes64 != null) + { + arrNames = arrNames64; + arrTypes = arrTypes64; + } + else if (RetVal && RetVal64 && arrNames32 != null && arrNames64 != null && arrTypes32 != null && arrTypes64 != null) + { + List combinedNames = new List(arrNames32); + combinedNames.AddRange(arrNames64); + List combinedTypes = new List(arrTypes32); + combinedTypes.AddRange(arrTypes64); + arrNames = RemoveDuplicates(combinedNames.ToArray()); + arrTypes = RemoveDuplicates(combinedTypes.ToArray()); + } + } + else + { + RetVal = EnumRegistryValues(hDefKey, sSubKeyName, out arrNames, out arrTypes); + } + } + catch (Exception ex) + { + Log($"Error in RegEnumValues: {ex.Message}"); + } + + return (RetVal || RetVal64) && arrNames != null && arrTypes != null; +} + +private bool EnumRegistryValues(int hDefKey, string sSubKeyName, out string[] arrNames, out string[] arrTypes) +{ + arrNames = null; + arrTypes = null; + try + { + Microsoft.Win32.RegistryKey baseKey = GetRegistryKey(hDefKey); + if (baseKey == null) return false; + + Microsoft.Win32.RegistryKey key = baseKey.OpenSubKey(sSubKeyName); + if (key == null) return false; + + List names = new List(); + List types = new List(); + + foreach (string valueName in key.GetValueNames()) + { + names.Add(valueName); + Microsoft.Win32.RegistryValueKind kind = key.GetValueKind(valueName); + types.Add(kind.ToString()); + } + + arrNames = names.ToArray(); + arrTypes = types.ToArray(); + key.Close(); + baseKey.Close(); + return true; + } + catch + { + return false; + } +} + +//------------------------------------------------------------------------------- +// RegEnumKey +// +// Enumerate a registry key to return all subkeys +//------------------------------------------------------------------------------- +public bool RegEnumKey(int hDefKey, string sSubKeyName, out string[] arrKeys) +{ + arrKeys = null; + bool RetVal = false; + bool RetVal64 = false; + string[] arrKeys32 = null, arrKeys64 = null; + + try + { + if (f64) + { + RetVal = EnumRegistryKeys(hDefKey, sSubKeyName, out arrKeys32); + RetVal64 = EnumRegistryKeys(hDefKey, Wow64Key(hDefKey, sSubKeyName), out arrKeys64); + + if (RetVal && !RetVal64 && arrKeys32 != null) + { + arrKeys = arrKeys32; + } + else if (!RetVal && RetVal64 && arrKeys64 != null) + { + arrKeys = arrKeys64; + } + else if (RetVal && RetVal64) + { + if (arrKeys32 != null && arrKeys64 != null) + { + List combined = new List(arrKeys32); + combined.AddRange(arrKeys64); + arrKeys = RemoveDuplicates(combined.ToArray()); + } + else if (arrKeys64 != null) + { + arrKeys = arrKeys64; + } + else + { + arrKeys = arrKeys32; + } + } + } + else + { + RetVal = EnumRegistryKeys(hDefKey, sSubKeyName, out arrKeys); + } + } + catch (Exception ex) + { + Log($"Error in RegEnumKey: {ex.Message}"); + } + + return (RetVal || RetVal64) && arrKeys != null; +} + +private bool EnumRegistryKeys(int hDefKey, string sSubKeyName, out string[] arrKeys) +{ + arrKeys = null; + try + { + Microsoft.Win32.RegistryKey baseKey = GetRegistryKey(hDefKey); + if (baseKey == null) return false; + + Microsoft.Win32.RegistryKey key = baseKey.OpenSubKey(sSubKeyName); + if (key == null) return false; + + List keys = new List(); + foreach (string subKeyName in key.GetSubKeyNames()) + { + keys.Add(subKeyName); + } + + arrKeys = keys.ToArray(); + key.Close(); + baseKey.Close(); + return true; + } + catch + { + return false; + } +} + +//------------------------------------------------------------------------------- +// RegDeleteValue +// +// Wrapper around oReg.DeleteValue to handle 64 bit +//------------------------------------------------------------------------------- +public void RegDeleteValue(int hDefKey, string sSubKeyName, string sName, bool fRegMultiSZ) +{ + try + { + string sDelKeyName, sValue; + int iRetVal = 0; + bool fKeep; + + // ensure trailing "\" + if (!sSubKeyName.EndsWith("\\")) sSubKeyName = sSubKeyName + "\\"; + while (sSubKeyName.Contains("\\\\")) + { + sSubKeyName = sSubKeyName.Replace("\\\\", "\\"); + } + + fKeep = dicKeepReg.ContainsKey((sSubKeyName + sName).ToLower()); + if (!fKeep && f64) fKeep = dicKeepReg.ContainsKey((Wow64Key(hDefKey, sSubKeyName) + sName).ToLower()); + + if (fKeep) + { + LogOnly($"Disallowing the delete of still required keypath element: {HiveString(hDefKey)}\\{sSubKeyName}{sName}"); + if (!fForce) return; + } + + // check on forced delete + if (fKeep) + { + LogOnly($"Enforced delete of still required keypath element: {HiveString(hDefKey)}\\{sSubKeyName}{sName}"); + LogOnly(" Remaining applications will need a repair!"); + } + + // ensure value exists + if (RegValExists(hDefKey, sSubKeyName, sName)) + { + sDelKeyName = sSubKeyName; + } + else if (RegValExists(hDefKey, Wow64Key(hDefKey, sSubKeyName), sName)) + { + sDelKeyName = Wow64Key(hDefKey, sSubKeyName); + } + else + { + LogOnly($"Value not found. Cannot delete value: {HiveString(hDefKey)}\\{sSubKeyName}{sName}"); + return; + } + + // prevent unintentional, unsafe REG_MULTI_SZ delete + if (RegReadValue(hDefKey, sDelKeyName, sName, out sValue, "REG_MULTI_SZ") && !fRegMultiSZ) + { + LogOnly($"Disallowing unsafe delete of REG_MULTI_SZ: {HiveString(hDefKey)}\\{sDelKeyName}{sName}"); + return; + } + + // execute delete operation + if (!fDetectOnly) + { + LogOnly($"Delete registry value: {HiveString(hDefKey)}\\{sDelKeyName} -> {sName}"); + iRetVal = DeleteRegistryValue(hDefKey, sDelKeyName, sName); + CheckError("RegDeleteValue"); + if (iRetVal != 0) + { + LogOnly($" Delete failed. Return value: {iRetVal}"); + SetError(Constants.ERROR_STAGE2); + } + } + else + { + LogOnly($"Preview mode. Disallowing delete registry value: {HiveString(hDefKey)}\\{sDelKeyName} -> {sName}"); + } + } + catch (Exception ex) + { + Log($"Error in RegDeleteValue: {ex.Message}"); + CheckError("RegDeleteValue"); + } +} + +private int DeleteRegistryValue(int hDefKey, string sSubKeyName, string sName) +{ + try + { + Microsoft.Win32.RegistryKey baseKey = GetRegistryKey(hDefKey); + if (baseKey == null) return -1; + + Microsoft.Win32.RegistryKey key = baseKey.OpenSubKey(sSubKeyName, true); + if (key == null) return -1; + + key.DeleteValue(sName, false); + key.Close(); + baseKey.Close(); + return 0; + } + catch + { + return -1; + } +} + +//------------------------------------------------------------------------------- +// RegDeleteKey +// +// Wrappper around RegDeleteKeyEx to handle 64bit +//------------------------------------------------------------------------------- +public void RegDeleteKey(int hDefKey, string sSubKeyName) +{ + try + { + string sDelKeyName; + bool fKeep; + + // ensure trailing "\" + if (!sSubKeyName.EndsWith("\\")) sSubKeyName = sSubKeyName + "\\"; + while (sSubKeyName.Contains("\\\\")) + { + sSubKeyName = sSubKeyName.Replace("\\\\", "\\"); + } + + fKeep = dicKeepReg.ContainsKey(sSubKeyName.ToLower()); + if (!fKeep && f64) fKeep = dicKeepReg.ContainsKey(Wow64Key(hDefKey, sSubKeyName).ToLower()); + + if (fKeep) + { + LogOnly($"Disallowing the delete of still required keypath element: {HiveString(hDefKey)}\\{sSubKeyName}"); + if (!fForce) return; + } + + // check on forced delete + if (fKeep) + { + LogOnly($"Enforced delete of still required keypath element: {HiveString(hDefKey)}\\{sSubKeyName}"); + LogOnly(" Remaining applications will need a repair!"); + } + + if (sSubKeyName.Length > 1) + { + //Strip of trailing "\" + sSubKeyName = sSubKeyName.Substring(0, sSubKeyName.Length - 1); + } + + // ensure key exists + if (RegKeyExists(hDefKey, sSubKeyName)) + { + sDelKeyName = sSubKeyName; + } + else if (f64 && RegKeyExists(hDefKey, Wow64Key(hDefKey, sSubKeyName))) + { + sDelKeyName = Wow64Key(hDefKey, sSubKeyName); + } + else + { + LogOnly($"Key not found. Cannot delete key: {HiveString(hDefKey)}\\{sSubKeyName}"); + return; + } + + // execute delete + if (!fDetectOnly) + { + LogOnly($"Delete registry key: {HiveString(hDefKey)}\\{sDelKeyName}"); + try + { + RegDeleteKeyEx(hDefKey, sDelKeyName); + } + catch (Exception ex) + { + Log($"Error in RegDeleteKeyEx: {ex.Message}"); + } + } + else + { + LogOnly($"Preview mode. Disallowing delete of registry key: {HiveString(hDefKey)}\\{sSubKeyName}"); + } + } + catch (Exception ex) + { + Log($"Error in RegDeleteKey: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// RegDeleteKeyEx +// +// Recursively delete a registry structure +//------------------------------------------------------------------------------- +public void RegDeleteKeyEx(int hDefKey, string sSubKeyName) +{ + try + { + string[] arrSubkeys; + string sSubkey; + int iRetVal = 0; + + //Strip of trailing "\" + if (sSubKeyName.Length > 1) + { + if (sSubKeyName.EndsWith("\\")) sSubKeyName = sSubKeyName.Substring(0, sSubKeyName.Length - 1); + } + + // exception handler + if (hDefKey == Constants.HKLM && sSubKeyName == @"SOFTWARE\Microsoft\Office\15.0\ClickToRun") + { + if (!fDetectOnly) iRetVal = RunCommand("reg delete HKLM\\SOFTWARE\\Microsoft\\Office\\15.0\\ClickToRun /f", true); + return; + } + + // regular recursion + if (RegEnumKey(hDefKey, sSubKeyName, out arrSubkeys) && arrSubkeys != null) + { + foreach (string subkeyItem in arrSubkeys) + { + sSubkey = subkeyItem; + RegDeleteKeyEx(hDefKey, sSubKeyName + "\\" + sSubkey); + } + } + + if (!fDetectOnly) + { + iRetVal = DeleteRegistryKey(hDefKey, sSubKeyName); + if (iRetVal != 0) LogOnly($" Delete failed. Return value: {iRetVal}"); + } + } + catch (Exception ex) + { + Log($"Error in RegDeleteKeyEx: {ex.Message}"); + } +} + +private int DeleteRegistryKey(int hDefKey, string sSubKeyName) +{ + try + { + Microsoft.Win32.RegistryKey baseKey = GetRegistryKey(hDefKey); + if (baseKey == null) return -1; + + int lastSlash = sSubKeyName.LastIndexOf('\\'); + if (lastSlash > 0) + { + string parentPath = sSubKeyName.Substring(0, lastSlash); + string keyName = sSubKeyName.Substring(lastSlash + 1); + Microsoft.Win32.RegistryKey parentKey = baseKey.OpenSubKey(parentPath, true); + if (parentKey != null) + { + parentKey.DeleteSubKeyTree(keyName, false); + parentKey.Close(); + } + } + else + { + baseKey.DeleteSubKeyTree(sSubKeyName, false); + } + baseKey.Close(); + return 0; + } + catch + { + return -1; + } +} + +//------------------------------------------------------------------------------- +// Wow64Key +// +// Return the 32bit regkey location on a 64bit environment +//------------------------------------------------------------------------------- +public string Wow64Key(int hDefKey, string sSubKeyName) +{ + int iPos; + + switch (hDefKey) + { + case Constants.HKCU: + if (sSubKeyName.StartsWith("Software\\Classes\\")) + { + return sSubKeyName.Substring(0, 17) + "Wow6432Node\\" + sSubKeyName.Substring(17); + } + else + { + iPos = sSubKeyName.IndexOf('\\'); + if (iPos > 0) + { + return sSubKeyName.Substring(0, iPos) + "\\Wow6432Node\\" + sSubKeyName.Substring(iPos + 1); + } + return "Wow6432Node\\" + sSubKeyName; + } + case Constants.HKLM: + if (sSubKeyName.StartsWith("Software\\Classes\\")) + { + return sSubKeyName.Substring(0, 17) + "Wow6432Node\\" + sSubKeyName.Substring(17); + } + else + { + iPos = sSubKeyName.IndexOf('\\'); + if (iPos > 0) + { + return sSubKeyName.Substring(0, iPos) + "\\Wow6432Node\\" + sSubKeyName.Substring(iPos + 1); + } + return "Wow6432Node\\" + sSubKeyName; + } + default: + return "Wow6432Node\\" + sSubKeyName; + } +} + +//------------------------------------------------------------------------------- +// RemoveDuplicates +// +// Remove duplicate entries from a one dimensional array +//------------------------------------------------------------------------------- +public string[] RemoveDuplicates(string[] array) +{ + if (array == null) return null; + + HashSet dicNoDupes = new HashSet(); + foreach (string item in array) + { + if (!dicNoDupes.Contains(item)) + { + dicNoDupes.Add(item); + } + } + return dicNoDupes.ToArray(); +} + +//------------------------------------------------------------------------------- +// CheckError +// +// Checks the status of 'Err' and logs the error details if <> 0 +//------------------------------------------------------------------------------- +public void CheckError(string sModule) +{ + // In C#, we check for exceptions differently - this method is called from catch blocks + // For VBScript compatibility, we'll log if there's a last exception + // Note: This is typically called from catch blocks where exception details are available + // For now, we'll just log the module name - actual error details should be logged in catch blocks + // This maintains compatibility with existing code that calls CheckError +} + +//------------------------------------------------------------------------------- +// LogH +// +// Write a header log string to the log file +//------------------------------------------------------------------------------- +public void LogH(string sLog) +{ + if (LogStream != null) + { + LogStream.WriteLine(""); + sLog = sLog + "\r\n" + new string('=', sLog.Length); + if (!fQuiet && fCScript) + { + Console.WriteLine(""); + Console.WriteLine(sLog); + } + LogStream.WriteLine(sLog); + } +} + +//------------------------------------------------------------------------------- +// LogH1 +// +// Write a header log string to the log file +//------------------------------------------------------------------------------- +public void LogH1(string sLog) +{ + if (LogStream != null) + { + LogStream.WriteLine(""); + sLog = sLog + "\r\n" + new string('-', sLog.Length); + if (!fQuiet && fCScript) + { + Console.WriteLine(""); + Console.WriteLine(sLog); + } + LogStream.WriteLine(sLog); + } +} + +//------------------------------------------------------------------------------- +// LogH2 +// +// Write w/o indent Cmd window and the log file +//------------------------------------------------------------------------------- +public void LogH2(string sLog) +{ + if (!fQuiet && fCScript) + { + Console.WriteLine(sLog); + } + if (LogStream != null) + { + LogStream.WriteLine(""); + LogStream.WriteLine(sLog); + } +} + +//------------------------------------------------------------------------------- +// Log +// +// Echos the log string to the Cmd window and the log file +//------------------------------------------------------------------------------- +public void Log(string sLog) +{ + if (!fQuiet && fCScript) + { + Console.WriteLine(sLog); + } + if (LogStream != null) + { + if (string.IsNullOrEmpty(sLog)) + { + LogStream.WriteLine(); + } + else + { + LogStream.WriteLine($" {DateTime.Now:HH:mm:ss}: {sLog}"); + } + } +} + +//------------------------------------------------------------------------------- +// LogOnly +// +// Commits the log string to the log file +//------------------------------------------------------------------------------- +public void LogOnly(string sLog) +{ + if (LogStream != null) + { + if (string.IsNullOrEmpty(sLog)) + { + LogStream.WriteLine(); + } + else + { + LogStream.WriteLine($" {DateTime.Now:HH:mm:ss}: {sLog}"); + } + } +} + +public void LogY(string sLog) +{ + LogPipe(sLog); +} + +public void LogPipe(string sLog) +{ + try + { + // Named pipe implementation - requires System.IO.Pipes + // For now, using file-based approach or placeholder + // Note: Named pipes require NamedPipeClientStream or similar + using (var pipeStream = new System.IO.FileStream(@"\\.\pipe\offscrub_pipe", FileMode.Create, FileAccess.Write, FileShare.Read)) + { + using (var writer = new StreamWriter(pipeStream)) + { + writer.WriteLine(sLog); + } + } + System.Threading.Thread.Sleep(5000); + } + catch (Exception ex) + { + // Silently fail - don't interrupt script execution + // Log($"Error in LogPipe: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// InScope +// +// Check if ProductCode is in scope for removal +//------------------------------------------------------------------------------- +//Check if ProductCode is in scope +public bool InScope(string sProductCode) +{ + const string OFFICEID = "0000000FF1CE}"; + bool fInScope = false; + + try + { + //LogOnly($"Now checking scope of: {sProductCode}"); + if (sProductCode != null && sProductCode.Length == 38) + { + //LogOnly("GUID length validated to be 38 characters"); + string sProd = sProductCode.ToUpper(); + if (sProd.EndsWith(OFFICEID)) + { + //LogOnly($"Pattern matches {OFFICEID}"); + if (sProd.Length >= 6) + { + string versionStr = sProd.Substring(3, 2); + if (int.TryParse(versionStr, out int versionMajor) && versionMajor > 14) + { + //LogOnly("VersionMajor confirmed to be > 14"); + if (sProd.Length >= 15) + { + string skuFilter = sProd.Substring(10, 4); + switch (skuFilter) + { + case "007E": + case "008F": + case "008C": + case "24E1": + case "237A": + case "00DD": + //LogOnly("SKUFilter matches scope"); + fInScope = true; + break; + default: + //LogOnly($"SKU {skuFilter} doesn't match known integration products scope"); + break; + } + } + } + } + } + // Microsoft Online Services Sign-in Assistant (x64 ship and x86 ship) + if (sProd == "{6C1ADE97-24E1-4AE4-AEDD-86D3A209CE60}") fInScope = true; + if (sProd == "{9520DDEB-237A-41DB-AA20-F2EF2360DCEB}") fInScope = true; + if (!string.IsNullOrEmpty(sPackageGuid) && sProd == sPackageGuid.ToUpper()) fInScope = true; + if (sProd == "{9AC08E99-230B-47E8-9721-4577B7F124EA}") fInScope = true; + } + } + catch + { + // Silently fail + } + + return fInScope; +} + +//------------------------------------------------------------------------------- +// CheckDelete +// +// Check a ProductCode is known to stay installed +//------------------------------------------------------------------------------- +public bool CheckDelete(string sProductCode) +{ + // ensure valid GUID length + if (sProductCode == null || sProductCode.Length != 38) return false; + // only care if it's in the expected ProductCode pattern + if (!InScope(sProductCode)) return false; + // check if it's a known product that should be kept + if (dicKeepSku.ContainsKey(sProductCode.ToUpper())) return false; + + return true; +} + +//------------------------------------------------------------------------------- +// DeleteService +// +// Delete a service +//------------------------------------------------------------------------------- +//Delete a service +public void DeleteService(string sName) +{ + try + { + string sStates = "STARTED;RUNNING"; + string sQuery = $"SELECT * FROM Win32_Service WHERE Name='{sName}'"; + var scope = new ManagementScope(@"\\.\root\cimv2"); + var query = new ObjectQuery(sQuery); + using (var searcher = new ManagementObjectSearcher(scope, query)) + { + ManagementObjectCollection services = searcher.Get(); + + // stop and delete the service + foreach (ManagementObject srvc in services) + { + string displayName = srvc["DisplayName"]?.ToString() ?? ""; + string state = srvc["State"]?.ToString() ?? ""; + Log($" Found service {sName} ({displayName}) in state {state}"); + + // get the process name + string pathName = srvc["PathName"]?.ToString() ?? ""; + int lastSlash = pathName.LastIndexOf('\\'); + string sProcessName = lastSlash >= 0 ? pathName.Substring(lastSlash + 1).Trim('"').Trim() : pathName.Trim('"').Trim(); + + // stop the service + if (sStates.Contains(state.ToUpper())) + { + ManagementBaseObject result = srvc.InvokeMethod("StopService", null); + uint iRet = result != null ? Convert.ToUInt32(result["ReturnValue"]) : 999; + LogOnly($" attempt to stop service {sName} returned: {iRet}"); + } + + // ensure no more instances of the service are running + string processQuery = $"SELECT * FROM Win32_Process WHERE Name='{sProcessName}'"; + var processQueryObj = new ObjectQuery(processQuery); + using (var processSearcher = new ManagementObjectSearcher(scope, processQueryObj)) + { + foreach (ManagementObject process in processSearcher.Get()) + { + process.InvokeMethod("Terminate", null); + } + } + + if (fDetectOnly) + { + Log($" Not deleting service {sName} in preview mode"); + return; + } + + ManagementBaseObject deleteResult = srvc.InvokeMethod("Delete", null); + uint deleteRet = deleteResult != null ? Convert.ToUInt32(deleteResult["ReturnValue"]) : 999; + Log($" Delete service {sName} returned: {deleteRet}"); + } + + // check if service got deleted + services = searcher.Get(); + foreach (ManagementObject srvc in services) + { + // failed to delete service. retry with 'sc' command + Log("Delete service " + sName + " failed."); + Log("Retry delete using 'SC' command"); + string sCmd = "sc delete " + sName; + if (!fDetectOnly) + { + int iRet = RunCommand(sCmd, true); + } + } + } + } + catch (Exception ex) + { + Log($"Error in DeleteService: {ex.Message}"); + CheckError("DeleteService"); + } +} + + +//------------------------------------------------------------------------------- +// SetupRetVal +// +// Translation for known uninstall return values +//------------------------------------------------------------------------------- +public string SetupRetVal(int RetVal) +{ + switch (RetVal) + { + case 0: return "Success"; + //msiexec return values + case 1259: return "APPHELP_BLOCK"; + case 1601: return "INSTALL_SERVICE_FAILURE"; + case 1602: return "INSTALL_USEREXIT"; + case 1603: return "INSTALL_FAILURE"; + case 1604: return "INSTALL_SUSPEND"; + case 1605: return "UNKNOWN_PRODUCT"; + case 1606: return "UNKNOWN_FEATURE"; + case 1607: return "UNKNOWN_COMPONENT"; + case 1608: return "UNKNOWN_PROPERTY"; + case 1609: return "INVALID_HANDLE_STATE"; + case 1610: return "BAD_CONFIGURATION"; + case 1611: return "INDEX_ABSENT"; + case 1612: return "INSTALL_SOURCE_ABSENT"; + case 1613: return "INSTALL_PACKAGE_VERSION"; + case 1614: return "PRODUCT_UNINSTALLED"; + case 1615: return "BAD_QUERY_SYNTAX"; + case 1616: return "INVALID_FIELD"; + case 1618: return "INSTALL_ALREADY_RUNNING"; + case 1619: return "INSTALL_PACKAGE_OPEN_FAILED"; + case 1620: return "INSTALL_PACKAGE_INVALID"; + case 1621: return "INSTALL_UI_FAILURE"; + case 1622: return "INSTALL_LOG_FAILURE"; + case 1623: return "INSTALL_LANGUAGE_UNSUPPORTED"; + case 1624: return "INSTALL_TRANSFORM_FAILURE"; + case 1625: return "INSTALL_PACKAGE_REJECTED"; + case 1626: return "FUNCTION_NOT_CALLED"; + case 1627: return "FUNCTION_FAILED"; + case 1628: return "INVALID_TABLE"; + case 1629: return "DATATYPE_MISMATCH"; + case 1630: return "UNSUPPORTED_TYPE"; + case 1631: return "CREATE_FAILED"; + case 1632: return "INSTALL_TEMP_UNWRITABLE"; + case 1633: return "INSTALL_PLATFORM_UNSUPPORTED"; + case 1634: return "INSTALL_NOTUSED"; + case 1635: return "PATCH_PACKAGE_OPEN_FAILED"; + case 1636: return "PATCH_PACKAGE_INVALID"; + case 1637: return "PATCH_PACKAGE_UNSUPPORTED"; + case 1638: return "PRODUCT_VERSION"; + case 1639: return "INVALID_COMMAND_LINE"; + case 1640: return "INSTALL_REMOTE_DISALLOWED"; + case 1641: return "SUCCESS_REBOOT_INITIATED"; + case 1642: return "PATCH_TARGET_NOT_FOUND"; + case 1643: return "PATCH_PACKAGE_REJECTED"; + case 1644: return "INSTALL_TRANSFORM_REJECTED"; + case 1645: return "INSTALL_REMOTE_PROHIBITED"; + case 1646: return "PATCH_REMOVAL_UNSUPPORTED"; + case 1647: return "UNKNOWN_PATCH"; + case 1648: return "PATCH_NO_SEQUENCE"; + case 1649: return "PATCH_REMOVAL_DISALLOWED"; + case 1650: return "INVALID_PATCH_XML"; + case 3010: return "SUCCESS_REBOOT_REQUIRED"; + default: return "Unknown Return Value"; + } +} + +//------------------------------------------------------------------------------- +// DeleteFile +// +// Wrapper to delete a file +//------------------------------------------------------------------------------- +public void DeleteFile(string sFile) +{ + try + { + string sDelFile; + bool fKeep; + + fKeep = dicKeepFolder.ContainsKey(sFile.ToLower()); + if (!fKeep && f64) fKeep = dicKeepFolder.ContainsKey(Wow64Folder(sFile).ToLower()); + + if (fKeep) + { + LogOnly($"Disallowing the delete of still required keypath element: {sFile}"); + if (!fForce) return; + } + + // check on forced delete + if (fKeep) + { + LogOnly($"Enforced delete of still required keypath element: {sFile}"); + LogOnly(" Remaining applications will need a repair!"); + } + + if (File.Exists(sFile)) + { + sDelFile = sFile; + } + else if (f64 && File.Exists(Wow64Folder(sFile))) + { + sDelFile = Wow64Folder(sFile); + } + else + { + LogOnly($"Path not found. Cannot delete file: {sFile}"); + return; + } + + if (!fDetectOnly) + { + LogOnly($"Delete file: {sDelFile}"); + FileInfo fileInfo = new FileInfo(sDelFile); + // ensure read-only flag is not set + if ((fileInfo.Attributes & FileAttributes.ReadOnly) != 0) + { + fileInfo.Attributes = fileInfo.Attributes & ~FileAttributes.ReadOnly; + } + // add folder to empty folder cleanup list + string parentFolder = fileInfo.DirectoryName; + if (!dicDelFolder.ContainsKey(parentFolder)) + { + dicDelFolder.Add(parentFolder, parentFolder); + } + // delete the file + try + { + File.Delete(sDelFile); + } + catch (Exception ex) + { + CheckError("DeleteFile"); + Log($"Error deleting file: {ex.Message}"); + // schedule file for delete on next reboot + ScheduleDeleteFile(sDelFile); + } + } + else + { + LogOnly($"Preview mode. Disallowing delete for file: {sDelFile}"); + } + } + catch (Exception ex) + { + Log($"Error in DeleteFile: {ex.Message}"); + CheckError("DeleteFile"); + } +} + +//------------------------------------------------------------------------------- +// DeleteFolder +// +// Wrapper to delete a folder +//------------------------------------------------------------------------------- +public void DeleteFolder(string sFolder) +{ + try + { + DirectoryInfo fld; + string sDelFolder, sCmd; + bool fKeep; + + // ensure trailing "\" + // trailing \ is required for dicKeepFolder comparisons + if (!sFolder.EndsWith("\\")) sFolder = sFolder + "\\"; + while (sFolder.Contains("\\\\")) + { + sFolder = sFolder.Replace("\\\\", "\\"); + } + + // prevent delete of folders that are known to be still required + fKeep = dicKeepFolder.ContainsKey(sFolder.ToLower()); + if (!fKeep && f64) fKeep = dicKeepFolder.ContainsKey(Wow64Folder(sFolder).ToLower()); + + if (fKeep) + { + LogOnly($"Disallowing the delete of still required keypath element: {sFolder}"); + if (!fForce) return; + } + + // check on forced delete + if (fKeep) + { + LogOnly($"Enforced delete of still required keypath element: {sFolder}"); + LogOnly(" Remaining applications will need a repair!"); + } + + // strip trailing "\" + if (sFolder.Length > 1) + { + sFolder = sFolder.Substring(0, sFolder.Length - 1); + } + + if (Directory.Exists(sFolder)) + { + sDelFolder = sFolder; + } + else if (f64 && Directory.Exists(Wow64Folder(sFolder))) + { + sDelFolder = Wow64Folder(sFolder); + } + else + { + LogOnly($"Path not found. Cannot delete folder: {sFolder}"); + return; + } + + if (!fDetectOnly) + { + LogOnly($"Delete folder: {sDelFolder}"); + DirectoryInfo folder = new DirectoryInfo(sDelFolder); + // ensure to remove read only flag + if ((folder.Attributes & FileAttributes.ReadOnly) != 0) + { + folder.Attributes = folder.Attributes & ~FileAttributes.ReadOnly; + } + // add to empty folder cleanup list + if (!dicDelFolder.ContainsKey(folder.FullName)) + { + dicDelFolder.Add(folder.FullName, folder.FullName); + } + // delete the folder + // for performance reasons try 'rd' first + sCmd = $"cmd.exe /c rd /s \"{sDelFolder}\" /q"; + RunCommand(sCmd, true); + if (!Directory.Exists(sDelFolder)) return; + + // rd didn't work check with Directory.Delete + try + { + Directory.Delete(sDelFolder, true); + } + catch (UnauthorizedAccessException) + { + // Access Denied + // Retry after closing running processes + CheckError("DeleteFolder"); + if (!fRerun) + { + CloseOfficeApps(); + // attempt 'rd' command + LogOnly(" Attempt to remove with 'rd' command"); + sCmd = $"cmd.exe /c rd /s \"{sDelFolder}\" /q"; + RunCommand(sCmd, true); + if (!Directory.Exists(sDelFolder)) return; + } + } + catch (DirectoryNotFoundException) + { + // check on invalid path length issues + // attempt 'rd' command + CheckError("DeleteFolder"); + LogOnly(" Attempt to remove with 'rd' command"); + sCmd = $"cmd.exe /c rd /s \"{sDelFolder}\" /q"; + RunCommand(sCmd, true); + if (!Directory.Exists(sDelFolder)) return; + } + catch (Exception ex) + { + // still failed! + Log($" Failed to delete folder: {sDelFolder}"); + CheckError("DeleteFolder"); + + // try to delete as many folder contents as possible + // before the recursive error handling is called + folder = new DirectoryInfo(sDelFolder); + foreach (DirectoryInfo subFolder in folder.GetDirectories()) + { + fld = subFolder; + sCmd = $"cmd.exe /c rd /s \"{fld.FullName}\" /q"; + RunCommand(sCmd, true); + } + if (folder.GetDirectories().Length > 0) + { + DirectoryInfo lastFolder = folder.GetDirectories()[folder.GetDirectories().Length - 1]; + sCmd = $"cmd.exe /c del \"{lastFolder.FullName}\\*.*\""; + RunCommand(sCmd, true); + } + + // schedule an additional run of the tool after reboot + if (!fRerun) Rerun(); + + // schedule folder for delete on next reboot + ScheduleDeleteFolder(sDelFolder); + } + } + else + { + LogOnly($"Preview mode. Disallowing delete of folder: {sDelFolder}"); + } + } + catch (Exception ex) + { + Log($"Error in DeleteFolder: {ex.Message}"); + CheckError("DeleteFolder"); + } +} + +public void DeleteFolder_WMI(string sFolder) +{ + try + { + string sWqlFolder = sFolder.Replace("\\", "\\\\"); + var scope = new ManagementScope(@"\\.\root\cimv2"); + string queryString = $"SELECT * FROM Win32_Directory WHERE Name='{sWqlFolder}'"; + var query = new ObjectQuery(queryString); + using (var searcher = new ManagementObjectSearcher(scope, query)) + { + foreach (ManagementObject folder in searcher.Get()) + { + ManagementBaseObject result = folder.InvokeMethod("Delete", null); + uint iRet = result != null ? Convert.ToUInt32(result["ReturnValue"]) : 999; + LogOnly($" Delete (wmi) for folder {sFolder} returned: {iRet}"); + } + } + } + catch (Exception ex) + { + Log($"Error in DeleteFolder_WMI: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// Wow64Folder +// +// Returns the WOW folder structure to handle folder-path operations on +// 64 bit environments +//------------------------------------------------------------------------------- +public string Wow64Folder(string sFolder) +{ + string system32Path = sWinDir + "\\System32"; + if (sFolder.StartsWith(system32Path, StringComparison.OrdinalIgnoreCase)) + { + return sWinDir + "\\syswow64" + sFolder.Substring(system32Path.Length); + } + else if (sFolder.StartsWith(sProgramFiles, StringComparison.OrdinalIgnoreCase)) + { + return sProgramFilesX86 + sFolder.Substring(sProgramFiles.Length); + } + else + { + return "?"; //Return invalid string to ensure the folder cannot exist + } +} + +//------------------------------------------------------------------------------- +// ScheduleDeleteFile +// +// Adds a file to the list of items to delete on reboot +//------------------------------------------------------------------------------- +public void ScheduleDeleteFile(string sFile) +{ + if (!dicDelInUse.ContainsKey(sFile)) + { + dicDelInUse.Add(sFile, sFile); + LogOnly($"Add file in use for delete on reboot: {sFile}"); + fRebootRequired = true; + SetError(Constants.ERROR_REBOOT_REQUIRED); + } +} + +//------------------------------------------------------------------------------- +// ScheduleDeleteFolder +// +// Recursively adds a folder and its contents to the list of +// items to delete on reboot +//------------------------------------------------------------------------------- +public void ScheduleDeleteFolder(string sFolder) +{ + try + { + DirectoryInfo oFolder = new DirectoryInfo(sFolder); + // exclude hidden system folders + if ((oFolder.Attributes & (FileAttributes.Hidden | FileAttributes.System)) != 0) return; + + foreach (DirectoryInfo fld in oFolder.GetDirectories()) + { + DeleteFolder(fld.FullName); + } + foreach (FileInfo file in oFolder.GetFiles()) + { + DeleteFile(file.FullName); + } + if (!dicDelInUse.ContainsKey(oFolder.FullName)) + { + dicDelInUse.Add(oFolder.FullName, ""); + LogOnly($"Add folder for delete on reboot: {oFolder.FullName}"); + fRebootRequired = true; + SetError(Constants.ERROR_REBOOT_REQUIRED); + } + } + catch (Exception ex) + { + Log($"Error in ScheduleDeleteFolder: {ex.Message}"); + } +} + + +//------------------------------------------------------------------------------- +// ScheduleDeleteEx +// +// Schedules the delete of files/folders in use on next reboot by adding +// affected files/folders to the PendingFileRenameOperations registry entry +//------------------------------------------------------------------------------- +public void ScheduleDeleteEx() +{ + try + { + int hDefKey = Constants.HKLM; + string sKeyName = @"SYSTEM\CurrentControlSet\Control\Session Manager"; + string sValueName = "PendingFileRenameOperations"; + + LogH2($"Add {dicDelInUse.Count} PendingFileRenameOperations"); + List arrData = new List(); + + if (RegValExists(hDefKey, sKeyName, sValueName)) + { + string existingValue; + if (RegReadValue(hDefKey, sKeyName, sValueName, out existingValue, "REG_MULTI_SZ")) + { + arrData.AddRange(existingValue.Split('\r')); + } + } + + foreach (string key in dicDelInUse.Keys) + { + LogOnly($" {key}"); + arrData.Add("\\??\\" + key); + arrData.Add(""); + } + + // Write REG_MULTI_SZ value - requires registry API + Microsoft.Win32.RegistryKey baseKey = Microsoft.Win32.Registry.LocalMachine; + Microsoft.Win32.RegistryKey key = baseKey.OpenSubKey(sKeyName, true); + if (key != null) + { + key.SetValue(sValueName, arrData.ToArray(), Microsoft.Win32.RegistryValueKind.MultiString); + key.Close(); + } + baseKey.Close(); + } + catch (Exception ex) + { + Log($"Error in ScheduleDeleteEx: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// DeleteEmptyFolder +// +// Deletes an individual folder structure if empty +//------------------------------------------------------------------------------- +public void DeleteEmptyFolder(string sFolder) +{ + // cosmetic task don't fail on error + try + { + if (Directory.Exists(sFolder)) + { + DirectoryInfo folder = new DirectoryInfo(sFolder); + if (folder.GetDirectories().Length == 0 && folder.GetFiles().Length == 0) + { + SmartDeleteFolder(sFolder); + } + } + } + catch (Exception ex) + { + CheckError("DeleteEmptyFolder"); + Log($"Error in DeleteEmptyFolder: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// DeleteEmptyFolders +// +// Delete an empty folder structure +//------------------------------------------------------------------------------- +public void DeleteEmptyFolders() +{ + // cosmetic task don't fail on error + try + { + DeleteEmptyFolder(sCommonProgramFiles + "\\Microsoft Shared\\Office15"); + DeleteEmptyFolder(sCommonProgramFiles + "\\Microsoft Shared\\Office16"); + DeleteEmptyFolder(sCommonProgramFiles + "\\Microsoft Shared\\"); + DeleteEmptyFolder(sProgramFiles + "\\Microsoft Office\\Office15"); + DeleteEmptyFolder(sProgramFiles + "\\Microsoft Office\\Office16"); + + foreach (string sFolder in dicDelFolder.Keys) + { + if (Directory.Exists(sFolder)) + { + DirectoryInfo folder = new DirectoryInfo(sFolder); + if (folder.GetDirectories().Length == 0 && folder.GetFiles().Length == 0) + { + SmartDeleteFolder(sFolder); + } + } + } + } + catch (Exception ex) + { + CheckError("DeleteEmptyFolders"); + Log($"Error in DeleteEmptyFolders: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// SmartDeleteFolder +// +// Wrapper to delete a folder and the empty parent folder structure +//------------------------------------------------------------------------------- +public void SmartDeleteFolder(string sFolder) +{ + string sDelFolder; + + if (Directory.Exists(sFolder)) + { + sDelFolder = sFolder; + } + else if (f64 && Directory.Exists(Wow64Folder(sFolder))) + { + sDelFolder = Wow64Folder(sFolder); + } + else + { + return; + } + + if (!fDetectOnly) + { + LogOnly($"Request SmartDelete for folder: {sDelFolder}"); + SmartDeleteFolderEx(sDelFolder); + } + else + { + LogOnly($"Preview mode. Disallowing SmartDelete request for folder: {sDelFolder}"); + } +} + +//------------------------------------------------------------------------------- +// SmartDeleteFolderEx +// +// Executes the folder delete operation(s) +//------------------------------------------------------------------------------- +public void SmartDeleteFolderEx(string sFolder) +{ + try + { + DeleteFolder(sFolder); + CheckError("SmartDeleteFolderEx"); + DirectoryInfo parentFolder = Directory.GetParent(sFolder); + if (parentFolder != null && parentFolder.GetDirectories().Length == 0 && parentFolder.GetFiles().Length == 0) + { + SmartDeleteFolderEx(parentFolder.FullName); + } + } + catch (Exception ex) + { + Log($"Error in SmartDeleteFolderEx: {ex.Message}"); + CheckError("SmartDeleteFolderEx"); + } +} + +//------------------------------------------------------------------------------- +// RestoreExplorer +// +// Ensure Windows Explorer is restarted if needed +//------------------------------------------------------------------------------- +public void RestoreExplorer() +{ + //Non critical routine. Don't fail on error + try + { + System.Threading.Thread.Sleep(1000); + var scope = new ManagementScope(@"\\.\root\cimv2"); + string queryString = "SELECT * FROM Win32_Process WHERE Name='explorer.exe'"; + var query = new ObjectQuery(queryString); + using (var searcher = new ManagementObjectSearcher(scope, query)) + { + if (searcher.Get().Count < 1) + { + Process.Start("explorer.exe"); + //To handle this in case of System context, schedule and run as interactive task + RunCommand("SCHTASKS /Create /TN OffScrEx /TR explorer /SC ONCE /ST 12:00 /IT", true); + RunCommand("SCHTASKS /Run /TN OffScrEx", true); + RunCommand("SCHTASKS /Delete /TN OffScrEx /F", false); + } + } + } + catch (Exception ex) + { + Log($"Error in RestoreExplorer: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// MyJoin +// +// Replacement function to the internal Join function to prevent failures +// that were seen in some instances +//------------------------------------------------------------------------------- +public string MyJoin(string[] arrToJoin, string sSeparator) +{ + string sJoined = ""; + if (arrToJoin != null) + { + foreach (string item in arrToJoin) + { + sJoined = sJoined + item + sSeparator; + } + } + if (sJoined.Length > 1) sJoined = sJoined.Substring(0, sJoined.Length - 1); + return sJoined; +} + +//------------------------------------------------------------------------------- +// Rerun +// +// Flag need for reboot and schedule autorun to run the tool again on reboot. +//------------------------------------------------------------------------------- +public void Rerun() +{ + try + { + string sValue; + + // check if Rerun has already been called + if (fRerun) return; + + // set Rerun flag + fRerun = true; + + // check if the previous run already initiated the Rerun + if (RegReadValue(Constants.HKCU, @"SOFTWARE\Microsoft\Office\15.0\CleanC2R", "Rerun", out sValue, "REG_DWORD")) + { + // Rerun has already been tried + LogH2("Error: Removal failed"); + SetError(Constants.ERROR_DCAF_FAILURE); + return; + } + + fRebootRequired = true; + SetError(Constants.ERROR_REBOOT_REQUIRED); + SetError(Constants.ERROR_INCOMPLETE); + + // cache the script to the local scrub folder + string exePath = System.Reflection.Assembly.GetExecutingAssembly().Location; + string destPath = Path.Combine(sScrubDir, Constants.SCRIPTFILE); + File.Copy(exePath, destPath, true); + + // Create registry keys and set value + Microsoft.Win32.RegistryKey baseKey = Microsoft.Win32.Registry.LocalMachine; + Microsoft.Win32.RegistryKey key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\Office\15.0\CleanC2R", true); + if (key != null) + { + key.SetValue("Rerun", 1, Microsoft.Win32.RegistryValueKind.DWord); + key.Close(); + } + baseKey.Close(); + + fSetRunOnce = true; + // key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce", true); + // if (key != null) + // { + // key.SetValue("CleanC2R", $"cscript.exe \"{Path.Combine(sScrubDir, Constants.SCRIPTFILE)}\"", Microsoft.Win32.RegistryValueKind.String); + // key.Close(); + // } + } + catch (Exception ex) + { + Log($"Error in Rerun: {ex.Message}"); + } +} + +//------------------------------------------------------------------------------- +// SetRunOnce +// +// Create a RunOnce entry to resume setup after a reboot +//------------------------------------------------------------------------------- +public void SetRunOnce() +{ + try + { + Microsoft.Win32.RegistryKey baseKey = Microsoft.Win32.Registry.LocalMachine; + Microsoft.Win32.RegistryKey key = baseKey.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce", true); + if (key != null) + { + string sValue = $"cscript.exe \"{Path.Combine(sScrubDir, Constants.SCRIPTFILE)}\" /NoElevate /Relaunched"; + key.SetValue("O15CleanUp", sValue, Microsoft.Win32.RegistryValueKind.String); + key.Close(); + } + baseKey.Close(); + } + catch (Exception ex) + { + Log($"Error in SetRunOnce: {ex.Message}"); + } +}