winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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();