Support portable.exe apps and better error reporting (#1287)

This commit is contained in:
Pavel Krymets 2018-08-22 12:04:04 -07:00 committed by GitHub
parent 36e5aceb3c
commit eebbb6a602
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 415 additions and 595 deletions

View File

@ -48,7 +48,7 @@
</Target> </Target>
<!-- Deps file injection--> <!-- Deps file injection-->
<ItemGroup> <ItemGroup Condition="('$(InProcessTestSite)' == 'true') AND ('$(ReferenceTestTasks)' != 'false')">
<ProjectReference Include="$(MSBuildThisFileDirectory)..\test\TestTasks\TestTasks.csproj"> <ProjectReference Include="$(MSBuildThisFileDirectory)..\test\TestTasks\TestTasks.csproj">
<ReferenceOutputAssembly>False</ReferenceOutputAssembly> <ReferenceOutputAssembly>False</ReferenceOutputAssembly>
</ProjectReference> </ProjectReference>

View File

@ -60,7 +60,7 @@ HandlerResolver::LoadRequestHandlerAssembly(IHttpApplication &pApplication, ASPN
pConfiguration.QueryArguments().c_str(), pConfiguration.QueryArguments().c_str(),
options)); options));
location = options->GetExeLocation(); location = options->GetDotnetExeLocation();
RETURN_IF_FAILED(LoggingHelpers::CreateLoggingProvider( RETURN_IF_FAILED(LoggingHelpers::CreateLoggingProvider(
pConfiguration.QueryStdoutLogEnabled(), pConfiguration.QueryStdoutLogEnabled(),
@ -225,7 +225,7 @@ HandlerResolver::FindNativeAssemblyFromHostfxr(
DWORD dwBufferSize = s_initialGetNativeSearchDirectoriesBufferSize; DWORD dwBufferSize = s_initialGetNativeSearchDirectoriesBufferSize;
DWORD dwRequiredBufferSize = 0; DWORD dwRequiredBufferSize = 0;
RETURN_LAST_ERROR_IF_NULL(m_hHostFxrDll = LoadLibraryW(hostfxrOptions.GetHostFxrLocation())); RETURN_LAST_ERROR_IF_NULL(m_hHostFxrDll = LoadLibraryW(hostfxrOptions.GetHostFxrLocation().c_str()));
auto pFnHostFxrSearchDirectories = reinterpret_cast<hostfxr_get_native_search_directories_fn>(GetProcAddress(m_hHostFxrDll, "hostfxr_get_native_search_directories")); auto pFnHostFxrSearchDirectories = reinterpret_cast<hostfxr_get_native_search_directories_fn>(GetProcAddress(m_hHostFxrDll, "hostfxr_get_native_search_directories"));
if (pFnHostFxrSearchDirectories == nullptr) if (pFnHostFxrSearchDirectories == nullptr)
@ -233,7 +233,7 @@ HandlerResolver::FindNativeAssemblyFromHostfxr(
EventLog::Error( EventLog::Error(
ASPNETCORE_EVENT_GENERAL_ERROR, ASPNETCORE_EVENT_GENERAL_ERROR,
ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG, ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG,
hostfxrOptions.GetHostFxrLocation() hostfxrOptions.GetHostFxrLocation().c_str()
); );
RETURN_IF_FAILED(E_FAIL); RETURN_IF_FAILED(E_FAIL);
} }
@ -243,9 +243,13 @@ HandlerResolver::FindNativeAssemblyFromHostfxr(
while (TRUE) while (TRUE)
{ {
DWORD hostfxrArgc;
std::unique_ptr<PCWSTR[]> hostfxrArgv;
hostfxrOptions.GetArguments(hostfxrArgc, hostfxrArgv);
const auto intHostFxrExitCode = pFnHostFxrSearchDirectories( const auto intHostFxrExitCode = pFnHostFxrSearchDirectories(
hostfxrOptions.GetArgc(), hostfxrArgc,
hostfxrOptions.GetArgv(), hostfxrArgv.get(),
struNativeSearchPaths.data(), struNativeSearchPaths.data(),
dwBufferSize, dwBufferSize,
&dwRequiredBufferSize &dwRequiredBufferSize

View File

@ -219,6 +219,7 @@
<ClInclude Include="SRWExclusiveLock.h" /> <ClInclude Include="SRWExclusiveLock.h" />
<ClInclude Include="SRWSharedLock.h" /> <ClInclude Include="SRWSharedLock.h" />
<ClInclude Include="stdafx.h" /> <ClInclude Include="stdafx.h" />
<ClInclude Include="StringHelpers.h" />
<ClInclude Include="sttimer.h" /> <ClInclude Include="sttimer.h" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -243,6 +244,7 @@
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader> <PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
</ClCompile> </ClCompile>
<ClCompile Include="StringHelpers.cpp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\IISLib\IISLib.vcxproj"> <ProjectReference Include="..\IISLib\IISLib.vcxproj">

View File

@ -5,3 +5,4 @@
// Workaround for VC++ bug https://developercommunity.visualstudio.com/content/problem/33928/constexpr-failing-on-nullptr-v141-compiler-regress.html // Workaround for VC++ bug https://developercommunity.visualstudio.com/content/problem/33928/constexpr-failing-on-nullptr-v141-compiler-regress.html
const HANDLE InvalidHandleTraits::DefaultHandle = INVALID_HANDLE_VALUE; const HANDLE InvalidHandleTraits::DefaultHandle = INVALID_HANDLE_VALUE;
const HANDLE FindFileHandleTraits::DefaultHandle = INVALID_HANDLE_VALUE;

View File

@ -4,7 +4,7 @@
#pragma once #pragma once
#include <Windows.h> #include <Windows.h>
#include <ntassert.h> #include "ntassert.h"
struct InvalidHandleTraits struct InvalidHandleTraits
{ {
@ -20,6 +20,13 @@ struct NullHandleTraits
static void Close(HANDLE handle) { CloseHandle(handle); } static void Close(HANDLE handle) { CloseHandle(handle); }
}; };
struct FindFileHandleTraits
{
using HandleType = HANDLE;
static const HANDLE DefaultHandle;
static void Close(HANDLE handle) { FindClose(handle); }
};
struct ModuleHandleTraits struct ModuleHandleTraits
{ {
using HandleType = HMODULE; using HandleType = HMODULE;

View File

@ -0,0 +1,15 @@
// 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 "StringHelpers.h"
bool ends_with(const std::wstring &source, const std::wstring &suffix, bool ignoreCase)
{
if (source.length() < suffix.length())
{
return false;
}
const auto offset = source.length() - suffix.length();
return CSTR_EQUAL == CompareStringOrdinal(source.c_str() + offset, static_cast<int>(suffix.length()), suffix.c_str(), static_cast<int>(suffix.length()), ignoreCase);
}

View File

@ -0,0 +1,20 @@
// 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.
#pragma once
#include <string>
[[nodiscard]]
bool ends_with(const std::wstring &source, const std::wstring &suffix, bool ignoreCase = false);
template<typename ... Args>
[[nodiscard]]
std::wstring format(const std::wstring& format, Args ... args)
{
const size_t size = swprintf(nullptr, 0, format.c_str(), args ...) + 1; // Extra char for '\0'
std::unique_ptr<wchar_t[]> formattedBuffer(new wchar_t[size]);
swprintf(formattedBuffer.get(), size, format.c_str(), args ... );
return std::wstring(formattedBuffer.get(), formattedBuffer.get() + size - 1);
}

View File

@ -38,12 +38,6 @@ Language=English
%1 %1
. .
Messageid=1002
SymbolicName=ASPNETCORE_EVENT_PROCESS_CRASH
Language=English
%1
.
Messageid=1003 Messageid=1003
SymbolicName=ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED SymbolicName=ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED
Language=English Language=English
@ -110,42 +104,12 @@ Language=English
%1 %1
. .
Messageid=1014
SymbolicName=ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP
Language=English
%1
.
Messageid=1015
SymbolicName=ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING
Language=English
%1
.
Messageid=1016
SymbolicName=ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND
Language=English
%1
.
Messageid=1017
SymbolicName=ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND
Language=English
%1
.
Messageid=1018 Messageid=1018
SymbolicName=ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION SymbolicName=ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION
Language=English Language=English
%1 %1
. .
Messageid=1019
SymbolicName=ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND
Language=English
%1
.
Messageid=1020 Messageid=1020
SymbolicName=ASPNETCORE_EVENT_PROCESS_START_FAILURE SymbolicName=ASPNETCORE_EVENT_PROCESS_START_FAILURE
Language=English Language=English
@ -164,12 +128,6 @@ Language=English
%1 %1
. .
Messageid=1023
SymbolicName=ASPNETCORE_EVENT_APP_IN_SHUTDOWN
Language=English
%1
.
Messageid=1024 Messageid=1024
SymbolicName=ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR SymbolicName=ASPNETCORE_EVENT_MONITOR_APPOFFLINE_ERROR
Language=English Language=English
@ -213,7 +171,7 @@ Language=English
. .
Messageid=1031 Messageid=1031
SymbolicName=ASPNETCORE_EVENT_INVALID_PROCESS_PATH SymbolicName=ASPNETCORE_EVENT_INPROCESS_START_ERROR
Language=English Language=English
%1 %1
. .

View File

@ -43,6 +43,7 @@
#define CATCH_RETURN() catch (...) { RETURN_CAUGHT_EXCEPTION(); } #define CATCH_RETURN() catch (...) { RETURN_CAUGHT_EXCEPTION(); }
#define LOG_IF_FAILED(hr) LogHResultFailed(LOCATION_INFO, hr) #define LOG_IF_FAILED(hr) LogHResultFailed(LOCATION_INFO, hr)
#define LOG_LAST_ERROR() LogLastErrorIf(LOCATION_INFO, true)
#define LOG_LAST_ERROR_IF(condition) LogLastErrorIf(LOCATION_INFO, condition) #define LOG_LAST_ERROR_IF(condition) LogLastErrorIf(LOCATION_INFO, condition)
#define SUCCEEDED_LOG(hr) SUCCEEDED(LOG_IF_FAILED(hr)) #define SUCCEEDED_LOG(hr) SUCCEEDED(LOG_IF_FAILED(hr))
#define FAILED_LOG(hr) FAILED(LOG_IF_FAILED(hr)) #define FAILED_LOG(hr) FAILED(LOG_IF_FAILED(hr))
@ -74,7 +75,7 @@
__declspec(noinline) inline VOID ReportException(LOCATION_ARGUMENTS std::exception& exception) __declspec(noinline) inline VOID ReportException(LOCATION_ARGUMENTS std::exception& exception)
{ {
DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, LOCATION_FORMAT "Unhandled exception: %s", LOCATION_CALL exception.what()); DebugPrintf(ASPNETCORE_DEBUG_FLAG_ERROR, "Exception : %s caught at" LOCATION_FORMAT, exception.what(), LOCATION_CALL_ONLY);
} }
__declspec(noinline) inline HRESULT LogHResultFailed(LOCATION_ARGUMENTS HRESULT hr) __declspec(noinline) inline HRESULT LogHResultFailed(LOCATION_ARGUMENTS HRESULT hr)

View File

@ -3,185 +3,78 @@
#include "hostfxr_utility.h" #include "hostfxr_utility.h"
#include <string>
#include <atlcomcli.h> #include <atlcomcli.h>
#include "EventLog.h"
#include "ntassert.h"
#include "fx_ver.h" #include "fx_ver.h"
#include "debugutil.h" #include "debugutil.h"
#include "exceptions.h" #include "exceptions.h"
#include "HandleWrapper.h" #include "HandleWrapper.h"
#include "Environment.h" #include "Environment.h"
#include "file_utility.h" #include "StringHelpers.h"
namespace fs = std::filesystem; namespace fs = std::filesystem;
// void
// Runs a standalone appliction.
// The folder structure looks like this:
// Application/
// hostfxr.dll
// Application.exe
// Application.dll
// etc.
// We get the full path to hostfxr.dll and Application.dll and run hostfxr_main,
// passing in Application.dll.
// Assuming we don't need Application.exe as the dll is the actual application.
//
HRESULT
HOSTFXR_UTILITY::GetStandaloneHostfxrParameters(
PCWSTR pwzExeAbsolutePath, // includes .exe file extension.
PCWSTR pcwzApplicationPhysicalPath,
PCWSTR pcwzArguments,
_Inout_ STRU* pStruHostFxrDllLocation,
_Out_ DWORD* pdwArgCount,
_Out_ BSTR** ppwzArgv
)
{
LOG_INFOF("Resolving standalone hostfxr parameters for application: %S arguments: %S path: %S",
pwzExeAbsolutePath,
pcwzArguments,
pwzExeAbsolutePath);
const fs::path exePath(pwzExeAbsolutePath);
if (!exePath.has_extension())
{
LOG_INFOF("Exe path has not extension, returning");
return false;
}
const fs::path physicalPath(pcwzApplicationPhysicalPath);
const fs::path hostFxrLocation = physicalPath / "hostfxr.dll";
LOG_INFOF("Checking hostfxr.dll at %S", hostFxrLocation.c_str());
if (!is_regular_file(hostFxrLocation))
{
fs::path runtimeConfigLocation = exePath;
runtimeConfigLocation.replace_extension(L".runtimeconfig.json");
LOG_INFOF("Checking runtimeconfig.json at %S", runtimeConfigLocation.c_str());
if (!is_regular_file(runtimeConfigLocation))
{
EventLog::Error(
ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP,
ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP_MSG,
pcwzApplicationPhysicalPath,
0);
return E_FAIL;
}
EventLog::Error(
ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND,
ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_MSG,
pcwzApplicationPhysicalPath,
0);
return E_FAIL;
}
fs::path dllPath = exePath;
dllPath.replace_extension(".dll");
if (!is_regular_file(dllPath))
{
LOG_INFOF("Application dll at %S was not found", dllPath.c_str());
return E_FAIL;
}
auto arguments = std::wstring(dllPath) + L" " + pcwzArguments;
RETURN_IF_FAILED(pStruHostFxrDllLocation->Copy(hostFxrLocation.c_str()));
RETURN_IF_FAILED(ParseHostfxrArguments(
arguments.c_str(),
pwzExeAbsolutePath,
pcwzApplicationPhysicalPath,
pdwArgCount,
ppwzArgv));
return S_OK;
}
BOOL
HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
{
auto name = dotnetPath.filename();
name.replace_extension("");
return _wcsnicmp(name.c_str(), L"dotnet", 6) == 0;
}
HRESULT
HOSTFXR_UTILITY::GetHostFxrParameters( HOSTFXR_UTILITY::GetHostFxrParameters(
_In_ PCWSTR pcwzProcessPath, const fs::path &processPath,
_In_ PCWSTR pcwzApplicationPhysicalPath, const fs::path &applicationPhysicalPath,
_In_ PCWSTR pcwzArguments, const std::wstring &applicationArguments,
_Inout_ STRU *pStruHostFxrDllLocation, fs::path &hostFxrDllPath,
_Inout_ STRU *pStruExeAbsolutePath, fs::path &dotnetExePath,
_Out_ DWORD *pdwArgCount, std::vector<std::wstring> &arguments
_Out_ BSTR **pbstrArgv
) )
{ {
HRESULT hr = S_OK; LOG_INFOF("Resolving hostfxr parameters for application: '%S' arguments: '%S' path: '%S'",
processPath.c_str(),
applicationArguments.c_str(),
applicationPhysicalPath.c_str());
LOG_INFOF("Resolving hostfxr parameters for application: %S arguments: %S path: %S", fs::path expandedProcessPath = Environment::ExpandEnvironmentVariables(processPath);
pcwzProcessPath, const auto expandedApplicationArguments = Environment::ExpandEnvironmentVariables(applicationArguments);
pcwzArguments,
pcwzApplicationPhysicalPath);
const fs::path applicationPhysicalPath = pcwzApplicationPhysicalPath; LOG_INFOF("Expanded hostfxr parameters for application: '%S' arguments: '%S'",
fs::path processPath = Environment::ExpandEnvironmentVariables(pcwzProcessPath); expandedProcessPath.c_str(),
std::wstring arguments = Environment::ExpandEnvironmentVariables(pcwzArguments); expandedApplicationArguments.c_str());
LOG_INFOF("Known dotnet.exe location: '%S'", dotnetExePath.c_str());
if (!expandedProcessPath.has_extension())
{
// The only executable extension inprocess supports
expandedProcessPath.replace_extension(".exe");
}
else if (!ends_with(expandedProcessPath, L".exe", true))
{
throw StartupParametersResolutionException(format(L"Process path '%s' doesn't have '.exe' extension.", expandedProcessPath.c_str()));
}
// Check if the absolute path is to dotnet or not. // Check if the absolute path is to dotnet or not.
if (IsDotnetExecutable(processPath)) if (IsDotnetExecutable(expandedProcessPath))
{ {
LOG_INFOF("Process path %S is dotnet, treating application as portable", processPath.c_str()); LOG_INFOF("Process path '%S' is dotnet, treating application as portable", expandedProcessPath.c_str());
//
// The processPath ends with dotnet.exe or dotnet if (dotnetExePath.empty())
// 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
//
// Make sure to append the dotnet.exe path correctly here (pass in regular path)?
auto fullProcessPath = GetAbsolutePathToDotnet(applicationPhysicalPath, processPath);
if (!fullProcessPath.has_value())
{ {
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); dotnetExePath = GetAbsolutePathToDotnet(applicationPhysicalPath, expandedProcessPath);
EventLog::Error(
ASPNETCORE_EVENT_INVALID_PROCESS_PATH,
ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG,
processPath.c_str(),
hr);
return hr;
} }
processPath = fullProcessPath.value(); hostFxrDllPath = GetAbsolutePathToHostFxr(dotnetExePath);
auto hostFxrPath = GetAbsolutePathToHostFxr(processPath); ParseHostfxrArguments(
if (!hostFxrPath.has_value()) expandedApplicationArguments,
{ dotnetExePath,
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); applicationPhysicalPath,
return hr; arguments,
} true);
RETURN_IF_FAILED(HOSTFXR_UTILITY::ParseHostfxrArguments(
arguments.c_str(),
processPath.c_str(),
pcwzApplicationPhysicalPath,
pdwArgCount,
pbstrArgv));
RETURN_IF_FAILED(pStruHostFxrDllLocation->Copy(hostFxrPath->c_str()));
RETURN_IF_FAILED(pStruExeAbsolutePath->Copy(processPath.c_str()));
} }
else else
{ {
LOG_INFOF("Process path %S is not dotnet, treating application as standalone", processPath.c_str()); LOG_INFOF("Process path '%S' is not dotnet, treating application as standalone or portable with bootstrapper", expandedProcessPath.c_str());
if (processPath.is_relative()) auto executablePath = expandedProcessPath;
if (executablePath.is_relative())
{ {
processPath = applicationPhysicalPath / processPath; executablePath = applicationPhysicalPath / expandedProcessPath;
} }
// //
@ -189,17 +82,42 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
// like: C:\test\MyApp.Exe or MyApp.Exe // like: C:\test\MyApp.Exe or MyApp.Exe
// Check if the file exists, and if it does, get the parameters for a standalone application // Check if the file exists, and if it does, get the parameters for a standalone application
// //
if (is_regular_file(processPath)) if (is_regular_file(executablePath))
{ {
RETURN_IF_FAILED(GetStandaloneHostfxrParameters( auto applicationDllPath = executablePath;
processPath.c_str(), applicationDllPath.replace_extension(".dll");
pcwzApplicationPhysicalPath,
arguments.c_str(),
pStruHostFxrDllLocation,
pdwArgCount,
pbstrArgv));
RETURN_IF_FAILED(pStruExeAbsolutePath->Copy(processPath.c_str())); LOG_INFOF("Checking application.dll at %S", 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());
if (is_regular_file(hostFxrDllPath))
{
LOG_INFOF("hostfxr.dll found app local at '%S', treating application as standalone", hostFxrDllPath.c_str());
}
else
{
LOG_INFOF("hostfxr.dll found app local at '%S', 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
if (dotnetExePath.empty())
{
dotnetExePath = GetAbsolutePathToDotnet(applicationPhysicalPath, L"dotnet");
}
executablePath = dotnetExePath;
hostFxrDllPath = GetAbsolutePathToHostFxr(dotnetExePath);
}
ParseHostfxrArguments(
applicationDllPath.generic_wstring() + L" " + expandedApplicationArguments,
executablePath,
applicationPhysicalPath,
arguments);
} }
else else
{ {
@ -207,140 +125,75 @@ HOSTFXR_UTILITY::GetHostFxrParameters(
// If the processPath file does not exist and it doesn't include dotnet.exe or dotnet // If the processPath file does not exist and it doesn't include dotnet.exe or dotnet
// then it is an invalid argument. // then it is an invalid argument.
// //
hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); throw StartupParametersResolutionException(format(L"Executable was not found at '%s'", executablePath.c_str()));
EventLog::Error(
ASPNETCORE_EVENT_INVALID_PROCESS_PATH,
ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG,
processPath.c_str(),
hr);
return hr;
} }
} }
return S_OK;
} }
// BOOL
// Forms the argument list in HOSTFXR_PARAMETERS. HOSTFXR_UTILITY::IsDotnetExecutable(const std::filesystem::path & dotnetPath)
// Sets the ArgCount and Arguments. {
// Arg structure: return ends_with(dotnetPath, L"dotnet.exe", true);
// argv[0] = Path to exe activating hostfxr. }
// argv[1] = L"exec"
// argv[2] = absolute path to dll. void
//
HRESULT
HOSTFXR_UTILITY::ParseHostfxrArguments( HOSTFXR_UTILITY::ParseHostfxrArguments(
PCWSTR pwzArgumentsFromConfig, const std::wstring &applicationArguments,
PCWSTR pwzExePath, const fs::path &applicationExePath,
PCWSTR pcwzApplicationPhysicalPath, const fs::path &applicationPhysicalPath,
_Out_ DWORD* pdwArgCount, std::vector<std::wstring> &arguments,
_Out_ BSTR** pbstrArgv bool expandDllPaths
) )
{ {
DBG_ASSERT(pdwArgCount != NULL); LOG_INFOF("Resolving hostfxr_main arguments application: '%S' arguments: '%S' path: %S", applicationExePath.c_str(), applicationArguments.c_str(), applicationPhysicalPath.c_str());
DBG_ASSERT(pbstrArgv != NULL);
DBG_ASSERT(pwzExePath != NULL);
HRESULT hr = S_OK; arguments = std::vector<std::wstring>();
INT argc = 0; arguments.push_back(applicationExePath);
BSTR* argv = NULL;
LPWSTR* pwzArgs = NULL;
STRU struTempPath;
INT intArgsProcessed = 0;
LOG_INFOF("Resolving hostfxr_main arguments application: %S arguments: %S path: %S", if (applicationArguments.empty())
pwzExePath,
pwzArgumentsFromConfig,
pcwzApplicationPhysicalPath);
// If we call CommandLineToArgvW with an empty string, argc is 5 for some interesting reason.
// Protectively guard against this by check if the string is null or empty.
if (pwzArgumentsFromConfig == NULL || wcscmp(pwzArgumentsFromConfig, L"") == 0)
{ {
hr = E_INVALIDARG; throw StartupParametersResolutionException(L"Application arguments are empty.");
goto Finished;
} }
pwzArgs = CommandLineToArgvW(pwzArgumentsFromConfig, &argc); int argc = 0;
auto pwzArgs = std::unique_ptr<LPWSTR[], LocalFreeDeleter>(CommandLineToArgvW(applicationArguments.c_str(), &argc));
if (pwzArgs == NULL) if (!pwzArgs)
{ {
hr = LOG_IF_FAILED(HRESULT_FROM_WIN32(GetLastError())); throw StartupParametersResolutionException(format(L"Unable parse command line argumens '%s' or '%s'", applicationArguments.c_str()));
goto Failure;
} }
argv = new BSTR[argc + 1]; for (int intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++)
{
std::wstring argument = pwzArgs[intArgsProcessed];
argv[0] = SysAllocString(pwzExePath); // Try expanding arguments ending in .dll to a full paths
if (argv[0] == NULL) if (expandDllPaths && ends_with(argument, L".dll", true))
{
hr = E_OUTOFMEMORY;
goto Failure;
}
// Try to convert the application dll from a relative to an absolute path
// Don't record this failure as pwzArgs[0] may already be an absolute path to the dll.
for (intArgsProcessed = 0; intArgsProcessed < argc; intArgsProcessed++)
{
DBG_ASSERT(pwzArgs[intArgsProcessed] != NULL);
struTempPath.Copy(pwzArgs[intArgsProcessed]);
if (struTempPath.EndsWith(L".dll"))
{ {
if (SUCCEEDED(FILE_UTILITY::ConvertPathToFullPath(pwzArgs[intArgsProcessed], pcwzApplicationPhysicalPath, &struTempPath))) fs::path argumentAsPath = argument;
if (argumentAsPath.is_relative())
{ {
argv[intArgsProcessed + 1] = SysAllocString(struTempPath.QueryStr()); argumentAsPath = applicationPhysicalPath / argumentAsPath;
} if (exists(argumentAsPath))
else {
{ LOG_INFOF("Converted argument '%S' to %S", argument.c_str(), argumentAsPath.c_str());
argv[intArgsProcessed + 1] = SysAllocString(pwzArgs[intArgsProcessed]); argument = argumentAsPath;
} }
if (argv[intArgsProcessed + 1] == NULL)
{
hr = E_OUTOFMEMORY;
goto Failure;
}
}
else
{
argv[intArgsProcessed + 1] = SysAllocString(pwzArgs[intArgsProcessed]);
if (argv[intArgsProcessed + 1] == NULL)
{
hr = E_OUTOFMEMORY;
goto Failure;
} }
} }
LOG_INFOF("Argument[%d] = %S", arguments.push_back(argument);
intArgsProcessed + 1,
argv[intArgsProcessed + 1]);
} }
*pbstrArgv = argv; for (size_t i = 0; i < arguments.size(); i++)
*pdwArgCount = argc + 1;
goto Finished;
Failure:
if (argv != NULL)
{ {
// intArgsProcess - 1 here as if we fail to allocated the ith string LOG_INFOF("Argument[%d] = %S", i, arguments[i].c_str());
// we don't want to free it.
for (INT i = 0; i < intArgsProcessed - 1; i++)
{
SysFreeString(argv[i]);
}
} }
delete[] argv;
Finished:
if (pwzArgs != NULL)
{
LocalFree(pwzArgs);
}
return hr;
} }
std::optional<fs::path> // The processPath ends with dotnet.exe or dotnet
// 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( HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
const fs::path & applicationPath, const fs::path & applicationPath,
const fs::path & requestedPath const fs::path & requestedPath
@ -357,21 +210,11 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
// //
// If we are given an absolute path to dotnet.exe, we are done // If we are given an absolute path to dotnet.exe, we are done
// //
if (is_regular_file(requestedPath)) if (is_regular_file(processPath))
{ {
LOG_INFOF("Found dotnet.exe at %S", requestedPath.c_str()); LOG_INFOF("Found dotnet.exe at %S", processPath.c_str());
return std::make_optional(requestedPath); return processPath;
}
auto pathWithExe = requestedPath;
pathWithExe.concat(L".exe");
if (is_regular_file(pathWithExe))
{
LOG_INFOF("Found dotnet.exe at %S", pathWithExe.c_str());
return std::make_optional(pathWithExe);
} }
// At this point, we are calling where.exe to find dotnet. // At this point, we are calling where.exe to find dotnet.
@ -382,7 +225,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
{ {
LOG_INFOF("Absolute path to dotnet.exe was not found at %S", requestedPath.c_str()); LOG_INFOF("Absolute path to dotnet.exe was not found at %S", requestedPath.c_str());
return std::nullopt; throw StartupParametersResolutionException(format(L"Could not find dotnet.exe at '%s'", processPath.c_str()));
} }
const auto dotnetViaWhere = InvokeWhereToFindDotnet(); const auto dotnetViaWhere = InvokeWhereToFindDotnet();
@ -390,7 +233,7 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
{ {
LOG_INFOF("Found dotnet.exe via where.exe invocation at %S", dotnetViaWhere.value().c_str()); LOG_INFOF("Found dotnet.exe via where.exe invocation at %S", dotnetViaWhere.value().c_str());
return dotnetViaWhere; return dotnetViaWhere.value();
} }
const auto programFilesLocation = GetAbsolutePathToDotnetFromProgramFiles(); const auto programFilesLocation = GetAbsolutePathToDotnetFromProgramFiles();
@ -398,14 +241,17 @@ HOSTFXR_UTILITY::GetAbsolutePathToDotnet(
{ {
LOG_INFOF("Found dotnet.exe in Program Files at %S", programFilesLocation.value().c_str()); LOG_INFOF("Found dotnet.exe in Program Files at %S", programFilesLocation.value().c_str());
return programFilesLocation; return programFilesLocation.value();
} }
LOG_INFOF("dotnet.exe not found"); LOG_INFOF("dotnet.exe not found");
return std::nullopt; 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.",
processPath.c_str()));
} }
std::optional<fs::path> fs::path
HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
const fs::path & dotnetPath const fs::path & dotnetPath
) )
@ -417,23 +263,14 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
if (!is_directory(hostFxrBase)) if (!is_directory(hostFxrBase))
{ {
EventLog::Error(ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, hostFxrBase.c_str(), HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT)); throw StartupParametersResolutionException(format(L"Unable to find hostfxr directory at %s", hostFxrBase.c_str()));
return std::nullopt;
} }
auto searchPattern = std::wstring(hostFxrBase) + L"\\*"; FindDotNetFolders(hostFxrBase, versionFolders);
FindDotNetFolders(searchPattern.c_str(), versionFolders);
if (versionFolders.empty()) if (versionFolders.empty())
{ {
EventLog::Error( throw StartupParametersResolutionException(format(L"Hostfxr directory '%s' doesn't contain any version subdirectories", hostFxrBase.c_str()));
ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND,
ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG,
hostFxrBase.c_str(),
HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT));
return std::nullopt;
} }
const auto highestVersion = FindHighestDotNetVersion(versionFolders); const auto highestVersion = FindHighestDotNetVersion(versionFolders);
@ -441,24 +278,18 @@ HOSTFXR_UTILITY::GetAbsolutePathToHostFxr(
if (!is_regular_file(hostFxrPath)) if (!is_regular_file(hostFxrPath))
{ {
EventLog::Error( throw StartupParametersResolutionException(format(L"hostfxr.dll not found at '%s'", hostFxrPath.c_str()));
ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND,
ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG,
hostFxrPath.c_str(),
HRESULT_FROM_WIN32(ERROR_FILE_INVALID));
return std::nullopt;
} }
LOG_INFOF("hostfxr.dll located at %S", hostFxrPath.c_str()); LOG_INFOF("hostfxr.dll located at %S", hostFxrPath.c_str());
return std::make_optional(hostFxrPath); return hostFxrPath;
} }
// //
// Tries to call where.exe to find the location of dotnet.exe. // Tries to call where.exe to find the location of dotnet.exe.
// Will check that the bitness of dotnet matches the current // Will check that the bitness of dotnet matches the current
// worker process bitness. // worker process bitness.
// Returns true if a valid dotnet was found, else false. // Returns true if a valid dotnet was found, else false.R
// //
std::optional<fs::path> std::optional<fs::path>
HOSTFXR_UTILITY::InvokeWhereToFindDotnet() HOSTFXR_UTILITY::InvokeWhereToFindDotnet()
@ -638,7 +469,6 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion(
fx_ver_t fx_ver(-1, -1, -1); fx_ver_t fx_ver(-1, -1, -1);
if (fx_ver_t::parse(dir, &fx_ver, false)) if (fx_ver_t::parse(dir, &fx_ver, false))
{ {
// TODO using max instead of std::max works
max_ver = max(max_ver, fx_ver); max_ver = max(max_ver, fx_ver);
} }
} }
@ -648,16 +478,16 @@ HOSTFXR_UTILITY::FindHighestDotNetVersion(
VOID VOID
HOSTFXR_UTILITY::FindDotNetFolders( HOSTFXR_UTILITY::FindDotNetFolders(
_In_ PCWSTR pszPath, const std::filesystem::path &path,
_Out_ std::vector<std::wstring> & pvFolders _Out_ std::vector<std::wstring> &pvFolders
) )
{ {
HANDLE handle = NULL; WIN32_FIND_DATAW data = {};
WIN32_FIND_DATAW data = { 0 }; const auto searchPattern = std::wstring(path) + L"\\*";
HandleWrapper<FindFileHandleTraits> handle = FindFirstFileExW(searchPattern.c_str(), FindExInfoStandard, &data, FindExSearchNameMatch, nullptr, 0);
handle = FindFirstFileExW(pszPath, FindExInfoStandard, &data, FindExSearchNameMatch, NULL, 0);
if (handle == INVALID_HANDLE_VALUE) if (handle == INVALID_HANDLE_VALUE)
{ {
LOG_LAST_ERROR();
return; return;
} }
@ -665,6 +495,4 @@ HOSTFXR_UTILITY::FindDotNetFolders(
{ {
pvFolders.emplace_back(data.cFileName); pvFolders.emplace_back(data.cFileName);
} while (FindNextFileW(handle, &data)); } while (FindNextFileW(handle, &data));
FindClose(handle);
} }

View File

@ -7,7 +7,7 @@
#include <vector> #include <vector>
#include <filesystem> #include <filesystem>
#include <optional> #include <optional>
#include "stringu.h" #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_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[]); typedef INT(*hostfxr_main_fn) (CONST DWORD argc, CONST PCWSTR argv[]);
@ -19,76 +19,88 @@ class HOSTFXR_UTILITY
public: public:
static static
HRESULT void
GetStandaloneHostfxrParameters(
PCWSTR pwzExeAbsolutePath, // includes .exe file extension.
PCWSTR pcwzApplicationPhysicalPath,
PCWSTR pcwzArguments,
_Inout_ STRU* pStruHostFxrDllLocation,
_Out_ DWORD* pdwArgCount,
_Out_ BSTR** ppwzArgv
);
static
HRESULT
ParseHostfxrArguments(
PCWSTR pwzArgumentsFromConfig,
PCWSTR pwzExePath,
PCWSTR pcwzApplicationPhysicalPath,
_Out_ DWORD* pdwArgCount,
_Out_ BSTR** pbstrArgv
);
static
BOOL
IsDotnetExecutable(
_In_ const std::filesystem::path & dotnetPath
);
static
HRESULT
GetHostFxrParameters( GetHostFxrParameters(
_In_ PCWSTR pcwzProcessPath, const std::filesystem::path &processPath,
_In_ PCWSTR pcwzApplicationPhysicalPath, const std::filesystem::path &applicationPhysicalPath,
_In_ PCWSTR pcwzArguments, const std::wstring &applicationArguments,
_Inout_ STRU *pStruHostFxrDllLocation, std::filesystem::path &hostFxrDllPath,
_Inout_ STRU *struExeAbsolutePath, std::filesystem::path &dotnetExePath,
_Out_ DWORD *pdwArgCount, std::vector<std::wstring> &arguments
_Out_ BSTR **ppwzArgv
); );
static class StartupParametersResolutionException: public std::runtime_error
VOID {
FindDotNetFolders( public:
_In_ PCWSTR pszPath, StartupParametersResolutionException(std::wstring msg)
_Out_ std::vector<std::wstring> & pvFolders : runtime_error("Startup parameter resulution error occured"), message(std::move(msg))
); {
}
std::wstring get_message() const { return message; }
private:
std::wstring message;
};
static static
std::wstring void
FindHighestDotNetVersion( ParseHostfxrArguments(
_In_ std::vector<std::wstring> & vFolders const std::wstring &arugments,
); const std::filesystem::path &exePath,
const std::filesystem::path &applicationPhysicalPath,
static std::vector<std::wstring> &arguments,
std::optional<std::filesystem::path> bool expandDllPaths = false
GetAbsolutePathToHostFxr(
_In_ const std::filesystem::path & dotnetPath
); );
static static
std::optional<std::filesystem::path> std::optional<std::filesystem::path>
GetAbsolutePathToDotnetFromProgramFiles(); GetAbsolutePathToDotnetFromProgramFiles();
private:
static
BOOL
IsDotnetExecutable(
const std::filesystem::path & dotnetPath
);
static
VOID
FindDotNetFolders(
const std::filesystem::path& path,
std::vector<std::wstring> & pvFolders
);
static
std::wstring
FindHighestDotNetVersion(
std::vector<std::wstring> & vFolders
);
static
std::filesystem::path
GetAbsolutePathToHostFxr(
const std::filesystem::path & dotnetPath
);
static static
std::optional<std::filesystem::path> std::optional<std::filesystem::path>
InvokeWhereToFindDotnet(); InvokeWhereToFindDotnet();
static static
std::optional<std::filesystem::path> std::filesystem::path
GetAbsolutePathToDotnet( GetAbsolutePathToDotnet(
_In_ const std::filesystem::path & applicationPath, const std::filesystem::path & applicationPath,
_In_ const std::filesystem::path & requestedPath const std::filesystem::path & requestedPath
); );
struct LocalFreeDeleter
{
void operator ()(LPWSTR* ptr) const
{
LocalFree(ptr);
}
};
}; };

