ConfigurationSetProcessorFactoryRemoting.cpp (19775B)
1 // Copyright (c) Microsoft Corporation. 2 // Licensed under the MIT License. 3 #include "pch.h" 4 #include "Public/ConfigurationSetProcessorFactoryRemoting.h" 5 #include <AppInstallerErrors.h> 6 #include <AppInstallerLanguageUtilities.h> 7 #include <AppInstallerLogging.h> 8 #include <AppInstallerRuntime.h> 9 #include <AppInstallerStrings.h> 10 #include <winget/ExperimentalFeature.h> 11 #include <winget/ILifetimeWatcher.h> 12 #include <winrt/Microsoft.Management.Configuration.SetProcessorFactory.h> 13 14 using namespace winrt::Windows::Foundation; 15 using namespace winrt::Microsoft::Management::Configuration; 16 using namespace std::string_view_literals; 17 18 namespace AppInstaller::CLI::ConfigurationRemoting 19 { 20 namespace 21 { 22 // The executable file name for the remote server process. 23 constexpr std::wstring_view s_RemoteServerFileName = L"DotNet\\ConfigurationRemotingServer.exe"sv; 24 25 constexpr std::wstring_view s_ProcessorEngine_PowerShell = L"pwsh"sv; 26 constexpr std::wstring_view s_ProcessorEngine_DSCv3 = L"dscv3"sv; 27 28 // The string used to divide the arguments sent to the remote server 29 constexpr std::wstring_view s_ArgumentsDivider = L"\n~~~~~~\n"sv; 30 31 // A helper with a convenient function that we use to receive the remote factory object. 32 struct RemoteFactoryCallback : winrt::implements<RemoteFactoryCallback, IConfigurationStatics> 33 { 34 RemoteFactoryCallback() 35 { 36 m_initEvent.create(); 37 } 38 39 ConfigurationUnit CreateConfigurationUnit() 40 { 41 THROW_HR(E_NOTIMPL); 42 } 43 44 ConfigurationSet CreateConfigurationSet() 45 { 46 THROW_HR(E_NOTIMPL); 47 } 48 49 IAsyncOperation<IConfigurationSetProcessorFactory> CreateConfigurationSetProcessorFactoryAsync(winrt::hstring handler) 50 { 51 // TODO: Ensure calling process has same package identity 52 std::wstringstream stringStream{ std::wstring{ static_cast<std::wstring_view>(handler) } }; 53 stringStream >> m_result; 54 m_initEvent.SetEvent(); 55 return nullptr; 56 } 57 58 ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory factory) 59 { 60 // TODO: Ensure calling process has same package identity 61 m_factory = factory; 62 m_initEvent.SetEvent(); 63 return nullptr; 64 } 65 66 bool IsConfigurationAvailable() 67 { 68 THROW_HR(E_NOTIMPL); 69 } 70 71 IAsyncActionWithProgress<uint32_t> EnsureConfigurationAvailableAsync() 72 { 73 THROW_HR(E_NOTIMPL); 74 } 75 76 IConfigurationSetProcessorFactory Wait(HANDLE process) 77 { 78 HANDLE waitHandles[2]; 79 waitHandles[0] = m_initEvent.get(); 80 waitHandles[1] = process; 81 82 for (;;) 83 { 84 // Wait up to 10 seconds for the server to complete initialization. 85 // This time is fairly arbitrary, although it does correspond with the maximum time for a COM fast rundown. 86 DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, 10000); 87 THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); 88 89 // The init event was signaled. 90 if (waitResult == WAIT_OBJECT_0) 91 { 92 break; 93 } 94 95 // Don't break things if the process is being debugged 96 if (waitResult == WAIT_TIMEOUT && IsDebuggerPresent()) 97 { 98 continue; 99 } 100 101 // If the process exited, then try to use the exit code. 102 DWORD processExitCode = 0; 103 if (waitResult == (WAIT_OBJECT_0 + 1) && GetExitCodeProcess(process, &processExitCode) && FAILED(processExitCode)) 104 { 105 THROW_HR(static_cast<HRESULT>(processExitCode)); 106 } 107 else 108 { 109 // The server timed out or didn't have a failed exit code. 110 THROW_HR(E_FAIL); 111 } 112 } 113 114 THROW_IF_FAILED(m_result); 115 116 // Double-check the result 117 THROW_HR_IF(E_POINTER, !m_factory); 118 return m_factory; 119 } 120 121 private: 122 IConfigurationSetProcessorFactory m_factory; 123 HRESULT m_result = S_OK; 124 wil::unique_event m_initEvent; 125 }; 126 127 // Represents a remote factory object that was created from a specific process. 128 struct RemoteFactory : winrt::implements<RemoteFactory, IConfigurationSetProcessorFactory, SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties, Collections::IMap<winrt::hstring, winrt::hstring>, winrt::cloaked<WinRT::ILifetimeWatcher>>, WinRT::LifetimeWatcherBase 129 { 130 RemoteFactory(ProcessorEngine processorEngine, bool useRunAs, const std::string& properties, const std::string& restrictions) 131 { 132 AICLI_LOG(Config, Verbose, << "Launching process for configuration processing..."); 133 134 // Create our callback and marshal it 135 auto callback = winrt::make_self<RemoteFactoryCallback>(); 136 137 wil::com_ptr<IStream> stream; 138 THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); 139 140 THROW_IF_FAILED(CoMarshalInterface(stream.get(), winrt::guid_of<IConfigurationStatics>(), reinterpret_cast<::IUnknown*>(winrt::get_abi(callback.as<IConfigurationStatics>())), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); 141 142 ULARGE_INTEGER streamSize{}; 143 THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); 144 145 ULONG bufferSize = static_cast<ULONG>(streamSize.QuadPart); 146 std::vector<uint8_t> buffer; 147 buffer.resize(bufferSize); 148 149 THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); 150 ULONG bytesRead = 0; 151 THROW_IF_FAILED(stream->Read(&buffer[0], bufferSize, &bytesRead)); 152 THROW_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); 153 154 std::wstring marshalledCallback = Utility::ConvertToUTF16(Utility::ConvertToHexString(buffer)); 155 156 // Create the event that the remote process will wait on to keep the object alive. 157 std::wstring completionEventName = Utility::CreateNewGuidNameWString(); 158 m_completionEvent.create(wil::EventOptions::None, completionEventName.c_str()); 159 auto completeEventIfFailureDuringConstruction = wil::scope_exit([&]() { m_completionEvent.SetEvent(); }); 160 161 // This will be presented to the user so it must be formatted nicely. 162 // Arguments are: 163 // server.exe <marshalled callback object> <completion event name> <this process id> 164 // 165 // Optionally, we may also place additional data that limits what the server may do as: 166 // ~~~~~~ 167 // { "JSON properties" } 168 // ~~~~~~ 169 // YAML configuration set definition 170 std::wostringstream argumentsStream; 171 argumentsStream << s_RemoteServerFileName << L' ' << marshalledCallback << L' ' << completionEventName << L' ' << GetCurrentProcessId() << L' ' << ToString(processorEngine); 172 173 if (!properties.empty() && !restrictions.empty()) 174 { 175 argumentsStream << L' ' << s_ArgumentsDivider << Utility::ConvertToUTF16(properties) << s_ArgumentsDivider << Utility::ConvertToUTF16(restrictions); 176 } 177 178 std::wstring arguments = argumentsStream.str(); 179 180 std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot); 181 serverPath /= s_RemoteServerFileName; 182 std::wstring serverPathString = serverPath.wstring(); 183 184 // Per documentation, the maximum length is 32767 *counting* the null. 185 THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, serverPathString.length() > 32766); 186 THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, arguments.length() > 32766); 187 // Overflow safe since we verify that each of the individual strings is also small. 188 // +1 for the space between the path and args. 189 THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, serverPathString.length() + 1 + arguments.length() > 32766); 190 191 SHELLEXECUTEINFOW execInfo = { 0 }; 192 execInfo.cbSize = sizeof(execInfo); 193 execInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI | SEE_MASK_NO_CONSOLE; 194 execInfo.lpFile = serverPath.c_str(); 195 execInfo.lpParameters = arguments.c_str(); 196 execInfo.nShow = SW_HIDE; 197 198 if (useRunAs) 199 { 200 execInfo.lpVerb = L"runas"; 201 } 202 203 THROW_LAST_ERROR_IF(!ShellExecuteExW(&execInfo) || !execInfo.hProcess); 204 205 wil::unique_process_handle process{ execInfo.hProcess }; 206 AICLI_LOG(Config, Verbose, << " Configuration remote PID is " << GetProcessId(process.get())); 207 208 m_remoteFactory = callback->Wait(process.get()); 209 AICLI_LOG(Config, Verbose, << "... configuration processing connection established."); 210 211 completeEventIfFailureDuringConstruction.release(); 212 } 213 214 ~RemoteFactory() 215 { 216 m_completionEvent.SetEvent(); 217 } 218 219 IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet) 220 { 221 return m_remoteFactory.CreateSetProcessor(configurationSet); 222 } 223 224 winrt::event_token Diagnostics(const EventHandler<IDiagnosticInformation>& handler) 225 { 226 return m_remoteFactory.Diagnostics(handler); 227 } 228 229 void Diagnostics(const winrt::event_token& token) noexcept 230 { 231 m_remoteFactory.Diagnostics(token); 232 } 233 234 DiagnosticLevel MinimumLevel() 235 { 236 return m_remoteFactory.MinimumLevel(); 237 } 238 239 void MinimumLevel(DiagnosticLevel value) 240 { 241 m_remoteFactory.MinimumLevel(value); 242 } 243 244 Collections::IVectorView<winrt::hstring> AdditionalModulePaths() const 245 { 246 return m_additionalModulePaths.GetView(); 247 } 248 249 void AdditionalModulePaths(const Collections::IVectorView<winrt::hstring>& value) 250 { 251 // Extract all values from incoming view 252 std::vector<winrt::hstring> newModulePaths{ value.Size() }; 253 value.GetMany(0, newModulePaths); 254 255 // Create a copy for remote and set remote module paths 256 std::vector<winrt::hstring> newRemotePaths{ newModulePaths }; 257 m_remoteAdditionalModulePaths = winrt::single_threaded_vector<winrt::hstring>(std::move(newRemotePaths)); 258 m_remoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>().AdditionalModulePaths(m_remoteAdditionalModulePaths.GetView()); 259 260 // Store the updated module paths that we were given 261 m_additionalModulePaths = winrt::single_threaded_vector<winrt::hstring>(std::move(newModulePaths)); 262 } 263 264 SetProcessorFactory::PwshConfigurationProcessorPolicy Policy() const 265 { 266 return m_remoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>().Policy(); 267 } 268 269 void Policy(SetProcessorFactory::PwshConfigurationProcessorPolicy value) 270 { 271 m_remoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>().Policy(value); 272 } 273 274 SetProcessorFactory::PwshConfigurationProcessorLocation Location() const 275 { 276 return m_remoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>().Location(); 277 } 278 279 void Location(SetProcessorFactory::PwshConfigurationProcessorLocation value) 280 { 281 m_remoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>().Location(value); 282 } 283 284 winrt::hstring CustomLocation() const 285 { 286 return m_remoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>().CustomLocation(); 287 } 288 289 void CustomLocation(winrt::hstring value) 290 { 291 m_remoteFactory.as<SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties>().CustomLocation(value); 292 } 293 294 // Implement a subset of IMap to enable property bag semantics 295 uint32_t Size() { THROW_HR(E_NOTIMPL); } 296 void Clear() { THROW_HR(E_NOTIMPL); } 297 Collections::IMapView<winrt::hstring, winrt::hstring> GetView() { THROW_HR(E_NOTIMPL); } 298 bool HasKey(winrt::hstring) { THROW_HR(E_NOTIMPL); } 299 void Remove(winrt::hstring) { THROW_HR(E_NOTIMPL); } 300 301 bool Insert(winrt::hstring key, winrt::hstring value) 302 { 303 auto map = m_remoteFactory.try_as<Collections::IMap<winrt::hstring, winrt::hstring>>(); 304 return map ? map.Insert(key, value) : false; 305 } 306 307 winrt::hstring Lookup(winrt::hstring key) 308 { 309 auto map = m_remoteFactory.try_as<Collections::IMap<winrt::hstring, winrt::hstring>>(); 310 return map ? map.Lookup(key) : winrt::hstring{}; 311 } 312 313 HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher) 314 { 315 return WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); 316 } 317 318 private: 319 IConfigurationSetProcessorFactory m_remoteFactory; 320 wil::unique_event m_completionEvent; 321 Collections::IVector<winrt::hstring> m_additionalModulePaths{ winrt::single_threaded_vector<winrt::hstring>() }; 322 Collections::IVector<winrt::hstring> m_remoteAdditionalModulePaths{ winrt::single_threaded_vector<winrt::hstring>() }; 323 }; 324 } 325 326 IConfigurationSetProcessorFactory CreateOutOfProcessFactory(ProcessorEngine processorEngine, bool useRunAs, const std::string& properties, const std::string& restrictions) 327 { 328 return winrt::make<RemoteFactory>(processorEngine, useRunAs, properties, restrictions); 329 } 330 331 ProcessorEngine DetermineProcessorEngine(ConfigurationSet set) 332 { 333 Utility::Version schemaVersion{ Utility::ConvertToUTF8(set.SchemaVersion()) }; 334 335 if (schemaVersion <= Utility::Version{ "0.3" }) 336 { 337 ProcessorEngine result = ProcessorEngine::Unknown; 338 339 std::wstring processorIdentifier = Utility::ToLower(set.Environment().ProcessorIdentifier()); 340 if (processorIdentifier.empty() || processorIdentifier == s_ProcessorEngine_PowerShell) 341 { 342 // Default to PowerShell 343 result = ProcessorEngine::PowerShell; 344 } 345 else if (processorIdentifier == s_ProcessorEngine_DSCv3) 346 { 347 result = ProcessorEngine::DSCv3; 348 } 349 else 350 { 351 AICLI_LOG(Config, Warning, << "Unknown processor: " << Utility::ConvertToUTF8(processorIdentifier)); 352 } 353 354 return result; 355 } 356 else 357 { 358 // Intentionally fail out here until a decision is made. 359 THROW_HR(E_NOTIMPL); 360 } 361 } 362 363 std::wstring_view ToString(ProcessorEngine value) 364 { 365 switch (value) 366 { 367 case ProcessorEngine::PowerShell: 368 return s_ProcessorEngine_PowerShell; 369 case ProcessorEngine::DSCv3: 370 return s_ProcessorEngine_DSCv3; 371 default: 372 THROW_HR(E_UNEXPECTED); 373 } 374 } 375 376 winrt::hstring ToHString(PropertyName name) 377 { 378 switch (name) 379 { 380 case PropertyName::DscExecutablePath: return L"DscExecutablePath"; 381 case PropertyName::FoundDscExecutablePath: return L"FoundDscExecutablePath"; 382 case PropertyName::DiagnosticTraceEnabled: return L"DiagnosticTraceEnabled"; 383 case PropertyName::FindDscStateMachine: return L"FindDscStateMachine"; 384 } 385 386 THROW_HR(E_UNEXPECTED); 387 } 388 } 389 390 HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, LPWSTR staticsCallback, LPWSTR completionEventName, DWORD parentProcessId) try 391 { 392 { 393 wil::com_ptr<IGlobalOptions> globalOptions; 394 RETURN_IF_FAILED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); 395 RETURN_IF_FAILED(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); 396 RETURN_IF_FAILED(globalOptions->Set(COMGLB_UNMARSHALING_POLICY, COMGLB_UNMARSHALING_POLICY_STRONG)); 397 RETURN_IF_FAILED(globalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY)); 398 } 399 400 using namespace AppInstaller; 401 using namespace AppInstaller::CLI::ConfigurationRemoting; 402 403 RETURN_HR_IF(E_POINTER, !staticsCallback); 404 405 auto callbackBytes = Utility::ParseFromHexString(Utility::ConvertToUTF8(staticsCallback)); 406 RETURN_HR_IF(E_INVALIDARG, callbackBytes.size() > (1 << 15)); 407 408 wil::com_ptr<IStream> stream; 409 RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); 410 RETURN_IF_FAILED(stream->Write(&callbackBytes[0], static_cast<ULONG>(callbackBytes.size()), nullptr)); 411 RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); 412 413 wil::com_ptr<::IUnknown> output; 414 RETURN_IF_FAILED(CoUnmarshalInterface(stream.get(), winrt::guid_of<IConfigurationStatics>(), reinterpret_cast<void**>(&output))); 415 416 IConfigurationStatics callback{ output.detach(), winrt::take_ownership_from_abi }; 417 418 if (FAILED(result)) 419 { 420 std::ignore = callback.CreateConfigurationSetProcessorFactoryAsync(std::to_wstring(result)); 421 } 422 else 423 { 424 IConfigurationSetProcessorFactory factoryObject; 425 winrt::copy_from_abi(factoryObject, factory); 426 std::ignore = callback.CreateConfigurationProcessor(factoryObject); 427 } 428 429 // Wait until the caller releases the object (signalling the event) or the parent process exits 430 wil::unique_event completionEvent; 431 completionEvent.open(completionEventName); 432 wil::unique_process_handle parentProcess{ OpenProcess(SYNCHRONIZE, FALSE, parentProcessId) }; 433 434 HANDLE waitHandles[2]; 435 waitHandles[0] = completionEvent.get(); 436 waitHandles[1] = parentProcess.get(); 437 438 std::ignore = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); 439 440 return S_OK; 441 } 442 CATCH_RETURN();