2021-05-04 20:55:39 -07:00
|
|
|
// 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[])
|
|
|
|
{
|
2021-07-07 08:04:20 -07:00
|
|
|
HRESULT hr = S_OK;
|
2021-05-04 20:55:39 -07:00
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
// 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.
|
2022-01-19 09:17:41 -08:00
|
|
|
// 2. The full SDK version, e.g. 6.0.105 or 6.0.398-preview.19
|
2021-07-07 08:04:20 -07:00
|
|
|
// 3. Target platform to search under the registry key to locate installed SDKs.
|
|
|
|
if (4 != argc)
|
|
|
|
{
|
|
|
|
return HRESULT_FROM_WIN32(ERROR_INVALID_COMMAND_LINE);
|
|
|
|
}
|
2021-05-04 20:55:39 -07:00
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
LogInitialize(::GetModuleHandleW(NULL));
|
2021-05-04 20:55:39 -07:00
|
|
|
|
|
|
|
#ifdef _DEBUG
|
2021-07-07 08:04:20 -07:00
|
|
|
LogSetLevel(REPORT_DEBUG, FALSE);
|
2021-05-04 20:55:39 -07:00
|
|
|
#else
|
2021-07-07 08:04:20 -07:00
|
|
|
LogSetLevel(REPORT_VERBOSE, FALSE); // FALSE means don't write an additional text line to the log saying the level changed
|
2021-05-04 20:55:39 -07:00
|
|
|
#endif
|
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
hr = LogOpen(NULL, argv[1], NULL, NULL, FALSE, TRUE, NULL);
|
|
|
|
ExitOnFailure(hr, "Failed to create log file.");
|
2021-05-04 20:55:39 -07:00
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
hr = RegInitialize();
|
|
|
|
ExitOnFailure(hr, "Failed to initialize the registry.");
|
2021-05-04 20:55:39 -07:00
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
hr = WiuInitialize();
|
|
|
|
ExitOnFailure(hr, "Failed to initialize Windows Installer.");
|
2021-05-04 20:55:39 -07:00
|
|
|
|
|
|
|
LExit:
|
2021-07-07 08:04:20 -07:00
|
|
|
return hr;
|
2021-05-04 20:55:39 -07:00
|
|
|
}
|
|
|
|
|
2022-04-24 21:27:50 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-09-15 10:33:50 -07:00
|
|
|
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;
|
|
|
|
|
2022-01-19 09:17:41 -08:00
|
|
|
hr = StrAllocFormatted(&sczKeyName, L"SOFTWARE\\Microsoft\\dotnet\\InstalledWorkloads\\Standalone\\%ls", sczArchitecture);
|
2021-09-15 10:33:50 -07:00
|
|
|
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.
|
2022-04-24 21:27:50 -07:00
|
|
|
hr = StrTrimBackslash(&sczKeyName, sczKeyName);
|
2021-09-15 10:33:50 -07:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
extern "C" HRESULT RemoveDependent(LPWSTR sczDependent, BOOL * pbRestartRequired)
|
2021-05-04 20:55:39 -07:00
|
|
|
{
|
2021-07-07 08:04:20 -07:00
|
|
|
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);
|
2021-09-15 10:33:50 -07:00
|
|
|
if (E_FILENOTFOUND == hr)
|
|
|
|
{
|
|
|
|
LogStringLine(REPORT_STANDARD, "Installer dependencies key does not exit.");
|
|
|
|
hr = S_OK;
|
|
|
|
goto LExit;
|
|
|
|
}
|
2021-07-07 08:04:20 -07:00
|
|
|
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)
|
|
|
|
{
|
2021-05-04 20:55:39 -07:00
|
|
|
// Possibly a corrupted provider key that wasn't cleaned up. We'll just ignore it.
|
2021-07-07 08:04:20 -07:00
|
|
|
LogStringLine(REPORT_STANDARD, " Product is not installed, ProductCode:%ls, result: 0x%.8x", sczProductId, hr);
|
|
|
|
hr = S_OK;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-05-04 20:55:39 -07:00
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
ReleaseRegKey(hkDependentsKey);
|
|
|
|
ReleaseRegKey(hkProviderKey);
|
|
|
|
}
|
2021-05-04 20:55:39 -07:00
|
|
|
|
|
|
|
LExit:
|
2021-07-07 08:04:20 -07:00
|
|
|
ReleaseStr(sczProductName);
|
|
|
|
ReleaseStr(sczProductId);
|
|
|
|
ReleaseStr(sczProviderKey);
|
|
|
|
ReleaseStr(sczDependentsKey);
|
|
|
|
ReleaseRegKey(hkDependentsKey);
|
|
|
|
ReleaseRegKey(hkProviderKey);
|
|
|
|
ReleaseRegKey(hkInstallerDependenciesKey);
|
|
|
|
return hr;
|
|
|
|
}
|
|
|
|
|
2022-01-19 09:17:41 -08:00
|
|
|
extern "C" HRESULT ParseSdkVersion(LPWSTR sczSdkVersion, LPWSTR * ppwzSdkFeatureBandVersion)
|
2021-07-07 08:04:20 -07:00
|
|
|
{
|
|
|
|
HRESULT hr = S_OK;
|
|
|
|
UINT cVersionParts = 0;
|
2022-01-19 09:17:41 -08:00
|
|
|
UINT cSemanticParts = 0;
|
|
|
|
UINT cPrereleaseParts = 0;
|
|
|
|
DWORD cchPatch = 0;
|
2021-07-07 08:04:20 -07:00
|
|
|
LPWSTR* rgsczVersionParts = NULL;
|
2022-01-19 09:17:41 -08:00
|
|
|
LPWSTR* rgsczSemanticParts = NULL;
|
|
|
|
LPWSTR* rgsczPrereleaseParts = NULL;
|
|
|
|
LPWSTR sczPrereleaseLabel = NULL;
|
|
|
|
int iMajor = 0;
|
|
|
|
int iMinor = 0;
|
|
|
|
int iFeatureBand = 0;
|
2021-07-07 08:04:20 -07:00
|
|
|
int iPatch = 0;
|
|
|
|
|
2022-01-19 09:17:41 -08:00
|
|
|
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".");
|
2021-07-07 08:04:20 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2022-01-19 09:17:41 -08:00
|
|
|
hr = StrStringToInt32(rgsczVersionParts[0], 0, &iMajor);
|
2021-07-07 08:04:20 -07:00
|
|
|
ExitOnFailure(hr, "Invalid major version.");
|
2022-01-19 09:17:41 -08:00
|
|
|
hr = StrStringToInt32(rgsczVersionParts[1], 0, &iMinor);
|
2021-07-07 08:04:20 -07:00
|
|
|
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.
|
2022-01-19 09:17:41 -08:00
|
|
|
hr = StrStringToInt32(rgsczVersionParts[2], 0, &iPatch);
|
2021-07-07 08:04:20 -07:00
|
|
|
ExitOnFailure(hr, "Invalid patch version.");
|
|
|
|
|
|
|
|
if (100 > iPatch)
|
|
|
|
{
|
|
|
|
hr = E_INVALIDARG;
|
|
|
|
ExitOnFailure(hr, "Invalid SDK feature band and patch level.");
|
|
|
|
}
|
|
|
|
|
2022-01-19 09:17:41 -08:00
|
|
|
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);
|
2021-07-07 08:04:20 -07:00
|
|
|
|
|
|
|
LExit:
|
|
|
|
ReleaseStrArray(rgsczVersionParts, cVersionParts);
|
2022-01-19 09:17:41 -08:00
|
|
|
ReleaseStrArray(rgsczSemanticParts, cSemanticParts);
|
|
|
|
ReleaseStrArray(rgsczPrereleaseParts, cPrereleaseParts);
|
|
|
|
ReleaseStr(sczPrereleaseLabel);
|
2021-07-07 08:04:20 -07:00
|
|
|
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;
|
2022-01-19 09:17:41 -08:00
|
|
|
LPWSTR sczInstalledFeatureBand = NULL;
|
2021-07-07 08:04:20 -07:00
|
|
|
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.
|
2022-01-19 09:17:41 -08:00
|
|
|
hr = StrAllocFormatted(&sczInstalledSdkVersionsKeyName, L"SOFTWARE\\WOW6432Node\\dotnet\\Setup\\InstalledVersions\\%ls\\sdk", sczArchitecture);
|
|
|
|
ExitOnFailure(hr, "Failed to allocate string for installed SDK versions key name.");
|
2021-07-07 08:04:20 -07:00
|
|
|
|
|
|
|
LogStringLine(REPORT_STANDARD, "Scanning %ls", sczInstalledSdkVersionsKeyName);
|
|
|
|
|
|
|
|
hr = RegOpen(HKEY_LOCAL_MACHINE, sczInstalledSdkVersionsKeyName, KEY_READ, &hkInstalledSdkVersionsKey);
|
|
|
|
ExitOnFailure(hr, "Failed to read installed versions key.");
|
|
|
|
|
|
|
|
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.");
|
|
|
|
|
2022-01-19 09:17:41 -08:00
|
|
|
hr = ParseSdkVersion(sczSdkVersion, &sczInstalledFeatureBand);
|
2021-07-07 08:04:20 -07:00
|
|
|
ExitOnFailure(hr, "Failed to parse %ls", sczSdkVersion);
|
|
|
|
|
2022-01-19 09:17:41 -08:00
|
|
|
LogStringLine(REPORT_STANDARD, "SDK version detected: %ls, mapping to %ls.", sczSdkVersion, sczInstalledFeatureBand);
|
2021-07-07 08:04:20 -07:00
|
|
|
|
|
|
|
// Bail out on the first match.
|
2022-01-19 09:17:41 -08:00
|
|
|
if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, sczInstalledFeatureBand, -1, sczSdkFeatureBandVersion, -1))
|
2021-07-07 08:04:20 -07:00
|
|
|
{
|
|
|
|
*pbInstalled = TRUE;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
LExit:
|
|
|
|
ReleaseRegKey(hkInstalledSdkVersionsKey);
|
|
|
|
ReleaseStr(sczInstalledSdkVersionsKeyName);
|
|
|
|
ReleaseStr(sczSdkVersion);
|
2022-01-19 09:17:41 -08:00
|
|
|
ReleaseStr(sczInstalledFeatureBand);
|
2021-07-07 08:04:20 -07:00
|
|
|
return hr;
|
2021-05-04 20:55:39 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
int wmain(int argc, wchar_t* argv[])
|
|
|
|
{
|
2021-07-07 08:04:20 -07:00
|
|
|
HRESULT hr = S_OK;
|
|
|
|
DWORD dwExitCode = 0;
|
|
|
|
LPWSTR sczDependent = NULL;
|
|
|
|
LPWSTR sczFeatureBandVersion = 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.");
|
|
|
|
|
|
|
|
// Convert the full SDK version to a feature band version
|
2022-01-19 09:17:41 -08:00
|
|
|
hr = ParseSdkVersion(argv[2], &sczFeatureBandVersion);
|
2021-07-07 08:04:20 -07:00
|
|
|
ExitOnFailure(hr, "Failed to parse version, %ls.", argv[2]);
|
|
|
|
|
|
|
|
// Create the dependent value, e.g., Microsoft.NET.Sdk,6.0.300,arm64
|
2022-01-19 09:17:41 -08:00
|
|
|
hr = StrAllocFormatted(&sczDependent, L"Microsoft.NET.Sdk,%ls,%ls", sczFeatureBandVersion, argv[3]);
|
2021-07-07 08:04:20 -07:00
|
|
|
ExitOnFailure(hr, "Failed to create dependent.");
|
|
|
|
LogStringLine(REPORT_STANDARD, "Setting target dependent to %ls.", sczDependent);
|
|
|
|
|
|
|
|
hr = ::DetectSdk(sczFeatureBandVersion, argv[3], &bSdkFeatureBandInstalled);
|
|
|
|
ExitOnFailure(hr, "Failed to detect installed SDKs.");
|
|
|
|
|
2021-08-18 15:54:00 -07:00
|
|
|
if (!bSdkFeatureBandInstalled)
|
2021-07-07 08:04:20 -07:00
|
|
|
{
|
2021-09-15 10:33:50 -07:00
|
|
|
LogStringLine(REPORT_STANDARD, "SDK with feature band %ls could not be found.", sczFeatureBandVersion);
|
2021-07-07 08:04:20 -07:00
|
|
|
goto LExit;
|
|
|
|
}
|
2021-05-04 20:55:39 -07:00
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
hr = ::RemoveDependent(sczDependent, &bRestartRequired);
|
|
|
|
ExitOnFailure(hr, "Failed to remove dependent \"%ls\".", sczDependent);
|
2021-05-04 20:55:39 -07:00
|
|
|
|
2021-09-15 10:33:50 -07:00
|
|
|
hr = ::DeleteWorkloadRecords(sczFeatureBandVersion, argv[3]);
|
|
|
|
ExitOnFailure(hr, "Failed to remove workload records.");
|
|
|
|
|
2021-07-07 08:04:20 -07:00
|
|
|
if (bRestartRequired)
|
|
|
|
{
|
|
|
|
dwExitCode = ERROR_SUCCESS_REBOOT_REQUIRED;
|
|
|
|
}
|
2021-05-04 20:55:39 -07:00
|
|
|
|
|
|
|
LExit:
|
2021-07-07 08:04:20 -07:00
|
|
|
ReleaseStr(sczDependent);
|
|
|
|
ReleaseStr(sczFeatureBandVersion);
|
|
|
|
LogUninitialize(TRUE);
|
|
|
|
RegUninitialize();
|
|
|
|
WiuUninitialize();
|
|
|
|
return FAILED(hr) ? (int)hr : (int)dwExitCode;
|
2021-05-04 20:55:39 -07:00
|
|
|
}
|