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