Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* MCP server available; run `winget mcp` for assistance on configuring your client.
* App Installer now uses WinUI 3. The package dependency on WinUI 2 has been replaced by a dependency on the Windows App Runtime 1.7.
* Manifest schema and validation updated to v1.12. This version update adds "Font" as an InstallerType and NestedInstallerType.
* Added UninstallerSwitches and UninstallerSuccessCodes to the manifest schema for v1.12.

## Bug Fixes
* Manifest validation no longer fails using `UTF-8 BOM` encoding when the schema header is on the first line
Expand Down
66 changes: 66 additions & 0 deletions schemas/JSON/manifests/latest/manifest.installer.latest.json
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,60 @@
"uniqueItems": true,
"description": "List of additional non-zero installer success exit codes other than known default values by winget"
},
"UninstallerSwitches": {
"type": "object",
"properties": {
"Silent": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "Silent is the value that should be passed to the uninstaller when user chooses a silent or quiet uninstall"
},
"SilentWithProgress": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "SilentWithProgress is the value that should be passed to the uninstaller when user chooses a non-interactive uninstall"
},
"Interactive": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "Interactive is the value that should be passed to the uninstaller when user chooses an interactive uninstall"
},
"Log": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "Log is the value passed to the uninstaller for custom log file path. <LOGPATH> token can be included in the switch value so that winget will replace the token with user provided path"
},
"Custom": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 2048,
"description": "Custom switches will be passed directly to the uninstaller by winget"
}
}
},
"UninstallerReturnCode": {
"type": "integer",
"format": "long",
"not": {
"enum": [ 0 ]
},
"minimum": -2147483648,
"maximum": 4294967295,
"description": "An exit code that can be returned by the uninstaller after execution"
},
"UninstallerSuccessCodes": {
"type": [ "array", "null" ],
"items": {
"$ref": "#/definitions/UninstallerReturnCode"
},
"maxItems": 16,
"uniqueItems": true,
"description": "List of additional non-zero uninstaller success exit codes other than known default values by winget"
},
"ExpectedReturnCodes": {
"type": [ "array", "null" ],
"items": {
Expand Down Expand Up @@ -765,6 +819,12 @@
},
"Authentication": {
"$ref": "#/definitions/Authentication"
},
"UninstallerSwitches": {
"$ref": "#/definitions/UninstallerSwitches"
},
"UninstallerSuccessCodes": {
"$ref": "#/definitions/UninstallerSuccessCodes"
}
},
"required": [
Expand Down Expand Up @@ -890,6 +950,12 @@
"Authentication": {
"$ref": "#/definitions/Authentication"
},
"UninstallerSwitches": {
"$ref": "#/definitions/UninstallerSwitches"
},
"UninstallerSuccessCodes": {
"$ref": "#/definitions/UninstallerSuccessCodes"
},
"Installers": {
"type": "array",
"items": {
Expand Down
66 changes: 66 additions & 0 deletions schemas/JSON/manifests/latest/manifest.singleton.latest.json
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,60 @@
"uniqueItems": true,
"description": "List of additional non-zero installer success exit codes other than known default values by winget"
},
"UninstallerSwitches": {
"type": "object",
"properties": {
"Silent": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "Silent is the value that should be passed to the uninstaller when user chooses a silent or quiet uninstall"
},
"SilentWithProgress": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "SilentWithProgress is the value that should be passed to the uninstaller when user chooses a non-interactive uninstall"
},
"Interactive": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "Interactive is the value that should be passed to the uninstaller when user chooses an interactive uninstall"
},
"Log": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 512,
"description": "Log is the value passed to the uninstaller for custom log file path. <LOGPATH> token can be included in the switch value so that winget will replace the token with user provided path"
},
"Custom": {
"type": [ "string", "null" ],
"minLength": 1,
"maxLength": 2048,
"description": "Custom switches will be passed directly to the uninstaller by winget"
}
}
},
"UninstallerReturnCode": {
"type": "integer",
"format": "long",
"not": {
"enum": [ 0 ]
},
"minimum": -2147483648,
"maximum": 4294967295,
"description": "An exit code that can be returned by the uninstaller after execution"
},
"UninstallerSuccessCodes": {
"type": [ "array", "null" ],
"items": {
"$ref": "#/definitions/UninstallerReturnCode"
},
"maxItems": 16,
"uniqueItems": true,
"description": "List of additional non-zero uninstaller success exit codes other than known default values by winget"
},
"ExpectedReturnCodes": {
"type": [ "array", "null" ],
"items": {
Expand Down Expand Up @@ -866,6 +920,12 @@
},
"Authentication": {
"$ref": "#/definitions/Authentication"
},
"UninstallerSwitches": {
"$ref": "#/definitions/UninstallerSwitches"
},
"UninstallerSuccessCodes": {
"$ref": "#/definitions/UninstallerSuccessCodes"
}
},
"required": [
Expand Down Expand Up @@ -1114,6 +1174,12 @@
"Authentication": {
"$ref": "#/definitions/Authentication"
},
"UninstallerSwitches": {
"$ref": "#/definitions/UninstallerSwitches"
},
"UninstallerSuccessCodes": {
"$ref": "#/definitions/UninstallerSuccessCodes"
},
"Installers": {
"type": "array",
"items": {
Expand Down
18 changes: 18 additions & 0 deletions src/AppInstallerCLITests/RestInterface_1_12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,16 @@ namespace
"InstallerSuccessCodes": [
0
],
"UninstallerSwitches": {
"Silent": "/s",
"SilentWithProgress": "/s",
"Interactive": "/i",
"Log": "/l",
"Custom": "/custom"
},
"UninstallerSuccessCodes": [
1
],
"UpgradeBehavior": "deny",
"Commands": [
"command1"
Expand Down Expand Up @@ -337,6 +347,14 @@ namespace
REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair");
REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1);
REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0);
REQUIRE(actualInstaller.UninstallerSwitches.size() == 5);
REQUIRE(actualInstaller.UninstallerSwitches.at(UninstallerSwitchType::Silent) == "/s");
REQUIRE(actualInstaller.UninstallerSwitches.at(UninstallerSwitchType::SilentWithProgress) == "/s");
REQUIRE(actualInstaller.UninstallerSwitches.at(UninstallerSwitchType::Interactive) == "/i");
REQUIRE(actualInstaller.UninstallerSwitches.at(UninstallerSwitchType::Log) == "/l");
REQUIRE(actualInstaller.UninstallerSwitches.at(UninstallerSwitchType::Custom) == "/custom");
REQUIRE(actualInstaller.UninstallerSuccessCodes.size() == 1);
REQUIRE(actualInstaller.UninstallerSuccessCodes.at(0) == 1);
REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny);
REQUIRE(actualInstaller.Commands.at(0) == "command1");
REQUIRE(actualInstaller.Protocols.at(0) == "protocol1");
Expand Down
15 changes: 15 additions & 0 deletions src/AppInstallerCLITests/TestData/ManifestV1_12-Singleton.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,18 @@ InstallerSwitches:
InstallLocation: /dir=<INSTALLPATH>
Upgrade: /upgrade
Repair: /repair
UninstallerSwitches:
Custom: /custom
SilentWithProgress: /silentwithprogress
Silent: /silence
Interactive: /interactive
Log: /log=<LOGPATH>
InstallerSuccessCodes:
- 1
- 0x80070005
UninstallerSuccessCodes:
- 2
- 0x80070002
UpgradeBehavior: uninstallPrevious
RepairBehavior: modify
Commands:
Expand Down Expand Up @@ -150,6 +159,12 @@ Installers:
InstallLocation: /d=<INSTALLPATH>
Upgrade: /u
Repair: /r
UninstallerSwitches:
Custom: /c
SilentWithProgress: /sp
Silent: /s
Interactive: /i
Log: /l=<LOGPATH>
UpgradeBehavior: install
Commands:
- makemsixPreview
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,18 @@ InstallerSwitches:
InstallLocation: /dir=<INSTALLPATH>
Upgrade: /upgrade
Repair: /repair
UninstallerSwitches:
Custom: /custom
SilentWithProgress: /silentwithprogress
Silent: /silence
Interactive: /interactive
Log: /log=<LOGPATH>
InstallerSuccessCodes:
- 1
- 0x80070005
UninstallerSuccessCodes:
- 2
- 0x80070002
UpgradeBehavior: uninstallPrevious
RepairBehavior: modify
Commands:
Expand Down Expand Up @@ -115,6 +124,12 @@ Installers:
InstallLocation: /d=<INSTALLPATH>
Upgrade: /u
Repair: /r
UninstallerSwitches:
Custom: /c
SilentWithProgress: /sp
Silent: /s
Interactive: /i
Log: /l=<LOGPATH>
UpgradeBehavior: install
Commands:
- makemsixPreview
Expand Down Expand Up @@ -170,6 +185,9 @@ Installers:
ProductCode: "{Bar}"
InstallerSwitches:
Repair: /r
UninstallerSwitches:
Silent: /s
Custom: /foo
UpgradeBehavior: deny
RepairBehavior: uninstaller
- Architecture: x86
Expand Down
20 changes: 20 additions & 0 deletions src/AppInstallerCLITests/YamlManifest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,16 @@ namespace
{
REQUIRE(manifest.DefaultInstallerInfo.ArchiveBinariesDependOnPath);
}

if (manifestVer >= ManifestVer{ s_ManifestVersionV1_12 })
{
auto defaultUninstallerSwitches = manifest.DefaultInstallerInfo.UninstallerSwitches;
REQUIRE(defaultUninstallerSwitches.at(UninstallerSwitchType::Custom) == "/custom");
REQUIRE(defaultUninstallerSwitches.at(UninstallerSwitchType::SilentWithProgress) == "/silentwithprogress");
REQUIRE(defaultUninstallerSwitches.at(UninstallerSwitchType::Silent) == "/silence");
REQUIRE(defaultUninstallerSwitches.at(UninstallerSwitchType::Interactive) == "/interactive");
REQUIRE(defaultUninstallerSwitches.at(UninstallerSwitchType::Log) == "/log=<LOGPATH>");
}
}

