winget_types/installer/
mod.rs

1#![expect(clippy::struct_excessive_bools)]
2
3mod apps_and_features_entry;
4mod architecture;
5pub mod authentication;
6mod capability;
7mod channel;
8mod command;
9mod dependencies;
10mod elevation_requirement;
11mod expected_return_codes;
12mod file_extension;
13mod install_modes;
14mod installation_metadata;
15mod installer_return_code;
16mod installer_type;
17mod market;
18mod minimum_os_version;
19mod nested;
20mod platform;
21mod protocol;
22mod repair_behavior;
23mod return_response;
24mod scope;
25pub mod switches;
26mod unsupported_arguments;
27mod unsupported_os_architectures;
28mod upgrade_behavior;
29
30use alloc::{collections::BTreeSet, string::String, vec::Vec};
31
32pub use apps_and_features_entry::AppsAndFeaturesEntry;
33pub use architecture::{Architecture, ParseArchitectureError};
34pub use authentication::Authentication;
35pub use capability::{Capability, CapabilityError, RestrictedCapability};
36pub use channel::{Channel, ChannelError};
37use chrono::NaiveDate;
38pub use command::{Command, CommandError};
39pub use dependencies::{Dependencies, PackageDependencies};
40pub use elevation_requirement::ElevationRequirement;
41pub use expected_return_codes::ExpectedReturnCodes;
42pub use file_extension::{FileExtension, FileExtensionError};
43pub use install_modes::InstallModes;
44pub use installation_metadata::InstallationMetadata;
45pub use installer_return_code::{InstallerReturnCode, InstallerSuccessCode};
46pub use installer_type::InstallerType;
47use itertools::Itertools;
48pub use market::{Market, MarketError, Markets, MarketsError};
49pub use minimum_os_version::{MinimumOSVersion, MinimumOSVersionError};
50use nested::installer_type::NestedInstallerType;
51pub use nested::{
52    PortableCommandAlias, PortableCommandAliasError, installer_files::NestedInstallerFiles,
53};
54pub use package_family_name::PackageFamilyName;
55pub use platform::{Platform, PlatformParseError};
56pub use protocol::{Protocol, ProtocolError};
57pub use repair_behavior::RepairBehavior;
58pub use scope::{Scope, ScopeParseError};
59pub use switches::InstallerSwitches;
60pub use unsupported_arguments::UnsupportedArguments;
61pub use unsupported_os_architectures::UnsupportedOSArchitecture;
62pub use upgrade_behavior::{UpgradeBehavior, UpgradeBehaviorParseError};
63
64use super::{
65    LanguageTag, Manifest, ManifestType, ManifestVersion, PackageIdentifier, PackageVersion,
66    Sha256String, url::DecodedUrl,
67};
68
69pub const VALID_FILE_EXTENSIONS: [&str; 7] = [
70    "msix",
71    "msi",
72    "appx",
73    "exe",
74    "zip",
75    "msixbundle",
76    "appxbundle",
77];
78
79#[derive(Clone, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
80#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
81#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
82pub struct InstallerManifest {
83    /// The unique identifier for a given package.
84    ///
85    /// This value is generally in the form of `Publisher.Package`. It is case-sensitive, and this
86    /// value must match the folder structure under the partition directory in GitHub.
87    pub package_identifier: PackageIdentifier,
88
89    /// The version of the package.
90    ///
91    /// It is related to the specific release this manifests targets. In some cases you will see a
92    /// perfectly formed [semantic version] number, and in other cases you might see something
93    /// different. These may be date driven, or they might have other characters with some package
94    /// specific meaning for example.
95    ///
96    /// The Windows Package Manager client uses this version to determine if an upgrade for a
97    /// package is available. In some cases, packages may be released with a marketing driven
98    /// version, and that causes trouble with the [`winget upgrade`] command.
99    ///
100    /// The current best practice is to use the value reported in Add / Remove Programs when this
101    /// version of the package is installed. In some cases, packages do not report a version
102    /// resulting in an upgrade loop or other unwanted behavior.
103    ///
104    /// [semantic version]: https://semver.org/
105    /// [`winget upgrade`]: https://docs.microsoft.com/windows/package-manager/winget/upgrade
106    pub package_version: PackageVersion,
107
108    /// The distribution channel for a package.
109    ///
110    /// Examples may include "stable" or "beta".
111    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
112    pub channel: Option<Channel>,
113
114    /// The locale for an installer not the package meta-data.
115    ///
116    /// Some installers are compiled with locale or language specific properties. If this key is
117    /// present, it is used to represent the package locale for an installer.
118    #[cfg_attr(
119        feature = "serde",
120        serde(rename = "InstallerLocale", skip_serializing_if = "Option::is_none")
121    )]
122    pub locale: Option<LanguageTag>,
123
124    /// The Windows platform targeted by the installer.
125    ///
126    /// The Windows Package Manager currently supports "Windows.Desktop" and "Windows.Universal".
127    #[cfg_attr(
128        feature = "serde",
129        serde(skip_serializing_if = "Platform::is_empty", default)
130    )]
131    pub platform: Platform,
132
133    /// The minimum version of the Windows operating system supported by the package.
134    #[cfg_attr(
135        feature = "serde",
136        serde(rename = "MinimumOSVersion", skip_serializing_if = "Option::is_none")
137    )]
138    pub minimum_os_version: Option<MinimumOSVersion>,
139
140    /// The installer type for the package.
141    ///
142    /// The Windows Package Manager supports [MSIX], [MSI], and executable installers. Some well
143    /// known formats ([Inno], [Nullsoft], [WiX], and [Burn]) provide standard sets of installer
144    /// switches to provide different installer experiences. Portable packages are supported as of
145    /// Windows Package Manager 1.3. Zip packages are supported as of Windows Package Manager 1.5.
146    ///
147    /// [MSIX]: https://docs.microsoft.com/windows/msix/overview
148    /// [MSI]: https://docs.microsoft.com/windows/win32/msi/windows-installer-portal
149    /// [Inno]: https://jrsoftware.org/isinfo.php
150    /// [Nullsoft]: https://sourceforge.net/projects/nsis
151    /// [WiX]: https://wixtoolset.org/
152    /// [Burn]: https://wixtoolset.org/docs/v3/bundle/
153    #[cfg_attr(
154        feature = "serde",
155        serde(rename = "InstallerType", skip_serializing_if = "Option::is_none")
156    )]
157    pub r#type: Option<InstallerType>,
158
159    /// The installer type of the file within the archive which will be used as the installer.
160    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
161    pub nested_installer_type: Option<NestedInstallerType>,
162
163    /// A list of all the installers to be executed within an archive.
164    #[cfg_attr(
165        feature = "serde",
166        serde(skip_serializing_if = "BTreeSet::is_empty", default)
167    )]
168    pub nested_installer_files: BTreeSet<NestedInstallerFiles>,
169
170    /// The scope the package is installed under.
171    ///
172    /// The two configurations are [`user`] and [`machine`]. Some installers support only one of
173    /// these scopes while others support both via arguments passed to the installer using
174    /// [`InstallerSwitches`].
175    ///
176    /// [`user`]: Scope::User
177    /// [`machine`]: Scope::Machine
178    /// [`InstallerSwitches`]: InstallerSwitches
179    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
180    pub scope: Option<Scope>,
181
182    /// The install modes supported by the installer.
183    ///
184    /// The Microsoft community package repository requires a package support "silent" and
185    /// "silent with progress". The Windows Package Manager also supports "interactive" installers.
186    #[cfg_attr(
187        feature = "serde",
188        serde(skip_serializing_if = "InstallModes::is_empty", default)
189    )]
190    pub install_modes: InstallModes,
191
192    /// The set of switches passed to installers.
193    #[cfg_attr(
194        feature = "serde",
195        serde(
196            rename = "InstallerSwitches",
197            skip_serializing_if = "InstallerSwitches::is_empty",
198            default
199        )
200    )]
201    pub switches: InstallerSwitches,
202
203    /// Any status codes returned by the installer representing a success condition other than zero.
204    #[cfg_attr(
205        feature = "serde",
206        serde(
207            rename = "InstallerSuccessCodes",
208            skip_serializing_if = "BTreeSet::is_empty",
209            default
210        )
211    )]
212    pub success_codes: BTreeSet<InstallerSuccessCode>,
213
214    /// Any status codes returned by the installer representing a condition other than zero.
215    #[cfg_attr(
216        feature = "serde",
217        serde(skip_serializing_if = "BTreeSet::is_empty", default)
218    )]
219    pub expected_return_codes: BTreeSet<ExpectedReturnCodes>,
220
221    /// What the Windows Package Manager should do regarding the currently installed package during
222    /// a package upgrade.
223    ///
224    /// If the package should be uninstalled first, the [`uninstallPrevious`] value should be
225    /// specified. If the package should not be upgraded through `WinGet`, the [`deny`] value should
226    /// be specified.
227    ///
228    /// [`uninstallPrevious`]: UpgradeBehavior::UninstallPrevious
229    /// [`deny`]: UpgradeBehavior::Deny
230    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
231    pub upgrade_behavior: Option<UpgradeBehavior>,
232
233    /// Any commands or aliases used to execute the package after it has been installed.
234    #[cfg_attr(
235        feature = "serde",
236        serde(skip_serializing_if = "BTreeSet::is_empty", default)
237    )]
238    pub commands: BTreeSet<Command>,
239
240    /// Any protocols (i.e. URI schemes) supported by the package. For example: `["ftp", "ldap"]`.
241    /// Entries shouldn't have trailing colons. The Windows Package Manager does not support any
242    /// behavior related to protocols handled by a package.
243    #[cfg_attr(
244        feature = "serde",
245        serde(skip_serializing_if = "BTreeSet::is_empty", default)
246    )]
247    pub protocols: BTreeSet<Protocol>,
248
249    /// Any file extensions supported by the package.
250    ///
251    /// For example: `["html", "jpg"]`. Entries shouldn't have leading dots. The Windows Package
252    /// Manager does not support any behavior related to the file extensions supported by the
253    /// package.
254    #[cfg_attr(
255        feature = "serde",
256        serde(skip_serializing_if = "BTreeSet::is_empty", default)
257    )]
258    pub file_extensions: BTreeSet<FileExtension>,
259
260    /// Any dependencies required to install or run the package.
261    #[cfg_attr(
262        feature = "serde",
263        serde(skip_serializing_if = "Dependencies::is_empty", default)
264    )]
265    pub dependencies: Dependencies,
266
267    /// The [package family name] specified in an MSIX installer.
268    ///
269    /// This value is used to assist with matching packages from a source to the program installed
270    /// in Windows via Add / Remove Programs for list, and upgrade behavior.
271    ///
272    /// [package family name]: https://learn.microsoft.com/windows/apps/desktop/modernize/package-identity-overview#package-family-name
273    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
274    pub package_family_name: Option<PackageFamilyName>,
275
276    /// The [product code].
277    ///
278    /// This value is used to assist with matching packages from a source to the program installed
279    /// in Windows via Add / Remove Programs for list, and upgrade behavior.
280    ///
281    /// [product code]: https://learn.microsoft.com/windows/win32/msi/product-codes
282    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
283    pub product_code: Option<String>,
284
285    /// The capabilities provided by an MSIX package.
286    ///
287    /// More information is available for [App capability declarations].
288    ///
289    /// [App capability declarations]: https://docs.microsoft.com/windows/uwp/packaging/app-capability-declarations
290    #[cfg_attr(
291        feature = "serde",
292        serde(skip_serializing_if = "BTreeSet::is_empty", default)
293    )]
294    pub capabilities: BTreeSet<Capability>,
295
296    /// The restricted capabilities provided by an MSIX package.
297    ///
298    /// More information is available for [App capability declarations].
299    ///
300    /// [App capability declarations]: https://docs.microsoft.com/windows/uwp/packaging/app-capability-declarations
301    #[cfg_attr(
302        feature = "serde",
303        serde(skip_serializing_if = "BTreeSet::is_empty", default)
304    )]
305    pub restricted_capabilities: BTreeSet<RestrictedCapability>,
306
307    /// Any markets a package may or may not be installed in.
308    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
309    pub markets: Option<Markets>,
310
311    /// The behavior associated with installers that abort the terminal.
312    ///
313    /// This most often occurs when a user is performing an upgrade of the running terminal.
314    #[cfg_attr(
315        feature = "serde",
316        serde(
317            rename = "InstallerAbortsTerminal",
318            skip_serializing_if = "core::ops::Not::not",
319            default
320        )
321    )]
322    pub aborts_terminal: bool,
323
324    /// The release date for a package, in RFC 3339 / ISO 8601 format, i.e. "YYYY-MM-DD".
325    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
326    pub release_date: Option<NaiveDate>,
327
328    /// The requirement to have an install location specified.
329    ///
330    /// These installers are known to deploy files to the location the installer is executed in.
331    #[cfg_attr(
332        feature = "serde",
333        serde(skip_serializing_if = "core::ops::Not::not", default)
334    )]
335    pub install_location_required: bool,
336
337    /// Identifies packages that upgrade themselves.
338    ///
339    /// By default, they are excluded from `winget upgrade --all`.
340    #[cfg_attr(
341        feature = "serde",
342        serde(skip_serializing_if = "core::ops::Not::not", default)
343    )]
344    pub require_explicit_upgrade: bool,
345
346    /// Whether a warning message is displayed to the user prior to install or upgrade if the
347    /// package is known to interfere with any running applications.
348    #[cfg_attr(
349        feature = "serde",
350        serde(skip_serializing_if = "core::ops::Not::not", default)
351    )]
352    pub display_install_warnings: bool,
353
354    /// Any architectures a package is known not to be compatible with.
355    ///
356    /// Generally, this is associated with emulation modes.
357    #[cfg_attr(
358        feature = "serde",
359        serde(
360            rename = "UnsupportedOSArchitectures",
361            skip_serializing_if = "UnsupportedOSArchitecture::is_empty",
362            default
363        )
364    )]
365    pub unsupported_os_architectures: UnsupportedOSArchitecture,
366
367    /// The list of Windows Package Manager Client arguments the installer does not support.
368    ///
369    /// Only the `--log` and `--location` arguments can be specified as unsupported arguments for an
370    /// installer.
371    #[cfg_attr(
372        feature = "serde",
373        serde(skip_serializing_if = "UnsupportedArguments::is_empty", default)
374    )]
375    pub unsupported_arguments: UnsupportedArguments,
376
377    /// The values reported by Windows Apps & Features.
378    ///
379    /// When a package is installed, entries are made into the Windows Registry.
380    #[cfg_attr(
381        feature = "serde",
382        serde(skip_serializing_if = "Vec::is_empty", default)
383    )]
384    pub apps_and_features_entries: Vec<AppsAndFeaturesEntry>,
385
386    /// The scope in which scope a package is required to be executed under.
387    ///
388    /// Some packages require user level execution while others require administrative level
389    /// execution.
390    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
391    pub elevation_requirement: Option<ElevationRequirement>,
392
393    /// Allows for additional metadata to be used for deeper installation detection.
394    #[cfg_attr(
395        feature = "serde",
396        serde(skip_serializing_if = "InstallationMetadata::is_empty", default)
397    )]
398    pub installation_metadata: InstallationMetadata,
399
400    /// When true, this flag will prohibit the manifest from being downloaded for offline
401    /// installation with the winget download command.
402    #[cfg_attr(
403        feature = "serde",
404        serde(skip_serializing_if = "core::ops::Not::not", default)
405    )]
406    pub download_command_prohibited: bool,
407
408    /// This field controls what method is used to repair existing installations of packages.
409    ///
410    /// Specifying `modify` will use the `ModifyPath` string from the package's ARP data,
411    /// `uninstaller` will use the Uninstall string from the package's ARP data, and `installer`
412    /// will download and run the installer. In each case, the `Repair` value from
413    /// `InstallerSwitches` will be added as an argument when invoking the command to repair the
414    /// package.
415    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
416    pub repair_behavior: Option<RepairBehavior>,
417
418    /// This field controls the behavior of environment variables when installing portable packages
419    /// from an archive (i.e. `zip`).
420    ///
421    /// Specifying `true` will add the install location directly to the `PATH` environment variable.
422    /// Specifying `false` will use the default behavior of adding a symlink to the `links` folder,
423    /// if supported, or adding the install location directly to `PATH` if symlinks are not
424    /// supported.
425    #[cfg_attr(
426        feature = "serde",
427        serde(skip_serializing_if = "core::ops::Not::not", default)
428    )]
429    pub archive_binaries_depend_on_path: bool,
430
431    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
432    pub authentication: Option<Authentication>,
433
434    pub installers: Vec<Installer>,
435
436    /// The manifest type.
437    ///
438    /// Must have the value [`installer`]. The Microsoft community package repository validation
439    /// pipelines also use this value to determine appropriate validation rules when evaluating this
440    /// file.
441    ///
442    /// [`installer`]: ManifestType::Installer
443    #[cfg_attr(feature = "serde", serde(default = "ManifestType::installer"))]
444    pub manifest_type: ManifestType,
445
446    /// The manifest syntax version.
447    ///
448    /// Must have the value `1.10.0`. The Microsoft community package repository validation
449    /// pipelines also use this value to determine appropriate validation rules when evaluating this
450    /// file.
451    #[cfg_attr(feature = "serde", serde(default))]
452    pub manifest_version: ManifestVersion,
453}
454
455impl Manifest for InstallerManifest {
456    const SCHEMA: &'static str = "https://aka.ms/winget-manifest.installer.1.10.0.schema.json";
457    const TYPE: ManifestType = ManifestType::Installer;
458}
459
460impl InstallerManifest {
461    #[expect(
462        clippy::cognitive_complexity,
463        reason = "The resulting complexity is generated by a macro"
464    )]
465    pub fn optimize(&mut self) {
466        macro_rules! optimize_keys {
467            ($($($field:ident).+),* $(,)?) => {
468                #[inline]
469                fn default<T: Default>(_: &T) -> T {
470                    T::default()
471                }
472
473                $(
474                    if let Ok(field) = self
475                        .installers
476                        .iter_mut()
477                        .map(|installer| &mut installer.$($field).+)
478                        .all_equal_value()
479                    {
480                        self.$($field).+ = core::mem::take(r#field);
481                        for installer in &mut self.installers {
482                            installer.$($field).+ = default(&installer.$($field).+);
483                        }
484                    } else {
485                        self.$($field).+ = default(&self.$($field).+);
486                    }
487                )*
488            };
489        }
490
491        optimize_keys!(
492            locale,
493            platform,
494            minimum_os_version,
495            r#type,
496            nested_installer_type,
497            nested_installer_files,
498            scope,
499            install_modes,
500            switches.silent,
501            switches.silent_with_progress,
502            switches.interactive,
503            switches.install_location,
504            switches.log,
505            switches.upgrade,
506            switches.repair,
507            success_codes,
508            expected_return_codes,
509            upgrade_behavior,
510            commands,
511            protocols,
512            file_extensions,
513            dependencies.windows_features,
514            dependencies.windows_libraries,
515            dependencies.package,
516            dependencies.external,
517            package_family_name,
518            product_code,
519            capabilities,
520            restricted_capabilities,
521            markets,
522            aborts_terminal,
523            release_date,
524            install_location_required,
525            require_explicit_upgrade,
526            display_install_warnings,
527            unsupported_os_architectures,
528            unsupported_arguments,
529            apps_and_features_entries,
530            elevation_requirement,
531            installation_metadata,
532            download_command_prohibited,
533            repair_behavior,
534            archive_binaries_depend_on_path,
535        );
536
537        self.manifest_version = ManifestVersion::default();
538
539        self.installers.sort_unstable();
540        self.installers.dedup();
541    }
542}
543
544#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
545#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
546#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
547pub struct Installer {
548    /// The locale for an installer *not* the package meta-data.
549    ///
550    /// Some installers are compiled with locale or language specific properties. If this key is
551    /// present, it is used to represent the package locale for an installer.
552    #[cfg_attr(
553        feature = "serde",
554        serde(rename = "InstallerLocale", skip_serializing_if = "Option::is_none")
555    )]
556    pub locale: Option<LanguageTag>,
557
558    /// The Windows platform targeted by the installer.
559    ///
560    /// The Windows Package Manager currently supports "Windows.Desktop" and "Windows.Universal".
561    #[cfg_attr(
562        feature = "serde",
563        serde(skip_serializing_if = "Platform::is_empty", default)
564    )]
565    pub platform: Platform,
566
567    /// The minimum version of the Windows operating system supported by the package.
568    #[cfg_attr(
569        feature = "serde",
570        serde(rename = "MinimumOSVersion", skip_serializing_if = "Option::is_none")
571    )]
572    pub minimum_os_version: Option<MinimumOSVersion>,
573
574    /// The hardware architecture targeted by the installer.
575    ///
576    /// The Windows Package Manager will attempt to determine the best architecture to use. If
577    /// emulation is available and the native hardware architecture does not have a supported
578    /// installer, the emulated architecture may be used.
579    pub architecture: Architecture,
580
581    /// The installer type for the package.
582    ///
583    /// The Windows Package Manager supports [MSIX], [MSI], and executable installers. Some well
584    /// known formats ([Inno], [Nullsoft], [WiX], and [Burn]) provide standard sets of installer
585    /// switches to provide different installer experiences. Portable packages are supported as of
586    /// Windows Package Manager 1.3. Zip packages are supported as of Windows Package Manager 1.5.
587    ///
588    /// [MSIX]: https://docs.microsoft.com/windows/msix/overview
589    /// [MSI]: https://docs.microsoft.com/windows/win32/msi/windows-installer-portal
590    /// [Inno]: https://jrsoftware.org/isinfo.php
591    /// [Nullsoft]: https://sourceforge.net/projects/nsis
592    /// [WiX]: https://wixtoolset.org/
593    /// [Burn]: https://wixtoolset.org/docs/v3/bundle/
594    #[cfg_attr(
595        feature = "serde",
596        serde(rename = "InstallerType", skip_serializing_if = "Option::is_none")
597    )]
598    pub r#type: Option<InstallerType>,
599
600    /// The installer type of the file within the archive which will be used as the installer.
601    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
602    pub nested_installer_type: Option<NestedInstallerType>,
603
604    /// A list of all the installers to be executed within an archive.
605    #[cfg_attr(
606        feature = "serde",
607        serde(skip_serializing_if = "BTreeSet::is_empty", default)
608    )]
609    pub nested_installer_files: BTreeSet<NestedInstallerFiles>,
610
611    /// The scope the package is installed under.
612    ///
613    /// The two configurations are [`user`] and [`machine`]. Some installers support only one of
614    /// these scopes while others support both via arguments passed to the installer using
615    /// [`InstallerSwitches`].
616    ///
617    /// [`user`]: Scope::User
618    /// [`machine`]: Scope::Machine
619    /// [`InstallerSwitches`]: InstallerSwitches
620    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
621    pub scope: Option<Scope>,
622
623    /// The URL to download the installer.
624    #[cfg_attr(feature = "serde", serde(rename = "InstallerUrl"))]
625    pub url: DecodedUrl,
626
627    /// The SHA 256 hash for the installer. It is used to confirm the installer has not been
628    /// modified. The Windows Package Manager will compare the hash in the manifest with the
629    /// calculated hash of the installer after it has been downloaded.
630    #[cfg_attr(feature = "serde", serde(rename = "InstallerSha256"))]
631    pub sha_256: Sha256String,
632
633    /// The signature file (AppxSignature.p7x) inside an MSIX installer. It is used to provide
634    /// streaming install for MSIX packages.
635    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
636    pub signature_sha_256: Option<Sha256String>,
637
638    /// The install modes supported by the installer.
639    ///
640    /// The Microsoft community package repository requires a package support "silent" and
641    /// "silent with progress". The Windows Package Manager also supports "interactive" installers.
642    #[cfg_attr(
643        feature = "serde",
644        serde(skip_serializing_if = "InstallModes::is_empty", default)
645    )]
646    pub install_modes: InstallModes,
647
648    /// The set of switches passed to installers.
649    #[cfg_attr(
650        feature = "serde",
651        serde(
652            rename = "InstallerSwitches",
653            skip_serializing_if = "InstallerSwitches::is_empty",
654            default
655        )
656    )]
657    pub switches: InstallerSwitches,
658
659    /// Any status codes returned by the installer representing a success condition other than zero.
660    #[cfg_attr(
661        feature = "serde",
662        serde(
663            rename = "InstallerSuccessCodes",
664            skip_serializing_if = "BTreeSet::is_empty",
665            default
666        )
667    )]
668    pub success_codes: BTreeSet<InstallerSuccessCode>,
669
670    /// Any status codes returned by the installer representing a condition other than zero.
671    #[cfg_attr(
672        feature = "serde",
673        serde(skip_serializing_if = "BTreeSet::is_empty", default)
674    )]
675    pub expected_return_codes: BTreeSet<ExpectedReturnCodes>,
676
677    /// What the Windows Package Manager should do regarding the currently installed package during
678    /// a package upgrade.
679    ///
680    /// If the package should be uninstalled first, the [`uninstallPrevious`] value should be
681    /// specified. If the package should not be upgraded through `WinGet`, the [`deny`] value should
682    /// be specified.
683    ///
684    /// [`uninstallPrevious`]: UpgradeBehavior::UninstallPrevious
685    /// [`deny`]: UpgradeBehavior::Deny
686    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
687    pub upgrade_behavior: Option<UpgradeBehavior>,
688
689    /// Any commands or aliases used to execute the package after it has been installed.
690    #[cfg_attr(
691        feature = "serde",
692        serde(skip_serializing_if = "BTreeSet::is_empty", default)
693    )]
694    pub commands: BTreeSet<Command>,
695
696    /// Any protocols (i.e. URI schemes) supported by the package. For example: `["ftp", "ldap"]`.
697    /// Entries shouldn't have trailing colons. The Windows Package Manager does not support any
698    /// behavior related to protocols handled by a package.
699    #[cfg_attr(
700        feature = "serde",
701        serde(skip_serializing_if = "BTreeSet::is_empty", default)
702    )]
703    pub protocols: BTreeSet<Protocol>,
704
705    /// Any file extensions supported by the package.
706    ///
707    /// For example: `["html", "jpg"]`. Entries shouldn't have leading dots. The Windows Package
708    /// Manager does not support any behavior related to the file extensions supported by the
709    /// package.
710    #[cfg_attr(
711        feature = "serde",
712        serde(skip_serializing_if = "BTreeSet::is_empty", default)
713    )]
714    pub file_extensions: BTreeSet<FileExtension>,
715
716    /// Any dependencies required to install or run the package.
717    #[cfg_attr(
718        feature = "serde",
719        serde(skip_serializing_if = "Dependencies::is_empty", default)
720    )]
721    pub dependencies: Dependencies,
722
723    /// The [package family name] specified in an MSIX installer.
724    ///
725    /// This value is used to assist with matching packages from a source to the program installed
726    /// in Windows via Add / Remove Programs for list, and upgrade behavior.
727    ///
728    /// [package family name]: https://learn.microsoft.com/windows/apps/desktop/modernize/package-identity-overview#package-family-name
729    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
730    pub package_family_name: Option<PackageFamilyName>,
731
732    /// The [product code].
733    ///
734    /// This value is used to assist with matching packages from a source to the program installed
735    /// in Windows via Add / Remove Programs for list, and upgrade behavior.
736    ///
737    /// [product code]: https://learn.microsoft.com/windows/win32/msi/product-codes
738    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
739    pub product_code: Option<String>,
740
741    /// The capabilities provided by an MSIX package.
742    ///
743    /// More information is available for [App capability declarations].
744    ///
745    /// [App capability declarations]: https://docs.microsoft.com/windows/uwp/packaging/app-capability-declarations
746    #[cfg_attr(
747        feature = "serde",
748        serde(skip_serializing_if = "BTreeSet::is_empty", default)
749    )]
750    pub capabilities: BTreeSet<Capability>,
751
752    /// The restricted capabilities provided by an MSIX package.
753    ///
754    /// More information is available for [App capability declarations].
755    ///
756    /// [App capability declarations]: https://docs.microsoft.com/windows/uwp/packaging/app-capability-declarations
757    #[cfg_attr(
758        feature = "serde",
759        serde(skip_serializing_if = "BTreeSet::is_empty", default)
760    )]
761    pub restricted_capabilities: BTreeSet<RestrictedCapability>,
762
763    /// Any markets a package may or may not be installed in.
764    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
765    pub markets: Option<Markets>,
766
767    /// The behavior associated with installers that abort the terminal.
768    ///
769    /// This most often occurs when a user is performing an upgrade of the running terminal.
770    #[cfg_attr(
771        feature = "serde",
772        serde(
773            rename = "InstallerAbortsTerminal",
774            skip_serializing_if = "core::ops::Not::not",
775            default
776        )
777    )]
778    pub aborts_terminal: bool,
779
780    /// The release date for a package, in RFC 3339 / ISO 8601 format, i.e. "YYYY-MM-DD".
781    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
782    pub release_date: Option<NaiveDate>,
783
784    /// The requirement to have an install location specified.
785    ///
786    /// These installers are known to deploy files to the location the installer is executed in.
787    #[cfg_attr(
788        feature = "serde",
789        serde(skip_serializing_if = "core::ops::Not::not", default)
790    )]
791    pub install_location_required: bool,
792
793    /// Identifies packages that upgrade themselves.
794    ///
795    /// By default, they are excluded from `winget upgrade --all`.
796    #[cfg_attr(
797        feature = "serde",
798        serde(skip_serializing_if = "core::ops::Not::not", default)
799    )]
800    pub require_explicit_upgrade: bool,
801
802    /// Whether a warning message is displayed to the user prior to install or upgrade if the
803    /// package is known to interfere with any running applications.
804    #[cfg_attr(
805        feature = "serde",
806        serde(skip_serializing_if = "core::ops::Not::not", default)
807    )]
808    pub display_install_warnings: bool,
809
810    /// Any architectures a package is known not to be compatible with.
811    ///
812    /// Generally, this is associated with emulation modes.
813    #[cfg_attr(
814        feature = "serde",
815        serde(
816            rename = "UnsupportedOSArchitectures",
817            skip_serializing_if = "UnsupportedOSArchitecture::is_empty",
818            default
819        )
820    )]
821    pub unsupported_os_architectures: UnsupportedOSArchitecture,
822
823    /// The list of Windows Package Manager Client arguments the installer does not support.
824    ///
825    /// Only the `--log` and `--location` arguments can be specified as unsupported arguments for an
826    /// installer.
827    #[cfg_attr(
828        feature = "serde",
829        serde(skip_serializing_if = "UnsupportedArguments::is_empty", default)
830    )]
831    pub unsupported_arguments: UnsupportedArguments,
832
833    /// The values reported by Windows Apps & Features.
834    ///
835    /// When a package is installed, entries are made into the Windows Registry.
836    #[cfg_attr(
837        feature = "serde",
838        serde(skip_serializing_if = "Vec::is_empty", default)
839    )]
840    pub apps_and_features_entries: Vec<AppsAndFeaturesEntry>,
841
842    /// The scope in which scope a package is required to be executed under.
843    ///
844    /// Some packages require user level execution while others require administrative level
845    /// execution.
846    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
847    pub elevation_requirement: Option<ElevationRequirement>,
848
849    /// Allows for additional metadata to be used for deeper installation detection.
850    #[cfg_attr(
851        feature = "serde",
852        serde(skip_serializing_if = "InstallationMetadata::is_empty", default)
853    )]
854    pub installation_metadata: InstallationMetadata,
855
856    /// When true, this flag will prohibit the manifest from being downloaded for offline
857    /// installation with the winget download command.
858    #[cfg_attr(
859        feature = "serde",
860        serde(skip_serializing_if = "core::ops::Not::not", default)
861    )]
862    pub download_command_prohibited: bool,
863
864    /// This field controls what method is used to repair existing installations of packages.
865    ///
866    /// Specifying `modify` will use the `ModifyPath` string from the package's ARP data,
867    /// `uninstaller` will use the Uninstall string from the package's ARP data, and `installer`
868    /// will download and run the installer. In each case, the `Repair` value from
869    /// `InstallerSwitches` will be added as an argument when invoking the command to repair the
870    /// package.
871    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
872    pub repair_behavior: Option<RepairBehavior>,
873
874    /// This field controls the behavior of environment variables when installing portable packages
875    /// from an archive (i.e. `zip`).
876    ///
877    /// Specifying `true` will add the install location directly to the `PATH` environment variable.
878    /// Specifying `false` will use the default behavior of adding a symlink to the `links` folder,
879    /// if supported, or adding the install location directly to `PATH` if symlinks are not
880    /// supported.
881    #[cfg_attr(
882        feature = "serde",
883        serde(skip_serializing_if = "core::ops::Not::not", default)
884    )]
885    pub archive_binaries_depend_on_path: bool,
886
887    /// This field controls the authentication for Entra ID secured private sources.
888    ///
889    /// Resource and scope information can be included if a specific resource is needed to download
890    /// or install the package.
891    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
892    pub authentication: Option<Authentication>,
893}
894
895impl Installer {
896    /// Merges two installers.
897    ///
898    /// If a key of `self` is equal to its default, it will take the value from `other`. If the key
899    /// of `self` is not equal to its default, it will retain that value and the equivalent key in
900    /// `other` is ignored.
901    #[expect(
902        clippy::cognitive_complexity,
903        reason = "The resulting complexity is generated by a macro"
904    )]
905    #[must_use]
906    pub fn merge_with(mut self, other: Self) -> Self {
907        macro_rules! merge_keys {
908            (
909                $($($field:ident).+),*,
910                [$($switch:ident),* $(,)?]$(,)?
911            ) => {
912                #[inline]
913                fn default<T: Default>(_: &T) -> T {
914                    T::default()
915                }
916
917                $(
918                    if self.$($field).+ == default(&self.$($field).+) {
919                        self.$($field).+ = other.$($field).+;
920                    }
921                )*
922
923                $(
924                    if let (Some(switch), Some(other_switch)) = (
925                        self.switches.$switch.as_mut(),
926                        other.switches.$switch.as_ref(),
927                    ) {
928                        for part in other_switch {
929                            if !switch.contains(part) {
930                                switch.push(part.clone());
931                            }
932                        }
933                    }
934                )*
935            };
936        }
937
938        merge_keys!(
939            locale,
940            platform,
941            minimum_os_version,
942            r#type,
943            nested_installer_type,
944            nested_installer_files,
945            scope,
946            install_modes,
947            success_codes,
948            expected_return_codes,
949            upgrade_behavior,
950            commands,
951            protocols,
952            file_extensions,
953            dependencies,
954            package_family_name,
955            product_code,
956            capabilities,
957            restricted_capabilities,
958            markets,
959            aborts_terminal,
960            release_date,
961            install_location_required,
962            require_explicit_upgrade,
963            display_install_warnings,
964            unsupported_os_architectures,
965            unsupported_arguments,
966            apps_and_features_entries,
967            elevation_requirement,
968            installation_metadata,
969            download_command_prohibited,
970            repair_behavior,
971            archive_binaries_depend_on_path,
972            [
973                silent,
974                silent_with_progress,
975                interactive,
976                install_location,
977                log,
978                upgrade,
979                custom,
980                repair
981            ],
982        );
983
984        self
985    }
986}
987
988#[cfg(test)]
989mod tests {
990    use alloc::vec;
991
992    use crate::{
993        installer::{Architecture, Installer, InstallerManifest, InstallerSwitches},
994        shared::LanguageTag,
995    };
996
997    #[test]
998    fn optimize_duplicate_locale() {
999        let mut manifest = InstallerManifest {
1000            installers: vec![
1001                Installer {
1002                    locale: Some("en-US".parse::<LanguageTag>().unwrap()),
1003                    architecture: Architecture::X86,
1004                    ..Installer::default()
1005                },
1006                Installer {
1007                    locale: Some("en-US".parse::<LanguageTag>().unwrap()),
1008                    architecture: Architecture::X64,
1009                    ..Installer::default()
1010                },
1011            ],
1012            ..InstallerManifest::default()
1013        };
1014
1015        manifest.optimize();
1016
1017        assert_eq!(
1018            manifest,
1019            InstallerManifest {
1020                locale: Some("en-US".parse::<LanguageTag>().unwrap()),
1021                installers: vec![
1022                    Installer {
1023                        architecture: Architecture::X86,
1024                        ..Installer::default()
1025                    },
1026                    Installer {
1027                        architecture: Architecture::X64,
1028                        ..Installer::default()
1029                    },
1030                ],
1031                ..InstallerManifest::default()
1032            }
1033        )
1034    }
1035
1036    #[test]
1037    fn optimize_duplicate_switch() {
1038        let mut manifest = InstallerManifest {
1039            installers: vec![
1040                Installer {
1041                    architecture: Architecture::X86,
1042                    switches: InstallerSwitches::new()
1043                        .silent("--silent")
1044                        .custom("--custom"),
1045                    ..Installer::default()
1046                },
1047                Installer {
1048                    architecture: Architecture::X64,
1049                    switches: InstallerSwitches::new().silent("--silent"),
1050                    ..Installer::default()
1051                },
1052            ],
1053            ..InstallerManifest::default()
1054        };
1055
1056        manifest.optimize();
1057
1058        assert_eq!(
1059            manifest,
1060            InstallerManifest {
1061                switches: InstallerSwitches::new().silent("--silent"),
1062                installers: vec![
1063                    Installer {
1064                        architecture: Architecture::X86,
1065                        switches: InstallerSwitches::new().custom("--custom"),
1066                        ..Installer::default()
1067                    },
1068                    Installer {
1069                        architecture: Architecture::X64,
1070                        ..Installer::default()
1071                    },
1072                ],
1073                ..InstallerManifest::default()
1074            }
1075        )
1076    }
1077}