View File

@ -6,82 +6,48 @@
#include "hostfxr_utility.h" #include "hostfxr_utility.h"
#include "debugutil.h" #include "debugutil.h"
#include "exceptions.h" #include "exceptions.h"
#include "EventLog.h"
HRESULT HOSTFXR_OPTIONS::Create( HRESULT HOSTFXR_OPTIONS::Create(
_In_ PCWSTR pcwzExeLocation, _In_ PCWSTR pcwzDotnetExePath,
_In_ PCWSTR pcwzProcessPath, _In_ PCWSTR pcwzProcessPath,
_In_ PCWSTR pcwzApplicationPhysicalPath, _In_ PCWSTR pcwzApplicationPhysicalPath,
_In_ PCWSTR pcwzArguments, _In_ PCWSTR pcwzArguments,
_Out_ std::unique_ptr<HOSTFXR_OPTIONS>& ppWrapper) _Out_ std::unique_ptr<HOSTFXR_OPTIONS>& ppWrapper)
{ {
STRU struHostFxrDllLocation; std::filesystem::path knownDotnetLocation;
STRU struExeAbsolutePath;
STRU struExeLocation;
BSTR* pwzArgv;
DWORD dwArgCount;
if (pcwzExeLocation != NULL) if (pcwzDotnetExePath != nullptr)
{ {
RETURN_IF_FAILED(struExeLocation.Copy(pcwzExeLocation)); knownDotnetLocation = pcwzDotnetExePath;
} }
try
{
std::filesystem::path hostFxrDllPath;
std::vector<std::wstring> arguments;
HOSTFXR_UTILITY::GetHostFxrParameters(
pcwzProcessPath,
pcwzApplicationPhysicalPath,
pcwzArguments,
hostFxrDllPath,
knownDotnetLocation,
arguments);
// If the exe was not provided by the shim, reobtain the hostfxr parameters (which finds dotnet). ppWrapper = std::make_unique<HOSTFXR_OPTIONS>(knownDotnetLocation, hostFxrDllPath, arguments);
if (struExeLocation.IsEmpty())
{
RETURN_IF_FAILED(HOSTFXR_UTILITY::GetHostFxrParameters(
pcwzProcessPath,
pcwzApplicationPhysicalPath,
pcwzArguments,
&struHostFxrDllLocation,
&struExeAbsolutePath,
&dwArgCount,
&pwzArgv));
} }
else if (HOSTFXR_UTILITY::IsDotnetExecutable(struExeLocation.QueryStr())) catch (HOSTFXR_UTILITY::StartupParametersResolutionException &resolutionException)
{ {
RETURN_IF_FAILED(HOSTFXR_UTILITY::ParseHostfxrArguments( OBSERVE_CAUGHT_EXCEPTION();
pcwzArguments,
pcwzExeLocation,
pcwzApplicationPhysicalPath,
&dwArgCount,
&pwzArgv));
}
else
{
RETURN_IF_FAILED(HOSTFXR_UTILITY::GetStandaloneHostfxrParameters(
pcwzExeLocation,
pcwzApplicationPhysicalPath,
pcwzArguments,
&struHostFxrDllLocation,
&dwArgCount,
&pwzArgv));
}
ppWrapper = std::make_unique<HOSTFXR_OPTIONS>(); EventLog::Error(
RETURN_IF_FAILED(ppWrapper->Populate(struHostFxrDllLocation.QueryStr(), struExeAbsolutePath.QueryStr(), dwArgCount, pwzArgv)); ASPNETCORE_EVENT_INPROCESS_START_ERROR,
ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG,
pcwzApplicationPhysicalPath,
resolutionException.get_message().c_str());
return E_FAIL;
}
CATCH_RETURN();
return S_OK; return S_OK;
} }
HRESULT HOSTFXR_OPTIONS::Populate(PCWSTR hostFxrLocation, PCWSTR struExeLocation, DWORD argc, BSTR argv[])
{
HRESULT hr;
m_argc = argc;
m_argv = argv;
if (FAILED(hr = m_hostFxrLocation.Copy(hostFxrLocation)))
{
goto Finished;
}
if (FAILED(hr = m_exeLocation.Copy(struExeLocation)))
{
goto Finished;
}
Finished:
return hr;
}

