Browse Source

Add InstallLocation registry key support (#5705)

Pavel Krymets 7 years ago
parent
commit
422b3222d2

+ 4 - 33
src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/dllmain.cpp

@@ -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)
-    {
-        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);
-    }
+    auto fDisableModule = RegistryKey::TryGetDWORD(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\IIS Extensions\\IIS AspNetCore Module V2\\Parameters", L"DisableANCM");
 
-    if (fDisableANCM)
+    if (fDisableModule.has_value() && fDisableModule.value() != 0)
     {
-        // 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;

+ 2 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj

@@ -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" />

+ 20 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.cpp

@@ -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;
+}

+ 2 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/Environment.h

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

+ 55 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RegistryKey.cpp

@@ -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;
+}
+

+ 22 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RegistryKey.h

@@ -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);
+};

+ 24 - 17
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp

@@ -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);
 

+ 62 - 1
src/Servers/IIS/IIS/test/Common.FunctionalTests/Inprocess/StartupTests.cs

@@ -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)

+ 28 - 0
src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/TestRegistryKey.cs

@@ -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);
+        }
+    }
+}

+ 5 - 0
src/Servers/IIS/IntegrationTesting.IIS/src/IISDeployer.cs

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

+ 7 - 1
src/Servers/IIS/tools/SetupTestEnvironment.ps1

@@ -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;