commit 596b1b0497c74fac3d743b7600113e3f73170b6a parent 950548928e01c9e4df1e740064c720a0b0f246cd Author: Ryan <69221034+ryfu-msft@users.noreply.github.com> Date: Mon, 8 Apr 2024 11:49:17 -0700 Dynamic runtime configuration support (#4355) Credit to @JohnMcPMS for initial prototyping. For reference: `DynamicFactory` holds the same integrity level remote factory `DynamicFactory` creates `DynamicSetProcessor`, giving it a copy of the same integrity level remote factory `DynamicSetProcessor` creates more remote factories and remote set processors at other integrity levels as needed `DynamicSetProcessor` uses the appropriate remote set processor to create the unit processor Changes: Added logic to separate out units with high integrity levels and serializing those units into a yaml string. This yaml string will be passed to `CreateOutOfProcessFactory()` along with the json string containing the "path" as the limitation set in the configuration remote server and processors. Tests: I only added one test for verifying serialization and manually verified the json output for `path`. In the interest of time, I created this PR to get out the feature and will continue to work on adding more thorough E2E testing after everything is fully integrated. ###### Microsoft Reviewers: [Open in CodeFlow](https://microsoft.github.io/open-pr/?codeflow=https://github.com/microsoft/winget-cli/pull/4355) --------- Co-authored-by: John McPherson <johnmcp@microsoft.com> Diffstat:
39 files changed, 1156 insertions(+), 305 deletions(-)
diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt @@ -101,6 +101,7 @@ diskfull DISMAPI dnld Dobbeleer +DONOT dsc DUPLICATEALIAS dustojnikhummer @@ -470,6 +471,7 @@ uninstalls Unk unknwn Unknwnbase +UNMARSHALING unparsable unvirtualized UParse diff --git a/doc/Settings.md b/doc/Settings.md @@ -312,7 +312,6 @@ You can enable the feature as shown below. "proxy": true }, ``` - ### sideBySide This feature enables experimental improvements for supporting multiple instances of a package being installed on a system. @@ -323,3 +322,14 @@ You can enable the feature as shown below. "sideBySide": true }, ``` + +### configureSelfElevate + +This feature enables configure commands to request elevation as needed. +Currently, this means that properly attributed configuration units (and only those) will be run through an elevated process while the rest are run from the current context. + +```json + "experimentalFeatures": { + "configureSelfElevate": true + }, +``` diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json @@ -280,6 +280,11 @@ "description": "Enable support for improved side-by-side handling", "type": "boolean", "default": false + }, + "configureSelfElevate": { + "description": "Enable configure commands request elevation as needed", + "type": "boolean", + "default": false } } } diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -443,6 +443,7 @@ <ClCompile Include="Commands\TestCommand.cpp" /> <ClCompile Include="ConfigurationCommon.cpp" /> <ClCompile Include="ConfigurationContext.cpp" /> + <ClCompile Include="ConfigurationDynamicRuntimeFactory.cpp" /> <ClCompile Include="ConfigurationSetProcessorFactoryRemoting.cpp" /> <ClCompile Include="ConfigurationWingetDscModuleUnitValidation.cpp" /> <ClCompile Include="ContextOrchestrator.cpp" /> diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -248,6 +248,9 @@ <ClInclude Include="Workflows\RepairFlow.h"> <Filter>Workflows</Filter> </ClInclude> + <ClInclude Include="ConfigurationWingetDscModuleUnitValidation.h"> + <Filter>Header Files</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <ClCompile Include="pch.cpp"> @@ -456,13 +459,19 @@ </ClCompile> <ClCompile Include="Commands\ErrorCommand.cpp"> <Filter>Commands</Filter> - </ClCompile> + </ClCompile> <ClCompile Include="Commands\RepairCommand.cpp"> <Filter>Commands</Filter> - </ClCompile> + </ClCompile> <ClCompile Include="Workflows\RepairFlow.cpp"> <Filter>Workflows</Filter> </ClCompile> + <ClCompile Include="ConfigurationWingetDscModuleUnitValidation.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="ConfigurationDynamicRuntimeFactory.cpp"> + <Filter>Source Files</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <None Include="PropertySheet.props" /> diff --git a/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp b/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp @@ -0,0 +1,245 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/ConfigurationSetProcessorFactoryRemoting.h" +#include <AppInstallerStrings.h> +#include <winget/ILifetimeWatcher.h> +#include <winget/Security.h> + +using namespace winrt::Windows::Foundation; +using namespace winrt::Microsoft::Management::Configuration; +using namespace winrt::Windows::Storage; + +namespace AppInstaller::CLI::ConfigurationRemoting +{ + namespace anonymous + { + struct DynamicProcessorInfo + { + IConfigurationSetProcessorFactory Factory; + IConfigurationSetProcessor Processor; + }; + + struct DynamicSetProcessor : winrt::implements<DynamicSetProcessor, IConfigurationSetProcessor> + { + using ProcessorMap = std::map<Security::IntegrityLevel, DynamicProcessorInfo>; + + DynamicSetProcessor(IConfigurationSetProcessorFactory defaultRemoteFactory, IConfigurationSetProcessor defaultRemoteSetProcessor, const ConfigurationSet& configurationSet) : m_configurationSet(configurationSet) + { + m_currentIntegrityLevel = Security::GetEffectiveIntegrityLevel(); + m_setProcessors.emplace(m_currentIntegrityLevel, DynamicProcessorInfo{ defaultRemoteFactory, defaultRemoteSetProcessor }); + } + + IConfigurationUnitProcessorDetails GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) + { + // Always get processor details from the current integrity level + return m_setProcessors[m_currentIntegrityLevel].Processor.GetUnitProcessorDetails(unit, detailFlags); + } + + // Creates a configuration unit processor for the given unit. + IConfigurationUnitProcessor CreateUnitProcessor(const ConfigurationUnit& unit) + { + // Determine and create set processors for all required integrity levels. + // Doing this here avoids creating them if the only call is going to be for details (ex. `configure show`) + std::call_once(m_createUnitSetProcessorsOnce, + [&]() + { + for (const auto& existingUnit : m_configurationSet.Units()) + { + Security::IntegrityLevel requiredIntegrityLevel = GetIntegrityLevelForUnit(existingUnit); + + if (m_setProcessors.find(requiredIntegrityLevel) == m_setProcessors.end()) + { + CreateSetProcessorForIntegrityLevel(requiredIntegrityLevel); + } + } + }); + + // Create set and unit processor for current unit. + Security::IntegrityLevel requiredIntegrityLevel = GetIntegrityLevelForUnit(unit); + + auto itr = m_setProcessors.find(requiredIntegrityLevel); + if (itr == m_setProcessors.end()) + { + itr = CreateSetProcessorForIntegrityLevel(requiredIntegrityLevel); + } + + return itr->second.Processor.CreateUnitProcessor(unit); + } + + private: + // Converts the string representation of SecurityContext to the integrity level + Security::IntegrityLevel SecurityContextToIntegrityLevel(winrt::hstring securityContext) + { + std::wstring securityContextLower = Utility::ToLower(securityContext); + + if (securityContextLower == L"elevated") + { + return Security::IntegrityLevel::High; + } + else if (securityContextLower == L"restricted") + { + // Not supporting elevated callers downgrading at the moment. + THROW_WIN32(ERROR_NOT_SUPPORTED); + + // Technically this means the default level of the user token, so if UAC is disabled it would be the only integrity level (aka current). + //return Security::IntegrityLevel::Medium; + } + else if (securityContextLower == L"current") + { + return m_currentIntegrityLevel; + } + + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + + // Gets the integrity level that the given unit should be run at + Security::IntegrityLevel GetIntegrityLevelForUnit(const ConfigurationUnit& unit) + { + // Support for 0.2 schema via metadata value + // TODO: Support case insensitive lookup by iteration + auto unitMetadata = unit.Metadata(); + auto securityContext = unitMetadata.TryLookup(L"SecurityContext"); + if (securityContext) + { + auto securityContextProperty = securityContext.try_as<IPropertyValue>(); + if (securityContextProperty && securityContextProperty.Type() == PropertyType::String) + { + return SecurityContextToIntegrityLevel(securityContextProperty.GetString()); + } + } + + // TODO: Support for 0.3 schema will require a group processor wrapper + + return m_currentIntegrityLevel; + } + + // Serializes the set properties to be sent to the remote server + std::string SerializeSetProperties() + { + Json::Value json{ Json::ValueType::objectValue }; + json["path"] = winrt::to_string(m_configurationSet.Path()); + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = "\t"; + return Json::writeString(writerBuilder, json); + } + + /// <summary> + /// Creates a separate configuration set containing high integrity units and returns the serialized string value. + /// </summary> + /// <returns>Serialized string value.</returns> + std::string SerializeHighIntegrityLevelSet() + { + ConfigurationSet highIntegritySet; + + // TODO: Currently we only support schema version 0.2 for handling elevated integrity levels. + highIntegritySet.SchemaVersion(L"0.2"); + + std::vector<ConfigurationUnit> highIntegrityUnits; + auto units = m_configurationSet.Units(); + + for (auto unit : units) + { + if (unit.IsActive() && GetIntegrityLevelForUnit(unit) == Security::IntegrityLevel::High) + { + highIntegrityUnits.emplace_back(unit); + } + } + + highIntegritySet.Units(std::move(highIntegrityUnits)); + + // Serialize high integrity set and return output string. + Streams::InMemoryRandomAccessStream memoryStream; + highIntegritySet.Serialize(memoryStream); + + Streams::DataReader reader(memoryStream.GetInputStreamAt(0)); + reader.UnicodeEncoding(Streams::UnicodeEncoding::Utf8); + reader.LoadAsync((uint32_t)memoryStream.Size()); + + winrt::hstring result; + uint32_t bytesToRead = reader.UnconsumedBufferLength(); + + if (bytesToRead > 0) + { + result = reader.ReadString(bytesToRead); + } + + return winrt::to_string(result); + } + + ProcessorMap::iterator CreateSetProcessorForIntegrityLevel(Security::IntegrityLevel integrityLevel) + { + IConfigurationSetProcessorFactory factory; + + // If we got here, the only option is that the current integrity level is not High. + if (integrityLevel == Security::IntegrityLevel::High) + { + factory = CreateOutOfProcessFactory(true, SerializeSetProperties(), SerializeHighIntegrityLevelSet()); + } + else + { + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + + return m_setProcessors.emplace(integrityLevel, DynamicProcessorInfo{ factory, factory.CreateSetProcessor(m_configurationSet) }).first; + } + + Security::IntegrityLevel m_currentIntegrityLevel; + ProcessorMap m_setProcessors; + ConfigurationSet m_configurationSet; + std::once_flag m_createUnitSetProcessorsOnce; + }; + + // This is implemented completely in the packaged context for now, if we want to make it more configurable, we will probably want to move it to configuration and + // have this implementation leverage that one with an event handler for the packaged specifics. + // TODO: Add SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties and pass values along to sets on creation + // In turn, any properties must only be set via the command line (or eventual UI requests to the user). + struct DynamicFactory : winrt::implements<DynamicFactory, IConfigurationSetProcessorFactory, winrt::cloaked<WinRT::ILifetimeWatcher>>, WinRT::LifetimeWatcherBase + { + DynamicFactory() + { + m_defaultRemoteFactory = CreateOutOfProcessFactory(); + } + + IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet) + { + return winrt::make<DynamicSetProcessor>(m_defaultRemoteFactory, m_defaultRemoteFactory.CreateSetProcessor(configurationSet), configurationSet); + } + + winrt::event_token Diagnostics(const EventHandler<IDiagnosticInformation>&) + { + // TODO: If we want diagnostics here, see ConfigurationProcessor for how to integrate nicely with the infrastructure. + // Best solution is probably to create a base class that both can leverage to handle it cleanly. + return {}; + } + + void Diagnostics(const winrt::event_token&) noexcept + { + } + + DiagnosticLevel MinimumLevel() + { + return m_minimumLevel; + } + + void MinimumLevel(DiagnosticLevel value) + { + m_minimumLevel = value; + } + + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher) + { + return WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); + } + + private: + IConfigurationSetProcessorFactory m_defaultRemoteFactory; + DiagnosticLevel m_minimumLevel = DiagnosticLevel::Informational; + }; + } + + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateDynamicRuntimeFactory() + { + return winrt::make<anonymous::DynamicFactory>(); + } +} diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp @@ -5,6 +5,7 @@ #include <AppInstallerLanguageUtilities.h> #include <AppInstallerLogging.h> #include <AppInstallerRuntime.h> +#include <AppInstallerStrings.h> #include <winget/ILifetimeWatcher.h> #include <winrt/Microsoft.Management.Configuration.Processor.h> #include <winrt/Microsoft.Management.Configuration.SetProcessorFactory.h> @@ -14,86 +15,64 @@ using namespace winrt::Microsoft::Management::Configuration; namespace AppInstaller::CLI::ConfigurationRemoting { - namespace details - { - // The layout of the memory being mapped. - struct MappedMemoryValue - { - // The size of the memory block itself. - static constexpr ULONG s_MemorySize = 4 << 10; - - HRESULT Result; - ULONG FactorySize; - uint8_t FactoryObject[1]; - - // The maximum size of the marshalled object. - static constexpr ULONG MaxFactorySize() - { - static_assert(s_MemorySize > offsetof(MappedMemoryValue, FactoryObject)); - return s_MemorySize - offsetof(MappedMemoryValue, FactoryObject); - } - }; - } - namespace { // The executable file name for the remote server process. constexpr std::wstring_view s_RemoteServerFileName = L"ConfigurationRemotingServer\\ConfigurationRemotingServer.exe"; - // Represents a remote factory object that was created from a specific process. - struct RemoteFactory : winrt::implements<RemoteFactory, IConfigurationSetProcessorFactory, SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties, winrt::cloaked<WinRT::ILifetimeWatcher>>, WinRT::LifetimeWatcherBase + // The string used to divide the arguments sent to the remote server + constexpr std::wstring_view s_ArgumentsDivider = L"\n~~~~~~\n"; + + // A helper with a convenient function that we use to receive the remote factory object. + struct RemoteFactoryCallback : winrt::implements<RemoteFactoryCallback, IConfigurationStatics> { - RemoteFactory() + RemoteFactoryCallback() { - AICLI_LOG(Config, Verbose, << "Launching process for configuration processing..."); - - // Security attributes to set handles as inherited. - SECURITY_ATTRIBUTES securityAttributes{}; - securityAttributes.nLength = sizeof(securityAttributes); - securityAttributes.bInheritHandle = TRUE; - securityAttributes.lpSecurityDescriptor = nullptr; - - // Create file mapping backed by page file. - wil::unique_handle memoryHandle{ CreateFileMappingW(INVALID_HANDLE_VALUE, &securityAttributes, PAGE_READWRITE, 0, details::MappedMemoryValue::s_MemorySize, nullptr) }; - THROW_LAST_ERROR_IF_NULL(memoryHandle); - - // Map the memory into the process. - wil::unique_mapview_ptr<details::MappedMemoryValue> mappedMemory{ reinterpret_cast<details::MappedMemoryValue*>(MapViewOfFile(memoryHandle.get(), FILE_MAP_READ | FILE_MAP_WRITE , 0, 0, 0)) }; - THROW_LAST_ERROR_IF_NULL(mappedMemory); - // Initialize the result to a failure in case the other process never comes through. - mappedMemory->Result = E_FAIL; - - // Create an event that the remote process will signal to indicate it has completed creating the object. - wil::unique_event initEvent; - initEvent.create(wil::EventOptions::None, nullptr, &securityAttributes); + m_initEvent.create(); + } - // Create the event that the remote process will wait on to keep the object alive. - m_completionEvent.create(wil::EventOptions::None, nullptr, &securityAttributes); - auto completeEventIfFailureDuringConstruction = wil::scope_exit([&]() { m_completionEvent.SetEvent(); }); + ConfigurationUnit CreateConfigurationUnit() + { + THROW_HR(E_NOTIMPL); + } - wil::unique_process_handle thisProcessHandle; - THROW_IF_WIN32_BOOL_FALSE(DuplicateHandle(GetCurrentProcess(), GetCurrentProcess(), GetCurrentProcess(), &thisProcessHandle, 0, TRUE, DUPLICATE_SAME_ACCESS)); + ConfigurationSet CreateConfigurationSet() + { + THROW_HR(E_NOTIMPL); + } - // Arguments are: - // server.exe <mapped memory handle> <event handle> <mutex handle> <parent process handle> - std::wostringstream argumentsStream; - argumentsStream << s_RemoteServerFileName << L' ' << reinterpret_cast<INT_PTR>(memoryHandle.get()) << L' ' << reinterpret_cast<INT_PTR>(initEvent.get()) - << L' ' << reinterpret_cast<INT_PTR>(m_completionEvent.get()) << L' ' << reinterpret_cast<INT_PTR>(thisProcessHandle.get()); - std::wstring arguments = argumentsStream.str(); + IAsyncOperation<IConfigurationSetProcessorFactory> CreateConfigurationSetProcessorFactoryAsync(winrt::hstring handler) + { + // TODO: Ensure calling process has same package identity + std::wstringstream stringStream{ std::wstring{ static_cast<std::wstring_view>(handler) } }; + stringStream >> m_result; + m_initEvent.SetEvent(); + return nullptr; + } - std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot); - serverPath /= s_RemoteServerFileName; + ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory factory) + { + // TODO: Ensure calling process has same package identity + m_factory = factory; + m_initEvent.SetEvent(); + return nullptr; + } - STARTUPINFOW startupInfo{}; - startupInfo.cb = sizeof(startupInfo); - wil::unique_process_information processInformation; + bool IsConfigurationAvailable() + { + THROW_HR(E_NOTIMPL); + } - THROW_IF_WIN32_BOOL_FALSE(CreateProcessW(serverPath.c_str(), &arguments[0], nullptr, nullptr, TRUE, DETACHED_PROCESS, nullptr, nullptr, &startupInfo, &processInformation)); - AICLI_LOG(Config, Verbose, << " Configuration remote PID is " << processInformation.dwProcessId); + IAsyncActionWithProgress<uint32_t> EnsureConfigurationAvailableAsync() + { + THROW_HR(E_NOTIMPL); + } + IConfigurationSetProcessorFactory Wait(HANDLE process) + { HANDLE waitHandles[2]; - waitHandles[0] = initEvent.get(); - waitHandles[1] = processInformation.hProcess; + waitHandles[0] = m_initEvent.get(); + waitHandles[1] = process; for (;;) { @@ -116,7 +95,7 @@ namespace AppInstaller::CLI::ConfigurationRemoting // If the process exited, then try to use the exit code. DWORD processExitCode = 0; - if (waitResult == (WAIT_OBJECT_0 + 1) && GetExitCodeProcess(processInformation.hProcess, &processExitCode) && FAILED(processExitCode)) + if (waitResult == (WAIT_OBJECT_0 + 1) && GetExitCodeProcess(process, &processExitCode) && FAILED(processExitCode)) { THROW_HR(static_cast<HRESULT>(processExitCode)); } @@ -127,21 +106,102 @@ namespace AppInstaller::CLI::ConfigurationRemoting } } - // Report on a failure in the server. - THROW_IF_FAILED(mappedMemory->Result); + THROW_IF_FAILED(m_result); + + // Double-check the result + THROW_HR_IF(E_POINTER, !m_factory); + return m_factory; + } - THROW_HR_IF(E_NOT_SUFFICIENT_BUFFER, mappedMemory->FactorySize == 0); - THROW_HR_IF(E_NOT_SUFFICIENT_BUFFER, mappedMemory->FactorySize > details::MappedMemoryValue::MaxFactorySize()); + private: + IConfigurationSetProcessorFactory m_factory; + HRESULT m_result = S_OK; + wil::unique_event m_initEvent; + }; + + // Represents a remote factory object that was created from a specific process. + struct RemoteFactory : winrt::implements<RemoteFactory, IConfigurationSetProcessorFactory, SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties, winrt::cloaked<WinRT::ILifetimeWatcher>>, WinRT::LifetimeWatcherBase + { + RemoteFactory(bool useRunAs, const std::string& properties, const std::string& restrictions) + { + AICLI_LOG(Config, Verbose, << "Launching process for configuration processing..."); + + // Create our callback and marshal it + auto callback = winrt::make_self<RemoteFactoryCallback>(); wil::com_ptr<IStream> stream; THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); - THROW_IF_FAILED(stream->Write(mappedMemory->FactoryObject, mappedMemory->FactorySize, nullptr)); + + THROW_IF_FAILED(CoMarshalInterface(stream.get(), winrt::guid_of<IConfigurationStatics>(), reinterpret_cast<::IUnknown*>(winrt::get_abi(callback.as<IConfigurationStatics>())), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); + + ULARGE_INTEGER streamSize{}; + THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); + + ULONG bufferSize = static_cast<ULONG>(streamSize.QuadPart); + std::vector<uint8_t> buffer; + buffer.resize(bufferSize); + THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + ULONG bytesRead = 0; + THROW_IF_FAILED(stream->Read(&buffer[0], bufferSize, &bytesRead)); + THROW_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); + + std::wstring marshalledCallback = Utility::ConvertToUTF16(Utility::ConvertToHexString(buffer)); + + // Create the event that the remote process will wait on to keep the object alive. + std::wstring completionEventName = Utility::CreateNewGuidNameWString(); + m_completionEvent.create(wil::EventOptions::None, completionEventName.c_str()); + auto completeEventIfFailureDuringConstruction = wil::scope_exit([&]() { m_completionEvent.SetEvent(); }); + + // This will be presented to the user so it must be formatted nicely. + // Arguments are: + // server.exe <marshalled callback object> <completion event name> <this process id> + // + // Optionally, we may also place additional data that limits what the server may do as: + // ~~~~~~ + // { "JSON properties" } + // ~~~~~~ + // YAML configuration set definition + std::wostringstream argumentsStream; + argumentsStream << s_RemoteServerFileName << L' ' << marshalledCallback << L' ' << completionEventName << L' ' << GetCurrentProcessId(); + + if (!properties.empty() && !restrictions.empty()) + { + argumentsStream << L' ' << s_ArgumentsDivider << Utility::ConvertToUTF16(properties) << s_ArgumentsDivider << Utility::ConvertToUTF16(restrictions); + } - wil::com_ptr<::IUnknown> output; - THROW_IF_FAILED(CoUnmarshalInterface(stream.get(), winrt::guid_of<IConfigurationSetProcessorFactory>(), reinterpret_cast<void**>(&output))); + std::wstring arguments = argumentsStream.str(); + + std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot); + serverPath /= s_RemoteServerFileName; + std::wstring serverPathString = serverPath.wstring(); + + // Per documentation, the maximum length is 32767 *counting* the null. + THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, serverPathString.length() > 32766); + THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, arguments.length() > 32766); + // Overflow safe since we verify that each of the individual strings is also small. + // +1 for the space between the path and args. + THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, serverPathString.length() + 1 + arguments.length() > 32766); + + SHELLEXECUTEINFOW execInfo = { 0 }; + execInfo.cbSize = sizeof(execInfo); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI | SEE_MASK_NO_CONSOLE; + execInfo.lpFile = serverPath.c_str(); + execInfo.lpParameters = arguments.c_str(); + execInfo.nShow = SW_HIDE; + + if (useRunAs) + { + execInfo.lpVerb = L"runas"; + } + + THROW_LAST_ERROR_IF(!ShellExecuteExW(&execInfo) || !execInfo.hProcess); + + wil::unique_process_handle process{ execInfo.hProcess }; + AICLI_LOG(Config, Verbose, << " Configuration remote PID is " << GetProcessId(process.get())); + + m_remoteFactory = callback->Wait(process.get()); AICLI_LOG(Config, Verbose, << "... configuration processing connection established."); - m_remoteFactory = IConfigurationSetProcessorFactory{ output.detach(), winrt::take_ownership_from_abi }; completeEventIfFailureDuringConstruction.release(); } @@ -263,52 +323,55 @@ namespace AppInstaller::CLI::ConfigurationRemoting }; } - IConfigurationSetProcessorFactory CreateOutOfProcessFactory() + IConfigurationSetProcessorFactory CreateOutOfProcessFactory(bool useRunAs, const std::string& properties, const std::string& restrictions) { - return winrt::make<RemoteFactory>(); + return winrt::make<RemoteFactory>(useRunAs, properties, restrictions); } } -HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, uint64_t memoryHandleIntPtr, uint64_t initEventHandleIntPtr, uint64_t completionMutexHandleIntPtr, uint64_t parentProcessIntPtr) try +HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, LPWSTR staticsCallback, LPWSTR completionEventName, DWORD parentProcessId) try { - using namespace AppInstaller::CLI::ConfigurationRemoting; - - RETURN_HR_IF(E_POINTER, !memoryHandleIntPtr); - - wil::unique_handle memoryHandle{ reinterpret_cast<HANDLE>(memoryHandleIntPtr) }; - wil::unique_mapview_ptr<details::MappedMemoryValue> mappedMemory{ reinterpret_cast<details::MappedMemoryValue*>(MapViewOfFile(memoryHandle.get(), FILE_MAP_WRITE, 0, 0, 0)) }; - RETURN_LAST_ERROR_IF_NULL(mappedMemory); + { + wil::com_ptr<IGlobalOptions> globalOptions; + RETURN_IF_FAILED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_UNMARSHALING_POLICY, COMGLB_UNMARSHALING_POLICY_STRONG)); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY)); + } - mappedMemory->Result = result; - mappedMemory->FactorySize = 0; + using namespace AppInstaller; + using namespace AppInstaller::CLI::ConfigurationRemoting; - if (SUCCEEDED(result)) - { - wil::com_ptr<IStream> stream; - RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + RETURN_HR_IF(E_POINTER, !staticsCallback); - RETURN_IF_FAILED(CoMarshalInterface(stream.get(), winrt::guid_of<IConfigurationSetProcessorFactory>(), reinterpret_cast<::IUnknown*>(factory), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); + auto callbackBytes = Utility::ParseFromHexString(Utility::ConvertToUTF8(staticsCallback)); + RETURN_HR_IF(E_INVALIDARG, callbackBytes.size() > (1 << 15)); - ULARGE_INTEGER streamSize{}; - RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); - RETURN_HR_IF(E_NOT_SUFFICIENT_BUFFER, streamSize.QuadPart > details::MappedMemoryValue::MaxFactorySize()); + wil::com_ptr<IStream> stream; + RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + RETURN_IF_FAILED(stream->Write(&callbackBytes[0], static_cast<ULONG>(callbackBytes.size()), nullptr)); + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); - ULONG bufferSize = static_cast<ULONG>(streamSize.QuadPart); + wil::com_ptr<::IUnknown> output; + RETURN_IF_FAILED(CoUnmarshalInterface(stream.get(), winrt::guid_of<IConfigurationStatics>(), reinterpret_cast<void**>(&output))); - RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); - ULONG bytesRead = 0; - RETURN_IF_FAILED(stream->Read(mappedMemory->FactoryObject, bufferSize, &bytesRead)); - RETURN_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); + IConfigurationStatics callback{ output.detach(), winrt::take_ownership_from_abi }; - mappedMemory->FactorySize = bufferSize; + if (FAILED(result)) + { + std::ignore = callback.CreateConfigurationSetProcessorFactoryAsync(std::to_wstring(result)); + } + else + { + IConfigurationSetProcessorFactory factoryObject; + winrt::copy_from_abi(factoryObject, factory); + std::ignore = callback.CreateConfigurationProcessor(factoryObject); } - - wil::unique_event initEvent{ reinterpret_cast<HANDLE>(initEventHandleIntPtr) }; - initEvent.SetEvent(); // Wait until the caller releases the object (signalling the event) or the parent process exits - wil::unique_event completionEvent{ reinterpret_cast<HANDLE>(completionMutexHandleIntPtr) }; - wil::unique_process_handle parentProcess{ reinterpret_cast<HANDLE>(parentProcessIntPtr) }; + wil::unique_event completionEvent; + completionEvent.open(completionEventName); + wil::unique_process_handle parentProcess{ OpenProcess(SYNCHRONIZE, FALSE, parentProcessId) }; HANDLE waitHandles[2]; waitHandles[0] = completionEvent.get(); diff --git a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h @@ -7,8 +7,11 @@ namespace AppInstaller::CLI::ConfigurationRemoting { // Creates a factory in another process - winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateOutOfProcessFactory(); + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateOutOfProcessFactory(bool useRunAs = false, const std::string& properties = {}, const std::string& restrictions = {}); + + // Creates a factory that can route configurations to the appropriate internal factory. + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateDynamicRuntimeFactory(); } // Export for use by the out of process factory server to report its initialization. -HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, uint64_t memoryHandle, uint64_t initEventHandle, uint64_t completionMutexHandle); +HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, LPWSTR staticsCallback, LPWSTR completionEventName, DWORD parentProcessId); diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -4,13 +4,15 @@ #include "ConfigurationFlow.h" #include "PromptFlow.h" #include "Public/ConfigurationSetProcessorFactoryRemoting.h" +#include "ConfigurationCommon.h" +#include "ConfigurationWingetDscModuleUnitValidation.h" #include <AppInstallerDownloader.h> #include <AppInstallerErrors.h> +#include <AppInstallerRuntime.h> #include <AppInstallerStrings.h> -#include <winrt/Microsoft.Management.Configuration.h> +#include <winget/ExperimentalFeature.h> #include <winget/SelfManagement.h> -#include "ConfigurationCommon.h" -#include "ConfigurationWingetDscModuleUnitValidation.h" +#include <winrt/Microsoft.Management.Configuration.h> using namespace AppInstaller::CLI::Execution; using namespace winrt::Microsoft::Management::Configuration; @@ -86,8 +88,21 @@ namespace AppInstaller::CLI::Workflow } #endif - auto factory = ConfigurationRemoting::CreateOutOfProcessFactory(); - Configuration::SetModulePath(context, factory); + IConfigurationSetProcessorFactory factory; + + // Since downgrading is not currently supported, only use dynamic if not running as admin. + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::ConfigureSelfElevation) && + !Runtime::IsRunningAsAdmin()) + { + factory = ConfigurationRemoting::CreateDynamicRuntimeFactory(); + // TODO: Implement SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties on dynamic factory + } + else + { + factory = ConfigurationRemoting::CreateOutOfProcessFactory(); + Configuration::SetModulePath(context, factory); + } + return factory; } diff --git a/src/AppInstallerCommonCore/ExperimentalFeature.cpp b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -48,6 +48,8 @@ namespace AppInstaller::Settings return userSettings.Get<Setting::EFSideBySide>(); case ExperimentalFeature::Feature::Proxy: return userSettings.Get<Setting::EFProxy>(); + case ExperimentalFeature::Feature::ConfigureSelfElevation: + return userSettings.Get<Setting::EFConfigureSelfElevation>(); default: THROW_HR(E_UNEXPECTED); } @@ -85,6 +87,8 @@ namespace AppInstaller::Settings return ExperimentalFeature{ "Side-by-side improvements", "sideBySide", "https://aka.ms/winget-settings", Feature::SideBySide }; case Feature::Proxy: return ExperimentalFeature{ "Proxy", "proxy", "https://aka.ms/winget-settings", Feature::Proxy }; + case Feature::ConfigureSelfElevation: + return ExperimentalFeature{ "Configure Self Elevation", "configureSelfElevate", "https://aka.ms/winget-settings", Feature::ConfigureSelfElevation }; default: THROW_HR(E_UNEXPECTED); } diff --git a/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h b/src/AppInstallerCommonCore/Public/winget/ExperimentalFeature.h @@ -27,6 +27,7 @@ namespace AppInstaller::Settings Configuration03 = 0x4, Proxy = 0x8, SideBySide = 0x10, + ConfigureSelfElevation = 0x20, Max, // This MUST always be after all experimental features // Features listed after Max will not be shown with the features command diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -74,6 +74,7 @@ namespace AppInstaller::Settings EFConfiguration03, EFSideBySide, EFProxy, + EFConfigureSelfElevation, // Telemetry TelemetryDisable, // Install behavior @@ -155,6 +156,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::EFConfiguration03, bool, bool, false, ".experimentalFeatures.configuration03"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFSideBySide, bool, bool, false, ".experimentalFeatures.sideBySide"sv); SETTINGMAPPING_SPECIALIZATION(Setting::EFProxy, bool, bool, false, ".experimentalFeatures.proxy"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFConfigureSelfElevation, bool, bool, false, ".experimentalFeatures.configureSelfElevate"sv); // Telemetry SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); // Install behavior diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp @@ -263,6 +263,7 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(EFConfiguration03) WINGET_VALIDATE_PASS_THROUGH(EFSideBySide) WINGET_VALIDATE_PASS_THROUGH(EFProxy) + WINGET_VALIDATE_PASS_THROUGH(EFConfigureSelfElevation) WINGET_VALIDATE_PASS_THROUGH(AnonymizePathForDisplay) WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) diff --git a/src/AppInstallerSharedLib/AppInstallerStrings.cpp b/src/AppInstallerSharedLib/AppInstallerStrings.cpp @@ -869,11 +869,22 @@ namespace AppInstaller::Utility std::string ConvertGuidToString(const GUID& value) { - wchar_t buffer[256]; + wchar_t buffer[40]; THROW_HR_IF(E_UNEXPECTED, !StringFromGUID2(value, buffer, ARRAYSIZE(buffer))); return ConvertToUTF8(buffer); } + std::wstring CreateNewGuidNameWString() + { + GUID guid; + THROW_IF_FAILED(CoCreateGuid(&guid)); + + wchar_t buffer[40]; + THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(guid, buffer, ARRAYSIZE(buffer)) != 39); + + return std::wstring{ &buffer[1], 36 }; + } + bool IsDwordFlagSet(const std::string& value) { if (std::empty(value)) diff --git a/src/AppInstallerSharedLib/Public/AppInstallerStrings.h b/src/AppInstallerSharedLib/Public/AppInstallerStrings.h @@ -269,6 +269,9 @@ namespace AppInstaller::Utility // Converts the given GUID value to a string. std::string ConvertGuidToString(const GUID& value); + // Creates a new GUID and returns the string value. + std::wstring CreateNewGuidNameWString(); + // Converts the input string to a DWORD value using std::stoul and returns a boolean value based on the resulting DWORD value. bool IsDwordFlagSet(const std::string& value); } diff --git a/src/AppInstallerSharedLib/Public/winget/ConfigurationSetProcessorHandlers.h b/src/AppInstallerSharedLib/Public/winget/ConfigurationSetProcessorHandlers.h @@ -8,4 +8,5 @@ namespace AppInstaller::Configuration { constexpr std::wstring_view PowerShellHandlerIdentifier = L"pwsh"; + constexpr std::wstring_view DynamicRuntimeHandlerIdentifier = L"{73fea39f-6f4a-41c9-ba94-6fd14d633e40}"; } diff --git a/src/ConfigurationRemotingServer/Program.cs b/src/ConfigurationRemotingServer/Program.cs @@ -1,8 +1,8 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -using System.IO; using System.Reflection; using System.Runtime.InteropServices; +using System.Runtime.Loader; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -13,6 +13,57 @@ using WinRT; namespace ConfigurationRemotingServer { + /// <summary> + /// Custom assembly load context. + /// </summary> + internal class NativeAssemblyLoadContext : AssemblyLoadContext + { + private static readonly string PackageRootPath; + + private static readonly NativeAssemblyLoadContext NativeALC = new(); + + static NativeAssemblyLoadContext() + { + var self = typeof(NativeAssemblyLoadContext).Assembly; + PackageRootPath = Path.Combine( + Path.GetDirectoryName(self.Location)!, + ".."); + } + + private NativeAssemblyLoadContext() + : base("NativeAssemblyLoadContext", isCollectible: false) + { + } + + /// <summary> + /// Handler to resolve unmanaged assemblies. + /// </summary> + /// <param name="context">Assembly load context.</param> + /// <param name="name">Assembly name.</param> + /// <returns>The assembly, null if not in our assembly location.</returns> + internal static IntPtr ResolvingUnmanagedHandler(Assembly context, string name) + { + if (name.Equals("WindowsPackageManager.dll", StringComparison.OrdinalIgnoreCase)) + { + return NativeALC.LoadUnmanagedDll(name); + } + + return IntPtr.Zero; + } + + /// <inheritdoc/> + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string path = Path.Combine(PackageRootPath, unmanagedDllName); + if (File.Exists(path)) + { + return this.LoadUnmanagedDllFromPath(path); + } + + return IntPtr.Zero; + } + } + internal class Program { private const string CommandLineSectionSeparator = "~~~~~~"; @@ -20,13 +71,15 @@ namespace ConfigurationRemotingServer static int Main(string[] args) { - ulong memoryHandle = ulong.Parse(args[0]); + // Help find WindowsPackageManager.dll + AssemblyLoadContext.Default.ResolvingUnmanagedDll += NativeAssemblyLoadContext.ResolvingUnmanagedHandler; + + string staticsCallback = args[1]; try { - ulong initEventHandle = ulong.Parse(args[1]); - ulong completionEventHandle = ulong.Parse(args[2]); - ulong parentProcessHandle = ulong.Parse(args[3]); + string completionEventName = args[2]; + uint parentProcessId = uint.Parse(args[3]); PowerShellConfigurationSetProcessorFactory factory = new PowerShellConfigurationSetProcessorFactory(); @@ -100,11 +153,11 @@ namespace ConfigurationRemotingServer IObjectReference factoryInterface = MarshalInterface<global::Microsoft.Management.Configuration.IConfigurationSetProcessorFactory>.CreateMarshaler(factory); - return WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(0, factoryInterface.ThisPtr, memoryHandle, initEventHandle, completionEventHandle, parentProcessHandle); + return WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(0, factoryInterface.ThisPtr, staticsCallback, completionEventName, parentProcessId); } catch(Exception ex) { - WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(ex.HResult, IntPtr.Zero, memoryHandle, 0, 0, 0); + WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(ex.HResult, IntPtr.Zero, staticsCallback, null, 0); return ex.HResult; } } @@ -135,7 +188,12 @@ namespace ConfigurationRemotingServer } [DllImport("WindowsPackageManager.dll")] - private static extern int WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(int result, IntPtr factory, ulong memoryHandle, ulong initEventHandle, ulong completionMutexHandle, ulong parentProcessHandle); + private static extern int WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization( + int result, + IntPtr factory, + [MarshalAs(UnmanagedType.LPWStr)]string staticsCallback, + [MarshalAs(UnmanagedType.LPWStr)]string? completionEventName, + uint parentProcessId); [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] private static extern IntPtr GetCommandLineW(); diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationProcessorTestBase.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -79,6 +79,29 @@ namespace Microsoft.Management.Configuration.UnitTests.Helpers } /// <summary> + /// Creates an string from the given output stream. + /// </summary> + /// <param name="stream">The output stream.</param> + /// <returns>The created string.</returns> + protected string ReadStream(InMemoryRandomAccessStream stream) + { + string result = string.Empty; + using (DataReader reader = new DataReader(stream.GetInputStreamAt(0))) + { + reader.UnicodeEncoding = UnicodeEncoding.Utf8; + reader.LoadAsync((uint)stream.Size).AsTask().Wait(); + uint bytesToRead = reader.UnconsumedBufferLength; + + if (bytesToRead > 0) + { + result = reader.ReadString(bytesToRead); + } + } + + return result; + } + + /// <summary> /// Creates a configuration unit via the configuration statics object. /// </summary> /// <returns>A new configuration unit.</returns> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="ConfigurationProcessorGroupTests.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs @@ -10,6 +10,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Microsoft.VisualBasic; + using Windows.Foundation.Collections; + using Windows.Storage.Streams; using Xunit; using Xunit.Abstractions; @@ -70,7 +72,6 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests ConfigurationUnitIntent testIntent = ConfigurationUnitIntent.Assert; ConfigurationUnit testUnit = this.ConfigurationUnit(); - testUnit.Type = testName; Assert.Equal(testName, testUnit.Type); testUnit.Identifier = testIdentifier; @@ -100,12 +101,33 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests } /// <summary> - /// This test is to ensure that real tests are added when Serialize is implemented. + /// Basic sanity check to verify that nested value sets can be serialized successfully. /// </summary> [Fact] - public void ConfigurationSetSerializeNotImplemented() + public void ConfigurationSetSerializeNestedValueSets() { - Assert.Throws<NotImplementedException>(() => this.ConfigurationSet().Serialize(null)); + ConfigurationSet testSet = this.ConfigurationSet(); + + testSet.SchemaVersion = "0.2"; + ConfigurationUnit testUnit = this.ConfigurationUnit(); + string testName = "Test Name"; + string testIdentifier = "Test Identifier"; + testUnit.Type = testName; + testUnit.Identifier = testIdentifier; + + ValueSet innerValueSet = new ValueSet(); + innerValueSet.Add("innerKey", "innerValue"); + + ValueSet outerValueSet = new ValueSet(); + outerValueSet.Add("outerKey", innerValueSet); + testUnit.Metadata = outerValueSet; + testSet.Units.Add(testUnit); + + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + testSet.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + Assert.NotNull(yamlOutput); } } } diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // <copyright file="OpenConfigurationSetTests.cs" company="Microsoft Corporation"> // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // </copyright> @@ -10,11 +10,14 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; + using Microsoft.Management.Configuration.Processor.Extensions; using Microsoft.Management.Configuration.UnitTests.Fixtures; using Microsoft.Management.Configuration.UnitTests.Helpers; using Microsoft.VisualBasic; using Newtonsoft.Json.Linq; using Windows.Foundation.Collections; + using Windows.Storage.Streams; + using WinRT; using Xunit; using Xunit.Abstractions; @@ -468,6 +471,79 @@ properties: } /// <summary> + /// Verifies that the configuration set (0.2) can be serialized and reopened correctly. + /// </summary> + [Fact] + public void TestSet_Serialize_0_2() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + assertions: + - resource: FakeModule + id: TestId + directives: + description: FakeDescription + allowPrerelease: true + SecurityContext: elevated + settings: + TestString: Hello + TestBool: false + TestInt: 1234 + resources: + - resource: FakeModule2 + id: TestId2 + dependsOn: + - TestId + - dependency2 + - dependency3 + directives: + description: FakeDescription2 + SecurityContext: elevated + settings: + TestString: Bye + TestBool: true + TestInt: 4321 + Mapping: + Key: TestValue +")); + + // Serialize set. + ConfigurationSet configurationSet = openResult.Set; + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + configurationSet.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + Assert.Null(serializedSetResult.ResultCode); + ConfigurationSet set = serializedSetResult.Set; + Assert.NotNull(set); + + Assert.Equal("0.2", set.SchemaVersion); + Assert.Equal(2, set.Units.Count); + + Assert.Equal("FakeModule", set.Units[0].Type); + Assert.Equal(ConfigurationUnitIntent.Assert, set.Units[0].Intent); + Assert.Equal("TestId", set.Units[0].Identifier); + this.VerifyValueSet(set.Units[0].Metadata, new ("description", "FakeDescription"), new ("allowPrerelease", true), new ("SecurityContext", "elevated")); + this.VerifyValueSet(set.Units[0].Settings, new ("TestString", "Hello"), new ("TestBool", false), new ("TestInt", 1234)); + + Assert.Equal("FakeModule2", set.Units[1].Type); + Assert.Equal(ConfigurationUnitIntent.Apply, set.Units[1].Intent); + Assert.Equal("TestId2", set.Units[1].Identifier); + this.VerifyStringArray(set.Units[1].Dependencies, "TestId", "dependency2", "dependency3"); + this.VerifyValueSet(set.Units[1].Metadata, new ("description", "FakeDescription2"), new ("SecurityContext", "elevated")); + + ValueSet mapping = new ValueSet(); + mapping.Add("Key", "TestValue"); + this.VerifyValueSet(set.Units[1].Settings, new ("TestString", "Bye"), new ("TestBool", true), new ("TestInt", 4321), new ("Mapping", mapping)); + } + + /// <summary> /// Test for using version 0.3 schema. /// </summary> [Fact] @@ -692,6 +768,12 @@ parameters: case string s: Assert.Equal(s, (string)value); break; + case bool b: + Assert.Equal(b, (bool)value); + break; + case ValueSet v: + Assert.True(v.ContentEquals(value.As<ValueSet>())); + break; default: Assert.Fail($"Add expected type `{expectation.Value.GetType().Name}` to switch statement."); break; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.cpp b/src/Microsoft.Management.Configuration/ConfigurationSet.cpp @@ -4,6 +4,7 @@ #include "ConfigurationSet.h" #include "ConfigurationSet.g.cpp" #include "ConfigurationSetParser.h" +#include "ConfigurationSetSerializer.h" namespace winrt::Microsoft::Management::Configuration::implementation { @@ -125,8 +126,14 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSet::Serialize(const Windows::Storage::Streams::IOutputStream& stream) { - UNREFERENCED_PARAMETER(stream); - THROW_HR(E_NOTIMPL); + std::unique_ptr<ConfigurationSetSerializer> serializer = ConfigurationSetSerializer::CreateSerializer(m_schemaVersion); + hstring result = serializer->Serialize(this); + + Windows::Storage::Streams::DataWriter dataWriter{ stream }; + dataWriter.UnicodeEncoding(Windows::Storage::Streams::UnicodeEncoding::Utf8); + dataWriter.WriteString(result); + dataWriter.StoreAsync().get(); + dataWriter.DetachStream(); } void ConfigurationSet::Remove() diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp @@ -10,6 +10,7 @@ #include <AppInstallerStrings.h> #include <AppInstallerVersions.h> +#include "ConfigurationSetUtilities.h" #include "ConfigurationSetParserError.h" #include "ConfigurationSetParser_0_1.h" #include "ConfigurationSetParser_0_2.h" @@ -178,7 +179,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::string schemaUriString; std::string schemaVersionString; - Node& schemaNode = document[GetFieldName(FieldName::Schema)]; + Node& schemaNode = document[GetConfigurationFieldName(ConfigurationField::Schema)]; if (schemaNode.IsScalar()) { schemaUriString = schemaNode.as<std::string>(); @@ -199,7 +200,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (schemaNode.IsScalar()) { AICLI_LOG(Config, Error, << "Unknown configuration schema: " << schemaUriString); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetFieldName(FieldName::Schema), schemaUriString); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetConfigurationFieldName(ConfigurationField::Schema), schemaUriString); } else { @@ -227,7 +228,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } AICLI_LOG(Config, Error, << "Unknown configuration version: " << schemaVersion.ToString()); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetFieldName(FieldName::ConfigurationVersion), schemaVersion.ToString()); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion), schemaVersion.ToString()); } bool ConfigurationSetParser::IsRecognizedSchemaVersion(hstring value) try @@ -325,76 +326,36 @@ namespace winrt::Microsoft::Management::Configuration::implementation SetError(result, field, value, static_cast<uint32_t>(mark.line), static_cast<uint32_t>(mark.column)); } - std::string_view ConfigurationSetParser::GetFieldName(FieldName fieldName) + const Node& ConfigurationSetParser::GetAndEnsureField(const Node& parent, ConfigurationField field, bool required, std::optional<Node::Type> type) { - switch (fieldName) - { - case FieldName::ConfigurationVersion: return "configurationVersion"sv; - case FieldName::Properties: return "properties"sv; - case FieldName::Resource: return "resource"sv; - case FieldName::Directives: return "directives"sv; - case FieldName::Settings: return "settings"sv; - case FieldName::Assertions: return "assertions"sv; - case FieldName::Id: return "id"sv; - case FieldName::DependsOn: return "dependsOn"sv; - - case FieldName::Resources: return "resources"sv; - case FieldName::ModuleDirective: return "module"sv; - - case FieldName::Schema: return "$schema"sv; - case FieldName::Metadata: return "metadata"sv; - case FieldName::Parameters: return "parameters"sv; - case FieldName::Variables: return "variables"sv; - case FieldName::Type: return "type"sv; - case FieldName::Description: return "description"sv; - case FieldName::Name: return "name"sv; - case FieldName::IsGroupMetadata: return "isGroup"sv; - case FieldName::DefaultValue: return "defaultValue"sv; - case FieldName::AllowedValues: return "allowedValues"sv; - case FieldName::MinimumLength: return "minLength"sv; - case FieldName::MaximumLength: return "maxLength"sv; - case FieldName::MinimumValue: return "minValue"sv; - case FieldName::MaximumValue: return "maxValue"sv; - } - - THROW_HR(E_UNEXPECTED); - } - - hstring ConfigurationSetParser::GetFieldNameHString(FieldName fieldName) - { - return hstring{ ConvertToUTF16(GetFieldName(fieldName)) }; - } - - const Node& ConfigurationSetParser::GetAndEnsureField(const Node& parent, FieldName field, bool required, std::optional<Node::Type> type) - { - const Node& fieldNode = parent[GetFieldName(field)]; + const Node& fieldNode = parent[GetConfigurationFieldName(field)]; if (fieldNode) { if (type && fieldNode.GetType() != type.value()) { - SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetFieldName(field), fieldNode.Mark()); + SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(field), fieldNode.Mark()); } } else if (required) { - SetError(WINGET_CONFIG_ERROR_MISSING_FIELD, GetFieldName(field)); + SetError(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(field)); } return fieldNode; } - void ConfigurationSetParser::EnsureFieldAbsent(const Node& parent, FieldName field) + void ConfigurationSetParser::EnsureFieldAbsent(const Node& parent, ConfigurationField field) { - const Node& fieldNode = parent[GetFieldName(field)]; + const Node& fieldNode = parent[GetConfigurationFieldName(field)]; if (fieldNode) { - SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, GetFieldName(field), fieldNode.Mark(), fieldNode.as<std::string>()); + SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, GetConfigurationFieldName(field), fieldNode.Mark(), fieldNode.as<std::string>()); } } - void ConfigurationSetParser::ParseValueSet(const Node& node, FieldName field, bool required, const Windows::Foundation::Collections::ValueSet& valueSet) + void ConfigurationSetParser::ParseValueSet(const Node& node, ConfigurationField field, bool required, const Windows::Foundation::Collections::ValueSet& valueSet) { const Node& mapNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Mapping)); @@ -404,7 +365,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } } - void ConfigurationSetParser::ParseMapping(const AppInstaller::YAML::Node& node, FieldName field, bool required, AppInstaller::YAML::Node::Type elementType, std::function<void(std::string, const AppInstaller::YAML::Node&)> operation) + void ConfigurationSetParser::ParseMapping(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, AppInstaller::YAML::Node::Type elementType, std::function<void(std::string, const AppInstaller::YAML::Node&)> operation) { const Node& mapNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Mapping)); if (!mapNode) @@ -413,7 +374,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } std::ostringstream strstr; - strstr << GetFieldName(field); + strstr << GetConfigurationFieldName(field); size_t index = 0; for (const auto& mapItem : mapNode.Mapping()) @@ -436,7 +397,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } } - void ConfigurationSetParser::ParseSequence(const AppInstaller::YAML::Node& node, FieldName field, bool required, std::optional<Node::Type> elementType, std::function<void(const AppInstaller::YAML::Node&)> operation) + void ConfigurationSetParser::ParseSequence(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, std::optional<Node::Type> elementType, std::function<void(const AppInstaller::YAML::Node&)> operation) { const Node& sequenceNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Sequence)); if (!sequenceNode) @@ -445,7 +406,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } std::ostringstream strstr; - strstr << GetFieldName(field); + strstr << GetConfigurationFieldName(field); size_t index = 0; for (const Node& item : sequenceNode.Sequence()) @@ -463,49 +424,49 @@ namespace winrt::Microsoft::Management::Configuration::implementation std::unique_ptr<ConfigurationSetParser> ConfigurationSetParser::GetSchemaVersionFromOldFormat(AppInstaller::YAML::Node& document, std::string& schemaVersionString) { - Node& propertiesNode = document[GetFieldName(FieldName::Properties)]; + Node& propertiesNode = document[GetConfigurationFieldName(ConfigurationField::Properties)]; if (!propertiesNode) { AICLI_LOG(Config, Error, << "No properties"); // Even though this is for the "older" format, if there is no properties entry then give an error for the newer format since this is probably neither. - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_MISSING_FIELD, GetFieldName(FieldName::Schema)); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(ConfigurationField::Schema)); } else if (!propertiesNode.IsMap()) { AICLI_LOG(Config, Error, << "Invalid properties type"); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetFieldName(FieldName::Properties), propertiesNode.Mark()); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(ConfigurationField::Properties), propertiesNode.Mark()); } - Node& versionNode = propertiesNode[GetFieldName(FieldName::ConfigurationVersion)]; + Node& versionNode = propertiesNode[GetConfigurationFieldName(ConfigurationField::ConfigurationVersion)]; if (!versionNode) { AICLI_LOG(Config, Error, << "No configuration version"); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_MISSING_FIELD, GetFieldName(FieldName::ConfigurationVersion)); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion)); } else if (!versionNode.IsScalar()) { AICLI_LOG(Config, Error, << "Invalid configuration version type"); - return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetFieldName(FieldName::ConfigurationVersion), versionNode.Mark()); + return std::make_unique<ConfigurationSetParserError>(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion), versionNode.Mark()); } schemaVersionString = versionNode.as<std::string>(); return {}; } - void ConfigurationSetParser::GetStringValueForUnit(const Node& node, FieldName field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)) + void ConfigurationSetParser::GetStringValueForUnit(const Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)) { const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Scalar)); if (valueNode) { hstring value{ valueNode.as<std::wstring>() }; - FIELD_MISSING_ERROR_IF(value.empty() && required, GetFieldName(field)); + FIELD_MISSING_ERROR_IF(value.empty() && required, GetConfigurationFieldName(field)); (unit->*propertyFunction)(std::move(value)); } } - void ConfigurationSetParser::GetStringArrayForUnit(const Node& node, FieldName field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector<hstring>&& value)) + void ConfigurationSetParser::GetStringArrayForUnit(const Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector<hstring>&& value)) { std::vector<hstring> arrayValue; CHECK_ERROR(ParseSequence(node, field, required, Node::Type::Scalar, [&](const AppInstaller::YAML::Node& item) @@ -519,25 +480,25 @@ namespace winrt::Microsoft::Management::Configuration::implementation } } - void ConfigurationSetParser::ValidateType(ConfigurationUnit* unit, const Node& unitNode, FieldName typeField, bool moveModuleNameToMetadata, bool moduleNameRequiredInType) + void ConfigurationSetParser::ValidateType(ConfigurationUnit* unit, const Node& unitNode, ConfigurationField typeField, bool moveModuleNameToMetadata, bool moduleNameRequiredInType) { QualifiedResourceName qualifiedName{ unit->Type() }; const Node& typeNode = CHECK_ERROR(GetAndEnsureField(unitNode, typeField, true, Node::Type::Scalar)); - FIELD_VALUE_ERROR_IF(qualifiedName.Resource.empty(), GetFieldName(typeField), ConvertToUTF8(unit->Type()), typeNode.Mark()); + FIELD_VALUE_ERROR_IF(qualifiedName.Resource.empty(), GetConfigurationFieldName(typeField), ConvertToUTF8(unit->Type()), typeNode.Mark()); if (!qualifiedName.Module.empty()) { // If the module is provided in both the resource name and the directives, ensure that it matches - hstring moduleDirectiveFieldName = GetFieldNameHString(FieldName::ModuleDirective); + hstring moduleDirectiveFieldName = GetConfigurationFieldNameHString(ConfigurationField::ModuleDirective); auto moduleDirective = unit->Metadata().TryLookup(moduleDirectiveFieldName); if (moduleDirective) { auto moduleProperty = moduleDirective.try_as<Windows::Foundation::IPropertyValue>(); - FIELD_TYPE_ERROR_IF(!moduleProperty, GetFieldName(FieldName::ModuleDirective), unitNode.Mark()); - FIELD_TYPE_ERROR_IF(moduleProperty.Type() != Windows::Foundation::PropertyType::String, GetFieldName(FieldName::ModuleDirective), unitNode.Mark()); + FIELD_TYPE_ERROR_IF(!moduleProperty, GetConfigurationFieldName(ConfigurationField::ModuleDirective), unitNode.Mark()); + FIELD_TYPE_ERROR_IF(moduleProperty.Type() != Windows::Foundation::PropertyType::String, GetConfigurationFieldName(ConfigurationField::ModuleDirective), unitNode.Mark()); hstring moduleValue = moduleProperty.GetString(); - FIELD_VALUE_ERROR_IF(qualifiedName.Module != moduleValue, GetFieldName(FieldName::ModuleDirective), ConvertToUTF8(moduleValue), unitNode.Mark()); + FIELD_VALUE_ERROR_IF(qualifiedName.Module != moduleValue, GetConfigurationFieldName(ConfigurationField::ModuleDirective), ConvertToUTF8(moduleValue), unitNode.Mark()); } else if (moveModuleNameToMetadata) { @@ -552,22 +513,22 @@ namespace winrt::Microsoft::Management::Configuration::implementation } else if (moduleNameRequiredInType) { - FIELD_VALUE_ERROR(GetFieldName(typeField), ConvertToUTF8(unit->Type()), typeNode.Mark()); + FIELD_VALUE_ERROR(GetConfigurationFieldName(typeField), ConvertToUTF8(unit->Type()), typeNode.Mark()); } } - void ConfigurationSetParser::ParseObject(const Node& node, FieldName fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result) + void ConfigurationSetParser::ParseObject(const Node& node, ConfigurationField fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result) { try { Windows::Foundation::IInspectable object = GetIInspectableFromNode(node); - FIELD_VALUE_ERROR_IF(!IsValidObjectType(object, type), GetFieldName(fieldForErrors), node.as<std::string>(), node.Mark()); + FIELD_VALUE_ERROR_IF(!IsValidObjectType(object, type), GetConfigurationFieldName(fieldForErrors), node.as<std::string>(), node.Mark()); result = std::move(object); } catch (...) { LOG_CAUGHT_EXCEPTION(); - FIELD_VALUE_ERROR(GetFieldName(fieldForErrors), node.as<std::string>(), node.Mark()); + FIELD_VALUE_ERROR(GetConfigurationFieldName(fieldForErrors), node.as<std::string>(), node.Mark()); } } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser.h @@ -3,14 +3,13 @@ #pragma once #include <ConfigurationUnit.h> #include <ConfigurationSet.h> +#include <ConfigurationSetUtilities.h> #include <winget/Yaml.h> #include <winrt/Windows.Storage.Streams.h> #include <memory> #include <string_view> #include <vector> -using namespace std::string_view_literals; - namespace winrt::Microsoft::Management::Configuration::implementation { // Interface for parsing a configuration set stream. @@ -79,44 +78,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation void SetError(hresult result, std::string_view field = {}, std::string_view value = {}, uint32_t line = 0, uint32_t column = 0); void SetError(hresult result, std::string_view field, const AppInstaller::YAML::Mark& mark, std::string_view value = {}); - // The various field names that are used in parsing. - enum class FieldName - { - // v0.1 and v0.2 - ConfigurationVersion, - Properties, - Resource, - Directives, - Settings, - Assertions, - Id, - DependsOn, - - // Universal - Resources, - ModuleDirective, - - // v0.3 - Schema, - Metadata, - Parameters, - Variables, - Type, - Description, - Name, - IsGroupMetadata, - DefaultValue, - AllowedValues, - MinimumLength, - MaximumLength, - MinimumValue, - MaximumValue, - }; - - // Gets the value of the field name. - static std::string_view GetFieldName(FieldName fieldName); - static hstring GetFieldNameHString(FieldName fieldName); - ConfigurationSetPtr m_configurationSet; hresult m_result; hstring m_field; @@ -125,31 +86,31 @@ namespace winrt::Microsoft::Management::Configuration::implementation uint32_t m_column = 0; // Gets the given `field` from the `parent` node, checking against the requirement and type. - const AppInstaller::YAML::Node& GetAndEnsureField(const AppInstaller::YAML::Node& parent, FieldName field, bool required, std::optional<AppInstaller::YAML::Node::Type> type); + const AppInstaller::YAML::Node& GetAndEnsureField(const AppInstaller::YAML::Node& parent, ConfigurationField field, bool required, std::optional<AppInstaller::YAML::Node::Type> type); // Errors if the given `field` is present. - void EnsureFieldAbsent(const AppInstaller::YAML::Node& parent, FieldName field); + void EnsureFieldAbsent(const AppInstaller::YAML::Node& parent, ConfigurationField field); // Parse the ValueSet named `field` from the given `node`. - void ParseValueSet(const AppInstaller::YAML::Node& node, FieldName field, bool required, const Windows::Foundation::Collections::ValueSet& valueSet); + void ParseValueSet(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, const Windows::Foundation::Collections::ValueSet& valueSet); // Parse the mapping named `field` from the given `node`. - void ParseMapping(const AppInstaller::YAML::Node& node, FieldName field, bool required, AppInstaller::YAML::Node::Type elementType, std::function<void(std::string, const AppInstaller::YAML::Node&)> operation); + void ParseMapping(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, AppInstaller::YAML::Node::Type elementType, std::function<void(std::string, const AppInstaller::YAML::Node&)> operation); // Parse the sequence named `field` from the given `node`. - void ParseSequence(const AppInstaller::YAML::Node& node, FieldName field, bool required, std::optional<AppInstaller::YAML::Node::Type> elementType, std::function<void(const AppInstaller::YAML::Node&)> operation); + void ParseSequence(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, std::optional<AppInstaller::YAML::Node::Type> elementType, std::function<void(const AppInstaller::YAML::Node&)> operation); // Gets the string value in `field` from the given `node`, setting this value on `unit` using the `propertyFunction`. - void GetStringValueForUnit(const AppInstaller::YAML::Node& node, FieldName field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)); + void GetStringValueForUnit(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)); // Gets the string array in `field` from the given `node`, setting this value on `unit` using the `propertyFunction`. - void GetStringArrayForUnit(const AppInstaller::YAML::Node& node, FieldName field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector<hstring>&& value)); + void GetStringArrayForUnit(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector<hstring>&& value)); // Validates the unit's Type property for correctness and consistency with the metadata. Should be called after parsing the Metadata value. - void ValidateType(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, FieldName typeField, bool moveModuleNameToMetadata, bool moduleNameRequiredInType); + void ValidateType(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationField typeField, bool moveModuleNameToMetadata, bool moduleNameRequiredInType); // Parses an object from the given node, attempting to treat it as the requested type if possible. - void ParseObject(const AppInstaller::YAML::Node& node, FieldName fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result); + void ParseObject(const AppInstaller::YAML::Node& node, ConfigurationField fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result); private: // Support older schema parsing. diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp @@ -16,10 +16,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetParser_0_1::Parse() { std::vector<Configuration::ConfigurationUnit> units; - const Node& properties = m_document[GetFieldName(FieldName::Properties)]; - ParseConfigurationUnitsFromField(properties, FieldName::Assertions, ConfigurationUnitIntent::Assert, units); - ParseConfigurationUnitsFromField(properties, FieldName::Parameters, ConfigurationUnitIntent::Inform, units); - ParseConfigurationUnitsFromField(properties, FieldName::Resources, ConfigurationUnitIntent::Apply, units); + const Node& properties = m_document[GetConfigurationFieldName(ConfigurationField::Properties)]; + ParseConfigurationUnitsFromField(properties, ConfigurationField::Assertions, ConfigurationUnitIntent::Assert, units); + ParseConfigurationUnitsFromField(properties, ConfigurationField::Parameters, ConfigurationUnitIntent::Inform, units); + ParseConfigurationUnitsFromField(properties, ConfigurationField::Resources, ConfigurationUnitIntent::Apply, units); m_configurationSet = make_self<wil::details::module_count_wrapper<implementation::ConfigurationSet>>(); m_configurationSet->Units(std::move(units)); @@ -32,7 +32,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation return s_schemaVersion; } - void ConfigurationSetParser_0_1::ParseConfigurationUnitsFromField(const Node& document, FieldName field, ConfigurationUnitIntent intent, std::vector<Configuration::ConfigurationUnit>& result) + void ConfigurationSetParser_0_1::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, ConfigurationUnitIntent intent, std::vector<Configuration::ConfigurationUnit>& result) { ParseSequence(document, field, false, Node::Type::Mapping, [&](const Node& item) { @@ -44,11 +44,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetParser_0_1::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, ConfigurationUnitIntent intent) { - CHECK_ERROR(GetStringValueForUnit(unitNode, FieldName::Resource, true, unit, &ConfigurationUnit::Type)); - CHECK_ERROR(GetStringValueForUnit(unitNode, FieldName::Id, false, unit, &ConfigurationUnit::Identifier)); + CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Resource, true, unit, &ConfigurationUnit::Type)); + CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Id, false, unit, &ConfigurationUnit::Identifier)); unit->Intent(intent); - CHECK_ERROR(GetStringArrayForUnit(unitNode, FieldName::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); - CHECK_ERROR(ParseValueSet(unitNode, FieldName::Directives, false, unit->Metadata())); - CHECK_ERROR(ParseValueSet(unitNode, FieldName::Settings, false, unit->Settings())); + CHECK_ERROR(GetStringArrayForUnit(unitNode, ConfigurationField::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); + CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Directives, false, unit->Metadata())); + CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Settings, false, unit->Settings())); } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h @@ -25,7 +25,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation hstring GetSchemaVersion() override; protected: - void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, FieldName field, ConfigurationUnitIntent intent, std::vector<Configuration::ConfigurationUnit>& result); + void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, ConfigurationUnitIntent intent, std::vector<Configuration::ConfigurationUnit>& result); virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationUnitIntent intent); AppInstaller::YAML::Node m_document; diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp @@ -22,6 +22,6 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetParser_0_2::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, ConfigurationUnitIntent intent) { CHECK_ERROR(ConfigurationSetParser_0_1::ParseConfigurationUnit(unit, unitNode, intent)); - ValidateType(unit, unitNode, FieldName::Resource, true, false); + ValidateType(unit, unitNode, ConfigurationField::Resource, true, false); } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp @@ -18,12 +18,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation { auto result = make_self<wil::details::module_count_wrapper<implementation::ConfigurationSet>>(); - CHECK_ERROR(ParseValueSet(m_document, FieldName::Metadata, false, result->Metadata())); + CHECK_ERROR(ParseValueSet(m_document, ConfigurationField::Metadata, false, result->Metadata())); CHECK_ERROR(ParseParameters(result)); - CHECK_ERROR(ParseValueSet(m_document, FieldName::Variables, false, result->Variables())); + CHECK_ERROR(ParseValueSet(m_document, ConfigurationField::Variables, false, result->Variables())); std::vector<Configuration::ConfigurationUnit> units; - CHECK_ERROR(ParseConfigurationUnitsFromField(m_document, FieldName::Resources, units)); + CHECK_ERROR(ParseConfigurationUnitsFromField(m_document, ConfigurationField::Resources, units)); result->Units(std::move(units)); result->SchemaVersion(GetSchemaVersion()); @@ -40,7 +40,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation { std::vector<Configuration::ConfigurationParameter> parameters; - ParseMapping(m_document, FieldName::Parameters, false, Node::Type::Mapping, [&](std::string name, const Node& item) + ParseMapping(m_document, ConfigurationField::Parameters, false, Node::Type::Mapping, [&](std::string name, const Node& item) { auto parameter = make_self<wil::details::module_count_wrapper<ConfigurationParameter>>(); CHECK_ERROR(ParseParameter(parameter.get(), item)); @@ -54,18 +54,18 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetParser_0_3::ParseParameter(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node) { CHECK_ERROR(ParseParameterType(parameter, node)); - CHECK_ERROR(ParseValueSet(node, FieldName::Metadata, false, parameter->Metadata())); - CHECK_ERROR(GetStringValueForParameter(node, FieldName::Description, parameter, &ConfigurationParameter::Description)); + CHECK_ERROR(ParseValueSet(node, ConfigurationField::Metadata, false, parameter->Metadata())); + CHECK_ERROR(GetStringValueForParameter(node, ConfigurationField::Description, parameter, &ConfigurationParameter::Description)); Windows::Foundation::PropertyType parameterType = parameter->Type(); - CHECK_ERROR(ParseObjectValueForParameter(node, FieldName::DefaultValue, parameterType, parameter, &ConfigurationParameter::DefaultValue)); + CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::DefaultValue, parameterType, parameter, &ConfigurationParameter::DefaultValue)); std::vector<Windows::Foundation::IInspectable> allowedValues; - CHECK_ERROR(ParseSequence(node, FieldName::AllowedValues, false, std::nullopt, [&](const Node& item) + CHECK_ERROR(ParseSequence(node, ConfigurationField::AllowedValues, false, std::nullopt, [&](const Node& item) { Windows::Foundation::IInspectable object; - CHECK_ERROR(ParseObject(item, FieldName::AllowedValues, parameterType, object)); + CHECK_ERROR(ParseObject(item, ConfigurationField::AllowedValues, parameterType, object)); allowedValues.emplace_back(std::move(object)); })); @@ -76,30 +76,30 @@ namespace winrt::Microsoft::Management::Configuration::implementation if (IsLengthType(parameterType)) { - CHECK_ERROR(GetUInt32ValueForParameter(node, FieldName::MinimumLength, parameter, &ConfigurationParameter::MinimumLength)); - CHECK_ERROR(GetUInt32ValueForParameter(node, FieldName::MaximumLength, parameter, &ConfigurationParameter::MaximumLength)); + CHECK_ERROR(GetUInt32ValueForParameter(node, ConfigurationField::MinimumLength, parameter, &ConfigurationParameter::MinimumLength)); + CHECK_ERROR(GetUInt32ValueForParameter(node, ConfigurationField::MaximumLength, parameter, &ConfigurationParameter::MaximumLength)); } else { - CHECK_ERROR(EnsureFieldAbsent(node, FieldName::MinimumLength)); - CHECK_ERROR(EnsureFieldAbsent(node, FieldName::MaximumLength)); + CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MinimumLength)); + CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MaximumLength)); } if (IsComparableType(parameterType)) { - CHECK_ERROR(ParseObjectValueForParameter(node, FieldName::MinimumValue, parameterType, parameter, &ConfigurationParameter::MinimumValue)); - CHECK_ERROR(ParseObjectValueForParameter(node, FieldName::MaximumValue, parameterType, parameter, &ConfigurationParameter::MaximumValue)); + CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::MinimumValue, parameterType, parameter, &ConfigurationParameter::MinimumValue)); + CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::MaximumValue, parameterType, parameter, &ConfigurationParameter::MaximumValue)); } else { - CHECK_ERROR(EnsureFieldAbsent(node, FieldName::MinimumValue)); - CHECK_ERROR(EnsureFieldAbsent(node, FieldName::MaximumValue)); + CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MinimumValue)); + CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MaximumValue)); } } void ConfigurationSetParser_0_3::ParseParameterType(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node) { - const Node& typeNode = CHECK_ERROR(GetAndEnsureField(node, FieldName::Type, true, Node::Type::Scalar)); + const Node& typeNode = CHECK_ERROR(GetAndEnsureField(node, ConfigurationField::Type, true, Node::Type::Scalar)); std::string typeValue = typeNode.as<std::string>(); if (typeValue == "string") @@ -134,7 +134,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } else { - FIELD_VALUE_ERROR(GetFieldName(FieldName::Type), typeValue, typeNode.Mark()); + FIELD_VALUE_ERROR(GetConfigurationFieldName(ConfigurationField::Type), typeValue, typeNode.Mark()); } // TODO: Consider supporting an expanded set of type strings @@ -142,7 +142,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetParser_0_3::GetStringValueForParameter( const Node& node, - FieldName field, + ConfigurationField field, ConfigurationParameter* parameter, void(ConfigurationParameter::* propertyFunction)(const hstring& value)) { @@ -156,7 +156,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetParser_0_3::GetUInt32ValueForParameter( const AppInstaller::YAML::Node& node, - FieldName field, + ConfigurationField field, ConfigurationParameter* parameter, void(ConfigurationParameter::* propertyFunction)(uint32_t value)) { @@ -167,7 +167,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation int64_t value = valueNode.as<int64_t>(); if (value < 0 || value > static_cast<int64_t>(std::numeric_limits<uint32_t>::max())) { - FIELD_VALUE_ERROR(GetFieldName(field), valueNode.as<std::string>(), valueNode.Mark()); + FIELD_VALUE_ERROR(GetConfigurationFieldName(field), valueNode.as<std::string>(), valueNode.Mark()); } (parameter->*propertyFunction)(static_cast<uint32_t>(value)); } @@ -175,7 +175,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ConfigurationSetParser_0_3::ParseObjectValueForParameter( const AppInstaller::YAML::Node& node, - FieldName field, + ConfigurationField field, Windows::Foundation::PropertyType type, ConfigurationParameter* parameter, void(ConfigurationParameter::* propertyFunction)(const Windows::Foundation::IInspectable& value)) @@ -191,7 +191,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation } } - void ConfigurationSetParser_0_3::ParseConfigurationUnitsFromField(const Node& document, FieldName field, std::vector<Configuration::ConfigurationUnit>& result) + void ConfigurationSetParser_0_3::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, std::vector<Configuration::ConfigurationUnit>& result) { ParseSequence(document, field, false, Node::Type::Mapping, [&](const Node& item) { @@ -206,14 +206,14 @@ namespace winrt::Microsoft::Management::Configuration::implementation // Set unknown intent as the new schema doesn't express it directly unit->Intent(ConfigurationUnitIntent::Unknown); - CHECK_ERROR(GetStringValueForUnit(unitNode, FieldName::Name, true, unit, &ConfigurationUnit::Identifier)); - CHECK_ERROR(GetStringValueForUnit(unitNode, FieldName::Type, true, unit, &ConfigurationUnit::Type)); - CHECK_ERROR(ParseValueSet(unitNode, FieldName::Metadata, false, unit->Metadata())); - CHECK_ERROR(ValidateType(unit, unitNode, FieldName::Type, false, true)); - CHECK_ERROR(GetStringArrayForUnit(unitNode, FieldName::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); + CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Name, true, unit, &ConfigurationUnit::Identifier)); + CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Type, true, unit, &ConfigurationUnit::Type)); + CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Metadata, false, unit->Metadata())); + CHECK_ERROR(ValidateType(unit, unitNode, ConfigurationField::Type, false, true)); + CHECK_ERROR(GetStringArrayForUnit(unitNode, ConfigurationField::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); // Regardless of being a group or not, parse the settings. - CHECK_ERROR(ParseValueSet(unitNode, FieldName::Properties, false, unit->Settings())); + CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Properties, false, unit->Settings())); if (ShouldConvertToGroup(unit)) { @@ -221,11 +221,11 @@ namespace winrt::Microsoft::Management::Configuration::implementation // TODO: The PS DSC v3 POR looks like it supports each group defining a new schema to be used for its group items. // Consider supporting that in the future; but for now just use the same schema for everything. - const Node& propertiesNode = GetAndEnsureField(unitNode, FieldName::Properties, false, Node::Type::Mapping); + const Node& propertiesNode = GetAndEnsureField(unitNode, ConfigurationField::Properties, false, Node::Type::Mapping); if (propertiesNode) { std::vector<Configuration::ConfigurationUnit> units; - CHECK_ERROR(ParseConfigurationUnitsFromField(propertiesNode, FieldName::Resources, units)); + CHECK_ERROR(ParseConfigurationUnitsFromField(propertiesNode, ConfigurationField::Resources, units)); unit->Units(std::move(units)); } } @@ -234,7 +234,7 @@ namespace winrt::Microsoft::Management::Configuration::implementation bool ConfigurationSetParser_0_3::ShouldConvertToGroup(ConfigurationUnit* unit) { // Allow the metadata to inform us that we should treat it as a group, including preventing a known type from being treated as one. - auto isGroupObject = unit->Metadata().TryLookup(GetFieldNameHString(FieldName::IsGroupMetadata)); + auto isGroupObject = unit->Metadata().TryLookup(GetConfigurationFieldNameHString(ConfigurationField::IsGroupMetadata)); if (isGroupObject) { auto isGroupProperty = isGroupObject.try_as<Windows::Foundation::IPropertyValue>(); diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h @@ -32,22 +32,22 @@ namespace winrt::Microsoft::Management::Configuration::implementation void ParseParameterType(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node); void GetStringValueForParameter( const AppInstaller::YAML::Node& node, - FieldName field, + ConfigurationField field, ConfigurationParameter* parameter, void(ConfigurationParameter::* propertyFunction)(const hstring& value)); void GetUInt32ValueForParameter( const AppInstaller::YAML::Node& node, - FieldName field, + ConfigurationField field, ConfigurationParameter* parameter, void(ConfigurationParameter::* propertyFunction)(uint32_t value)); void ParseObjectValueForParameter( const AppInstaller::YAML::Node& node, - FieldName field, + ConfigurationField field, Windows::Foundation::PropertyType type, ConfigurationParameter* parameter, void(ConfigurationParameter::* propertyFunction)(const Windows::Foundation::IInspectable& value)); - void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, FieldName field, std::vector<Configuration::ConfigurationUnit>& result); + void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, std::vector<Configuration::ConfigurationUnit>& result); virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode); // Determines if the given unit should be converted to a group. bool ShouldConvertToGroup(ConfigurationUnit* unit); diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +#include <AppInstallerLogging.h> +#include <AppInstallerVersions.h> +#include <AppInstallerStrings.h> + +#include "ConfigurationSetSerializer.h" +#include "ConfigurationSetSerializer_0_2.h" +#include "ConfigurationSetUtilities.h" + +using namespace AppInstaller::YAML; +using namespace winrt::Windows::Foundation; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + std::unique_ptr<ConfigurationSetSerializer> ConfigurationSetSerializer::CreateSerializer(hstring version) + { + // Create the parser based on the version selected + AppInstaller::Utility::SemanticVersion schemaVersion(std::move(winrt::to_string(version))); + + // TODO: Consider having the version/uri/type information all together in the future + if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 1) + { + throw E_NOTIMPL; + } + else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 2) + { + return std::make_unique<ConfigurationSetSerializer_0_2>(); + } + else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 3) + { + throw E_NOTIMPL; + } + else + { + AICLI_LOG(Config, Error, << "Unknown configuration version: " << schemaVersion.ToString()); + throw E_UNEXPECTED; + } + } + + void ConfigurationSetSerializer::WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet) + { + emitter << BeginMap; + + for (const auto& [key, value] : valueSet) + { + std::string keyName = winrt::to_string(key); + const auto& currentValueSet = value.try_as<Windows::Foundation::Collections::ValueSet>(); + + if (currentValueSet) + { + emitter << AppInstaller::YAML::Key << keyName; + WriteYamlValueSet(emitter, currentValueSet); + } + else + { + IPropertyValue property = value.as<IPropertyValue>(); + auto type = property.Type(); + + if (type == PropertyType::Boolean) + { + emitter << AppInstaller::YAML::Key << keyName << AppInstaller::YAML::Value << property.GetBoolean(); + } + else if (type == PropertyType::String) + { + emitter << AppInstaller::YAML::Key << keyName << AppInstaller::YAML::Value << AppInstaller::Utility::ConvertToUTF8(property.GetString()); + } + else if (type == PropertyType::Int64) + { + emitter << AppInstaller::YAML::Key << keyName << AppInstaller::YAML::Value << property.GetInt64(); + } + else + { + THROW_HR(E_NOTIMPL);; + } + } + } + + emitter << EndMap; + } + + void ConfigurationSetSerializer::WriteYamlConfigurationUnits(AppInstaller::YAML::Emitter& emitter, const std::vector<ConfigurationUnit>& units) + { + emitter << BeginSeq; + + for (const auto& unit : units) + { + // Resource + emitter << BeginMap; + emitter << Key << GetConfigurationFieldName(ConfigurationField::Resource) << Value << AppInstaller::Utility::ConvertToUTF8(unit.Type()); + + // Id + if (!unit.Identifier().empty()) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Id) << Value << AppInstaller::Utility::ConvertToUTF8(unit.Identifier()); + } + + // Dependencies + if (unit.Dependencies().Size() > 0) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::DependsOn); + emitter << BeginSeq; + + for (const auto& dependency : unit.Dependencies()) + { + emitter << AppInstaller::Utility::ConvertToUTF8(dependency); + } + + emitter << EndSeq; + } + + // Directives + const auto& metadata = unit.Metadata(); + emitter << Key << GetConfigurationFieldName(ConfigurationField::Directives); + WriteYamlValueSet(emitter, metadata); + + // Settings + const auto& settings = unit.Settings(); + emitter << Key << GetConfigurationFieldName(ConfigurationField::Settings); + WriteYamlValueSet(emitter, settings); + + emitter << EndMap; + } + + emitter << EndSeq; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.h b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.h @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetSerializer.h" +#include "ConfigurationSet.h" + +#include <winget/Yaml.h> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationSetSerializer + { + static std::unique_ptr<ConfigurationSetSerializer> CreateSerializer(hstring version); + + virtual ~ConfigurationSetSerializer() noexcept = default; + + ConfigurationSetSerializer(const ConfigurationSetSerializer&) = delete; + ConfigurationSetSerializer& operator=(const ConfigurationSetSerializer&) = delete; + ConfigurationSetSerializer(ConfigurationSetSerializer&&) = default; + ConfigurationSetSerializer& operator=(ConfigurationSetSerializer&&) = default; + + // Serializes a configuration set to the original yaml string. + virtual hstring Serialize(ConfigurationSet*) = 0; + + protected: + ConfigurationSetSerializer() = default; + + void WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet); + + void WriteYamlConfigurationUnits(AppInstaller::YAML::Emitter& emitter, const std::vector<ConfigurationUnit>& units); + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetSerializer_0_2.h" +#include "ConfigurationSetUtilities.h" + +#include <AppInstallerStrings.h> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + using namespace AppInstaller::YAML; + + hstring ConfigurationSetSerializer_0_2::Serialize(ConfigurationSet* configurationSet) + { + std::vector<ConfigurationUnit> assertions; + std::vector<ConfigurationUnit> resources; + + for (auto unit : configurationSet->Units()) + { + if (unit.Intent() == ConfigurationUnitIntent::Assert) + { + assertions.emplace_back(unit); + } + else if (unit.Intent() == ConfigurationUnitIntent::Apply) + { + resources.emplace_back(unit); + } + } + + Emitter emitter; + + emitter << BeginMap; + emitter << Key << GetConfigurationFieldName(ConfigurationField::Properties); + + emitter << BeginMap; + emitter << Key << GetConfigurationFieldName(ConfigurationField::ConfigurationVersion) << Value << AppInstaller::Utility::ConvertToUTF8(configurationSet->SchemaVersion()); + + if (!assertions.empty()) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Assertions); + WriteYamlConfigurationUnits(emitter, assertions); + } + + if (!resources.empty()) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Resources); + WriteYamlConfigurationUnits(emitter, resources); + } + + emitter << EndMap; + emitter << EndMap; + + return winrt::to_hstring(emitter.str()); + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.h b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetSerializer.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Serializer for schema version 0.2 + struct ConfigurationSetSerializer_0_2 : public ConfigurationSetSerializer + { + ConfigurationSetSerializer_0_2() {} + + virtual ~ConfigurationSetSerializer_0_2() noexcept = default; + + ConfigurationSetSerializer_0_2(const ConfigurationSetSerializer_0_2&) = delete; + ConfigurationSetSerializer_0_2& operator=(const ConfigurationSetSerializer_0_2&) = delete; + ConfigurationSetSerializer_0_2(ConfigurationSetSerializer_0_2&&) = default; + ConfigurationSetSerializer_0_2& operator=(ConfigurationSetSerializer_0_2&&) = default; + + hstring Serialize(ConfigurationSet* configurationSet) override; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetUtilities.h" +#include <AppInstallerStrings.h> + +using namespace std::string_view_literals; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + std::string_view GetConfigurationFieldName(ConfigurationField fieldName) + { + switch (fieldName) + { + case ConfigurationField::ConfigurationVersion: return "configurationVersion"sv; + case ConfigurationField::Properties: return "properties"sv; + case ConfigurationField::Resource: return "resource"sv; + case ConfigurationField::Directives: return "directives"sv; + case ConfigurationField::Settings: return "settings"sv; + case ConfigurationField::Assertions: return "assertions"sv; + case ConfigurationField::Id: return "id"sv; + case ConfigurationField::DependsOn: return "dependsOn"sv; + + case ConfigurationField::Resources: return "resources"sv; + case ConfigurationField::ModuleDirective: return "module"sv; + + case ConfigurationField::Schema: return "$schema"sv; + case ConfigurationField::Metadata: return "metadata"sv; + case ConfigurationField::Parameters: return "parameters"sv; + case ConfigurationField::Variables: return "variables"sv; + case ConfigurationField::Type: return "type"sv; + case ConfigurationField::Description: return "description"sv; + case ConfigurationField::Name: return "name"sv; + case ConfigurationField::IsGroupMetadata: return "isGroup"sv; + case ConfigurationField::DefaultValue: return "defaultValue"sv; + case ConfigurationField::AllowedValues: return "allowedValues"sv; + case ConfigurationField::MinimumLength: return "minLength"sv; + case ConfigurationField::MaximumLength: return "maxLength"sv; + case ConfigurationField::MinimumValue: return "minValue"sv; + case ConfigurationField::MaximumValue: return "maxValue"sv; + } + + THROW_HR(E_UNEXPECTED); + } + + hstring GetConfigurationFieldNameHString(ConfigurationField fieldName) + { + return hstring{ AppInstaller::Utility::ConvertToUTF16(GetConfigurationFieldName(fieldName)) }; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include <string_view> +#include <winrt/base.h> + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // The various configuration fields that are used in parsing/serialization. + enum class ConfigurationField + { + // v0.1 and v0.2 + ConfigurationVersion, + Properties, + Resource, + Directives, + Settings, + Assertions, + Id, + DependsOn, + + // Universal + Resources, + ModuleDirective, + + // v0.3 + Schema, + Metadata, + Parameters, + Variables, + Type, + Description, + Name, + IsGroupMetadata, + DefaultValue, + AllowedValues, + MinimumLength, + MaximumLength, + MinimumValue, + MaximumValue, + }; + + // Gets the name value of the configuration field. + std::string_view GetConfigurationFieldName(ConfigurationField fieldName); + + winrt::hstring GetConfigurationFieldNameHString(ConfigurationField fieldName); +} diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -214,6 +214,9 @@ <ClInclude Include="ConfigurationSetParser_0_1.h" /> <ClInclude Include="ConfigurationSetParser_0_2.h" /> <ClInclude Include="ConfigurationSetParser_0_3.h" /> + <ClInclude Include="ConfigurationSetSerializer.h" /> + <ClInclude Include="ConfigurationSetSerializer_0_2.h" /> + <ClInclude Include="ConfigurationSetUtilities.h" /> <ClInclude Include="ConfigurationStaticFunctions.h" /> <ClInclude Include="ConfigurationUnit.h" /> <ClInclude Include="ConfigurationUnitResultInformation.h" /> @@ -252,6 +255,9 @@ <ClCompile Include="ConfigurationSetParser_0_1.cpp" /> <ClCompile Include="ConfigurationSetParser_0_2.cpp" /> <ClCompile Include="ConfigurationSetParser_0_3.cpp" /> + <ClCompile Include="ConfigurationSetSerializer.cpp" /> + <ClCompile Include="ConfigurationSetSerializer_0_2.cpp" /> + <ClCompile Include="ConfigurationSetUtilities.cpp" /> <ClCompile Include="ConfigurationStaticFunctions.cpp" /> <ClCompile Include="ConfigurationUnit.cpp" /> <ClCompile Include="ConfigurationUnitResultInformation.cpp" /> diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -102,6 +102,15 @@ <ClCompile Include="ApplyGroupSettingsResult.cpp"> <Filter>Internals</Filter> </ClCompile> + <ClCompile Include="ConfigurationSetSerializer.cpp"> + <Filter>Parser</Filter> + </ClCompile> + <ClCompile Include="ConfigurationSetSerializer_0_2.cpp"> + <Filter>Parser</Filter> + </ClCompile> + <ClCompile Include="ConfigurationSetUtilities.cpp"> + <Filter>Parser</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ClInclude Include="pch.h" /> @@ -213,6 +222,11 @@ <ClInclude Include="ApplyGroupSettingsResult.h"> <Filter>Internals</Filter> </ClInclude> + <ClInclude Include="ConfigurationSetSerializer.h" /> + <ClInclude Include="ConfigurationSetSerializer_0_2.h" /> + <ClInclude Include="ConfigurationSetUtilities.h"> + <Filter>Parser</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Midl Include="Microsoft.Management.Configuration.idl" /> diff --git a/src/WinGetServer/WinMain.cpp b/src/WinGetServer/WinMain.cpp @@ -111,6 +111,8 @@ int __stdcall wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR cmdLine, wil::com_ptr<IGlobalOptions> globalOptions; RETURN_IF_FAILED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); RETURN_IF_FAILED(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_UNMARSHALING_POLICY, COMGLB_UNMARSHALING_POLICY_STRONG)); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY)); } RETURN_IF_FAILED(WindowsPackageManagerServerInitialize()); diff --git a/src/WindowsPackageManager/ConfigurationStaticFunctions.cpp b/src/WindowsPackageManager/ConfigurationStaticFunctions.cpp @@ -110,6 +110,10 @@ namespace ConfigurationShim { result = AppInstaller::CLI::ConfigurationRemoting::CreateOutOfProcessFactory(); } + else if (lowerHandler == AppInstaller::Configuration::DynamicRuntimeHandlerIdentifier) + { + result = AppInstaller::CLI::ConfigurationRemoting::CreateDynamicRuntimeFactory(); + } if (result) {