-
Notifications
You must be signed in to change notification settings - Fork 446
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Optional Workload: Finalizer (#10356)
MSI finalizer to remove optional workloads when uninstalling the SDK.
- Loading branch information
Showing
19 changed files
with
440 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
|
||
Microsoft Visual Studio Solution File, Format Version 12.00 | ||
# Visual Studio Version 16 | ||
VisualStudioVersion = 16.0.28603.18 | ||
MinimumVisualStudioVersion = 10.0.40219.1 | ||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{ED2FE3E2-F7E7-4389-8231-B65123F2076F}" | ||
EndProject | ||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "finalizer_shim", "src\finalizer_shim\finalizer_shim.csproj", "{688E2883-C5A9-4D66-A207-772C9160989C}" | ||
EndProject | ||
Global | ||
GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
Debug|x86 = Debug|x86 | ||
Debug|x64 = Debug|x64 | ||
Debug|arm64 = Debug|arm64 | ||
Release|x86 = Release|x86 | ||
Release|x64 = Release|x64 | ||
Release|arm64 = Release|arm64 | ||
EndGlobalSection | ||
GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Debug|x86.ActiveCfg = Debug|x86 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Debug|x86.Build.0 = Debug|x86 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Debug|x64.ActiveCfg = Debug|x64 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Debug|x64.Build.0 = Debug|x64 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Debug|arm64.ActiveCfg = Debug|arm64 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Debug|arm64.Build.0 = Debug|arm64 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Release|x86.ActiveCfg = Release|x86 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Release|x86.Build.0 = Release|x86 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Release|x64.ActiveCfg = Release|x64 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Release|x64.Build.0 = Release|x64 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Release|arm64.ActiveCfg = Release|arm64 | ||
{688E2883-C5A9-4D66-A207-772C9160989C}.Release|arm64.Build.0 = Release|arm64 | ||
EndGlobalSection | ||
GlobalSection(SolutionProperties) = preSolution | ||
HideSolutionNode = FALSE | ||
EndGlobalSection | ||
GlobalSection(NestedProjects) = preSolution | ||
{688E2883-C5A9-4D66-A207-772C9160989C} = {ED2FE3E2-F7E7-4389-8231-B65123F2076F} | ||
EndGlobalSection | ||
GlobalSection(ExtensibilityGlobals) = postSolution | ||
SolutionGuid = {18FCFCA3-D1A8-4D3A-9763-6A658D0D726F} | ||
EndGlobalSection | ||
EndGlobal |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<Project> | ||
<ItemGroup> | ||
<ProjectToBuild Include="$(RepoRoot)Microsoft.DotNet.Cli.sln" /> | ||
<ProjectToBuild Condition="'$(OS)' == 'Windows_NT' And ('$(Architecture)' == 'x86' Or '$(Architecture)' == 'x64' Or '$(Architecture)' == 'arm64')" | ||
Include="$(RepoRoot)eng\version.csproj; | ||
$(RepoRoot)eng\native.proj" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,6 @@ | ||
# SdkTests do not currently work with globally installed CLI as they use dotnet-install.ps1 to install more runtimes | ||
|
||
useInstalledDotNetCli="false" | ||
useInstalledDotNetCli="false" | ||
|
||
# Working around issue https://github.com/dotnet/arcade/issues/7327 | ||
DisableNativeToolsetInstalls=true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. --> | ||
<Project> | ||
<PropertyGroup> | ||
<Platform Condition="'$(Platform)'=='' Or $(Platform)=='AnyCPU'">$(Architecture)</Platform> | ||
</PropertyGroup> | ||
|
||
<Target Name="Build"> | ||
<MSBuild Projects="$(RepoRoot)Native.sln" Properties="Platform=$(Platform)" Targets="Restore;Build" /> | ||
</Target> | ||
|
||
<Target Name="Test" /> | ||
<Target Name="Pack" /> | ||
<Target Name="Rebuild" /> | ||
<Target Name="IntegrationTest" /> | ||
<Target Name="PerformanceTest" /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
<Project Sdk="Microsoft.NET.Sdk" ToolsVersion="15.0"> | ||
<PropertyGroup> | ||
<TargetFramework>$(CoreSdkTargetFramework)</TargetFramework> | ||
<NativeVersionFile>$(ArtifactsObjDir)sdk_version.h</NativeVersionFile> | ||
</PropertyGroup> | ||
|
||
<Target Name="GenerateSdkVersionFile" BeforeTargets="CoreCompile" DependsOnTargets="GenerateNativeVersionFile" /> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
cmake_minimum_required(VERSION 3.15.5) | ||
|
||
# Create project named finalizer, this will | ||
# 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_shim) | ||
# that copies the "lib" and "inc" folders to a stable location. | ||
|
||
include_directories(../../artifacts/WixSdk/inc) | ||
include_directories(../../artifacts/obj) | ||
link_directories(../../artifacts/WixSdk/lib/${Platform}) | ||
|
||
add_compile_options(/MT) | ||
|
||
add_executable(Finalizer | ||
finalizer.cpp | ||
native.rc | ||
) | ||
|
||
# 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) | ||
|
||
# Add WiX libraries | ||
target_link_libraries(Finalizer wcautil.lib) | ||
target_link_libraries(Finalizer dutil.lib) | ||
|
||
install(TARGETS Finalizer) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
// 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 two parameters: | ||
// 1. The path of the log file, created by the bundle. | ||
// 2. The dependent we're trying to clean up. | ||
if (argc != 3) | ||
{ | ||
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 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); | ||
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 duirng 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 dwDepdentsKeyIndex = 0;; ++dwDepdentsKeyIndex) | ||
{ | ||
hr = RegKeyEnum(hkDependentsKey, dwDepdentsKeyIndex, &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 | ||
dwDepdentsKeyIndex = dwDepdentsKeyIndex > 1 ? dwDepdentsKeyIndex-- : 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; | ||
} | ||
|
||
int wmain(int argc, wchar_t* argv[]) | ||
{ | ||
HRESULT hr = S_OK; | ||
DWORD dwExitCode = 0; | ||
LPWSTR sczDependent = NULL; | ||
BOOL bRestartRequired = FALSE; | ||
|
||
hr = ::Initialize(argc, argv); | ||
ExitOnFailure(hr, "Failed to initialize."); | ||
|
||
sczDependent = argv[2]; | ||
hr = ::RemoveDependent(sczDependent, &bRestartRequired); | ||
ExitOnFailure(hr, "Failed to remove dependent \"%ls\".", sczDependent); | ||
|
||
if (bRestartRequired) | ||
{ | ||
dwExitCode = ERROR_SUCCESS_REBOOT_REQUIRED; | ||
} | ||
|
||
LExit: | ||
LogUninitialize(TRUE); | ||
RegUninitialize(); | ||
WiuUninitialize(); | ||
return FAILED(hr) ? (int)hr : (int)dwExitCode; | ||
} |
Oops, something went wrong.