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}