View File

@ -5,41 +5,45 @@
#include <Windows.h> #include <Windows.h>
#include <memory> #include <memory>
#include <filesystem>
#include "stringu.h" #include <utility>
#include <vector>
#include <string>
class HOSTFXR_OPTIONS class HOSTFXR_OPTIONS
{ {
public: public:
HOSTFXR_OPTIONS() {} HOSTFXR_OPTIONS(
std::filesystem::path dotnetExeLocation,
std::filesystem::path hostFxrLocation,
std::vector<std::wstring> arguments
)
: m_dotnetExeLocation(std::move(dotnetExeLocation)),
m_hostFxrLocation(std::move(hostFxrLocation)),
m_arguments(std::move(arguments))
{}
~HOSTFXR_OPTIONS() void
GetArguments(DWORD &hostfxrArgc, std::unique_ptr<PCWSTR[]> &hostfxrArgv) const
{ {
delete[] m_argv; hostfxrArgc = static_cast<DWORD>(m_arguments.size());
hostfxrArgv = std::unique_ptr<PCWSTR[]>(new PCWSTR[hostfxrArgc]);
for (DWORD i = 0; i < hostfxrArgc; ++i)
{
hostfxrArgv[i] = m_arguments[i].c_str();
}
} }
DWORD const std::filesystem::path&
GetArgc() const
{
return m_argc;
}
BSTR*
GetArgv() const
{
return m_argv;
}
PCWSTR
GetHostFxrLocation() const GetHostFxrLocation() const
{ {
return m_hostFxrLocation.QueryStr(); return m_hostFxrLocation;
} }
PCWSTR const std::filesystem::path&
GetExeLocation() const GetDotnetExeLocation() const
{ {
return m_exeLocation.QueryStr(); return m_dotnetExeLocation;
} }
static static
@ -51,12 +55,7 @@ public:
_Out_ std::unique_ptr<HOSTFXR_OPTIONS>& ppWrapper); _Out_ std::unique_ptr<HOSTFXR_OPTIONS>& ppWrapper);
private: private:
const std::filesystem::path m_dotnetExeLocation;
HRESULT Populate(PCWSTR hostFxrLocation, PCWSTR struExeLocation, DWORD argc, BSTR argv[]); const std::filesystem::path m_hostFxrLocation;
const std::vector<std::wstring> m_arguments;
STRU m_exeLocation;
STRU m_hostFxrLocation;
DWORD m_argc;
BSTR* m_argv;
}; };

