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.
// 2. The full SDK version, e.g. 6.0.105 or 6.0.398-preview19
// 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
}
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 ;
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 ;
}
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 ;
}
extern " C " HRESULT ParseSdkVersion ( LPWSTR sczSdkVersion , INT * piMajor , INT * piMinor , INT * piFeatureBand )
{
HRESULT hr = S_OK ;
UINT cVersionParts = 0 ;
LPWSTR * rgsczVersionParts = NULL ;
int iPatch = 0 ;
hr = StrSplitAllocArray ( & rgsczVersionParts , & cVersionParts , sczSdkVersion , 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 , piMajor ) ;
ExitOnFailure ( hr , " Invalid major version. " ) ;
hr = StrStringToInt32 ( rgsczVersionParts [ 1 ] , 0 , piMinor ) ;
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 ] , 3 , & iPatch ) ;
ExitOnFailure ( hr , " Invalid patch version. " ) ;
if ( 100 > iPatch )
{
hr = E_INVALIDARG ;
ExitOnFailure ( hr , " Invalid SDK feature band and patch level. " ) ;
}
* piFeatureBand = iPatch - ( iPatch % 100 ) ;
LExit :
ReleaseStrArray ( rgsczVersionParts , cVersionParts ) ;
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 ;
DWORD dwSdkVersionValueType = 0 ;
int iInstalledMajor = 0 ;
int iInstalledMinor = 0 ;
int iInstalledFeatureBand = 0 ;
int iExpectedMajor = 0 ;
int iExpectedMinor = 0 ;
int iExpectedFeatureBand = 0 ;
hr = ParseSdkVersion ( sczSdkFeatureBandVersion , & iExpectedMajor , & iExpectedMinor , & iExpectedFeatureBand ) ;
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 = StrAllocConcatFormatted ( & sczInstalledSdkVersionsKeyName , L " SOFTWARE \\ WOW6432Node \\ dotnet \\ Setup \\ InstalledVersions \\ %ls \\ sdk " , sczArchitecture ) ;
ExitOnFailure ( hr , " Failed to allocate string for installed SDK versions. " ) ;
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. " ) ;
hr = ParseSdkVersion ( sczSdkVersion , & iInstalledMajor , & iInstalledMinor , & iInstalledFeatureBand ) ;
ExitOnFailure ( hr , " Failed to parse %ls " , sczSdkVersion ) ;
LogStringLine ( REPORT_STANDARD , " SDK version detected: %ls, mapping to %li.%li.%li. " , sczSdkVersion , iInstalledMajor , iInstalledMinor , iInstalledFeatureBand ) ;
// Bail out on the first match.
if ( ( iInstalledMajor = = iExpectedMajor ) & & ( iInstalledMinor = = iExpectedMinor ) & & ( iInstalledFeatureBand = = iExpectedFeatureBand ) )
{
* pbInstalled = TRUE ;
break ;
}
}
LExit :
ReleaseRegKey ( hkInstalledSdkVersionsKey ) ;
ReleaseStr ( sczInstalledSdkVersionsKeyName ) ;
ReleaseStr ( sczSdkVersion ) ;
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
hr = ParseSdkVersion ( argv [ 2 ] , & iMajor , & iMinor , & iFeatureBand ) ;
ExitOnFailure ( hr , " Failed to parse version, %ls. " , argv [ 2 ] ) ;
hr = StrAllocConcatFormatted ( & sczFeatureBandVersion , L " %li.%li.%li " , iMajor , iMinor , iFeatureBand ) ;
ExitOnFailure ( hr , " Failed to create feature band version. " ) ;
// Create the dependent value, e.g., Microsoft.NET.Sdk,6.0.300,arm64
hr = StrAllocConcatFormatted ( & sczDependent , L " Microsoft.NET.Sdk,%ls,%ls " , sczFeatureBandVersion , argv [ 3 ] ) ;
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
}