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