From d357b335f555f012957d3500c8e9b413952d9511 Mon Sep 17 00:00:00 2001 From: David Bennett Date: Mon, 23 Jun 2025 01:03:59 -0700 Subject: [PATCH 01/73] initial conversion --- .../manifest.defaultLocale.1.11.0.json | 280 ++++ .../v1.11.0/manifest.installer.1.11.0.json | 920 +++++++++++++ .../v1.11.0/manifest.locale.1.11.0.json | 271 ++++ .../v1.11.0/manifest.singleton.1.11.0.json | 1149 +++++++++++++++++ .../v1.11.0/manifest.version.1.11.0.json | 46 + .../AppInstallerCLICore.vcxproj | 4 +- .../AppInstallerCLICore.vcxproj.filters | 6 + .../Commands/FontCommand.cpp | 52 +- .../Commands/FontCommand.h | 19 + src/AppInstallerCLICore/FontInstaller.cpp | 147 +++ src/AppInstallerCLICore/FontInstaller.h | 46 + src/AppInstallerCLICore/Resources.h | 5 + .../Workflows/DownloadFlow.cpp | 33 +- .../Workflows/FontFlow.cpp | 79 +- src/AppInstallerCLICore/Workflows/FontFlow.h | 6 + .../Workflows/InstallFlow.cpp | 15 + .../Workflows/InstallFlow.h | 6 + src/AppInstallerCLIE2ETests/Constants.cs | 6 + src/AppInstallerCLIE2ETests/FontCommand.cs | 61 + .../Helpers/TestCommon.cs | 75 ++ .../Helpers/TestIndex.cs | 23 + .../Helpers/TestSetup.cs | 6 + src/AppInstallerCLIE2ETests/Test.runsettings | 7 +- .../TestData/AppInstallerTestFont.ttf | Bin 0 -> 229376 bytes .../TestData/Manifests/TestFont.yaml | 14 + .../TestData/Manifests/TestInvalidFont.yaml | 14 + .../TestData/localsource.json | 6 + .../Shared/Strings/en-us/winget.resw | 21 + .../AppInstallerCLITests.vcxproj | 20 +- .../AppInstallerCLITests.vcxproj.filters | 18 + src/AppInstallerCLITests/Fonts.cpp | 24 + .../TestData/ManifestV1_11-Singleton.yaml | 202 +++ ...ManifestV1_11-MultiFile-DefaultLocale.yaml | 41 + .../ManifestV1_11-MultiFile-Installer.yaml | 213 +++ .../ManifestV1_11-MultiFile-Locale.yaml | 40 + .../ManifestV1_11-MultiFile-Version.yaml | 7 + .../TestData/TestFont.ttf | Bin 0 -> 229376 bytes src/AppInstallerCLITests/YamlManifest.cpp | 25 + src/AppInstallerCommonCore/Fonts.cpp | 32 +- .../Manifest/ManifestCommon.cpp | 16 +- .../Manifest/ManifestSchemaValidation.cpp | 12 +- .../Public/winget/Fonts.h | 6 + .../Public/winget/ManifestCommon.h | 7 + src/AppInstallerCommonCore/Runtime.cpp | 3 + src/AppInstallerSharedLib/Errors.cpp | 3 + .../Public/AppInstallerErrors.h | 3 + .../Public/winget/Registry.h | 3 + src/AppInstallerSharedLib/Registry.cpp | 31 + src/LocalhostWebServer/Startup.cs | 1 + src/ManifestSchema/ManifestSchema.h | 6 + src/ManifestSchema/ManifestSchema.rc | 6 + src/ManifestSchema/ManifestSchema.vcxitems | 14 +- .../ManifestSchema.vcxitems.filters | 18 + .../PackageManager.idl | 2 + .../Model/InstallerType.cs | 3 +- 55 files changed, 4043 insertions(+), 30 deletions(-) create mode 100644 schemas/JSON/manifests/v1.11.0/manifest.defaultLocale.1.11.0.json create mode 100644 schemas/JSON/manifests/v1.11.0/manifest.installer.1.11.0.json create mode 100644 schemas/JSON/manifests/v1.11.0/manifest.locale.1.11.0.json create mode 100644 schemas/JSON/manifests/v1.11.0/manifest.singleton.1.11.0.json create mode 100644 schemas/JSON/manifests/v1.11.0/manifest.version.1.11.0.json create mode 100644 src/AppInstallerCLICore/FontInstaller.cpp create mode 100644 src/AppInstallerCLICore/FontInstaller.h create mode 100644 src/AppInstallerCLIE2ETests/FontCommand.cs create mode 100644 src/AppInstallerCLIE2ETests/TestData/AppInstallerTestFont.ttf create mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestFont.yaml create mode 100644 src/AppInstallerCLIE2ETests/TestData/Manifests/TestInvalidFont.yaml create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1_11-Singleton.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_11/ManifestV1_11-MultiFile-DefaultLocale.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_11/ManifestV1_11-MultiFile-Installer.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_11/ManifestV1_11-MultiFile-Locale.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_11/ManifestV1_11-MultiFile-Version.yaml create mode 100644 src/AppInstallerCLITests/TestData/TestFont.ttf diff --git a/schemas/JSON/manifests/v1.11.0/manifest.defaultLocale.1.11.0.json b/schemas/JSON/manifests/v1.11.0/manifest.defaultLocale.1.11.0.json new file mode 100644 index 0000000000..2d4d5faf69 --- /dev/null +++ b/schemas/JSON/manifests/v1.11.0/manifest.defaultLocale.1.11.0.json @@ -0,0 +1,280 @@ +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.11.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.11.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.10.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.11.0/manifest.installer.1.11.0.json b/schemas/JSON/manifests/v1.11.0/manifest.installer.1.11.0.json new file mode 100644 index 0000000000..679c394fd7 --- /dev/null +++ b/schemas/JSON/manifests/v1.11.0/manifest.installer.1.11.0.json @@ -0,0 +1,920 @@ +{ + "$id": "https://aka.ms/winget-manifest.installer.1.11.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.11.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url type" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable", + "font" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + }, + "Repair": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The 'Repair' value must be passed to the installer, ModifyPath ARP command, or uninstaller ARP command when the user opts for a repair." + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious", + "deny" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, + "RepairBehavior": { + "type": [ "string", "null" ], + "enum": [ + "modify", + "uninstaller", + "installer" + ], + "description": "The repair method" + }, + "ArchiveBinariesDependOnPath": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages." + }, + "Authentication": { + "type": [ "object", "null" ], + "properties": { + "AuthenticationType": { + "type": "string", + "enum": [ + "none", + "microsoftEntraId", + "microsoftEntraIdForAzureBlobStorage" + ], + "description": "The authentication type" + }, + "MicrosoftEntraIdAuthenticationInfo": { + "type": [ "object", "null" ], + "properties": { + "Resource": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The resource value for Microsoft Entra Id authentication." + }, + "Scope": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The scope value for Microsoft Entra Id authentication." + } + }, + "description": "The Microsoft Entra Id authentication info" + } + }, + "required": [ + "AuthenticationType" + ], + "description": "The authentication requirement for downloading the installer." + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "ArchiveBinariesDependOnPath": { + "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "ArchiveBinariesDependOnPath": { + "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.10.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.11.0/manifest.locale.1.11.0.json b/schemas/JSON/manifests/v1.11.0/manifest.locale.1.11.0.json new file mode 100644 index 0000000000..11e5028244 --- /dev/null +++ b/schemas/JSON/manifests/v1.11.0/manifest.locale.1.11.0.json @@ -0,0 +1,271 @@ +{ + "$id": "https://aka.ms/winget-manifest.locale.1.11.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.11.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.10.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.11.0/manifest.singleton.1.11.0.json b/schemas/JSON/manifests/v1.11.0/manifest.singleton.1.11.0.json new file mode 100644 index 0000000000..62cc69652b --- /dev/null +++ b/schemas/JSON/manifests/v1.11.0/manifest.singleton.1.11.0.json @@ -0,0 +1,1149 @@ +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.11.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.11.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable", + "font" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + }, + "Repair": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The 'Repair' value must be passed to the installer, ModifyPath ARP command, or uninstaller ARP command when the user opts for a repair" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious", + "deny" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, + "RepairBehavior": { + "type": [ "string", "null" ], + "enum": [ + "modify", + "uninstaller", + "installer" + ], + "description": "The repair method" + }, + "ArchiveBinariesDependOnPath": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages." + }, + "Authentication": { + "type": [ "object", "null" ], + "properties": { + "AuthenticationType": { + "type": "string", + "enum": [ + "none", + "microsoftEntraId", + "microsoftEntraIdForAzureBlobStorage" + ], + "description": "The authentication type" + }, + "MicrosoftEntraIdAuthenticationInfo": { + "type": [ "object", "null" ], + "properties": { + "Resource": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The resource value for Microsoft Entra Id authentication." + }, + "Scope": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The scope value for Microsoft Entra Id authentication." + } + }, + "description": "The Microsoft Entra Id authentication info" + } + }, + "required": [ + "AuthenticationType" + ], + "description": "The authentication requirement for downloading the installer." + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "ArchiveBinariesDependOnPath": { + "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "ArchiveBinariesDependOnPath": { + "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.10.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.11.0/manifest.version.1.11.0.json b/schemas/JSON/manifests/v1.11.0/manifest.version.1.11.0.json new file mode 100644 index 0000000000..8eac1d82fa --- /dev/null +++ b/schemas/JSON/manifests/v1.11.0/manifest.version.1.11.0.json @@ -0,0 +1,46 @@ +{ + "$id": "https://aka.ms/winget-manifest.version.1.11.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.11.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.10.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 573f1cef35..d099c89fe0 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -340,6 +340,7 @@ + @@ -414,6 +415,7 @@ + @@ -499,4 +501,4 @@ - + \ No newline at end of file diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index ce771bdb4e..9562bc0dfe 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -293,6 +293,9 @@ Commands\Configuration + + Header Files + @@ -553,6 +556,9 @@ Commands\Configuration + + Source Files + diff --git a/src/AppInstallerCLICore/Commands/FontCommand.cpp b/src/AppInstallerCLICore/Commands/FontCommand.cpp index c6faae5b44..9caf2b0055 100644 --- a/src/AppInstallerCLICore/Commands/FontCommand.cpp +++ b/src/AppInstallerCLICore/Commands/FontCommand.cpp @@ -3,8 +3,9 @@ #include "pch.h" #include "FontCommand.h" #include "Workflows/CompletionFlow.h" -#include "Workflows/WorkflowBase.h" #include "Workflows/FontFlow.h" +#include "Workflows/InstallFlow.h" +#include "Workflows/WorkflowBase.h" #include "Resources.h" namespace AppInstaller::CLI @@ -20,6 +21,7 @@ namespace AppInstaller::CLI { return InitializeFromMoveOnly>>({ std::make_unique(FullName()), + std::make_unique(FullName()), }); } @@ -83,4 +85,52 @@ namespace AppInstaller::CLI { context << Workflow::ReportInstalledFonts; } + + std::vector FontInstallCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Manifest), + Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Args::Type::Force), + }; + } + + Resource::LocString FontInstallCommand::ShortDescription() const + { + return { Resource::String::FontInstallCommandShortDescription }; + } + + Resource::LocString FontInstallCommand::LongDescription() const + { + return { Resource::String::FontInstallCommandLongDescription }; + } + + void FontInstallCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + UNREFERENCED_PARAMETER(valueType); + context.Reporter.Error() << Resource::String::PendingWorkError << std::endl; + THROW_HR(E_NOTIMPL); + } + + Utility::LocIndView FontInstallCommand::HelpLink() const + { + return s_FontCommand_HelpLink; + } + + void FontInstallCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void FontInstallCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::Manifest)) + { + context << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << + Workflow::GetManifestFromArg << + Workflow::SelectInstaller << + Workflow::InstallSinglePackage; + } + } } diff --git a/src/AppInstallerCLICore/Commands/FontCommand.h b/src/AppInstallerCLICore/Commands/FontCommand.h index c29f703f6c..337a6abdc2 100644 --- a/src/AppInstallerCLICore/Commands/FontCommand.h +++ b/src/AppInstallerCLICore/Commands/FontCommand.h @@ -37,4 +37,23 @@ namespace AppInstaller::CLI protected: void ExecuteInternal(Execution::Context& context) const override; }; + + struct FontInstallCommand final : public Command + { + FontInstallCommand(std::string_view parent) : Command("install", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; + } diff --git a/src/AppInstallerCLICore/FontInstaller.cpp b/src/AppInstallerCLICore/FontInstaller.cpp new file mode 100644 index 0000000000..61d3f3c911 --- /dev/null +++ b/src/AppInstallerCLICore/FontInstaller.cpp @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation.Add commentMore actions +// Licensed under the MIT License. + +#include "pch.h" +#include "ExecutionContext.h" +#include "FontInstaller.h" +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::CLI::Font +{ + namespace + { + // Prefix all packaged fonts are installed under. + constexpr std::wstring_view s_FontsWinGetPrefix = L"winget_v1"; + constexpr std::wstring_view s_FontsPathSubkey = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts\\winget_v1"; + constexpr std::wstring_view s_TrueType = L" (TrueType)"; + + bool IsTrueTypeFont(DWRITE_FONT_FILE_TYPE fileType) + { + return ( + fileType == DWRITE_FONT_FILE_TYPE_TRUETYPE || + fileType == DWRITE_FONT_FILE_TYPE_TRUETYPE_COLLECTION + ); + } + } + + FontFile::FontFile(std::filesystem::path filePath, DWRITE_FONT_FILE_TYPE fileType, std::wstring packageName) + : FilePath(std::move(filePath)), FileType(fileType), PackageName(std::move(packageName)) + { + Title = AppInstaller::Fonts::GetFontFileTitle(FilePath); + + if (IsTrueTypeFont(FileType)) + { + Title += s_TrueType; + } + } + + FontInstaller::FontInstaller(Manifest::ScopeEnum scope) : m_scope(scope) + { + if (scope == Manifest::ScopeEnum::Machine) + { + m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsMachineInstallLocation); + m_key = Registry::Key::Create(HKEY_LOCAL_MACHINE, std::wstring{ s_FontsPathSubkey }); + } + else + { + m_installLocation = Runtime::GetPathTo(Runtime::PathName::FontsUserInstallLocation); + m_key = Registry::Key::Create(HKEY_CURRENT_USER, std::wstring{ s_FontsPathSubkey }); + } + } + + bool FontInstaller::EnsureInstall() + { + for (auto& fontFile : m_fontFiles) + { + if (m_key[fontFile.Title].has_value()) + { + if (!std::filesystem::exists(m_key[fontFile.Title]->GetValue())) + { + AICLI_LOG(CLI, Info, << "Removing existing font value as font file does not exist."); + m_key.DeleteValue(fontFile.Title); + } + else + { + AICLI_LOG(CLI, Info, << "Existing font value found: " << AppInstaller::Utility::ConvertToUTF8(fontFile.Title)); + return false; + } + } + + std::filesystem::path destinationPath = m_installLocation / fontFile.FilePath.filename(); + auto initialStem = fontFile.FilePath.stem(); + auto extension = fontFile.FilePath.extension(); + + // If a file exists at the destination path, make the filename unique. + int index = 0; + while (std::filesystem::exists(destinationPath)) + { + std::filesystem::path unique = { "_" + std::to_string(index) }; + auto duplicateStem = initialStem; + duplicateStem += unique; + duplicateStem += extension; + destinationPath = m_installLocation / duplicateStem; + index++; + } + + fontFile.DestinationPath = std::move(destinationPath); + } + + return true; + } + + void FontInstaller::Install() + { + bool isMachineScope = m_scope == Manifest::ScopeEnum::Machine; + + for (const auto& fontFile : m_fontFiles) + { + AICLI_LOG(CLI, Info, << "Creating font value with name : " << AppInstaller::Utility::ConvertToUTF8(fontFile.Title)); + if (isMachineScope) + { + m_key.SetValue(fontFile.Title, fontFile.DestinationPath.filename(), REG_SZ); + } + else + { + m_key.SetValue(fontFile.Title, fontFile.DestinationPath, REG_SZ); + } + } + + for (const auto& fontFile : m_fontFiles) + { + AICLI_LOG(CLI, Info, << "Moving font file to: " << fontFile.DestinationPath); + AppInstaller::Filesystem::RenameFile(fontFile.FilePath, fontFile.DestinationPath); + } + } + + void FontInstaller::Uninstall() + { + for (const auto& fontFile : m_fontFiles) + { + if (m_key[fontFile.Title].has_value()) + { + AICLI_LOG(CLI, Info, << "Existing font value found:" << AppInstaller::Utility::ConvertToUTF8(fontFile.Title)); + std::filesystem::path existingFontFilePath = { m_key[fontFile.Title]->GetValue() }; + + if (m_scope == Manifest::ScopeEnum::Machine) + { + // Font entries in the HKEY_LOCAL_MACHINE hive only have the filename specified as the value. Prepend install location. + existingFontFilePath = m_installLocation / existingFontFilePath; + } + + if (std::filesystem::exists(existingFontFilePath)) + { + AICLI_LOG(CLI, Info, << "Removing existing font file at:" << existingFontFilePath); + std::filesystem::remove(existingFontFilePath); + } + + AICLI_LOG(CLI, Info, << "Deleting registry value:" << existingFontFilePath); + m_key.DeleteValue(fontFile.Title); + } + } + } +} diff --git a/src/AppInstallerCLICore/FontInstaller.h b/src/AppInstallerCLICore/FontInstaller.h new file mode 100644 index 0000000000..91cb7764cb --- /dev/null +++ b/src/AppInstallerCLICore/FontInstaller.h @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#include +#include + +namespace AppInstaller::CLI::Font +{ + struct FontFile + { + FontFile(std::filesystem::path filePath, DWRITE_FONT_FILE_TYPE fileType, std::wstring packageName); + + std::filesystem::path FilePath; + DWRITE_FONT_FILE_TYPE FileType; + std::wstring PackageName; + std::wstring Title; + std::filesystem::path DestinationPath; + }; + + struct FontInstaller + { + FontInstaller(Manifest::ScopeEnum scope); + + std::filesystem::path FontFileLocation; + + void SetFontFiles(const std::vector& fontFiles) + { + m_fontFiles = fontFiles; + } + + // Checks if all expected registry values and font files can be installed prior to installation. + bool EnsureInstall(); + + void Install(); + + void Uninstall(); + + private: + Manifest::ScopeEnum m_scope; + std::filesystem::path m_installLocation; + Registry::Key m_key; + + std::vector m_fontFiles; + }; +} diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 1b651a3219..e0f3adb826 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -303,13 +303,18 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(FileNotFound); WINGET_DEFINE_RESOURCE_STRINGID(FilesRemainInInstallDirectory); WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); + WINGET_DEFINE_RESOURCE_STRINGID(FontAlreadyInstalled); WINGET_DEFINE_RESOURCE_STRINGID(FontCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontFace); WINGET_DEFINE_RESOURCE_STRINGID(FontFaces); WINGET_DEFINE_RESOURCE_STRINGID(FontFamily); WINGET_DEFINE_RESOURCE_STRINGID(FontFamilyNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontFileNotSupported); WINGET_DEFINE_RESOURCE_STRINGID(FontFilePaths); + WINGET_DEFINE_RESOURCE_STRINGID(FontInstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontInstallCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontInstallFailed); WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandLongDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(FontVersion); diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp index 119e2d1b1d..4f22e2ffdb 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -78,22 +78,28 @@ namespace AppInstaller::CLI::Workflow std::filesystem::path GetInstallerPostHashValidationFileName(Execution::Context& context) { // Get file name from download URI - std::filesystem::path filename = GetFileNameFromURI(context.Get()->Url); - std::wstring_view installerExtension = GetInstallerFileExtension(context); + const auto& installer = context.Get(); + std::filesystem::path filename = GetFileNameFromURI(installer->Url); - // Assuming that we find a safe stem value in the URI, use it. - // This should be extremely common, but just in case fall back to the older name style. - if (filename.has_stem() && ((filename.wstring().size() + installerExtension.size()) < MAX_PATH)) - { - filename = filename.stem(); - } - else + // Installers that support multiple extensions will use the Url. + if (!DoesInstallerTypeSupportMultipleFileExtensions(installer->BaseInstallerType)) { - const auto& manifest = context.Get(); - filename = Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); - } + std::wstring_view installerExtension = GetInstallerFileExtension(context); + + // Assuming that we find a safe stem value in the URI, use it. + // This should be extremely common, but just in case fall back to the older name style. + if (filename.has_stem() && ((filename.wstring().size() + installerExtension.size()) < MAX_PATH)) + { + filename = filename.stem(); + } + else + { + const auto& manifest = context.Get(); + filename = Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); + } - filename += installerExtension; + filename += installerExtension; + } // Make file name suitable for file system path filename = Utility::ConvertToUTF16(Utility::MakeSuitablePathPart(filename.u8string())); @@ -288,6 +294,7 @@ namespace AppInstaller::CLI::Workflow case InstallerTypeEnum::Nullsoft: case InstallerTypeEnum::Portable: case InstallerTypeEnum::Wix: + case InstallerTypeEnum::Font: case InstallerTypeEnum::Zip: context << DownloadInstallerFile; break; diff --git a/src/AppInstallerCLICore/Workflows/FontFlow.cpp b/src/AppInstallerCLICore/Workflows/FontFlow.cpp index ce9bb7a233..5c6dfc3262 100644 --- a/src/AppInstallerCLICore/Workflows/FontFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/FontFlow.cpp @@ -1,21 +1,25 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. + #include "pch.h" #include "FontFlow.h" #include "TableOutput.h" #include #include +#include namespace AppInstaller::CLI::Workflow { using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Font; namespace { struct InstalledFontFamiliesTableLine { InstalledFontFamiliesTableLine(Utility::LocIndString familyName, int faceCount) - : FamilyName(familyName), FaceCount(faceCount) {} + : FamilyName(familyName), FaceCount(faceCount) { + } Utility::LocIndString FamilyName; int FaceCount; @@ -24,7 +28,8 @@ namespace AppInstaller::CLI::Workflow struct InstalledFontFacesTableLine { InstalledFontFacesTableLine(Utility::LocIndString familyName, Utility::LocIndString faceName, Utility::LocIndString faceVersion, std::filesystem::path filePath) - : FamilyName(familyName), FaceName(faceName), FaceVersion(faceVersion), FilePath(filePath) {} + : FamilyName(familyName), FaceName(faceName), FaceVersion(faceVersion), FilePath(filePath) { + } Utility::LocIndString FamilyName; Utility::LocIndString FaceName; @@ -121,4 +126,74 @@ namespace AppInstaller::CLI::Workflow OutputInstalledFontFamiliesTable(context, lines); } } + + void FontInstallImpl(Execution::Context& context) + { + Manifest::ScopeEnum scope = Manifest::ScopeEnum::Unknown; + if (context.Args.Contains(Execution::Args::Type::InstallScope)) + { + scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + } + + FontInstaller fontInstaller = FontInstaller(scope); + + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + try + { + const auto& installerPath = context.Get(); + std::vector filePaths; + + // InstallerPath will point to a directory if extracted from an archive. + if (std::filesystem::is_directory(installerPath)) + { + const std::vector& nestedInstallerFiles = context.Get()->NestedInstallerFiles; + for (const auto& nestedInstallerFile : nestedInstallerFiles) + { + filePaths.emplace_back(installerPath / ConvertToUTF16(nestedInstallerFile.RelativeFilePath)); + } + } + else + { + filePaths.emplace_back(installerPath); + } + + Fonts::FontCatalog fontCatalog; + std::vector fontFiles; + + for (const std::filesystem::path filePath : filePaths) + { + DWRITE_FONT_FILE_TYPE fileType; + if (!fontCatalog.IsFontFileSupported(filePath, fileType)) + { + AICLI_LOG(CLI, Warning, << "Font file is not supported: " << filePath); + context.Reporter.Error() << Resource::String::FontFileNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED); + } + else + { + AICLI_LOG(CLI, Verbose, << "Font file is supported: " << filePath); + fontFiles.emplace_back(FontFile(filePath, fileType, L"unknown")); + } + } + + fontInstaller.SetFontFiles(fontFiles); + + if (!fontInstaller.EnsureInstall()) + { + context.Reporter.Warn() << Resource::String::FontAlreadyInstalled << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED); + } + + fontInstaller.Install(); + context.Add(S_OK); + } + catch (...) + { + context.Add(Workflow::HandleException(context, std::current_exception())); + context.Reporter.Warn() << Resource::String::FontInstallFailed << std::endl; + fontInstaller.Uninstall(); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED); + } + } } diff --git a/src/AppInstallerCLICore/Workflows/FontFlow.h b/src/AppInstallerCLICore/Workflows/FontFlow.h index c3346e1a60..5ad5e0a2e8 100644 --- a/src/AppInstallerCLICore/Workflows/FontFlow.h +++ b/src/AppInstallerCLICore/Workflows/FontFlow.h @@ -10,4 +10,10 @@ namespace AppInstaller::CLI::Workflow // Inputs: None // Outputs: None void ReportInstalledFonts(Execution::Context& context); + + // Installs the font package. + // Required Args: None + // Inputs: Manifest, Scope, Rename, Location + // Outputs: None + void FontInstallImpl(Execution::Context& context); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 193d2c150d..6cca2db9e1 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "InstallFlow.h" #include "DownloadFlow.h" +#include "FontFlow.h" #include "UninstallFlow.h" #include "UpdateFlow.h" #include "ResumeFlow.h" @@ -276,6 +277,17 @@ namespace AppInstaller::CLI::Workflow VerifyAndSetNestedInstaller << ExecuteInstallerForType(context.Get().value().NestedInstallerType); } + + // Runs the flow for installing a font package. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void FontInstall(Execution::Context& context) + { + context << + FontInstallImpl << + ReportInstallerResult("Font"sv, APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED, true); + } } bool ExemptFromSingleInstallLocking(InstallerTypeEnum type) @@ -443,6 +455,9 @@ namespace AppInstaller::CLI::Workflow case InstallerTypeEnum::Zip: context << details::ArchiveInstall; break; + case InstallerTypeEnum::Font: + context << details::FontInstall; + break; default: THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); } diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index 89a74021c4..af34bee4f9 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -49,6 +49,12 @@ namespace AppInstaller::CLI::Workflow // Inputs: Installer, InstallerPath, Manifest // Outputs: None void ArchiveInstall(Execution::Context& context); + + // Runs the flow for intsalling a font package. + // Required Args: None + // Inputs: Installer, InstallerPath, Manifest + // Outputs: None + void FontInstall(Execution::Context& context); } // Ensures that there is an applicable installer. diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index 20b105501b..4e107c4eb5 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -27,6 +27,7 @@ public class Constants public const string MsiInstallerPathParameter = "MsiTestInstallerPath"; public const string MsiInstallerV2PathParameter = "MsiTestInstallerV2Path"; public const string MsixInstallerPathParameter = "MsixTestInstallerPath"; + public const string FontPathParameter = "FontTestPath"; public const string PackageCertificatePathParameter = "PackageCertificatePath"; public const string PowerShellModulePathParameter = "PowerShellModulePath"; public const string SkipTestSourceParameter = "SkipTestSource"; @@ -60,6 +61,7 @@ public class Constants public const string MsiInstallerV2FileName = "AppInstallerTestMsiInstallerV2.msi"; public const string MsixInstallerFileName = "AppInstallerTestMsixInstaller.msix"; public const string ZipInstallerFileName = "AppInstallerTestZipInstaller.zip"; + public const string FontFileName = "AppInstallerTestFont.ttf"; public const string ModifyRepairInstaller = "AppInstallerTest.TestModifyRepair"; public const string IndexPackage = "source.msix"; public const string MakeAppx = "makeappx.exe"; @@ -118,6 +120,8 @@ public class Constants public const string UninstallSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; public const string PathSubKey_User = @"Environment"; public const string PathSubKey_Machine = @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; + public const string FontsSubKey = @"Software\Microsoft\Windows NT\CurrentVersion\Fonts"; + public const string TestFontSubKeyName = "Cascadia Code PL Regular (TrueType)"; // User settings public const string ArchiveExtractionMethod = "archiveExtractionMethod"; @@ -273,6 +277,8 @@ public class ErrorCode public const int ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED = unchecked((int)0x8A15007D); public const int ERROR_INSTALLER_ZERO_BYTE_FILE = unchecked((int)0x8A150086); + public const int ERROR_FONT_INSTALL_FAILED = unchecked((int)0x8A150087); + public const int ERROR_FONT_FILE_NOT_SUPPORTED = unchecked((int)0x8A150088); public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); diff --git a/src/AppInstallerCLIE2ETests/FontCommand.cs b/src/AppInstallerCLIE2ETests/FontCommand.cs new file mode 100644 index 0000000000..1292caad24 --- /dev/null +++ b/src/AppInstallerCLIE2ETests/FontCommand.cs @@ -0,0 +1,61 @@ +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test font command. + /// + public class FontCommand : BaseCommand + { + /// + /// One time set up. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("fonts", true); + } + + /// + /// Test install a font with user scope. + /// + [Test] + public void InstallFont_UserScope() + { + var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestFont"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyFontPackage(Constants.TestFontSubKeyName, Constants.FontFileName, TestCommon.Scope.User); + } + + /// + /// Test install a font with machine scope. + /// + [Test] + public void InstallFont_MachineScope() + { + var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestFont --scope Machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyFontPackage(Constants.TestFontSubKeyName, Constants.FontFileName, TestCommon.Scope.Machine); + } + + /// + /// Test install an invalid font file. + /// + [Test] + public void InstallInvalidFont() + { + var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestInvalidFont"); + Assert.AreEqual(Constants.ErrorCode.ERROR_FONT_FILE_NOT_SUPPORTED, result.ExitCode); + Assert.True(result.StdOut.Contains("The font file is not supported and cannot be installed.")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 4afe1b2ceb..a7239cd473 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -355,6 +355,81 @@ public static string GetCheckpointsDirectory() } } + /// + /// Gets the fonts directory based on scope. + /// + /// Scope. + /// The path of the fonts directory. + public static string GetFontsDirectory(Scope scope) + { + if (scope == Scope.Machine) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts"); + } + else + { + return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "Windows", "Fonts"); + } + } + + /// + /// Verify font package. + /// + /// Name of the font registry subkey entry. + /// Filename of the installed font file. + /// Scope. + /// Should exist. + public static void VerifyFontPackage( + string fontSubKeyName, + string fontFileName, + Scope scope = Scope.User, + bool shouldExist = true) + { + // Expected font file path. + string expectedFontInstallPath = Path.Combine(GetFontsDirectory(scope), fontFileName); + bool fontFileExists = File.Exists(expectedFontInstallPath); + + // Expected font registry entry. + bool fontEntryExists = false; + RegistryKey baseKey = (scope == Scope.Machine) ? Registry.LocalMachine : Registry.CurrentUser; + string expectedSubKeyValue = (scope == Scope.Machine) ? Path.GetFileName(expectedFontInstallPath) : expectedFontInstallPath; + using (RegistryKey fontsRegistryKey = baseKey.OpenSubKey(Constants.FontsSubKey, true)) + { + var fontSubKeyValue = fontsRegistryKey.GetValue(fontSubKeyName); + if (fontSubKeyValue != null) + { + fontEntryExists = fontSubKeyValue.Equals(expectedSubKeyValue); + } + } + + if (shouldExist) + { + // TODO: Replace with font uninstall when implemented. + if (fontEntryExists) + { + using (RegistryKey fontsRegistryKey = baseKey.OpenSubKey(Constants.FontsSubKey, true)) + { + fontsRegistryKey.DeleteValue(fontSubKeyName); + } + } + + if (fontFileExists) + { + try + { + File.Delete(expectedFontInstallPath); + } + catch (UnauthorizedAccessException) + { + // TODO: This error occurs for user mode if the font is in use. Skip cleanup. + } + } + } + + Assert.AreEqual(shouldExist, fontFileExists, $"Expected font path: {expectedFontInstallPath}"); + Assert.AreEqual(shouldExist, fontEntryExists, $"Expected {fontSubKeyName} subkey with value {expectedSubKeyValue} in registry path: {Constants.FontsSubKey}"); + } + /// /// Verify portable package. /// diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs b/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs index 2728661e86..d79d57aa90 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs @@ -25,6 +25,7 @@ static TestIndex() TestIndex.MsiInstallerV2 = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsiInstaller, Constants.MsiInstallerV2FileName); TestIndex.MsixInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsixInstaller, Constants.MsixInstallerFileName); TestIndex.ZipInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.ZipInstaller, Constants.ZipInstallerFileName); + TestIndex.Font = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.FontFileName, Constants.FontFileName); } /// @@ -52,6 +53,11 @@ static TestIndex() /// public static string ZipInstaller { get; private set; } + /// + /// Gets the font file path used by the manifests in the E2E test. + /// + public static string Font { get; private set; } + /// /// Generate test source. /// @@ -99,6 +105,16 @@ public static void GenerateE2ESource() throw new FileNotFoundException(testParams.MsixInstallerPath); } + if (string.IsNullOrEmpty(testParams.FontPath)) + { + throw new ArgumentNullException($"{Constants.FontPathParameter} is required"); + } + + if (!File.Exists(testParams.FontPath)) + { + throw new FileNotFoundException(testParams.FontPath); + } + if (string.IsNullOrEmpty(testParams.PackageCertificatePath)) { throw new ArgumentNullException($"{Constants.PackageCertificatePathParameter} is required"); @@ -148,6 +164,13 @@ public static void GenerateE2ESource() HashToken = "", SignatureToken = "", }, + new LocalInstaller + { + Type = InstallerType.Font, + Name = Path.Combine(Constants.FontFileName, Constants.FontFileName), + Input = testParams.FontPath, + HashToken = "", + }, }, DynamicInstallers = new () { diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs b/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs index b681fc710f..a3ed192959 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs @@ -44,6 +44,7 @@ private TestSetup() this.MsiInstallerPath = this.InitializeFileParam(Constants.MsiInstallerPathParameter); this.MsixInstallerPath = this.InitializeFileParam(Constants.MsixInstallerPathParameter); this.MsiInstallerV2Path = this.InitializeFileParam(Constants.MsiInstallerV2PathParameter); + this.FontPath = this.InitializeFileParam(Constants.FontPathParameter); this.ForcedExperimentalFeatures = this.InitializeStringArrayParam(Constants.ForcedExperimentalFeaturesParameter); } @@ -119,6 +120,11 @@ public static TestSetup Parameters /// public string ZipInstallerPath { get; } + /// + /// Gets the font path. + /// + public string FontPath { get; } + /// /// Gets the package cert path. /// diff --git a/src/AppInstallerCLIE2ETests/Test.runsettings b/src/AppInstallerCLIE2ETests/Test.runsettings index 6f6079e24e..87d018b7f9 100644 --- a/src/AppInstallerCLIE2ETests/Test.runsettings +++ b/src/AppInstallerCLIE2ETests/Test.runsettings @@ -1,4 +1,4 @@ - +