Skip to main content

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