View File

@ -11,7 +11,6 @@
#define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module V2" #define ASPNETCORE_EVENT_PROVIDER L"IIS AspNetCore Module V2"
#define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module V2" #define ASPNETCORE_IISEXPRESS_EVENT_PROVIDER L"IIS Express AspNetCore Module V2"
#define ASPNETCORE_EVENT_MSG_BUFFER_SIZE 256
#define ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG L"Application '%s' started process '%d' successfully and process '%d' is listening on port '%d'." #define ASPNETCORE_EVENT_PROCESS_START_SUCCESS_MSG L"Application '%s' started process '%d' successfully and process '%d' is listening on port '%d'."
#define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded." #define ASPNETCORE_EVENT_RAPID_FAIL_COUNT_EXCEEDED_MSG L"Maximum rapid fail count per minute of '%d' exceeded."
#define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s' at stage '%s', ErrorCode = '0x%x', assigned port %d, retryCounter '%d'." #define ASPNETCORE_EVENT_PROCESS_START_ERROR_MSG L"Application '%s' with physical root '%s' failed to start process with commandline '%s' at stage '%s', ErrorCode = '0x%x', assigned port %d, retryCounter '%d'."
@ -39,15 +38,9 @@
#define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG L"Application '%s' was recycled due to configuration change" #define ASPNETCORE_EVENT_RECYCLE_CONFIGURATION_MSG L"Application '%s' was recycled due to configuration change"
#define ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG L"Failed to recycle application due to a configuration change at '%s'. Recycling worker process." #define ASPNETCORE_EVENT_RECYCLE_FAILURE_CONFIGURATION_MSG L"Failed to recycle application due to a configuration change at '%s'. Recycling worker process."
#define ASPNETCORE_EVENT_MODULE_DISABLED_MSG L"AspNetCore Module is disabled" #define ASPNETCORE_EVENT_MODULE_DISABLED_MSG L"AspNetCore Module is disabled"
#define ASPNETCORE_EVENT_INPROCESS_FULL_FRAMEWORK_APP_MSG L"Application '%s' was compiled for .NET Framework. Please compile for .NET core to run the inprocess application or change the process mode to out of process. ErrorCode = '0x%x'."
#define ASPNETCORE_EVENT_PORTABLE_APP_DOTNET_MISSING_MSG L"Could not find dotnet.exe on the system PATH environment variable for portable application '%s'. Check that a valid path to dotnet is on the PATH and the bitness of dotnet matches the bitness of the IIS worker process. ErrorCode = '0x%x'."
#define ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG L"Could not find the hostfxr directory '%s' in the dotnet directory. ErrorCode = '0x%x'."
#define ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG L"Could not find hostfxr.dll in '%s'. ErrorCode = '0x%x'."
#define ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG L"Hostfxr version used does not support 'hostfxr_get_native_search_directories', update the version of hostfxr to a higher version. Path to hostfxr: '%s'." #define ASPNETCORE_EVENT_HOSTFXR_DLL_INVALID_VERSION_MSG L"Hostfxr version used does not support 'hostfxr_get_native_search_directories', update the version of hostfxr to a higher version. Path to hostfxr: '%s'."
#define ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_LEVEL EVENTLOG_ERROR_TYPE
#define ASPNETCORE_EVENT_APPLICATION_EXE_NOT_FOUND_MSG L"Could not find application executable in '%s'. ErrorCode = '0x%x'."
#define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, ErrorCode = '0x%x. Please check the stderr logs for more information." #define ASPNETCORE_EVENT_INPROCESS_THREAD_EXCEPTION_MSG L"Application '%s' with physical root '%s' hit unexpected managed exception, ErrorCode = '0x%x. Please check the stderr logs for more information."
#define ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG L"Invalid or unknown processPath provided in web.config: processPath = '%s', ErrorCode = '0x%x'."
#define ASPNETCORE_EVENT_INPROCESS_RH_ERROR_MSG L"Could not find the assembly '%s' for in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application. Captured output: %s" #define ASPNETCORE_EVENT_INPROCESS_RH_ERROR_MSG L"Could not find the assembly '%s' for in-process application. Please confirm the Microsoft.AspNetCore.Server.IIS package is referenced in your application. Captured output: %s"
#define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG L"Could not find the assembly '%s' for out-of-process application. Please confirm the assembly is installed correctly for IIS or IISExpress." #define ASPNETCORE_EVENT_OUT_OF_PROCESS_RH_MISSING_MSG L"Could not find the assembly '%s' for out-of-process application. Please confirm the assembly is installed correctly for IIS or IISExpress."
#define ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG L"Application '%s' started the coreclr in-process successfully." #define ASPNETCORE_EVENT_INPROCESS_START_SUCCESS_MSG L"Application '%s' started the coreclr in-process successfully."
#define ASPNETCORE_EVENT_INPROCESS_START_ERROR_MSG L"Application '%s' wasn't able to start. %s"

