diff --git a/Directory.Build.props b/Directory.Build.props index 93eea8242e9d..d5827f7bf06f 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -9,13 +9,6 @@ $(BuildArchitecture) $(BuildArchitecture) x64 - - - $(Architecture) enable @@ -59,6 +52,7 @@ + $([MSBuild]::NormalizeDirectory('$(MSBuildThisFileDirectory)', 'src')) MIT Latest True diff --git a/build.cmd b/build.cmd index d657443157c0..dc2225590270 100644 --- a/build.cmd +++ b/build.cmd @@ -8,5 +8,5 @@ if %errorlevel%==0 ( set SkipBuildingInstallers=/p:SkipBuildingInstallers=true set DISABLE_CROSSGEN=true ) -powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -command "& """%~dp0eng\common\build.ps1""" -restore -build -nativeToolsOnMachine -msbuildEngine dotnet %SkipBuildingInstallers% %*" +powershell -NoLogo -NoProfile -ExecutionPolicy ByPass -command "& """%~dp0eng\common\build.ps1""" -restore -build -msbuildEngine dotnet %SkipBuildingInstallers% %*" exit /b %ErrorLevel% diff --git a/global.json b/global.json index f88993e47d31..01a7b918b994 100644 --- a/global.json +++ b/global.json @@ -13,13 +13,9 @@ "version": "16.8" } }, - "native-tools": { - "cmake": "latest" - }, "msbuild-sdks": { "Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.25126.4", "Microsoft.DotNet.Helix.Sdk": "10.0.0-beta.25126.4", - "Microsoft.Build.NoTargets": "3.7.0", - "Microsoft.DotNet.CMake.Sdk": "9.0.0-beta.24217.1" + "Microsoft.Build.NoTargets": "3.7.0" } } diff --git a/sdk.sln b/sdk.sln index 38438bd61e06..1b3c292c168b 100644 --- a/sdk.sln +++ b/sdk.sln @@ -485,7 +485,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-format.UnitTests", " EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Installer", "Installer", "{3FA6F1CB-295B-4414-B18F-93845917A8CD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "finalizer-build", "src\Installer\finalizer\finalizer-build.csproj", "{32DA04FF-A951-43EA-B2FA-86A825009A97}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "finalizer", "src\Installer\finalizer\finalizer.csproj", "{32DA04FF-A951-43EA-B2FA-86A825009A97}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "redist-installer", "src\Installer\redist-installer\redist-installer.proj", "{FAADC193-BA41-449D-97CE-0EF82836046A}" EndProject diff --git a/src/Installer/finalizer/CMakeLists.txt b/src/Installer/finalizer/CMakeLists.txt deleted file mode 100644 index 47163924e66e..000000000000 --- a/src/Installer/finalizer/CMakeLists.txt +++ /dev/null @@ -1,65 +0,0 @@ -cmake_minimum_required(VERSION 3.20) - -# Create project named finalizer, this will generate Finalizer.vcxproj -project(Finalizer) - -set(CMAKE_MACOSX_RPATH 1) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib") -set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:wmainCRTStartup") - -# The WiX SDK is extracted from a NuGet package using an SDK .csproj (finalizer-build) that copies the "lib" and "inc" folders to a stable location. -# The defines below (values in ${}) are set in the finalizer.nativeproj. -# See: https://github.com/dotnet/arcade/tree/main/src/Microsoft.DotNet.CMake.Sdk#common-items -# Note: The directory paths use forward slashes because backslashes are escape characters. -include_directories(${ArtifactsDir}WixSdk/inc) -include_directories(${ArtifactsDir}obj) -link_directories(${ArtifactsDir}WixSdk/lib/${Platform}) - -add_compile_options(/MT) - -# Microsoft.Security.SystemsADM.10086 -add_compile_options($<$:/W3>) -add_compile_options($<$:/WX>) -add_compile_options($<$:/we4018>) # 'expression' : signed/unsigned mismatch -add_compile_options($<$:/we4055>) # 'conversion' : from data pointer 'type1' to function pointer 'type2' -add_compile_options($<$:/we4146>) # unary minus operator applied to unsigned type, result still unsigned -add_compile_options($<$:/we4242>) # 'identifier' : conversion from 'type1' to 'type2', possible loss of data -add_compile_options($<$:/we4244>) # 'conversion' conversion from 'type1' to 'type2', possible loss of data -add_compile_options($<$:/we4267>) # 'var' : conversion from 'size_t' to 'type', possible loss of data -add_compile_options($<$:/we4302>) # 'conversion' : truncation from 'type 1' to 'type 2' -add_compile_options($<$:/we4308>) # negative integral constant converted to unsigned type -add_compile_options($<$:/we4509>) # nonstandard extension used: 'function' uses SEH and 'object' has destructor -add_compile_options($<$:/we4510>) # 'class' : default constructor could not be generated -add_compile_options($<$:/we4532>) # 'continue' : jump out of __finally/finally block has undefined behavior during termination handling -add_compile_options($<$:/we4533>) # initialization of 'variable' is skipped by 'instruction' -add_compile_options($<$:/we4610>) # object 'class' can never be instantiated - user-defined constructor required -add_compile_options($<$:/we4611>) # interaction between 'function' and C++ object destruction is non-portable -add_compile_options($<$:/we4700>) # uninitialized local variable 'name' used -add_compile_options($<$:/we4701>) # Potentially uninitialized local variable 'name' used -add_compile_options($<$:/we4703>) # Potentially uninitialized local pointer variable 'name' used -add_compile_options($<$:/we4789>) # destination of memory copy is too small -add_compile_options($<$:/we4995>) # 'function': name was marked as #pragma deprecated -add_compile_options($<$:/we4996>) # 'function': was declared deprecated also 'std::' -add_compile_options($<$:/guard:cf>) # Enable control flow guard - -add_executable(Finalizer - finalizer.cpp - native.rc -) - -add_link_options(/guard:cf) - -# These are normally part of a .vcxproj in Visual Studio, but appears to be missing when CMAKE generates a .vcxproj for arm64. -target_link_libraries(Finalizer shell32.lib) -target_link_libraries(Finalizer advapi32.lib) -target_link_libraries(Finalizer version.lib) -target_link_libraries(Finalizer msi.lib) -target_link_libraries(Finalizer shlwapi.lib) - -# Add WiX libraries -target_link_libraries(Finalizer wcautil.lib) -target_link_libraries(Finalizer dutil.lib) - -install(TARGETS Finalizer) diff --git a/src/Installer/finalizer/Directory.Build.props b/src/Installer/finalizer/Directory.Build.props index 79225ac8b807..7445ab504722 100644 --- a/src/Installer/finalizer/Directory.Build.props +++ b/src/Installer/finalizer/Directory.Build.props @@ -1,9 +1,5 @@ - - true - - - \ No newline at end of file + diff --git a/src/Installer/finalizer/Program.cs b/src/Installer/finalizer/Program.cs new file mode 100644 index 000000000000..4348aff65d01 --- /dev/null +++ b/src/Installer/finalizer/Program.cs @@ -0,0 +1,310 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.NET.Sdk.WorkloadManifestReader; +using Microsoft.Win32; +using Microsoft.Win32.Msi; + +if (args.Length < 3) +{ + return (int)Error.INVALID_COMMAND_LINE; +} + +string logPath = args[0]; +string sdkVersion = args[1]; +string platform = args[2]; + +using StreamWriter logStream = new StreamWriter(logPath); + +Logger.Init(logStream); + +Logger.Log($"{nameof(logPath)}: {logPath}"); +Logger.Log($"{nameof(sdkVersion)}: {sdkVersion}"); +Logger.Log($"{nameof(platform)}: {platform}"); +int exitCode = (int)Error.SUCCESS; + +try +{ + // Step 1: Parse and format SDK feature band version + SdkFeatureBand featureBandVersion = new SdkFeatureBand(sdkVersion); + string dependent = $"Microsoft.NET.Sdk,{featureBandVersion},{platform}"; + + // Step 2: Check if SDK feature band is installed + if (DetectSdk(featureBandVersion, platform)) + { + return (int)Error.SUCCESS; + } + + // Step 3: Remove dependent components if necessary + if (RemoveDependent(dependent)) + { + // Pass potential restart exit codes back to the bundle based on executing the + // workload related MSIs. The bundle may take additional actions such as prompting the user. + exitCode = (int)Error.SUCCESS_REBOOT_REQUIRED; + }; + + // Step 4: Delete workload records + DeleteWorkloadRecords(featureBandVersion, platform); + + // Step 5: Clean up install state file + RemoveInstallStateFile(featureBandVersion, platform); +} +catch (Exception ex) +{ + Logger.Log($"Error: {ex}"); + exitCode = ex.HResult; +} + +return exitCode; + +static bool DetectSdk(SdkFeatureBand featureBandVersion, string platform) +{ + string registryPath = $@"SOFTWARE\WOW6432Node\dotnet\Setup\InstalledVersions\{platform}\sdk"; + using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(registryPath)) + { + if (key is null) + { + Logger.Log("SDK registry path not found."); + return false; + } + + foreach (var valueName in key.GetValueNames()) + { + try + { + // Convert the full SDK version into an SdkFeatureBand to see whether it matches the + // SDK being removed. + SdkFeatureBand installedFeatureBand = new SdkFeatureBand(valueName); + + if (installedFeatureBand.Equals(featureBandVersion)) + { + Logger.Log($"Another SDK with the same feature band is installed: {valueName} ({installedFeatureBand})"); + return true; + } + } + catch + { + Logger.Log($"Failed to check installed SDK version: {valueName}"); + } + } + } + return false; +} + +static bool RemoveDependent(string dependent) +{ + bool restartRequired = false; + + // Open the installer dependencies registry key + // This has to be an exhaustive search as we're not looking for a specific provider key, but for a specific dependent + // that could be registered against any provider key. + using var hkInstallerDependenciesKey = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Classes\Installer\Dependencies", writable: true); + if (hkInstallerDependenciesKey is null) + { + Logger.Log("Installer dependencies key does not exist."); + return false; + } + + // Iterate over each provider key in the dependencies + foreach (string providerKeyName in hkInstallerDependenciesKey.GetSubKeyNames()) + { + Logger.Log($"Processing provider key: {providerKeyName}"); + + using var hkProviderKey = hkInstallerDependenciesKey.OpenSubKey(providerKeyName, writable: true); + if (hkProviderKey is null) + { + continue; + } + + // Open the Dependents subkey + using var hkDependentsKey = hkProviderKey.OpenSubKey("Dependents", writable: true); + if (hkDependentsKey is null) + { + continue; + } + + // Check if the dependent exists and continue if it does not + bool dependentExists = false; + foreach (string dependentsKeyName in hkDependentsKey.GetSubKeyNames()) + { + if (string.Equals(dependentsKeyName, dependent, StringComparison.OrdinalIgnoreCase)) + { + dependentExists = true; + break; + } + } + + if (!dependentExists) + { + continue; + } + + Logger.Log($"Dependent match found: {dependent}"); + + // Attempt to remove the dependent key + try + { + hkDependentsKey.DeleteSubKey(dependent); + Logger.Log("Dependent deleted"); + } + catch (Exception ex) + { + Logger.Log($"Exception while removing dependent key: {ex.Message}"); + return false; + } + + // Check if any dependents are left + if (hkDependentsKey.SubKeyCount == 0) + { + // No remaining dependents, handle product uninstallation + try + { + // Default value should be a REG_SZ containing the product code. + string? productCode = hkProviderKey.GetValue(null) as string; + + if (productCode is null) + { + Logger.Log($"No product ID found, provider key: {providerKeyName}"); + continue; + } + + // Let's make sure the product is actually installed. The provider key for an MSI typically + // stores the ProductCode, DisplayName, and Version, but by calling into MsiGetProductInfo, + // we're doing an implicit detect and getting a property back. This avoids reading additional + // registry keys. + uint error = WindowsInstaller.GetProductInfo(productCode, "ProductName", out string productName); + + if (error != Error.SUCCESS) + { + Logger.Log($"Failed to detect product, ProductCode: {productCode}, result: 0x{error:x8}"); + continue; + } + + // Need to set the UI level before executing the MSI. + _ = WindowsInstaller.SetInternalUI(InstallUILevel.None); + + // Configure the product to be absent (uninstall the product) + error = WindowsInstaller.ConfigureProduct(productCode, + WindowsInstaller.INSTALLLEVEL_DEFAULT, + InstallState.ABSENT, + "MSIFASTINSTALL=7 IGNOREDEPENDENCIES=ALL REBOOT=ReallySuppress"); + Logger.Log($"Uninstall of {productName} ({productCode}) exited with 0x{error:x8}"); + + if (error == Error.SUCCESS_REBOOT_INITIATED || error == Error.SUCCESS_REBOOT_REQUIRED) + { + restartRequired = true; + } + + // Remove the provider key. Typically these are removed by the engine, but since the workload + // packs and manifest were installed by the CLI, the finalizer needs to clean these up. + hkInstallerDependenciesKey.DeleteSubKeyTree(providerKeyName, throwOnMissingSubKey: false); + } + catch (Exception ex) + { + Logger.Log($"Failed to process dependentprocess: {ex.Message}"); + return restartRequired; + } + } + } + + return restartRequired; +} + +static void DeleteWorkloadRecords(SdkFeatureBand featureBandVersion, string platform) +{ + string? workloadKey = $@"SOFTWARE\Microsoft\dotnet\InstalledWorkloads\Standalone\{platform}"; + + using (RegistryKey? key = Registry.LocalMachine.OpenSubKey(workloadKey, writable: true)) + { + if (key is not null) + { + key.DeleteSubKeyTree(featureBandVersion.ToString(), throwOnMissingSubKey: false); + Logger.Log($"Deleted workload records for '{featureBandVersion}'."); + } + else + { + Logger.Log("No workload records found to delete."); + } + } + + DeleteEmptyKeyToRoot(Registry.LocalMachine, workloadKey); +} + +static void DeleteEmptyKeyToRoot(RegistryKey key, string name) +{ + string? subKeyName = Path.GetFileName(name.TrimEnd(Path.DirectorySeparatorChar)); + string? tempName = name; + + while (!string.IsNullOrWhiteSpace(tempName)) + { + using (RegistryKey? k = key.OpenSubKey(tempName)) + { + if (k is not null && k.SubKeyCount == 0 && k.ValueCount == 0) + { + tempName = Path.GetDirectoryName(tempName); + + if (tempName is not null) + { + try + { + using (RegistryKey? parentKey = key.OpenSubKey(tempName, writable: true)) + { + if (parentKey is not null) + { + parentKey.DeleteSubKeyTree(subKeyName, throwOnMissingSubKey: false); + Logger.Log($"Deleted empty key: {subKeyName}"); + subKeyName = Path.GetFileName(tempName.TrimEnd(Path.DirectorySeparatorChar)); + } + } + } + catch (Exception ex) + { + Logger.Log($"Failed to delete key: {tempName}, error: {ex.Message}"); + break; + } + } + } + else + { + break; + } + } + } +} + +static void RemoveInstallStateFile(SdkFeatureBand featureBandVersion, string platform) +{ + string programDataPath = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + string installStatePath = Path.Combine(programDataPath, "dotnet", "workloads", platform, featureBandVersion.ToString(), "installstate", "default.json"); + + if (File.Exists(installStatePath)) + { + File.Delete(installStatePath); + Logger.Log($"Deleted install state file: {installStatePath}"); + + var dir = new DirectoryInfo(installStatePath).Parent; + while (dir is not null && dir.Exists && dir.GetFiles().Length == 0 && dir.GetDirectories().Length == 0) + { + dir.Delete(); + dir = dir.Parent; + } + } + else + { + Logger.Log("Install state file does not exist."); + } +} + +static class Logger +{ + static StreamWriter? s_logStream; + + public static void Init(StreamWriter logStream) => s_logStream = logStream; + + public static void Log(string message) + { + var pid = Environment.ProcessId; + var tid = Environment.CurrentManagedThreadId; + s_logStream!.WriteLine($"[{pid:X4}:{tid:X4}][{DateTime.Now:yyyy-MM-ddTHH:mm:ss}] Finalizer: {message}"); + } +} diff --git a/src/Installer/finalizer/finalizer-build.csproj b/src/Installer/finalizer/finalizer-build.csproj deleted file mode 100644 index 628773d7c974..000000000000 --- a/src/Installer/finalizer/finalizer-build.csproj +++ /dev/null @@ -1,38 +0,0 @@ - - - - $(SdkTargetFramework) - true - false - false - $(ArtifactsObjDir)sdk_version.h - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Installer/finalizer/finalizer.cpp b/src/Installer/finalizer/finalizer.cpp deleted file mode 100644 index dbd120d2d2c9..000000000000 --- a/src/Installer/finalizer/finalizer.cpp +++ /dev/null @@ -1,605 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#include "precomp.h" - -extern "C" HRESULT Initialize(int argc, wchar_t* argv[]) -{ - HRESULT hr = S_OK; - - // We're not going to do any clever parsing. This is intended to be called from - // the standalone bundle only and there will only be a fixed set of parameters: - // 1. The path of the log file, created by the bundle. - // 2. The full SDK version, e.g. 6.0.105 or 6.0.398-preview.19 - // 3. Target platform to search under the registry key to locate installed SDKs. - if (4 != argc) - { - return HRESULT_FROM_WIN32(ERROR_INVALID_COMMAND_LINE); - } - - LogInitialize(::GetModuleHandleW(NULL)); - -#ifdef _DEBUG - LogSetLevel(REPORT_DEBUG, FALSE); -#else - LogSetLevel(REPORT_VERBOSE, FALSE); // FALSE means don't write an additional text line to the log saying the level changed -#endif - - hr = LogOpen(NULL, argv[1], NULL, NULL, FALSE, TRUE, NULL); - ExitOnFailure(hr, "Failed to create log file."); - - hr = RegInitialize(); - ExitOnFailure(hr, "Failed to initialize the registry."); - - hr = WiuInitialize(); - ExitOnFailure(hr, "Failed to initialize Windows Installer."); - -LExit: - return hr; -} - -extern "C" HRESULT StrTrimBackslash(LPWSTR* ppwz, LPCWSTR wzSource) -{ - HRESULT hr = S_OK; - LPWSTR sczResult = NULL; - - int i = lstrlenW(wzSource); - - if (0 < i) - { - for (i = i - 1; i > 0; --i) - { - if (L'\\' != wzSource[i]) - { - break; - } - } - - ++i; - } - - hr = StrAllocString(&sczResult, wzSource, i); - ExitOnFailure(hr, "Failed to copy result string"); - - // Output result - *ppwz = sczResult; - sczResult = NULL; - -LExit: - ReleaseStr(sczResult); - - return hr; -} - -extern "C" HRESULT DeleteWorkloadRecords(LPWSTR sczSdkFeatureBandVersion, LPWSTR sczArchitecture) -{ - HRESULT hr = S_OK; - LPWSTR sczKeyName = NULL; - LPWSTR pszName = NULL; - LPWSTR sczSubKey = NULL; - HKEY hkWorkloadRecordsKey = NULL; - HKEY hkCurrentKey = NULL; - DWORD dwIndex = 0; - DWORD dwType = 0; - DWORD_PTR cbKeyName = 0; - DWORD cbSubKeys = 0; - DWORD cbValues = 0; - BOOL bDeleteKey = FALSE; - - hr = StrAllocFormatted(&sczKeyName, L"SOFTWARE\\Microsoft\\dotnet\\InstalledWorkloads\\Standalone\\%ls", sczArchitecture); - ExitOnFailure(hr, "Failed to allocate string for workload records registry path."); - - hr = RegOpen(HKEY_LOCAL_MACHINE, sczKeyName, KEY_READ | KEY_WRITE, &hkWorkloadRecordsKey); - - if (S_OK == hr) - { - // Delete the SDK feature band's workload records. - hr = RegDelete(hkWorkloadRecordsKey, sczSdkFeatureBandVersion, REG_KEY_DEFAULT, TRUE); - ExitOnFailure(hr, "Failed to delete workload records key under '%ls' for '%ls'.", sczKeyName, sczSdkFeatureBandVersion); - LogStringLine(REPORT_STANDARD, "Deleted workload records for '%ls'.", sczSdkFeatureBandVersion); - } - else if (E_FILENOTFOUND == hr) - { - // Ignore missing registry keys. - hr = S_OK; - } - ExitOnFailure(hr, "Failed to open workload records key: %ls.", sczKeyName); - - // Clean out empty registry keys by walking backwards. Eventually we'll hit HKLM\SOFTWARE\Microsoft and stop. - for (;;) - { - bDeleteKey = TRUE; - LogStringLine(REPORT_STANDARD, "Processing '%ls'.", sczKeyName); - hr = RegOpen(HKEY_LOCAL_MACHINE, sczKeyName, KEY_READ | KEY_WRITE, &hkCurrentKey); - - if (E_FILENOTFOUND != hr && S_OK != hr) - { - ExitOnFailure(hr, "Failed to open registry key: %ls", sczKeyName); - } - - if (S_OK == hr) - { - hr = RegQueryKey(hkCurrentKey, &cbSubKeys, &cbValues); - ExitOnFailure(hr, "Failed to query key info."); - - if (0 < cbSubKeys || 0 < cbValues) - { - // If the current key has any subkeys or values then we're done. - LogStringLine(REPORT_STANDARD, "Non-empty key found. '%ls' contains %d value(s) and %d subkey(s).", sczKeyName, cbValues, cbSubKeys); - break; - } - - LogStringLine(REPORT_STANDARD, "'%ls' is empty and can be deleted.", sczKeyName); - ReleaseRegKey(hkCurrentKey); - } - else - { - // We want to continue traversing up the registry, but we can't delete a non-existing key. - LogStringLine(REPORT_STANDARD, "'%ls' does not exist, continuing.", sczKeyName); - bDeleteKey = FALSE; - } - - // Move up one level and delete the current key. For example, if we looked at SOFTWARE\Microsoft\dotnet\InstalledWorkloads\Standalone\x64, we'll - // delete the x64 subkey. - hr = StrSize(sczKeyName, &cbKeyName); - ExitOnFailure(hr, "Failed to get size of key name."); - - // Need to remove trailing backslash otherwise PathFile returns an empty string. - hr = StrTrimBackslash(&sczKeyName, sczKeyName); - ExitOnFailure(hr, "Failed to remove backslash."); - - hr = StrAllocString(&sczSubKey, PathFile(sczKeyName), 0); - ExitOnFailure(hr, "Failed to allocate string for subkey."); - - hr = PathGetParentPath(sczKeyName, &sczKeyName); - ExitOnFailure(hr, "Failed to get parent path of registry key."); - - if (bDeleteKey) - { - hr = RegOpen(HKEY_LOCAL_MACHINE, sczKeyName, KEY_READ | KEY_WRITE, &hkCurrentKey); - ExitOnFailure(hr, "Failed to open registry key: %ls.", sczKeyName); - - hr = RegDelete(hkCurrentKey, sczSubKey, REG_KEY_DEFAULT, FALSE); - ExitOnFailure(hr, "Failed to delete registry key '%ls' under '%ls'", sczSubKey, sczKeyName); - - ReleaseRegKey(hkCurrentKey); - } - } - -LExit: - ReleaseStr(sczKeyName); - ReleaseStr(pszName); - ReleaseStr(sczSubKey); - ReleaseRegKey(hkCurrentKey); - ReleaseRegKey(hkWorkloadRecordsKey); - return hr; -} - -extern "C" HRESULT RemoveDependent(LPWSTR sczDependent, BOOL * pbRestartRequired) -{ - HRESULT hr = S_OK; - HKEY hkInstallerDependenciesKey = NULL; - HKEY hkProviderKey = NULL; - HKEY hkDependentsKey = NULL; - LPWSTR sczProviderKey = NULL; - LPWSTR sczDependentsKey = NULL; - LPWSTR sczProductId = NULL; - LPWSTR sczProductName = NULL; - DWORD cSubKeys = 0; - DWORD dwExitCode = 0; - WIU_RESTART restart = WIU_RESTART_NONE; - - // Optional workloads are always per-machine installs, so we don't need to check HKCU. - hr = RegOpen(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Classes\\Installer\\Dependencies", KEY_READ, &hkInstallerDependenciesKey); - if (E_FILENOTFOUND == hr) - { - LogStringLine(REPORT_STANDARD, "Installer dependencies key does not exit."); - hr = S_OK; - goto LExit; - } - ExitOnFailure(hr, "Failed to read installer dependencies key."); - - // This has to be an exhaustive search as we're not looking for a specific provider key, but for a specific dependent - // that could be registered against any provider key. - for (DWORD dwIndex = 0;; ++dwIndex) - { - // Get the next provider key name - hr = RegKeyEnum(hkInstallerDependenciesKey, dwIndex, &sczProviderKey); - - if (E_NOMOREITEMS == hr) - { - hr = S_OK; - break; - } - - ExitOnFailure(hr, "Failed to enumerate installer dependency provider keys."); - LogStringLine(REPORT_STANDARD, "Processing provider key: %ls", sczProviderKey); - - hr = RegOpen(hkInstallerDependenciesKey, sczProviderKey, KEY_READ, &hkProviderKey); - ExitOnFailure(hr, "Unable to open provider key."); - - // Open the dependents key with write permissions so we can modify it if it matches - // the target dependent value. - hr = RegOpen(hkProviderKey, L"Dependents", KEY_READ | KEY_WRITE, &hkDependentsKey); - if (E_FILENOTFOUND == hr) - { - // Providers can sometimes become orphaned during uninstalls. If there's no Dependents subkey, we just - // release the handle and continue to the next provider key. - hr = S_OK; - ReleaseRegKey(hkProviderKey); - - continue; - } - - ExitOnFailure(hr, "Unable to open dependents key."); - - // Enumerate over all the dependent keys - for (DWORD dwDependentsKeyIndex = 0;; ++dwDependentsKeyIndex) - { - hr = RegKeyEnum(hkDependentsKey, dwDependentsKeyIndex, &sczDependentsKey); - - if (E_NOMOREITEMS == hr) - { - hr = S_OK; - break; - } - - ExitOnFailure(hr, "Failed to read provider's dependent key."); - - if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczDependentsKey, -1, sczDependent, -1)) - { - LogStringLine(REPORT_STANDARD, " Dependent match found: %ls", sczDependentsKey); - - hr = RegDelete(hkDependentsKey, sczDependent, REG_KEY_DEFAULT, TRUE); - ExitOnFailure(hr, "Failed to delete dependent \"%ls\"", sczDependent); - LogStringLine(REPORT_STANDARD, " Dependent deleted"); - // Reset the index since we're deleting keys while enumerating - dwDependentsKeyIndex = dwDependentsKeyIndex > 1 ? dwDependentsKeyIndex-- : 0; - - // Check if there are any subkeys remaining under the dependents key. If not, we - // can uninstall the MSI. We'll recheck the key again in case the MSI fails to clean up the - // provider key to make sure we don't have orphaned keys. - hr = RegQueryKey(hkDependentsKey, &cSubKeys, NULL); - ExitOnFailure(hr, "Failed to query dependents key."); - - LogStringLine(REPORT_STANDARD, " Remaining dependents: %i", cSubKeys); - - if (0 == cSubKeys) - { - // This was the final dependent, so now we can remove the installation if the provider wasn't corrupted and - // still contains the product ID. - hr = RegReadString(hkProviderKey, NULL, &sczProductId); - - if (E_FILENOTFOUND == hr) - { - LogStringLine(REPORT_STANDARD, " No product ID found, provider key: %ls", sczProviderKey); - hr = S_OK; - break; - } - else - { - ExitOnFailure(hr, "Failed to read product ID."); - } - - // Let's make sure the product is actually installed. The provider key for an MSI typically - // stores the ProductCode, DisplayName, and Version, but by calling into MsiGetProductInfo, - // we're doing an implicit detect and getting a property back. - hr = WiuGetProductInfo(sczProductId, L"ProductName", &sczProductName); - if (SUCCEEDED(hr)) - { - // The provider key *should* have the ProductName and ProductVersion properties, but since - // we know it's installed, we just query the installer service. - MsiSetInternalUI(INSTALLUILEVEL_NONE, NULL); - hr = WiuConfigureProductEx(sczProductId, INSTALLLEVEL_DEFAULT, INSTALLSTATE_ABSENT, L"MSIFASTINSTALL=7 IGNOREDEPENDENCIES=ALL REBOOT=ReallySuppress", &restart); - LogStringLine(REPORT_STANDARD, " Uninstall of \"%ls\" (%ls%) exited with 0x%.8x", sczProductName, sczProductId, hr); - - // Flag any reboot since we need to return that to the bundle. - if (WIU_RESTART_INITIATED == restart || WIU_RESTART_REQUIRED == restart) - { - LogStringLine(REPORT_STANDARD, " Reboot requested, deferring."); - *pbRestartRequired = TRUE; - } - - // Reset potential failures so we can continue to remove as many dependents as possible. - hr = S_OK; - } - else if (HRESULT_FROM_WIN32(ERROR_UNKNOWN_PRODUCT) == hr || HRESULT_FROM_WIN32(ERROR_UNKNOWN_PROPERTY) == hr) - { - // Possibly a corrupted provider key that wasn't cleaned up. We'll just ignore it. - LogStringLine(REPORT_STANDARD, " Product is not installed, ProductCode:%ls, result: 0x%.8x", sczProductId, hr); - hr = S_OK; - } - } - } - } - - ReleaseRegKey(hkDependentsKey); - ReleaseRegKey(hkProviderKey); - } - -LExit: - ReleaseStr(sczProductName); - ReleaseStr(sczProductId); - ReleaseStr(sczProviderKey); - ReleaseStr(sczDependentsKey); - ReleaseRegKey(hkDependentsKey); - ReleaseRegKey(hkProviderKey); - ReleaseRegKey(hkInstallerDependenciesKey); - return hr; -} - -extern "C" HRESULT ParseSdkVersion(LPWSTR sczSdkVersion, LPWSTR * ppwzSdkFeatureBandVersion) -{ - HRESULT hr = S_OK; - UINT cVersionParts = 0; - UINT cSemanticParts = 0; - UINT cPrereleaseParts = 0; - DWORD cchPatch = 0; - LPWSTR* rgsczVersionParts = NULL; - LPWSTR* rgsczSemanticParts = NULL; - LPWSTR* rgsczPrereleaseParts = NULL; - LPWSTR sczPrereleaseLabel = NULL; - int iMajor = 0; - int iMinor = 0; - int iFeatureBand = 0; - int iPatch = 0; - - LogStringLine(REPORT_STANDARD, "Parsing SDK version: %ls", sczSdkVersion); - - // Split the version to separate potential prerelease labels from the core version - hr = StrSplitAllocArray(&rgsczSemanticParts, &cSemanticParts, sczSdkVersion, L"-"); - ExitOnFailure(hr, "Failed to split version."); - - if (2 == cSemanticParts) - { - LogStringLine(REPORT_STANDARD, "Semantic version component: %ls", rgsczSemanticParts[1]); - - hr = StrSplitAllocArray(&rgsczPrereleaseParts, &cPrereleaseParts, rgsczSemanticParts[1], L"."); - ExitOnFailure(hr, "Failed to split prerelease labels."); - - // SDK versions for CI/DEV builds map to pure feature band versions, e.g. 6.0.108-ci maps to 6.0.100. - if ((CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, rgsczPrereleaseParts[0], -1, L"dev", -1)) && - (CSTR_EQUAL != ::CompareStringW(LOCALE_INVARIANT, 0, rgsczPrereleaseParts[0], -1, L"ci", -1))) - { - if (1 <= cPrereleaseParts) - { - hr = StrAllocFormatted(&sczPrereleaseLabel, L"%ls.%ls", rgsczPrereleaseParts[0], rgsczPrereleaseParts[1]); - ExitOnFailure(hr, "Failed to allocate string for prerelease label."); - } - else - { - hr = StrAllocFormatted(&sczPrereleaseLabel, L"%ls", rgsczPrereleaseParts[0]); - ExitOnFailure(hr, "Failed to allocate string for prerelease label."); - } - - LogStringLine(REPORT_STANDARD, "Prerelease label: %ls", sczPrereleaseLabel); - } - } - - // Split the core version - hr = StrSplitAllocArray(&rgsczVersionParts, &cVersionParts, rgsczSemanticParts[0], L"."); - ExitOnFailure(hr, "Failed to split version."); - - // We only care about the major.minor.patch values - // to convert to a feature band. If we don't have at least - // all 3 parts, we'll ignore the value. - if (3 > cVersionParts) - { - ExitOnFailure(E_INVALIDARG, "Invalid SDK version: %ls %li", sczSdkVersion, cVersionParts); - } - - hr = StrStringToInt32(rgsczVersionParts[0], 0, &iMajor); - ExitOnFailure(hr, "Invalid major version."); - hr = StrStringToInt32(rgsczVersionParts[1], 0, &iMinor); - ExitOnFailure(hr, "Invalid minor version."); - - // If this is a valid SDK version the 'patch' should be a 3 digit field - // containing the feature band and patch level, e.g. 100 or 207. We - // can discard any prerelease labels from the semantic version. - hr = StrStringToInt32(rgsczVersionParts[2], 0, &iPatch); - ExitOnFailure(hr, "Invalid patch version."); - - if (100 > iPatch) - { - hr = E_INVALIDARG; - ExitOnFailure(hr, "Invalid SDK feature band and patch level."); - } - - iFeatureBand = iPatch - (iPatch % 100); - - if (NULL == sczPrereleaseLabel) - { - hr = StrAllocFormatted(ppwzSdkFeatureBandVersion, L"%li.%li.%li", iMajor, iMinor, iFeatureBand); - ExitOnFailure(hr, "Failed to allocate string for SDK feature band version."); - } - else - { - hr = StrAllocFormatted(ppwzSdkFeatureBandVersion, L"%li.%li.%li-%ls", iMajor, iMinor, iFeatureBand, sczPrereleaseLabel); - ExitOnFailure(hr, "Failed to allocate string for SDK feature band version."); - } - - LogStringLine(REPORT_STANDARD, "SDK feature band version: %ls", *ppwzSdkFeatureBandVersion); - -LExit: - ReleaseStrArray(rgsczVersionParts, cVersionParts); - ReleaseStrArray(rgsczSemanticParts, cSemanticParts); - ReleaseStrArray(rgsczPrereleaseParts, cPrereleaseParts); - ReleaseStr(sczPrereleaseLabel); - return hr; -} - -extern "C" HRESULT DetectSdk(LPWSTR sczSdkFeatureBandVersion, LPWSTR sczArchitecture, BOOL * pbInstalled) -{ - HRESULT hr = S_OK; - HKEY hkInstalledSdkVersionsKey = NULL; - LPWSTR sczInstalledSdkVersionsKeyName = NULL; - LPWSTR sczSdkVersion = NULL; - LPWSTR sczInstalledFeatureBand = NULL; - DWORD dwSdkVersionValueType = 0; - - LogStringLine(REPORT_STANDARD, "Detecting installed SDK versions for %ls", sczSdkFeatureBandVersion); - - // Scan the registry to see if any SDK matching the feature band we're trying to - // clean up is still installed. All the installation keys reside in the 32-bit hive. - hr = StrAllocFormatted(&sczInstalledSdkVersionsKeyName, L"SOFTWARE\\WOW6432Node\\dotnet\\Setup\\InstalledVersions\\%ls\\sdk", sczArchitecture); - ExitOnFailure(hr, "Failed to allocate string for installed SDK versions key name."); - - LogStringLine(REPORT_STANDARD, "Scanning %ls", sczInstalledSdkVersionsKeyName); - - hr = RegOpen(HKEY_LOCAL_MACHINE, sczInstalledSdkVersionsKeyName, KEY_READ, &hkInstalledSdkVersionsKey); - - // When the last SDK is removed the registry key should no longer exist so we can just exit - if (E_FILENOTFOUND == hr) - { - LogStringLine(REPORT_STANDARD, "Registry key not found: %ls.", sczInstalledSdkVersionsKeyName); - hr = S_OK; - *pbInstalled = FALSE; - goto LExit; - } - - ExitOnFailure(hr, "Failed to open registry key: %ls.", sczInstalledSdkVersionsKeyName); - - for (DWORD dwSdkVersionsValueIndex = 0;; ++dwSdkVersionsValueIndex) - { - hr = RegValueEnum(hkInstalledSdkVersionsKey, dwSdkVersionsValueIndex, &sczSdkVersion, &dwSdkVersionValueType); - - if (E_NOMOREITEMS == hr) - { - hr = S_OK; - break; - } - - ExitOnFailure(hr, "Failed to read SDK version values from registry."); - - hr = ParseSdkVersion(sczSdkVersion, &sczInstalledFeatureBand); - ExitOnFailure(hr, "Failed to parse %ls", sczSdkVersion); - - LogStringLine(REPORT_STANDARD, "SDK version detected: %ls, mapping to %ls.", sczSdkVersion, sczInstalledFeatureBand); - - // Bail out on the first match. - if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczInstalledFeatureBand, -1, sczSdkFeatureBandVersion, -1)) - { - *pbInstalled = TRUE; - break; - } - } - -LExit: - ReleaseRegKey(hkInstalledSdkVersionsKey); - ReleaseStr(sczInstalledSdkVersionsKeyName); - ReleaseStr(sczSdkVersion); - ReleaseStr(sczInstalledFeatureBand); - return hr; -} - -void RemoveInstallStateFile(LPWSTR sczSdkFeatureBandVersion, LPWSTR sczPlatform) -{ - HRESULT hr = S_OK; - LPWSTR sczProgramData = NULL; - LPWSTR sczInstallStatePath = NULL; - LPWSTR sczPath = NULL; - - hr = ShelGetFolder(&sczProgramData, CSIDL_COMMON_APPDATA); - ExitOnFailure(hr, "Failed to get shell folder."); - - hr = PathConcat(sczProgramData, L"dotnet", &sczInstallStatePath); - ExitOnFailure(hr, "Failed to concat dotnet to install state path."); - - hr = PathConcat(sczInstallStatePath, L"workloads", &sczInstallStatePath); - ExitOnFailure(hr, "Failed to concat workloads to install state path."); - - hr = PathConcat(sczInstallStatePath, sczPlatform, &sczInstallStatePath); - ExitOnFailure(hr, "Failed to concat platform (%ls) to install state path.", sczPlatform); - - hr = PathConcat(sczInstallStatePath, sczSdkFeatureBandVersion, &sczInstallStatePath); - ExitOnFailure(hr, "Failed to concat feature band (%ls) to install state path.", sczSdkFeatureBandVersion); - - hr = PathConcat(sczInstallStatePath, L"installstate", &sczInstallStatePath); - ExitOnFailure(hr, "Failed to concat installstate to install state path."); - - hr = PathConcat(sczInstallStatePath, L"default.json", &sczInstallStatePath); - ExitOnFailure(hr, "Failed to concat default.json to install state path."); - - if (FileExistsEx(sczInstallStatePath, NULL)) - { - LogStringLine(REPORT_STANDARD, "Deleting install state file: %ls", sczInstallStatePath); - hr = FileEnsureDelete(sczInstallStatePath); - ExitOnFailure(hr, "Failed to delete install state file: %ls", sczInstallStatePath); - - hr = PathGetParentPath(sczInstallStatePath, &sczPath); - ExitOnFailure(hr, "Failed to get parent path of install state file."); - - LogStringLine(REPORT_STANDARD, "Cleaning up empty workload folders."); - DirDeleteEmptyDirectoriesToRoot(sczPath, 0); - } - else - { - LogStringLine(REPORT_STANDARD, "Install state file does not exist: %ls", sczInstallStatePath); - } - -LExit: - ReleaseStr(sczPath); - ReleaseStr(sczInstallStatePath) - ReleaseStr(sczProgramData); -} - -int wmain(int argc, wchar_t* argv[]) -{ - HRESULT hr = S_OK; - DWORD dwExitCode = 0; - LPWSTR sczDependent = NULL; - LPWSTR sczFeatureBandVersion = NULL; - LPWSTR sczPlatform = NULL; - BOOL bRestartRequired = FALSE; - BOOL bSdkFeatureBandInstalled = FALSE; - int iMajor = 0; - int iMinor = 0; - int iFeatureBand = 0; - - hr = ::Initialize(argc, argv); - ExitOnFailure(hr, "Failed to initialize."); - - hr = StrAllocString(&sczPlatform, argv[3], 0); - ExitOnFailure(hr, "Failed to copy platform argument."); - - // Convert the full SDK version to a feature band version - hr = ParseSdkVersion(argv[2], &sczFeatureBandVersion); - ExitOnFailure(hr, "Failed to parse version, %ls.", argv[2]); - - // Create the dependent value, e.g., Microsoft.NET.Sdk,6.0.300,arm64 - hr = StrAllocFormatted(&sczDependent, L"Microsoft.NET.Sdk,%ls,%ls", sczFeatureBandVersion, sczPlatform); - ExitOnFailure(hr, "Failed to create dependent."); - LogStringLine(REPORT_STANDARD, "Setting target dependent to %ls.", sczDependent); - - hr = ::DetectSdk(sczFeatureBandVersion, sczPlatform, &bSdkFeatureBandInstalled); - ExitOnFailure(hr, "Failed to detect installed SDKs."); - - // If the feature band is still present, do not remove workloads. - if (bSdkFeatureBandInstalled) - { - LogStringLine(REPORT_STANDARD, "Detected SDK with feature band %ls.", sczFeatureBandVersion); - goto LExit; - } - - hr = ::RemoveDependent(sczDependent, &bRestartRequired); - ExitOnFailure(hr, "Failed to remove dependent \"%ls\".", sczDependent); - - hr = ::DeleteWorkloadRecords(sczFeatureBandVersion, sczPlatform); - ExitOnFailure(hr, "Failed to remove workload records."); - - if (bRestartRequired) - { - dwExitCode = ERROR_SUCCESS_REBOOT_REQUIRED; - } - - RemoveInstallStateFile(sczFeatureBandVersion, sczPlatform); - -LExit: - ReleaseStr(sczDependent); - ReleaseStr(sczFeatureBandVersion); - ReleaseStr(sczPlatform); - LogUninitialize(TRUE); - RegUninitialize(); - WiuUninitialize(); - return FAILED(hr) ? (int)hr : (int)dwExitCode; -} \ No newline at end of file diff --git a/src/Installer/finalizer/finalizer.csproj b/src/Installer/finalizer/finalizer.csproj new file mode 100644 index 000000000000..47330a6494a8 --- /dev/null +++ b/src/Installer/finalizer/finalizer.csproj @@ -0,0 +1,31 @@ + + + + $(SdkTargetFramework)-windows + WinExe + enable + true + Guard + true + en-US + Size + false + <_IsPublishing>true + $(ArtifactsBinDir)finalizer\win-$(Architecture)\$(Configuration)\bin + + + + + + + + + + + + + + + + + diff --git a/src/Installer/finalizer/finalizer.nativeproj b/src/Installer/finalizer/finalizer.nativeproj deleted file mode 100644 index d3c56a7dc231..000000000000 --- a/src/Installer/finalizer/finalizer.nativeproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - $(Architecture) - - $([System.String]::Copy($(ArtifactsDir)).Replace('\','/')) - CMakeLists.txt - - - - - - - - - - - diff --git a/src/Installer/finalizer/native.rc b/src/Installer/finalizer/native.rc deleted file mode 100644 index bea8f0b52014..000000000000 Binary files a/src/Installer/finalizer/native.rc and /dev/null differ diff --git a/src/Installer/finalizer/precomp.h b/src/Installer/finalizer/precomp.h deleted file mode 100644 index a0264c2026d0..000000000000 --- a/src/Installer/finalizer/precomp.h +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Configure some logging parameters for WiX -#define ExitTrace LogErrorString -#define ExitTrace1 LogErrorString -#define ExitTrace2 LogErrorString -#define ExitTrace3 LogErrorString - -// Includes from WiX SDK -#include "dutil.h" -#include "regutil.h" -#include "logutil.h" -#include "pathutil.h" -#include "strutil.h" -#include "wiutil.h" -#include "dirutil.h" -#include "fileutil.h" -#include "shelutil.h" diff --git a/src/Installer/redist-installer/targets/GenerateMSIs.targets b/src/Installer/redist-installer/targets/GenerateMSIs.targets index 0f4b5def63bf..06cb2ef2212b 100644 --- a/src/Installer/redist-installer/targets/GenerateMSIs.targets +++ b/src/Installer/redist-installer/targets/GenerateMSIs.targets @@ -20,7 +20,7 @@ $(SdkPkgSourcesRootDirectory)/generatebundle.ps1 $(SdkPkgSourcesRootDirectory)/generatenupkg.ps1 - $(ArtifactsBinDir)finalizer/$(Architecture)/$(Configuration)/bin/finalizer.exe + $(ArtifactsBinDir)finalizer\win-$(Architecture)\$(Configuration)\bin\finalizer.exe $(SdkPkgSourcesRootDirectory)/VS.Redist.Common.NetCore.Toolset.nuspec $(ArtifactsNonShippingPackagesDir)VS.Redist.Common.NetCore.Toolset.$(Architecture).$(FullNugetVersion).nupkg diff --git a/src/Microsoft.Win32.Msi/Error.cs b/src/Microsoft.Win32.Msi/Error.cs index c85635d825d5..dda1d3c09ca4 100644 --- a/src/Microsoft.Win32.Msi/Error.cs +++ b/src/Microsoft.Win32.Msi/Error.cs @@ -115,6 +115,11 @@ public static class Error /// public const uint FUNCTION_FAILED = 1627; + /// + /// Invalid command line argument. Consult the Windows Installer SDK for detailed command line help. + /// + public const uint INVALID_COMMAND_LINE = 1639; + /// /// The installer has initiated a restart. This message is indicative of a success. /// diff --git a/src/SourceBuild/content/repo-projects/sdk.proj b/src/SourceBuild/content/repo-projects/sdk.proj index fa38b1d60404..eba94246d16e 100644 --- a/src/SourceBuild/content/repo-projects/sdk.proj +++ b/src/SourceBuild/content/repo-projects/sdk.proj @@ -6,7 +6,6 @@ true - $(BuildArgs) -nativeToolsOnMachine $(BuildArgs) /p:PackageProjectUrl=https://github.com/dotnet/sdk $(BuildArgs) /p:PortableRid=$(PortableRid)