Add InstallLocation registry key support (#5705)

This commit is contained in:
Pavel Krymets 2019-01-09 13:47:00 -08:00 committed by GitHub
parent 89ef215146
commit 422b3222d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 232 additions and 53 deletions

View File

@ -10,6 +10,7 @@
#include "resources.h"
#include "exceptions.h"
#include "EventLog.h"
#include "RegistryKey.h"
DECLARE_DEBUG_PRINT_OBJECT("aspnetcorev2.dll");
@ -88,9 +89,6 @@ HRESULT
--*/
{
HKEY hKey {};
BOOL fDisableANCM = FALSE;
UNREFERENCED_PARAMETER(dwServerVersion);
if (pHttpServer->IsCommandLineLaunch())
@ -102,38 +100,11 @@ HRESULT
g_hEventLog = RegisterEventSource(nullptr, ASPNETCORE_EVENT_PROVIDER);
}
// check whether the feature is disabled due to security reason
if (RegOpenKeyEx(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters",
0,
KEY_READ,
&hKey) == NO_ERROR)
auto fDisableModule = RegistryKey::TryGetDWORD(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", L"DisableANCM");
if (fDisableModule.has_value() && fDisableModule.value() != 0)
{
DWORD dwType = 0;
DWORD dwData = 0;
DWORD cbData;
cbData = sizeof(dwData);
if ((RegQueryValueEx(hKey,
L"DisableANCM",
nullptr,
&dwType,
(LPBYTE)&dwData,
&cbData) == NO_ERROR) &&
(dwType == REG_DWORD))
{
fDisableANCM = (dwData != 0);
}
RegCloseKey(hKey);
}
if (fDisableANCM)
{
// Logging
EventLog::Warn(
ASPNETCORE_EVENT_MODULE_DISABLED,
ASPNETCORE_EVENT_MODULE_DISABLED_MSG);
EventLog::Warn(ASPNETCORE_EVENT_MODULE_DISABLED, ASPNETCORE_EVENT_MODULE_DISABLED_MSG);
// this will return 500 error to client
// as we did not register the module
return S_OK;

View File

@ -225,6 +225,7 @@
<ClInclude Include="NonCopyable.h" />
<ClInclude Include="NullOutputManager.h" />
<ClInclude Include="PipeOutputManager.h" />
<ClInclude Include="RegistryKey.h" />
<ClInclude Include="requesthandler.h" />
<ClInclude Include="resources.h" />
<ClInclude Include="ServerErrorApplication.h" />
@ -253,6 +254,7 @@
<ClCompile Include="hostfxroptions.cpp" />
<ClCompile Include="LoggingHelpers.cpp" />
<ClCompile Include="PipeOutputManager.cpp" />
<ClCompile Include="RegistryKey.cpp" />
<ClCompile Include="StdWrapper.cpp" />
<ClCompile Include="SRWExclusiveLock.cpp" />
<ClCompile Include="SRWSharedLock.cpp" />

View File

@ -4,6 +4,7 @@
#include "Environment.h"
#include <Windows.h>
#include "exceptions.h"
std::wstring
Environment::ExpandEnvironmentVariables(const std::wstring & str)
@ -120,3 +121,22 @@ std::wstring Environment::GetDllDirectoryValue()
return expandedStr;
}
bool Environment::IsRunning64BitProcess()
{
// Check the bitness of the currently running process
// matches the dotnet.exe found.
BOOL fIsWow64Process = false;
THROW_LAST_ERROR_IF(!IsWow64Process(GetCurrentProcess(), &fIsWow64Process));
if (fIsWow64Process)
{
// 32 bit mode
return false;
}
// Check the SystemInfo to see if we are currently 32 or 64 bit.
SYSTEM_INFO systemInfo;
GetNativeSystemInfo(&systemInfo);
return systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64;
}

View File

@ -20,5 +20,7 @@ public:
std::wstring GetCurrentDirectoryValue();
static
std::wstring GetDllDirectoryValue();
static
bool IsRunning64BitProcess();
};

View File

@ -0,0 +1,55 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#include "RegistryKey.h"
#include "exceptions.h"
std::optional<DWORD> RegistryKey::TryGetDWORD(HKEY section, const std::wstring& subSectionName, const std::wstring& valueName, DWORD flags)
{
DWORD dwData = 0;
DWORD cbData = sizeof(dwData);
if (!CheckReturnValue(RegGetValue(section, subSectionName.c_str(), valueName.c_str(), RRF_RT_REG_DWORD | flags, nullptr, reinterpret_cast<LPBYTE>(&dwData), &cbData)))
{
return std::nullopt;
}
return dwData;
}
std::optional<std::wstring> RegistryKey::TryGetString(HKEY section, const std::wstring& subSectionName, const std::wstring& valueName, DWORD flags)
{
DWORD cbData;
if (!CheckReturnValue(RegGetValue(section, subSectionName.c_str(), valueName.c_str(), RRF_RT_REG_SZ | flags, nullptr, nullptr, &cbData) != NO_ERROR))
{
return std::nullopt;
}
std::wstring data;
data.resize(cbData / sizeof(wchar_t));
if (!CheckReturnValue(RegGetValue(section, subSectionName.c_str(), valueName.c_str(), RRF_RT_REG_SZ | flags, nullptr, data.data(), &cbData) != NO_ERROR))
{
return std::nullopt;
}
data.resize(cbData / sizeof(wchar_t) - 1);
return data;
}
bool RegistryKey::CheckReturnValue(int errorCode)
{
if (errorCode == NO_ERROR)
{
return true;
}
// NotFound result is expected, don't spam logs with failures
if (errorCode != ERROR_FILE_NOT_FOUND)
{
LOG_IF_FAILED(HRESULT_FROM_WIN32(errorCode));
}
return false;
}

View File

@ -0,0 +1,22 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
#pragma once
#include <optional>
#include "HandleWrapper.h"
class RegistryKey
{
public:
static
std::optional<DWORD> TryGetDWORD(HKEY section, const std::wstring& subSectionName, const std::wstring& valueName, DWORD flags = 0);
static
std::optional<std::wstring> TryGetString(HKEY section, const std::wstring& subSectionName, const std::wstring& valueName, DWORD flags = 0);
private:
static
bool
CheckReturnValue(int errorCode);
};

View File

@ -10,6 +10,7 @@
#include "HandleWrapper.h"
#include "Environment.h"
#include "StringHelpers.h"
#include "RegistryKey.h"
namespace fs = std::filesystem;
@ -257,6 +258,28 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
return dotnetViaWhere.value();
}
auto isWow64Process = Environment::IsRunning64BitProcess();
const auto platform = isWow64Process? L"x64" : L"x86";
const auto installationLocation = RegistryKey::TryGetString(
HKEY_LOCAL_MACHINE,
std::wstring(L"SOFTWARE\\dotnet\\Setup\\InstalledVersions\\") + platform + L"\\sdk",
L"InstallLocation",
RRF_SUBKEY_WOW6432KEY);
if (installationLocation.has_value())
{
LOG_INFOF(L"InstallLocation registry key is set to '%ls'", installationLocation.value().c_str());
auto const installationLocationDotnet = fs::path(installationLocation.value()) / "dotnet.exe";
if (is_regular_file(installationLocationDotnet))
{
LOG_INFOF(L"Found dotnet.exe in InstallLocation at '%ls'", installationLocationDotnet.c_str());
return installationLocationDotnet;
}
}
const auto programFilesLocation = GetAbsolutePathToDotnetFromProgramFiles();
if (programFilesLocation.has_value())
{
@ -328,7 +351,6 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
HandleWrapper<InvalidHandleTraits> hThread;
CComBSTR pwzDotnetName = NULL;
DWORD dwFilePointer;
BOOL fIsWow64Process;
BOOL fIsCurrentProcess64Bit;
DWORD dwExitCode;
STRU struDotnetSubstring;
@ -421,22 +443,7 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
LOG_INFOF(L"where.exe invocation returned: '%ls'", struDotnetLocationsString.QueryStr());
// Check the bitness of the currently running process
// matches the dotnet.exe found.
FINISHED_LAST_ERROR_IF (!IsWow64Process(GetCurrentProcess(), &fIsWow64Process));
if (fIsWow64Process)
{
// 32 bit mode
fIsCurrentProcess64Bit = FALSE;
}
else
{
// Check the SystemInfo to see if we are currently 32 or 64 bit.
SYSTEM_INFO systemInfo;
GetNativeSystemInfo(&systemInfo);
fIsCurrentProcess64Bit = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64;
}
fIsCurrentProcess64Bit = Environment::IsRunning64BitProcess();
LOG_INFOF(L"Current process bitness type detected as isX64=%d", fIsCurrentProcess64Bit);

View File

@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Server.IntegrationTesting;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
using Microsoft.AspNetCore.Testing.xunit;
using Newtonsoft.Json;
using Microsoft.Win32;
using Xunit;
namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
@ -107,6 +107,67 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal(1, TestSink.Writes.Count(w => w.Message.Contains("Invoking where.exe to find dotnet.exe")));
}
[ConditionalTheory]
[InlineData(RuntimeArchitecture.x64)]
[InlineData(RuntimeArchitecture.x86)]
[RequiresNewShim]
[RequiresIIS(IISCapability.PoolEnvironmentVariables)]
public async Task StartsWithDotnetInstallLocation(RuntimeArchitecture runtimeArchitecture)
{
var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true);
deploymentParameters.RuntimeArchitecture = runtimeArchitecture;
// IIS doesn't allow empty PATH
deploymentParameters.EnvironmentVariables["PATH"] = ".";
deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "dotnet"));
// Key is always in 32bit view
using (var localMachine = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32))
{
var installDir = DotNetCommands.GetDotNetInstallDir(runtimeArchitecture);
using (new TestRegistryKey(
localMachine,
"SOFTWARE\\dotnet\\Setup\\InstalledVersions\\" + runtimeArchitecture + "\\sdk",
"InstallLocation",
installDir))
{
var deploymentResult = await DeployAsync(deploymentParameters);
await deploymentResult.AssertStarts();
StopServer();
// Verify that in this scenario dotnet.exe was found using InstallLocation lookup
// I would've liked to make a copy of dotnet directory in this test and use it for verification
// but dotnet roots are usually very large on dev machines so this test would take disproportionally long time and disk space
Assert.Equal(1, TestSink.Writes.Count(w => w.Message.Contains($"Found dotnet.exe in InstallLocation at '{installDir}\\dotnet.exe'")));
}
}
}
[ConditionalFact]
[RequiresIIS(IISCapability.PoolEnvironmentVariables)]
public async Task DoesNotStartIfDisabled()
{
var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true);
using (new TestRegistryKey(
Registry.LocalMachine,
"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters",
"DisableANCM",
1))
{
var deploymentResult = await DeployAsync(deploymentParameters);
// Disabling ANCM produces no log files
deploymentResult.AllowNoLogs();
var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld");
Assert.False(response.IsSuccessStatusCode);
StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, "AspNetCore Module is disabled");
}
}
public static TestMatrix TestVariants
=> TestMatrix.ForServers(DeployerSelector.ServerType)
.WithTfms(Tfm.NetCoreApp30)

