From 610e340921ab85ffabb4ed422b9ca41996508700 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 20 Nov 2025 03:33:49 +0100 Subject: [PATCH 1/2] Enhance ValidationViewModel with detailed state comparison and YAML output for set --- .../ViewModels/ValidationViewModel.cs | 153 +++++++++++++++++- 1 file changed, 151 insertions(+), 2 deletions(-) diff --git a/src/WinGetStudio/ViewModels/ValidationViewModel.cs b/src/WinGetStudio/ViewModels/ValidationViewModel.cs index a8db826..ab07d55 100644 --- a/src/WinGetStudio/ViewModels/ValidationViewModel.cs +++ b/src/WinGetStudio/ViewModels/ValidationViewModel.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +using System.Linq; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using Microsoft.Extensions.Localization; @@ -95,11 +96,159 @@ private async Task OnSetAsync() { await RunDscOperationAsync(async (dscUnit) => { - var result = await _dsc.SetUnitAsync(dscUnit); - return result.ResultInformation; + var inputSettings = dscUnit.Settings.DeepCopy(); + var setResult = await _dsc.SetUnitAsync(dscUnit); + var afterResult = await _dsc.GetUnitAsync(dscUnit); + + // Merge input with actual state for a complete "before" view + // Note: This works well for simple properties but has limitations with deeply nested objects + var beforeSettings = MergeSettings(inputSettings, afterResult.Settings); + var beforeState = beforeSettings.ToYaml().TrimEnd(); + var afterState = afterResult.Settings.ToYaml().TrimEnd(); + + var action = setResult.PreviouslyInDesiredState ? "None" : "Partial"; + + var changedProperties = GetChangedProperties(beforeSettings, afterResult.Settings); + + // Build similar output as DSC + var changedPropsStr = changedProperties.Count == 0 + ? "[]" + : Environment.NewLine + string.Join(Environment.NewLine, changedProperties.Select(p => $"- {p}")); + + var output = $"beforeState:{IndentYaml(beforeState, 2)}{Environment.NewLine}afterState:{IndentYaml(afterState, 2)}{Environment.NewLine}action: {action}{Environment.NewLine}changedProperties: {changedPropsStr}"; + + if (setResult.ResultInformation?.IsOk ?? true) + { + OutputText = output; + } + + return setResult.ResultInformation; }); } + /// + /// Merges input settings with actual state to create a complete "before" view. + /// This overlays input values on top of the actual state. + /// Note: Deep merging of nested DSCPropertySet objects is complex and may not always + /// produce accurate results when the input contains partial nested objects. + /// This works best for resources with simple, flat property structures. + /// + private DSCPropertySet MergeSettings(DSCPropertySet input, DSCPropertySet actual) + { + var merged = new DSCPropertySet(); + + // Copy all properties from actual state + foreach (var kvp in actual) + { + merged[kvp.Key] = kvp.Value is DSCPropertySet nested ? nested.DeepCopy() : kvp.Value; + } + + // Overlay with input values (this may overwrite entire nested objects) + foreach (var kvp in input) + { + merged[kvp.Key] = kvp.Value is DSCPropertySet nested ? nested.DeepCopy() : kvp.Value; + } + + return merged; + } + + /// + /// Indents YAML content by the specified number of spaces. + /// + private string IndentYaml(string yaml, int spaces) + { + var indent = new string(' ', spaces); + var lines = yaml.Split(["\r\n", "\r", "\n"], StringSplitOptions.None); + var indented = string.Join(Environment.NewLine, lines.Select(line => string.IsNullOrWhiteSpace(line) ? line : indent + line)); + return Environment.NewLine + indented; + } + + /// + /// Compares two property sets and returns a list of changed property names. + /// + private List GetChangedProperties(DSCPropertySet before, DSCPropertySet after) + { + var changedProperties = new List(); + + // Check for modified or added properties + foreach (var kvp in after) + { + if (!before.TryGetValue(kvp.Key, out var beforeValue) || !AreValuesEqual(beforeValue, kvp.Value)) + { + changedProperties.Add(kvp.Key); + } + } + + // Check for removed properties + foreach (var kvp in before) + { + if (!after.ContainsKey(kvp.Key)) + { + changedProperties.Add(kvp.Key); + } + } + + return changedProperties; + } + + /// + /// Compares two values for equality, handling nested objects. + /// + private bool AreValuesEqual(object value1, object value2) + { + if (value1 == null && value2 == null) + { + return true; + } + + if (value1 == null || value2 == null) + { + return false; + } + + if (value1 is DSCPropertySet dict1 && value2 is DSCPropertySet dict2) + { + if (dict1.Count != dict2.Count) + { + return false; + } + + return dict1.All(kvp => dict2.TryGetValue(kvp.Key, out var v2) && AreValuesEqual(kvp.Value, v2)); + } + + // Handle collections (but not strings or dictionaries) + if (value1 is not string && value2 is not string && + value1 is not DSCPropertySet && value2 is not DSCPropertySet) + { + try + { + if (value1 is System.Collections.IList list1 && value2 is System.Collections.IList list2) + { + if (list1.Count != list2.Count) + { + return false; + } + + for (int i = 0; i < list1.Count; i++) + { + if (!AreValuesEqual(list1[i]!, list2[i]!)) + { + return false; + } + } + + return true; + } + } + catch + { + // If enumeration fails, fall back to simple equality + } + } + + return value1.Equals(value2); + } + /// /// Tests whether the current machine state matches the specified configuration unit asynchronously. /// From fb9e2ca368a8a9a5d68fe404052223b91965b020 Mon Sep 17 00:00:00 2001 From: "G.Reijn" Date: Thu, 20 Nov 2025 03:38:33 +0100 Subject: [PATCH 2/2] Fix spelling --- .github/actions/spelling/allow.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt index ec00346..3ebd0ab 100644 --- a/.github/actions/spelling/allow.txt +++ b/.github/actions/spelling/allow.txt @@ -461,3 +461,4 @@ Xlang XResource XTOKEN xunit +kvp