Remove workload records when uninstalling the SDK (#11924)

* Remove workload records when uninstalling the SDK

* PR Feedback - E_FILENOTFOUND

* Clean up empty registry keys.
This commit is contained in:
Jacques Eloff 2021-09-15 10:33:50 -07:00 committed by GitHub
parent 0d1cdfa6a0
commit e212c360ff
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 139 additions and 2 deletions

View file

@ -31,6 +31,7 @@ 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 pathcch.lib)
# Add WiX libraries
target_link_libraries(Finalizer wcautil.lib)

22
src/finalizer/README.md Normal file
View file

@ -0,0 +1,22 @@
# Manually Testing Finalizer Changes
The finalizer can be partially tested in isolation by creating the necessary registry keys
an installation would create. These can be easily configured on a VM or using Windows Sandbox. Otherwise, a full build of the installer is required along with the modified finalizer that results in a longer inner loop.
The finalizer will use the default hive, e.g., when compiled for x64, the finalizer will default to using the 64-bit registry hive.
### SDK Installation key
The installation key resides under the 32-bit hive, regardless of the OS architecture.
`REG ADD HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x64\sdk /v 6.0.107 /t REG_DWORD /d 1 /reg:32`
### Workload Pack Records
Records related to workload packs are created by the pack installers. Testing changes to the removal process requires the packs to be installed since the finalizer relies on using specific data such as the workload pack installer's product code to remove the installation.
### Workload Records
A workload record entry can be created using the following command. The key resides in either hive depending on the SDK installation. Records are associated with an SDK feature band, e.g., 6.0.100.
`REG ADD HKLM\SOFTWARE\Microsoft\dotnet\InstalledWorkloads\Standalone\x64\6.0.100\wasm-tools`

View file

@ -38,6 +38,110 @@ LExit:
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 = StrAllocConcatFormatted(&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 = PathCchRemoveBackslash(sczKeyName, cbKeyName);
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;
@ -54,6 +158,12 @@ extern "C" HRESULT RemoveDependent(LPWSTR sczDependent, BOOL * pbRestartRequired
// 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
@ -319,14 +429,16 @@ int wmain(int argc, wchar_t* argv[])
if (!bSdkFeatureBandInstalled)
{
LogStringLine(REPORT_STANDARD, "SDK with feature band %ls could not be found.", sczFeatureBandVersion);
goto LExit;
}
LogStringLine(REPORT_STANDARD, "SDK with feature band %ls could not be found.", sczFeatureBandVersion);
hr = ::RemoveDependent(sczDependent, &bRestartRequired);
ExitOnFailure(hr, "Failed to remove dependent \"%ls\".", sczDependent);
hr = ::DeleteWorkloadRecords(sczFeatureBandVersion, argv[3]);
ExitOnFailure(hr, "Failed to remove workload records.");
if (bRestartRequired)
{
dwExitCode = ERROR_SUCCESS_REBOOT_REQUIRED;

View file

@ -11,6 +11,7 @@
#include <cwchar>
#include <winreg.h>
#include <msi.h>
#include <pathcch.h>
// Configure some logging parameters for WiX
#define ExitTrace LogErrorString
@ -22,5 +23,6 @@
#include "dutil.h"
#include "regutil.h"
#include "logutil.h"
#include "pathutil.h"
#include "strutil.h"
#include "wiutil.h"