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)