if (isSingleton || isExported)
Expand Down Expand Up @@ -384,6 +394,16 @@ namespace
REQUIRE(installer1.RepairBehavior == RepairBehaviorEnum::Modify);
}

if (manifestVer >= ManifestVer{ s_ManifestVersionV1_12 })
{
auto installer1UninstallSwitches = installer1.UninstallerSwitches;
REQUIRE(installer1UninstallSwitches.at(UninstallerSwitchType::Custom) == "/c");
REQUIRE(installer1UninstallSwitches.at(UninstallerSwitchType::SilentWithProgress) == "/sp");
REQUIRE(installer1UninstallSwitches.at(UninstallerSwitchType::Silent) == "/s");
REQUIRE(installer1UninstallSwitches.at(UninstallerSwitchType::Interactive) == "/i");
REQUIRE(installer1UninstallSwitches.at(UninstallerSwitchType::Log) == "/l=<LOGPATH>");
}

if (manifestVer >= ManifestVer{ s_ManifestVersionV1_9 })
{
REQUIRE_FALSE(installer1.ArchiveBinariesDependOnPath);
Expand Down
19 changes: 19 additions & 0 deletions src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,25 @@ namespace AppInstaller::Manifest
return "Unknown"sv;
}

std::string_view UninstallerSwitchTypeToString(UninstallerSwitchType uninstallerSwitchType)
{
switch (uninstallerSwitchType)
{
case UninstallerSwitchType::Custom:
return "Custom"sv;
case UninstallerSwitchType::Silent:
return "Silent"sv;
case UninstallerSwitchType::SilentWithProgress:
return "SilentWithProgress"sv;
case UninstallerSwitchType::Interactive:
return "Interactive"sv;
case UninstallerSwitchType::Log:
return "Log"sv;
}

return "Unknown"sv;
}

std::string_view ElevationRequirementToString(ElevationRequirementEnum elevationRequirement)
{
switch (elevationRequirement)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ namespace AppInstaller::Manifest::YamlParser
{ "DisplayInstallWarnings"sv, YamlScalarType::Bool },
{ "InstallerReturnCode"sv, YamlScalarType::Int },
{ "DownloadCommandProhibited", YamlScalarType::Bool },
{ "ArchiveBinariesDependOnPath", YamlScalarType::Bool }
{ "ArchiveBinariesDependOnPath", YamlScalarType::Bool },
{ "UninstallerSuccessCodes"sv, YamlScalarType::Int },
};

YamlScalarType GetManifestScalarValueType(const std::string& key)
Expand Down
Loading
Loading