Просмотр исходного кода

Use hostfxr error callback support (#6043)

Pavel Krymets 7 лет назад
Родитель
Сommit
5299eff616
42 измененных файлов с 1098 добавлено и 983 удалено
  1. 55 71
      src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp
  2. 9 3
      src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h
  3. 1 1
      src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp
  4. 1 1
      src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h
  5. 0 68
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h
  6. 10 11
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj
  7. 0 178
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp
  8. 0 30
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.h
  9. 70 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.cpp
  10. 57 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h
  11. 77 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.cpp
  12. 53 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.h
  13. 10 10
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp
  14. 1 5
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h
  15. 0 27
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/IOutputManager.h
  16. 11 45
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp
  17. 8 9
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h
  18. 21 5
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h
  19. 0 29
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/NullOutputManager.h
  20. 0 38
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h
  21. 131 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.cpp
  22. 96 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.h
  23. 23 58
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.cpp
  24. 66 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.h
  25. 11 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp
  26. 3 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.h
  27. 35 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/exceptions.h
  28. 2 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLib/stdafx.h
  29. 1 2
      src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj
  30. 0 152
      src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/FileOutputManagerTests.cpp
  31. 0 162
      src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/PipeOutputManagerTests.cpp
  32. 252 0
      src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/StandardOutputRedirectionTest.cpp
  33. 7 7
      src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/hostfxr_utility_tests.cpp
  34. 1 1
      src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/stdafx.h
  35. 0 2
      src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h
  36. 60 50
      src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp
  37. 11 5
      src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h
  38. 0 1
      src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp
  39. 2 2
      src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs
  40. 12 6
      src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs
  41. 0 2
      src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs
  42. 1 2
      src/Servers/IIS/tools/SetupTestEnvironment.ps1

+ 55 - 71
src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp

@@ -6,17 +6,15 @@
 #include "SRWExclusiveLock.h"
 #include "applicationinfo.h"
 #include "EventLog.h"
-#include "hostfxr_utility.h"
 #include "GlobalVersionUtility.h"
 #include "HandleWrapper.h"
 #include "file_utility.h"
 #include "LoggingHelpers.h"
 #include "resources.h"
-#include "ConfigurationLoadException.h"
-#include "WebConfigConfigurationSource.h"
 #include "ModuleHelpers.h"
-#include "BaseOutputManager.h"
 #include "Environment.h"
+#include "HostFxr.h"
+#include "RedirectionOutput.h"
 
 const PCWSTR HandlerResolver::s_pwzAspnetcoreInProcessRequestHandlerName = L"aspnetcorev2_inprocess.dll";
 const PCWSTR HandlerResolver::s_pwzAspnetcoreOutOfProcessRequestHandlerName = L"aspnetcorev2_outofprocess.dll";
@@ -55,10 +53,9 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
     {
         if (pConfiguration.QueryHostingModel() == APP_HOSTING_MODEL::HOSTING_IN_PROCESS)
         {
-            std::unique_ptr<HOSTFXR_OPTIONS> options;
-            std::unique_ptr<BaseOutputManager> outputManager;
+            std::unique_ptr<HostFxrResolutionResult> options;
 
-            RETURN_IF_FAILED(HOSTFXR_OPTIONS::Create(
+            RETURN_IF_FAILED(HostFxrResolutionResult::Create(
                 L"",
                 pConfiguration.QueryProcessPath(),
                 pApplication.GetApplicationPhysicalPath(),
@@ -67,19 +64,13 @@ HandlerResolver::LoadRequestHandlerAssembly(const IHttpApplication &pApplication
 
             location = options->GetDotnetExeLocation();
 
-            RETURN_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
-                pConfiguration.QueryStdoutLogEnabled(),
-                !m_pServer.IsCommandLineLaunch(),
-                pConfiguration.QueryStdoutLogFile().c_str(),
-                pApplication.GetApplicationPhysicalPath(),
-                outputManager));
-
+            auto redirectionOutput = std::make_shared<StringStreamRedirectionOutput>();
 
-            hr = FindNativeAssemblyFromHostfxr(*options.get(), pstrHandlerDllName, handlerDllPath, outputManager.get());
+            hr = FindNativeAssemblyFromHostfxr(*options, pstrHandlerDllName, handlerDllPath, pApplication, pConfiguration, redirectionOutput);
 
             if (FAILED_LOG(hr))
             {
-                auto output = outputManager->GetStdOutContent();
+                auto output = redirectionOutput->GetOutput();
 
                 EventLog::Error(
                     ASPNETCORE_EVENT_GENERAL_ERROR,
@@ -208,83 +199,76 @@ HandlerResolver::FindNativeAssemblyFromGlobalLocation(
 //
 HRESULT
 HandlerResolver::FindNativeAssemblyFromHostfxr(
-    const HOSTFXR_OPTIONS& hostfxrOptions,
+    const HostFxrResolutionResult& hostfxrOptions,
     PCWSTR libraryName,
     std::wstring& handlerDllPath,
-    BaseOutputManager* outputManager
+    const IHttpApplication &pApplication,
+    const ShimOptions& pConfiguration,
+    std::shared_ptr<RedirectionOutput> stringRedirectionOutput
 )
+try
 {
     std::wstring   struNativeSearchPaths;
     size_t         intIndex = 0;
     size_t         intPrevIndex = 0;
     DWORD          dwBufferSize = s_initialGetNativeSearchDirectoriesBufferSize;
     DWORD          dwRequiredBufferSize = 0;
-    hostfxr_get_native_search_directories_fn pFnHostFxrSearchDirectories = nullptr;
 
     RETURN_LAST_ERROR_IF_NULL(m_hHostFxrDll = LoadLibraryW(hostfxrOptions.GetHostFxrLocation().c_str()));
 
-    try
-    {
-        pFnHostFxrSearchDirectories = ModuleHelpers::GetKnownProcAddress<hostfxr_get_native_search_directories_fn>(m_hHostFxrDll, "hostfxr_get_native_search_directories");
-    }
-    catch (...)
-    {
-        EventLog::Error(
-            ASPNETCORE_EVENT_GENERAL_ERROR,
-            ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG,
-            hostfxrOptions.GetHostFxrLocation().c_str()
-        );
-        return OBSERVE_CAUGHT_EXCEPTION();
-    }
-
-    RETURN_LAST_ERROR_IF_NULL(pFnHostFxrSearchDirectories);
-    struNativeSearchPaths.resize(dwBufferSize);
+    auto const hostFxr = HostFxr::CreateFromLoadedModule();
 
-    outputManager->TryStartRedirection();
-
-    while (TRUE)
     {
-        DWORD                       hostfxrArgc;
-        std::unique_ptr<PCWSTR[]>   hostfxrArgv;
-
-        hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv);
-        const auto intHostFxrExitCode = pFnHostFxrSearchDirectories(
-            hostfxrArgc,
-            hostfxrArgv.get(),
-            struNativeSearchPaths.data(),
-            dwBufferSize,
-            &dwRequiredBufferSize
-        );
+        auto redirectionOutput = LoggingHelpers::CreateOutputs(
+                pConfiguration.QueryStdoutLogEnabled(),
+                pConfiguration.QueryStdoutLogFile(),
+                pApplication.GetApplicationPhysicalPath(),
+                std::move(stringRedirectionOutput)
+            );
 
-        if (intHostFxrExitCode == 0)
-        {
-            break;
-        }
-        else if (dwRequiredBufferSize > dwBufferSize)
-        {
-            dwBufferSize = dwRequiredBufferSize + 1; // for null terminator
+        StandardStreamRedirection stdOutRedirection(*redirectionOutput.get(), m_pServer.IsCommandLineLaunch());
+        auto hostFxrErrorRedirection = hostFxr.RedirectOutput(redirectionOutput.get());
 
-            struNativeSearchPaths.resize(dwBufferSize);
-        }
-        else
+        struNativeSearchPaths.resize(dwBufferSize);
+        while (TRUE)
         {
-            // Stop redirecting before logging to event log to avoid logging debug logs
-            // twice.
-            outputManager->TryStopRedirection();
+            DWORD                       hostfxrArgc;
+            std::unique_ptr<PCWSTR[]>   hostfxrArgv;
 
-            // If hostfxr didn't set the required buffer size, something in the app is misconfigured
-            // Ex: Framework not found.
-            EventLog::Error(
-                ASPNETCORE_EVENT_GENERAL_ERROR,
-                ASPNETCORE_EVENT_HOSTFXR_FAILURE_MSG
+            hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv);
+
+            const auto intHostFxrExitCode = hostFxr.GetNativeSearchDirectories(
+                hostfxrArgc,
+                hostfxrArgv.get(),
+                struNativeSearchPaths.data(),
+                dwBufferSize,
+                &dwRequiredBufferSize
             );
 
-            return E_UNEXPECTED;
+            if (intHostFxrExitCode == 0)
+            {
+                break;
+            }
+            else if (dwRequiredBufferSize > dwBufferSize)
+            {
+                dwBufferSize = dwRequiredBufferSize + 1; // for null terminator
+
+                struNativeSearchPaths.resize(dwBufferSize);
+            }
+            else
+            {
+                // If hostfxr didn't set the required buffer size, something in the app is misconfigured
+                // Ex: Framework not found.
+                EventLog::Error(
+                    ASPNETCORE_EVENT_GENERAL_ERROR,
+                    ASPNETCORE_EVENT_HOSTFXR_FAILURE_MSG
+                );
+
+                return E_UNEXPECTED;
+            }
         }
     }
 
-    outputManager->TryStopRedirection();
-
     struNativeSearchPaths.resize(struNativeSearchPaths.find(L'\0'));
 
     auto fFound = FALSE;
@@ -323,4 +307,4 @@ HandlerResolver::FindNativeAssemblyFromHostfxr(
 
     return S_OK;
 }
-
+CATCH_RETURN()

+ 9 - 3
src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/HandlerResolver.h

@@ -6,10 +6,10 @@
 #include <memory>
 #include <string>
 #include "ShimOptions.h"
-#include "hostfxroptions.h"
+#include "HostFxrResolutionResult.h"
 #include "HandleWrapper.h"
 #include "ApplicationFactory.h"
-#include "BaseOutputManager.h"
+#include "RedirectionOutput.h"
 
 class HandlerResolver
 {
@@ -21,7 +21,13 @@ public:
 private:
     HRESULT LoadRequestHandlerAssembly(const IHttpApplication &pApplication, const ShimOptions& pConfiguration, std::unique_ptr<ApplicationFactory>& pApplicationFactory);
     HRESULT FindNativeAssemblyFromGlobalLocation(const ShimOptions& pConfiguration, PCWSTR libraryName, std::wstring& handlerDllPath);
-    HRESULT FindNativeAssemblyFromHostfxr(const HOSTFXR_OPTIONS& hostfxrOptions, PCWSTR libraryName, std::wstring& handlerDllPath, BaseOutputManager* outputManager);
+    HRESULT FindNativeAssemblyFromHostfxr(
+        const HostFxrResolutionResult& hostfxrOptions,
+        PCWSTR libraryName,
+        std::wstring& handlerDllPath,
+        const IHttpApplication &pApplication,
+        const ShimOptions& pConfiguration,
+        std::shared_ptr<RedirectionOutput> stringRedirectionOutput);
 
     HMODULE m_hModule;
     const IHttpServer &m_pServer;

+ 1 - 1
src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp

@@ -4,7 +4,7 @@
 #include "applicationinfo.h"
 
 #include "proxymodule.h"
-#include "hostfxr_utility.h"
+#include "HostFxrResolver.h"
 #include "debugutil.h"
 #include "resources.h"
 #include "SRWExclusiveLock.h"

+ 1 - 1
src/Servers/IIS/AspNetCoreModuleV2/AspNetCore/applicationinfo.h

@@ -3,7 +3,7 @@
 
 #pragma once
 
-#include "hostfxroptions.h"
+#include "HostFxrResolutionResult.h"
 #include "iapplication.h"
 #include "SRWSharedLock.h"
 #include "HandlerResolver.h"

+ 0 - 68
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/BaseOutputManager.h

@@ -1,68 +0,0 @@
-// 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 "IOutputManager.h"
-#include "StdWrapper.h"
-#include "EventLog.h"
-#include "exceptions.h"
-#include "StringHelpers.h"
-#include "debugutil.h"
-
-class BaseOutputManager :
-    public IOutputManager
-{
-public:
-    BaseOutputManager() : BaseOutputManager(/* fEnableNativeLogging */ true) {}
-    BaseOutputManager(bool enableNativeLogging) :
-        m_disposed(false),
-        stdoutWrapper(nullptr),
-        stderrWrapper(nullptr),
-        m_enableNativeRedirection(enableNativeLogging)
-    {
-        InitializeSRWLock(&m_srwLock);
-    }
-    ~BaseOutputManager() {}
-
-    void
-    TryStartRedirection()
-    {
-        const auto startLambda = [&]() { this->Start(); };
-        TryOperation(startLambda, L"Could not start stdout redirection in %s. Exception message: %s.");
-    }
-
-    void
-    TryStopRedirection()
-    {
-        const auto stopLambda = [&]() { this->Stop(); };
-        TryOperation(stopLambda, L"Could not stop stdout redirection in %s. Exception message: %s.");
-    }
-
-protected:
-    std::wstring m_stdOutContent;
-    bool m_disposed;
-    bool m_enableNativeRedirection;
-    SRWLOCK m_srwLock{};
-    std::unique_ptr<StdWrapper> stdoutWrapper;
-    std::unique_ptr<StdWrapper> stderrWrapper;
-
-    template<typename Functor>
-    void
-    TryOperation(Functor func,
-        std::wstring exceptionMessage)
-    {
-        try
-        {
-            func();
-        }
-        catch (const std::runtime_error& exception)
-        {
-            EventLog::Warn(ASPNETCORE_EVENT_GENERAL_WARNING, exceptionMessage.c_str(), GetModuleName().c_str(), to_wide_string(exception.what(), GetConsoleOutputCP()).c_str());
-        }
-        catch (...)
-        {
-            OBSERVE_CAUGHT_EXCEPTION();
-        }
-    }
-};
-

+ 10 - 11
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/CommonLib.vcxproj

@@ -199,7 +199,6 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClInclude Include="application.h" />
-    <ClInclude Include="baseoutputmanager.h" />
     <ClInclude Include="BindingInformation.h" />
     <ClInclude Include="ConfigurationSection.h" />
     <ClInclude Include="ConfigurationSource.h" />
@@ -209,22 +208,21 @@
     <ClInclude Include="EventTracing.h" />
     <ClInclude Include="exceptions.h" />
     <ClInclude Include="file_utility.h" />
-    <ClInclude Include="FileOutputManager.h" />
     <ClInclude Include="GlobalVersionUtility.h" />
     <ClInclude Include="fx_ver.h" />
     <ClInclude Include="HandleWrapper.h" />
-    <ClInclude Include="hostfxroptions.h" />
-    <ClInclude Include="hostfxr_utility.h" />
+    <ClInclude Include="HostFxr.h" />
+    <ClInclude Include="HostFxrResolutionResult.h" />
+    <ClInclude Include="HostFxrResolver.h" />
     <ClInclude Include="iapplication.h" />
     <ClInclude Include="debugutil.h" />
     <ClInclude Include="InvalidOperationException.h" />
-    <ClInclude Include="IOutputManager.h" />
+    <ClInclude Include="RedirectionOutput.h" />
     <ClInclude Include="irequesthandler.h" />
     <ClInclude Include="LoggingHelpers.h" />
     <ClInclude Include="ModuleHelpers.h" />
     <ClInclude Include="NonCopyable.h" />
-    <ClInclude Include="NullOutputManager.h" />
-    <ClInclude Include="PipeOutputManager.h" />
+    <ClInclude Include="StandardStreamRedirection.h" />
     <ClInclude Include="RegistryKey.h" />
     <ClInclude Include="requesthandler.h" />
     <ClInclude Include="resources.h" />
@@ -246,14 +244,15 @@
     <ClCompile Include="Environment.cpp" />
     <ClCompile Include="EventLog.cpp" />
     <ClCompile Include="file_utility.cpp" />
-    <ClCompile Include="FileOutputManager.cpp" />
     <ClCompile Include="fx_ver.cpp" />
     <ClCompile Include="GlobalVersionUtility.cpp" />
     <ClCompile Include="HandleWrapper.cpp" />
-    <ClCompile Include="hostfxr_utility.cpp" />
-    <ClCompile Include="hostfxroptions.cpp" />
+    <ClCompile Include="HostFxr.cpp" />
+    <ClCompile Include="HostFxrResolver.cpp" />
+    <ClCompile Include="HostFxrResolutionResult.cpp" />
     <ClCompile Include="LoggingHelpers.cpp" />
-    <ClCompile Include="PipeOutputManager.cpp" />
+    <ClCompile Include="StandardStreamRedirection.cpp" />
+    <ClCompile Include="RedirectionOutput.cpp" />
     <ClCompile Include="RegistryKey.cpp" />
     <ClCompile Include="StdWrapper.cpp" />
     <ClCompile Include="SRWExclusiveLock.cpp" />

+ 0 - 178
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.cpp

@@ -1,178 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the MIT License. See License.txt in the project root for license information.
-
-#include "stdafx.h"
-#include "FileOutputManager.h"
-#include "sttimer.h"
-#include "exceptions.h"
-#include "debugutil.h"
-#include "SRWExclusiveLock.h"
-#include "file_utility.h"
-#include "StdWrapper.h"
-#include "StringHelpers.h"
-
-FileOutputManager::FileOutputManager(std::wstring pwzStdOutLogFileName, std::wstring  pwzApplicationPath) :
-    FileOutputManager(pwzStdOutLogFileName, pwzApplicationPath, /* fEnableNativeLogging */ true) { }
-
-FileOutputManager::FileOutputManager(std::wstring  pwzStdOutLogFileName, std::wstring  pwzApplicationPath, bool fEnableNativeLogging) :
-    BaseOutputManager(fEnableNativeLogging),
-    m_hLogFileHandle(INVALID_HANDLE_VALUE),
-    m_applicationPath(pwzApplicationPath),
-    m_stdOutLogFileName(pwzStdOutLogFileName)
-{
-    InitializeSRWLock(&m_srwLock);
-}
-
-FileOutputManager::~FileOutputManager()
-{
-    FileOutputManager::Stop();
-}
-
-// Start redirecting stdout and stderr into the file handle.
-// Uses sttimer to continuously flush output into the file.
-void
-FileOutputManager::Start()
-{
-    SYSTEMTIME systemTime;
-    SECURITY_ATTRIBUTES saAttr = { 0 };
-    FILETIME processCreationTime;
-    FILETIME dummyFileTime;
-    
-    // To make Console.* functions work, allocate a console
-    // in the current process.
-    if (!AllocConsole())
-    {
-        THROW_LAST_ERROR_IF(GetLastError() != ERROR_ACCESS_DENIED);
-    }
-
-    // Concatenate the log file name and application path
-    auto logPath = m_applicationPath / m_stdOutLogFileName;
-    create_directories(logPath.parent_path());
-
-    THROW_LAST_ERROR_IF(!GetProcessTimes(
-        GetCurrentProcess(), 
-        &processCreationTime, 
-        &dummyFileTime, 
-        &dummyFileTime, 
-        &dummyFileTime));
-
-    THROW_LAST_ERROR_IF(!FileTimeToSystemTime(&processCreationTime, &systemTime));
-
-    m_logFilePath = format(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
-        logPath.c_str(),
-        systemTime.wYear,
-        systemTime.wMonth,
-        systemTime.wDay,
-        systemTime.wHour,
-        systemTime.wMinute,
-        systemTime.wSecond,
-        GetCurrentProcessId());
-
-    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
-    saAttr.bInheritHandle = TRUE;
-    saAttr.lpSecurityDescriptor = NULL;
-
-    // Create the file with both READ and WRITE.
-    m_hLogFileHandle = CreateFileW(m_logFilePath.c_str(),
-        FILE_READ_DATA | FILE_WRITE_DATA,
-        FILE_SHARE_READ,
-        &saAttr,
-        CREATE_ALWAYS,
-        FILE_ATTRIBUTE_NORMAL,
-        nullptr);
-
-    THROW_LAST_ERROR_IF(m_hLogFileHandle == INVALID_HANDLE_VALUE);
-
-    stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, m_hLogFileHandle, m_enableNativeRedirection);
-    stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, m_hLogFileHandle, m_enableNativeRedirection);
-
-    stdoutWrapper->StartRedirection();
-    stderrWrapper->StartRedirection();
-}
-
-void
-FileOutputManager::Stop()
-{
-    CHAR            pzFileContents[MAX_FILE_READ_SIZE] = { 0 };
-    DWORD           dwNumBytesRead;
-    LARGE_INTEGER   li = { 0 };
-    DWORD           dwFilePointer = 0;
-    HANDLE   handle = NULL;
-    WIN32_FIND_DATA fileData;
-
-    if (m_disposed)
-    {
-        return;
-    }
-
-    SRWExclusiveLock lock(m_srwLock);
-
-    if (m_disposed)
-    {
-        return;
-    }
-
-    m_disposed = true;
-
-    if (m_hLogFileHandle == INVALID_HANDLE_VALUE)
-    {
-        THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND));
-    }
-
-    FlushFileBuffers(m_hLogFileHandle);
-
-    if (stdoutWrapper != nullptr)
-    {
-        THROW_IF_FAILED(stdoutWrapper->StopRedirection());
-    }
-
-    if (stderrWrapper != nullptr)
-    {
-        THROW_IF_FAILED(stderrWrapper->StopRedirection());
-    }
-
-    // delete empty log file
-    handle = FindFirstFile(m_logFilePath.c_str(), &fileData);
-    if (handle != INVALID_HANDLE_VALUE &&
-        handle != NULL &&
-        fileData.nFileSizeHigh == 0 &&
-        fileData.nFileSizeLow == 0) // skip check of nFileSizeHigh
-    {
-        FindClose(handle);
-        LOG_LAST_ERROR_IF(!DeleteFile(m_logFilePath.c_str()));
-        return;
-    }
-
-    // Read the first 30Kb from the file and store it in a buffer.
-    // By doing this, we can close the handle to the file and be done with it.
-    THROW_LAST_ERROR_IF(!GetFileSizeEx(m_hLogFileHandle, &li));
-
-    if (li.HighPart > 0)
-    {
-        THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_INVALID));
-    }
-
-    dwFilePointer = SetFilePointer(m_hLogFileHandle, 0, NULL, FILE_BEGIN);
-
-    THROW_LAST_ERROR_IF(dwFilePointer == INVALID_SET_FILE_POINTER);
-
-    THROW_LAST_ERROR_IF(!ReadFile(m_hLogFileHandle, pzFileContents, MAX_FILE_READ_SIZE, &dwNumBytesRead, NULL));
-
-    m_stdOutContent = to_wide_string(std::string(pzFileContents, dwNumBytesRead), GetConsoleOutputCP());
-
-    auto content = GetStdOutContent();
-    if (!content.empty())
-    {
-        // printf will fail in in full IIS
-        if (wprintf(content.c_str()) != -1)
-        {
-            // Need to flush contents for the new stdout and stderr
-            _flushall();
-        }
-    }
-}
-
-std::wstring FileOutputManager::GetStdOutContent()
-{
-    return m_stdOutContent;
-}

+ 0 - 30
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/FileOutputManager.h

@@ -1,30 +0,0 @@
-// 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 "sttimer.h"
-#include "HandleWrapper.h"
-#include "StdWrapper.h"
-#include "stringa.h"
-#include "stringu.h"
-#include "BaseOutputManager.h"
-
-class FileOutputManager : public BaseOutputManager
-{
-    #define MAX_FILE_READ_SIZE 30000
-public:
-    FileOutputManager(std::wstring pwzApplicationPath, std::wstring pwzStdOutLogFileName);
-    FileOutputManager(std::wstring pwzApplicationPath, std::wstring pwzStdOutLogFileName, bool fEnableNativeLogging);
-    ~FileOutputManager();
-
-    virtual std::wstring GetStdOutContent() override;
-    void Start() override;
-    void Stop() override;
-
-private:
-    HandleWrapper<InvalidHandleTraits> m_hLogFileHandle;
-    std::wstring m_stdOutLogFileName;
-    std::filesystem::path m_applicationPath;
-    std::filesystem::path m_logFilePath;
-};

+ 70 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.cpp

@@ -0,0 +1,70 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+#include "HostFxr.h"
+
+#include "ModuleHelpers.h"
+#include "EventLog.h"
+
+HostFxrErrorRedirector::HostFxrErrorRedirector(corehost_set_error_writer_fn setErrorWriterFn, RedirectionOutput* writeFunction) noexcept
+    : m_setErrorWriter(setErrorWriterFn)
+{
+    if (m_setErrorWriter)
+    {
+        m_writeFunction = writeFunction;
+        m_setErrorWriter(HostFxrErrorRedirectorCallback);
+    }
+}
+
+HostFxrErrorRedirector::~HostFxrErrorRedirector()
+{
+    if (m_setErrorWriter)
+    {
+        m_setErrorWriter(nullptr);
+        m_writeFunction = nullptr;
+    }
+}
+
+void HostFxrErrorRedirector::HostFxrErrorRedirectorCallback(const WCHAR* message)
+{
+    m_writeFunction->Append(std::wstring(message) + L"\r\n");
+}
+
+int HostFxr::Main(DWORD argc, const PCWSTR* argv) const noexcept(false)
+{
+    return m_hostfxr_main_fn(argc, argv);
+}
+
+int HostFxr::GetNativeSearchDirectories(INT argc, const PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size) const noexcept
+{
+    return m_hostfxr_get_native_search_directories_fn(argc, argv, buffer, buffer_size, required_buffer_size);
+}
+
+HostFxrErrorRedirector HostFxr::RedirectOutput(RedirectionOutput* writer) const noexcept
+{
+    return HostFxrErrorRedirector(m_corehost_set_error_writer_fn, writer);
+}
+
+HostFxr HostFxr::CreateFromLoadedModule()
+{
+    HMODULE hModule;
+    THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll"));
+
+    try
+    {
+        return HostFxr(
+            ModuleHelpers::GetKnownProcAddress<hostfxr_main_fn>(hModule, "hostfxr_main"),
+            ModuleHelpers::GetKnownProcAddress<hostfxr_get_native_search_directories_fn>(hModule, "hostfxr_get_native_search_directories"),
+            ModuleHelpers::GetKnownProcAddress<corehost_set_error_writer_fn>(hModule, "hostfxr_set_error_writer", /* optional */ true));
+    }
+    catch (...)
+    {
+        EventLog::Error(
+            ASPNETCORE_EVENT_GENERAL_ERROR,
+            ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG,
+            ModuleHelpers::GetModuleFileNameValue(hModule).c_str()
+        );
+
+        throw;
+    }
+}

+ 57 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxr.h

@@ -0,0 +1,57 @@
+// 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 "HostFxrResolver.h"
+#include "exceptions.h"
+#include "RedirectionOutput.h"
+
+typedef INT(*hostfxr_get_native_search_directories_fn) (INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size);
+typedef INT(*hostfxr_main_fn) (DWORD argc, CONST PCWSTR argv[]);
+typedef void(*corehost_error_writer_fn) (const WCHAR* message);
+typedef corehost_error_writer_fn(*corehost_set_error_writer_fn) (corehost_error_writer_fn error_writer);
+
+class HostFxrErrorRedirector: NonCopyable
+{
+public:
+    HostFxrErrorRedirector(corehost_set_error_writer_fn setErrorWriterFn, RedirectionOutput* writeFunction) noexcept;
+
+    ~HostFxrErrorRedirector();
+
+    static void HostFxrErrorRedirectorCallback(const WCHAR* message);
+
+private:
+    corehost_set_error_writer_fn m_setErrorWriter;
+    static inline thread_local RedirectionOutput* m_writeFunction;
+};
+
+class HostFxr
+{
+public:
+    HostFxr(
+        hostfxr_main_fn hostfxr_main_fn,
+        hostfxr_get_native_search_directories_fn hostfxr_get_native_search_directories_fn,
+        corehost_set_error_writer_fn corehost_set_error_writer_fn) noexcept
+        : m_hostfxr_main_fn(hostfxr_main_fn),
+          m_hostfxr_get_native_search_directories_fn(hostfxr_get_native_search_directories_fn),
+          m_corehost_set_error_writer_fn(corehost_set_error_writer_fn)
+    {
+    }
+
+    ~HostFxr() = default;
+
+    int Main(DWORD argc, CONST PCWSTR* argv) const noexcept(false);
+
+    int GetNativeSearchDirectories(INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size) const noexcept;
+
+    HostFxrErrorRedirector RedirectOutput(RedirectionOutput* writer) const noexcept;
+
+    static
+    HostFxr CreateFromLoadedModule();
+
+private:
+    hostfxr_main_fn m_hostfxr_main_fn;
+    hostfxr_get_native_search_directories_fn m_hostfxr_get_native_search_directories_fn;
+    corehost_set_error_writer_fn m_corehost_set_error_writer_fn;
+};

+ 77 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.cpp

@@ -0,0 +1,77 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+#include "HostFxrResolutionResult.h"
+
+#include "HostFxrResolver.h"
+#include "debugutil.h"
+#include "exceptions.h"
+#include "EventLog.h"
+
+void HostFxrResolutionResult::GetArguments(DWORD& hostfxrArgc, std::unique_ptr<PCWSTR[]>& hostfxrArgv) const
+{
+    hostfxrArgc = static_cast<DWORD>(m_arguments.size());
+    hostfxrArgv = std::make_unique<PCWSTR[]>(hostfxrArgc);
+    for (DWORD i = 0; i < hostfxrArgc; ++i)
+    {
+        hostfxrArgv[i] = m_arguments[i].c_str();
+    }
+}
+
+HRESULT HostFxrResolutionResult::Create(
+        _In_ const std::wstring& pcwzDotnetExePath,
+        _In_ const std::wstring& pcwzProcessPath,
+        _In_ const std::wstring& pcwzApplicationPhysicalPath,
+        _In_ const std::wstring& pcwzArguments,
+        _Out_ std::unique_ptr<HostFxrResolutionResult>& ppWrapper)
+{
+    std::filesystem::path knownDotnetLocation;
+
+    if (!pcwzDotnetExePath.empty())
+    {
+        knownDotnetLocation = pcwzDotnetExePath;
+    }
+
+    try
+    {
+        std::filesystem::path hostFxrDllPath;
+        std::vector<std::wstring> arguments;
+        HostFxrResolver::GetHostFxrParameters(
+                pcwzProcessPath,
+                pcwzApplicationPhysicalPath,
+                pcwzArguments,
+                hostFxrDllPath,
+                knownDotnetLocation,
+                arguments);
+
+        LOG_INFOF(L"Parsed hostfxr options: dotnet location: '%ls' hostfxr path: '%ls' arguments:", knownDotnetLocation.c_str(), hostFxrDllPath.c_str());
+        for (size_t i = 0; i < arguments.size(); i++)
+        {
+            LOG_INFOF(L"Argument[%d] = '%ls'", i, arguments[i].c_str());
+        }
+        ppWrapper = std::make_unique<HostFxrResolutionResult>(knownDotnetLocation, hostFxrDllPath, arguments);
+    }
+    catch (InvalidOperationException &ex)
+    {
+        EventLog::Error(
+            ASPNETCORE_EVENT_INPROCESS_START_ERROR,
+            ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
+            pcwzApplicationPhysicalPath.c_str(),
+            ex.as_wstring().c_str());
+
+        RETURN_CAUGHT_EXCEPTION();
+    }
+    catch (std::runtime_error &ex)
+    {
+        EventLog::Error(
+            ASPNETCORE_EVENT_INPROCESS_START_ERROR,
+            ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
+            pcwzApplicationPhysicalPath.c_str(),
+            GetUnexpectedExceptionMessage(ex).c_str());
+
+        RETURN_CAUGHT_EXCEPTION();
+    }
+    CATCH_RETURN();
+
+    return S_OK;
+}

+ 53 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolutionResult.h

@@ -0,0 +1,53 @@
+// 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 <Windows.h>
+#include <memory>
+#include <filesystem>
+#include <utility>
+#include <vector>
+#include <string>
+
+class HostFxrResolutionResult
+{
+public:
+    HostFxrResolutionResult(
+        std::filesystem::path dotnetExeLocation,
+        std::filesystem::path hostFxrLocation,
+        std::vector<std::wstring> arguments
+        ) noexcept
+    :   m_dotnetExeLocation(std::move(dotnetExeLocation)),
+        m_hostFxrLocation(std::move(hostFxrLocation)),
+        m_arguments(std::move(arguments))
+    {}
+
+    void
+    GetArguments(DWORD& hostfxrArgc, std::unique_ptr<PCWSTR[]>& hostfxrArgv) const;
+
+    const std::filesystem::path&
+    GetHostFxrLocation() const noexcept
+    {
+        return m_hostFxrLocation;
+    }
+
+    const std::filesystem::path&
+    GetDotnetExeLocation() const noexcept
+    {
+        return m_dotnetExeLocation;
+    }
+
+    static
+    HRESULT Create(
+         _In_  const std::wstring& pcwzExeLocation,
+         _In_  const std::wstring& pcwzProcessPath,
+         _In_  const std::wstring& pcwzApplicationPhysicalPath,
+         _In_  const std::wstring& pcwzArguments,
+         _Out_ std::unique_ptr<HostFxrResolutionResult>& ppWrapper);
+
+private:
+    const std::filesystem::path m_dotnetExeLocation;
+    const std::filesystem::path m_hostFxrLocation;
+    const std::vector<std::wstring> m_arguments;
+};

+ 10 - 10
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp → src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.cpp

@@ -1,7 +1,7 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the MIT License. See License.txt in the project root for license information.
 
-#include "hostfxr_utility.h"
+#include "HostFxrResolver.h"
 
 #include <atlcomcli.h>
 #include "fx_ver.h"
@@ -15,7 +15,7 @@
 namespace fs = std::filesystem;
 
 void
-HOSTFXR_UTILITY::GetHostFxrParameters(
+HostFxrResolver::GetHostFxrParameters(
     const fs::path     &processPath,
     const fs::path     &applicationPhysicalPath,
     const std::wstring &applicationArguments,
@@ -138,13 +138,13 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
 }
 
 BOOL
-HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
+HostFxrResolver::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
 {
     return ends_with(dotnetPath, L"dotnet.exe", true);
 }
 
 void
-HOSTFXR_UTILITY::AppendArguments(
+HostFxrResolver::AppendArguments(
     const std::wstring &applicationArguments,
     const fs::path     &applicationPhysicalPath,
     std::vector<std::wstring> &arguments,
@@ -216,7 +216,7 @@ HOSTFXR_UTILITY::AppendArguments(
 // like: C:\Program Files\dotnet\dotnet.exe, C:\Program Files\dotnet\dotnet, dotnet.exe, or dotnet.
 // Get the absolute path to dotnet. If the path is already an absolute path, it will return that path
 fs::path
-HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
+HostFxrResolver::GetAbsolutePathToDotnet(
      const fs::path & applicationPath,
      const fs::path & requestedPath
 )
@@ -296,7 +296,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
 }
 
 fs::path
-HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
+HostFxrResolver::GetAbsolutePathToHostFxr(
     const fs::path & dotnetPath
 )
 {
@@ -336,7 +336,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
 // Returns true if a valid dotnet was found, else false.R
 //
 std::optional<fs::path>
-HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
+HostFxrResolver::InvokeWhereToFindDotnet()
 {
     HRESULT             hr = S_OK;
     // Arguments to call where.exe
@@ -480,14 +480,14 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
 }
 
 std::optional<fs::path>
-HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles()
+HostFxrResolver::GetAbsolutePathToDotnetFromProgramFiles()
 {
     const auto programFilesDotnet = fs::path(Environment::ExpandEnvironmentVariables(L"%ProgramFiles%")) / "dotnet" / "dotnet.exe";
     return is_regular_file(programFilesDotnet) ? std::make_optional(programFilesDotnet) : std::nullopt;
 }
 
 std::wstring
-HOSTFXR_UTILITY::FindHighestDotNetVersion(
+HostFxrResolver::FindHighestDotNetVersion(
     _In_ std::vector<std::wstring> & vFolders
 )
 {
@@ -505,7 +505,7 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion(
 }
 
 VOID
-HOSTFXR_UTILITY::FindDotNetFolders(
+HostFxrResolver::FindDotNetFolders(
     const std::filesystem::path &path,
     _Out_ std::vector<std::wstring> &pvFolders
 )

+ 1 - 5
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h → src/Servers/IIS/AspNetCoreModuleV2/CommonLib/HostFxrResolver.h

@@ -9,12 +9,9 @@
 #include <optional>
 #include <string>
 
-typedef INT(*hostfxr_get_native_search_directories_fn) (CONST INT argc, CONST PCWSTR* argv, PWSTR buffer, DWORD buffer_size, DWORD* required_buffer_size);
-typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]);
-
 #define READ_BUFFER_SIZE 4096
 
-class HOSTFXR_UTILITY
+class HostFxrResolver
 {
 public:
 
@@ -68,7 +65,6 @@ private:
         const std::filesystem::path & dotnetPath
     );
 
-
     static
     std::optional<std::filesystem::path>
     InvokeWhereToFindDotnet();

+ 0 - 27
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/IOutputManager.h

@@ -1,27 +0,0 @@
-// 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 "stdafx.h"
-#include "stringa.h"
-
-class IOutputManager
-{
-public:
-    virtual
-    void
-    Start() = 0;
-
-    virtual
-    ~IOutputManager() = default;
-
-    virtual
-    std::wstring
-    GetStdOutContent() = 0;
-
-    virtual
-    void
-    Stop() = 0;
-};
-

+ 11 - 45
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.cpp

@@ -3,55 +3,21 @@
 
 #include "stdafx.h"
 #include "LoggingHelpers.h"
-#include "FileOutputManager.h"
-#include "PipeOutputManager.h"
-#include "NullOutputManager.h"
-#include "debugutil.h"
-#include <Windows.h>
-#include <io.h>
-#include "ntassert.h"
+#include "StandardStreamRedirection.h"
 #include "exceptions.h"
-#include "EventLog.h"
-#include "BaseOutputManager.h"
 
-HRESULT
-LoggingHelpers::CreateLoggingProvider(
-    bool fIsLoggingEnabled,
-    bool fEnableNativeLogging,
-    PCWSTR pwzStdOutFileName,
-    PCWSTR pwzApplicationPath,
-    std::unique_ptr<BaseOutputManager>& outputManager
-)
+std::shared_ptr<RedirectionOutput> LoggingHelpers::CreateOutputs(
+    bool enableFileLogging,
+    std::wstring outputFileName,
+    std::wstring applicationPath,
+    std::shared_ptr<RedirectionOutput> stringStreamOutput)
 {
-    HRESULT hr = S_OK;
-
-    DBG_ASSERT(outputManager != NULL);
-
-    try
-    {
-        // Check if there is an existing active console window before redirecting
-        // Window == IISExpress with active console window, don't redirect to a pipe
-        // if true.
-        CONSOLE_SCREEN_BUFFER_INFO dummy;
-
-        if (fIsLoggingEnabled)
-        {
-            auto manager = std::make_unique<FileOutputManager>(pwzStdOutFileName, pwzApplicationPath, fEnableNativeLogging);
-            outputManager = std::move(manager);
-        }
-        else if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &dummy))
-        {
-            outputManager = std::make_unique<PipeOutputManager>(fEnableNativeLogging);
-        }
-        else
-        {
-            outputManager = std::make_unique<NullOutputManager>();
-        }
-    }
-    catch (std::bad_alloc&)
+    auto stdOutOutput = std::make_shared<StandardOutputRedirectionOutput>();
+    std::shared_ptr<RedirectionOutput> fileOutput;
+    if (enableFileLogging)
     {
-        hr = E_OUTOFMEMORY;
+        fileOutput = std::make_shared<FileRedirectionOutput>(applicationPath, outputFileName);
     }
 
-    return hr;
+    return std::make_shared<AggregateRedirectionOutput>(std::move(fileOutput), std::move(stdOutOutput), std::move(stringStreamOutput));
 }

+ 8 - 9
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/LoggingHelpers.h

@@ -3,20 +3,19 @@
 
 #pragma once
 
-#include "BaseOutputManager.h"
+#include "NonCopyable.h"
+#include "StandardStreamRedirection.h"
 
 class LoggingHelpers
 {
 public:
 
-    static
-    HRESULT
-    CreateLoggingProvider(
-        bool fLoggingEnabled,
-        bool fEnableNativeLogging,
-        PCWSTR pwzStdOutFileName,
-        PCWSTR pwzApplicationPath,
-        std::unique_ptr<BaseOutputManager>& outputManager
+    static std::shared_ptr<RedirectionOutput>
+    CreateOutputs(
+        bool enableFileLogging,
+        std::wstring outputFileName,
+        std::wstring applicationPath,
+        std::shared_ptr<RedirectionOutput> stringStreamOutput
     );
 };
 

+ 21 - 5
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/ModuleHelpers.h

@@ -14,7 +14,7 @@ public:
     void IncrementCurrentModuleRefCount(HandleWrapper<ModuleHandleTraits> &handle)
     {
         WCHAR path[MAX_PATH];
-        
+
 #pragma warning( push )
 #pragma warning ( disable : 26485 ) // Calling WinAPI causes expected array to pointer decay
 
@@ -26,14 +26,30 @@ public:
 
     template<typename Func>
     static
-    Func GetKnownProcAddress(HMODULE hModule, LPCSTR lpProcName) {
-        
+    Func GetKnownProcAddress(HMODULE hModule, LPCSTR lpProcName, bool optional = false) {
+
 #pragma warning( push )
-#pragma warning ( disable : 26490 ) // Disable Don't use reinterpret_cast 
+#pragma warning ( disable : 26490 ) // Disable Don't use reinterpret_cast
         auto proc = reinterpret_cast<Func>(GetProcAddress(hModule, lpProcName));
 #pragma warning( pop )
 
-        THROW_LAST_ERROR_IF (!proc);
+        THROW_LAST_ERROR_IF (!optional && !proc);
         return proc;
     }
+
+    static
+    std::wstring
+    GetModuleFileNameValue(HMODULE hModule)
+    {
+        // this method is used for logging purposes for modules known to be under MAX_PATH
+        WCHAR path[MAX_PATH];
+
+#pragma warning( push )
+#pragma warning ( disable : 26485 ) // Calling WinAPI causes expected array to pointer decay
+
+        THROW_LAST_ERROR_IF(!GetModuleFileName(hModule, path, MAX_PATH));
+
+        return path;
+#pragma warning( pop )
+    }
 };

+ 0 - 29
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/NullOutputManager.h

@@ -1,29 +0,0 @@
-// 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 "stdafx.h"
-
-class NullOutputManager : public BaseOutputManager
-{
-public:
-
-    NullOutputManager() = default;
-
-    ~NullOutputManager() = default;
-
-    void Start()
-    {
-    }
-
-    void Stop()
-    {
-    }
-
-    std::wstring GetStdOutContent()
-    {
-        return L"";
-    }
-};
-

+ 0 - 38
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.h

@@ -1,38 +0,0 @@
-// 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 "StdWrapper.h"
-#include "stringu.h"
-#include "BaseOutputManager.h"
-
-class PipeOutputManager : public BaseOutputManager
-{
-    // Timeout to be used if a thread never exits
-    #define PIPE_OUTPUT_THREAD_TIMEOUT 2000
-
-    // Max event log message is ~32KB, limit pipe size just below that.
-    #define MAX_PIPE_READ_SIZE 30000
-public:
-    PipeOutputManager();
-    PipeOutputManager(bool fEnableNativeLogging);
-    ~PipeOutputManager();
-
-    void Start() override;
-    void Stop() override;
-    std::wstring GetStdOutContent() override;
-
-    // Thread functions
-    void ReadStdErrHandleInternal();
-
-    static void ReadStdErrHandle(LPVOID pContext);
-
-private:
-
-    HANDLE                          m_hErrReadPipe;
-    HANDLE                          m_hErrWritePipe;
-    HANDLE                          m_hErrThread;
-    CHAR                            m_pipeContents[MAX_PIPE_READ_SIZE] = { 0 };
-    DWORD                           m_numBytesReadTotal;
-};

+ 131 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.cpp

@@ -0,0 +1,131 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See License.txt in the project root for license information.
+
+#include "RedirectionOutput.h"
+#include <filesystem>
+#include "exceptions.h"
+#include "EventLog.h"
+
+AggregateRedirectionOutput::AggregateRedirectionOutput(std::shared_ptr<RedirectionOutput> outputA, std::shared_ptr<RedirectionOutput> outputB, std::shared_ptr<RedirectionOutput> outputC) noexcept(true):
+    m_outputA(std::move(outputA)), m_outputB(std::move(outputB)), m_outputC(std::move(outputC))
+{
+}
+
+void AggregateRedirectionOutput::Append(const std::wstring& text)
+{
+    if (m_outputA != nullptr)
+    {
+        m_outputA->Append(text);
+    }
+    if (m_outputB != nullptr)
+    {
+        m_outputB->Append(text);
+    }
+    if (m_outputC != nullptr)
+    {
+        m_outputC->Append(text);
+    }
+}
+
+FileRedirectionOutput::FileRedirectionOutput(const std::wstring& applicationPath, const std::wstring& fileName)
+{
+    try
+    {
+        SYSTEMTIME systemTime{};
+        FILETIME processCreationTime;
+        FILETIME dummyFileTime;
+
+        // Concatenate the log file name and application path
+        auto logPath = std::filesystem::path(applicationPath) / fileName;
+        create_directories(logPath.parent_path());
+
+        THROW_LAST_ERROR_IF(!GetProcessTimes(
+                GetCurrentProcess(),
+                &processCreationTime,
+                &dummyFileTime,
+                &dummyFileTime,
+                &dummyFileTime));
+
+        THROW_LAST_ERROR_IF(!FileTimeToSystemTime(&processCreationTime, &systemTime));
+
+        m_fileName = format(L"%s_%d%02d%02d%02d%02d%02d_%d.log",
+                            logPath.c_str(),
+                            systemTime.wYear,
+                            systemTime.wMonth,
+                            systemTime.wDay,
+                            systemTime.wHour,
+                            systemTime.wMinute,
+                            systemTime.wSecond,
+                            GetCurrentProcessId());
+
+        m_file.exceptions(std::ifstream::failbit);
+        m_file.open(m_fileName, std::wofstream::out | std::wofstream::app);
+    }
+    catch (...)
+    {
+        OBSERVE_CAUGHT_EXCEPTION();
+        EventLog::Warn(
+            ASPNETCORE_EVENT_GENERAL_WARNING,
+            L"Could not start stdout file redirection to '%s' with application base '%s'. %s.",
+            fileName.c_str(),
+            applicationPath.c_str(),
+            CaughtExceptionToString().c_str());
+    }
+}
+
+void FileRedirectionOutput::Append(const std::wstring& text)
+{
+    if (m_file.is_open())
+    {
+        const auto multiByte = to_multi_byte_string(text, CP_UTF8);
+        m_file << multiByte;
+    }
+}
+
+FileRedirectionOutput::~FileRedirectionOutput()
+{
+    if (m_file.is_open())
+    {
+        m_file.close();
+        std::error_code ec;
+        if (std::filesystem::file_size(m_fileName, ec) == 0 && SUCCEEDED_LOG(ec))
+        {
+            std::filesystem::remove(m_fileName, ec);
+            LOG_IF_FAILED(ec);
+        }
+    }
+}
+
+StandardOutputRedirectionOutput::StandardOutputRedirectionOutput(): m_handle(GetStdHandle(STD_OUTPUT_HANDLE))
+{
+    HANDLE stdOutHandle;
+    DuplicateHandle(
+      /* hSourceProcessHandle*/ GetCurrentProcess(),
+      /* hSourceHandle */ GetStdHandle(STD_OUTPUT_HANDLE),
+      /* hTargetProcessHandle */ GetCurrentProcess(),
+      /* lpTargetHandle */&stdOutHandle,
+      /* dwDesiredAccess */ 0, // dwDesired is ignored if DUPLICATE_SAME_ACCESS is specified
+      /* bInheritHandle */ FALSE,
+      /* dwOptions  */ DUPLICATE_SAME_ACCESS);
+    m_handle = stdOutHandle;
+}
+
+void StandardOutputRedirectionOutput::Append(const std::wstring& text)
+{
+    DWORD nBytesWritten = 0;
+    auto encodedBytes = to_multi_byte_string(text, GetConsoleOutputCP());
+    WriteFile(m_handle, encodedBytes.data(), static_cast<DWORD>(encodedBytes.size()), &nBytesWritten, nullptr);
+}
+
+StringStreamRedirectionOutput::StringStreamRedirectionOutput()
+{
+    InitializeSRWLock(&m_srwLock);
+}
+
+void StringStreamRedirectionOutput::Append(const std::wstring& text)
+{
+    SRWExclusiveLock lock(m_srwLock);
+    auto const writeSize = min(m_charactersLeft, text.size());
+    m_output.write(text.c_str(), writeSize);
+    m_charactersLeft -= writeSize;
+}

+ 96 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/RedirectionOutput.h

@@ -0,0 +1,96 @@
+// 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 "SRWExclusiveLock.h"
+#include "NonCopyable.h"
+#include "HandleWrapper.h"
+#include <fstream>
+
+class RedirectionOutput
+{
+public:
+    virtual ~RedirectionOutput() = default;
+    virtual void Append(const std::wstring& text) = 0;
+};
+
+class AggregateRedirectionOutput: NonCopyable, public RedirectionOutput
+{
+public:
+    AggregateRedirectionOutput(std::shared_ptr<RedirectionOutput> outputA, std::shared_ptr<RedirectionOutput> outputB, std::shared_ptr<RedirectionOutput> outputC) noexcept(true);
+
+    void Append(const std::wstring& text) override;
+
+private:
+    std::shared_ptr<RedirectionOutput> m_outputA;
+    std::shared_ptr<RedirectionOutput> m_outputB;
+    std::shared_ptr<RedirectionOutput> m_outputC;
+};
+
+class FileRedirectionOutput: NonCopyable, public RedirectionOutput
+{
+public:
+    FileRedirectionOutput(const std::wstring& applicationPath, const std::wstring& fileName);
+
+    void Append(const std::wstring& text) override;
+
+    ~FileRedirectionOutput() override;
+
+private:
+    std::wstring m_fileName;
+    std::ofstream m_file;
+};
+
+class StandardOutputRedirectionOutput: NonCopyable, public RedirectionOutput
+{
+public:
+    StandardOutputRedirectionOutput();
+
+    void Append(const std::wstring& text) override;
+
+private:
+    HandleWrapper<InvalidHandleTraits> m_handle;
+};
+
+class ForwardingRedirectionOutput: NonCopyable, public RedirectionOutput
+{
+public:
+    ForwardingRedirectionOutput(RedirectionOutput ** target) noexcept
+        : m_target(target)
+    {
+    }
+
+    void Append(const std::wstring& text) override
+    {
+        auto const target = *m_target;
+        if (target)
+        {
+            target->Append(text);
+        }
+    }
+
+private:
+    RedirectionOutput** m_target;
+};
+
+class StringStreamRedirectionOutput: NonCopyable, public RedirectionOutput
+{
+public:
+    StringStreamRedirectionOutput();
+
+    void Append(const std::wstring& text) override;
+
+    std::wstring GetOutput() const
+    {
+        return m_output.str();
+    }
+
+private:
+    // Logs collected by this output are mostly used for Event Log messages where size limit is 32K
+    std::size_t m_charactersLeft = 30000;
+    std::wstringstream m_output;
+    SRWLOCK m_srwLock{};
+};
+
+

+ 23 - 58
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp → src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.cpp

@@ -1,7 +1,7 @@
 // Copyright (c) .NET Foundation. All rights reserved.
 // Licensed under the MIT License. See License.txt in the project root for license information.
 
-#include "PipeOutputManager.h"
+#include "StandardStreamRedirection.h"
 
 #include "stdafx.h"
 #include "Exceptions.h"
@@ -13,29 +13,27 @@
 #define LOG_IF_DUPFAIL(err) do { if (err == -1) { LOG_IF_FAILED(HRESULT_FROM_WIN32(_doserrno)); } } while (0, 0);
 #define LOG_IF_ERRNO(err) do { if (err != 0) { LOG_IF_FAILED(HRESULT_FROM_WIN32(_doserrno)); } } while (0, 0);
 
-PipeOutputManager::PipeOutputManager()
-    : PipeOutputManager( /* fEnableNativeLogging */ true)
-{
-}
 
-PipeOutputManager::PipeOutputManager(bool fEnableNativeLogging) :
-    BaseOutputManager(fEnableNativeLogging),
+StandardStreamRedirection::StandardStreamRedirection(RedirectionOutput& output, bool commandLineLaunch) :
+    m_output(output),
     m_hErrReadPipe(INVALID_HANDLE_VALUE),
     m_hErrWritePipe(INVALID_HANDLE_VALUE),
     m_hErrThread(nullptr),
-    m_numBytesReadTotal(0)
+    m_disposed(false),
+    m_commandLineLaunch(commandLineLaunch)
 {
+    TryStartRedirection();
 }
 
-PipeOutputManager::~PipeOutputManager()
+StandardStreamRedirection::~StandardStreamRedirection() noexcept(false)
 {
-    PipeOutputManager::Stop();
+    TryStopRedirection();
 }
 
 // Start redirecting stdout and stderr into a pipe
 // Continuously read the pipe on a background thread
 // until Stop is called.
-void PipeOutputManager::Start()
+void StandardStreamRedirection::Start()
 {
     SECURITY_ATTRIBUTES     saAttr = { 0 };
     HANDLE                  hStdErrReadPipe;
@@ -57,8 +55,8 @@ void PipeOutputManager::Start()
     m_hErrReadPipe = hStdErrReadPipe;
     m_hErrWritePipe = hStdErrWritePipe;
 
-    stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, m_enableNativeRedirection);
-    stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, hStdErrWritePipe, m_enableNativeRedirection);
+    stdoutWrapper = std::make_unique<StdWrapper>(stdout, STD_OUTPUT_HANDLE, hStdErrWritePipe, !m_commandLineLaunch);
+    stderrWrapper = std::make_unique<StdWrapper>(stderr, STD_ERROR_HANDLE, hStdErrWritePipe, !m_commandLineLaunch);
 
     LOG_IF_FAILED(stdoutWrapper->StartRedirection());
     LOG_IF_FAILED(stderrWrapper->StartRedirection());
@@ -80,7 +78,7 @@ void PipeOutputManager::Start()
 // and prints any output that was captured in the pipe.
 // If more than 30Kb was written to the pipe, that output will
 // be thrown away.
-void PipeOutputManager::Stop()
+void StandardStreamRedirection::Stop()
 {
     DWORD    dwThreadStatus = 0;
 
@@ -123,12 +121,13 @@ void PipeOutputManager::Stop()
     // Don't check return value as IO may or may not be completed already.
     if (m_hErrThread != nullptr)
     {
+        LOG_INFO(L"Canceling standard stream pipe reader");
         CancelSynchronousIo(m_hErrThread);
     }
 
     // GetExitCodeThread returns 0 on failure; thread status code is invalid.
     if (m_hErrThread != nullptr &&
-        !LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) &&
+        !LOG_LAST_ERROR_IF(!GetExitCodeThread(m_hErrThread, &dwThreadStatus)) &&
         dwThreadStatus == STILL_ACTIVE)
     {
         // Wait for graceful shutdown, i.e., the exit of the background thread or timeout
@@ -155,74 +154,40 @@ void PipeOutputManager::Stop()
         CloseHandle(m_hErrReadPipe);
         m_hErrReadPipe = INVALID_HANDLE_VALUE;
     }
-
-    // If we captured any output, relog it to the original stdout
-    // Useful for the IIS Express scenario as it is running with stdout and stderr
-    m_stdOutContent = to_wide_string(std::string(m_pipeContents, m_numBytesReadTotal), GetConsoleOutputCP());
-
-    if (!m_stdOutContent.empty())
-    {
-        // printf will fail in in full IIS
-        if (wprintf(m_stdOutContent.c_str()) != -1)
-        {
-            // Need to flush contents for the new stdout and stderr
-            _flushall();
-        }
-    }
 }
 
-std::wstring PipeOutputManager::GetStdOutContent()
-{
-    return m_stdOutContent;
-}
 
 void
-PipeOutputManager::ReadStdErrHandle(
+StandardStreamRedirection::ReadStdErrHandle(
     LPVOID pContext
 )
 {
-    auto pLoggingProvider = static_cast<PipeOutputManager*>(pContext);
+    auto pLoggingProvider = static_cast<StandardStreamRedirection*>(pContext);
     DBG_ASSERT(pLoggingProvider != NULL);
     pLoggingProvider->ReadStdErrHandleInternal();
 }
 
 void
-PipeOutputManager::ReadStdErrHandleInternal()
+StandardStreamRedirection::ReadStdErrHandleInternal()
 {
+    std::string tempBuffer;
+    tempBuffer.resize(PIPE_READ_SIZE);
+
     // If ReadFile ever returns false, exit the thread
     DWORD dwNumBytesRead = 0;
     while (true)
     {
-        // Fill a maximum of MAX_PIPE_READ_SIZE into a buffer.
         if (ReadFile(m_hErrReadPipe,
-            &m_pipeContents[m_numBytesReadTotal],
-            MAX_PIPE_READ_SIZE - m_numBytesReadTotal,
+            tempBuffer.data(),
+            PIPE_READ_SIZE,
             &dwNumBytesRead,
             nullptr))
         {
-            m_numBytesReadTotal += dwNumBytesRead;
-            if (m_numBytesReadTotal >= MAX_PIPE_READ_SIZE)
-            {
-                break;
-            }
+            m_output.Append(to_wide_string(tempBuffer.substr(0, dwNumBytesRead), GetConsoleOutputCP()));
         }
         else
         {
             return;
         }
     }
-
-    // Using std::string as a wrapper around new char[] so we don't need to call delete
-    // Also don't allocate on stack as stack size is 128KB by default.
-    std::string tempBuffer; 
-    tempBuffer.resize(MAX_PIPE_READ_SIZE);
-
-    // After reading the maximum amount of data, keep reading in a loop until Stop is called on the output manager.
-    while (true)
-    {
-        if (!ReadFile(m_hErrReadPipe, tempBuffer.data(), MAX_PIPE_READ_SIZE, &dwNumBytesRead, nullptr))
-        {
-            return;
-        }
-    }
 }

+ 66 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StandardStreamRedirection.h

@@ -0,0 +1,66 @@
+// 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 "RedirectionOutput.h"
+#include "StdWrapper.h"
+#include "ModuleHelpers.h"
+
+class StandardStreamRedirection : NonCopyable
+{
+    // Timeout to be used if a thread never exits
+    #define PIPE_OUTPUT_THREAD_TIMEOUT 2000
+    #define PIPE_READ_SIZE 4096
+
+public:
+    StandardStreamRedirection(RedirectionOutput& output, bool commandLineLaunch);
+
+    ~StandardStreamRedirection() noexcept(false);
+
+private:
+
+    void Start();
+    void Stop();
+
+    void
+    TryStartRedirection()
+    {
+        try
+        {
+            Start();
+        }
+        catch (...)
+        {
+            OBSERVE_CAUGHT_EXCEPTION();
+        }
+    }
+
+    void
+    TryStopRedirection()
+    {
+        try
+        {
+            Stop();
+        }
+        catch (...)
+        {
+            OBSERVE_CAUGHT_EXCEPTION();
+        }
+    }
+
+    // Thread functions
+    void ReadStdErrHandleInternal();
+    static void ReadStdErrHandle(LPVOID pContext);
+
+    HANDLE                          m_hErrReadPipe;
+    HANDLE                          m_hErrWritePipe;
+    HANDLE                          m_hErrThread;
+
+    bool m_disposed;
+    bool m_commandLineLaunch;
+    SRWLOCK m_srwLock{};
+    std::unique_ptr<StdWrapper> stdoutWrapper;
+    std::unique_ptr<StdWrapper> stderrWrapper;
+    RedirectionOutput& m_output;
+};

+ 11 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.cpp

@@ -46,3 +46,14 @@ std::wstring to_wide_string(const std::string &source, const unsigned int codePa
 
     return destination;
 }
+
+std::string to_multi_byte_string(const std::wstring& text, const unsigned int codePage)
+{
+    auto const encodedByteCount = WideCharToMultiByte(codePage, 0, text.data(), -1, nullptr, 0, nullptr, nullptr);
+
+    std::string encodedBytes;
+    encodedBytes.resize(encodedByteCount);
+    WideCharToMultiByte(codePage, 0, text.data(), -1, encodedBytes.data(), encodedByteCount, nullptr, nullptr);
+    encodedBytes.resize(encodedByteCount - 1);
+    return encodedBytes;
+}

+ 3 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/StringHelpers.h

@@ -17,6 +17,9 @@ int compare_ignore_case(const std::wstring& s1, const std::wstring& s2);
 [[nodiscard]]
 std::wstring to_wide_string(const std::string &source, const unsigned int codePage);
 
+[[nodiscard]]
+std::string to_multi_byte_string(const std::wstring& text, const unsigned int codePage);
+
 template<typename ... Args>
 [[nodiscard]]
 std::wstring format(const std::wstring& format, Args ... args)

+ 35 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLib/exceptions.h

@@ -156,6 +156,17 @@ private:
     return hr;
 }
 
+ __declspec(noinline) inline HRESULT LogHResultFailed(LOCATION_ARGUMENTS const std::error_code& error_code)
+{
+    if (error_code)
+    {
+        TraceHRESULT(LOCATION_CALL error_code.value());
+        DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR,  "Failed error_code returned: 0x%x 0xs at " LOCATION_FORMAT, error_code.value(), error_code.message().c_str(), LOCATION_CALL_ONLY);
+        return E_FAIL;
+    }
+    return ERROR_SUCCESS;
+}
+
 __declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ONLY)
 {
     try
@@ -188,6 +199,30 @@ __declspec(noinline) inline HRESULT CaughtExceptionHResult(LOCATION_ARGUMENTS_ON
     }
 }
 
+__declspec(noinline) inline std::wstring CaughtExceptionToString()
+{
+    try
+    {
+        throw;
+    }
+    catch (const InvalidOperationException& exception)
+    {
+        return exception.as_wstring();
+    }
+    catch (const std::system_error& exception)
+    {
+        return to_wide_string(exception.what(), CP_ACP);
+    }
+    catch (const std::exception& exception)
+    {
+        return to_wide_string(exception.what(), CP_ACP);
+    }
+    catch (...)
+    {
+        return L"Unknown exception type";
+    }
+}
+
 [[noreturn]]
  __declspec(noinline) inline void ThrowResultException(LOCATION_ARGUMENTS HRESULT hr)
 {

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

@@ -15,3 +15,5 @@
 #include <sstream>
 #include <memory>
 #include <filesystem>
+#include <string>
+#include <fstream>

+ 1 - 2
src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/CommonLibTests.vcxproj

@@ -50,13 +50,12 @@
   </ItemGroup>
   <ItemGroup>
     <ClCompile Include="ConfigUtilityTests.cpp" />
-    <ClCompile Include="FileOutputManagerTests.cpp" />
     <ClCompile Include="GlobalVersionTests.cpp" />
     <ClCompile Include="Helpers.cpp" />
     <ClCompile Include="hostfxr_utility_tests.cpp" />
     <ClCompile Include="inprocess_application_tests.cpp" />
     <ClCompile Include="main.cpp" />
-    <ClCompile Include="PipeOutputManagerTests.cpp" />
+    <ClCompile Include="StandardOutputRedirectionTest.cpp" />
     <ClCompile Include="BindingInformationTest.cpp" />
     <ClCompile Include="utility_tests.cpp" />
   </ItemGroup>

+ 0 - 152
src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/FileOutputManagerTests.cpp

@@ -1,152 +0,0 @@
-// 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.
-
-#include "stdafx.h"
-#include "gtest/internal/gtest-port.h"
-#include "FileOutputManager.h"
-
-class FileManagerWrapper
-{
-public:
-    FileOutputManager* manager;
-    FileManagerWrapper(FileOutputManager* m)
-        : manager(m)
-    {
-        manager->Start();
-    }
-
-    ~FileManagerWrapper()
-    {
-        delete manager;
-    }
-};
-
-namespace FileOutManagerStartupTests
-{
-    using ::testing::Test;
-    class FileOutputManagerTest : public Test
-    {
-    protected:
-        void
-        Test(std::wstring fileNamePrefix, FILE* out)
-        {
-            PCWSTR expected = L"test";
-
-            auto tempDirectory = TempDirectory();
-            FileOutputManager* pManager = new FileOutputManager(fileNamePrefix, tempDirectory.path());
-
-            {
-                FileManagerWrapper wrapper(pManager);
-
-                wprintf(expected, out);
-            }
-
-            for (auto & p : std::filesystem::directory_iterator(tempDirectory.path()))
-            {
-                std::wstring filename(p.path().filename());
-                ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix);
-
-                std::wstring content = Helpers::ReadFileContent(std::wstring(p.path()));
-            }
-        }
-    };
-
-    TEST_F(FileOutputManagerTest, WriteToFileCheckContentsWritten)
-    {
-        Test(L"", stdout);
-        Test(L"log", stdout);
-    }
-
-    TEST_F(FileOutputManagerTest, WriteToFileCheckContentsWrittenErr)
-    {
-        Test(L"", stderr);
-        Test(L"log", stderr);
-    }
-}
-
-namespace FileOutManagerOutputTests
-{
-    TEST(FileOutManagerOutputTest, StdOut)
-    {
-        PCWSTR expected = L"test";
-
-        auto tempDirectory = TempDirectory();
-
-        FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
-        {
-            FileManagerWrapper wrapper(pManager);
-
-            fwprintf(stdout, expected);
-            pManager->Stop();
-
-            auto output = pManager->GetStdOutContent();
-            ASSERT_FALSE(output.empty());
-
-            ASSERT_STREQ(output.c_str(), expected);
-        }
-    }
-
-    TEST(FileOutManagerOutputTest, StdErr)
-    {
-        PCWSTR expected = L"test";
-
-        auto tempDirectory = TempDirectory();
-
-        FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path().c_str());
-        {
-            FileManagerWrapper wrapper(pManager);
-
-            fwprintf(stderr, expected);
-            pManager->Stop();
-
-            auto output = pManager->GetStdOutContent();
-            ASSERT_FALSE(output.empty());
-
-            ASSERT_STREQ(output.c_str(), expected);
-        }
-    }
-
-    TEST(FileOutManagerOutputTest, CapAt30KB)
-    {
-        PCWSTR expected = L"hello world";
-
-        auto tempDirectory = TempDirectory();
-
-        FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
-        {
-            FileManagerWrapper wrapper(pManager);
-
-            for (int i = 0; i < 3000; i++)
-            {
-                wprintf(expected);
-            }
-            pManager->Stop();
-            auto output = pManager->GetStdOutContent();
-            ASSERT_FALSE(output.empty());
-
-            ASSERT_EQ(output.size(), 30000);
-        }
-    }
-
-    TEST(FileOutManagerOutputTest, StartStopRestoresCorrectly)
-    {
-        PCWSTR expected = L"test";
-
-        auto tempDirectory = TempDirectory();
-
-        for (int i = 0; i < 10; i++)
-        {
-            FileOutputManager* pManager = new FileOutputManager(L"", tempDirectory.path());
-            {
-                FileManagerWrapper wrapper(pManager);
-
-                wprintf(expected);
-                pManager->Stop();
-                auto output = pManager->GetStdOutContent();
-                ASSERT_FALSE(output.empty());
-
-                ASSERT_STREQ(output.c_str(), expected);
-            }
-        }
-    }
-}

+ 0 - 162
src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/PipeOutputManagerTests.cpp

@@ -1,162 +0,0 @@
-// 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.
-
-#include "stdafx.h"
-#include "gtest/internal/gtest-port.h"
-#include "PipeOutputManager.h"
-
-class FileManagerWrapper
-{
-public:
-    PipeOutputManager * manager;
-    FileManagerWrapper(PipeOutputManager* m)
-        : manager(m)
-    {
-        manager->Start();
-    }
-
-    ~FileManagerWrapper()
-    {
-        delete manager;
-    }
-};
-
-namespace PipeOutputManagerTests
-{
-    TEST(PipeManagerOutputTest, StdOut)
-    {
-        PCWSTR expected = L"test";
-
-        PipeOutputManager* pManager = new PipeOutputManager(true);
-
-        pManager->Start();
-        fwprintf(stdout, expected);
-        pManager->Stop();
-
-        auto output = pManager->GetStdOutContent();
-        ASSERT_STREQ(output.c_str(), expected);
-        delete pManager;
-    }
-
-    TEST(PipeManagerOutputTest, StdOutMultiToWide)
-    {
-        PipeOutputManager* pManager = new PipeOutputManager(true);
-
-        pManager->Start();
-        fprintf(stdout, "test");
-        pManager->Stop();
-
-        auto output = pManager->GetStdOutContent();
-        ASSERT_STREQ(output.c_str(), L"test");
-        delete pManager;
-    }
-
-    TEST(PipeManagerOutputTest, StdErr)
-    {
-        PCWSTR expected = L"test";
-
-        PipeOutputManager* pManager = new PipeOutputManager();
-
-        pManager->Start();
-        fwprintf(stderr, expected);
-        pManager->Stop();
-
-        auto output = pManager->GetStdOutContent();
-        ASSERT_STREQ(output.c_str(), expected);
-        delete pManager;
-    }
-
-    TEST(PipeManagerOutputTest, CheckMaxPipeSize)
-    {
-        std::wstring test;
-        for (int i = 0; i < 3000; i++)
-        {
-            test.append(L"hello world");
-        }
-
-        PipeOutputManager* pManager = new PipeOutputManager();
-
-        pManager->Start();
-        wprintf(test.c_str());
-        pManager->Stop();
-
-        auto output = pManager->GetStdOutContent();
-        ASSERT_EQ(output.size(), (DWORD)30000);
-        delete pManager;
-    }
-
-    TEST(PipeManagerOutputTest, SetInvalidHandlesForErrAndOut)
-    {
-        auto m_fdPreviousStdOut = _dup(_fileno(stdout));
-        auto m_fdPreviousStdErr = _dup(_fileno(stderr));
-
-        SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
-        SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE);
-
-        PCWSTR expected = L"test";
-
-        PipeOutputManager* pManager = new PipeOutputManager();
-        pManager->Start();
-
-        _dup2(m_fdPreviousStdOut, _fileno(stdout));
-        _dup2(m_fdPreviousStdErr, _fileno(stderr));
-
-        // Test will fail if we didn't redirect stdout back to a file descriptor.
-        // This is because gtest relies on console output to know if a test succeeded or failed.
-        // If the output still points to a file/pipe, the test (and all other tests after it) will fail.
-        delete pManager;
-    }
-
-    TEST(PipeManagerOutputTest, CreateDeleteMultipleTimesStdOutWorks)
-    {
-        for (int i = 0; i < 10; i++)
-        {
-            auto stdoutBefore = _fileno(stdout);
-            auto stderrBefore = _fileno(stderr);
-            PCWSTR expected = L"test";
-
-            PipeOutputManager* pManager = new PipeOutputManager();
-
-            pManager->Start();
-            fwprintf(stdout, expected);
-
-            pManager->Stop();
-
-            auto output = pManager->GetStdOutContent();
-            ASSERT_STREQ(output.c_str(), expected);
-            ASSERT_EQ(stdoutBefore, _fileno(stdout));
-            ASSERT_EQ(stderrBefore, _fileno(stderr));
-            delete pManager;
-        }
-        // When this returns, we get an AV from gtest.
-    }
-
-    TEST(PipeManagerOutputTest, CreateDeleteKeepOriginalStdErr)
-    {
-        for (int i = 0; i < 10; i++)
-        {
-            auto stdoutBefore = _fileno(stdout);
-            auto stderrBefore = _fileno(stderr);
-            auto stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
-            auto stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
-            PCWSTR expected = L"test";
-
-            PipeOutputManager* pManager = new PipeOutputManager();
-
-            pManager->Start();
-            fwprintf(stderr, expected);
-            pManager->Stop();
-
-            auto output = pManager->GetStdOutContent();
-            ASSERT_STREQ(output.c_str(), expected);
-            ASSERT_EQ(stdoutBefore, _fileno(stdout));
-
-            ASSERT_EQ(stderrBefore, _fileno(stderr));
-
-            delete pManager;
-        }
-
-        wprintf(L"Hello!");
-    }
-}
-

+ 252 - 0
src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/StandardOutputRedirectionTest.cpp

@@ -0,0 +1,252 @@
+// 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.
+
+#include "stdafx.h"
+#include "gtest/internal/gtest-port.h"
+#include "StandardStreamRedirection.h"
+
+namespace FileRedirectionOutputTests
+{
+    using ::testing::Test;
+    class FileRedirectionOutputTest : public Test
+    {
+    protected:
+        void
+        Test(std::wstring fileNamePrefix, FILE* out)
+        {
+            PCWSTR expected = L"test";
+
+            auto tempDirectory = TempDirectory();
+
+            {
+                FileRedirectionOutput redirectionOutput(tempDirectory.path(), fileNamePrefix);
+                StandardStreamRedirection pManager(redirectionOutput, false);
+
+                wprintf(expected, out);
+            }
+
+            for (auto & p : std::filesystem::directory_iterator(tempDirectory.path()))
+            {
+                std::wstring filename(p.path().filename());
+                ASSERT_EQ(filename.substr(0, fileNamePrefix.size()), fileNamePrefix);
+
+                std::wstring content = Helpers::ReadFileContent(std::wstring(p.path()));
+            }
+        }
+    };
+
+    TEST_F(FileRedirectionOutputTest, WriteToFileCheckContentsWritten)
+    {
+        Test(L"", stdout);
+        Test(L"log", stdout);
+    }
+
+    TEST_F(FileRedirectionOutputTest, WriteToFileCheckContentsWrittenErr)
+    {
+        Test(L"", stderr);
+        Test(L"log", stderr);
+    }
+}
+
+namespace PipeOutputManagerTests
+{
+    TEST(PipeManagerOutputTest, StdOut)
+    {
+        PCWSTR expected = L"test";
+
+        StringStreamRedirectionOutput redirectionOutput;
+        {
+            StandardStreamRedirection pManager(redirectionOutput, false);
+            fwprintf(stdout, expected);
+        }
+
+        auto output = redirectionOutput.GetOutput();
+        ASSERT_STREQ(output.c_str(), expected);
+    }
+
+    TEST(PipeManagerOutputTest, StdOutMultiToWide)
+    {
+        StringStreamRedirectionOutput redirectionOutput;
+        {
+            StandardStreamRedirection pManager(redirectionOutput, false);
+            fprintf(stdout, "test");
+        }
+        auto output = redirectionOutput.GetOutput();
+        ASSERT_STREQ(output.c_str(), L"test");
+    }
+
+    TEST(PipeManagerOutputTest, StdErr)
+    {
+        PCWSTR expected = L"test";
+
+        StringStreamRedirectionOutput redirectionOutput;
+        {
+            StandardStreamRedirection pManager(redirectionOutput, false);
+            fwprintf(stderr, expected);
+        }
+
+        auto output = redirectionOutput.GetOutput();
+        ASSERT_STREQ(output.c_str(), expected);
+    }
+
+    TEST(PipeManagerOutputTest, CheckMaxPipeSize)
+    {
+        std::wstring test;
+        for (int i = 0; i < 3000; i++)
+        {
+            test.append(L"hello world");
+        }
+
+        StringStreamRedirectionOutput redirectionOutput;
+        {
+            StandardStreamRedirection pManager(redirectionOutput, false);
+            wprintf(test.c_str());
+        }
+
+        auto output = redirectionOutput.GetOutput();
+        ASSERT_EQ(output.size(), (DWORD)30000);
+    }
+
+    TEST(PipeManagerOutputTest, SetInvalidHandlesForErrAndOut)
+    {
+        auto m_fdPreviousStdOut = _dup(_fileno(stdout));
+        auto m_fdPreviousStdErr = _dup(_fileno(stderr));
+
+        SetStdHandle(STD_ERROR_HANDLE, INVALID_HANDLE_VALUE);
+        SetStdHandle(STD_OUTPUT_HANDLE, INVALID_HANDLE_VALUE);
+
+        StringStreamRedirectionOutput redirectionOutput;
+        {
+            StandardStreamRedirection pManager(redirectionOutput, false);
+            _dup2(m_fdPreviousStdOut, _fileno(stdout));
+            _dup2(m_fdPreviousStdErr, _fileno(stderr));
+
+            // Test will fail if we didn't redirect stdout back to a file descriptor.
+            // This is because gtest relies on console output to know if a test succeeded or failed.
+            // If the output still points to a file/pipe, the test (and all other tests after it) will fail.
+        }
+    }
+
+    TEST(PipeManagerOutputTest, CreateDeleteMultipleTimesStdOutWorks)
+    {
+        for (int i = 0; i < 10; i++)
+        {
+            auto stdoutBefore = _fileno(stdout);
+            auto stderrBefore = _fileno(stderr);
+            PCWSTR expected = L"test";
+
+            StringStreamRedirectionOutput redirectionOutput;
+            {
+                StandardStreamRedirection pManager(redirectionOutput, false);
+                fwprintf(stdout, expected);
+            }
+
+            auto output = redirectionOutput.GetOutput();
+            ASSERT_STREQ(output.c_str(), expected);
+            ASSERT_EQ(stdoutBefore, _fileno(stdout));
+            ASSERT_EQ(stderrBefore, _fileno(stderr));
+        }
+        // When this returns, we get an AV from gtest.
+    }
+
+    TEST(PipeManagerOutputTest, CreateDeleteKeepOriginalStdErr)
+    {
+        for (int i = 0; i < 10; i++)
+        {
+            auto stdoutBefore = _fileno(stdout);
+            auto stderrBefore = _fileno(stderr);
+            auto stdoutHandle = GetStdHandle(STD_OUTPUT_HANDLE);
+            auto stderrHandle = GetStdHandle(STD_ERROR_HANDLE);
+            PCWSTR expected = L"test";
+
+            StringStreamRedirectionOutput redirectionOutput;
+            {
+                StandardStreamRedirection pManager(redirectionOutput, false);
+                fwprintf(stderr, expected);
+            }
+
+            auto output = redirectionOutput.GetOutput();
+            ASSERT_STREQ(output.c_str(), expected);
+            ASSERT_EQ(stdoutBefore, _fileno(stdout));
+
+            ASSERT_EQ(stderrBefore, _fileno(stderr));
+        }
+
+        wprintf(L"Hello!");
+    }
+
+
+    TEST(StringStreamRedirectionOutputTest, StdOut)
+    {
+        PCWSTR expected = L"test";
+
+        {
+            StringStreamRedirectionOutput redirectionOutput;
+            {
+                StandardStreamRedirection pManager(redirectionOutput, false);
+                fwprintf(stdout, expected);
+            }
+
+            auto output = redirectionOutput.GetOutput();
+            ASSERT_FALSE(output.empty());
+
+            ASSERT_STREQ(output.c_str(), expected);
+        }
+    }
+
+    TEST(StringStreamRedirectionOutputTest, StdErr)
+    {
+        PCWSTR expected = L"test";
+
+        StringStreamRedirectionOutput redirectionOutput;
+        {
+            StandardStreamRedirection pManager(redirectionOutput, false);
+            fwprintf(stderr, expected);
+        }
+
+        auto output = redirectionOutput.GetOutput();
+        ASSERT_FALSE(output.empty());
+
+        ASSERT_STREQ(output.c_str(), expected);
+    }
+
+    TEST(StringStreamRedirectionOutputTest, CapAt30KB)
+    {
+        PCWSTR expected = L"hello world";
+
+        auto tempDirectory = TempDirectory();
+
+        StringStreamRedirectionOutput redirectionOutput;
+        {
+            StandardStreamRedirection pManager(redirectionOutput, false);
+            for (int i = 0; i < 3000; i++)
+            {
+                wprintf(expected);
+            }
+        }
+
+        auto output = redirectionOutput.GetOutput();
+        ASSERT_FALSE(output.empty());
+
+        ASSERT_EQ(output.size(), 30000);
+    }
+
+    TEST(StringStreamRedirectionOutputTest, StartStopRestoresCorrectly)
+    {
+        PCWSTR expected = L"test";
+
+        for (int i = 0; i < 10; i++)
+        {
+            StringStreamRedirectionOutput redirectionOutput;
+            {
+                StandardStreamRedirection pManager(redirectionOutput, false);
+                wprintf(expected);
+            }
+            auto output = redirectionOutput.GetOutput();
+            ASSERT_FALSE(output.empty());
+
+            ASSERT_STREQ(output.c_str(), expected);
+        }
+    }
+}
+

+ 7 - 7
src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/hostfxr_utility_tests.cpp

@@ -5,14 +5,14 @@
 #include <filesystem>
 #include <vector>
 #include <string>
-#include "hostfxr_utility.h"
+#include "HostFxrResolver.h"
 #include "Environment.h"
 
 TEST(ParseHostFxrArguments, BasicHostFxrArguments)
 {
     std::vector<std::wstring> bstrArray;
 
-    HOSTFXR_UTILITY::AppendArguments(
+    HostFxrResolver::AppendArguments(
         L"exec \"test.dll\"", // args
         L"invalid",  // physical path to application
         bstrArray); // args array.
@@ -26,7 +26,7 @@ TEST(ParseHostFxrArguments, NoExecProvided)
 {
     std::vector<std::wstring> bstrArray;
 
-    HOSTFXR_UTILITY::AppendArguments(
+    HostFxrResolver::AppendArguments(
         L"test.dll", // args
         L"ignored",  // physical path to application
         bstrArray); // args array.
@@ -40,7 +40,7 @@ TEST(ParseHostFxrArguments, ConvertDllToAbsolutePath)
     std::vector<std::wstring> bstrArray;
     // we need to use existing dll so let's use ntdll that we know exists everywhere
     auto system32 = Environment::ExpandEnvironmentVariables(L"%WINDIR%\\System32");
-    HOSTFXR_UTILITY::AppendArguments(
+    HostFxrResolver::AppendArguments(
         L"exec \"ntdll.dll\"", // args
         system32,  // physical path to application
         bstrArray, // args array.
@@ -57,7 +57,7 @@ TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs)
     std::filesystem::path struHostFxrDllLocation;
     std::filesystem::path struExeLocation;
 
-    EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters(
+    EXPECT_THROW(HostFxrResolver::GetHostFxrParameters(
         L"dotnet", // processPath
         L"some\\path",  // application physical path, ignored.
         L"",  //arguments
@@ -94,7 +94,7 @@ TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks)
         fDotnetInProgramFiles = std::filesystem::is_regular_file(L"C:/Program Files (x86)/dotnet/dotnet.exe");
     }
 
-    auto dotnetPath = HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles();
+    auto dotnetPath = HostFxrResolver::GetAbsolutePathToDotnetFromProgramFiles();
     if (fDotnetInProgramFiles)
     {
         EXPECT_TRUE(dotnetPath.has_value());
@@ -111,7 +111,7 @@ TEST(GetHostFxrArguments, InvalidParams)
     std::filesystem::path struHostFxrDllLocation;
     std::filesystem::path struExeLocation;
 
-    EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters(
+    EXPECT_THROW(HostFxrResolver::GetHostFxrParameters(
         L"bogus", // processPath
         L"",  // application physical path, ignored.
         L"ignored",  //arguments

+ 1 - 1
src/Servers/IIS/AspNetCoreModuleV2/CommonLibTests/stdafx.h

@@ -42,7 +42,7 @@
 #include "hashfn.h"
 
 #include "requesthandler_config.h"
-#include "hostfxr_utility.h"
+#include "HostFxrResolver.h"
 #include "config_utility.h"
 #include "environmentvariablehash.h"
 #include "iapplication.h"

+ 0 - 2
src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/InProcessApplicationBase.h

@@ -5,8 +5,6 @@
 
 #include "AppOfflineTrackingApplication.h"
 
-typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]); // TODO these may need to be BSTRs
-
 class InProcessApplicationBase : public AppOfflineTrackingApplication
 {
 public:

+ 60 - 50
src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp

@@ -3,7 +3,7 @@
 
 #include "inprocessapplication.h"
 #include "inprocesshandler.h"
-#include "hostfxroptions.h"
+#include "HostFxrResolutionResult.h"
 #include "requesthandler_config.h"
 #include "environmentvariablehelpers.h"
 #include "exceptions.h"
@@ -12,6 +12,7 @@
 #include "EventLog.h"
 #include "ModuleHelpers.h"
 #include "Environment.h"
+#include "HostFxr.h"
 
 IN_PROCESS_APPLICATION*  IN_PROCESS_APPLICATION::s_Application = NULL;
 
@@ -34,6 +35,8 @@ IN_PROCESS_APPLICATION::IN_PROCESS_APPLICATION(
     {
         m_dotnetExeKnownLocation = knownLocation;
     }
+
+    m_stringRedirectionOutput = std::make_shared<StringStreamRedirectionOutput>();
 }
 
 IN_PROCESS_APPLICATION::~IN_PROCESS_APPLICATION()
@@ -170,45 +173,27 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
 {
     try
     {
-        std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions;
+        std::unique_ptr<HostFxrResolutionResult> hostFxrResolutionResult;
 
         auto context = std::make_shared<ExecuteClrContext>();
 
-        auto pProc = s_fMainCallback;
-        if (pProc == nullptr)
+        if (s_fMainCallback == nullptr)
         {
-            HMODULE hModule;
-            // hostfxr should already be loaded by the shim. If not, then we will need
-            // to load it ourselves by finding hostfxr again.
-            THROW_LAST_ERROR_IF_NULL(hModule = GetModuleHandle(L"hostfxr.dll"));
-
-            // Get the entry point for main
-            pProc = reinterpret_cast<hostfxr_main_fn>(GetProcAddress(hModule, "hostfxr_main"));
-            THROW_LAST_ERROR_IF_NULL(pProc);
-
-            THROW_IF_FAILED(HOSTFXR_OPTIONS::Create(
+            THROW_IF_FAILED(HostFxrResolutionResult::Create(
                 m_dotnetExeKnownLocation,
                 m_pConfig->QueryProcessPath(),
                 QueryApplicationPhysicalPath(),
                 m_pConfig->QueryArguments(),
-                hostFxrOptions
+                hostFxrResolutionResult
                 ));
 
-            hostFxrOptions->GetArguments(context->m_argc, context->m_argv);
+            hostFxrResolutionResult->GetArguments(context->m_argc, context->m_argv);
             THROW_IF_FAILED(SetEnvironmentVariablesOnWorkerProcess());
+            context->m_hostFxr = HostFxr::CreateFromLoadedModule();
         }
-        context->m_pProc = pProc;
-
-        if (m_pLoggerProvider == nullptr)
+        else
         {
-            THROW_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
-                m_pConfig->QueryStdoutLogEnabled(),
-                !m_pHttpServer.IsCommandLineLaunch(),
-                m_pConfig->QueryStdoutLogFile().c_str(),
-                QueryApplicationPhysicalPath().c_str(),
-                m_pLoggerProvider));
-
-            m_pLoggerProvider->TryStartRedirection();
+            context->m_hostFxr = HostFxr(s_fMainCallback, nullptr, nullptr);
         }
 
         // There can only ever be a single instance of .NET Core
@@ -238,36 +223,52 @@ IN_PROCESS_APPLICATION::ExecuteApplication()
             LOG_INFOF(L"Setting current directory to %s", this->QueryApplicationPhysicalPath().c_str());
         }
 
-        //Start CLR thread
-        m_clrThread = std::thread(ClrThreadEntryPoint, context);
+        bool clrThreadExited;
 
-        // Wait for thread exit or shutdown event
-        const HANDLE waitHandles[2] = { m_pShutdownEvent, m_clrThread.native_handle() };
+        {
+            auto redirectionOutput = LoggingHelpers::CreateOutputs(
+                    m_pConfig->QueryStdoutLogEnabled(),
+                    m_pConfig->QueryStdoutLogFile(),
+                    QueryApplicationPhysicalPath(),
+                    m_stringRedirectionOutput
+                );
 
-        // Wait for shutdown request
-        const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
-        THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
+            StandardStreamRedirection redirection(*redirectionOutput.get(), m_pHttpServer.IsCommandLineLaunch());
 
-        LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
+            context->m_redirectionOutput = redirectionOutput.get();
+
+            //Start CLR thread
+            m_clrThread = std::thread(ClrThreadEntryPoint, context);
+
+            // Wait for thread exit or shutdown event
+            const HANDLE waitHandles[2] = { m_pShutdownEvent, m_clrThread.native_handle() };
+
+            // Wait for shutdown request
+            const auto waitResult = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
+
+            // Disconnect output
+            context->m_redirectionOutput = nullptr;
 
-        bool clrThreadExited = waitResult == (WAIT_OBJECT_0 + 1);
-        // shutdown was signaled
-        // only wait for shutdown in case of successful startup
-        if (m_waitForShutdown)
-        {
-            const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
             THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
 
-            clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
-        }
+            LOG_INFOF(L"Starting shutdown sequence %d", waitResult);
+
+            clrThreadExited = waitResult == (WAIT_OBJECT_0 + 1);
+            // shutdown was signaled
+            // only wait for shutdown in case of successful startup
+            if (m_waitForShutdown)
+            {
+                const auto clrWaitResult = WaitForSingleObject(m_clrThread.native_handle(), m_pConfig->QueryShutdownTimeLimitInMS());
+                THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED);
 
-        LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited);
+                clrThreadExited = clrWaitResult != WAIT_TIMEOUT;
+            }
+            LOG_INFOF(L"Clr thread wait ended: clrThreadExited: %d", clrThreadExited);
+        }
 
         // At this point CLR thread either finished or timed out, abandon it.
         m_clrThread.detach();
 
-        m_pLoggerProvider->TryStopRedirection();
-
         if (m_fStopCalled)
         {
             if (clrThreadExited)
@@ -390,7 +391,7 @@ IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& con
 {
     __try
     {
-        auto const exitCode = context->m_pProc(context->m_argc, context->m_argv.get());
+        auto const exitCode = context->m_hostFxr.Main(context->m_argc, context->m_argv.get());
 
         LOG_INFOF(L"Managed application exited with code %d", exitCode);
 
@@ -412,13 +413,22 @@ IN_PROCESS_APPLICATION::ExecuteClr(const std::shared_ptr<ExecuteClrContext>& con
 VOID
 IN_PROCESS_APPLICATION::ClrThreadEntryPoint(const std::shared_ptr<ExecuteClrContext> &context)
 {
+    HandleWrapper<ModuleHandleTraits> moduleHandle;
+
     // Keep aspnetcorev2_inprocess.dll loaded while this thread is running
     // this is required because thread might be abandoned
-    HandleWrapper<ModuleHandleTraits> moduleHandle;
     ModuleHelpers::IncrementCurrentModuleRefCount(moduleHandle);
 
-    ExecuteClr(context);
+    // Nested block is required here because FreeLibraryAndExitThread would prevent destructors from running
+    // so we need to do in in a nested scope
+    {
+        // We use forwarder here instead of context->m_errorWriter itself to be able to
+        // disconnect listener before CLR exits
+        ForwardingRedirectionOutput redirectionForwarder(&context->m_redirectionOutput);
+        const auto redirect = context->m_hostFxr.RedirectOutput(&redirectionForwarder);
 
+        ExecuteClr(context);
+    }
     FreeLibraryAndExitThread(moduleHandle.release(), 0);
 }
 
@@ -446,7 +456,7 @@ IN_PROCESS_APPLICATION::SetEnvironmentVariablesOnWorkerProcess()
 VOID
 IN_PROCESS_APPLICATION::UnexpectedThreadExit(const ExecuteClrContext& context) const
 {
-    auto content = m_pLoggerProvider->GetStdOutContent();
+    auto content = m_stringRedirectionOutput->GetOutput();
 
     if (context.m_exceptionCode != 0)
     {

+ 11 - 5
src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.h

@@ -6,7 +6,7 @@
 #include <thread>
 #include "InProcessApplicationBase.h"
 #include "InProcessOptions.h"
-#include "BaseOutputManager.h"
+#include "HostFxr.h"
 
 class IN_PROCESS_HANDLER;
 typedef REQUEST_NOTIFICATION_STATUS(WINAPI * PFN_REQUEST_HANDLER) (IN_PROCESS_HANDLER* pInProcessHandler, void* pvRequestHandlerContext);
@@ -115,16 +115,22 @@ private:
     {
         ExecuteClrContext():
             m_argc(0),
-            m_pProc(nullptr),
+            m_hostFxr(nullptr, nullptr, nullptr),
             m_exitCode(0),
             m_exceptionCode(0)
         {
         }
 
+        ~ExecuteClrContext()
+        {
+            m_redirectionOutput = nullptr;
+        }
+
         DWORD m_argc;
-        std::unique_ptr<PCWSTR[]>   m_argv;
-        hostfxr_main_fn m_pProc;
+        std::unique_ptr<PCWSTR[]> m_argv;
 
+        HostFxr m_hostFxr;
+        RedirectionOutput* m_redirectionOutput;
         int m_exitCode;
         int m_exceptionCode;
     };
@@ -159,7 +165,7 @@ private:
 
     static IN_PROCESS_APPLICATION*  s_Application;
 
-    std::unique_ptr<BaseOutputManager> m_pLoggerProvider;
+    std::shared_ptr<StringStreamRedirectionOutput> m_stringRedirectionOutput;
 
     inline static const LPCSTR      s_exeLocationParameterName = "InProcessExeLocation";
 

+ 0 - 1
src/Servers/IIS/AspNetCoreModuleV2/InProcessRequestHandler/inprocesshandler.cpp

@@ -3,7 +3,6 @@
 
 #include "inprocesshandler.h"
 #include "inprocessapplication.h"
-#include "IOutputManager.h"
 #include "ShuttingDownApplication.h"
 #include "ntassert.h"
 

+ 2 - 2
src/Servers/IIS/IIS/test/Common.FunctionalTests/LogFileTests.cs

@@ -78,8 +78,8 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
             StopServer();
             if (variant.HostingModel == HostingModel.InProcess)
             {
-                EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not start stdout redirection in (.*)aspnetcorev2.dll. Exception message: HRESULT 0x80070003");
-                EventLogHelpers.VerifyEventLogEvent(deploymentResult, "Could not stop stdout redirection in (.*)aspnetcorev2.dll. Exception message: HRESULT 0x80070002");
+                // Error is getting logged twice, from shim and handler
+                EventLogHelpers.VerifyEventLogEvent(deploymentResult, EventLogHelpers.CouldNotStartStdoutFileRedirection("Q:\\std", deploymentResult), allowMultiple: true);
             }
         }
 

+ 12 - 6
src/Servers/IIS/IIS/test/Common.FunctionalTests/Utilities/EventLogHelpers.cs

@@ -15,12 +15,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
 {
     public class EventLogHelpers
     {
-        public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString)
+        public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString, bool allowMultiple = false)
         {
             Assert.True(deploymentResult.HostProcess.HasExited);
 
             var entries = GetEntries(deploymentResult);
-            AssertSingleEntry(expectedRegexMatchString, entries);
+            AssertEntry(expectedRegexMatchString, entries, allowMultiple);
         }
 
         public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, string expectedRegexMatchString, ILogger logger)
@@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
             var entries = GetEntries(deploymentResult);
             try
             {
-                AssertSingleEntry(expectedRegexMatchString, entries);
+                AssertEntry(expectedRegexMatchString, entries);
             }
             catch (Exception ex)
             {
@@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
             var entries = GetEntries(deploymentResult).ToList();
             foreach (var regexString in expectedRegexMatchString)
             {
-                var matchedEntries = AssertSingleEntry(regexString, entries);
+                var matchedEntries = AssertEntry(regexString, entries);
 
                 foreach (var matchedEntry in matchedEntries)
                 {
@@ -60,12 +60,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
             Assert.True(0 == entries.Count, $"Some entries were not matched by any regex {FormatEntries(entries)}");
         }
 
-        private static EventLogEntry[] AssertSingleEntry(string regexString, IEnumerable<EventLogEntry> entries)
+        private static EventLogEntry[] AssertEntry(string regexString, IEnumerable<EventLogEntry> entries, bool allowMultiple = false)
         {
             var expectedRegex = new Regex(regexString, RegexOptions.Singleline);
             var matchedEntries = entries.Where(entry => expectedRegex.IsMatch(entry.Message)).ToArray();
             Assert.True(matchedEntries.Length > 0, $"No entries matched by '{regexString}'");
-            Assert.True(matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}");
+            Assert.True(allowMultiple || matchedEntries.Length < 2, $"Multiple entries matched by '{regexString}': {FormatEntries(matchedEntries)}");
             return matchedEntries;
         }
 
@@ -197,6 +197,12 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
             return "Could not find the assembly '(.*)' referenced for the in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application.";
         }
 
+        public static string CouldNotStartStdoutFileRedirection(string file, IISDeploymentResult deploymentResult)
+        {
+            return
+                $"Could not start stdout file redirection to '{Regex.Escape(file)}' with application base '{EscapedContentRoot(deploymentResult)}'.";
+        }
+
         private static string EscapedContentRoot(IISDeploymentResult deploymentResult)
         {
             var contentRoot = deploymentResult.ContentRoot;

+ 0 - 2
src/Servers/IIS/IIS/test/IIS.Shared.FunctionalTests/Inprocess/StdOutRedirectionTests.cs

@@ -25,7 +25,6 @@ namespace IIS.FunctionalTests.Inprocess
         }
 
         [ConditionalFact]
-        [SkipIfDebug]
         public async Task FrameworkNotFoundExceptionLogged_Pipe()
         {
             var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true);
@@ -44,7 +43,6 @@ namespace IIS.FunctionalTests.Inprocess
         }
 
         [ConditionalFact]
-        [SkipIfDebug]
         public async Task FrameworkNotFoundExceptionLogged_File()
         {
             var deploymentParameters =

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

@@ -6,10 +6,9 @@ param($Mode)
 
 Remove-Item "HKLM:\SOFTWARE\Microsoft\IIS Extensions\IIS AspNetCore Module V2\Parameters" -ErrorAction Ignore;
 
-$DumpFolder = "$env:ASPNETCORE_TEST_LOG_DIR\dumps"
 if (!($DumpFolder))
 {
-    $DumpFolder = "$PSScriptRoot\..\artifacts\dumps"
+    $DumpFolder = "$PSScriptRoot\..\..\..\..\artifacts\dumps"
 }
 if (!(Test-Path $DumpFolder))
 {