diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index e8869de97f..60627baee8 100644 --- 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 index 04810d5c20..3f58777d44 100644 --- 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 index ce8b5d47e1..921aaa9559 100644 --- 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 index cc301b936c..26adbfa273 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -443,6 +443,7 @@ + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index a54df43884..dde62a5482 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -248,6 +248,9 @@ Workflows + + Header Files + @@ -456,13 +459,19 @@ Commands - + Commands - + Workflows + + Source Files + + + Source Files + diff --git a/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp b/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp new file mode 100644 index 0000000000..10bac58a36 --- /dev/null +++ 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 +#include +#include + +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 + { + using ProcessorMap = std::map; + + 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(); + 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); + } + + /// + /// Creates a separate configuration set containing high integrity units and returns the serialized string value. + /// + /// Serialized string value. + 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 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>, WinRT::LifetimeWatcherBase + { + DynamicFactory() + { + m_defaultRemoteFactory = CreateOutOfProcessFactory(); + } + + IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet) + { + return winrt::make(m_defaultRemoteFactory, m_defaultRemoteFactory.CreateSetProcessor(configurationSet), configurationSet); + } + + winrt::event_token Diagnostics(const EventHandler&) + { + // 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(); + } +} diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp index 44a5dff42c..ec606883c9 100644 --- a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp +++ b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -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>, 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 { - 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 mappedMemory{ reinterpret_cast(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 - std::wostringstream argumentsStream; - argumentsStream << s_RemoteServerFileName << L' ' << reinterpret_cast(memoryHandle.get()) << L' ' << reinterpret_cast(initEvent.get()) - << L' ' << reinterpret_cast(m_completionEvent.get()) << L' ' << reinterpret_cast(thisProcessHandle.get()); - std::wstring arguments = argumentsStream.str(); + IAsyncOperation CreateConfigurationSetProcessorFactoryAsync(winrt::hstring handler) + { + // TODO: Ensure calling process has same package identity + std::wstringstream stringStream{ std::wstring{ static_cast(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 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(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>, 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(); wil::com_ptr 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(), reinterpret_cast<::IUnknown*>(winrt::get_abi(callback.as())), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); + + ULARGE_INTEGER streamSize{}; + THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); + + ULONG bufferSize = static_cast(streamSize.QuadPart); + std::vector 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 + // + // 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(), reinterpret_cast(&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(); + return winrt::make(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(memoryHandleIntPtr) }; - wil::unique_mapview_ptr mappedMemory{ reinterpret_cast(MapViewOfFile(memoryHandle.get(), FILE_MAP_WRITE, 0, 0, 0)) }; - RETURN_LAST_ERROR_IF_NULL(mappedMemory); + { + wil::com_ptr 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 stream; - RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + RETURN_HR_IF(E_POINTER, !staticsCallback); - RETURN_IF_FAILED(CoMarshalInterface(stream.get(), winrt::guid_of(), 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 stream; + RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + RETURN_IF_FAILED(stream->Write(&callbackBytes[0], static_cast(callbackBytes.size()), nullptr)); + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); - ULONG bufferSize = static_cast(streamSize.QuadPart); + wil::com_ptr<::IUnknown> output; + RETURN_IF_FAILED(CoUnmarshalInterface(stream.get(), winrt::guid_of(), reinterpret_cast(&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(initEventHandleIntPtr) }; - initEvent.SetEvent(); // Wait until the caller releases the object (signalling the event) or the parent process exits - wil::unique_event completionEvent{ reinterpret_cast(completionMutexHandleIntPtr) }; - wil::unique_process_handle parentProcess{ reinterpret_cast(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 index 78fe43bfb7..9c44521f13 100644 --- 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 index 279be1662a..58736ea4fb 100644 --- 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 #include +#include #include -#include +#include #include -#include "ConfigurationCommon.h" -#include "ConfigurationWingetDscModuleUnitValidation.h" +#include 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 index 5b7845a042..9475a32c5d 100644 --- a/src/AppInstallerCommonCore/ExperimentalFeature.cpp +++ b/src/AppInstallerCommonCore/ExperimentalFeature.cpp @@ -48,6 +48,8 @@ namespace AppInstaller::Settings return userSettings.Get(); case ExperimentalFeature::Feature::Proxy: return userSettings.Get(); + case ExperimentalFeature::Feature::ConfigureSelfElevation: + return userSettings.Get(); 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 index 00e47f419c..9c5684af2c 100644 --- 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 index fa8f1b412d..94e7f67500 100644 --- 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 index 091ba83423..926c45a110 100644 --- 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 index c763f8453b..17bb54a720 100644 --- 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 index 51ca0373c6..e04e4912ad 100644 --- 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 index 3c6d783b61..3d675211e8 100644 --- 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 index 1a7d11890e..c875c04062 100644 --- 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 @@ namespace ConfigurationRemotingServer { + /// + /// Custom assembly load context. + /// + 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) + { + } + + /// + /// Handler to resolve unmanaged assemblies. + /// + /// Assembly load context. + /// Assembly name. + /// The assembly, null if not in our assembly location. + internal static IntPtr ResolvingUnmanagedHandler(Assembly context, string name) + { + if (name.Equals("WindowsPackageManager.dll", StringComparison.OrdinalIgnoreCase)) + { + return NativeALC.LoadUnmanagedDll(name); + } + + return IntPtr.Zero; + } + + /// + 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 @@ internal class Program 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 @@ static int Main(string[] args) IObjectReference factoryInterface = MarshalInterface.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 @@ private static string GetExternalModulesPath() } [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 index aeb44ca504..c2e9375640 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -78,6 +78,29 @@ protected IInputStream CreateStream(string contents) return result; } + /// + /// Creates an string from the given output stream. + /// + /// The output stream. + /// The created string. + 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; + } + /// /// Creates a configuration unit via the configuration statics object. /// diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs index 1dc857e39f..54343194df 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs index 0a5d6019c6..5d56565aa5 100644 --- 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 @@ public void ConfigurationUnitAndProperties() ConfigurationUnitIntent testIntent = ConfigurationUnitIntent.Assert; ConfigurationUnit testUnit = this.ConfigurationUnit(); - testUnit.Type = testName; Assert.Equal(testName, testUnit.Type); testUnit.Identifier = testIdentifier; @@ -100,12 +101,33 @@ public void ConfigurationUnitAndProperties() } /// - /// 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. /// [Fact] - public void ConfigurationSetSerializeNotImplemented() + public void ConfigurationSetSerializeNestedValueSets() { - Assert.Throws(() => 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 index 2e31263bf4..db34cb2f05 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs @@ -1,4 +1,4 @@ -// ----------------------------------------------------------------------------- +// ----------------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // @@ -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; @@ -467,6 +470,79 @@ public void EmptyResourceWithModule() Assert.NotEqual(0U, result.Column); } + /// + /// Verifies that the configuration set (0.2) can be serialized and reopened correctly. + /// + [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)); + } + /// /// Test for using version 0.3 schema. /// @@ -692,6 +768,12 @@ private void VerifyValueSet(ValueSet values, params KeyValuePair 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())); + 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 index 8f7aa3f233..c120066fb2 100644 --- 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 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 index c172902a38..2397ff515d 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp @@ -10,6 +10,7 @@ #include #include +#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(); @@ -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(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetFieldName(FieldName::Schema), schemaUriString); + return std::make_unique(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(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetFieldName(FieldName::ConfigurationVersion), schemaVersion.ToString()); + return std::make_unique(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(mark.line), static_cast(mark.column)); } - std::string_view ConfigurationSetParser::GetFieldName(FieldName fieldName) + const Node& ConfigurationSetParser::GetAndEnsureField(const Node& parent, ConfigurationField field, bool required, std::optional 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 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()); + SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, GetConfigurationFieldName(field), fieldNode.Mark(), fieldNode.as()); } } - 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 operation) + void ConfigurationSetParser::ParseMapping(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, AppInstaller::YAML::Node::Type elementType, std::function 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 elementType, std::function operation) + void ConfigurationSetParser::ParseSequence(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, std::optional elementType, std::function 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::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(WINGET_CONFIG_ERROR_MISSING_FIELD, GetFieldName(FieldName::Schema)); + return std::make_unique(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(ConfigurationField::Schema)); } else if (!propertiesNode.IsMap()) { AICLI_LOG(Config, Error, << "Invalid properties type"); - return std::make_unique(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetFieldName(FieldName::Properties), propertiesNode.Mark()); + return std::make_unique(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(WINGET_CONFIG_ERROR_MISSING_FIELD, GetFieldName(FieldName::ConfigurationVersion)); + return std::make_unique(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion)); } else if (!versionNode.IsScalar()) { AICLI_LOG(Config, Error, << "Invalid configuration version type"); - return std::make_unique(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetFieldName(FieldName::ConfigurationVersion), versionNode.Mark()); + return std::make_unique(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion), versionNode.Mark()); } schemaVersionString = versionNode.as(); 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() }; - 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&& value)) + void ConfigurationSetParser::GetStringArrayForUnit(const Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector&& value)) { std::vector 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(); - 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(), node.Mark()); + FIELD_VALUE_ERROR_IF(!IsValidObjectType(object, type), GetConfigurationFieldName(fieldForErrors), node.as(), node.Mark()); result = std::move(object); } catch (...) { LOG_CAUGHT_EXCEPTION(); - FIELD_VALUE_ERROR(GetFieldName(fieldForErrors), node.as(), node.Mark()); + FIELD_VALUE_ERROR(GetConfigurationFieldName(fieldForErrors), node.as(), node.Mark()); } } } diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser.h index d12c286f06..c0fa73cee8 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser.h @@ -3,14 +3,13 @@ #pragma once #include #include +#include #include #include #include #include #include -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 type); + const AppInstaller::YAML::Node& GetAndEnsureField(const AppInstaller::YAML::Node& parent, ConfigurationField field, bool required, std::optional 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 operation); + void ParseMapping(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, AppInstaller::YAML::Node::Type elementType, std::function operation); // Parse the sequence named `field` from the given `node`. - void ParseSequence(const AppInstaller::YAML::Node& node, FieldName field, bool required, std::optional elementType, std::function operation); + void ParseSequence(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, std::optional elementType, std::function 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&& value)); + void GetStringArrayForUnit(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector&& 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 index cffbd52ae8..e679677f9e 100644 --- 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 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>(); 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& result) + void ConfigurationSetParser_0_1::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, ConfigurationUnitIntent intent, std::vector& 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 index 10383612f7..0762fef505 100644 --- 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& result); + void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, ConfigurationUnitIntent intent, std::vector& 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 index b52fa9e779..8b2234fae7 100644 --- 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 index 394504c884..7ef8162286 100644 --- 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>(); - 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 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 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>(); 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 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(); 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(); if (value < 0 || value > static_cast(std::numeric_limits::max())) { - FIELD_VALUE_ERROR(GetFieldName(field), valueNode.as(), valueNode.Mark()); + FIELD_VALUE_ERROR(GetConfigurationFieldName(field), valueNode.as(), valueNode.Mark()); } (parameter->*propertyFunction)(static_cast(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& result) + void ConfigurationSetParser_0_3::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, std::vector& 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 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(); diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h index 8f29ebc47d..60b8430b05 100644 --- 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& result); + void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, std::vector& 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 new file mode 100644 index 0000000000..2107fdef17 --- /dev/null +++ b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +#include +#include +#include + +#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::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(); + } + 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(); + + if (currentValueSet) + { + emitter << AppInstaller::YAML::Key << keyName; + WriteYamlValueSet(emitter, currentValueSet); + } + else + { + IPropertyValue property = value.as(); + 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& 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 new file mode 100644 index 0000000000..bf522172c4 --- /dev/null +++ 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 + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationSetSerializer + { + static std::unique_ptr 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& units); + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp new file mode 100644 index 0000000000..5631e1d2e3 --- /dev/null +++ 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 + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + using namespace AppInstaller::YAML; + + hstring ConfigurationSetSerializer_0_2::Serialize(ConfigurationSet* configurationSet) + { + std::vector assertions; + std::vector 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 new file mode 100644 index 0000000000..c7034215d9 --- /dev/null +++ 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 new file mode 100644 index 0000000000..80c64a732f --- /dev/null +++ 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 + +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 new file mode 100644 index 0000000000..85a0f27ebc --- /dev/null +++ b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +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 index feddee7735..7dd3b9d52e 100644 --- a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj +++ b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -214,6 +214,9 @@ + + + @@ -252,6 +255,9 @@ + + + diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters index 16905780ad..ed58521881 100644 --- a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters +++ b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -102,6 +102,15 @@ Internals + + Parser + + + Parser + + + Parser + @@ -213,6 +222,11 @@ Internals + + + + Parser + diff --git a/src/WinGetServer/WinMain.cpp b/src/WinGetServer/WinMain.cpp index 541a13deb4..11fe1a3749 100644 --- 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 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 index d45a662acd..0e2a9a2151 100644 --- 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) {