View File

@ -392,11 +392,10 @@ IN_PROCESS_APPLICATION::ExecuteApplication(
{ {
HRESULT hr; HRESULT hr;
HMODULE hModule = nullptr; HMODULE hModule = nullptr;
DWORD hostfxrArgc = 0;
BSTR *hostfxrArgv = NULL;
hostfxr_main_fn pProc; hostfxr_main_fn pProc;
std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions = NULL; std::unique_ptr<HOSTFXR_OPTIONS> hostFxrOptions = NULL;
DWORD hostfxrArgc = 0;
std::unique_ptr<PCWSTR[]> hostfxrArgv;
DBG_ASSERT(m_status == MANAGED_APPLICATION_STATUS::STARTING); DBG_ASSERT(m_status == MANAGED_APPLICATION_STATUS::STARTING);
pProc = s_fMainCallback; pProc = s_fMainCallback;
@ -428,10 +427,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication(
m_pConfig->QueryArguments()->QueryStr(), m_pConfig->QueryArguments()->QueryStr(),
hostFxrOptions hostFxrOptions
)); ));
hostFxrOptions->GetArguments(hostfxrArgc, hostfxrArgv);
hostfxrArgc = hostFxrOptions->GetArgc();
hostfxrArgv = hostFxrOptions->GetArgv();
FINISHED_IF_FAILED(SetEnvironementVariablesOnWorkerProcess()); FINISHED_IF_FAILED(SetEnvironementVariablesOnWorkerProcess());
} }
@ -457,7 +453,7 @@ IN_PROCESS_APPLICATION::ExecuteApplication(
// set the callbacks // set the callbacks
s_Application = this; s_Application = this;
hr = RunDotnetApplication(hostfxrArgc, hostfxrArgv, pProc); hr = RunDotnetApplication(hostfxrArgc, hostfxrArgv.get(), pProc);
Finished: Finished:

View File

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using Microsoft.AspNetCore.Server.IntegrationTesting.IIS; using Microsoft.AspNetCore.Server.IntegrationTesting.IIS;
@ -12,6 +11,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
{ {
public class EventLogHelpers public class EventLogHelpers
{ {
private static readonly Regex EventLogRegex = new Regex("Event Log: (?<EventLogMessage>.+?)End Event Log Message.", RegexOptions.Singleline | RegexOptions.Compiled);
public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, ITestSink testSink, string expectedRegexMatchString) public static void VerifyEventLogEvent(IISDeploymentResult deploymentResult, ITestSink testSink, string expectedRegexMatchString)
{ {
Assert.True(deploymentResult.HostProcess.HasExited); Assert.True(deploymentResult.HostProcess.HasExited);
@ -23,9 +23,19 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
builder.Append(context.Message); builder.Append(context.Message);
} }
var eventLogRegex = new Regex($"Event Log: (.*?){expectedRegexMatchString}(.*?)End Event Log Message.", RegexOptions.Singleline); var count = 0;
var expectedRegex = new Regex(expectedRegexMatchString, RegexOptions.Singleline);
foreach (Match match in EventLogRegex.Matches(builder.ToString()))
{
var eventLogText = match.Groups["EventLogMessage"].Value;
if (expectedRegex.IsMatch(eventLogText))
{
count++;
}
}
Assert.Matches(eventLogRegex, builder.ToString()); Assert.True(count > 0, $"'{expectedRegexMatchString}' didn't match any event log messaged");
Assert.True(count < 2, $"'{expectedRegexMatchString}' matched more then one event log message");
} }
} }
} }

