diff --git a/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp b/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp index fbed8b09e9..2c79f60a85 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/HandlerResolver.cpp @@ -108,7 +108,7 @@ HandlerResolver::LoadRequestHandlerAssembly(IHttpApplication &pApplication, Shim } } - LOG_INFOF("Loading request handler: %S", handlerDllPath.c_str()); + LOG_INFOF(L"Loading request handler: '%ls'", handlerDllPath.c_str()); hRequestHandlerDll = LoadLibrary(handlerDllPath.c_str()); if (preventUnload) diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp index e250045677..9f2b20cf8e 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationinfo.cpp @@ -34,7 +34,7 @@ APPLICATION_INFO::GetOrCreateApplication( { if (m_pApplication->QueryStatus() == RECYCLED) { - LOG_INFO("Application went offline"); + LOG_INFO(L"Application went offline"); // Call to wait for application to complete stopping m_pApplication->Stop(/* fServerInitiated */ false); @@ -50,14 +50,14 @@ APPLICATION_INFO::GetOrCreateApplication( if (AppOfflineApplication::ShouldBeStarted(httpApplication)) { - LOG_INFO("Detected app_offline file, creating polling application"); + LOG_INFO(L"Detected app_offline file, creating polling application"); m_pApplication.reset(new AppOfflineApplication(httpApplication)); } else { FINISHED_IF_FAILED(m_handlerResolver.GetApplicationFactory(httpApplication, m_pApplicationFactory)); - LOG_INFO("Creating handler application"); + LOG_INFO(L"Creating handler application"); IAPPLICATION * newApplication; FINISHED_IF_FAILED(m_pApplicationFactory->Execute( &m_pServer, @@ -97,7 +97,7 @@ APPLICATION_INFO::ShutDownApplication(bool fServerInitiated) if (m_pApplication) { - LOG_ERRORF("Stopping application %S", QueryApplicationInfoKey().c_str()); + LOG_ERRORF(L"Stopping application '%ls'", QueryApplicationInfoKey().c_str()); m_pApplication ->Stop(fServerInitiated); m_pApplication = nullptr; m_pApplicationFactory = nullptr; diff --git a/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp b/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp index 36fc9a96ba..7b9df85a43 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/applicationmanager.cpp @@ -142,7 +142,7 @@ APPLICATION_MANAGER::RecycleApplicationFromManager( } catch (...) { - LOG_ERRORF("Failed to stop application %S", application->QueryApplicationInfoKey().c_str()); + LOG_ERRORF(L"Failed to stop application '%ls'", application->QueryApplicationInfoKey().c_str()); OBSERVE_CAUGHT_EXCEPTION(); // Failed to recycle an application. Log an event diff --git a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp index 0a9c0ccf02..755b1ffc79 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp +++ b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.cpp @@ -22,7 +22,7 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening( { UNREFERENCED_PARAMETER(pProvider); - LOG_INFO("ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening"); + LOG_INFO(L"ASPNET_CORE_GLOBAL_MODULE::OnGlobalStopListening"); if (g_fInShutdown) { @@ -56,7 +56,7 @@ ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange( // Retrieve the path that has changed. PCWSTR pwszChangePath = pProvider->GetChangePath(); - LOG_INFOF("ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange %S", pwszChangePath); + LOG_INFOF(L"ASPNET_CORE_GLOBAL_MODULE::OnGlobalConfigurationChange '%ls'", pwszChangePath); // Test for an error. if (NULL != pwszChangePath && diff --git a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h index b790ebb0f5..13b6f86bea 100644 --- a/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h +++ b/src/AspNetCoreModuleV2/AspNetCore/globalmodule.h @@ -20,7 +20,7 @@ public: VOID Terminate() { - LOG_INFO("ASPNET_CORE_GLOBAL_MODULE::Terminate"); + LOG_INFO(L"ASPNET_CORE_GLOBAL_MODULE::Terminate"); // Remove the class from memory. delete this; } diff --git a/src/AspNetCoreModuleV2/CommonLib/EventLog.cpp b/src/AspNetCoreModuleV2/CommonLib/EventLog.cpp index fe97805a39..a8b32849ff 100644 --- a/src/AspNetCoreModuleV2/CommonLib/EventLog.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/EventLog.cpp @@ -42,7 +42,7 @@ EventLog::LogEvent( ); } - DebugPrintf(dwEventInfoType == EVENTLOG_ERROR_TYPE ? ASPNETCORE_DEBUG_FLAG_ERROR : ASPNETCORE_DEBUG_FLAG_INFO, "Event Log: %S \r\nEnd Event Log Message.", pstrMsg); + DebugPrintfW(dwEventInfoType == EVENTLOG_ERROR_TYPE ? ASPNETCORE_DEBUG_FLAG_ERROR : ASPNETCORE_DEBUG_FLAG_INFO, L"Event Log: '%ls' \r\nEnd Event Log Message.", pstrMsg); } VOID diff --git a/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp b/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp index 54fb162fe4..8ddfcb437f 100644 --- a/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/PipeOutputManager.cpp @@ -132,7 +132,7 @@ HRESULT PipeOutputManager::Stop() if (!LOG_LAST_ERROR_IF(GetExitCodeThread(m_hErrThread, &dwThreadStatus) == 0) && dwThreadStatus == STILL_ACTIVE) { - LOG_WARN("Thread reading stdout/err hit timeout, forcibly closing thread."); + LOG_WARN(L"Thread reading stdout/err hit timeout, forcibly closing thread."); TerminateThread(m_hErrThread, STATUS_CONTROL_C_EXIT); } } diff --git a/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp b/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp index 3988d78796..964c411bf9 100644 --- a/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/debugutil.cpp @@ -23,7 +23,7 @@ HRESULT PrintDebugHeader() { // Major, minor are stored in dwFileVersionMS field and patch, build in dwFileVersionLS field as pair of 32 bit numbers - DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, "Initializing logs for %S. %S. %S.", + DebugPrintfW(ASPNETCORE_DEBUG_FLAG_INFO, L"Initializing logs for '%ls'. %ls. %ls.", GetModuleName().c_str(), GetProcessIdString().c_str(), GetVersionInfoString().c_str() @@ -86,7 +86,7 @@ std::wstring GetModuleName() { WCHAR path[MAX_PATH]; - LOG_LAST_ERROR_IF(GetModuleFileName(g_hModule, path, sizeof(path))); + LOG_LAST_ERROR_IF(!GetModuleFileName(g_hModule, path, sizeof(path))); return path; } @@ -146,7 +146,7 @@ bool CreateDebugLogFile(const std::wstring &debugOutputFile) { if (g_logFile != INVALID_HANDLE_VALUE) { - LOG_INFOF("Switching debug log files to %S", debugOutputFile.c_str()); + LOG_INFOF(L"Switching debug log files to '%ls'", debugOutputFile.c_str()); } SRWExclusiveLock lock(g_logFileLock); @@ -296,19 +296,29 @@ IsEnabled( return ( dwFlag & DEBUG_FLAGS_VAR ); } +void WriteFileEncoded(UINT codePage, HANDLE hFile, const LPCWSTR szString) +{ + DWORD nBytesWritten = 0; + auto const encodedByteCount = WideCharToMultiByte(codePage, 0, szString, -1, nullptr, 0, nullptr, nullptr); + auto encodedBytes = std::shared_ptr(new CHAR[encodedByteCount]); + WideCharToMultiByte(codePage, 0, szString, -1, encodedBytes.get(), encodedByteCount, nullptr, nullptr); + + WriteFile(hFile, encodedBytes.get(), encodedByteCount - 1, &nBytesWritten, nullptr); +} + VOID -DebugPrint( +DebugPrintW( DWORD dwFlag, - const LPCSTR szString + const LPCWSTR szString ) { - STACK_STRA (strOutput, 256); + STACK_STRU (strOutput, 256); HRESULT hr = S_OK; if ( IsEnabled( dwFlag ) ) { - hr = strOutput.SafeSnprintf( - "[%s] %s\r\n", + hr = strOutput.SafeSnwprintf( + L"[%S] %s\r\n", DEBUG_LABEL_VAR, szString ); if (FAILED (hr)) @@ -316,22 +326,68 @@ DebugPrint( return; } - OutputDebugStringA( strOutput.QueryStr() ); - DWORD nBytesWritten = 0; - if (IsEnabled(ASPNETCORE_DEBUG_FLAG_CONSOLE)) + OutputDebugString( strOutput.QueryStr() ); + + if (IsEnabled(ASPNETCORE_DEBUG_FLAG_CONSOLE) || g_logFile != INVALID_HANDLE_VALUE) { - auto outputHandle = GetStdHandle(STD_OUTPUT_HANDLE); - WriteFile(outputHandle, strOutput.QueryStr(), strOutput.QueryCB(), &nBytesWritten, nullptr); + if (IsEnabled(ASPNETCORE_DEBUG_FLAG_CONSOLE)) + { + WriteFileEncoded(GetConsoleOutputCP(), GetStdHandle(STD_OUTPUT_HANDLE), strOutput.QueryStr()); + } + + if (g_logFile != INVALID_HANDLE_VALUE) + { + SRWExclusiveLock lock(g_logFileLock); + + SetFilePointer(g_logFile, 0, nullptr, FILE_END); + WriteFileEncoded(CP_UTF8, g_logFile, strOutput.QueryStr()); + FlushFileBuffers(g_logFile); + } + } + } +} + +VOID +DebugPrintfW( + DWORD dwFlag, + const LPCWSTR szFormat, + ... + ) +{ + STACK_STRU (strCooked,256); + + va_list args; + HRESULT hr = S_OK; + + if ( IsEnabled( dwFlag ) ) + { + va_start( args, szFormat ); + + hr = strCooked.SafeVsnwprintf(szFormat, args ); + + va_end( args ); + + if (FAILED (hr)) + { + return; } - if (g_logFile != INVALID_HANDLE_VALUE) - { - SRWExclusiveLock lock(g_logFileLock); + DebugPrintW( dwFlag, strCooked.QueryStr() ); + } +} - SetFilePointer(g_logFile, 0, nullptr, FILE_END); - WriteFile(g_logFile, strOutput.QueryStr(), strOutput.QueryCB(), &nBytesWritten, nullptr); - FlushFileBuffers(g_logFile); - } +VOID +DebugPrint( + DWORD dwFlag, + const LPCSTR szString + ) +{ + STACK_STRU (strOutput, 256); + + if ( IsEnabled( dwFlag ) ) + { + strOutput.CopyA(szString); + DebugPrintW(dwFlag, strOutput.QueryStr()); } } diff --git a/src/AspNetCoreModuleV2/CommonLib/debugutil.h b/src/AspNetCoreModuleV2/CommonLib/debugutil.h index 611d00d53d..f8c7a9b7c9 100644 --- a/src/AspNetCoreModuleV2/CommonLib/debugutil.h +++ b/src/AspNetCoreModuleV2/CommonLib/debugutil.h @@ -14,14 +14,14 @@ #define ASPNETCORE_DEBUG_FLAG_CONSOLE 0x00000008 #define ASPNETCORE_DEBUG_FLAG_FILE 0x00000010 -#define LOG_INFO(...) DebugPrint(ASPNETCORE_DEBUG_FLAG_INFO, __VA_ARGS__) -#define LOG_INFOF(...) DebugPrintf(ASPNETCORE_DEBUG_FLAG_INFO, __VA_ARGS__) +#define LOG_INFO(...) DebugPrintW(ASPNETCORE_DEBUG_FLAG_INFO, __VA_ARGS__) +#define LOG_INFOF(...) DebugPrintfW(ASPNETCORE_DEBUG_FLAG_INFO, __VA_ARGS__) -#define LOG_WARN(...) DebugPrint(ASPNETCORE_DEBUG_FLAG_WARNING, __VA_ARGS__) -#define LOG_WARNF(...) DebugPrintf(ASPNETCORE_DEBUG_FLAG_WARNING, __VA_ARGS__) +#define LOG_WARN(...) DebugPrintW(ASPNETCORE_DEBUG_FLAG_WARNING, __VA_ARGS__) +#define LOG_WARNF(...) DebugPrintfW(ASPNETCORE_DEBUG_FLAG_WARNING, __VA_ARGS__) -#define LOG_ERROR(...) DebugPrint(ASPNETCORE_DEBUG_FLAG_ERROR, __VA_ARGS__) -#define LOG_ERRORF(...) DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, __VA_ARGS__) +#define LOG_ERROR(...) DebugPrintW(ASPNETCORE_DEBUG_FLAG_ERROR, __VA_ARGS__) +#define LOG_ERRORF(...) DebugPrintfW(ASPNETCORE_DEBUG_FLAG_ERROR, __VA_ARGS__) VOID DebugInitialize(HMODULE hModule); @@ -37,6 +37,20 @@ IsEnabled( DWORD dwFlag ); +VOID +DebugPrintW( + DWORD dwFlag, + LPCWSTR szString + ); + +VOID +DebugPrintfW( + DWORD dwFlag, + LPCWSTR szFormat, + ... + ); + + VOID DebugPrint( DWORD dwFlag, diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp index 223cb38ab4..eb82490ced 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.cpp @@ -23,19 +23,16 @@ HOSTFXR_UTILITY::GetHostFxrParameters( std::vector &arguments ) { - LOG_INFOF("Resolving hostfxr parameters for application: '%S' arguments: '%S' path: '%S'", + LOG_INFOF(L"Resolving hostfxr parameters for application: '%ls' arguments: '%ls' path: '%ls'", processPath.c_str(), applicationArguments.c_str(), applicationPhysicalPath.c_str()); + arguments = std::vector(); fs::path expandedProcessPath = Environment::ExpandEnvironmentVariables(processPath); const auto expandedApplicationArguments = Environment::ExpandEnvironmentVariables(applicationArguments); - LOG_INFOF("Expanded hostfxr parameters for application: '%S' arguments: '%S'", - expandedProcessPath.c_str(), - expandedApplicationArguments.c_str()); - - LOG_INFOF("Known dotnet.exe location: '%S'", dotnetExePath.c_str()); + LOG_INFOF(L"Known dotnet.exe location: '%ls'", dotnetExePath.c_str()); if (!expandedProcessPath.has_extension()) { @@ -50,7 +47,12 @@ HOSTFXR_UTILITY::GetHostFxrParameters( // Check if the absolute path is to dotnet or not. if (IsDotnetExecutable(expandedProcessPath)) { - LOG_INFOF("Process path '%S' is dotnet, treating application as portable", expandedProcessPath.c_str()); + LOG_INFOF(L"Process path '%ls' is dotnet, treating application as portable", expandedProcessPath.c_str()); + + if (applicationArguments.empty()) + { + throw StartupParametersResolutionException(L"Application arguments are empty."); + } if (dotnetExePath.empty()) { @@ -59,16 +61,16 @@ HOSTFXR_UTILITY::GetHostFxrParameters( hostFxrDllPath = GetAbsolutePathToHostFxr(dotnetExePath); - ParseHostfxrArguments( + arguments.push_back(dotnetExePath); + AppendArguments( expandedApplicationArguments, - dotnetExePath, applicationPhysicalPath, arguments, true); } else { - LOG_INFOF("Process path '%S' is not dotnet, treating application as standalone or portable with bootstrapper", expandedProcessPath.c_str()); + LOG_INFOF(L"Process path '%ls' is not dotnet, treating application as standalone or portable with bootstrapper", expandedProcessPath.c_str()); auto executablePath = expandedProcessPath; @@ -87,21 +89,23 @@ HOSTFXR_UTILITY::GetHostFxrParameters( auto applicationDllPath = executablePath; applicationDllPath.replace_extension(".dll"); - LOG_INFOF("Checking application.dll at %S", applicationDllPath.c_str()); + LOG_INFOF(L"Checking application.dll at '%ls'", applicationDllPath.c_str()); if (!is_regular_file(applicationDllPath)) { throw StartupParametersResolutionException(format(L"Application .dll was not found at %s", applicationDllPath.c_str())); } hostFxrDllPath = executablePath.parent_path() / "hostfxr.dll"; - LOG_INFOF("Checking hostfxr.dll at %S", hostFxrDllPath.c_str()); + LOG_INFOF(L"Checking hostfxr.dll at '%ls'", hostFxrDllPath.c_str()); if (is_regular_file(hostFxrDllPath)) { - LOG_INFOF("hostfxr.dll found app local at '%S', treating application as standalone", hostFxrDllPath.c_str()); + LOG_INFOF(L"hostfxr.dll found app local at '%ls', treating application as standalone", hostFxrDllPath.c_str()); + // For standalone apps we need .exe to be argv[0], dll would be discovered next to it + arguments.push_back(executablePath); } else { - LOG_INFOF("hostfxr.dll found app local at '%S', treating application as portable with launcher", hostFxrDllPath.c_str()); + LOG_INFOF(L"hostfxr.dll found app local at '%ls', treating application as portable with launcher", hostFxrDllPath.c_str()); // passing "dotnet" here because we don't know where dotnet.exe should come from // so trying all fallbacks is appropriate @@ -109,13 +113,15 @@ HOSTFXR_UTILITY::GetHostFxrParameters( { dotnetExePath = GetAbsolutePathToDotnet(applicationPhysicalPath, L"dotnet"); } - executablePath = dotnetExePath; hostFxrDllPath = GetAbsolutePathToHostFxr(dotnetExePath); + + // For portable with launcher apps we need dotnet.exe to be argv[0] and .dll be argv[1] + arguments.push_back(dotnetExePath); + arguments.push_back(applicationDllPath); } - ParseHostfxrArguments( - applicationDllPath.generic_wstring() + L" " + expandedApplicationArguments, - executablePath, + AppendArguments( + expandedApplicationArguments, applicationPhysicalPath, arguments); } @@ -137,29 +143,49 @@ HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath) } void -HOSTFXR_UTILITY::ParseHostfxrArguments( +HOSTFXR_UTILITY::AppendArguments( const std::wstring &applicationArguments, - const fs::path &applicationExePath, const fs::path &applicationPhysicalPath, std::vector &arguments, bool expandDllPaths ) { - LOG_INFOF("Resolving hostfxr_main arguments application: '%S' arguments: '%S' path: %S", applicationExePath.c_str(), applicationArguments.c_str(), applicationPhysicalPath.c_str()); - - arguments = std::vector(); - arguments.push_back(applicationExePath); - if (applicationArguments.empty()) { - throw StartupParametersResolutionException(L"Application arguments are empty."); + return; + } + + // don't throw while trying to expand arguments + std::error_code ec; + + // Try to treat entire arguments section as a single path + if (expandDllPaths) + { + fs::path argumentAsPath = applicationArguments; + if (is_regular_file(argumentAsPath, ec)) + { + LOG_INFOF(L"Treating '%ls' as a single path argument", applicationArguments.c_str()); + arguments.push_back(argumentAsPath); + return; + } + + if (argumentAsPath.is_relative()) + { + argumentAsPath = applicationPhysicalPath / argumentAsPath; + if (is_regular_file(argumentAsPath, ec)) + { + LOG_INFOF(L"Converted argument '%ls' to '%ls'", applicationArguments.c_str(), argumentAsPath.c_str()); + arguments.push_back(argumentAsPath); + return; + } + } } int argc = 0; auto pwzArgs = std::unique_ptr(CommandLineToArgvW(applicationArguments.c_str(), &argc)); if (!pwzArgs) { - throw StartupParametersResolutionException(format(L"Unable parse command line arguments '%s' or '%s'", applicationArguments.c_str())); + throw StartupParametersResolutionException(format(L"Unable parse command line arguments '%s'", applicationArguments.c_str())); } for (int intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++) @@ -173,9 +199,9 @@ HOSTFXR_UTILITY::ParseHostfxrArguments( if (argumentAsPath.is_relative()) { argumentAsPath = applicationPhysicalPath / argumentAsPath; - if (exists(argumentAsPath)) + if (is_regular_file(argumentAsPath, ec)) { - LOG_INFOF("Converted argument '%S' to %S", argument.c_str(), argumentAsPath.c_str()); + LOG_INFOF(L"Converted argument '%ls' to '%ls'", argument.c_str(), argumentAsPath.c_str()); argument = argumentAsPath; } } @@ -183,11 +209,6 @@ HOSTFXR_UTILITY::ParseHostfxrArguments( arguments.push_back(argument); } - - for (size_t i = 0; i < arguments.size(); i++) - { - LOG_INFOF("Argument[%d] = %S", i, arguments[i].c_str()); - } } // The processPath ends with dotnet.exe or dotnet @@ -199,7 +220,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( const fs::path & requestedPath ) { - LOG_INFOF("Resolving absolute path to dotnet.exe from %S", requestedPath.c_str()); + LOG_INFOF(L"Resolving absolute path to dotnet.exe from '%ls'", requestedPath.c_str()); auto processPath = requestedPath; if (processPath.is_relative()) @@ -212,7 +233,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( // if (is_regular_file(processPath)) { - LOG_INFOF("Found dotnet.exe at %S", processPath.c_str()); + LOG_INFOF(L"Found dotnet.exe at '%ls'", processPath.c_str()); return processPath; } @@ -223,7 +244,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( // Only do it if no path is specified if (requestedPath.has_parent_path()) { - LOG_INFOF("Absolute path to dotnet.exe was not found at %S", requestedPath.c_str()); + LOG_INFOF(L"Absolute path to dotnet.exe was not found at '%ls'", requestedPath.c_str()); throw StartupParametersResolutionException(format(L"Could not find dotnet.exe at '%s'", processPath.c_str())); } @@ -231,7 +252,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( const auto dotnetViaWhere = InvokeWhereToFindDotnet(); if (dotnetViaWhere.has_value()) { - LOG_INFOF("Found dotnet.exe via where.exe invocation at %S", dotnetViaWhere.value().c_str()); + LOG_INFOF(L"Found dotnet.exe via where.exe invocation at '%ls'", dotnetViaWhere.value().c_str()); return dotnetViaWhere.value(); } @@ -239,12 +260,12 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet( const auto programFilesLocation = GetAbsolutePathToDotnetFromProgramFiles(); if (programFilesLocation.has_value()) { - LOG_INFOF("Found dotnet.exe in Program Files at %S", programFilesLocation.value().c_str()); + LOG_INFOF(L"Found dotnet.exe in Program Files at '%ls'", programFilesLocation.value().c_str()); return programFilesLocation.value(); } - LOG_INFOF("dotnet.exe not found"); + LOG_INFOF(L"dotnet.exe not found"); throw StartupParametersResolutionException(format( L"Could not find dotnet.exe at '%s' or using the system PATH environment variable." " Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process.", @@ -259,7 +280,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( std::vector versionFolders; const auto hostFxrBase = dotnetPath.parent_path() / "host" / "fxr"; - LOG_INFOF("Resolving absolute path to hostfxr.dll from %S", dotnetPath.c_str()); + LOG_INFOF(L"Resolving absolute path to hostfxr.dll from '%ls'", dotnetPath.c_str()); if (!is_directory(hostFxrBase)) { @@ -281,7 +302,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( throw StartupParametersResolutionException(format(L"hostfxr.dll not found at '%s'", hostFxrPath.c_str())); } - LOG_INFOF("hostfxr.dll located at %S", hostFxrPath.c_str()); + LOG_INFOF(L"hostfxr.dll located at '%ls'", hostFxrPath.c_str()); return hostFxrPath; } @@ -323,7 +344,7 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet() securityAttributes.lpSecurityDescriptor = NULL; securityAttributes.bInheritHandle = TRUE; - LOG_INFO("Invoking where.exe to find dotnet.exe"); + LOG_INFO(L"Invoking where.exe to find dotnet.exe"); // Create a read/write pipe that will be used for reading the result of where.exe FINISHED_LAST_ERROR_IF(!CreatePipe(&hStdOutReadPipe, &hStdOutWritePipe, &securityAttributes, 0)); @@ -398,7 +419,7 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet() FINISHED_IF_FAILED(struDotnetLocationsString.CopyA(pzFileContents, dwNumBytesRead)); - LOG_INFOF("where.exe invocation returned: %S", struDotnetLocationsString.QueryStr()); + LOG_INFOF(L"where.exe invocation returned: '%ls'", struDotnetLocationsString.QueryStr()); // Check the bitness of the currently running process // matches the dotnet.exe found. @@ -417,7 +438,7 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet() fIsCurrentProcess64Bit = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; } - LOG_INFOF("Current process bitness type detected as isX64=%d", fIsCurrentProcess64Bit); + LOG_INFOF(L"Current process bitness type detected as isX64=%d", fIsCurrentProcess64Bit); while (TRUE) { @@ -431,14 +452,14 @@ HOSTFXR_UTILITY::InvokeWhereToFindDotnet() // \r\n is two wchars, so add 2 here. prevIndex = index + 2; - LOG_INFOF("Processing entry %S", struDotnetSubstring.QueryStr()); + LOG_INFOF(L"Processing entry '%ls'", struDotnetSubstring.QueryStr()); if (LOG_LAST_ERROR_IF(!GetBinaryTypeW(struDotnetSubstring.QueryStr(), &dwBinaryType))) { continue; } - LOG_INFOF("Binary type %d", dwBinaryType); + LOG_INFOF(L"Binary type %d", dwBinaryType); if (fIsCurrentProcess64Bit == (dwBinaryType == SCS_64BIT_BINARY)) { diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h index e12153aaf6..d13b7e139e 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxr_utility.h @@ -45,9 +45,8 @@ public: static void - ParseHostfxrArguments( + AppendArguments( const std::wstring &arugments, - const std::filesystem::path &exePath, const std::filesystem::path &applicationPhysicalPath, std::vector &arguments, bool expandDllPaths = false diff --git a/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp b/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp index 7445724d98..9e1d8ab609 100644 --- a/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp +++ b/src/AspNetCoreModuleV2/CommonLib/hostfxroptions.cpp @@ -33,6 +33,11 @@ HRESULT HOSTFXR_OPTIONS::Create( 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(knownDotnetLocation, hostFxrDllPath, arguments); } catch (HOSTFXR_UTILITY::StartupParametersResolutionException &resolutionException) diff --git a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp index 0a07ccc616..5f43952287 100644 --- a/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp +++ b/src/AspNetCoreModuleV2/InProcessRequestHandler/inprocessapplication.cpp @@ -443,7 +443,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication( FINISHED_IF_FAILED(SetEnvironementVariablesOnWorkerProcess()); } - LOG_INFO("Starting managed application"); + LOG_INFO(L"Starting managed application"); if (m_pLoggerProvider == NULL) { @@ -547,12 +547,12 @@ IN_PROCESS_APPLICATION::RunDotnetApplication(DWORD argc, CONST PCWSTR* argv, hos hr = HRESULT_FROM_WIN32(GetLastError()); } - LOG_INFOF("Managed application exited with code %d", m_ProcessExitCode); + LOG_INFOF(L"Managed application exited with code %d", m_ProcessExitCode); } __except(GetExceptionCode() != 0) { - LOG_INFOF("Managed threw an exception %d", GetExceptionCode()); + LOG_INFOF(L"Managed threw an exception %d", GetExceptionCode()); hr = HRESULT_FROM_WIN32(GetLastError()); } diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp index f156ec1fbc..0b987bf8cd 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/AppOfflineTrackingApplication.cpp @@ -8,7 +8,7 @@ HRESULT AppOfflineTrackingApplication::StartMonitoringAppOffline() { - LOG_INFOF("Starting app_offline monitoring in application %S", m_applicationPath.c_str()); + LOG_INFOF(L"Starting app_offline monitoring in application '%ls'", m_applicationPath.c_str()); HRESULT hr = StartMonitoringAppOflineImpl(); if (FAILED_LOG(hr)) @@ -56,7 +56,7 @@ void AppOfflineTrackingApplication::OnAppOffline() return; } - LOG_INFOF("Received app_offline notification in application %S", m_applicationPath.c_str()); + LOG_INFOF(L"Received app_offline notification in application '%ls'", m_applicationPath.c_str()); EventLog::Info( ASPNETCORE_EVENT_RECYCLE_APPOFFLINE, ASPNETCORE_EVENT_RECYCLE_APPOFFLINE_MSG, diff --git a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp index 7c0aaccefa..9d17f95b37 100644 --- a/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp +++ b/src/AspNetCoreModuleV2/RequestHandlerLib/filewatcher.cpp @@ -137,7 +137,7 @@ Win32 error DWORD dwErrorStatus; ULONG_PTR completionKey; - LOG_INFO("Starting file watcher thread"); + LOG_INFO(L"Starting file watcher thread"); pFileMonitor = (FILE_WATCHER*)pvArg; DBG_ASSERT(pFileMonitor != NULL); @@ -177,7 +177,7 @@ Win32 error pFileMonitor->m_fThreadExit = TRUE; - LOG_INFO("Stopping file watcher thread"); + LOG_INFO(L"Stopping file watcher thread"); ExitThread(0); } diff --git a/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs b/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs index e0d49615a5..1a1a4ce490 100644 --- a/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs +++ b/test/Common.FunctionalTests/Utilities/FunctionalTestsBase.cs @@ -4,6 +4,7 @@ using System; using System.IO; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.Extensions.Logging.Testing; using Xunit.Abstractions; @@ -37,6 +38,13 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting return (IISDeploymentResult)await _deployer.DeployAsync(); } + protected virtual async Task StartAsync(IISDeploymentParameters parameters) + { + var result = await DeployAsync(parameters); + await result.AssertStarts(); + return result; + } + public override void Dispose() { StopServer(false); diff --git a/test/Common.FunctionalTests/Utilities/Helpers.cs b/test/Common.FunctionalTests/Utilities/Helpers.cs index 34fb88082a..9ff2fda815 100644 --- a/test/Common.FunctionalTests/Utilities/Helpers.cs +++ b/test/Common.FunctionalTests/Utilities/Helpers.cs @@ -65,12 +65,15 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { foreach (DirectoryInfo directoryInfo in source.GetDirectories()) { - CopyFiles(directoryInfo, target.CreateSubdirectory(directoryInfo.Name), logger); + if (directoryInfo.FullName != target.FullName) + { + CopyFiles(directoryInfo, target.CreateSubdirectory(directoryInfo.Name), logger); + } } - logger.LogDebug($"Processing {target.FullName}"); + logger?.LogDebug($"Processing {target.FullName}"); foreach (FileInfo fileInfo in source.GetFiles()) { - logger.LogDebug($" Copying {fileInfo.Name}"); + logger?.LogDebug($" Copying {fileInfo.Name}"); var destFileName = Path.Combine(target.FullName, fileInfo.Name); fileInfo.CopyTo(destFileName); } diff --git a/test/CommonLibTests/hostfxr_utility_tests.cpp b/test/CommonLibTests/hostfxr_utility_tests.cpp index 7d323868f8..a9beb49676 100644 --- a/test/CommonLibTests/hostfxr_utility_tests.cpp +++ b/test/CommonLibTests/hostfxr_utility_tests.cpp @@ -11,64 +11,58 @@ TEST(ParseHostFxrArguments, BasicHostFxrArguments) { std::vector bstrArray; - PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; - HOSTFXR_UTILITY::ParseHostfxrArguments( + HOSTFXR_UTILITY::AppendArguments( L"exec \"test.dll\"", // args - exeStr, // exe path L"invalid", // physical path to application bstrArray); // args array. - EXPECT_EQ(3, bstrArray.size()); - ASSERT_STREQ(exeStr, bstrArray[0].c_str()); - ASSERT_STREQ(L"exec", bstrArray[1].c_str()); - ASSERT_STREQ(L"test.dll", bstrArray[2].c_str()); + EXPECT_EQ(2, bstrArray.size()); + ASSERT_STREQ(L"exec", bstrArray[0].c_str()); + ASSERT_STREQ(L"test.dll", bstrArray[1].c_str()); } TEST(ParseHostFxrArguments, NoExecProvided) { std::vector bstrArray; - PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; - HOSTFXR_UTILITY::ParseHostfxrArguments( + HOSTFXR_UTILITY::AppendArguments( L"test.dll", // args - exeStr, // exe path L"ignored", // physical path to application bstrArray); // args array. - EXPECT_EQ(DWORD(2), bstrArray.size()); - ASSERT_STREQ(exeStr, bstrArray[0].c_str()); - ASSERT_STREQ(L"test.dll", bstrArray[1].c_str()); + EXPECT_EQ(1, bstrArray.size()); + ASSERT_STREQ(L"test.dll", bstrArray[0].c_str()); } TEST(ParseHostFxrArguments, ConvertDllToAbsolutePath) { std::vector bstrArray; - PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; // 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::ParseHostfxrArguments( + HOSTFXR_UTILITY::AppendArguments( L"exec \"ntdll.dll\"", // args - exeStr, // exe path system32, // physical path to application bstrArray, // args array. true); // expandDllPaths - EXPECT_EQ(DWORD(3), bstrArray.size()); - ASSERT_STREQ(exeStr, bstrArray[0].c_str()); - ASSERT_STREQ(L"exec", bstrArray[1].c_str()); - ASSERT_STREQ((system32 + L"\\ntdll.dll").c_str(), bstrArray[2].c_str()); + EXPECT_EQ(2, bstrArray.size()); + ASSERT_STREQ(L"exec", bstrArray[0].c_str()); + ASSERT_STREQ((system32 + L"\\ntdll.dll").c_str(), bstrArray[1].c_str()); } TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs) { std::vector bstrArray; - PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; + std::filesystem::path struHostFxrDllLocation; + std::filesystem::path struExeLocation; - ASSERT_THROW(HOSTFXR_UTILITY::ParseHostfxrArguments( - L"", // args - exeStr, // exe path - L"ignored", // physical path to application + EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters( + L"dotnet", // processPath + L"some\\path", // application physical path, ignored. + L"", //arguments + struHostFxrDllLocation, + struExeLocation, bstrArray), // args array. HOSTFXR_UTILITY::StartupParametersResolutionException); } diff --git a/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs b/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs index 55d399ef03..1953a6975a 100644 --- a/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs +++ b/test/IISExpress.FunctionalTests/InProcess/StartupTests.cs @@ -33,13 +33,10 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task ExpandEnvironmentVariableInWebConfig() { // Point to dotnet installed in user profile. - await AssertStarts( - deploymentParameters => - { - deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation; - deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "%DotnetPath%")); - } - ); + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + deploymentParameters.EnvironmentVariables["DotnetPath"] = _dotnetLocation; + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", "%DotnetPath%")); + await StartAsync(deploymentParameters); } [ConditionalTheory] @@ -68,26 +65,23 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [ConditionalFact] public async Task StartsWithDotnetLocationWithoutExe() { - var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")); + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); - await AssertStarts( - deploymentParameters => - { - deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", dotnetLocationWithoutExtension)); - } - ); + var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", dotnetLocationWithoutExtension)); + + await StartAsync(deploymentParameters); } [ConditionalFact] public async Task StartsWithDotnetLocationUppercase() { + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + var dotnetLocationWithoutExtension = _dotnetLocation.Substring(0, _dotnetLocation.LastIndexOf(".")).ToUpperInvariant(); - await AssertStarts( - deploymentParameters => - { - deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", dotnetLocationWithoutExtension)); - } - ); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", dotnetLocationWithoutExtension)); + + await StartAsync(deploymentParameters); } [ConditionalTheory] @@ -95,32 +89,18 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [InlineData("dotnet.EXE")] public async Task StartsWithDotnetOnThePath(string path) { - await AssertStarts( - deploymentParameters => - { - deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(_dotnetLocation); - deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path)); - } - ); + var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + + deploymentParameters.EnvironmentVariables["PATH"] = Path.GetDirectoryName(_dotnetLocation); + deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path)); + + var deploymentResult = await DeployAsync(deploymentParameters); + await deploymentResult.AssertStarts(); // Verify that in this scenario where.exe was invoked only once by shim and request handler uses cached value Assert.Equal(1, TestSink.Writes.Count(w => w.Message.Contains("Invoking where.exe to find dotnet.exe"))); } - private async Task AssertStarts(Action preDeploy = null) - { - var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); - - preDeploy?.Invoke(deploymentParameters); - - var deploymentResult = await DeployAsync(deploymentParameters); - - var response = await deploymentResult.HttpClient.GetAsync("HelloWorld"); - - var responseText = await response.Content.ReadAsStringAsync(); - Assert.Equal("Hello World", responseText); - } - public static TestMatrix TestVariants => TestMatrix.ForServers(DeployerSelector.ServerType) .WithTfms(Tfm.NetCoreApp22) @@ -132,13 +112,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests public async Task HelloWorld(TestVariant variant) { var deploymentParameters = _fixture.GetBaseDeploymentParameters(variant, publish: true); - - var deploymentResult = await DeployAsync(deploymentParameters); - - var response = await deploymentResult.HttpClient.GetAsync("/HelloWorld"); - var responseText = await response.Content.ReadAsStringAsync(); - - Assert.Equal("Hello World", responseText); + await StartAsync(deploymentParameters); } [ConditionalFact] @@ -194,7 +168,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests [ConditionalTheory] [MemberData(nameof(InvalidConfigTransformationsScenarios))] - public async Task StartsWithWebConfigVariationsPortable(string scenario) + public async Task ReportsWebConfigAuthoringErrors(string scenario) { var (expectedError, action) = InvalidConfigTransformations[scenario]; var iisDeploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); @@ -227,5 +201,148 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests )); return dictionary; } + + private static Dictionary> PortableConfigTransformations = InitPortableWebConfigTransformations(); + public static IEnumerable PortableConfigTransformationsScenarios => PortableConfigTransformations.ToTheoryData(); + + [ConditionalTheory] + [MemberData(nameof(PortableConfigTransformationsScenarios))] + public async Task StartsWithWebConfigVariationsPortable(string scenario) + { + var action = PortableConfigTransformations[scenario]; + var iisDeploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + var expectedArguments = action(iisDeploymentParameters); + var result = await DeployAsync(iisDeploymentParameters); + Assert.Equal(expectedArguments, await result.HttpClient.GetStringAsync("/CommandLineArgs")); + } + + public static Dictionary> InitPortableWebConfigTransformations() + { + var dictionary = new Dictionary>(); + var pathWithSpace = "\u03c0 \u2260 3\u00b714"; + + dictionary.Add("App in bin subdirectory full path to dll using exec and quotes", + parameters => { + MoveApplication(parameters, "bin"); + TransformArguments(parameters, (arguments, root) => "exec " + Path.Combine(root, "bin", arguments)); + return ""; + }); + + dictionary.Add("App in subdirectory with space", + parameters => { + MoveApplication(parameters, pathWithSpace); + TransformArguments(parameters, (arguments, root) => Path.Combine(pathWithSpace, arguments)); + return ""; + }); + + dictionary.Add("App in subdirectory with space and full path to dll", + parameters => { + MoveApplication(parameters, pathWithSpace); + TransformArguments(parameters, (arguments, root) => Path.Combine(root, pathWithSpace, arguments)); + return ""; + }); + + dictionary.Add("App in bin subdirectory with space full path to dll using exec and quotes", + parameters => { + MoveApplication(parameters, pathWithSpace); + TransformArguments(parameters, (arguments, root) => "exec \"" + Path.Combine(root, pathWithSpace, arguments) + "\" extra arguments"); + return "extra|arguments"; + }); + + dictionary.Add("App in bin subdirectory and quoted argument", + parameters => { + MoveApplication(parameters, "bin"); + TransformArguments(parameters, (arguments, root) => Path.Combine("bin", arguments) + " \"extra argument\""); + return "extra argument"; + }); + + dictionary.Add("App in bin subdirectory full path to dll", + parameters => { + MoveApplication(parameters, "bin"); + TransformArguments(parameters, (arguments, root) => Path.Combine(root, "bin", arguments) + " extra arguments"); + return "extra|arguments"; + }); + return dictionary; + } + + + private static Dictionary> StandaloneConfigTransformations = InitStandaloneConfigTransformations(); + public static IEnumerable StandaloneConfigTransformationsScenarios => StandaloneConfigTransformations.ToTheoryData(); + + [ConditionalTheory] + [MemberData(nameof(StandaloneConfigTransformationsScenarios))] + public async Task StartsWithWebConfigVariationsStandalone(string scenario) + { + var action = StandaloneConfigTransformations[scenario]; + var iisDeploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); + iisDeploymentParameters.ApplicationType = ApplicationType.Standalone; + var expectedArguments = action(iisDeploymentParameters); + var result = await DeployAsync(iisDeploymentParameters); + Assert.Equal(expectedArguments, await result.HttpClient.GetStringAsync("/CommandLineArgs")); + } + + public static Dictionary> InitStandaloneConfigTransformations() + { + var dictionary = new Dictionary>(); + var pathWithSpace = "\u03c0 \u2260 3\u00b714"; + + dictionary.Add("App in subdirectory", + parameters => { + MoveApplication(parameters, pathWithSpace); + TransformPath(parameters, (path, root) => Path.Combine(pathWithSpace, path)); + TransformArguments(parameters, (arguments, root) => "\"additional argument\""); + return "additional argument"; + }); + + dictionary.Add("App in bin subdirectory full path", + parameters => { + MoveApplication(parameters, pathWithSpace); + TransformPath(parameters, (path, root) => Path.Combine(root, pathWithSpace, path)); + TransformArguments(parameters, (arguments, root) => "additional arguments"); + return "additional|arguments"; + }); + + return dictionary; + } + + private static void MoveApplication( + IISDeploymentParameters parameters, + string subdirectory) + { + parameters.WebConfigActionList.Add((config, contentRoot) => + { + var source = new DirectoryInfo(contentRoot); + var subDirectoryPath = source.CreateSubdirectory(subdirectory); + + // Copy everything into a subfolder + Helpers.CopyFiles(source, subDirectoryPath, null); + // Cleanup files + foreach (var fileSystemInfo in source.GetFiles()) + { + fileSystemInfo.Delete(); + } + }); + } + + private static void TransformPath(IISDeploymentParameters parameters, Func transformation) + { + parameters.WebConfigActionList.Add( + (config, contentRoot) => + { + var aspNetCoreElement = config.Descendants("aspNetCore").Single(); + aspNetCoreElement.SetAttributeValue("processPath", transformation((string)aspNetCoreElement.Attribute("processPath"), contentRoot)); + }); + } + + private static void TransformArguments(IISDeploymentParameters parameters, Func transformation) + { + parameters.WebConfigActionList.Add( + (config, contentRoot) => + { + var aspNetCoreElement = config.Descendants("aspNetCore").Single(); + aspNetCoreElement.SetAttributeValue("arguments", transformation((string)aspNetCoreElement.Attribute("arguments"), contentRoot)); + }); + } + } } diff --git a/test/WebSites/InProcessWebSite/Startup.cs b/test/WebSites/InProcessWebSite/Startup.cs index 7142f627d3..08837417bf 100644 --- a/test/WebSites/InProcessWebSite/Startup.cs +++ b/test/WebSites/InProcessWebSite/Startup.cs @@ -3,6 +3,7 @@ using System; using System.IO; +using System.Linq; using System.Net; using System.Text; using System.Threading.Tasks; @@ -46,7 +47,7 @@ namespace IISTestSite private async Task HostingEnvironment(HttpContext context) { var hostingEnv = context.RequestServices.GetService(); - + await context.Response.WriteAsync("ContentRootPath "+hostingEnv.ContentRootPath + Environment.NewLine); await context.Response.WriteAsync("WebRootPath "+hostingEnv.WebRootPath + Environment.NewLine); await context.Response.WriteAsync("CurrentDirectory "+Environment.CurrentDirectory); @@ -762,5 +763,10 @@ namespace IISTestSite } await context.Response.WriteAsync("Response End"); } + + private async Task CommandLineArgs(HttpContext context) + { + await context.Response.WriteAsync(string.Join("|", Environment.GetCommandLineArgs().Skip(1))); + } } }