From a7e902163a2c09beafa4380c27ab3cea62f2c78a Mon Sep 17 00:00:00 2001
From: Muhammad Danish <88161975+mdanish-kh@users.noreply.github.com>
Date: Sat, 24 Oct 2026 02:39:10 +0500
Subject: [PATCH 1/3] Bump manifest schema version to 1.28.0
---
Directory.Packages.props | 2 +-
src/WingetCreateCLI/Commands/BaseCommand.cs | 2 +-
src/WingetCreateCLI/Commands/NewCommand.cs | 2 +-
src/WingetCreateCLI/Commands/ShowCommand.cs | 2 +-
src/WingetCreateCLI/Commands/SubmitCommand.cs | 2 +-
src/WingetCreateCLI/Commands/UpdateCommand.cs | 8 +-
src/WingetCreateCLI/PromptHelper.cs | 2 +-
src/WingetCreateCLI/Properties/Resources.resx | 21 +++
src/WingetCreateCLI/WingetCreateCLI.csproj | 2 +-
.../Models/DefaultLocaleManifestModels.cs | 4 +-
.../Models/InstallerManifestModels.cs | 152 ++++++++++++++-
.../Models/LocaleManifestModels.cs | 4 +-
.../Models/Partials/InstallerPartials.cs | 140 ++++++++++++++
.../Models/SingletonManifestModels.cs | 152 ++++++++++++++-
.../Models/VersionManifestModels.cs | 4 +-
src/WingetCreateCore/WingetCreateCore.csproj | 10 +-
...blisher.DontMoveInstallerFieldsToRoot.yaml | 44 ++++-
.../TestPublisher.FullJsonSingleton1_28.json | 177 ++++++++++++++++++
.../TestPublisher.FullYamlSingleton1_28.yaml | 125 +++++++++++++
...stPublisher.MoveInstallerFieldsToRoot.yaml | 44 ++++-
.../UnitTests/LocalizationTests.cs | 2 +-
.../UnitTests/UpdateCommandTests.cs | 35 +++-
.../WingetCreateTests.csproj | 6 +
23 files changed, 915 insertions(+), 27 deletions(-)
create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_28.json
create mode 100644 src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_28.yaml
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 77bb574b..608ddd61 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -12,7 +12,7 @@
-
+
diff --git a/src/WingetCreateCLI/Commands/BaseCommand.cs b/src/WingetCreateCLI/Commands/BaseCommand.cs
index e987b1c3..e9003ef5 100644
--- a/src/WingetCreateCLI/Commands/BaseCommand.cs
+++ b/src/WingetCreateCLI/Commands/BaseCommand.cs
@@ -15,7 +15,6 @@ namespace Microsoft.WingetCreateCLI.Commands
using System.Threading.Tasks;
using Microsoft.WingetCreateCLI.Logging;
using Microsoft.WingetCreateCLI.Models.Settings;
- using Microsoft.WingetCreateCLI.Properties;
using Microsoft.WingetCreateCLI.Telemetry;
using Microsoft.WingetCreateCLI.Telemetry.Events;
using Microsoft.WingetCreateCore;
@@ -30,6 +29,7 @@ namespace Microsoft.WingetCreateCLI.Commands
using Octokit;
using RestSharp;
using Sharprompt;
+ using Resources = Microsoft.WingetCreateCLI.Properties.Resources;
///
/// Abstract base command class that all commands inherit from.
diff --git a/src/WingetCreateCLI/Commands/NewCommand.cs b/src/WingetCreateCLI/Commands/NewCommand.cs
index 829fc237..a50f37ee 100644
--- a/src/WingetCreateCLI/Commands/NewCommand.cs
+++ b/src/WingetCreateCLI/Commands/NewCommand.cs
@@ -15,7 +15,6 @@ namespace Microsoft.WingetCreateCLI.Commands
using CommandLine.Text;
using Microsoft.WingetCreateCLI.Logging;
using Microsoft.WingetCreateCLI.Models.Settings;
- using Microsoft.WingetCreateCLI.Properties;
using Microsoft.WingetCreateCLI.Telemetry;
using Microsoft.WingetCreateCLI.Telemetry.Events;
using Microsoft.WingetCreateCore;
@@ -27,6 +26,7 @@ namespace Microsoft.WingetCreateCLI.Commands
using Microsoft.WingetCreateCore.Models.Version;
using Newtonsoft.Json.Schema.Generation;
using Sharprompt;
+ using Resources = Microsoft.WingetCreateCLI.Properties.Resources;
///
/// Command to launch a wizard that prompt users for information to generate a new manifest.
diff --git a/src/WingetCreateCLI/Commands/ShowCommand.cs b/src/WingetCreateCLI/Commands/ShowCommand.cs
index 555f68ec..548aa8d2 100644
--- a/src/WingetCreateCLI/Commands/ShowCommand.cs
+++ b/src/WingetCreateCLI/Commands/ShowCommand.cs
@@ -10,7 +10,6 @@ namespace Microsoft.WingetCreateCLI.Commands
using CommandLine.Text;
using Microsoft.WingetCreateCLI.Logging;
using Microsoft.WingetCreateCLI.Models.Settings;
- using Microsoft.WingetCreateCLI.Properties;
using Microsoft.WingetCreateCLI.Telemetry;
using Microsoft.WingetCreateCLI.Telemetry.Events;
using Microsoft.WingetCreateCore;
@@ -18,6 +17,7 @@ namespace Microsoft.WingetCreateCLI.Commands
using Microsoft.WingetCreateCore.Models.Installer;
using Microsoft.WingetCreateCore.Models.Singleton;
using Microsoft.WingetCreateCore.Models.Version;
+ using Resources = Microsoft.WingetCreateCLI.Properties.Resources;
///
/// Show command to display manifest from the packages repository.
diff --git a/src/WingetCreateCLI/Commands/SubmitCommand.cs b/src/WingetCreateCLI/Commands/SubmitCommand.cs
index 81d248b0..45738217 100644
--- a/src/WingetCreateCLI/Commands/SubmitCommand.cs
+++ b/src/WingetCreateCLI/Commands/SubmitCommand.cs
@@ -11,12 +11,12 @@ namespace Microsoft.WingetCreateCLI.Commands
using CommandLine.Text;
using Microsoft.VisualBasic;
using Microsoft.WingetCreateCLI.Logging;
- using Microsoft.WingetCreateCLI.Properties;
using Microsoft.WingetCreateCLI.Telemetry;
using Microsoft.WingetCreateCLI.Telemetry.Events;
using Microsoft.WingetCreateCore;
using Microsoft.WingetCreateCore.Models;
using Microsoft.WingetCreateCore.Models.Singleton;
+ using Resources = Microsoft.WingetCreateCLI.Properties.Resources;
///
/// Command to validate and submit a manifest to the GitHub Windows Package Manager repository.
diff --git a/src/WingetCreateCLI/Commands/UpdateCommand.cs b/src/WingetCreateCLI/Commands/UpdateCommand.cs
index bb208ec3..e063c6cd 100644
--- a/src/WingetCreateCLI/Commands/UpdateCommand.cs
+++ b/src/WingetCreateCLI/Commands/UpdateCommand.cs
@@ -15,7 +15,6 @@ namespace Microsoft.WingetCreateCLI.Commands
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.WingetCreateCLI.Logging;
using Microsoft.WingetCreateCLI.Models.Settings;
- using Microsoft.WingetCreateCLI.Properties;
using Microsoft.WingetCreateCLI.Telemetry;
using Microsoft.WingetCreateCLI.Telemetry.Events;
using Microsoft.WingetCreateCore;
@@ -27,6 +26,7 @@ namespace Microsoft.WingetCreateCLI.Commands
using Microsoft.WingetCreateCore.Models.Locale;
using Microsoft.WingetCreateCore.Models.Version;
using Sharprompt;
+ using Resources = Microsoft.WingetCreateCLI.Properties.Resources;
///
/// Command for updating the elements of an existing or local manifest.
@@ -640,6 +640,12 @@ private static Manifests ConvertSingletonToMultifileManifest(WingetCreateCore.Mo
cfg.CreateMap();
cfg.CreateMap();
cfg.CreateMap();
+ cfg.CreateMap();
+ cfg.CreateMap();
+ cfg.CreateMap();
+ cfg.CreateMap();
+ cfg.CreateMap();
+ cfg.CreateMap();
},
NullLoggerFactory.Instance);
var mapper = config.CreateMapper();
diff --git a/src/WingetCreateCLI/PromptHelper.cs b/src/WingetCreateCLI/PromptHelper.cs
index 6b221969..5a04e677 100644
--- a/src/WingetCreateCLI/PromptHelper.cs
+++ b/src/WingetCreateCLI/PromptHelper.cs
@@ -10,12 +10,12 @@ namespace Microsoft.WingetCreateCLI
using System.Linq;
using System.Reflection;
using Microsoft.WingetCreateCLI.Logging;
- using Microsoft.WingetCreateCLI.Properties;
using Microsoft.WingetCreateCore;
using Microsoft.WingetCreateCore.Common;
using Microsoft.WingetCreateCore.Models.DefaultLocale;
using Microsoft.WingetCreateCore.Models.Installer;
using Sharprompt;
+ using Resources = Microsoft.WingetCreateCLI.Properties.Resources;
///
/// Provides functionality for prompting the user for input to obtain field values for a manifest.
diff --git a/src/WingetCreateCLI/Properties/Resources.resx b/src/WingetCreateCLI/Properties/Resources.resx
index a264a300..dad5ef02 100644
--- a/src/WingetCreateCLI/Properties/Resources.resx
+++ b/src/WingetCreateCLI/Properties/Resources.resx
@@ -1486,4 +1486,25 @@ Warning: Using this argument may result in the token being logged. Consider an a
Found mixed font and non-font installer types in the manifest. This is not a supported configuration and the manifest should be edited to contain only font or non-font installers before submission.
+
+ Desired state configuration (DSC) resources related to the package
+
+
+ DSC resources contained in PowerShell modules
+
+
+ The URL of the repository containing the PowerShell module
+
+
+ The name of the PowerShell module containing DSC resources
+
+
+ The DSC resources contained in the module or package
+
+
+ DSC resources contained in the package using the DSC v3 specification
+
+
+ The fully qualified type of the DSC resource
+
\ No newline at end of file
diff --git a/src/WingetCreateCLI/WingetCreateCLI.csproj b/src/WingetCreateCLI/WingetCreateCLI.csproj
index 93cd08f6..6f5b8f00 100644
--- a/src/WingetCreateCLI/WingetCreateCLI.csproj
+++ b/src/WingetCreateCLI/WingetCreateCLI.csproj
@@ -5,7 +5,7 @@
net9.0-windows10.0.26100.0
WingetCreateCLI
Microsoft.WingetCreateCLI
- 1.12
+ 1.28
x64;x86
win-x64;win-x86
true
diff --git a/src/WingetCreateCore/Models/DefaultLocaleManifestModels.cs b/src/WingetCreateCore/Models/DefaultLocaleManifestModels.cs
index 71a33484..7105ed2b 100644
--- a/src/WingetCreateCore/Models/DefaultLocaleManifestModels.cs
+++ b/src/WingetCreateCore/Models/DefaultLocaleManifestModels.cs
@@ -133,7 +133,7 @@ public System.Collections.Generic.IDictionary AdditionalProperti
}
///
- /// A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.12.0
+ /// A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.28.0
///
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class DefaultLocaleManifest
@@ -335,7 +335,7 @@ public partial class DefaultLocaleManifest
[Newtonsoft.Json.JsonProperty("ManifestVersion", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
[System.ComponentModel.DataAnnotations.RegularExpression(@"^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$")]
- public string ManifestVersion { get; set; } = "1.12.0";
+ public string ManifestVersion { get; set; } = "1.28.0";
diff --git a/src/WingetCreateCore/Models/InstallerManifestModels.cs b/src/WingetCreateCore/Models/InstallerManifestModels.cs
index c5229c24..00e15135 100644
--- a/src/WingetCreateCore/Models/InstallerManifestModels.cs
+++ b/src/WingetCreateCore/Models/InstallerManifestModels.cs
@@ -466,6 +466,38 @@ public partial class Authentication
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
+ ///
+ /// References to desired state configuration (DSC) resources that are related to the package.
+ ///
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class DesiredStateConfiguration
+ {
+ ///
+ /// Contains data about DSC resources that are contained in PowerShell modules.
+ ///
+ [Newtonsoft.Json.JsonProperty("PowerShell", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.MaxLength(16)]
+ public System.Collections.Generic.List PowerShell { get; set; }
+
+ ///
+ /// Contains data about DSC resources that are contained in the package using the DSC v3 specification.
+ ///
+ [Newtonsoft.Json.JsonProperty("DSCv3", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public DSCv3 DSCv3 { get; set; }
+
+
+
private System.Collections.Generic.IDictionary _additionalProperties;
[Newtonsoft.Json.JsonExtensionData]
@@ -638,6 +670,9 @@ public partial class Installer
[Newtonsoft.Json.JsonProperty("Authentication", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public Authentication Authentication { get; set; }
+ [Newtonsoft.Json.JsonProperty("DesiredStateConfiguration", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public DesiredStateConfiguration DesiredStateConfiguration { get; set; }
+
private System.Collections.Generic.IDictionary _additionalProperties;
@@ -652,7 +687,7 @@ public System.Collections.Generic.IDictionary AdditionalProperti
}
///
- /// A representation of a single-file manifest representing an app installers in the OWC. v1.12.0
+ /// A representation of a single-file manifest representing an app installers in the OWC. v1.28.0
///
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class InstallerManifest
@@ -802,6 +837,9 @@ public partial class InstallerManifest
[Newtonsoft.Json.JsonProperty("Authentication", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public Authentication Authentication { get; set; }
+ [Newtonsoft.Json.JsonProperty("DesiredStateConfiguration", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public DesiredStateConfiguration DesiredStateConfiguration { get; set; }
+
[Newtonsoft.Json.JsonProperty("Installers", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[System.ComponentModel.DataAnnotations.Required]
[System.ComponentModel.DataAnnotations.MinLength(1)]
@@ -821,7 +859,7 @@ public partial class InstallerManifest
[Newtonsoft.Json.JsonProperty("ManifestVersion", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
[System.ComponentModel.DataAnnotations.RegularExpression(@"^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$")]
- public string ManifestVersion { get; set; } = "1.12.0";
+ public string ManifestVersion { get; set; } = "1.28.0";
@@ -955,6 +993,68 @@ public partial class MicrosoftEntraIdAuthenticationInfo
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class PowerShell
+ {
+ [Newtonsoft.Json.JsonProperty("RepositoryUrl", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.StringLength(2048)]
+ [System.ComponentModel.DataAnnotations.RegularExpression(@"^([Hh][Tt][Tt][Pp][Ss]?)://.+$")]
+ public string RepositoryUrl { get; set; }
+
+ ///
+ /// The name of the module containing resources.
+ ///
+ [Newtonsoft.Json.JsonProperty("ModuleName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ [System.ComponentModel.DataAnnotations.StringLength(100)]
+ [System.ComponentModel.DataAnnotations.RegularExpression(@"^\w+([.-]\w+)*$")]
+ public string ModuleName { get; set; }
+
+ ///
+ /// The resources contained within the module.
+ ///
+ [Newtonsoft.Json.JsonProperty("Resources", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.Required]
+ [System.ComponentModel.DataAnnotations.MaxLength(64)]
+ public System.Collections.Generic.List Resources { get; set; } = new System.Collections.Generic.List();
+
+
+
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class DSCv3
+ {
+ ///
+ /// The resources contained within the package.
+ ///
+ [Newtonsoft.Json.JsonProperty("Resources", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.Required]
+ [System.ComponentModel.DataAnnotations.MaxLength(128)]
+ public System.Collections.Generic.List Resources { get; set; } = new System.Collections.Generic.List();
+
+
+
private System.Collections.Generic.IDictionary _additionalProperties;
[Newtonsoft.Json.JsonExtensionData]
@@ -1119,6 +1219,54 @@ public enum FilesFileType
}
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class Resources
+ {
+ ///
+ /// The name of the resource.
+ ///
+ [Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.StringLength(100)]
+ [System.ComponentModel.DataAnnotations.RegularExpression(@"^[A-Za-z][-_A-Za-z0-9]*$")]
+ public string Name { get; set; }
+
+
+
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class Resources2
+ {
+ ///
+ /// The name of the resource.
+ ///
+ [Newtonsoft.Json.JsonProperty("Type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.StringLength(256)]
+ [System.ComponentModel.DataAnnotations.RegularExpression(@"^\w+(\.\w+){0,2}\/\w+$")]
+ public string Type { get; set; }
+
+
+
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
public enum ExpectedReturnCodeReturnResponse
{
diff --git a/src/WingetCreateCore/Models/LocaleManifestModels.cs b/src/WingetCreateCore/Models/LocaleManifestModels.cs
index 35f9ceeb..15263e40 100644
--- a/src/WingetCreateCore/Models/LocaleManifestModels.cs
+++ b/src/WingetCreateCore/Models/LocaleManifestModels.cs
@@ -133,7 +133,7 @@ public System.Collections.Generic.IDictionary AdditionalProperti
}
///
- /// A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.12.0
+ /// A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.28.0
///
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class LocaleManifest
@@ -324,7 +324,7 @@ public partial class LocaleManifest
[Newtonsoft.Json.JsonProperty("ManifestVersion", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
[System.ComponentModel.DataAnnotations.RegularExpression(@"^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$")]
- public string ManifestVersion { get; set; } = "1.12.0";
+ public string ManifestVersion { get; set; } = "1.28.0";
diff --git a/src/WingetCreateCore/Models/Partials/InstallerPartials.cs b/src/WingetCreateCore/Models/Partials/InstallerPartials.cs
index 5b30845b..54a382d1 100644
--- a/src/WingetCreateCore/Models/Partials/InstallerPartials.cs
+++ b/src/WingetCreateCore/Models/Partials/InstallerPartials.cs
@@ -231,6 +231,146 @@ public override bool Equals(object obj)
}
}
+ ///
+ /// Partial class that implements helper methods for the Authentication class.
+ ///
+#pragma warning disable SA1402 // File may only contain a single type
+ public partial class Authentication
+#pragma warning restore SA1402 // File may only contain a single type
+ {
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj == null || this.GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ Authentication other = (Authentication)obj;
+ return this.AuthenticationType == other.AuthenticationType && Equals(this.MicrosoftEntraIdAuthenticationInfo, other.MicrosoftEntraIdAuthenticationInfo);
+ }
+ }
+
+ ///
+ /// Partial class that implements helper methods for the MicrosoftEntraIdAuthenticationInfo class.
+ ///
+#pragma warning disable SA1402 // File may only contain a single type
+ public partial class MicrosoftEntraIdAuthenticationInfo
+#pragma warning restore SA1402 // File may only contain a single type
+ {
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj == null || this.GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ MicrosoftEntraIdAuthenticationInfo other = (MicrosoftEntraIdAuthenticationInfo)obj;
+ return this.Resource == other.Resource && this.Scope == other.Scope;
+ }
+ }
+
+ ///
+ /// Partial class that implements helper methods for the DesiredStateConfiguration class.
+ ///
+#pragma warning disable SA1402 // File may only contain a single type
+ public partial class DesiredStateConfiguration
+#pragma warning restore SA1402 // File may only contain a single type
+ {
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj == null || this.GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ DesiredStateConfiguration other = (DesiredStateConfiguration)obj;
+ return (this.PowerShell == null ? other.PowerShell == null : other.PowerShell != null && this.PowerShell.SequenceEqual(other.PowerShell)) && Equals(this.DSCv3, other.DSCv3);
+ }
+ }
+
+ ///
+ /// Partial class that implements helper methods for the PowerShell class.
+ ///
+#pragma warning disable SA1402 // File may only contain a single type
+ public partial class PowerShell
+#pragma warning restore SA1402 // File may only contain a single type
+ {
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj == null || this.GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ PowerShell other = (PowerShell)obj;
+ return this.RepositoryUrl == other.RepositoryUrl && this.ModuleName == other.ModuleName && (this.Resources == null ? other.Resources == null : other.Resources != null && this.Resources.SequenceEqual(other.Resources));
+ }
+ }
+
+ ///
+ /// Partial class that implements helper methods for the DSCv3 class.
+ ///
+#pragma warning disable SA1402 // File may only contain a single type
+ public partial class DSCv3
+#pragma warning restore SA1402 // File may only contain a single type
+ {
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj == null || this.GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ DSCv3 other = (DSCv3)obj;
+ return this.Resources == null ? other.Resources == null : other.Resources != null && this.Resources.SequenceEqual(other.Resources);
+ }
+ }
+
+ ///
+ /// Partial class that implements helper methods for the Resources class.
+ ///
+#pragma warning disable SA1402 // File may only contain a single type
+ public partial class Resources
+#pragma warning restore SA1402 // File may only contain a single type
+ {
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj == null || this.GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ Resources other = (Resources)obj;
+ return this.Name == other.Name;
+ }
+ }
+
+ ///
+ /// Partial class that implements helper methods for the Resources2 class.
+ ///
+#pragma warning disable SA1402 // File may only contain a single type
+ public partial class Resources2
+#pragma warning restore SA1402 // File may only contain a single type
+ {
+ ///
+ public override bool Equals(object obj)
+ {
+ if (obj == null || this.GetType() != obj.GetType())
+ {
+ return false;
+ }
+
+ Resources2 other = (Resources2)obj;
+ return this.Type == other.Type;
+ }
+ }
+
///
/// Partial InstallerManifest class for defining a string type ReleaseDateTimeField.
/// Workaround for issue with model generating ReleaseDate with DateTimeOffset type.
diff --git a/src/WingetCreateCore/Models/SingletonManifestModels.cs b/src/WingetCreateCore/Models/SingletonManifestModels.cs
index 4317e924..1c9db8c2 100644
--- a/src/WingetCreateCore/Models/SingletonManifestModels.cs
+++ b/src/WingetCreateCore/Models/SingletonManifestModels.cs
@@ -589,6 +589,38 @@ public partial class Authentication
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
+ ///
+ /// References to desired state configuration (DSC) resources that are related to the package.
+ ///
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class DesiredStateConfiguration
+ {
+ ///
+ /// Contains data about DSC resources that are contained in PowerShell modules.
+ ///
+ [Newtonsoft.Json.JsonProperty("PowerShell", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.MaxLength(16)]
+ public System.Collections.Generic.List PowerShell { get; set; }
+
+ ///
+ /// Contains data about DSC resources that are contained in the package using the DSC v3 specification.
+ ///
+ [Newtonsoft.Json.JsonProperty("DSCv3", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public DSCv3 DSCv3 { get; set; }
+
+
+
private System.Collections.Generic.IDictionary _additionalProperties;
[Newtonsoft.Json.JsonExtensionData]
@@ -761,6 +793,9 @@ public partial class Installer
[Newtonsoft.Json.JsonProperty("Authentication", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public Authentication Authentication { get; set; }
+ [Newtonsoft.Json.JsonProperty("DesiredStateConfiguration", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public DesiredStateConfiguration DesiredStateConfiguration { get; set; }
+
private System.Collections.Generic.IDictionary _additionalProperties;
@@ -775,7 +810,7 @@ public System.Collections.Generic.IDictionary AdditionalProperti
}
///
- /// A representation of a single-file manifest representing an app in the OWC. v1.12.0
+ /// A representation of a single-file manifest representing an app in the OWC. v1.28.0
///
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class SingletonManifest
@@ -1087,6 +1122,9 @@ public partial class SingletonManifest
[Newtonsoft.Json.JsonProperty("Authentication", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
public Authentication Authentication { get; set; }
+ [Newtonsoft.Json.JsonProperty("DesiredStateConfiguration", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ public DesiredStateConfiguration DesiredStateConfiguration { get; set; }
+
[Newtonsoft.Json.JsonProperty("Installers", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[System.ComponentModel.DataAnnotations.Required]
[System.ComponentModel.DataAnnotations.MinLength(1)]
@@ -1106,7 +1144,7 @@ public partial class SingletonManifest
[Newtonsoft.Json.JsonProperty("ManifestVersion", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
[System.ComponentModel.DataAnnotations.RegularExpression(@"^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$")]
- public string ManifestVersion { get; set; } = "1.12.0";
+ public string ManifestVersion { get; set; } = "1.28.0";
@@ -1346,6 +1384,68 @@ public partial class MicrosoftEntraIdAuthenticationInfo
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class PowerShell
+ {
+ [Newtonsoft.Json.JsonProperty("RepositoryUrl", Required = Newtonsoft.Json.Required.Default, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.StringLength(2048)]
+ [System.ComponentModel.DataAnnotations.RegularExpression(@"^([Hh][Tt][Tt][Pp][Ss]?)://.+$")]
+ public string RepositoryUrl { get; set; }
+
+ ///
+ /// The name of the module containing resources.
+ ///
+ [Newtonsoft.Json.JsonProperty("ModuleName", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ [System.ComponentModel.DataAnnotations.StringLength(100)]
+ [System.ComponentModel.DataAnnotations.RegularExpression(@"^\w+([.-]\w+)*$")]
+ public string ModuleName { get; set; }
+
+ ///
+ /// The resources contained within the module.
+ ///
+ [Newtonsoft.Json.JsonProperty("Resources", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.Required]
+ [System.ComponentModel.DataAnnotations.MaxLength(64)]
+ public System.Collections.Generic.List Resources { get; set; } = new System.Collections.Generic.List();
+
+
+
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class DSCv3
+ {
+ ///
+ /// The resources contained within the package.
+ ///
+ [Newtonsoft.Json.JsonProperty("Resources", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.Required]
+ [System.ComponentModel.DataAnnotations.MaxLength(128)]
+ public System.Collections.Generic.List Resources { get; set; } = new System.Collections.Generic.List();
+
+
+
private System.Collections.Generic.IDictionary _additionalProperties;
[Newtonsoft.Json.JsonExtensionData]
@@ -1510,6 +1610,54 @@ public enum FilesFileType
}
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class Resources
+ {
+ ///
+ /// The name of the resource.
+ ///
+ [Newtonsoft.Json.JsonProperty("Name", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.StringLength(100)]
+ [System.ComponentModel.DataAnnotations.RegularExpression(@"^[A-Za-z][-_A-Za-z0-9]*$")]
+ public string Name { get; set; }
+
+
+
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
+ public partial class Resources2
+ {
+ ///
+ /// The name of the resource.
+ ///
+ [Newtonsoft.Json.JsonProperty("Type", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
+ [System.ComponentModel.DataAnnotations.StringLength(256)]
+ [System.ComponentModel.DataAnnotations.RegularExpression(@"^\w+(\.\w+){0,2}\/\w+$")]
+ public string Type { get; set; }
+
+
+
+ private System.Collections.Generic.IDictionary _additionalProperties;
+
+ [Newtonsoft.Json.JsonExtensionData]
+ public System.Collections.Generic.IDictionary AdditionalProperties
+ {
+ get { return _additionalProperties ?? (_additionalProperties = new System.Collections.Generic.Dictionary()); }
+ set { _additionalProperties = value; }
+ }
+
+ }
+
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
public enum ExpectedReturnCodeReturnResponse
{
diff --git a/src/WingetCreateCore/Models/VersionManifestModels.cs b/src/WingetCreateCore/Models/VersionManifestModels.cs
index f0b8bf64..f6493030 100644
--- a/src/WingetCreateCore/Models/VersionManifestModels.cs
+++ b/src/WingetCreateCore/Models/VersionManifestModels.cs
@@ -10,7 +10,7 @@ namespace Microsoft.WingetCreateCore.Models.Version
#pragma warning disable // Disable all warnings
///
- /// A representation of a multi-file manifest representing an app version in the OWC. v1.12.0
+ /// A representation of a multi-file manifest representing an app version in the OWC. v1.28.0
///
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "11.0.0.0 (Newtonsoft.Json v13.0.0.0)")]
public partial class VersionManifest
@@ -55,7 +55,7 @@ public partial class VersionManifest
[Newtonsoft.Json.JsonProperty("ManifestVersion", Required = Newtonsoft.Json.Required.DisallowNull, NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore)]
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
[System.ComponentModel.DataAnnotations.RegularExpression(@"^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$")]
- public string ManifestVersion { get; set; } = "1.12.0";
+ public string ManifestVersion { get; set; } = "1.28.0";
diff --git a/src/WingetCreateCore/WingetCreateCore.csproj b/src/WingetCreateCore/WingetCreateCore.csproj
index 8687e17e..63b60288 100644
--- a/src/WingetCreateCore/WingetCreateCore.csproj
+++ b/src/WingetCreateCore/WingetCreateCore.csproj
@@ -48,11 +48,11 @@
-
-
-
-
-
+
+
+
+
+
diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.DontMoveInstallerFieldsToRoot.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.DontMoveInstallerFieldsToRoot.yaml
index 82e8a7dd..a80546fb 100644
--- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.DontMoveInstallerFieldsToRoot.yaml
+++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.DontMoveInstallerFieldsToRoot.yaml
@@ -77,6 +77,20 @@ Installers:
DownloadCommandProhibited: true
RepairBehavior: modify
ArchiveBinariesDependOnPath: true
+ Authentication:
+ AuthenticationType: microsoftEntraId
+ MicrosoftEntraIdAuthenticationInfo:
+ Resource: TestResource1
+ Scope: TestScope1
+ DesiredStateConfiguration:
+ PowerShell:
+ - RepositoryUrl: https://fakeTestUrl.com/repository1
+ ModuleName: TestModule1
+ Resources:
+ - Name: TestResource1
+ DSCv3:
+ Resources:
+ - Type: TestPublisher/TestResource1
- Architecture: x86
InstallerType: zip
InstallerUrl: https://fakedomain.com/WingetCreateTestZipInstaller.zip
@@ -147,6 +161,20 @@ Installers:
DownloadCommandProhibited: false
RepairBehavior: installer
ArchiveBinariesDependOnPath: false
+ Authentication:
+ AuthenticationType: microsoftEntraId
+ MicrosoftEntraIdAuthenticationInfo:
+ Resource: TestResource2
+ Scope: TestScope2
+ DesiredStateConfiguration:
+ PowerShell:
+ - RepositoryUrl: https://fakeTestUrl.com/repository2
+ ModuleName: TestModule2
+ Resources:
+ - Name: TestResource2
+ DSCv3:
+ Resources:
+ - Type: TestPublisher/TestResource2
- Architecture: arm
InstallerType: zip
InstallerUrl: https://fakedomain.com/WingetCreateTestZipInstaller.zip
@@ -217,6 +245,20 @@ Installers:
DownloadCommandProhibited: false
RepairBehavior: modify
ArchiveBinariesDependOnPath: false
+ Authentication:
+ AuthenticationType: microsoftEntraId
+ MicrosoftEntraIdAuthenticationInfo:
+ Resource: TestResource2
+ Scope: TestScope2
+ DesiredStateConfiguration:
+ PowerShell:
+ - RepositoryUrl: https://fakeTestUrl.com/repository2
+ ModuleName: TestModule2
+ Resources:
+ - Name: TestResource2
+ DSCv3:
+ Resources:
+ - Type: TestPublisher/TestResource2
PackageLocale: en-US
ManifestType: singleton
-ManifestVersion: 1.9.0
\ No newline at end of file
+ManifestVersion: 1.28.0
\ No newline at end of file
diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_28.json b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_28.json
new file mode 100644
index 00000000..b55671c5
--- /dev/null
+++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullJsonSingleton1_28.json
@@ -0,0 +1,177 @@
+{
+ "PackageIdentifier": "TestPublisher.FullJsonSingleton1_28",
+ "PackageVersion": "0.1.2.3",
+ "PackageLocale": "en-US",
+ "DefaultLocale": "en-US",
+ "Publisher": "TestPublisher",
+ "PublisherUrl": "https://fakeTestUrl.com",
+ "PublisherSupportUrl": "https://fakeTestUrl.com",
+ "PrivacyUrl": "https://fakeTestUrl.com",
+ "Author": "fakeAuthor",
+ "PackageName": "Full Singleton 1.28 Test",
+ "PackageUrl": "https://fakeTestUrl.com",
+ "License": "MIT",
+ "LicenseUrl": "https://fakeTestUrl.com",
+ "Copyright": "fakeCopyright",
+ "CopyrightUrl": "https://fakeTestUrl.com",
+ "ShortDescription": "A manifest used to verify that all fields from a full 1.28 singleton manifest can be deserialized and updated.",
+ "Description": "A manifest used to verify that all fields from a full 1.28 singleton manifest can be deserialized and updated.",
+ "Moniker": "testMoniker",
+ "Tags": [
+ "testSingleton"
+ ],
+ "Agreements": [
+ {
+ "AgreementLabel": "fakeAgreementLabel",
+ "AgreementUrl": "https://fakeTestUrl.com",
+ "Agreement": "fakeAgreementContent"
+ }
+ ],
+ "ReleaseNotes": "fakeReleaseNotes",
+ "ReleaseNotesUrl": "https://fakeTestUrl.com",
+ "PurchaseUrl": "https://fakeTestUrl.com",
+ "InstallationNotes": "fakeInstallationNotes",
+ "Documentations": [
+ {
+ "DocumentLabel": "fakeDocumentLabel"
+ },
+ {
+ "DocumentUrl": "fakeDocumentUrl"
+ }
+ ],
+ "Icons": [
+ {
+ "IconUrl": "https://fakeTestIcon",
+ "IconFileType": "png",
+ "IconResolution": "32x32",
+ "IconTheme": "light",
+ "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321"
+ }
+ ],
+ "Installers": [
+ {
+ "MinimumOSVersion": "10.0.0.0",
+ "Architecture": "x64",
+ "InstallerUrl": "https://fakedomain.com/WingetCreateTestExeInstaller.exe",
+ "InstallerType": "exe",
+ "InstallerSha256": "A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC",
+ "ProductCode": "FakeProductCode",
+ "PackageFamilyName": "FakePackageFamilyName",
+ "InstallModes": [
+ "silent"
+ ],
+ "InstallerSwitches": {
+ "Silent": "/s",
+ "Upgrade": "/u",
+ "Repair": "/r"
+ },
+ "InstallerSuccessCodes": [
+ 1
+ ],
+ "ExpectedReturnCodes": [
+ {
+ "InstallerReturnCode": 123,
+ "ReturnResponse": "alreadyInstalled",
+ "ReturnResponseUrl": "https://fakeTestUrl.com"
+ }
+ ],
+ "UpgradeBehavior": "install",
+ "Commands": [
+ "fakeCommand"
+ ],
+ "Protocols": [
+ "fakeProtocol"
+ ],
+ "FileExtensions": [
+ ".exe"
+ ],
+ "Dependencies": {
+ "WindowsFeatures": [
+ "fakeValue"
+ ],
+ "WindowsLibraries": [
+ "fakeValue"
+ ],
+ "PackageDependencies": [
+ {
+ "MinimumVersion": "10.0.0.0"
+ }
+ ],
+ "ExternalDependencies": [
+ "fakeValue"
+ ]
+ },
+ "Markets": {
+ "AllowedMarkets": [
+ "fakeAllowedMarket"
+ ],
+ "ExcludedMarkets": [
+ "fakeExcludedMarket"
+ ]
+ },
+ "InstallerAbortsTerminal": true,
+ "InstallLocationRequired": true,
+ "RequireExplicitUpgrade": true,
+ "UnsupportedOSArchitectures": [
+ "arm64"
+ ],
+ "AppsAndFeaturesEntries": [
+ {
+ "DisplayName": "testName",
+ "Publisher": "testPublisher",
+ "DisplayVersion": "testVersion",
+ "UpgradeCode": "fakeProductCode"
+ }
+ ],
+ "ElevationRequirement": "elevationRequired",
+ "ReleaseDate": "2022-01-01",
+ "DisplayInstallWarnings": true,
+ "UnsupportedArguments": [
+ "log",
+ "location"
+ ],
+ "NestedInstallerFiles": [
+ {
+ "RelativeFilePath": "RelativeFilePath",
+ "PortableCommandAlias": "PortableCommandAlias"
+ }
+ ],
+ "InstallationMetadata": {
+ "DefaultInstallLocation": "%ProgramFiles%\\TestApp",
+ "Files": [
+ {
+ "RelativeFilePath": "main.exe",
+ "FileSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82",
+ "FileType": "launch",
+ "InvocationParameter": "/arg"
+ }
+ ]
+ },
+ "DownloadCommandProhibited": true,
+ "RepairBehavior": "modify",
+ "ArchiveBinariesDependOnPath": true,
+ "DesiredStateConfiguration": {
+ "PowerShell": [
+ {
+ "RepositoryUrl": "https://fakeTestUrl.com",
+ "ModuleName": "TestModule",
+ "Resources": [
+ {
+ "Name": "TestResource"
+ }
+ ]
+ }
+ ],
+ "DSCv3": {
+ "Resources": [
+ {
+ "Type": "TestPublisher/TestResource"
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "ManifestType": "singleton",
+ "ManifestVersion": "1.28.0"
+}
\ No newline at end of file
diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_28.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_28.yaml
new file mode 100644
index 00000000..549eb97c
--- /dev/null
+++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.FullYamlSingleton1_28.yaml
@@ -0,0 +1,125 @@
+PackageIdentifier: TestPublisher.FullYamlSingleton1_28
+PackageVersion: 0.1.2.3
+PackageLocale: en-US
+DefaultLocale: en-US
+Publisher: TestPublisher
+PublisherUrl: https://fakeTestUrl.com
+PublisherSupportUrl: https://fakeTestUrl.com
+PrivacyUrl: https://fakeTestUrl.com
+Author: fakeAuthor
+PackageName: Full Singleton 1.28 Test
+PackageUrl: https://fakeTestUrl.com
+License: MIT
+LicenseUrl: https://fakeTestUrl.com
+Copyright: fakeCopyright
+CopyrightUrl: https://fakeTestUrl.com
+ShortDescription: A manifest used to verify that all fields from a full 1.28 singleton manifest can be deserialized and updated.
+Description: A manifest used to verify that all fields from a full 1.28 singleton manifest can be deserialized and updated.
+Moniker: testMoniker
+Tags:
+- testSingleton
+Agreements:
+- AgreementLabel: fakeAgreementLabel
+ AgreementUrl: https://fakeTestUrl.com
+ Agreement: fakeAgreementContent
+ReleaseNotes: fakeReleaseNotes
+ReleaseNotesUrl: https://fakeTestUrl.com
+PurchaseUrl: https://fakeTestUrl.com
+InstallationNotes: fakeInstallationNotes
+Documentations:
+- DocumentLabel: fakeDocumentLabel
+- DocumentUrl: fakeDocumentUrl
+Icons:
+ - IconUrl: https://fakeTestIcon
+ IconFileType: png
+ IconResolution: 32x32
+ IconTheme: light
+ IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321
+Installers:
+- MinimumOSVersion: 10.0.0.0
+ Architecture: x64
+ InstallerUrl: https://fakedomain.com/WingetCreateTestExeInstaller.exe
+ InstallerType: exe
+ InstallerSha256: A7803233EEDB6A4B59B3024CCF9292A6FFFB94507DC998AA67C5B745D197A5DC
+ ProductCode: FakeProductCode
+ PackageFamilyName: FakePackageFamilyName
+ InstallModes:
+ - silent
+ InstallerSwitches:
+ Silent: /s
+ Upgrade: /u
+ Repair: /r
+ InstallerSuccessCodes:
+ - 1
+ ExpectedReturnCodes:
+ - InstallerReturnCode: 123
+ ReturnResponse: alreadyInstalled
+ ReturnResponseUrl: https://fakeTestUrl.com
+ UpgradeBehavior: install
+ Commands:
+ - fakeCommand
+ Protocols:
+ - fakeProtocol
+ FileExtensions:
+ - .exe
+ Dependencies:
+ WindowsFeatures:
+ - fakeValue
+ WindowsLibraries:
+ - fakeValue
+ PackageDependencies:
+ - MinimumVersion: 10.0.0.0
+ ExternalDependencies:
+ - fakeValue
+ Markets:
+ AllowedMarkets:
+ - fakeAllowedMarket
+ ExcludedMarkets:
+ - fakeExcludedMarket
+ InstallerAbortsTerminal: true
+ InstallLocationRequired: true
+ RequireExplicitUpgrade: true
+ UnsupportedOSArchitectures:
+ - arm64
+ AppsAndFeaturesEntries:
+ - DisplayName: testName
+ Publisher: testPublisher
+ DisplayVersion: testVersion
+ UpgradeCode: fakeProductCode
+ ElevationRequirement: elevationRequired
+ ReleaseDate: 1-1-2022
+ DisplayInstallWarnings: true
+ UnsupportedArguments:
+ - log
+ - location
+ NestedInstallerFiles:
+ - RelativeFilePath: RelativeFilePath
+ PortableCommandAlias: PortableCommandAlias
+ InstallationMetadata:
+ DefaultInstallLocation: "%ProgramFiles%\\TestApp"
+ Files:
+ - RelativeFilePath: "main.exe"
+ FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82
+ FileType: launch
+ InvocationParameter: "/arg"
+ DownloadCommandProhibited: true
+ RepairBehavior: modify
+ ArchiveBinariesDependOnPath: true
+ DesiredStateConfiguration:
+ PowerShell:
+ - RepositoryUrl: https://fakeTestUrl.com
+ ModuleName: TestModule
+ Resources:
+ - Name: TestResource
+ DSCv3:
+ Resources:
+ - Type: TestPublisher/TestResource
+# `winget validate` fails if this field is used as it requires verified publisher feature
+# Uncomment when verified publisher feature is implemented
+# Authentication:
+# AuthenticationType: microsoftEntraId
+# MicrosoftEntraIdAuthenticationInfo:
+# Scope: DefaultScope
+# Resource: DefaultResource
+ManifestType: singleton
+ManifestVersion: 1.28.0
\ No newline at end of file
diff --git a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MoveInstallerFieldsToRoot.yaml b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MoveInstallerFieldsToRoot.yaml
index cc03ea60..2af08b76 100644
--- a/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MoveInstallerFieldsToRoot.yaml
+++ b/src/WingetCreateTests/WingetCreateTests/Resources/TestPublisher.MoveInstallerFieldsToRoot.yaml
@@ -76,6 +76,20 @@ Installers:
DownloadCommandProhibited: true
RepairBehavior: modify
ArchiveBinariesDependOnPath: true
+ Authentication:
+ AuthenticationType: microsoftEntraId
+ MicrosoftEntraIdAuthenticationInfo:
+ Resource: TestResource
+ Scope: TestScope
+ DesiredStateConfiguration:
+ PowerShell:
+ - RepositoryUrl: https://fakeTestUrl.com/repository
+ ModuleName: TestModule
+ Resources:
+ - Name: TestResource
+ DSCv3:
+ Resources:
+ - Type: TestPublisher/TestResource
- Architecture: x86
InstallerType: zip
InstallerUrl: https://fakedomain.com/WingetCreateTestZipInstaller.zip
@@ -146,6 +160,20 @@ Installers:
DownloadCommandProhibited: true
RepairBehavior: modify
ArchiveBinariesDependOnPath: true
+ Authentication:
+ AuthenticationType: microsoftEntraId
+ MicrosoftEntraIdAuthenticationInfo:
+ Resource: TestResource
+ Scope: TestScope
+ DesiredStateConfiguration:
+ PowerShell:
+ - RepositoryUrl: https://fakeTestUrl.com/repository
+ ModuleName: TestModule
+ Resources:
+ - Name: TestResource
+ DSCv3:
+ Resources:
+ - Type: TestPublisher/TestResource
- Architecture: arm
InstallerType: zip
InstallerUrl: https://fakedomain.com/WingetCreateTestZipInstaller.zip
@@ -217,6 +245,20 @@ Installers:
DownloadCommandProhibited: true
RepairBehavior: modify
ArchiveBinariesDependOnPath: true
+ Authentication:
+ AuthenticationType: microsoftEntraId
+ MicrosoftEntraIdAuthenticationInfo:
+ Resource: TestResource
+ Scope: TestScope
+ DesiredStateConfiguration:
+ PowerShell:
+ - RepositoryUrl: https://fakeTestUrl.com/repository
+ ModuleName: TestModule
+ Resources:
+ - Name: TestResource
+ DSCv3:
+ Resources:
+ - Type: TestPublisher/TestResource
PackageLocale: en-US
ManifestType: singleton
-ManifestVersion: 1.9.0
\ No newline at end of file
+ManifestVersion: 1.28.0
\ No newline at end of file
diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocalizationTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocalizationTests.cs
index e645df4f..2ac2a275 100644
--- a/src/WingetCreateTests/WingetCreateTests/UnitTests/LocalizationTests.cs
+++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/LocalizationTests.cs
@@ -10,7 +10,6 @@ namespace Microsoft.WingetCreateUnitTests
using System.IO;
using System.Linq;
using System.Reflection;
- using Microsoft.WingetCreateCLI.Properties;
using Microsoft.WingetCreateCore;
using Microsoft.WingetCreateCore.Models.DefaultLocale;
using Microsoft.WingetCreateCore.Models.Installer;
@@ -18,6 +17,7 @@ namespace Microsoft.WingetCreateUnitTests
using Microsoft.WingetCreateCore.Models.Singleton;
using Microsoft.WingetCreateCore.Models.Version;
using NUnit.Framework;
+ using Resources = Microsoft.WingetCreateCLI.Properties.Resources;
///
/// Test cases for verifying that strings are correctly localized.
diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs
index 544581c7..878195b2 100644
--- a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs
+++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs
@@ -11,7 +11,6 @@ namespace Microsoft.WingetCreateUnitTests
using Microsoft.WingetCreateCLI.Commands;
using Microsoft.WingetCreateCLI.Logging;
using Microsoft.WingetCreateCLI.Models.Settings;
- using Microsoft.WingetCreateCLI.Properties;
using Microsoft.WingetCreateCLI.Telemetry.Events;
using Microsoft.WingetCreateCore;
using Microsoft.WingetCreateCore.Common;
@@ -22,6 +21,7 @@ namespace Microsoft.WingetCreateUnitTests
using Microsoft.WingetCreateTests;
using NUnit.Framework;
using NUnit.Framework.Legacy;
+ using Resources = Microsoft.WingetCreateCLI.Properties.Resources;
///
/// Test cases for verifying that the "update" command is working as expected.
@@ -1039,6 +1039,19 @@ public async Task UpdateFullYamlSingletonVersion1_12()
ClassicAssert.IsNotNull(updatedManifests, "Command should have succeeded");
}
+ ///
+ /// Ensures that all fields from the YAML Singleton v1.28 manifest can be deserialized and updated correctly.
+ ///
+ /// A representing the result of the asynchronous operation.
+ [Test]
+ public async Task UpdateFullYamlSingletonVersion1_28()
+ {
+ TestUtils.InitializeMockDownloads(TestConstants.TestZipInstaller, TestConstants.TestExeInstaller);
+ (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullYamlSingleton1_28", null, this.tempPath, null);
+ var updatedManifests = await RunUpdateCommand(command, initialManifestContent);
+ ClassicAssert.IsNotNull(updatedManifests, "Command should have succeeded");
+ }
+
///
/// Ensures that all fields from the JSON Singleton v1.1 manifest can be deserialized and updated correctly.
///
@@ -1156,6 +1169,19 @@ public async Task UpdateFullJsonSingletonVersion1_12()
ClassicAssert.IsNotNull(updatedManifests, "Command should have succeeded");
}
+ ///
+ /// Ensures that all fields from the Singleton JSON v1.28 manifest can be deserialized and updated correctly.
+ ///
+ /// A representing the result of the asynchronous operation.
+ [Test]
+ public async Task UpdateFullJsonSingletonVersion1_28()
+ {
+ TestUtils.InitializeMockDownloads(TestConstants.TestZipInstaller, TestConstants.TestExeInstaller);
+ (UpdateCommand command, var initialManifestContent) = GetUpdateCommandAndManifestData("TestPublisher.FullJsonSingleton1_28", null, this.tempPath, null, manifestFormat: ManifestFormat.Json);
+ var updatedManifests = await RunUpdateCommand(command, initialManifestContent);
+ ClassicAssert.IsNotNull(updatedManifests, "Command should have succeeded");
+ }
+
///
/// Ensures that version specific fields are reset after an update when using YAML manifests.
///
@@ -1526,6 +1552,7 @@ public async Task MoveInstallerFieldsToRoot()
ClassicAssert.IsTrue(updatedInstallerManifest.DownloadCommandProhibited == true, "DownloadCommandProhibited at the root level should be true");
ClassicAssert.IsNotNull(updatedInstallerManifest.RepairBehavior, "RepairBehavior at the root level should not be null");
ClassicAssert.IsTrue(updatedInstallerManifest.ArchiveBinariesDependOnPath == true, "ArchiveBinariesDependOnPath at the root level should be true");
+ ClassicAssert.AreEqual(AuthenticationType.MicrosoftEntraId, updatedInstallerManifest.Authentication.AuthenticationType);
foreach (var installer in updatedInstallerManifest.Installers)
{
@@ -1559,6 +1586,8 @@ public async Task MoveInstallerFieldsToRoot()
ClassicAssert.IsNull(installer.DownloadCommandProhibited, "DownloadCommandProhibited at the installer level should be null");
ClassicAssert.IsNull(installer.RepairBehavior, "RepairBehavior at the installer level should be null");
ClassicAssert.IsNull(installer.ArchiveBinariesDependOnPath, "ArchiveBinariesDependOnPath at the installer level should be null");
+ ClassicAssert.IsNull(installer.Authentication, "Authentication at the installer level should be null");
+ ClassicAssert.IsNull(installer.DesiredStateConfiguration, "DesiredStateConfiguration at the installer level should be null");
}
}
@@ -1608,6 +1637,8 @@ public async Task DontMoveInstallerFieldsToRoot()
ClassicAssert.IsNull(updatedInstallerManifest.DownloadCommandProhibited, "DownloadCommandProhibited at the root level should be null");
ClassicAssert.IsNull(updatedInstallerManifest.RepairBehavior, "RepairBehavior at the root level should be null");
ClassicAssert.IsNull(updatedInstallerManifest.ArchiveBinariesDependOnPath, "ArchiveBinariesDependOnPath at the root level should be null");
+ ClassicAssert.IsNull(updatedInstallerManifest.Authentication, "Authentication at the root level should be null");
+ ClassicAssert.IsNull(updatedInstallerManifest.DesiredStateConfiguration, "DesiredStateConfiguration at the root level should be null");
foreach (var installer in updatedInstallerManifest.Installers)
{
@@ -1641,6 +1672,8 @@ public async Task DontMoveInstallerFieldsToRoot()
ClassicAssert.IsNotNull(installer.DownloadCommandProhibited, "DownloadCommandProhibited at the installer level should not be null");
ClassicAssert.IsNotNull(installer.RepairBehavior, "RepairBehavior at the installer level should not be null");
ClassicAssert.IsNotNull(installer.ArchiveBinariesDependOnPath, "ArchiveBinariesDependOnPath at the installer level should not be null");
+ ClassicAssert.IsNotNull(installer.Authentication, "Authentication at the installer level should not be null");
+ ClassicAssert.IsNotNull(installer.DesiredStateConfiguration, "DesiredStateConfiguration at the installer level should not be null");
}
}
diff --git a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj
index 8b49b3f3..8004aee9 100644
--- a/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj
+++ b/src/WingetCreateTests/WingetCreateTests/WingetCreateTests.csproj
@@ -67,9 +67,15 @@
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
+
+ PreserveNewest
+
PreserveNewest
From a7f753e12e24bffe3a0da7fb4e43f4a3a3c0ae6c Mon Sep 17 00:00:00 2001
From: Muhammad Danish <88161975+mdanish-kh@users.noreply.github.com>
Date: Sat, 24 Oct 2026 06:20:43 +0500
Subject: [PATCH 2/3] address review comment
---
.../WingetCreateTests/UnitTests/UpdateCommandTests.cs | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs
index 878195b2..5006226e 100644
--- a/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs
+++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/UpdateCommandTests.cs
@@ -1553,6 +1553,11 @@ public async Task MoveInstallerFieldsToRoot()
ClassicAssert.IsNotNull(updatedInstallerManifest.RepairBehavior, "RepairBehavior at the root level should not be null");
ClassicAssert.IsTrue(updatedInstallerManifest.ArchiveBinariesDependOnPath == true, "ArchiveBinariesDependOnPath at the root level should be true");
ClassicAssert.AreEqual(AuthenticationType.MicrosoftEntraId, updatedInstallerManifest.Authentication.AuthenticationType);
+ ClassicAssert.IsNotNull(updatedInstallerManifest.DesiredStateConfiguration, "DesiredStateConfiguration at the root level should not be null");
+ ClassicAssert.AreEqual("https://fakeTestUrl.com/repository", updatedInstallerManifest.DesiredStateConfiguration.PowerShell.Single().RepositoryUrl);
+ ClassicAssert.AreEqual("TestModule", updatedInstallerManifest.DesiredStateConfiguration.PowerShell.Single().ModuleName);
+ ClassicAssert.AreEqual("TestResource", updatedInstallerManifest.DesiredStateConfiguration.PowerShell.Single().Resources.Single().Name);
+ ClassicAssert.AreEqual("TestPublisher/TestResource", updatedInstallerManifest.DesiredStateConfiguration.DSCv3.Resources.Single().Type);
foreach (var installer in updatedInstallerManifest.Installers)
{
From 8b657111945946e63f3ff936ddfc5b11cef7b484 Mon Sep 17 00:00:00 2001
From: Muhammad Danish <88161975+mdanish-kh@users.noreply.github.com>
Date: Tue, 27 Oct 2026 00:19:47 +0500
Subject: [PATCH 3/3] handle nested schemas for interactive mode edits
---
src/WingetCreateCLI/PromptHelper.cs | 77 ++++++-----
.../UnitTests/PromptHelperTests.cs | 123 ++++++++++++++++++
2 files changed, 165 insertions(+), 35 deletions(-)
create mode 100644 src/WingetCreateTests/WingetCreateTests/UnitTests/PromptHelperTests.cs
diff --git a/src/WingetCreateCLI/PromptHelper.cs b/src/WingetCreateCLI/PromptHelper.cs
index 5a04e677..33d0504b 100644
--- a/src/WingetCreateCLI/PromptHelper.cs
+++ b/src/WingetCreateCLI/PromptHelper.cs
@@ -490,7 +490,7 @@ private static List GenerateInstallerSelectionList(List insta
var installerTuple = string.Join(" | ", new[]
{
installer.Architecture.ToEnumAttributeValue(),
- installer.InstallerType.ToEnumAttributeValue(),
+ installer.InstallerType?.ToEnumAttributeValue(),
installer.Scope?.ToEnumAttributeValue(),
installer.InstallerLocale,
installer.InstallerUrl,
@@ -515,56 +515,63 @@ private static List GenerateInstallerSelectionList(List insta
private static void ApplyChangesToIndividualInstallers(Installer installerCopy, List installers)
{
// Skip architecture as the default value when instantiated is x86.
- var modifiedFields = installerCopy.GetType().GetProperties()
- .Select(prop => prop)
- .Where(pi =>
- pi.GetValue(installerCopy) != null &&
- pi.Name != nameof(Installer.Architecture) &&
- pi.Name != nameof(Installer.AdditionalProperties));
-
- foreach (var field in modifiedFields)
+ foreach (var property in typeof(Installer).GetProperties().Where(p =>
+ p.Name != nameof(Installer.Architecture) && p.Name != nameof(Installer.AdditionalProperties)))
{
- foreach (Installer installer in installers)
+ var value = property.GetValue(installerCopy);
+ if (value != null)
{
- var fieldValue = field.GetValue(installerCopy);
- var prop = installer.GetType().GetProperty(field.Name);
- if (prop.PropertyType.IsValueType)
+ foreach (var installer in installers)
{
- prop.SetValue(installer, fieldValue);
+ property.SetValue(installer, ApplyPropertyChanges(value, property.GetValue(installer)));
}
- else if (fieldValue is IList list)
+ }
+ }
+ }
+
+ private static object ApplyPropertyChanges(object value, object target)
+ {
+ var pending = new Queue<(object Source, object Target)>();
+ var result = CopyValue(value, target);
+ while (pending.Count > 0)
+ {
+ var (source, destination) = pending.Dequeue();
+ if (source is IList list)
+ {
+ foreach (var item in list)
{
- prop.SetValue(installer, list.DeepClone());
+ ((IList)destination).Add(CopyValue(item, null));
}
- else if (fieldValue is Dependencies dependencies)
+ }
+ else
+ {
+ foreach (var property in source.GetType().GetProperties().Where(p => p.CanWrite && p.Name != nameof(Installer.AdditionalProperties)))
{
- ApplyDependencyChangesToInstaller(dependencies, installer);
+ var propertyValue = property.GetValue(source);
+ if (propertyValue != null)
+ {
+ property.SetValue(destination, CopyValue(propertyValue, property.GetValue(destination)));
+ }
}
}
}
- }
- ///
- /// Clones any non-null property values of the dependencies object and assigns them to the provided installer object.
- ///
- /// Dependencies object with new values.
- /// Installer object to assign new changes to.
- private static void ApplyDependencyChangesToInstaller(Dependencies dependencies, Installer installer)
- {
- var modifiedFields = dependencies.GetType().GetProperties()
- .Select(prop => prop)
- .Where(pi => pi.GetValue(dependencies) != null);
+ return result;
- foreach (var field in modifiedFields.Where(f => f.Name != nameof(Installer.AdditionalProperties)))
+ object CopyValue(object source, object destination)
{
- var fieldValue = field.GetValue(dependencies);
- installer.Dependencies ??= new Dependencies();
- var prop = installer.Dependencies.GetType().GetProperty(field.Name);
+ if (source == null || source.GetType().IsValueType || source is string)
+ {
+ return source;
+ }
- if (fieldValue is IList list)
+ if (source is IList || destination == null)
{
- prop.SetValue(installer.Dependencies, list.DeepClone());
+ destination = Activator.CreateInstance(source.GetType());
}
+
+ pending.Enqueue((source, destination));
+ return destination;
}
}
diff --git a/src/WingetCreateTests/WingetCreateTests/UnitTests/PromptHelperTests.cs b/src/WingetCreateTests/WingetCreateTests/UnitTests/PromptHelperTests.cs
new file mode 100644
index 00000000..e1c55c81
--- /dev/null
+++ b/src/WingetCreateTests/WingetCreateTests/UnitTests/PromptHelperTests.cs
@@ -0,0 +1,123 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Microsoft.WingetCreateUnitTests
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Reflection;
+ using Microsoft.WingetCreateCLI;
+ using Microsoft.WingetCreateCore.Models.Installer;
+ using Newtonsoft.Json.Linq;
+ using NUnit.Framework;
+
+ ///
+ /// Tests for applying interactive installer edits.
+ ///
+ public class PromptHelperTests
+ {
+ ///
+ /// Verifies partial edits preserve existing fields and replace lists without sharing their elements.
+ ///
+ [Test]
+ public void ApplyPartialChangesToAllInstallers()
+ {
+ var edits = new Installer
+ {
+ InstallerSwitches = new InstallerSwitches { Silent = "new" },
+ AppsAndFeaturesEntries = new List { new AppsAndFeaturesEntry { DisplayName = "new" } },
+ InstallerAbortsTerminal = false,
+ };
+ var installers = new List
+ {
+ new Installer { Architecture = Architecture.X64, InstallerSwitches = new InstallerSwitches { Silent = "old", Custom = "keep" }, InstallerAbortsTerminal = true },
+ new Installer { Architecture = Architecture.Arm64 },
+ };
+ typeof(PromptHelper).GetMethod("ApplyChangesToIndividualInstallers", BindingFlags.NonPublic | BindingFlags.Static)
+ .Invoke(null, new object[] { edits, installers });
+
+ Assert.That(installers[0].InstallerSwitches.Custom, Is.EqualTo("keep"));
+ Assert.That(installers[0].InstallerSwitches.Silent, Is.EqualTo("new"));
+ Assert.That(installers[0].InstallerAbortsTerminal, Is.False);
+ Assert.That(installers[0].Architecture, Is.EqualTo(Architecture.X64));
+ Assert.That(installers[1].Architecture, Is.EqualTo(Architecture.Arm64));
+ installers[0].AppsAndFeaturesEntries[0].DisplayName = "changed again";
+ Assert.That(installers[1].AppsAndFeaturesEntries[0].DisplayName, Is.EqualTo("new"));
+ Assert.That(edits.AppsAndFeaturesEntries[0].DisplayName, Is.EqualTo("new"));
+ }
+
+ ///
+ /// Verifies every editable field, including nested properties, reaches each installer.
+ ///
+ /// The editable installer property.
+ [TestCaseSource(nameof(EditableInstallerProperties))]
+ public void ApplyChangesToAllInstallers(PropertyInfo property)
+ {
+ var edits = new Installer();
+ property.SetValue(edits, CreateValue(property.PropertyType));
+ var expected = JToken.FromObject(property.GetValue(edits));
+ var installers = new List { new Installer(), new Installer() };
+
+ typeof(PromptHelper).GetMethod("ApplyChangesToIndividualInstallers", BindingFlags.NonPublic | BindingFlags.Static)
+ .Invoke(null, new object[] { edits, installers });
+
+ foreach (var installer in installers)
+ {
+ var actual = property.GetValue(installer);
+ Assert.That(actual, Is.Not.Null, property.Name);
+ Assert.That(JToken.DeepEquals(expected, JToken.FromObject(actual)), Is.True, property.Name);
+ if (!property.PropertyType.IsValueType && property.PropertyType != typeof(string))
+ {
+ Assert.That(actual, Is.Not.SameAs(property.GetValue(edits)), property.Name);
+ }
+ }
+ }
+
+ private static IEnumerable EditableInstallerProperties()
+ {
+ var properties = typeof(Installer).GetProperties();
+ var names = (List)typeof(PromptHelper)
+ .GetMethod("FilterPropertiesAndCreateSelectionList", BindingFlags.NonPublic | BindingFlags.Static)
+ .MakeGenericMethod(typeof(Installer))
+ .Invoke(null, new object[] { new Installer(), properties });
+ return properties.Where(p => names.Contains(p.Name) && p.Name != nameof(Installer.Architecture))
+ .Select(p => new TestCaseData(p).SetArgDisplayNames(p.Name));
+ }
+
+ private static object CreateValue(Type type)
+ {
+ type = Nullable.GetUnderlyingType(type) ?? type;
+ if (type == typeof(string))
+ {
+ return "test";
+ }
+
+ if (type.IsEnum)
+ {
+ return Enum.GetValues(type).GetValue(0);
+ }
+
+ if (type.IsValueType)
+ {
+ return Convert.ChangeType(1, type);
+ }
+
+ var value = Activator.CreateInstance(type);
+ if (value is IList list)
+ {
+ list.Add(CreateValue(type.GetGenericArguments()[0]));
+ }
+ else
+ {
+ foreach (var property in type.GetProperties().Where(p => p.CanWrite && p.Name != nameof(Installer.AdditionalProperties)))
+ {
+ property.SetValue(value, CreateValue(property.PropertyType));
+ }
+ }
+
+ return value;
+ }
+ }
+}