View File

@ -28,11 +28,6 @@ namespace Microsoft.AspNetCore.Server.IntegrationTesting
parameters.EnvironmentVariables[DebugEnvironmentVariable] = "console"; parameters.EnvironmentVariables[DebugEnvironmentVariable] = "console";
} }
if (parameters.ApplicationPublisher == null)
{
throw new InvalidOperationException("All tests should use ApplicationPublisher");
}
return IISApplicationDeployerFactory.Create(parameters, LoggerFactory); return IISApplicationDeployerFactory.Create(parameters, LoggerFactory);
} }

View File

@ -2,80 +2,75 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#include "stdafx.h" #include "stdafx.h"
#include "file_utility.h" #include <filesystem>
#include <vector>
#include <string>
#include "hostfxr_utility.h"
#include "Environment.h"
TEST(ParseHostFxrArguments, BasicHostFxrArguments) TEST(ParseHostFxrArguments, BasicHostFxrArguments)
{ {
DWORD retVal = 0; std::vector<std::wstring> bstrArray;
BSTR* bstrArray;
PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; PCWSTR exeStr = L"C:/Program Files/dotnet.exe";
HRESULT hr = HOSTFXR_UTILITY::ParseHostfxrArguments(
HOSTFXR_UTILITY::ParseHostfxrArguments(
L"exec \"test.dll\"", // args L"exec \"test.dll\"", // args
exeStr, // exe path exeStr, // exe path
L"invalid", // physical path to application L"invalid", // physical path to application
&retVal, // arg count bstrArray); // args array.
&bstrArray); // args array.
EXPECT_EQ(hr, S_OK); EXPECT_EQ(3, bstrArray.size());
EXPECT_EQ(DWORD(3), retVal); ASSERT_STREQ(exeStr, bstrArray[0].c_str());
ASSERT_STREQ(exeStr, bstrArray[0]); ASSERT_STREQ(L"exec", bstrArray[1].c_str());
ASSERT_STREQ(L"exec", bstrArray[1]); ASSERT_STREQ(L"test.dll", bstrArray[2].c_str());
ASSERT_STREQ(L"test.dll", bstrArray[2]);
} }
TEST(ParseHostFxrArguments, NoExecProvided) TEST(ParseHostFxrArguments, NoExecProvided)
{ {
DWORD retVal = 0; std::vector<std::wstring> bstrArray;
BSTR* bstrArray;
PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; PCWSTR exeStr = L"C:/Program Files/dotnet.exe";
HRESULT hr = HOSTFXR_UTILITY::ParseHostfxrArguments( HOSTFXR_UTILITY::ParseHostfxrArguments(
L"test.dll", // args L"test.dll", // args
exeStr, // exe path exeStr, // exe path
L"ignored", // physical path to application L"ignored", // physical path to application
&retVal, // arg count bstrArray); // args array.
&bstrArray); // args array.
EXPECT_EQ(hr, S_OK); EXPECT_EQ(DWORD(2), bstrArray.size());
EXPECT_EQ(DWORD(2), retVal); ASSERT_STREQ(exeStr, bstrArray[0].c_str());
ASSERT_STREQ(exeStr, bstrArray[0]); ASSERT_STREQ(L"test.dll", bstrArray[1].c_str());
ASSERT_STREQ(L"test.dll", bstrArray[1]);
} }
TEST(ParseHostFxrArguments, ConvertDllToAbsolutePath) TEST(ParseHostFxrArguments, ConvertDllToAbsolutePath)
{ {
DWORD retVal = 0; std::vector<std::wstring> bstrArray;
BSTR* bstrArray;
PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; PCWSTR exeStr = L"C:/Program Files/dotnet.exe";
// we need to use existing dll so let's use ntdll that we know exists everywhere
HRESULT hr = HOSTFXR_UTILITY::ParseHostfxrArguments( auto system32 = Environment::ExpandEnvironmentVariables(L"%WINDIR%\\System32");
L"exec \"test.dll\"", // args HOSTFXR_UTILITY::ParseHostfxrArguments(
L"exec \"ntdll.dll\"", // args
exeStr, // exe path exeStr, // exe path
L"C:/test", // physical path to application system32, // physical path to application
&retVal, // arg count bstrArray, // args array.
&bstrArray); // args array. true); // expandDllPaths
EXPECT_EQ(hr, S_OK); EXPECT_EQ(DWORD(3), bstrArray.size());
EXPECT_EQ(DWORD(3), retVal); ASSERT_STREQ(exeStr, bstrArray[0].c_str());
ASSERT_STREQ(exeStr, bstrArray[0]); ASSERT_STREQ(L"exec", bstrArray[1].c_str());
ASSERT_STREQ(L"exec", bstrArray[1]); ASSERT_STREQ((system32 + L"\\ntdll.dll").c_str(), bstrArray[2].c_str());
ASSERT_STREQ(L"C:\\test\\test.dll", bstrArray[2]);
} }
TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs) TEST(ParseHostFxrArguments, ProvideNoArgs_InvalidArgs)
{ {
DWORD retVal = 0; std::vector<std::wstring> bstrArray;
BSTR* bstrArray;
PCWSTR exeStr = L"C:/Program Files/dotnet.exe"; PCWSTR exeStr = L"C:/Program Files/dotnet.exe";
HRESULT hr = HOSTFXR_UTILITY::ParseHostfxrArguments( ASSERT_THROW(HOSTFXR_UTILITY::ParseHostfxrArguments(
L"", // args L"", // args
exeStr, // exe path exeStr, // exe path
L"ignored", // physical path to application L"ignored", // physical path to application
&retVal, // arg count bstrArray), // args array.
&bstrArray); // args array. HOSTFXR_UTILITY::StartupParametersResolutionException);
EXPECT_EQ(E_INVALIDARG, hr);
} }
TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks) TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks)
@ -118,19 +113,16 @@ TEST(GetAbsolutePathToDotnetFromProgramFiles, BackupWorks)
TEST(GetHostFxrArguments, InvalidParams) TEST(GetHostFxrArguments, InvalidParams)
{ {
DWORD retVal = 0; std::vector<std::wstring> bstrArray;
BSTR* bstrArray; std::filesystem::path struHostFxrDllLocation;
STRU struHostFxrDllLocation; std::filesystem::path struExeLocation;
STRU struExeLocation;
HRESULT hr = HOSTFXR_UTILITY::GetHostFxrParameters( EXPECT_THROW(HOSTFXR_UTILITY::GetHostFxrParameters(
L"bogus", // processPath L"bogus", // processPath
L"", // application physical path, ignored. L"", // application physical path, ignored.
L"ignored", //arguments L"ignored", //arguments
NULL, struHostFxrDllLocation,
&struExeLocation, struExeLocation,
&retVal, // arg count bstrArray), // args array.
&bstrArray); // args array. HOSTFXR_UTILITY::StartupParametersResolutionException);
EXPECT_EQ(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), hr);
} }

View File

@ -5,6 +5,7 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities; using Microsoft.AspNetCore.Server.IIS.FunctionalTests.Utilities;
using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.AspNetCore.Server.IntegrationTesting;
@ -40,14 +41,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
} }
[ConditionalTheory] [ConditionalTheory]
[InlineData("bogus")] [InlineData("bogus", "", @"Executable was not found at '.*?\\bogus.exe")]
[InlineData("c:\\random files\\dotnet.exe")] [InlineData("c:\\random files\\dotnet.exe", "something.dll", @"Could not find dotnet.exe at '.*?\\dotnet.exe'")]
[InlineData(".\\dotnet.exe")] [InlineData(".\\dotnet.exe", "something.dll", @"Could not find dotnet.exe at '.*?\\.\\dotnet.exe'")]
public async Task InvalidProcessPath_ExpectServerError(string path) [InlineData("dotnet.exe", "", @"Application arguments are empty.")]
[InlineData("dotnet.zip", "", @"Process path 'dotnet.zip' doesn't have '.exe' extension.")]
public async Task InvalidProcessPath_ExpectServerError(string path, string arguments, string subError)
{ {
var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true); var deploymentParameters = _fixture.GetBaseDeploymentParameters(publish: true);
deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path)); deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("processPath", path));
deploymentParameters.WebConfigActionList.Add(WebConfigHelpers.AddOrModifyAspNetCoreSection("arguments", arguments));
var deploymentResult = await DeployAsync(deploymentParameters); var deploymentResult = await DeployAsync(deploymentParameters);
@ -57,7 +60,7 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
StopServer(); StopServer();
EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, @"Invalid or unknown processPath provided in web\.config: processPath = '.+', ErrorCode = '0x80070002'\."); EventLogHelpers.VerifyEventLogEvent(deploymentResult, TestSink, $@"Application '{Regex.Escape(deploymentResult.ContentRoot)}\\' wasn't able to start. {subError}");
} }
[ConditionalFact] [ConditionalFact]
@ -136,6 +139,24 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests
Assert.Equal("Hello World", responseText); Assert.Equal("Hello World", responseText);
} }
[ConditionalFact]
public async Task StartsWithPortableAndBootstraperExe()
{
var deploymentParameters = _fixture.GetBaseDeploymentParameters(_fixture.InProcessTestSite, publish: true);
// rest publisher as it doesn't support additional parameters
deploymentParameters.ApplicationPublisher = null;
// ReferenceTestTasks is workaround for https://github.com/dotnet/sdk/issues/2482
deploymentParameters.AdditionalPublishParameters = "-p:RuntimeIdentifier=win7-x64 -p:UseAppHost=true -p:SelfContained=false -p:ReferenceTestTasks=false";
deploymentParameters.RestoreOnPublish = true;
var deploymentResult = await DeployAsync(deploymentParameters);
Assert.True(File.Exists(Path.Combine(deploymentResult.ContentRoot, "InProcessWebSite.exe")));
Assert.False(File.Exists(Path.Combine(deploymentResult.ContentRoot, "hostfxr.dll")));
Assert.Contains("InProcessWebSite.exe", File.ReadAllText(Path.Combine(deploymentResult.ContentRoot, "web.config")));
await deploymentResult.AssertStarts();
}
[ConditionalFact] [ConditionalFact]
public async Task DetectsOveriddenServer() public async Task DetectsOveriddenServer()
{ {