From 87fa4cddb50cb33f99756b456664c0d3a87fee31 Mon Sep 17 00:00:00 2001 From: Elinor Fung <47805090+elinor-fung@users.noreply.github.com> Date: Mon, 25 Mar 2019 16:57:29 -0700 Subject: [PATCH] Expose function for locating hostfxr (dotnet/core-setup#5522) * Add nethost library and implement nethost_get_hostfxr_path * Add basic tests for nethost_get_hostfxr_path Commit migrated from https://github.com/dotnet/core-setup/commit/6001915623d99527f5af3252b9d19694935ae4a8 --- docs/installer/design-docs/host-components.md | 2 +- .../corehost/Windows/gen-buildsys-win.bat | 4 +- src/installer/corehost/build.proj | 19 +- src/installer/corehost/build.sh | 4 +- src/installer/corehost/cli/CMakeLists.txt | 1 + src/installer/corehost/cli/comhost/CMakeLists.txt | 7 - src/installer/corehost/cli/comhost/comhost.cpp | 6 +- src/installer/corehost/cli/fxr_resolver.cpp | 20 +-- src/installer/corehost/cli/fxr_resolver.h | 5 +- src/installer/corehost/cli/ijwhost/CMakeLists.txt | 7 - src/installer/corehost/cli/ijwhost/ijwhost.cpp | 4 +- src/installer/corehost/cli/nethost/CMakeLists.txt | 27 +++ src/installer/corehost/cli/nethost/nethost.cpp | 52 ++++++ src/installer/corehost/cli/nethost/nethost.h | 67 +++++++ src/installer/corehost/cli/setup.cmake | 6 +- src/installer/corehost/cli/test/CMakeLists.txt | 1 + .../corehost/cli/test/nativehost/CMakeLists.txt | 26 +++ .../corehost/cli/test/nativehost/nativehost.cpp | 75 ++++++++ src/installer/corehost/cli/test/testexe.cmake | 16 ++ src/installer/corehost/corehost.cpp | 4 +- src/installer/signing/sign.proj | 3 + .../HostActivationTests/MultilevelSDKLookup.cs | 30 +--- .../HostActivationTests/NativeHosting/Nethost.cs | 197 +++++++++++++++++++++ .../HostActivationTests/PortableAppActivation.cs | 17 +- .../RegisteredInstallKeyOverride.cs | 58 ++++++ 25 files changed, 568 insertions(+), 90 deletions(-) create mode 100644 src/installer/corehost/cli/nethost/CMakeLists.txt create mode 100644 src/installer/corehost/cli/nethost/nethost.cpp create mode 100644 src/installer/corehost/cli/nethost/nethost.h create mode 100644 src/installer/corehost/cli/test/nativehost/CMakeLists.txt create mode 100644 src/installer/corehost/cli/test/nativehost/nativehost.cpp create mode 100644 src/installer/corehost/cli/test/testexe.cmake create mode 100644 src/installer/test/HostActivationTests/NativeHosting/Nethost.cs create mode 100644 src/installer/test/HostActivationTests/RegisteredInstallKeyOverride.cs diff --git a/docs/installer/design-docs/host-components.md b/docs/installer/design-docs/host-components.md index c9870c5..21252a6 100644 --- a/docs/installer/design-docs/host-components.md +++ b/docs/installer/design-docs/host-components.md @@ -9,7 +9,7 @@ The .NET Core default hosting setup consists of several components which are des * `comhost` (library) - which is used to enable COM server hosting. Component which wants to expose COM server objects will be built with this dynamic library in its output. The `comhost` then acts as the main entry point for the OS. The executable does just one thing, it finds the `hostfxr` library and passes control to it. It also exposes the right entry points for its purpose (so the "main" for `dotnet` and `apphost`, the COM exports for `comhost` and so on). -* `dotnet` host - `hostfxr` is obtained from the `./shared/host/fxr` folder (relative to the location of the `dotnet` host). +* `dotnet` host - `hostfxr` is obtained from the `./host/fxr` folder (relative to the location of the `dotnet` host). * `apphost` and `comhost` - `hostfxr` is located by 1. The app's folder is searched first. This is either the folder where the `apphost` or `comhost` lives or in case of `apphost` it is the path it has embedded in it as the app path. 1. If the `DOTNET_ROOT` environment variable is defined, that path is searched diff --git a/src/installer/corehost/Windows/gen-buildsys-win.bat b/src/installer/corehost/Windows/gen-buildsys-win.bat index ff97131..58a18cd 100644 --- a/src/installer/corehost/Windows/gen-buildsys-win.bat +++ b/src/installer/corehost/Windows/gen-buildsys-win.bat @@ -41,8 +41,8 @@ for /f "delims=" %%a in ('powershell -NoProfile -ExecutionPolicy ByPass "& .\Win popd :DoGen -echo "%CMakePath%" %__sourceDir% %__SDKVersion% "-DCLI_CMAKE_RUNTIME_ID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_HOST_VER:STRING=%__HostVersion%" "-DCLI_CMAKE_APPHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_COMHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_IJWHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_HOST_FXR_VER:STRING=%__HostFxrVersion%" "-DCLI_CMAKE_HOST_POLICY_VER:STRING=%__HostPolicyVersion%" "-DCLI_CMAKE_PKG_RID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_COMMIT_HASH:STRING=%__LatestCommit%" "-DCLI_CMAKE_PLATFORM_ARCH_%cm_Arch%=1" "-DCMAKE_INSTALL_PREFIX=%__CMakeBinDir%" "-DCLI_CMAKE_RESOURCE_DIR:STRING=%__ResourcesDir%" -G "Visual Studio %__VSString%" %__ExtraCmakeParams% -"%CMakePath%" %__sourceDir% %__SDKVersion% "-DCLI_CMAKE_RUNTIME_ID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_HOST_VER:STRING=%__HostVersion%" "-DCLI_CMAKE_APPHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_COMHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_IJWHOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_HOST_FXR_VER:STRING=%__HostFxrVersion%" "-DCLI_CMAKE_HOST_POLICY_VER:STRING=%__HostPolicyVersion%" "-DCLI_CMAKE_PKG_RID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_COMMIT_HASH:STRING=%__LatestCommit%" "-DCLI_CMAKE_PLATFORM_ARCH_%cm_Arch%=1" "-DCMAKE_INSTALL_PREFIX=%__CMakeBinDir%" "-DCLI_CMAKE_RESOURCE_DIR:STRING=%__ResourcesDir%" -G "Visual Studio %__VSString%" %__ExtraCmakeParams% +echo "%CMakePath%" %__sourceDir% %__SDKVersion% "-DCLI_CMAKE_RUNTIME_ID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_HOST_VER:STRING=%__HostVersion%" "-DCLI_CMAKE_COMMON_HOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_HOST_FXR_VER:STRING=%__HostFxrVersion%" "-DCLI_CMAKE_HOST_POLICY_VER:STRING=%__HostPolicyVersion%" "-DCLI_CMAKE_PKG_RID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_COMMIT_HASH:STRING=%__LatestCommit%" "-DCLI_CMAKE_PLATFORM_ARCH_%cm_Arch%=1" "-DCMAKE_INSTALL_PREFIX=%__CMakeBinDir%" "-DCLI_CMAKE_RESOURCE_DIR:STRING=%__ResourcesDir%" -G "Visual Studio %__VSString%" %__ExtraCmakeParams% +"%CMakePath%" %__sourceDir% %__SDKVersion% "-DCLI_CMAKE_RUNTIME_ID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_HOST_VER:STRING=%__HostVersion%" "-DCLI_CMAKE_COMMON_HOST_VER:STRING=%__AppHostVersion%" "-DCLI_CMAKE_HOST_FXR_VER:STRING=%__HostFxrVersion%" "-DCLI_CMAKE_HOST_POLICY_VER:STRING=%__HostPolicyVersion%" "-DCLI_CMAKE_PKG_RID:STRING=%cm_BaseRid%" "-DCLI_CMAKE_COMMIT_HASH:STRING=%__LatestCommit%" "-DCLI_CMAKE_PLATFORM_ARCH_%cm_Arch%=1" "-DCMAKE_INSTALL_PREFIX=%__CMakeBinDir%" "-DCLI_CMAKE_RESOURCE_DIR:STRING=%__ResourcesDir%" -G "Visual Studio %__VSString%" %__ExtraCmakeParams% endlocal GOTO :DONE diff --git a/src/installer/corehost/build.proj b/src/installer/corehost/build.proj index 5179384..260a712 100644 --- a/src/installer/corehost/build.proj +++ b/src/installer/corehost/build.proj @@ -7,7 +7,7 @@ - + true @@ -19,11 +19,11 @@ DependsOnTargets="GetLatestCommitHash;GenerateVersionSourceFile"> $(IntermediateOutputRootPath)corehost\cmake\ - + --configuration $(ConfigurationGroup) --arch $(TargetArchitecture) --apphostver $(AppHostVersion) --hostver $(HostVersion) --fxrver $(HostResolverVersion) --policyver $(HostPolicyVersion) --commithash $(LatestCommit) - $(BuildArgs) -portable - $(BuildArgs) --cross - $(BuildArgs) --stripsymbols + $(BuildArgs) -portable + $(BuildArgs) --cross + $(BuildArgs) --stripsymbols @@ -51,15 +51,18 @@ .NET Core IJW Host + + .NET Core Component Host + - + $(ConfigurationGroup) $(TargetArchitecture) apphostver $(AppHostVersion) hostver $(HostVersion) fxrver $(HostResolverVersion) policyver $(HostPolicyVersion) commit $(LatestCommit) rid $(OutputRid) - $(BuildArgs) portable + $(BuildArgs) portable $(BuildArgs) incremental-native-build diff --git a/src/installer/corehost/build.sh b/src/installer/corehost/build.sh index 572e827..f64b6b6 100755 --- a/src/installer/corehost/build.sh +++ b/src/installer/corehost/build.sh @@ -263,9 +263,9 @@ if [ $__CrossBuild == 1 ]; then fi export TARGET_BUILD_ARCH=$__build_arch_lowcase export __DistroRid=$__rid_plat - cmake "$DIR" -G "Unix Makefiles" $__cmake_defines -DCLI_CMAKE_HOST_VER:STRING=$__host_ver -DCLI_CMAKE_APPHOST_VER:STRING=$__apphost_ver -DCLI_CMAKE_HOST_FXR_VER:STRING=$__fxr_ver -DCLI_CMAKE_HOST_POLICY_VER:STRING=$__policy_ver -DCLI_CMAKE_PKG_RID:STRING=$__base_rid -DCLI_CMAKE_COMMIT_HASH:STRING=$__commit_hash -DCMAKE_INSTALL_PREFIX=$__cmake_bin_prefix -DCMAKE_TOOLCHAIN_FILE=$DIR/../../cross/toolchain.cmake + cmake "$DIR" -G "Unix Makefiles" $__cmake_defines -DCLI_CMAKE_HOST_VER:STRING=$__host_ver -DCLI_CMAKE_COMMON_HOST_VER:STRING=$__apphost_ver -DCLI_CMAKE_HOST_FXR_VER:STRING=$__fxr_ver -DCLI_CMAKE_HOST_POLICY_VER:STRING=$__policy_ver -DCLI_CMAKE_PKG_RID:STRING=$__base_rid -DCLI_CMAKE_COMMIT_HASH:STRING=$__commit_hash -DCMAKE_INSTALL_PREFIX=$__cmake_bin_prefix -DCMAKE_TOOLCHAIN_FILE=$DIR/../../cross/toolchain.cmake else - cmake "$DIR" -G "Unix Makefiles" $__cmake_defines -DCLI_CMAKE_HOST_VER:STRING=$__host_ver -DCLI_CMAKE_APPHOST_VER:STRING=$__apphost_ver -DCLI_CMAKE_HOST_FXR_VER:STRING=$__fxr_ver -DCLI_CMAKE_HOST_POLICY_VER:STRING=$__policy_ver -DCLI_CMAKE_PKG_RID:STRING=$__base_rid -DCLI_CMAKE_COMMIT_HASH:STRING=$__commit_hash -DCMAKE_INSTALL_PREFIX=$__cmake_bin_prefix + cmake "$DIR" -G "Unix Makefiles" $__cmake_defines -DCLI_CMAKE_HOST_VER:STRING=$__host_ver -DCLI_CMAKE_COMMON_HOST_VER:STRING=$__apphost_ver -DCLI_CMAKE_HOST_FXR_VER:STRING=$__fxr_ver -DCLI_CMAKE_HOST_POLICY_VER:STRING=$__policy_ver -DCLI_CMAKE_PKG_RID:STRING=$__base_rid -DCLI_CMAKE_COMMIT_HASH:STRING=$__commit_hash -DCMAKE_INSTALL_PREFIX=$__cmake_bin_prefix fi popd diff --git a/src/installer/corehost/cli/CMakeLists.txt b/src/installer/corehost/cli/CMakeLists.txt index 50bd771..4d307fc 100644 --- a/src/installer/corehost/cli/CMakeLists.txt +++ b/src/installer/corehost/cli/CMakeLists.txt @@ -2,6 +2,7 @@ add_subdirectory(apphost) add_subdirectory(dotnet) add_subdirectory(fxr) add_subdirectory(hostpolicy) +add_subdirectory(nethost) add_subdirectory(test_fx_ver) add_subdirectory(test) diff --git a/src/installer/corehost/cli/comhost/CMakeLists.txt b/src/installer/corehost/cli/comhost/CMakeLists.txt index ecd2289..c80db1e 100644 --- a/src/installer/corehost/cli/comhost/CMakeLists.txt +++ b/src/installer/corehost/cli/comhost/CMakeLists.txt @@ -36,13 +36,6 @@ endif() include(../lib.cmake) -# Move to setup.cmake if COMHOST is ever built on non-Windows -if("${CLI_CMAKE_COMHOST_VER}" STREQUAL "") - message(FATAL_ERROR "comhost version is not specified") -else() - add_definitions(-DLIBHOST_PKG_VER="${CLI_CMAKE_COMHOST_VER}") -endif() - add_definitions(-DFEATURE_LIBHOST=1) # Specify non-default Windows libs to be used for Arm/Arm64 builds diff --git a/src/installer/corehost/cli/comhost/comhost.cpp b/src/installer/corehost/cli/comhost/comhost.cpp index 7bad50f..0798b18 100644 --- a/src/installer/corehost/cli/comhost/comhost.cpp +++ b/src/installer/corehost/cli/comhost/comhost.cpp @@ -62,7 +62,7 @@ namespace { get_comhost_error_stream() << msg; } - + int get_com_activation_delegate(pal::string_t *app_path, com_activation_fn *delegate) { pal::string_t host_path; @@ -74,7 +74,7 @@ namespace pal::string_t dotnet_root; pal::string_t fxr_path; - if (!resolve_fxr_path(get_directory(host_path), &dotnet_root, &fxr_path)) + if (!fxr_resolver::try_get_path(get_directory(host_path), &dotnet_root, &fxr_path)) { return StatusCode::CoreHostLibMissingFailure; } @@ -130,7 +130,7 @@ COM_API HRESULT STDMETHODCALLTYPE DllGetClassObject( { trace::setup(); reset_comhost_error_stream(); - + error_writer_scope_t writer_scope(comhost_error_writer); int ec = get_com_activation_delegate(&app_path, &act); diff --git a/src/installer/corehost/cli/fxr_resolver.cpp b/src/installer/corehost/cli/fxr_resolver.cpp index d7f1d6f..4757972 100644 --- a/src/installer/corehost/cli/fxr_resolver.cpp +++ b/src/installer/corehost/cli/fxr_resolver.cpp @@ -2,15 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#include "pal.h" +#include #include "fxr_resolver.h" -#include "error_codes.h" -#include "fx_ver.h" -#include "trace.h" -#include "utils.h" +#include +#include +#include namespace -{ +{ bool get_latest_fxr(pal::string_t fxr_root, pal::string_t* out_fxr_path) { trace::info(_X("Reading fx resolver directory=[%s]"), fxr_root.c_str()); @@ -54,12 +53,12 @@ namespace } } -bool resolve_fxr_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path) +bool fxr_resolver::try_get_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path) { pal::string_t fxr_dir; #if FEATURE_APPHOST || FEATURE_LIBHOST - // If a hostfxr exists in app_root, then assume self-contained. - if (library_exists_in_dir(root_path, LIBFXR_NAME, out_fxr_path)) + // If a hostfxr exists in root_path, then assume self-contained. + if (root_path.length() > 0 && library_exists_in_dir(root_path, LIBFXR_NAME, out_fxr_path)) { trace::info(_X("Resolved fxr [%s]..."), out_fxr_path->c_str()); out_dotnet_root->assign(root_path); @@ -67,7 +66,6 @@ bool resolve_fxr_path(const pal::string_t& root_path, pal::string_t* out_dotnet_ } // For framework-dependent apps, use DOTNET_ROOT - pal::string_t default_install_location; pal::string_t dotnet_root_env_var_name = get_dotnet_root_env_var_name(); if (get_file_path_from_env(dotnet_root_env_var_name.c_str(), out_dotnet_root)) @@ -86,8 +84,6 @@ bool resolve_fxr_path(const pal::string_t& root_path, pal::string_t* out_dotnet_ trace::error(_X("A fatal error occurred, the default install location cannot be obtained.")); return false; } - trace::info(_X("Using default installation location [%s] as runtime location."), default_install_location.c_str()); - out_dotnet_root->assign(default_install_location); } fxr_dir = *out_dotnet_root; diff --git a/src/installer/corehost/cli/fxr_resolver.h b/src/installer/corehost/cli/fxr_resolver.h index de8dd7f..30dfc29 100644 --- a/src/installer/corehost/cli/fxr_resolver.h +++ b/src/installer/corehost/cli/fxr_resolver.h @@ -7,6 +7,9 @@ #include -bool resolve_fxr_path(const pal::string_t& host_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path); +namespace fxr_resolver +{ + bool try_get_path(const pal::string_t& root_path, pal::string_t* out_dotnet_root, pal::string_t* out_fxr_path); +} #endif //_COREHOST_CLI_FXR_RESOLVER_H_ diff --git a/src/installer/corehost/cli/ijwhost/CMakeLists.txt b/src/installer/corehost/cli/ijwhost/CMakeLists.txt index f07d9aa..f706e04 100644 --- a/src/installer/corehost/cli/ijwhost/CMakeLists.txt +++ b/src/installer/corehost/cli/ijwhost/CMakeLists.txt @@ -29,13 +29,6 @@ if(WIN32) Exports.def) endif() -# Move to setup.cmake if IJWHOST is ever built on non-Windows -if("${CLI_CMAKE_IJWHOST_VER}" STREQUAL "") - message(FATAL_ERROR "ijwhost version is not specified") -else() - add_definitions(-DLIBHOST_PKG_VER="${CLI_CMAKE_IJWHOST_VER}") -endif() - set (ASM_HELPERS_SOURCES ${ARCH_SPECIFIC_FOLDER_NAME}/asmhelpers.asm) diff --git a/src/installer/corehost/cli/ijwhost/ijwhost.cpp b/src/installer/corehost/cli/ijwhost/ijwhost.cpp index a4337ea..1894342 100644 --- a/src/installer/corehost/cli/ijwhost/ijwhost.cpp +++ b/src/installer/corehost/cli/ijwhost/ijwhost.cpp @@ -34,7 +34,7 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m pal::string_t dotnet_root; pal::string_t fxr_path; - if (!resolve_fxr_path(get_directory(host_path), &dotnet_root, &fxr_path)) + if (!fxr_resolver::try_get_path(get_directory(host_path), &dotnet_root, &fxr_path)) { return StatusCode::CoreHostLibMissingFailure; } @@ -61,7 +61,7 @@ pal::hresult_t get_load_in_memory_assembly_delegate(pal::dll_t handle, load_in_m trace::error(_X("Failed to resolve full path of the current mixed-mode module [%s]"), host_path.c_str()); return StatusCode::LibHostCurExeFindFailure; } - + return get_delegate_from_hostfxr(host_path.c_str(), dotnet_root.c_str(), app_path.c_str(), hostfxr_delegate_type::load_in_memory_assembly, (void**)delegate); } diff --git a/src/installer/corehost/cli/nethost/CMakeLists.txt b/src/installer/corehost/cli/nethost/CMakeLists.txt new file mode 100644 index 0000000..8528cf3 --- /dev/null +++ b/src/installer/corehost/cli/nethost/CMakeLists.txt @@ -0,0 +1,27 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +cmake_minimum_required (VERSION 2.6) +project(nethost) + +set(DOTNET_PROJECT_NAME "nethost") + +# Include directories +include_directories(../fxr) + +# CMake does not recommend using globbing since it messes with the freshness checks +set(SOURCES + nethost.cpp + ../fxr_resolver.cpp + ../fxr/fx_ver.cpp +) + +include(../lib.cmake) + +add_definitions(-DFEATURE_LIBHOST=1) +add_definitions(-DNETHOST_EXPORT) + +install(FILES nethost.h DESTINATION corehost) +install(TARGETS nethost DESTINATION corehost) +install_symbols(nethost corehost) \ No newline at end of file diff --git a/src/installer/corehost/cli/nethost/nethost.cpp b/src/installer/corehost/cli/nethost/nethost.cpp new file mode 100644 index 0000000..637a3eb --- /dev/null +++ b/src/installer/corehost/cli/nethost/nethost.cpp @@ -0,0 +1,52 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include "nethost.h" +#include +#include +#include +#include +#include + +namespace +{ + // Swallow the trace messages so we don't output to stderr of a process that we do not own unless tracing is enabled. + void swallow_trace(const pal::char_t* msg) + { + (void)msg; + } +} + +NETHOST_API int NETHOST_CALLTYPE nethost_get_hostfxr_path( + char_t * result_buffer, + size_t buffer_size, + size_t * out_buffer_required_size, + const char_t * assembly_path) +{ + if (out_buffer_required_size == nullptr) + return StatusCode::InvalidArgFailure; + + trace::setup(); + error_writer_scope_t writer_scope(swallow_trace); + + pal::string_t root_path; + if (assembly_path != nullptr) + root_path = get_directory(assembly_path); + + pal::string_t dotnet_root; + pal::string_t fxr_path; + if(!fxr_resolver::try_get_path(root_path, &dotnet_root, &fxr_path)) + return StatusCode::CoreHostLibMissingFailure; + + size_t len = fxr_path.length(); + size_t required_size = len + 1; // null terminator + + *out_buffer_required_size = required_size; + if (result_buffer == nullptr || buffer_size < required_size) + return StatusCode::HostApiBufferTooSmall; + + fxr_path.copy(result_buffer, len); + result_buffer[len] = '\0'; + return StatusCode::Success; +} \ No newline at end of file diff --git a/src/installer/corehost/cli/nethost/nethost.h b/src/installer/corehost/cli/nethost/nethost.h new file mode 100644 index 0000000..c7f3edc --- /dev/null +++ b/src/installer/corehost/cli/nethost/nethost.h @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#ifndef __NETHOST_H__ +#define __NETHOST_H__ + +#include + +#if defined(_WIN32) + #ifdef NETHOST_EXPORT + #define NETHOST_API __declspec(dllexport) + #else + #define NETHOST_API __declspec(dllimport) + #endif + + #define NETHOST_CALLTYPE __stdcall + #ifdef _WCHAR_T_DEFINED + using char_t = wchar_t; + #else + using char_t = unsigned short; + #endif +#else + #ifdef NETHOST_EXPORT + #define NETHOST_API __attribute__((__visibility__("default"))) + #else + #define NETHOST_API + #endif + + #define NETHOST_CALLTYPE + using char_t = char; +#endif + +// +// Get the path to the hostfxr library +// +// Parameters: +// result_buffer +// Buffer that will be populated with the hostfxr path, including a null terminator. +// +// buffer_size +// Size of result_buffer in char_t units +// +// out_buffer_required_size +// Minimum required size in char_t units for a buffer to hold the hostfxr path +// +// assembly_path +// Optional. Path to the compenent's assembly. Whether or not this is specified +// determines the behaviour for locating the hostfxr library. +// If nullptr, hostfxr is located using the enviroment variable or global registration +// If specified, hostfxr is located as if the assembly_path is the apphost +// +// Return value: +// 0 on success, otherwise failure +// 0x80008098 - result_buffer is too small (HostApiBufferTooSmall) +// +// Remarks: +// The full search for the hostfxr library is done on every call. To minimize the need +// to call this function multiple times, pass a large result_buffer (e.g. PATH_MAX). +// +extern "C" NETHOST_API int NETHOST_CALLTYPE nethost_get_hostfxr_path( + char_t * result_buffer, + size_t buffer_size, + size_t * out_buffer_required_size, + const char_t * assembly_path); + +#endif // __NETHOST_H__ \ No newline at end of file diff --git a/src/installer/corehost/cli/setup.cmake b/src/installer/corehost/cli/setup.cmake index 983f968..0d6a1cd 100644 --- a/src/installer/corehost/cli/setup.cmake +++ b/src/installer/corehost/cli/setup.cmake @@ -24,10 +24,10 @@ else() add_definitions(-DHOST_PKG_VER="${CLI_CMAKE_HOST_VER}") endif() -if("${CLI_CMAKE_APPHOST_VER}" STREQUAL "") - message(FATAL_ERROR "apphost version is not specified") +if("${CLI_CMAKE_COMMON_HOST_VER}" STREQUAL "") + message(FATAL_ERROR "Common host version is not specified") else() - add_definitions(-DAPPHOST_PKG_VER="${CLI_CMAKE_APPHOST_VER}") + add_definitions(-DCOMMON_HOST_PKG_VER="${CLI_CMAKE_COMMON_HOST_VER}") endif() if("${CLI_CMAKE_PKG_RID}" STREQUAL "") diff --git a/src/installer/corehost/cli/test/CMakeLists.txt b/src/installer/corehost/cli/test/CMakeLists.txt index bc83dc1..c898a29 100644 --- a/src/installer/corehost/cli/test/CMakeLists.txt +++ b/src/installer/corehost/cli/test/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(mockhostpolicy) +add_subdirectory(nativehost) \ No newline at end of file diff --git a/src/installer/corehost/cli/test/nativehost/CMakeLists.txt b/src/installer/corehost/cli/test/nativehost/CMakeLists.txt new file mode 100644 index 0000000..e1eeec1 --- /dev/null +++ b/src/installer/corehost/cli/test/nativehost/CMakeLists.txt @@ -0,0 +1,26 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +cmake_minimum_required (VERSION 2.8.12) +project(nativehost) + +set(DOTNET_PROJECT_NAME "nativehost") + +set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) +set(MACOSX_RPATH ON) +if (CMAKE_SYSTEM_NAME STREQUAL Darwin) + set(CMAKE_INSTALL_RPATH "@loader_path") +else() + set(CMAKE_INSTALL_RPATH "\$ORIGIN") +endif() + +include_directories(${CMAKE_CURRENT_LIST_DIR}/../../nethost) + +set(SOURCES + ./nativehost.cpp +) + +include(../testexe.cmake) + +target_link_libraries(${DOTNET_PROJECT_NAME} nethost) \ No newline at end of file diff --git a/src/installer/corehost/cli/test/nativehost/nativehost.cpp b/src/installer/corehost/cli/test/nativehost/nativehost.cpp new file mode 100644 index 0000000..ce50fa4 --- /dev/null +++ b/src/installer/corehost/cli/test/nativehost/nativehost.cpp @@ -0,0 +1,75 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#include +#include +#include +#include + +namespace +{ + std::vector tostr(const pal::string_t &value) + { + std::vector vect; + pal::pal_utf8string(value, &vect); + return vect; + } +} + +#if defined(_WIN32) +int __cdecl wmain(const int argc, const pal::char_t *argv[]) +#else +int main(const int argc, const pal::char_t *argv[]) +#endif +{ + if (argc < 2) + { + std::cerr << "Invalid arguments" << std::endl; + return -1; + } + + const pal::char_t *command = argv[1]; + if (pal::strcmp(command, _X("nethost_get_hostfxr_path")) == 0) + { + const pal::char_t *assembly_path = nullptr; + if (argc >= 3) + assembly_path = argv[2]; + +#if defined(_WIN32) + pal::string_t testOverride; + if (pal::getenv(_X("TEST_OVERRIDE_PROGRAMFILES"), &testOverride)) + { + std::cout << tostr(testOverride).data() << std::endl; + ::SetEnvironmentVariableW(_X("ProgramFiles"), testOverride.c_str()); + ::SetEnvironmentVariableW(_X("ProgramFiles(x86)"), testOverride.c_str()); + } +#endif + + pal::string_t fxr_path; + size_t len = 0; + int res = nethost_get_hostfxr_path(nullptr, 0, &len, assembly_path); + if (res == StatusCode::HostApiBufferTooSmall) + { + fxr_path.resize(len); + res = nethost_get_hostfxr_path(&fxr_path[0], fxr_path.size(), &len, assembly_path); + } + + if (res == StatusCode::Success) + { + std::cout << "nethost_get_hostfxr_path succeeded" << std::endl; + std::cout << "hostfxr_path: " << tostr(pal::to_lower(fxr_path)).data() << std::endl; + return 0; + } + else + { + std::cout << "nethost_get_hostfxr_path failed: " << std::hex << std::showbase << res << std::endl; + return 1; + } + } + else + { + std::cerr << "Invalid arguments" << std::endl; + return -1; + } +} \ No newline at end of file diff --git a/src/installer/corehost/cli/test/testexe.cmake b/src/installer/corehost/cli/test/testexe.cmake new file mode 100644 index 0000000..0aa25d9 --- /dev/null +++ b/src/installer/corehost/cli/test/testexe.cmake @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +project(${DOTNET_PROJECT_NAME}) + +set(SKIP_VERSIONING 1) + +include(${CMAKE_CURRENT_LIST_DIR}/../common.cmake) + +add_executable(${DOTNET_PROJECT_NAME} ${SOURCES}) + +install(TARGETS ${DOTNET_PROJECT_NAME} DESTINATION corehost_test) +install_symbols(${DOTNET_PROJECT_NAME} corehost_test) + +set_common_libs("exe") \ No newline at end of file diff --git a/src/installer/corehost/corehost.cpp b/src/installer/corehost/corehost.cpp index fa85473..f472c72 100644 --- a/src/installer/corehost/corehost.cpp +++ b/src/installer/corehost/corehost.cpp @@ -12,7 +12,7 @@ #if FEATURE_APPHOST #define CURHOST_TYPE _X("apphost") -#define CUREXE_PKG_VER APPHOST_PKG_VER +#define CUREXE_PKG_VER COMMON_HOST_PKG_VER #define CURHOST_EXE /** @@ -156,7 +156,7 @@ int exe_start(const int argc, const pal::char_t* argv[]) pal::string_t dotnet_root; pal::string_t fxr_path; - if (!resolve_fxr_path(app_root, &dotnet_root, &fxr_path)) + if (!fxr_resolver::try_get_path(app_root, &dotnet_root, &fxr_path)) { return StatusCode::CoreHostLibMissingFailure; } diff --git a/src/installer/signing/sign.proj b/src/installer/signing/sign.proj index ef80e07..170fd30 100644 --- a/src/installer/signing/sign.proj +++ b/src/installer/signing/sign.proj @@ -40,6 +40,9 @@ $(CertificateId) + + $(CertificateId) + diff --git a/src/installer/test/HostActivationTests/MultilevelSDKLookup.cs b/src/installer/test/HostActivationTests/MultilevelSDKLookup.cs index bd166b0..fb0331a 100644 --- a/src/installer/test/HostActivationTests/MultilevelSDKLookup.cs +++ b/src/installer/test/HostActivationTests/MultilevelSDKLookup.cs @@ -566,30 +566,10 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation var dotnet = fixture.BuiltDotnet; - // To correctly test the product we need a registry key which is - // - writable without admin access (so that the tests don't require admin to run) - // - redirected in WOW64 - so that there are both 32bit and 64bit versions of the key - // this is because the product stores the info in the 32bit version only and even 64bit - // product must look into the 32bit version. - // Without the redirection we would not be able to test that the product always looks - // into 32bit only. - // Per this page https://docs.microsoft.com/en-us/windows/desktop/WinProg64/shared-registry-keys - // a user writable redirected key is for example HKCU\Software\Classes\Interface - // so we're going to use that one - it's not super clean as they key stored COM interfaces - // but we should not corrupt anything by adding a special subkey even if it's left behind. - // - // Note: If you want to inspect the values written by the test and/or modify them manually - // you have to navigate to HKCU\Software\Classes\Wow6432Node\Interface on a 64bit OS. - - RegistryKey hkcu = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32); - RegistryKey interfaceKey = hkcu.CreateSubKey(@"Software\Classes\Interface"); - string testKeyName = "_DOTNET_Test" + System.Diagnostics.Process.GetCurrentProcess().Id.ToString(); - RegistryKey testKey = interfaceKey.CreateSubKey(testKeyName); - try + using (var regKeyOverride = new RegisteredInstallKeyOverride()) { string architecture = fixture.CurrentRid.Split('-')[1]; - RegistryKey dotnetLocationKey = testKey.CreateSubKey($@"Setup\InstalledVersions\{architecture}"); - dotnetLocationKey.SetValue("InstallLocation", _regDir); + regKeyOverride.SetInstallLocation(_regDir, architecture); // Add SDK versions AddAvailableSdkVersions(_regSdkBaseDir, "9999.0.4"); @@ -605,17 +585,13 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation .WithUserProfile(_userDir) .Environment(s_DefaultEnvironment) .EnvironmentVariable("DOTNET_MULTILEVEL_LOOKUP", "1") - .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", testKey.Name) + .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", regKeyOverride.KeyPath) .CaptureStdOut() .CaptureStdErr() .Execute() .Should().Pass() .And.HaveStdErrContaining(Path.Combine(_regSelectedMessage, "9999.0.4", _dotnetSdkDllMessageTerminator)); } - finally - { - interfaceKey.DeleteSubKeyTree(testKeyName); - } } [Fact] diff --git a/src/installer/test/HostActivationTests/NativeHosting/Nethost.cs b/src/installer/test/HostActivationTests/NativeHosting/Nethost.cs new file mode 100644 index 0000000..c9b01c0 --- /dev/null +++ b/src/installer/test/HostActivationTests/NativeHosting/Nethost.cs @@ -0,0 +1,197 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.DotNet.Cli.Build.Framework; +using Microsoft.Win32; +using System; +using System.IO; +using System.Runtime.InteropServices; +using Xunit; + +namespace Microsoft.DotNet.CoreSetup.Test.HostActivation.NativeHosting +{ + public class Nethost : IClassFixture + { + private const string GetHostFxrPath = "nethost_get_hostfxr_path"; + private const int CoreHostLibMissingFailure = unchecked((int)0x80008083); + + private static readonly string HostFxrName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("hostfxr"); + private readonly SharedTestState sharedState; + + public Nethost(SharedTestState sharedTestState) + { + sharedState = sharedTestState; + } + + [Theory] + [InlineData(false, true)] + [InlineData(false, false)] + [InlineData(true, true)] + [InlineData(true, false)] + public void GetHostFxrPath_DotNetRootEnvironment(bool useAssemblyPath, bool isValid) + { + string dotNetRoot = isValid ? Path.Combine(sharedState.ValidInstallRoot, "dotnet") : sharedState.InvalidInstallRoot; + CommandResult result = Command.Create(sharedState.NativeHostPath, $"{GetHostFxrPath} {(useAssemblyPath ? sharedState.TestAssemblyPath : string.Empty)}") + .CaptureStdErr() + .CaptureStdOut() + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("DOTNET_ROOT", dotNetRoot) + .Execute(); + + result.Should().HaveStdErrContaining("Using environment variable"); + + if (isValid) + { + result.Should().Pass() + .And.HaveStdOutContaining($"hostfxr_path: {sharedState.HostFxrPath}".ToLower()); + } + else + { + result.Should().Fail() + .And.ExitWith(1) + .And.HaveStdOutContaining($"{GetHostFxrPath} failed: 0x{CoreHostLibMissingFailure.ToString("x")}") + .And.HaveStdErrContaining($"The required library {HostFxrName} could not be found"); + } + } + + [Theory] + [InlineData(false, true, false)] + [InlineData(false, true, true)] + [InlineData(false, false, false)] + [InlineData(false, false, true)] + [InlineData(true, true, false)] + [InlineData(true, true, true)] + [InlineData(true, false, false)] + [InlineData(true, false, true)] + public void GetHostFxrPath_GlobalInstallation(bool useAssemblyPath, bool useRegisteredLocation, bool isValid) + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // We don't have a good way of hooking into how the product looks for global installations yet. + return; + } + + // Overide the registry key for self-registered global installs. + // If using the registered location, set the install location value to the valid/invalid root. + // If not using the registered location, do not set the value. When the value does not exist, + // the product falls back to the default install location. + CommandResult result; + string installRoot = Path.Combine(isValid ? sharedState.ValidInstallRoot : sharedState.InvalidInstallRoot); + using (var regKeyOverride = new RegisteredInstallKeyOverride()) + { + if (useRegisteredLocation) + { + regKeyOverride.SetInstallLocation(Path.Combine(installRoot, "dotnet"), sharedState.RepoDirectories.BuildArchitecture); + } + + string programFilesOverride = useRegisteredLocation ? sharedState.InvalidInstallRoot : installRoot; + result = Command.Create(sharedState.NativeHostPath, $"{GetHostFxrPath} {(useAssemblyPath ? sharedState.TestAssemblyPath : string.Empty)}") + .CaptureStdErr() + .CaptureStdOut() + .EnvironmentVariable("COREHOST_TRACE", "1") + .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", regKeyOverride.KeyPath) + .EnvironmentVariable("TEST_OVERRIDE_PROGRAMFILES", programFilesOverride) + .Execute(); + } + + result.Should().HaveStdErrContaining("Using global installation location"); + + if (isValid) + { + result.Should().Pass() + .And.HaveStdOutContaining($"hostfxr_path: {sharedState.HostFxrPath}".ToLower()); + } + else + { + result.Should().Fail() + .And.ExitWith(1) + .And.HaveStdOutContaining($"{GetHostFxrPath} failed: 0x{CoreHostLibMissingFailure.ToString("x")}") + .And.HaveStdErrContaining($"The required library {HostFxrName} could not be found"); + } + } + + [Fact] + public void GetHostFxrPath_WithAssemblyPath_AppLocalFxr() + { + string appLocalFxrDir = Path.Combine(sharedState.BaseDirectory, "appLocalFxr"); + Directory.CreateDirectory(appLocalFxrDir); + string assemblyPath = Path.Combine(appLocalFxrDir, "AppLocalFxr.dll"); + string hostFxrPath = Path.Combine(appLocalFxrDir, HostFxrName); + File.WriteAllText(assemblyPath, string.Empty); + File.WriteAllText(hostFxrPath, string.Empty); + + Command.Create(sharedState.NativeHostPath, $"{GetHostFxrPath} {assemblyPath}") + .CaptureStdErr() + .CaptureStdOut() + .EnvironmentVariable("COREHOST_TRACE", "1") + .Execute() + .Should().Pass() + .And.HaveStdOutContaining($"hostfxr_path: {hostFxrPath}".ToLower()); + } + + public class SharedTestState : IDisposable + { + public string BaseDirectory { get; } + public string NativeHostPath { get; } + public RepoDirectoriesProvider RepoDirectories { get; } + + public string HostFxrPath { get; } + public string InvalidInstallRoot { get; } + public string ValidInstallRoot { get; } + + public string TestAssemblyPath { get; } + + public SharedTestState() + { + BaseDirectory = SharedFramework.CalculateUniqueTestDirectory(Path.Combine(TestArtifact.TestArtifactsPath, "nativeHosting")); + Directory.CreateDirectory(BaseDirectory); + + string nativeHostName = RuntimeInformationExtensions.GetExeFileNameForCurrentPlatform("nativehost"); + NativeHostPath = Path.Combine(BaseDirectory, nativeHostName); + + // Copy over native host and nethost + RepoDirectories = new RepoDirectoriesProvider(); + string nethostName = RuntimeInformationExtensions.GetSharedLibraryFileNameForCurrentPlatform("nethost"); + File.Copy(Path.Combine(RepoDirectories.CorehostPackages, nethostName), Path.Combine(BaseDirectory, nethostName)); + File.Copy(Path.Combine(RepoDirectories.Artifacts, "corehost_test", nativeHostName), NativeHostPath); + + InvalidInstallRoot = Path.Combine(BaseDirectory, "invalid"); + Directory.CreateDirectory(InvalidInstallRoot); + + ValidInstallRoot = Path.Combine(BaseDirectory, "valid"); + HostFxrPath = CreateHostFxr(Path.Combine(ValidInstallRoot, "dotnet")); + + string appDir = Path.Combine(BaseDirectory, "app"); + Directory.CreateDirectory(appDir); + string assemblyPath = Path.Combine(appDir, "App.dll"); + File.WriteAllText(assemblyPath, string.Empty); + TestAssemblyPath = assemblyPath; + } + + private string CreateHostFxr(string destinationDirectory) + { + string fxrRoot = Path.Combine(destinationDirectory, "host", "fxr"); + Directory.CreateDirectory(fxrRoot); + + string[] versions = new string[] { "1.1.0", "2.2.1", "2.3.0" }; + foreach (string version in versions) + { + string versionDirectory = Path.Combine(fxrRoot, version); + Directory.CreateDirectory(versionDirectory); + File.WriteAllText(Path.Combine(versionDirectory, HostFxrName), string.Empty); + } + + return Path.Combine(fxrRoot, "2.3.0", HostFxrName); + } + + public void Dispose() + { + if (!TestArtifact.PreserveTestRuns() && Directory.Exists(BaseDirectory)) + { + Directory.Delete(BaseDirectory, true); + } + } + } + } +} diff --git a/src/installer/test/HostActivationTests/PortableAppActivation.cs b/src/installer/test/HostActivationTests/PortableAppActivation.cs index e5bb979..be8ffca 100644 --- a/src/installer/test/HostActivationTests/PortableAppActivation.cs +++ b/src/installer/test/HostActivationTests/PortableAppActivation.cs @@ -335,21 +335,16 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation // Get the framework location that was built string builtDotnet = fixture.BuiltDotnet.BinPath; - RegistryKey hkcu = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32); - RegistryKey interfaceKey = hkcu.CreateSubKey(@"Software\Classes\Interface"); - string testKeyName = "_DOTNET_Test" + System.Diagnostics.Process.GetCurrentProcess().Id.ToString(); - RegistryKey testKey = interfaceKey.CreateSubKey(testKeyName); - try + using (var regKeyOverride = new RegisteredInstallKeyOverride()) { string architecture = fixture.CurrentRid.Split('-')[1]; - RegistryKey dotnetLocationKey = testKey.CreateSubKey($@"Setup\InstalledVersions\{architecture}"); - dotnetLocationKey.SetValue("InstallLocation", builtDotnet); + regKeyOverride.SetInstallLocation(builtDotnet, architecture); // Verify running with the default working directory Command.Create(appExe) .CaptureStdErr() .CaptureStdOut() - .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", testKey.Name) + .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", regKeyOverride.KeyPath) .Execute() .Should().Pass() .And.HaveStdOutContaining("Hello World") @@ -358,7 +353,7 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation // Verify running from within the working directory Command.Create(appExe) .WorkingDirectory(fixture.TestProject.OutputDirectory) - .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", testKey.Name) + .EnvironmentVariable("_DOTNET_TEST_SDK_REGISTRY_PATH", regKeyOverride.KeyPath) .CaptureStdErr() .CaptureStdOut() .Execute() @@ -366,10 +361,6 @@ namespace Microsoft.DotNet.CoreSetup.Test.HostActivation .And.HaveStdOutContaining("Hello World") .And.HaveStdOutContaining($"Framework Version:{sharedTestState.RepoDirectories.MicrosoftNETCoreAppVersion}"); } - finally - { - interfaceKey.DeleteSubKeyTree(testKeyName); - } } private string MoveDepsJsonToSubdirectory(TestProjectFixture testProjectFixture) diff --git a/src/installer/test/HostActivationTests/RegisteredInstallKeyOverride.cs b/src/installer/test/HostActivationTests/RegisteredInstallKeyOverride.cs new file mode 100644 index 0000000..6226ac4 --- /dev/null +++ b/src/installer/test/HostActivationTests/RegisteredInstallKeyOverride.cs @@ -0,0 +1,58 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.Win32; +using System; + +namespace Microsoft.DotNet.CoreSetup.Test.HostActivation +{ + public class RegisteredInstallKeyOverride : IDisposable + { + public string KeyPath { get; } + + private readonly RegistryKey parentKey; + private readonly RegistryKey key; + private readonly string keyName; + + public RegisteredInstallKeyOverride() + { + // To test registered installs, we need a registry key which is: + // - writable without admin access - so that the tests don't require admin to run + // - redirected in WOW64 - so that there are both 32-bit and 64-bit versions of the key + // This is because the product stores the info in the 32-bit hive only and even 64-bit + // product must look into the 32-bit hive. + // Without the redirection we would not be able to test that the product always looks + // into 32-bit only. + // Per this page https://docs.microsoft.com/en-us/windows/desktop/WinProg64/shared-registry-keys + // a user writable redirected key is for example HKCU\Software\Classes\Interface + // so we're going to use that one - it's not super clean as the key stores COM interfaces, + // but we should not corrupt anything by adding a special subkey even if it's left behind. + // + // Note: If you want to inspect the values written by the test and/or modify them manually + // you have to navigate to HKCU\Software\Classes\Wow6432Node\Interface on a 64-bit OS. + using (RegistryKey hkcu = RegistryKey.OpenBaseKey(RegistryHive.CurrentUser, RegistryView.Registry32)) + { + parentKey = hkcu.CreateSubKey(@"Software\Classes\Interface"); + keyName = "_DOTNET_Test" + System.Diagnostics.Process.GetCurrentProcess().Id.ToString(); + key = parentKey.CreateSubKey(keyName); + KeyPath = key.Name; + } + } + + public void SetInstallLocation(string installLocation, string architecture) + { + using (RegistryKey dotnetLocationKey = key.CreateSubKey($@"Setup\InstalledVersions\{architecture}")) + { + dotnetLocationKey.SetValue("InstallLocation", installLocation); + } + } + + public void Dispose() + { + parentKey.DeleteSubKeyTree(keyName, throwOnMissingSubKey: false); + key.Dispose(); + parentKey.Dispose(); + } + } +} -- 2.7.4