diff --git a/src/CommonLib/hostfxr_utility.cpp b/src/CommonLib/hostfxr_utility.cpp index b7fe5a1eea..6d3d6d74a0 100644 --- a/src/CommonLib/hostfxr_utility.cpp +++ b/src/CommonLib/hostfxr_utility.cpp @@ -164,19 +164,25 @@ HOSTFXR_UTILITY::GetHostFxrParameters( { HRESULT hr = S_OK; STRU struSystemPathVariable; - STRU struHostFxrPath; - STRU struExeLocation; - STRU struHostFxrSearchExpression; - STRU struHighestDotnetVersion; + STRU struAbsolutePathToHostFxr; + STRU struAbsolutePathToDotnet; STRU struEventMsg; - std::vector vVersionFolders; - DWORD dwPosition; + STACK_STRU(struExpandedProcessPath, MAX_PATH); + STACK_STRU(struExpandedArguments, MAX_PATH); - // Convert the process path an absolute path. + // Copy and Expand the processPath and Arguments. + if (FAILED(hr = struExpandedProcessPath.CopyAndExpandEnvironmentStrings(pcwzProcessPath)) + || FAILED(hr = struExpandedArguments.CopyAndExpandEnvironmentStrings(pcwzArguments))) + { + goto Finished; + } + + // Convert the process path an absolute path to our current application directory. + // If the path is already an absolute path, it will be unchanged. hr = UTILITY::ConvertPathToFullPath( - pcwzProcessPath, + struExpandedProcessPath.QueryStr(), pcwzApplicationPhysicalPath, - &struExeLocation + &struAbsolutePathToDotnet ); if (FAILED(hr)) @@ -184,152 +190,76 @@ HOSTFXR_UTILITY::GetHostFxrParameters( goto Finished; } - if (UTILITY::CheckIfFileExists(struExeLocation.QueryStr())) + // Check if the absolute path is to dotnet or not. + if (struAbsolutePathToDotnet.EndsWith(L"dotnet.exe") || struAbsolutePathToDotnet.EndsWith(L"dotnet")) { - // Check if hostfxr is in this folder, if it is, we are a standalone application, - // else we assume we received an absolute path to dotnet.exe - if (!struExeLocation.EndsWith(L"dotnet.exe") && - !struExeLocation.EndsWith(L"dotnet")) + // + // 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 + // + if (FAILED(hr = HOSTFXR_UTILITY::GetAbsolutePathToDotnet(&struAbsolutePathToDotnet))) // Make sure to append the dotnet.exe path correctly here (pass in regular path)? + { + goto Finished; + } + + if (FAILED(hr = GetAbsolutePathToHostFxr(&struAbsolutePathToDotnet, hEventLog, &struAbsolutePathToHostFxr))) + { + goto Finished; + } + + if (FAILED(hr = ParseHostfxrArguments( + struExpandedArguments.QueryStr(), + struAbsolutePathToDotnet.QueryStr(), + pcwzApplicationPhysicalPath, + hEventLog, + pdwArgCount, + pbstrArgv))) + { + goto Finished; + } + + if (FAILED(hr = struHostFxrDllLocation->Copy(struAbsolutePathToHostFxr))) { - hr = GetStandaloneHostfxrParameters( - struExeLocation.QueryStr(), - pcwzApplicationPhysicalPath, - pcwzArguments, - hEventLog, - struHostFxrDllLocation, - pdwArgCount, - pbstrArgv); goto Finished; } } else { - if (FAILED(hr = HOSTFXR_UTILITY::FindDotnetExePath(&struExeLocation))) + // + // The processPath is a path to the application executable + // like: C:\test\MyApp.Exe or MyApp.Exe + // Check if the file exists, and if it does, get the parameters for a standalone application + // + if (UTILITY::CheckIfFileExists(struAbsolutePathToDotnet.QueryStr())) { - goto Finished; + hr = GetStandaloneHostfxrParameters( + struAbsolutePathToDotnet.QueryStr(), + pcwzApplicationPhysicalPath, + struExpandedArguments.QueryStr(), + hEventLog, + struHostFxrDllLocation, + pdwArgCount, + pbstrArgv); } - } - - if (FAILED(hr = struExeLocation.SyncWithBuffer()) || - FAILED(hr = struHostFxrPath.Copy(struExeLocation))) - { - goto Finished; - } - - dwPosition = struHostFxrPath.LastIndexOf(L'\\', 0); - if (dwPosition == -1) - { - hr = E_FAIL; - goto Finished; - } - - struHostFxrPath.QueryStr()[dwPosition] = L'\0'; - - if (FAILED(hr = struHostFxrPath.SyncWithBuffer()) || - FAILED(hr = struHostFxrPath.Append(L"\\"))) - { - goto Finished; - } - - hr = struHostFxrPath.Append(L"host\\fxr"); - if (FAILED(hr)) - { - goto Finished; - } - - if (!UTILITY::DirectoryExists(&struHostFxrPath)) - { - hr = ERROR_BAD_ENVIRONMENT; - if (SUCCEEDED(struEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr))) + else { - UTILITY::LogEvent(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, - struEventMsg.QueryStr()); + // + // If the processPath file does not exist and it doesn't include dotnet.exe or dotnet + // then it is an invalid argument. + // + hr = HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND);; + if (SUCCEEDED(struEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_INVALID_PROCESS_PATH_MSG, + struExpandedProcessPath.QueryStr(), + hr))) + { + UTILITY::LogEvent(hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_GENERAL_ERROR_MSG, + struEventMsg.QueryStr()); + } } - goto Finished; - } - - // Find all folders under host\\fxr\\ for version numbers. - hr = struHostFxrSearchExpression.Copy(struHostFxrPath); - if (FAILED(hr)) - { - goto Finished; - } - - hr = struHostFxrSearchExpression.Append(L"\\*"); - if (FAILED(hr)) - { - goto Finished; - } - - // As we use the logic from core-setup, we are opting to use std here. - // TODO remove all uses of std? - UTILITY::FindDotNetFolders(struHostFxrSearchExpression.QueryStr(), &vVersionFolders); - - if (vVersionFolders.size() == 0) - { - hr = HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT); - if (SUCCEEDED(struEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr))) - { - UTILITY::LogEvent(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, - struEventMsg.QueryStr()); - } - goto Finished; - } - - hr = UTILITY::FindHighestDotNetVersion(vVersionFolders, &struHighestDotnetVersion); - if (FAILED(hr)) - { - goto Finished; - } - - if (FAILED(hr = struHostFxrPath.Append(L"\\")) - || FAILED(hr = struHostFxrPath.Append(struHighestDotnetVersion.QueryStr())) - || FAILED(hr = struHostFxrPath.Append(L"\\hostfxr.dll"))) - { - goto Finished; - } - - if (!UTILITY::CheckIfFileExists(struHostFxrPath.QueryStr())) - { - // ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG - hr = HRESULT_FROM_WIN32(ERROR_FILE_INVALID); - if (SUCCEEDED(struEventMsg.SafeSnwprintf( - ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG, - struHostFxrPath.QueryStr(), - hr))) - { - UTILITY::LogEvent(hEventLog, - EVENTLOG_ERROR_TYPE, - ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND, - struEventMsg.QueryStr()); - } - goto Finished; - } - - if (FAILED(hr = ParseHostfxrArguments( - pcwzArguments, - struExeLocation.QueryStr(), - pcwzApplicationPhysicalPath, - hEventLog, - pdwArgCount, - pbstrArgv))) - { - goto Finished; - } - - if (FAILED(hr = struHostFxrDllLocation->Copy(struHostFxrPath))) - { - goto Finished; } Finished: @@ -383,7 +313,7 @@ HOSTFXR_UTILITY::ParseHostfxrArguments( goto Failure; } - argv = new PWSTR[argc + 1]; + argv = new BSTR[argc + 1]; if (argv == NULL) { hr = E_OUTOFMEMORY; @@ -457,41 +387,220 @@ Finished: return hr; } -// -// Invoke where.exe to find the location of dotnet.exe -// Copies contents of dotnet.exe to a temp file -// Respects path ordering. HRESULT -HOSTFXR_UTILITY::FindDotnetExePath( - _Out_ STRU* struDotnetPath +HOSTFXR_UTILITY::GetAbsolutePathToDotnet( + _Inout_ STRU* pStruAbsolutePathToDotnet ) { HRESULT hr = S_OK; + + // + // If we are given an absolute path to dotnet.exe, we are done + // + if (UTILITY::CheckIfFileExists(pStruAbsolutePathToDotnet->QueryStr())) + { + goto Finished; + } + + // + // If the path was C:\Program Files\dotnet\dotnet + // We need to try appending .exe and check if the file exists too. + // + if (FAILED(hr = pStruAbsolutePathToDotnet->Append(L".exe"))) + { + goto Finished; + } + + if (UTILITY::CheckIfFileExists(pStruAbsolutePathToDotnet->QueryStr())) + { + goto Finished; + } + + // At this point, we are calling where.exe to find dotnet. + // If we encounter any failures, try getting dotnet.exe from the + // backup location. + if (!InvokeWhereToFindDotnet(pStruAbsolutePathToDotnet)) + { + hr = GetAbsolutePathToDotnetFromProgramFiles(pStruAbsolutePathToDotnet); + } + +Finished: + + return hr; +} + +HRESULT +HOSTFXR_UTILITY::GetAbsolutePathToHostFxr( + STRU* pStruAbsolutePathToDotnet, + HANDLE hEventLog, + STRU* pStruAbsolutePathToHostfxr +) +{ + HRESULT hr = S_OK; + STRU struHostFxrPath; + STRU struHostFxrSearchExpression; + STRU struHighestDotnetVersion; + STRU struEventMsg; + std::vector vVersionFolders; + DWORD dwPosition = 0; + + if (FAILED(hr = struHostFxrPath.Copy(pStruAbsolutePathToDotnet))) + { + goto Finished; + } + + dwPosition = struHostFxrPath.LastIndexOf(L'\\', 0); + if (dwPosition == -1) + { + hr = E_FAIL; + goto Finished; + } + + struHostFxrPath.QueryStr()[dwPosition] = L'\0'; + + if (FAILED(hr = struHostFxrPath.SyncWithBuffer()) || + FAILED(hr = struHostFxrPath.Append(L"\\"))) + { + goto Finished; + } + + hr = struHostFxrPath.Append(L"host\\fxr"); + if (FAILED(hr)) + { + goto Finished; + } + + if (!UTILITY::DirectoryExists(&struHostFxrPath)) + { + hr = ERROR_BAD_ENVIRONMENT; + if (SUCCEEDED(struEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, + struHostFxrPath.QueryStr(), + hr))) + { + UTILITY::LogEvent(hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, + struEventMsg.QueryStr()); + } + goto Finished; + } + + // Find all folders under host\\fxr\\ for version numbers. + hr = struHostFxrSearchExpression.Copy(struHostFxrPath); + if (FAILED(hr)) + { + goto Finished; + } + + hr = struHostFxrSearchExpression.Append(L"\\*"); + if (FAILED(hr)) + { + goto Finished; + } + + // As we use the logic from core-setup, we are opting to use std here. + UTILITY::FindDotNetFolders(struHostFxrSearchExpression.QueryStr(), &vVersionFolders); + + if (vVersionFolders.size() == 0) + { + hr = HRESULT_FROM_WIN32(ERROR_BAD_ENVIRONMENT); + if (SUCCEEDED(struEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND_MSG, + struHostFxrPath.QueryStr(), + hr))) + { + UTILITY::LogEvent(hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_HOSTFXR_DIRECTORY_NOT_FOUND, + struEventMsg.QueryStr()); + } + goto Finished; + } + + hr = UTILITY::FindHighestDotNetVersion(vVersionFolders, &struHighestDotnetVersion); + if (FAILED(hr)) + { + goto Finished; + } + + if (FAILED(hr = struHostFxrPath.Append(L"\\")) + || FAILED(hr = struHostFxrPath.Append(struHighestDotnetVersion.QueryStr())) + || FAILED(hr = struHostFxrPath.Append(L"\\hostfxr.dll"))) + { + goto Finished; + } + + if (!UTILITY::CheckIfFileExists(struHostFxrPath.QueryStr())) + { + // ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG + hr = HRESULT_FROM_WIN32(ERROR_FILE_INVALID); + if (SUCCEEDED(struEventMsg.SafeSnwprintf( + ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG, + struHostFxrPath.QueryStr(), + hr))) + { + UTILITY::LogEvent(hEventLog, + EVENTLOG_ERROR_TYPE, + ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND, + struEventMsg.QueryStr()); + } + goto Finished; + } + + if (FAILED(hr = pStruAbsolutePathToHostfxr->Copy(struHostFxrPath))) + { + goto Finished; + } + +Finished: + return hr; +} + +// +// Tries to call where.exe to find the location of dotnet.exe. +// Will check that the bitness of dotnet matches the current +// worker process bitness. +// Returns true if a valid dotnet was found, else false. +// +BOOL +HOSTFXR_UTILITY::InvokeWhereToFindDotnet( + _Inout_ STRU* pStruAbsolutePathToDotnet +) +{ + HRESULT hr = S_OK; + // Arguments to call where.exe STARTUPINFOW startupInfo = { 0 }; PROCESS_INFORMATION processInformation = { 0 }; SECURITY_ATTRIBUTES securityAttributes; - STRU struDotnetSubstring; - STRU struDotnetLocationsString; - LPWSTR pwzDotnetName = NULL; - DWORD dwExitCode; - DWORD dwNumBytesRead; - DWORD dwFilePointer; - DWORD dwBinaryType; - DWORD dwPathSize = MAX_PATH; - INT index = 0; - INT prevIndex = 0; - BOOL fResult = FALSE; - BOOL fIsWow64Process; - BOOL fIsCurrentProcess64Bit; - BOOL fFound = FALSE; + CHAR pzFileContents[READ_BUFFER_SIZE]; HANDLE hStdOutReadPipe = INVALID_HANDLE_VALUE; HANDLE hStdOutWritePipe = INVALID_HANDLE_VALUE; + LPWSTR pwzDotnetName = NULL; + DWORD dwFilePointer; + BOOL fIsWow64Process; + BOOL fIsCurrentProcess64Bit; + DWORD dwExitCode; + STRU struDotnetSubstring; + STRU struDotnetLocationsString; + DWORD dwNumBytesRead; + DWORD dwBinaryType; + INT index = 0; + INT prevIndex = 0; + BOOL fProcessCreationResult = FALSE; + BOOL fResult = FALSE; + // Set the security attributes for the read/write pipe securityAttributes.nLength = sizeof(securityAttributes); securityAttributes.lpSecurityDescriptor = NULL; securityAttributes.bInheritHandle = TRUE; + // Reset the path to dotnet as we will be using whether the string is + // empty or not as state + pStruAbsolutePathToDotnet->Reset(); + + // Create a read/write pipe that will be used for reading the result of where.exe if (!CreatePipe(&hStdOutReadPipe, &hStdOutWritePipe, &securityAttributes, 0)) { hr = HRESULT_FROM_WIN32(GetLastError()); @@ -503,7 +612,7 @@ HOSTFXR_UTILITY::FindDotnetExePath( goto Finished; } - // Set stdout and error to redirect to the temp file. + // Set the stdout and err pipe to the write pipes. startupInfo.cb = sizeof(startupInfo); startupInfo.dwFlags |= STARTF_USESTDHANDLES; startupInfo.hStdOutput = hStdOutWritePipe; @@ -511,14 +620,14 @@ HOSTFXR_UTILITY::FindDotnetExePath( // CreateProcess requires a mutable string to be passed to commandline // See https://blogs.msdn.microsoft.com/oldnewthing/20090601-00/?p=18083/ - pwzDotnetName = SysAllocString(L"\"where.exe\" dotnet.exe"); if (pwzDotnetName == NULL) { - hr = E_OUTOFMEMORY; goto Finished; } - fResult = CreateProcessW(NULL, + + // Create a process to invoke where.exe + fProcessCreationResult = CreateProcessW(NULL, pwzDotnetName, NULL, NULL, @@ -530,14 +639,15 @@ HOSTFXR_UTILITY::FindDotnetExePath( &processInformation ); - if (!fResult) + if (!fProcessCreationResult) { - hr = HRESULT_FROM_WIN32(GetLastError()); goto Finished; } - if (WaitForSingleObject(processInformation.hProcess, 2000) != WAIT_OBJECT_0) // 2 seconds + // Wait for where.exe to return, waiting 2 seconds. + if (WaitForSingleObject(processInformation.hProcess, 2000) != WAIT_OBJECT_0) { + // Timeout occured, terminate the where.exe process and return. TerminateProcess(processInformation.hProcess, 2); hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT); goto Finished; @@ -550,131 +660,95 @@ HOSTFXR_UTILITY::FindDotnetExePath( // if (!GetExitCodeProcess(processInformation.hProcess, &dwExitCode)) { - goto Fallback; + goto Finished; } // // In this block, if anything fails, we will goto our fallback of // looking in C:/Program Files/ // - if (dwExitCode == 0) + if (dwExitCode != 0) { - // Where succeeded. - // Reset file pointer to the beginning of the file. - dwFilePointer = SetFilePointer(hStdOutReadPipe, 0, NULL, FILE_BEGIN); - if (dwFilePointer == INVALID_SET_FILE_POINTER) - { - goto Fallback; - } - - // - // As the call to where.exe succeeded (dotnet.exe was found), ReadFile should not hang. - // TODO consider putting ReadFile in a separate thread with a timeout to guarantee it doesn't block. - // - if (!ReadFile(hStdOutReadPipe, pzFileContents, READ_BUFFER_SIZE, &dwNumBytesRead, NULL)) - { - goto Fallback; - } - if (dwNumBytesRead >= READ_BUFFER_SIZE) - { - // This shouldn't ever be this large. We could continue to call ReadFile in a loop, - // however I'd rather error out here and report an issue. - goto Fallback; - } - - if (FAILED(hr = struDotnetLocationsString.CopyA(pzFileContents, dwNumBytesRead))) - { - goto Finished; - } - - // Check the bitness of the currently running process - // matches the dotnet.exe found. - if (!IsWow64Process(GetCurrentProcess(), &fIsWow64Process)) - { - // Calling IsWow64Process failed - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - if (fIsWow64Process) - { - // 32 bit mode - fIsCurrentProcess64Bit = FALSE; - } - else - { - SYSTEM_INFO systemInfo; - GetNativeSystemInfo(&systemInfo); - fIsCurrentProcess64Bit = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; - } - - while (!fFound) - { - index = struDotnetLocationsString.IndexOf(L"\r\n", prevIndex); - if (index == -1) - { - break; - } - if (FAILED(hr = struDotnetSubstring.Copy(&struDotnetLocationsString.QueryStr()[prevIndex], index - prevIndex))) - { - goto Finished; - } - // \r\n is two wchars, so add 2 here. - prevIndex = index + 2; - - if (GetBinaryTypeW(struDotnetSubstring.QueryStr(), &dwBinaryType) && - fIsCurrentProcess64Bit == (dwBinaryType == SCS_64BIT_BINARY)) { - // Found a valid dotnet. - if (FAILED(hr = struDotnetPath->Copy(struDotnetSubstring))) - { - goto Finished; - } - fFound = TRUE; - } - } + goto Finished; } -Fallback: - - // Look in ProgramFiles - while (!fFound) + // Where succeeded. + // Reset file pointer to the beginning of the file. + dwFilePointer = SetFilePointer(hStdOutReadPipe, 0, NULL, FILE_BEGIN); + if (dwFilePointer == INVALID_SET_FILE_POINTER) { - if (FAILED(hr = struDotnetSubstring.Resize(dwPathSize))) - { - goto Finished; - } - - // Program files will changes based on the - dwNumBytesRead = GetEnvironmentVariable(L"ProgramFiles", struDotnetSubstring.QueryStr(), dwPathSize); - if (dwNumBytesRead == 0) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - goto Finished; - } - else if (dwNumBytesRead == dwPathSize) - { - dwPathSize *= 2 + 30; // for dotnet substring - } - else - { - if (FAILED(hr = struDotnetSubstring.SyncWithBuffer()) || - FAILED(hr = struDotnetSubstring.Append(L"\\dotnet\\dotnet.exe"))) - { - goto Finished; - } - if (!UTILITY::CheckIfFileExists(struDotnetSubstring.QueryStr())) - { - hr = HRESULT_FROM_WIN32( GetLastError() ); - goto Finished; - } - if (FAILED(hr = struDotnetPath->Copy(struDotnetSubstring))) - { - goto Finished; - } - fFound = TRUE; - } + goto Finished; } + // + // As the call to where.exe succeeded (dotnet.exe was found), ReadFile should not hang. + // TODO consider putting ReadFile in a separate thread with a timeout to guarantee it doesn't block. + // + if (!ReadFile(hStdOutReadPipe, pzFileContents, READ_BUFFER_SIZE, &dwNumBytesRead, NULL)) + { + goto Finished; + } + if (dwNumBytesRead >= READ_BUFFER_SIZE) + { + // This shouldn't ever be this large. We could continue to call ReadFile in a loop, + // however if someone had this many dotnet.exes on their machine. + goto Finished; + } + + hr = HRESULT_FROM_WIN32(GetLastError()); + if (FAILED(hr = struDotnetLocationsString.CopyA(pzFileContents, dwNumBytesRead))) + { + goto Finished; + } + + // Check the bitness of the currently running process + // matches the dotnet.exe found. + if (!IsWow64Process(GetCurrentProcess(), &fIsWow64Process)) + { + // Calling IsWow64Process failed + goto Finished; + } + if (fIsWow64Process) + { + // 32 bit mode + fIsCurrentProcess64Bit = FALSE; + } + else + { + // Check the SystemInfo to see if we are currently 32 or 64 bit. + SYSTEM_INFO systemInfo; + GetNativeSystemInfo(&systemInfo); + fIsCurrentProcess64Bit = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; + } + + while (TRUE) + { + index = struDotnetLocationsString.IndexOf(L"\r\n", prevIndex); + if (index == -1) + { + break; + } + if (FAILED(hr = struDotnetSubstring.Copy(&struDotnetLocationsString.QueryStr()[prevIndex], index - prevIndex))) + { + goto Finished; + } + // \r\n is two wchars, so add 2 here. + prevIndex = index + 2; + + if (GetBinaryTypeW(struDotnetSubstring.QueryStr(), &dwBinaryType) && + fIsCurrentProcess64Bit == (dwBinaryType == SCS_64BIT_BINARY)) + { + // The bitness of dotnet matched with the current worker process bitness. + if (FAILED(hr = pStruAbsolutePathToDotnet->Copy(struDotnetSubstring))) + { + goto Finished; + } + fResult = TRUE; + break; + } + } + Finished: if (hStdOutReadPipe != INVALID_HANDLE_VALUE) @@ -698,5 +772,60 @@ Finished: SysFreeString(pwzDotnetName); } + return fResult; +} + + +HRESULT +HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles( + _Inout_ STRU* pStruAbsolutePathToDotnet +) +{ + HRESULT hr = S_OK; + BOOL fFound = FALSE; + DWORD dwNumBytesRead = 0; + DWORD dwPathSize = MAX_PATH; + STRU struDotnetSubstring; + + while (!fFound) + { + if (FAILED(hr = struDotnetSubstring.Resize(dwPathSize))) + { + goto Finished; + } + + dwNumBytesRead = GetEnvironmentVariable(L"ProgramFiles", struDotnetSubstring.QueryStr(), dwPathSize); + if (dwNumBytesRead == 0) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + else if (dwNumBytesRead >= dwPathSize) + { + // + // The path to ProgramFiles should never be this long, but resize and try again. + dwPathSize *= 2 + 30; // for dotnet substring + } + else + { + if (FAILED(hr = struDotnetSubstring.SyncWithBuffer()) || + FAILED(hr = struDotnetSubstring.Append(L"\\dotnet\\dotnet.exe"))) + { + goto Finished; + } + if (!UTILITY::CheckIfFileExists(struDotnetSubstring.QueryStr())) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + goto Finished; + } + if (FAILED(hr = pStruAbsolutePathToDotnet->Copy(struDotnetSubstring))) + { + goto Finished; + } + fFound = TRUE; + } + } + +Finished: return hr; } diff --git a/src/CommonLib/hostfxr_utility.h b/src/CommonLib/hostfxr_utility.h index 2effba31f1..e7703c5bfa 100644 --- a/src/CommonLib/hostfxr_utility.h +++ b/src/CommonLib/hostfxr_utility.h @@ -21,7 +21,7 @@ public: PCWSTR pcwzProcessPath, PCWSTR pcwzApplicationPhysicalPath, PCWSTR pcwzArguments, - _Inout_ STRU* struHostFxrDllLocation, + _Inout_ STRU* pStruHostFxrDllLocation, _Out_ DWORD* pdwArgCount, _Out_ BSTR** ppwzArgv ); @@ -33,7 +33,7 @@ public: PCWSTR pcwzApplicationPhysicalPath, PCWSTR pcwzArguments, HANDLE hEventLog, - _Inout_ STRU* struHostFxrDllLocation, + _Inout_ STRU* pStruHostFxrDllLocation, _Out_ DWORD* pdwArgCount, _Out_ BSTR** ppwzArgv ); @@ -46,13 +46,33 @@ public: PCWSTR pcwzApplicationPhysicalPath, HANDLE hEventLog, _Out_ DWORD* pdwArgCount, - _Out_ BSTR** ppwzArgv + _Out_ BSTR** ppwzArgv ); static HRESULT - FindDotnetExePath( - STRU* struDotnetLocation + GetAbsolutePathToDotnet( + STRU* pStruAbsolutePathToDotnet + ); + + static + HRESULT + GetAbsolutePathToHostFxr( + _In_ STRU* pStruAbsolutePathToDotnet, + _In_ HANDLE hEventLog, + _Out_ STRU* pStruAbsolutePathToHostfxr + ); + + static + BOOL + InvokeWhereToFindDotnet( + _Inout_ STRU* pStruAbsolutePathToDotnet + ); + + static + HRESULT + GetAbsolutePathToDotnetFromProgramFiles( + _Inout_ STRU* pStruAbsolutePathToDotnet ); }; diff --git a/src/CommonLib/resources.h b/src/CommonLib/resources.h index adba68b794..63a9cf301e 100644 --- a/src/CommonLib/resources.h +++ b/src/CommonLib/resources.h @@ -41,3 +41,4 @@ #define ASPNETCORE_EVENT_HOSTFXR_DLL_NOT_FOUND_MSG L"Could not find hostfxr.dll in '%s'. ErrorCode = '0x%x'." #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_INVALID_PROCESS_PATH_MSG L"Invalid or unknown processPath provided in web.config: processPath = %s, ErrorCode = '0x%x'." diff --git a/src/CommonLib/utility.h b/src/CommonLib/utility.h index 49549c0f33..891f0f0092 100644 --- a/src/CommonLib/utility.h +++ b/src/CommonLib/utility.h @@ -118,7 +118,7 @@ public: _In_ WORD dwEventInfoType, _In_ DWORD dwEventId, _In_ LPCWSTR pstrMsg - ); + ); private: diff --git a/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj b/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj index 1dc88d2ac3..910b52ee74 100644 --- a/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj +++ b/test/AspNetCoreModuleTests/AspNetCoreModuleTests.vcxproj @@ -167,6 +167,7 @@ Create + diff --git a/test/AspNetCoreModuleTests/hostfxr_utility_tests.cpp b/test/AspNetCoreModuleTests/hostfxr_utility_tests.cpp index dfa0e3c4b9..bd6528624a 100644 --- a/test/AspNetCoreModuleTests/hostfxr_utility_tests.cpp +++ b/test/AspNetCoreModuleTests/hostfxr_utility_tests.cpp @@ -90,5 +90,63 @@ namespace AspNetCoreModuleTests Assert::AreEqual(E_INVALIDARG, hr); } + + TEST_METHOD(GetAbsolutePathToDotnetFromProgramFiles_BackupWorks) + { + STRU struAbsolutePathToDotnet; + HRESULT hr = S_OK; + BOOL fDotnetInProgramFiles; + BOOL is64Bit; + BOOL fIsWow64 = FALSE; + SYSTEM_INFO systemInfo; + IsWow64Process(GetCurrentProcess(), &fIsWow64); + if (fIsWow64) + { + is64Bit = FALSE; + } + else + { + GetNativeSystemInfo(&systemInfo); + is64Bit = systemInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64; + } + + if (is64Bit) + { + fDotnetInProgramFiles = UTILITY::CheckIfFileExists(L"C:/Program Files/dotnet/dotnet.exe"); + } + else + { + fDotnetInProgramFiles = UTILITY::CheckIfFileExists(L"C:/Program Files (x86)/dotnet/dotnet.exe"); + } + + hr = HOSTFXR_UTILITY::GetAbsolutePathToDotnetFromProgramFiles(&struAbsolutePathToDotnet); + if (fDotnetInProgramFiles) + { + Assert::AreEqual(hr, S_OK); + } + else + { + Assert::AreNotEqual(hr, S_OK); + Assert::IsTrue(struAbsolutePathToDotnet.IsEmpty()); + } + } + + TEST_METHOD(GetHostFxrArguments_InvalidParams) + { + DWORD retVal = 0; + BSTR* bstrArray; + STRU struHostFxrDllLocation; + + HRESULT hr = HOSTFXR_UTILITY::GetHostFxrParameters( + INVALID_HANDLE_VALUE, + L"bogus", // processPath + L"", // application physical path, ignored. + L"ignored", //arguments + NULL, // event log + &retVal, // arg count + &bstrArray); // args array. + + Assert::AreEqual(E_INVALIDARG, hr); + } }; } diff --git a/test/AspNetCoreModuleTests/utility_tests.cpp b/test/AspNetCoreModuleTests/utility_tests.cpp new file mode 100644 index 0000000000..a24711cf1a --- /dev/null +++ b/test/AspNetCoreModuleTests/utility_tests.cpp @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for license information. + +#include "stdafx.h" +#include "CppUnitTest.h" + +using namespace Microsoft::VisualStudio::CppUnitTestFramework; + +namespace AspNetCoreModuleTests +{ + TEST_CLASS(UTILITY_TESTS) + { + public: + + TEST_METHOD(PassUnexpandedString_ExpandsResult) + { + HRESULT hr = S_OK; + PCWSTR unexpandedString = L"ANCM_TEST_ENV_VAR"; + PCWSTR unexpandedStringValue = L"foobar"; + STRU struExpandedString; + SetEnvironmentVariable(L"ANCM_TEST_ENV_VAR", unexpandedStringValue); + + hr = struExpandedString.CopyAndExpandEnvironmentStrings(L"%ANCM_TEST_ENV_VAR%"); + Assert::AreEqual(hr, S_OK); + Assert::AreEqual(L"foobar", struExpandedString.QueryStr()); + } + + TEST_METHOD(PassUnexpandedString_Resize_ExpandsResult) + { + HRESULT hr = S_OK; + PCWSTR unexpandedString = L"ANCM_TEST_ENV_VAR_LONG"; + STRU struStringValue; + STACK_STRU(struExpandedString, MAX_PATH); + + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + struStringValue.Append(L"TestValueThatIsLongerThan256CharactersLongToTriggerResize"); + + SetEnvironmentVariable(unexpandedString, struStringValue.QueryStr()); + + hr = struExpandedString.CopyAndExpandEnvironmentStrings(L"%ANCM_TEST_ENV_VAR_LONG%"); + Assert::AreEqual(hr, S_OK); + Assert::AreEqual(struStringValue.QueryCCH(), struExpandedString.QueryCCH()); + // The values are exactly the same, however Assert::AreEqual is returning false. + //Assert::AreEqual(struStringValue.QueryStr(), struExpandedString.QueryStr()); + Assert::AreEqual(0, wcscmp(struStringValue.QueryStr(), struExpandedString.QueryStr())); + } + }; +} diff --git a/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs b/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs new file mode 100644 index 0000000000..ac58c894e5 --- /dev/null +++ b/test/IISIntegration.FunctionalTests/Inprocess/StartupTests.cs @@ -0,0 +1,240 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using Microsoft.AspNetCore.Server.IntegrationTesting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; +using Xunit; +using Xunit.Abstractions; +using Xunit.Sdk; + + +namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests +{ + public class StartupTests : LoggedTest + { + public StartupTests(ITestOutputHelper output) : base(output) + { + + } + + [Fact] + public async Task ExpandEnvironmentVariableInWebConfig() + { + var runtimeFlavor = RuntimeFlavor.CoreClr; + var serverType = ServerType.IISExpress; + var testName = $"HelloWorld_{runtimeFlavor}"; + var architecture = RuntimeArchitecture.x64; + var dotnetLocation = $"%USERPROFILE%\\.dotnet\\{architecture.ToString()}\\dotnet.exe"; + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("HelloWorldTest"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), serverType, runtimeFlavor, architecture) + { + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Http.config") : null, + SiteName = "HttpTestSite", // This is configured in the Http.config + TargetFramework = "netcoreapp2.1", + ApplicationType = ApplicationType.Portable, + Configuration = +#if DEBUG + "Debug" +#else + "Release" +#endif + }; + + // Point to dotnet installed in user profile. + deploymentParameters.EnvironmentVariables["DotnetPath"] = Environment.ExpandEnvironmentVariables(dotnetLocation); // Path to dotnet. + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"); + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return deploymentResult.HttpClient.GetAsync("HelloWorld"); + }, logger, deploymentResult.HostShutdownToken, retryCount: 30); + + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Hello World", responseText); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + } + } + + [Fact] + public async Task InvalidProcessPath_ExpectServerError() + { + var architecture = RuntimeArchitecture.x64; + var runtimeFlavor = RuntimeFlavor.CoreClr; + var serverType = ServerType.IISExpress; + var testName = $"HelloWorld_{runtimeFlavor}"; + var dotnetLocation = "bogus"; + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("HelloWorldTest"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), serverType, runtimeFlavor, architecture) + { + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Http.config") : null, + SiteName = "HttpTestSite", // This is configured in the Http.config + TargetFramework = "netcoreapp2.1", + ApplicationType = ApplicationType.Portable, + Configuration = +#if DEBUG + "Debug" +#else + "Release" +#endif + }; + + // Point to dotnet installed in user profile. + deploymentParameters.EnvironmentVariables["DotnetPath"] = Environment.ExpandEnvironmentVariables(dotnetLocation); // Path to dotnet. + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", "%DotnetPath%"); + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return deploymentResult.HttpClient.GetAsync("HelloWorld"); + }, logger, deploymentResult.HostShutdownToken, retryCount: 30); + + Assert.Equal(HttpStatusCode.InternalServerError, response.StatusCode); + } + } + } + +#if NETCOREAPP2_0 || NETCOREAPP2_1 + + [Fact] // Consistently fails on CI for net461 + public async Task StandaloneApplication_ExpectCorrectPublish() + { + var architecture = RuntimeArchitecture.x64; + var runtimeFlavor = RuntimeFlavor.CoreClr; + var serverType = ServerType.IISExpress; + var testName = $"HelloWorld_{runtimeFlavor}"; + + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("HelloWorldTest"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), serverType, runtimeFlavor, architecture) + { + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Http.config") : null, + SiteName = "HttpTestSite", // This is configured in the Http.config + TargetFramework = "netcoreapp2.1", + ApplicationType = ApplicationType.Standalone, + Configuration = +#if DEBUG + "Debug" +#else + "Release" +#endif + }; + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return deploymentResult.HttpClient.GetAsync("HelloWorld"); + }, logger, deploymentResult.HostShutdownToken, retryCount: 30); + + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Hello World", responseText); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + } + } + + [Fact] // Consistently fails on CI for net461 + public async Task StandaloneApplication_AbsolutePathToExe_ExpectCorrectPublish() + { + var architecture = RuntimeArchitecture.x64; + var runtimeFlavor = RuntimeFlavor.CoreClr; + var serverType = ServerType.IISExpress; + var testName = $"HelloWorld_{runtimeFlavor}"; + + using (StartLog(out var loggerFactory, testName)) + { + var logger = loggerFactory.CreateLogger("HelloWorldTest"); + + var deploymentParameters = new DeploymentParameters(Helpers.GetInProcessTestSitesPath(), serverType, runtimeFlavor, architecture) + { + ServerConfigTemplateContent = (serverType == ServerType.IISExpress) ? File.ReadAllText("AppHostConfig/Http.config") : null, + SiteName = "HttpTestSite", // This is configured in the Http.config + TargetFramework = "netcoreapp2.1", + ApplicationType = ApplicationType.Standalone, + Configuration = +#if DEBUG + "Debug" +#else + "Release" +#endif + }; + + using (var deployer = ApplicationDeployerFactory.Create(deploymentParameters, loggerFactory)) + { + var deploymentResult = await deployer.DeployAsync(); + + Helpers.ModifyAspNetCoreSectionInWebConfig(deploymentResult, "processPath", $"{deploymentResult.ContentRoot}\\IISTestSite.exe"); + + // Request to base address and check if various parts of the body are rendered & measure the cold startup time. + var response = await RetryHelper.RetryRequest(() => + { + return deploymentResult.HttpClient.GetAsync("HelloWorld"); + }, logger, deploymentResult.HostShutdownToken, retryCount: 30); + + var responseText = await response.Content.ReadAsStringAsync(); + try + { + Assert.Equal("Hello World", responseText); + } + catch (XunitException) + { + logger.LogWarning(response.ToString()); + logger.LogWarning(responseText); + throw; + } + } + } + } + +#elif NET461 +#else +#error Target frameworks need to be updated +#endif + + } +} diff --git a/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs b/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs index c09b1bf3a7..def988473f 100644 --- a/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs +++ b/test/IISIntegration.FunctionalTests/Utilities/Helpers.cs @@ -1,8 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.AspNetCore.Server.IntegrationTesting; using System; using System.IO; +using System.Linq; +using System.Xml.Linq; namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests { @@ -29,5 +32,16 @@ namespace Microsoft.AspNetCore.Server.IISIntegration.FunctionalTests "..", // projectfolder "TestSites")); } + + public static void ModifyAspNetCoreSectionInWebConfig(DeploymentResult deploymentResult, string key, string value) + { + // modify the web.config after publish + var root = deploymentResult.ContentRoot; + var webConfigFile = $"{root}/web.config"; + var config = XDocument.Load(webConfigFile); + var element = config.Descendants("aspNetCore").FirstOrDefault(); + element.SetAttributeValue(key, value); + config.Save(webConfigFile); + } } }