winget_types/installer/
mod.rs

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