winget_types/installer/
mod.rs

1#![expect(clippy::struct_excessive_bools)]
2
3mod apps_and_features_entries;
4mod architecture;
5pub mod authentication;
6mod capability;
7mod channel;
8mod command;
9mod dependencies;
10mod elevation_requirement;
11mod expected_return_codes;
12mod file_extension;
13mod install_modes;
14mod installation_metadata;
15mod installer_return_code;
16mod installer_type;
17mod market;
18mod minimum_os_version;
19mod nested;
20mod platform;
21mod protocol;
22mod repair_behavior;
23mod return_response;
24mod scope;
25pub mod switches;
26mod unsupported_arguments;
27mod unsupported_os_architectures;
28mod upgrade_behavior;
29
30use alloc::{collections::BTreeSet, string::String, vec::Vec};
31
32pub use apps_and_features_entries::{AppsAndFeaturesEntries, AppsAndFeaturesEntry};
33pub use architecture::{Architecture, ParseArchitectureError};
34pub use authentication::Authentication;
35pub use capability::{Capability, CapabilityError, RestrictedCapability};
36pub use channel::{Channel, ChannelError};
37pub use command::{Command, CommandError};
38pub use dependencies::{Dependencies, PackageDependencies};
39pub use elevation_requirement::ElevationRequirement;
40pub use expected_return_codes::ExpectedReturnCodes;
41pub use file_extension::{FileExtension, FileExtensionError};
42pub use install_modes::InstallModes;
43pub use installation_metadata::InstallationMetadata;
44pub use installer_return_code::{InstallerReturnCode, InstallerSuccessCode};
45pub use installer_type::InstallerType;
46use itertools::Itertools;
47pub use market::{Market, MarketError, Markets, MarketsError};
48pub use minimum_os_version::{MinimumOSVersion, MinimumOSVersionError};
49use nested::installer_type::NestedInstallerType;
50pub use nested::{
51    PortableCommandAlias, PortableCommandAliasError, installer_files::NestedInstallerFiles,
52};
53pub use package_family_name::PackageFamilyName;
54pub use platform::{Platform, PlatformParseError};
55pub use protocol::{Protocol, ProtocolError};
56pub use repair_behavior::RepairBehavior;
57pub use 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<'static>>,
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 = "AppsAndFeaturesEntries::is_empty", default)
394    )]
395    pub apps_and_features_entries: AppsAndFeaturesEntries,
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.custom,
515            switches.repair,
516            success_codes,
517            expected_return_codes,
518            upgrade_behavior,
519            commands,
520            protocols,
521            file_extensions,
522            dependencies.windows_features,
523            dependencies.windows_libraries,
524            dependencies.package,
525            dependencies.external,
526            package_family_name,
527            product_code,
528            capabilities,
529            restricted_capabilities,
530            markets,
531            aborts_terminal,
532            release_date,
533            install_location_required,
534            require_explicit_upgrade,
535            display_install_warnings,
536            unsupported_os_architectures,
537            unsupported_arguments,
538            apps_and_features_entries,
539            elevation_requirement,
540            installation_metadata,
541            download_command_prohibited,
542            repair_behavior,
543            archive_binaries_depend_on_path,
544        );
545
546        self.manifest_version = ManifestVersion::default();
547
548        self.installers.sort_unstable();
549        self.installers.dedup();
550    }
551}
552
553#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Ord, PartialOrd)]
554#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
555#[cfg_attr(feature = "serde", serde(rename_all = "PascalCase"))]
556pub struct Installer {
557    /// The locale for an installer *not* the package meta-data.
558    ///
559    /// Some installers are compiled with locale or language specific properties. If this key is
560    /// present, it is used to represent the package locale for an installer.
561    #[cfg_attr(
562        feature = "serde",
563        serde(rename = "InstallerLocale", skip_serializing_if = "Option::is_none")
564    )]
565    pub locale: Option<LanguageTag>,
566
567    /// The Windows platform targeted by the installer.
568    ///
569    /// The Windows Package Manager currently supports "Windows.Desktop" and "Windows.Universal".
570    #[cfg_attr(
571        feature = "serde",
572        serde(skip_serializing_if = "Platform::is_empty", default)
573    )]
574    pub platform: Platform,
575
576    /// The minimum version of the Windows operating system supported by the package.
577    #[cfg_attr(
578        feature = "serde",
579        serde(rename = "MinimumOSVersion", skip_serializing_if = "Option::is_none")
580    )]
581    pub minimum_os_version: Option<MinimumOSVersion>,
582
583    /// The hardware architecture targeted by the installer.
584    ///
585    /// The Windows Package Manager will attempt to determine the best architecture to use. If
586    /// emulation is available and the native hardware architecture does not have a supported
587    /// installer, the emulated architecture may be used.
588    pub architecture: Architecture,
589
590    /// The installer type for the package.
591    ///
592    /// The Windows Package Manager supports [MSIX], [MSI], and executable installers. Some well
593    /// known formats ([Inno], [Nullsoft], [WiX], and [Burn]) provide standard sets of installer
594    /// switches to provide different installer experiences. Portable packages are supported as of
595    /// Windows Package Manager 1.3. Zip packages are supported as of Windows Package Manager 1.5.
596    ///
597    /// [MSIX]: https://docs.microsoft.com/windows/msix/overview
598    /// [MSI]: https://docs.microsoft.com/windows/win32/msi/windows-installer-portal
599    /// [Inno]: https://jrsoftware.org/isinfo.php
600    /// [Nullsoft]: https://sourceforge.net/projects/nsis
601    /// [WiX]: https://wixtoolset.org/
602    /// [Burn]: https://wixtoolset.org/docs/v3/bundle/
603    #[cfg_attr(
604        feature = "serde",
605        serde(rename = "InstallerType", skip_serializing_if = "Option::is_none")
606    )]
607    pub r#type: Option<InstallerType>,
608
609    /// The installer type of the file within the archive which will be used as the installer.
610    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
611    pub nested_installer_type: Option<NestedInstallerType>,
612
613    /// A list of all the installers to be executed within an archive.
614    #[cfg_attr(
615        feature = "serde",
616        serde(skip_serializing_if = "BTreeSet::is_empty", default)
617    )]
618    pub nested_installer_files: BTreeSet<NestedInstallerFiles>,
619
620    /// The scope the package is installed under.
621    ///
622    /// The two configurations are [`user`] and [`machine`]. Some installers support only one of
623    /// these scopes while others support both via arguments passed to the installer using
624    /// [`InstallerSwitches`].
625    ///
626    /// [`user`]: Scope::User
627    /// [`machine`]: Scope::Machine
628    /// [`InstallerSwitches`]: InstallerSwitches
629    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
630    pub scope: Option<Scope>,
631
632    /// The URL to download the installer.
633    #[cfg_attr(feature = "serde", serde(rename = "InstallerUrl"))]
634    pub url: DecodedUrl,
635
636    /// The SHA 256 hash for the installer. It is used to confirm the installer has not been
637    /// modified. The Windows Package Manager will compare the hash in the manifest with the
638    /// calculated hash of the installer after it has been downloaded.
639    #[cfg_attr(feature = "serde", serde(rename = "InstallerSha256"))]
640    pub sha_256: Sha256String,
641
642    /// The signature file (AppxSignature.p7x) inside an MSIX installer. It is used to provide
643    /// streaming install for MSIX packages.
644    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
645    pub signature_sha_256: Option<Sha256String>,
646
647    /// The install modes supported by the installer.
648    ///
649    /// The Microsoft community package repository requires a package support "silent" and
650    /// "silent with progress". The Windows Package Manager also supports "interactive" installers.
651    #[cfg_attr(
652        feature = "serde",
653        serde(skip_serializing_if = "InstallModes::is_empty", default)
654    )]
655    pub install_modes: InstallModes,
656
657    /// The set of switches passed to installers.
658    #[cfg_attr(
659        feature = "serde",
660        serde(
661            rename = "InstallerSwitches",
662            skip_serializing_if = "InstallerSwitches::is_empty",
663            default
664        )
665    )]
666    pub switches: InstallerSwitches,
667
668    /// Any status codes returned by the installer representing a success condition other than zero.
669    #[cfg_attr(
670        feature = "serde",
671        serde(
672            rename = "InstallerSuccessCodes",
673            skip_serializing_if = "BTreeSet::is_empty",
674            default
675        )
676    )]
677    pub success_codes: BTreeSet<InstallerSuccessCode>,
678
679    /// Any status codes returned by the installer representing a condition other than zero.
680    #[cfg_attr(
681        feature = "serde",
682        serde(skip_serializing_if = "BTreeSet::is_empty", default)
683    )]
684    pub expected_return_codes: BTreeSet<ExpectedReturnCodes>,
685
686    /// What the Windows Package Manager should do regarding the currently installed package during
687    /// a package upgrade.
688    ///
689    /// If the package should be uninstalled first, the [`uninstallPrevious`] value should be
690    /// specified. If the package should not be upgraded through `WinGet`, the [`deny`] value should
691    /// be specified.
692    ///
693    /// [`uninstallPrevious`]: UpgradeBehavior::UninstallPrevious
694    /// [`deny`]: UpgradeBehavior::Deny
695    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
696    pub upgrade_behavior: Option<UpgradeBehavior>,
697
698    /// Any commands or aliases used to execute the package after it has been installed.
699    #[cfg_attr(
700        feature = "serde",
701        serde(skip_serializing_if = "BTreeSet::is_empty", default)
702    )]
703    pub commands: BTreeSet<Command>,
704
705    /// Any protocols (i.e. URI schemes) supported by the package. For example: `["ftp", "ldap"]`.
706    /// Entries shouldn't have trailing colons. The Windows Package Manager does not support any
707    /// behavior related to protocols handled by a package.
708    #[cfg_attr(
709        feature = "serde",
710        serde(skip_serializing_if = "BTreeSet::is_empty", default)
711    )]
712    pub protocols: BTreeSet<Protocol>,
713
714    /// Any file extensions supported by the package.
715    ///
716    /// For example: `["html", "jpg"]`. Entries shouldn't have leading dots. The Windows Package
717    /// Manager does not support any behavior related to the file extensions supported by the
718    /// package.
719    #[cfg_attr(
720        feature = "serde",
721        serde(skip_serializing_if = "BTreeSet::is_empty", default)
722    )]
723    pub file_extensions: BTreeSet<FileExtension>,
724
725    /// Any dependencies required to install or run the package.
726    #[cfg_attr(
727        feature = "serde",
728        serde(skip_serializing_if = "Dependencies::is_empty", default)
729    )]
730    pub dependencies: Dependencies,
731
732    /// The [package family name] specified in an MSIX installer.
733    ///
734    /// This value is used to assist with matching packages from a source to the program installed
735    /// in Windows via Add / Remove Programs for list, and upgrade behavior.
736    ///
737    /// [package family name]: https://learn.microsoft.com/windows/apps/desktop/modernize/package-identity-overview#package-family-name
738    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
739    pub package_family_name: Option<PackageFamilyName<'static>>,
740
741    /// The [product code].
742    ///
743    /// This value is used to assist with matching packages from a source to the program installed
744    /// in Windows via Add / Remove Programs for list, and upgrade behavior.
745    ///
746    /// [product code]: https://learn.microsoft.com/windows/win32/msi/product-codes
747    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
748    pub product_code: Option<String>,
749
750    /// The capabilities provided by an MSIX package.
751    ///
752    /// More information is available for [App capability declarations].
753    ///
754    /// [App capability declarations]: https://docs.microsoft.com/windows/uwp/packaging/app-capability-declarations
755    #[cfg_attr(
756        feature = "serde",
757        serde(skip_serializing_if = "BTreeSet::is_empty", default)
758    )]
759    pub capabilities: BTreeSet<Capability>,
760
761    /// The restricted capabilities provided by an MSIX package.
762    ///
763    /// More information is available for [App capability declarations].
764    ///
765    /// [App capability declarations]: https://docs.microsoft.com/windows/uwp/packaging/app-capability-declarations
766    #[cfg_attr(
767        feature = "serde",
768        serde(skip_serializing_if = "BTreeSet::is_empty", default)
769    )]
770    pub restricted_capabilities: BTreeSet<RestrictedCapability>,
771
772    /// Any markets a package may or may not be installed in.
773    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
774    pub markets: Option<Markets>,
775
776    /// The behavior associated with installers that abort the terminal.
777    ///
778    /// This most often occurs when a user is performing an upgrade of the running terminal.
779    #[cfg_attr(
780        feature = "serde",
781        serde(
782            rename = "InstallerAbortsTerminal",
783            skip_serializing_if = "core::ops::Not::not",
784            default
785        )
786    )]
787    pub aborts_terminal: bool,
788
789    /// The release date for a package, in RFC 3339 / ISO 8601 format, i.e. "YYYY-MM-DD".
790    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
791    pub release_date: Option<Date>,
792
793    /// The requirement to have an install location specified.
794    ///
795    /// These installers are known to deploy files to the location the installer is executed in.
796    #[cfg_attr(
797        feature = "serde",
798        serde(skip_serializing_if = "core::ops::Not::not", default)
799    )]
800    pub install_location_required: bool,
801
802    /// Identifies packages that upgrade themselves.
803    ///
804    /// By default, they are excluded from `winget upgrade --all`.
805    #[cfg_attr(
806        feature = "serde",
807        serde(skip_serializing_if = "core::ops::Not::not", default)
808    )]
809    pub require_explicit_upgrade: bool,
810
811    /// Whether a warning message is displayed to the user prior to install or upgrade if the
812    /// package is known to interfere with any running applications.
813    #[cfg_attr(
814        feature = "serde",
815        serde(skip_serializing_if = "core::ops::Not::not", default)
816    )]
817    pub display_install_warnings: bool,
818
819    /// Any architectures a package is known not to be compatible with.
820    ///
821    /// Generally, this is associated with emulation modes.
822    #[cfg_attr(
823        feature = "serde",
824        serde(
825            rename = "UnsupportedOSArchitectures",
826            skip_serializing_if = "UnsupportedOSArchitecture::is_empty",
827            default
828        )
829    )]
830    pub unsupported_os_architectures: UnsupportedOSArchitecture,
831
832    /// The list of Windows Package Manager Client arguments the installer does not support.
833    ///
834    /// Only the `--log` and `--location` arguments can be specified as unsupported arguments for an
835    /// installer.
836    #[cfg_attr(
837        feature = "serde",
838        serde(skip_serializing_if = "UnsupportedArguments::is_empty", default)
839    )]
840    pub unsupported_arguments: UnsupportedArguments,
841
842    /// The values reported by Windows Apps & Features.
843    ///
844    /// When a package is installed, entries are made into the Windows Registry.
845    #[cfg_attr(
846        feature = "serde",
847        serde(skip_serializing_if = "AppsAndFeaturesEntries::is_empty", default)
848    )]
849    pub apps_and_features_entries: AppsAndFeaturesEntries,
850
851    /// The scope in which scope a package is required to be executed under.
852    ///
853    /// Some packages require user level execution while others require administrative level
854    /// execution.
855    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
856    pub elevation_requirement: Option<ElevationRequirement>,
857
858    /// Allows for additional metadata to be used for deeper installation detection.
859    #[cfg_attr(
860        feature = "serde",
861        serde(skip_serializing_if = "InstallationMetadata::is_empty", default)
862    )]
863    pub installation_metadata: InstallationMetadata,
864
865    /// When true, this flag will prohibit the manifest from being downloaded for offline
866    /// installation with the winget download command.
867    #[cfg_attr(
868        feature = "serde",
869        serde(skip_serializing_if = "core::ops::Not::not", default)
870    )]
871    pub download_command_prohibited: bool,
872
873    /// This field controls what method is used to repair existing installations of packages.
874    ///
875    /// Specifying `modify` will use the `ModifyPath` string from the package's ARP data,
876    /// `uninstaller` will use the Uninstall string from the package's ARP data, and `installer`
877    /// will download and run the installer. In each case, the `Repair` value from
878    /// `InstallerSwitches` will be added as an argument when invoking the command to repair the
879    /// package.
880    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
881    pub repair_behavior: Option<RepairBehavior>,
882
883    /// This field controls the behavior of environment variables when installing portable packages
884    /// from an archive (i.e. `zip`).
885    ///
886    /// Specifying `true` will add the install location directly to the `PATH` environment variable.
887    /// Specifying `false` will use the default behavior of adding a symlink to the `links` folder,
888    /// if supported, or adding the install location directly to `PATH` if symlinks are not
889    /// supported.
890    #[cfg_attr(
891        feature = "serde",
892        serde(skip_serializing_if = "core::ops::Not::not", default)
893    )]
894    pub archive_binaries_depend_on_path: bool,
895
896    /// This field controls the authentication for Entra ID secured private sources.
897    ///
898    /// Resource and scope information can be included if a specific resource is needed to download
899    /// or install the package.
900    #[cfg_attr(feature = "serde", serde(skip_serializing_if = "Option::is_none"))]
901    pub authentication: Option<Authentication>,
902}
903
904impl Installer {
905    /// Merges two installers.
906    ///
907    /// If a key of `self` is equal to its default, it will take the value from `other`. If the key
908    /// of `self` is not equal to its default, it will retain that value and the equivalent key in
909    /// `other` is ignored.
910    #[expect(
911        clippy::cognitive_complexity,
912        reason = "The resulting complexity is generated by a macro"
913    )]
914    #[must_use]
915    pub fn merge_with(mut self, other: Self) -> Self {
916        macro_rules! merge_keys {
917            (
918                $($($field:ident).+),*,
919                [$($switch:ident),* $(,)?]$(,)?
920            ) => {
921                #[inline]
922                fn default<T: Default>(_: &T) -> T {
923                    T::default()
924                }
925
926                $(
927                    if self.$($field).+ == default(&self.$($field).+) {
928                        self.$($field).+ = other.$($field).+;
929                    }
930                )*
931
932                $(
933                    match (&mut self.switches.$switch, &other.switches.$switch) {
934                        (None, Some(other_switch)) => {
935                            self.switches.$switch = Some(other_switch.clone());
936                        },
937                        (Some(self_switch), Some(other_switch)) => {
938                            for part in other_switch {
939                                if !self_switch.contains(part) {
940                                    self_switch.push(part.clone());
941                                }
942                            }
943                        },
944                        _ => {}
945                    }
946                )*
947            };
948        }
949
950        merge_keys!(
951            locale,
952            platform,
953            minimum_os_version,
954            r#type,
955            nested_installer_type,
956            nested_installer_files,
957            scope,
958            install_modes,
959            success_codes,
960            expected_return_codes,
961            upgrade_behavior,
962            commands,
963            protocols,
964            file_extensions,
965            dependencies,
966            package_family_name,
967            product_code,
968            capabilities,
969            restricted_capabilities,
970            markets,
971            aborts_terminal,
972            release_date,
973            install_location_required,
974            require_explicit_upgrade,
975            display_install_warnings,
976            unsupported_os_architectures,
977            unsupported_arguments,
978            apps_and_features_entries,
979            elevation_requirement,
980            installation_metadata,
981            download_command_prohibited,
982            repair_behavior,
983            archive_binaries_depend_on_path,
984            [
985                silent,
986                silent_with_progress,
987                interactive,
988                install_location,
989                log,
990                upgrade,
991                custom,
992                repair
993            ],
994        );
995
996        self
997    }
998}
999
1000#[cfg(test)]
1001mod tests {
1002    use alloc::vec;
1003
1004    use crate::{
1005        installer::{Architecture, Installer, InstallerManifest, InstallerSwitches},
1006        shared::LanguageTag,
1007    };
1008
1009    #[test]
1010    fn optimize_duplicate_locale() {
1011        let mut manifest = InstallerManifest {
1012            installers: vec![
1013                Installer {
1014                    locale: Some("en-US".parse::<LanguageTag>().unwrap()),
1015                    architecture: Architecture::X86,
1016                    ..Installer::default()
1017                },
1018                Installer {
1019                    locale: Some("en-US".parse::<LanguageTag>().unwrap()),
1020                    architecture: Architecture::X64,
1021                    ..Installer::default()
1022                },
1023            ],
1024            ..InstallerManifest::default()
1025        };
1026
1027        manifest.optimize();
1028
1029        assert_eq!(
1030            manifest,
1031            InstallerManifest {
1032                locale: Some("en-US".parse::<LanguageTag>().unwrap()),
1033                installers: vec![
1034                    Installer {
1035                        architecture: Architecture::X86,
1036                        ..Installer::default()
1037                    },
1038                    Installer {
1039                        architecture: Architecture::X64,
1040                        ..Installer::default()
1041                    },
1042                ],
1043                ..InstallerManifest::default()
1044            }
1045        )
1046    }
1047
1048    #[test]
1049    fn optimize_duplicate_switch() {
1050        let mut manifest = InstallerManifest {
1051            installers: vec![
1052                Installer {
1053                    architecture: Architecture::X86,
1054                    switches: InstallerSwitches::builder()
1055                        .maybe_silent("--silent".parse().ok())
1056                        .maybe_custom("--custom".parse().ok())
1057                        .build(),
1058                    ..Installer::default()
1059                },
1060                Installer {
1061                    architecture: Architecture::X64,
1062                    switches: InstallerSwitches::builder()
1063                        .maybe_silent("--silent".parse().ok())
1064                        .build(),
1065                    ..Installer::default()
1066                },
1067            ],
1068            ..InstallerManifest::default()
1069        };
1070
1071        manifest.optimize();
1072
1073        assert_eq!(
1074            manifest,
1075            InstallerManifest {
1076                switches: InstallerSwitches::builder()
1077                    .maybe_silent("--silent".parse().ok())
1078                    .build(),
1079                installers: vec![
1080                    Installer {
1081                        architecture: Architecture::X86,
1082                        switches: InstallerSwitches::builder()
1083                            .maybe_custom("--custom".parse().ok())
1084                            .build(),
1085                        ..Installer::default()
1086                    },
1087                    Installer {
1088                        architecture: Architecture::X64,
1089                        ..Installer::default()
1090                    },
1091                ],
1092                ..InstallerManifest::default()
1093            }
1094        )
1095    }
1096}