View File

@ -0,0 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.Win32;
namespace Microsoft.AspNetCore.Server.IntegrationTesting
{
public class TestRegistryKey : IDisposable
{
private readonly RegistryKey _baseHive;
private readonly RegistryKey _subKey;
private readonly string _keyName;
public TestRegistryKey(RegistryKey baseHive, string keyName, string valueName, object value)
{
_baseHive = baseHive;
_keyName = keyName;
_subKey = baseHive.CreateSubKey(keyName);
_subKey.SetValue(valueName, value);
}
public void Dispose()
{
_baseHive.DeleteSubKeyTree(_keyName, throwOnMissingSubKey: true);
}
}
}

View File

@ -325,6 +325,11 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting.IIS
}
if (DeploymentParameters.RuntimeArchitecture == RuntimeArchitecture.x86)
{
pool.SetAttributeValue("enable32BitAppOnWin64", "true");;
}
RunServerConfigActions(config, contentRoot);
}

View File

@ -1,5 +1,11 @@
param($Mode)
# TEMP TEMP TEMP
# While doing https://github.com/aspnet/AspNetCore/pull/5705 I accidentally disabled ANCM on CI machines using
# the registy key. Remove it to allow tests to pass
Remove-Item "HKLM:\SOFTWARE\Microsoft\IIS Extensions\IIS AspNetCore Module V2\Parameters" -ErrorAction Ignore;
$DumpFolder = "$env:ASPNETCORE_TEST_LOG_DIR\dumps"
if (!($DumpFolder))
{
@ -70,7 +76,7 @@ function Setup-Dumps()
New-Item -Path $werHive -Name LocalDumps
}
Move-Item $env:windir\System32\vsjitdebugger.exe $env:windir\System32\_vsjitdebugger.exe;
Move-Item $env:windir\System32\vsjitdebugger.exe $env:windir\System32\_vsjitdebugger.exe -ErrorAction Ignore;
New-ItemProperty $werHive -Name "DontShowUI" -Value 1 -PropertyType "DWORD" -Force;