winget_types/installer/
mod.rs

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