diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index c2d02de16e..55651a737c 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -28,6 +28,10 @@ match criteria that factor into the result ordering. This will prevent them from ## Minor Features +### Upgrade delay for `winget upgrade --all` + +Added the `installBehavior.upgradeDelayInDays` user setting to delay `winget upgrade --all` until an upgrade's `ReleaseDate` is at least N days old. This helps reduce exposure to newly published updates that may be part of a supply chain attack. + ### Preserve installer arguments across export and import `winget export` now captures the `--override` and `--custom` arguments that were used when a package was originally installed and saves them into the export file. When subsequently running `winget import`, those values are automatically re-applied during installation — `--override` replaces all installer arguments and `--custom` appends extra switches — so packages can be reinstalled with the same customizations without any manual intervention. Both fields are optional and independent of each other; packages without stored installer arguments are unaffected. diff --git a/doc/Settings.md b/doc/Settings.md index 4915f81afa..9e2feb7cbd 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -218,6 +218,18 @@ The `maxResumes` setting determines the maximum number of times that a command m > Note: [The resume behavior is an experimental feature.](#resume) +### Upgrade delay + +The `upgradeDelayInDays` setting delays upgrades when using `winget upgrade --all` by skipping upgrades whose manifest `ReleaseDate` is more recent than the configured age. The default value is 0 (disabled). + +```json + "installBehavior": { + "upgradeDelayInDays": 14 + }, +``` + +> Note: If the package manifest does not include a valid `ReleaseDate`, the upgrade will be skipped when this setting is enabled. To upgrade anyway, use `winget upgrade ` which does not consider this setting, or use `winget upgrade --all --force` which will ignore the age of the upgrade. + ## Uninstall Behavior The `uninstallBehavior` settings affect the default behavior of uninstalling (where applicable) packages. diff --git a/schemas/JSON/settings/settings.schema.0.2.json b/schemas/JSON/settings/settings.schema.0.2.json index 25b04e3d1f..1fc15b1838 100644 --- a/schemas/JSON/settings/settings.schema.0.2.json +++ b/schemas/JSON/settings/settings.schema.0.2.json @@ -215,6 +215,13 @@ "default": 3, "minimum": 1 }, + "upgradeDelayInDays": { + "description": "Minimum number of days since the manifest ReleaseDate before winget upgrade --all applies the upgrade. Set to 0 to disable.", + "type": "integer", + "default": 0, + "minimum": 0, + "maximum": 3650 + }, "archiveExtractionMethod": { "description": "Controls the behavior how the installer extracts archives. The current two supported values are 'shellApi' and 'tar'. 'shellApi' uses the Windows Shell API to extract archives. 'tar' uses the tar command to extract archives.", "type": "string", diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 11b4b8d1a7..34cc47c161 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -796,6 +796,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDelaySkippedCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeInstallTechnologyMismatchCount); WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount); diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index fbdeddfb76..c7b67f2958 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -8,6 +8,7 @@ #include #include #include +#include using namespace AppInstaller::Repository; using namespace AppInstaller::Repository::Microsoft; @@ -17,6 +18,75 @@ namespace AppInstaller::CLI::Workflow { namespace { + std::optional TryParseISO8601Date(std::string_view value) + { + // YYYY-MM-DD is the expected format + if (value.size() < 10) + { + return {}; + } + + auto toInt = [](std::string_view sv) -> std::optional + { + int result = 0; + for (char c : sv) + { + if (c < '0' || c > '9') + { + return {}; + } + result = (result * 10) + (c - '0'); + } + return result; + }; + + auto year = toInt(value.substr(0, 4)); + auto month = toInt(value.substr(5, 2)); + auto day = toInt(value.substr(8, 2)); + + if (!year || !month || !day || value[4] != '-' || value[7] != '-') + { + return {}; + } + + if (*month < 1 || *month > 12 || *day < 1 || *day > 31) + { + return {}; + } + + std::tm tm{}; + tm.tm_year = *year - 1900; + tm.tm_mon = *month - 1; + tm.tm_mday = *day; + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + + time_t utcTime = _mkgmtime(&tm); + if (utcTime == static_cast(-1)) + { + return {}; + } + + return std::chrono::system_clock::from_time_t(utcTime); + } + + bool IsUpgradeEligibleByDelay(std::string_view releaseDate, std::chrono::hours upgradeDelay) + { + if (upgradeDelay <= std::chrono::hours{ 0 }) + { + return true; + } + + auto parsed = TryParseISO8601Date(releaseDate); + if (!parsed) + { + return false; + } + + return (std::chrono::system_clock::now() - *parsed) >= upgradeDelay; + } + bool IsUpdateVersionAvailable(const Utility::Version& installedVersion, const Utility::Version& updateVersion) { return installedVersion < updateVersion; @@ -217,6 +287,9 @@ namespace AppInstaller::CLI::Workflow int packagesWithUnknownVersionSkipped = 0; int packagesThatRequireExplicitSkipped = 0; int packagesSkippedInstallTechnologyMismatch = 0; + int packagesSkippedByUpgradeDelay = 0; + const auto upgradeDelay = Settings::User().Get(); + const bool ignoreUpgradeDelay = context.Args.Contains(Execution::Args::Type::Force); for (const auto& match : matches) { @@ -256,6 +329,36 @@ namespace AppInstaller::CLI::Workflow continue; } + if (!ignoreUpgradeDelay && upgradeDelay > std::chrono::hours{ 0 }) + { + std::string_view releaseDate; + if (updateContext.Contains(Execution::Data::Installer)) + { + const auto& installer = updateContext.Get(); + if (installer.has_value() && !installer->ReleaseDate.empty()) + { + releaseDate = installer->ReleaseDate; + } + } + + if (releaseDate.empty() && updateContext.Contains(Execution::Data::Manifest)) + { + const auto& manifest = updateContext.Get(); + if (!manifest.DefaultInstallerInfo.ReleaseDate.empty()) + { + releaseDate = manifest.DefaultInstallerInfo.ReleaseDate; + } + } + + if (!IsUpgradeEligibleByDelay(releaseDate, upgradeDelay)) + { + AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) + << " as its selected upgrade does not meet the upgrade delay requirement"); + ++packagesSkippedByUpgradeDelay; + continue; + } + } + // Filter out packages that require explicit upgrade. // User-defined pins are handled when selecting the version to use. auto installedMetadata = updateContext.Get()->GetMetadata(); @@ -314,6 +417,12 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Info, << packagesSkippedInstallTechnologyMismatch << " package(s) skipped due to install technology mismatch"); context.Reporter.Info() << Resource::String::UpgradeInstallTechnologyMismatchCount(packagesSkippedInstallTechnologyMismatch) << std::endl; } + + if (packagesSkippedByUpgradeDelay > 0) + { + AICLI_LOG(CLI, Info, << packagesSkippedByUpgradeDelay << " package(s) skipped due to upgrade delay setting"); + context.Reporter.Info() << Resource::String::UpgradeDelaySkippedCount(packagesSkippedByUpgradeDelay) << std::endl; + } } void SelectSinglePackageVersionForInstallOrUpgrade::operator()(Execution::Context& context) const diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 7293ac52a6..edd31829bc 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1389,6 +1389,10 @@ Please specify one of them using the --source option to proceed. {0} package(s) are pinned and need to be explicitly upgraded. {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages that require explicit upgrades. + + {0} package(s) were skipped due to the configured upgrade delay. To upgrade them anyway, upgrade them individually or use --force. + {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages skipped for this reason during winget upgrade --all. + The arguments provided can only be used with a query. diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 5cd2f1b486..9859f14539 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -727,6 +727,18 @@ true + + true + + + true + + + true + + + true + true diff --git a/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_Eligible_Install.yaml b/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_Eligible_Install.yaml new file mode 100644 index 0000000000..c664b9921e --- /dev/null +++ b/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_Eligible_Install.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeDelayEligible +PackageVersion: 1.0.0.0 +PackageLocale: en-US +Publisher: Microsoft Corporation +PackageName: AppInstaller Test Exe Installer (Delay Eligible) +License: Test +InstallerType: exe +InstallerSwitches: + Upgrade: /update + SilentWithProgress: /silentwithprogress + Silent: /silence +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_Eligible_Update.yaml b/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_Eligible_Update.yaml new file mode 100644 index 0000000000..f0c7df2a10 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_Eligible_Update.yaml @@ -0,0 +1,21 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeDelayEligible +PackageVersion: 2.0.0.0 +PackageLocale: en-US +Publisher: Microsoft Corporation +PackageName: AppInstaller Test Exe Installer (Delay Eligible) +License: Test +ReleaseDate: 2000-01-01 +InstallerType: exe +InstallerSwitches: + Upgrade: /update + SilentWithProgress: /silentwithprogress + Silent: /silence +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_TooRecent_Install.yaml b/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_TooRecent_Install.yaml new file mode 100644 index 0000000000..be91c5144b --- /dev/null +++ b/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_TooRecent_Install.yaml @@ -0,0 +1,20 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeDelayTooRecent +PackageVersion: 1.0.0.0 +PackageLocale: en-US +Publisher: Microsoft Corporation +PackageName: AppInstaller Test Exe Installer (Delay Too Recent) +License: Test +InstallerType: exe +InstallerSwitches: + Upgrade: /update + SilentWithProgress: /silentwithprogress + Silent: /silence +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_TooRecent_Update.yaml b/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_TooRecent_Update.yaml new file mode 100644 index 0000000000..e9fe84beb6 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/UpgradeDelayTest_Exe_TooRecent_Update.yaml @@ -0,0 +1,21 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeDelayTooRecent +PackageVersion: 2.0.0.0 +PackageLocale: en-US +Publisher: Microsoft Corporation +PackageName: AppInstaller Test Exe Installer (Delay Too Recent) +License: Test +ReleaseDate: 2999-01-01 +InstallerType: exe +InstallerSwitches: + Upgrade: /update + SilentWithProgress: /silentwithprogress + Silent: /silence +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/UpdateFlow.cpp b/src/AppInstallerCLITests/UpdateFlow.cpp index 6e38204bb2..f22cf18d59 100644 --- a/src/AppInstallerCLITests/UpdateFlow.cpp +++ b/src/AppInstallerCLITests/UpdateFlow.cpp @@ -3,6 +3,7 @@ #include "pch.h" #include "WorkflowCommon.h" #include "TestHooks.h" +#include "TestSettings.h" #include #include #include @@ -556,6 +557,75 @@ TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]") REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); } +TEST_CASE("UpdateFlow_UpdateAllApplicable_UpgradeDelay", "[UpdateFlow][workflow]") +{ + auto settingsGuard = DeleteUserSettingsFiles(); + SetSetting(Stream::PrimaryUserSettings, R"({ "installBehavior": { "upgradeDelayInDays": 14 } })"); + + std::vector installationLog; + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe_UpgradeDelayEligible, + TSR::TestInstaller_Exe_UpgradeDelayTooRecent, + })); + OverrideForShellExecute(context, installationLog); + context.Args.AddArg(Execution::Args::Type::All); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE(installationLog.size() == 1); + REQUIRE(installationLog[0].Id() == "AppInstallerCliTest.TestExeDelayEligible"); + REQUIRE(updateOutput.str().find(Resource::String::UpgradeDelaySkippedCount(1)) != std::string::npos); +} + +TEST_CASE("UpdateFlow_UpdateAllApplicable_UpgradeDelay_Force", "[UpdateFlow][workflow]") +{ + auto settingsGuard = DeleteUserSettingsFiles(); + SetSetting(Stream::PrimaryUserSettings, R"({ "installBehavior": { "upgradeDelayInDays": 14 } })"); + + std::vector installationLog; + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe_UpgradeDelayEligible, + TSR::TestInstaller_Exe_UpgradeDelayTooRecent, + })); + OverrideForShellExecute(context, installationLog); + context.Args.AddArg(Execution::Args::Type::All); + context.Args.AddArg(Execution::Args::Type::Force); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE(installationLog.size() == 2); + + bool foundEligible = false; + bool foundTooRecent = false; + for (const auto& entry : installationLog) + { + if (entry.Id() == "AppInstallerCliTest.TestExeDelayEligible") + { + foundEligible = true; + } + else if (entry.Id() == "AppInstallerCliTest.TestExeDelayTooRecent") + { + foundTooRecent = true; + } + } + + REQUIRE(foundEligible); + REQUIRE(foundTooRecent); + REQUIRE(updateOutput.str().find(Resource::String::UpgradeDelaySkippedCount(1)) == std::string::npos); +} + TEST_CASE("UpdateFlow_UpdateAll_IncludeUnknown", "[UpdateFlow][workflow]") { TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); diff --git a/src/AppInstallerCLITests/WorkflowCommon.cpp b/src/AppInstallerCLITests/WorkflowCommon.cpp index 51d004c583..719bc9a412 100644 --- a/src/AppInstallerCLITests/WorkflowCommon.cpp +++ b/src/AppInstallerCLITests/WorkflowCommon.cpp @@ -105,6 +105,62 @@ namespace TestCommon PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); }); + const TestSourceResult TestInstaller_Exe_UpgradeDelayEligible( + "AppInstallerCliTest.TestExeDelayEligible"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("UpgradeDelayTest_Exe_Eligible_Install.yaml")); + auto updateManifest = YamlParser::CreateFromPath(TestDataFile("UpgradeDelayTest_Exe_Eligible_Update.yaml")); + + auto testPackage = + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector{ updateManifest, manifest }, + source + ); + for (auto& availablePackage : testPackage->Available) + { + availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + } + matches.emplace_back( + ResultMatch( + testPackage, + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeDelayEligible"))); + }); + + const TestSourceResult TestInstaller_Exe_UpgradeDelayTooRecent( + "AppInstallerCliTest.TestExeDelayTooRecent"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("UpgradeDelayTest_Exe_TooRecent_Install.yaml")); + auto updateManifest = YamlParser::CreateFromPath(TestDataFile("UpgradeDelayTest_Exe_TooRecent_Update.yaml")); + + auto testPackage = + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector{ updateManifest, manifest }, + source + ); + for (auto& availablePackage : testPackage->Available) + { + availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + } + matches.emplace_back( + ResultMatch( + testPackage, + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeDelayTooRecent"))); + }); + const TestSourceResult TestInstaller_Portable( "AppInstallerCliTest.TestPortableInstaller"sv, [](std::vector& matches, std::weak_ptr source) { diff --git a/src/AppInstallerCLITests/WorkflowCommon.h b/src/AppInstallerCLITests/WorkflowCommon.h index 8bd5348d2d..7e650390e0 100644 --- a/src/AppInstallerCLITests/WorkflowCommon.h +++ b/src/AppInstallerCLITests/WorkflowCommon.h @@ -44,6 +44,8 @@ namespace TestCommon const extern TestSourceResult TestInstaller_Exe_UnsupportedArguments; const extern TestSourceResult TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems; const extern TestSourceResult TestInstaller_Exe_UpgradeUsesAgreements; + const extern TestSourceResult TestInstaller_Exe_UpgradeDelayEligible; + const extern TestSourceResult TestInstaller_Exe_UpgradeDelayTooRecent; const extern TestSourceResult TestInstaller_Msix; const extern TestSourceResult TestInstaller_Msix_UpgradeRequiresExplicit; const extern TestSourceResult TestInstaller_Msix_UpgradeUsesAgreements; diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index 95fbe73549..d4fdda428c 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -95,6 +95,7 @@ namespace AppInstaller::Settings PortablePackageUserRoot, PortablePackageMachineRoot, MaxResumes, + UpgradeDelayInDays, // Network NetworkDownloader, NetworkDOProgressTimeoutInSeconds, @@ -183,6 +184,7 @@ namespace AppInstaller::Settings SETTINGMAPPING_SPECIALIZATION(Setting::PortablePackageMachineRoot, std::string, std::filesystem::path, {}, ".installBehavior.portablePackageMachineRoot"sv); SETTINGMAPPING_SPECIALIZATION(Setting::InstallDefaultRoot, std::string, std::filesystem::path, {}, ".installBehavior.defaultInstallRoot"sv); SETTINGMAPPING_SPECIALIZATION(Setting::MaxResumes, uint32_t, int, 3, ".installBehavior.maxResumes"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::UpgradeDelayInDays, uint32_t, std::chrono::hours, 0h, ".installBehavior.upgradeDelayInDays"sv); // Uninstall behavior SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortablePackage, bool, bool, false, ".uninstallBehavior.purgePortablePackage"sv); // Download behavior diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index db276351ff..4983ea0bf4 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -276,6 +276,10 @@ namespace AppInstaller::Settings WINGET_VALIDATE_PASS_THROUGH(UninstallPurgePortablePackage) WINGET_VALIDATE_PASS_THROUGH(NetworkWingetAlternateSourceURL) WINGET_VALIDATE_PASS_THROUGH(MaxResumes) + WINGET_VALIDATE_SIGNATURE(UpgradeDelayInDays) + { + return std::chrono::hours{ static_cast(value) * 24 }; + } WINGET_VALIDATE_PASS_THROUGH(LoggingFileTotalSizeLimitInMB) WINGET_VALIDATE_PASS_THROUGH(LoggingFileIndividualSizeLimitInMB) WINGET_VALIDATE_PASS_THROUGH(LoggingFileCountLimit)