mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-08-29 10:09:43 +02:00
Add preview release versioning and update channel support (#49414)
## Summary - Publish scheduled `main` builds as GitHub prereleases while keeping manual `main` runs as preview validation builds. - Add an opt-in Settings switch for prerelease update checks; stable updates remain the default. - Use one MSI-safe version across bundles, MSI packages, binaries, symbols, and package manifests. - Prevent preview releases from triggering Microsoft Store, WinGet, or public-symbol publication. - Label preview builds explicitly in Settings, update notifications, and What's New. ## Build intent | Source | Trigger | Intent | | --- | --- | --- | | `main` | Scheduled | Publish a preview release | | `main` | Manual | Validate a preview build without publishing | | `stable` | Manual | Produce a stable release | | Other branches | Any supported trigger | Produce a private validation build | ## MSI-safe release versioning Windows Installer compares only `major.minor.build` and ignores the fourth version component. Preview and stable release builds therefore use: ```text major.minor.YDDDB.0 ``` - `Y`: zero-based number of calendar years since `ReleaseTrainEpoch`. - `DDD`: three-position calendar day of year. - `B`: daily release sequence `1-9`. - The fourth component is always `0`. With `ReleaseTrainVersion=0.100` and `ReleaseTrainEpoch=2026-01-01`: ```text 0.100.2111.0 = July 30, 2026, release build 1 0.100.3659.0 = December 31, 2026, release build 9 0.100.10011.0 = January 1, 2027, release build 1 ``` The allocator formats `DDD` as exactly three digits before converting the MSI component to its numeric representation. Leading zeros may not be displayed because Windows version components are numeric; decoding remains positional: ```text B = component % 10 DDD = (component / 10) % 1000 Y = component / 10000 ``` `ReleaseTrainVersion` and `ReleaseTrainEpoch` are checked in under `src/Version.props`. The epoch remains January 1 of the active epoch year and advances on the first release-train minor change in a new year. ## Daily release counter Azure DevOps persists the daily sequence server-side using a counter keyed as `release-YYYYMMDD`. - `main` and `stable` share the same daily counter. - Other branches do not evaluate or consume the release counter. - Failed or canceled `main`/`stable` runs may leave gaps. - The build fails when the daily sequence exceeds `9`. - The counter date and encoded `YDDD` date both use `pipeline.startTime`. Private branches retain independent `0.0.<extended-day><NN>.0` validation versions. ## Update behavior - Stable users continue to query GitHub's stable latest-release path. - Users who explicitly enable preview updates can select newer GitHub prereleases. - Preview releases and notifications are labeled as PowerToys Preview. - What's New separates preview entries from stable release history and hides previews by default. ## Validation - 17 Pester tests cover `main`, `stable`, private branches, year rollover, epoch reset, monotonicity, override validation, sequence limits, and date alignment. - Version propagation verified `0.100.2111.0` in `Version.props` and all affected AppX/MSIX manifests. - Azure DevOps pipeline dry-runs succeeded for both `refs/heads/main` and `refs/heads/stable`. - The affected native version project builds successfully. - PR CI is green for x64, ARM64, Command Palette SDK, dependency review, telemetry detection, and CLA. ## Remaining end-to-end checks - Install two locally or officially produced installers with consecutive MSI-visible `YDDDB` versions and verify the upgrade preserves binaries, package registrations, hardlinks, and shell integrations. - On the first natural post-merge `main` or `stable` run, verify the production counter value and resolved version in the release logs. ## Local GPO verification Validated locally with the signed `v0.100.2171` build from Azure DevOps build [153961073](https://microsoft.visualstudio.com/Dart/_build/results?buildId=153961073). These checks cover the administrative-template integration and Settings behavior. ### Policy enabled: preview updates are disabled With `PreviewUpdatesDisabled=1`, **Include prerelease updates** is forced off and locked, and Settings displays the managed-by-your-organization notice.  ### Policy removed: the user preference is preserved After removing `PreviewUpdatesDisabled` and restarting PowerToys, the previously selected preview-update preference is restored and editable. The policy suppresses the preference without overwriting it.  ### Group Policy Editor After importing the updated ADMX/ADML templates, **Disable preview build updates** appears under **Microsoft PowerToys > Installer and Updates**. The policy dialog documents that **Enabled** blocks preview updates, while **Disabled** or **Not Configured** leaves the choice available to the user.  --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: ad8b7909-0472-4464-bdee-deaeca726f94 Copilot-Session: 8e04a72e-3b0f-4ac4-8156-d04ea9b8bb85
This commit is contained in:
11
.github/actions/spell-check/allow/code.txt
vendored
11
.github/actions/spell-check/allow/code.txt
vendored
@@ -401,6 +401,17 @@ HHH
|
||||
riday
|
||||
YYY
|
||||
|
||||
# Release versioning identifiers and format fragments
|
||||
DAILYVERSIONSEQUENCE
|
||||
DDNNN
|
||||
Mdd
|
||||
SOURCEBRANCH
|
||||
SOURCEVERSION
|
||||
VERSIONDATE
|
||||
YDDD
|
||||
YDDDB
|
||||
YYMM
|
||||
|
||||
# Unicode
|
||||
precomposed
|
||||
|
||||
|
||||
213
.github/skills/release-note-generation/scripts/create-github-draft-release.ps1
vendored
Normal file
213
.github/skills/release-note-generation/scripts/create-github-draft-release.ps1
vendored
Normal file
@@ -0,0 +1,213 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Creates or updates a GitHub draft release from a prepared PowerToys release folder.
|
||||
|
||||
.DESCRIPTION
|
||||
Validates a prepared release folder, finds the four installers, two symbol
|
||||
zips, GPO zip, and release notes, then uses GitHub CLI (`gh`) to create or
|
||||
update a draft release and upload the assets. Asset filenames use the
|
||||
numeric product version; -TagName may include channel suffixes such as
|
||||
v0.100.2607.08001-preview.
|
||||
#>
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ReleaseFolder,
|
||||
|
||||
[string]$GitHubRepo = "microsoft/PowerToys",
|
||||
|
||||
[string]$TagName,
|
||||
|
||||
[string]$Target,
|
||||
|
||||
[string]$Title,
|
||||
|
||||
[string]$NotesFile,
|
||||
|
||||
[switch]$Prerelease,
|
||||
|
||||
[switch]$LatestFalse,
|
||||
|
||||
[switch]$Clobber,
|
||||
|
||||
[switch]$DryRun
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Resolve-ExistingPath {
|
||||
param([Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][string]$Description)
|
||||
$resolved = Resolve-Path -LiteralPath $Path -ErrorAction SilentlyContinue
|
||||
if (-not $resolved) {
|
||||
throw "$Description not found: $Path"
|
||||
}
|
||||
|
||||
return $resolved.ProviderPath
|
||||
}
|
||||
|
||||
function Invoke-Gh {
|
||||
param([Parameter(Mandatory)][string[]]$Arguments, [switch]$AllowFailure)
|
||||
if ($DryRun) {
|
||||
Write-Host "gh $($Arguments -join ' ')" -ForegroundColor DarkGray
|
||||
return $null
|
||||
}
|
||||
|
||||
$output = & gh @Arguments 2>&1
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -ne 0 -and -not $AllowFailure) {
|
||||
throw "gh $($Arguments -join ' ') failed with exit code $exitCode.`n$output"
|
||||
}
|
||||
|
||||
if ($exitCode -ne 0) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $output
|
||||
}
|
||||
|
||||
function Get-ReleaseNotesFile {
|
||||
param([Parameter(Mandatory)][string]$Folder, [Parameter(Mandatory)][string]$Version, [string]$ExplicitNotesFile)
|
||||
if ($ExplicitNotesFile) {
|
||||
return Resolve-ExistingPath -Path $ExplicitNotesFile -Description "Release notes file"
|
||||
}
|
||||
|
||||
$markdownFiles = @(Get-ChildItem -LiteralPath $Folder -File -Filter "*.md" | Where-Object {
|
||||
$_.Name -notmatch '^(hashes|sha256|checksums)\.md$'
|
||||
})
|
||||
|
||||
foreach ($name in @("v$Version-release-notes.md", "$Version-release-notes.md", "release-notes.md", "ReleaseNotes.md", "notes.md")) {
|
||||
$match = $markdownFiles | Where-Object { $_.Name -ieq $name } | Select-Object -First 1
|
||||
if ($match) {
|
||||
return $match.FullName
|
||||
}
|
||||
}
|
||||
|
||||
if ($markdownFiles.Count -eq 1) {
|
||||
return $markdownFiles[0].FullName
|
||||
}
|
||||
|
||||
throw "Could not infer release notes file. Pass -NotesFile explicitly."
|
||||
}
|
||||
|
||||
function Test-ReleaseNotesInstallerLinks {
|
||||
param([Parameter(Mandatory)][string]$Path, [Parameter(Mandatory)][string]$Tag, [Parameter(Mandatory)][string]$Version)
|
||||
$content = Get-Content -LiteralPath $Path -Raw
|
||||
$expectedLinks = @{
|
||||
"ptUserX64" = "https://github.com/$GitHubRepo/releases/download/$Tag/PowerToysUserSetup-$Version-x64.exe"
|
||||
"ptUserArm64" = "https://github.com/$GitHubRepo/releases/download/$Tag/PowerToysUserSetup-$Version-arm64.exe"
|
||||
"ptMachineX64" = "https://github.com/$GitHubRepo/releases/download/$Tag/PowerToysSetup-$Version-x64.exe"
|
||||
"ptMachineArm64" = "https://github.com/$GitHubRepo/releases/download/$Tag/PowerToysSetup-$Version-arm64.exe"
|
||||
}
|
||||
|
||||
foreach ($link in $expectedLinks.GetEnumerator()) {
|
||||
if ($content -notmatch "(?im)^\[$([regex]::Escape($link.Key))\]:\s*$([regex]::Escape($link.Value))\s*$") {
|
||||
throw "Release notes are missing installer link reference [$($link.Key)]: $($link.Value)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Get-Command gh -ErrorAction SilentlyContinue)) {
|
||||
throw "GitHub CLI (`gh`) was not found in PATH."
|
||||
}
|
||||
|
||||
Invoke-Gh -Arguments @("auth", "status") | Out-Null
|
||||
|
||||
$releaseFolderPath = Resolve-ExistingPath -Path $ReleaseFolder -Description "Release folder"
|
||||
if (-not (Test-Path -LiteralPath $releaseFolderPath -PathType Container)) {
|
||||
throw "ReleaseFolder must be a directory: $releaseFolderPath"
|
||||
}
|
||||
|
||||
$allFiles = @(Get-ChildItem -LiteralPath $releaseFolderPath -File)
|
||||
$installerMatches = @()
|
||||
foreach ($file in $allFiles) {
|
||||
if ($file.Name -match '^PowerToys(?<User>User)?Setup-(?<Version>.+)-(?<Arch>x64|arm64)\.exe$') {
|
||||
$installerMatches += [pscustomobject]@{ File = $file; Version = $Matches.Version; Arch = $Matches.Arch; IsUser = -not [string]::IsNullOrEmpty($Matches.User) }
|
||||
}
|
||||
}
|
||||
|
||||
if ($installerMatches.Count -ne 4) {
|
||||
throw "Expected exactly four PowerToys installer EXEs, found $($installerMatches.Count)."
|
||||
}
|
||||
|
||||
$versions = @($installerMatches | Select-Object -ExpandProperty Version -Unique)
|
||||
if ($versions.Count -ne 1) {
|
||||
throw "Installer versions do not match: $($versions -join ', ')"
|
||||
}
|
||||
|
||||
$numericVersion = $versions[0]
|
||||
if (-not $TagName) {
|
||||
$TagName = "v$numericVersion"
|
||||
}
|
||||
if (-not $Title) {
|
||||
$displayVersion = $TagName.TrimStart('v', 'V') -replace '-preview$', ''
|
||||
$Title = if ($Prerelease) { "PowerToys v$displayVersion Preview" } else { "Release v$numericVersion" }
|
||||
}
|
||||
|
||||
$filesByName = @{}
|
||||
foreach ($file in $allFiles) {
|
||||
$filesByName[$file.Name] = $file
|
||||
}
|
||||
|
||||
$expectedAssets = @(
|
||||
"PowerToysSetup-$numericVersion-x64.exe",
|
||||
"PowerToysSetup-$numericVersion-arm64.exe",
|
||||
"PowerToysUserSetup-$numericVersion-x64.exe",
|
||||
"PowerToysUserSetup-$numericVersion-arm64.exe",
|
||||
"symbols-x64.zip",
|
||||
"symbols-arm64.zip",
|
||||
"GroupPolicyObjectFiles-$numericVersion.zip"
|
||||
)
|
||||
|
||||
$assets = New-Object System.Collections.Generic.List[string]
|
||||
foreach ($name in $expectedAssets) {
|
||||
if (-not $filesByName.ContainsKey($name)) {
|
||||
throw "Required release asset is missing: $name"
|
||||
}
|
||||
$assets.Add($filesByName[$name].FullName)
|
||||
}
|
||||
|
||||
$notesFilePath = Get-ReleaseNotesFile -Folder $releaseFolderPath -Version $numericVersion -ExplicitNotesFile $NotesFile
|
||||
Test-ReleaseNotesInstallerLinks -Path $notesFilePath -Tag $TagName -Version $numericVersion
|
||||
|
||||
Write-Host "Release folder: $releaseFolderPath"
|
||||
Write-Host "Repository: $GitHubRepo"
|
||||
Write-Host "Numeric version:$numericVersion"
|
||||
Write-Host "Tag: $TagName"
|
||||
Write-Host "Title: $Title"
|
||||
Write-Host "Notes: $notesFilePath"
|
||||
if ($Target) {
|
||||
Write-Host "Target: $Target"
|
||||
} else {
|
||||
Write-Warning "No -Target was provided. If the tag does not already exist, gh will create it from the repository default branch."
|
||||
}
|
||||
|
||||
$releaseJson = Invoke-Gh -Arguments @("release", "view", $TagName, "--repo", $GitHubRepo, "--json", "isDraft,url") -AllowFailure
|
||||
$releaseExists = -not [string]::IsNullOrWhiteSpace(($releaseJson | Out-String))
|
||||
|
||||
if ($releaseExists) {
|
||||
$release = ($releaseJson | Out-String) | ConvertFrom-Json
|
||||
if (-not $release.isDraft) {
|
||||
throw "Release $TagName already exists and is not a draft: $($release.url)"
|
||||
}
|
||||
|
||||
$editArgs = @("release", "edit", $TagName, "--repo", $GitHubRepo, "--draft", "--title", $Title, "--notes-file", $notesFilePath)
|
||||
if ($Target) { $editArgs += @("--target", $Target) }
|
||||
if ($Prerelease) { $editArgs += "--prerelease" }
|
||||
Invoke-Gh -Arguments $editArgs | Out-Null
|
||||
|
||||
$uploadArgs = @("release", "upload", $TagName) + $assets + @("--repo", $GitHubRepo)
|
||||
if ($Clobber) { $uploadArgs += "--clobber" }
|
||||
Invoke-Gh -Arguments $uploadArgs | Out-Null
|
||||
} else {
|
||||
$createArgs = @("release", "create", $TagName) + $assets + @("--repo", $GitHubRepo, "--draft", "--title", $Title, "--notes-file", $notesFilePath)
|
||||
if ($Target) { $createArgs += @("--target", $Target) }
|
||||
if ($Prerelease) { $createArgs += "--prerelease" }
|
||||
if ($Prerelease -or $LatestFalse) { $createArgs += "--latest=false" }
|
||||
Invoke-Gh -Arguments $createArgs | Out-Null
|
||||
}
|
||||
|
||||
if ($DryRun) {
|
||||
Write-Host "Dry run complete. No GitHub release was created or modified." -ForegroundColor Yellow
|
||||
} else {
|
||||
$result = Invoke-Gh -Arguments @("release", "view", $TagName, "--repo", $GitHubRepo, "--json", "url", "--jq", ".url")
|
||||
Write-Host "Draft release ready: $result" -ForegroundColor Green
|
||||
}
|
||||
1
.github/workflows/msstore-submissions.yml
vendored
1
.github/workflows/msstore-submissions.yml
vendored
@@ -12,6 +12,7 @@ jobs:
|
||||
|
||||
microsoft_store:
|
||||
name: Publish Microsoft Store
|
||||
if: ${{ !github.event.release.prerelease }}
|
||||
environment: store
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
339
.pipelines/resolveBuildMetadata.ps1
Normal file
339
.pipelines/resolveBuildMetadata.ps1
Normal file
@@ -0,0 +1,339 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[AllowEmptyString()]
|
||||
[string]$VersionOverride = "",
|
||||
|
||||
[string]$SourceBranch = $env:BUILD_SOURCEBRANCH,
|
||||
|
||||
[string]$BuildReason = $env:BUILD_REASON,
|
||||
|
||||
[string]$BuildNumber = $env:BUILD_BUILDNUMBER,
|
||||
|
||||
[AllowEmptyString()]
|
||||
[string]$BuildDate = "",
|
||||
|
||||
[AllowEmptyString()]
|
||||
[string]$DailyVersionSequence = "",
|
||||
|
||||
[string]$VersionPropsPath = (Join-Path $PSScriptRoot "..\src\Version.props")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-BuildStamp {
|
||||
param([string]$PipelineBuildNumber)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($PipelineBuildNumber)) {
|
||||
$now = Get-Date
|
||||
return [pscustomobject]@{
|
||||
Date = $now.Date
|
||||
Revision = 1
|
||||
}
|
||||
}
|
||||
|
||||
if ($PipelineBuildNumber -notmatch "_(?<yearMonth>\d{4})\.(?<day>\d{2})(?<revision>\d{3})(?:-.+)?$") {
|
||||
throw "Build number '$PipelineBuildNumber' does not end with the expected _YYMM.DDNNN pattern"
|
||||
}
|
||||
|
||||
try {
|
||||
$date = [datetime]::ParseExact(
|
||||
"20$($matches["yearMonth"])$($matches["day"])",
|
||||
"yyyyMMdd",
|
||||
[Globalization.CultureInfo]::InvariantCulture)
|
||||
}
|
||||
catch {
|
||||
throw "Build number '$PipelineBuildNumber' contains an invalid date"
|
||||
}
|
||||
|
||||
$revision = [int]::Parse($matches["revision"])
|
||||
if ($revision -lt 1 -or $revision -gt 99) {
|
||||
throw "Build number '$PipelineBuildNumber' has daily revision '$revision'; canonical versions support revisions 001 through 099"
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
Date = $date
|
||||
Revision = $revision
|
||||
}
|
||||
}
|
||||
|
||||
function Test-VersionParts {
|
||||
param([Parameter(Mandatory)][string[]]$Parts)
|
||||
|
||||
foreach ($part in $Parts) {
|
||||
$value = [int]::Parse($part)
|
||||
if ($value -lt 0 -or $value -gt [UInt16]::MaxValue) {
|
||||
throw "Version component '$value' is outside the supported Windows version range 0-65535"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-VersionDate {
|
||||
param(
|
||||
[AllowEmptyString()][string]$DateOverride,
|
||||
[Parameter(Mandatory)]$BuildStamp
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($DateOverride)) {
|
||||
return $BuildStamp.Date
|
||||
}
|
||||
|
||||
try {
|
||||
return [datetime]::ParseExact(
|
||||
$DateOverride,
|
||||
"yyyyMMdd",
|
||||
[Globalization.CultureInfo]::InvariantCulture)
|
||||
}
|
||||
catch {
|
||||
throw "Build date '$DateOverride' must use the yyyyMMdd format"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ReleaseTrainMetadata {
|
||||
param([Parameter(Mandatory)][string]$Path)
|
||||
|
||||
[xml]$versionProps = Get-Content -LiteralPath $Path
|
||||
$releaseTrain = [string]$versionProps.Project.PropertyGroup.ReleaseTrainVersion
|
||||
if ($releaseTrain -notmatch "^(?<major>\d+)\.(?<minor>\d+)$") {
|
||||
throw "ReleaseTrainVersion in '$Path' must use the major.minor format"
|
||||
}
|
||||
|
||||
Test-VersionParts -Parts @($matches["major"], $matches["minor"])
|
||||
if ([int]::Parse($matches["major"]) -gt 255 -or [int]::Parse($matches["minor"]) -gt 255) {
|
||||
throw "ReleaseTrainVersion in '$Path' must keep major and minor within the MSI-supported range 0-255"
|
||||
}
|
||||
|
||||
$epochText = [string]$versionProps.Project.PropertyGroup.ReleaseTrainEpoch
|
||||
try {
|
||||
$epoch = [datetime]::ParseExact(
|
||||
$epochText,
|
||||
"yyyy-MM-dd",
|
||||
[Globalization.CultureInfo]::InvariantCulture)
|
||||
}
|
||||
catch {
|
||||
throw "ReleaseTrainEpoch in '$Path' must use the yyyy-MM-dd format"
|
||||
}
|
||||
|
||||
if ($epoch.Month -ne 1 -or $epoch.Day -ne 1) {
|
||||
throw "ReleaseTrainEpoch in '$Path' must be January 1 of the active epoch year"
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
Version = $releaseTrain
|
||||
Epoch = $epoch
|
||||
}
|
||||
}
|
||||
|
||||
function Get-ReleaseVersion {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ReleaseTrain,
|
||||
[Parameter(Mandatory)][datetime]$Epoch,
|
||||
[Parameter(Mandatory)]$BuildStamp,
|
||||
[Parameter(Mandatory)][int]$DailySequence
|
||||
)
|
||||
|
||||
if ($BuildStamp.Date -lt $Epoch) {
|
||||
throw "Build date '$($BuildStamp.Date.ToString("yyyy-MM-dd"))' is before ReleaseTrainEpoch '$($Epoch.ToString("yyyy-MM-dd"))'"
|
||||
}
|
||||
|
||||
if ($DailySequence -lt 1 -or $DailySequence -gt 9) {
|
||||
throw "Daily release sequence '$DailySequence' is outside the YDDDB-supported range 1-9"
|
||||
}
|
||||
|
||||
$yearOffset = $BuildStamp.Date.Year - $Epoch.Year
|
||||
if ($yearOffset -gt 6) {
|
||||
throw "Release train year offset '$yearOffset' exceeds the MSI-safe YDDDB range 0-6; advance the release train and reset ReleaseTrainEpoch"
|
||||
}
|
||||
|
||||
$thirdComponentText = "{0}{1:D3}{2}" -f $yearOffset, $BuildStamp.Date.DayOfYear, $DailySequence
|
||||
$thirdComponent = [int]::Parse($thirdComponentText)
|
||||
if ($thirdComponent -gt [UInt16]::MaxValue) {
|
||||
throw "Generated version component '$thirdComponent' exceeds 65535; advance the release train and reset ReleaseTrainEpoch"
|
||||
}
|
||||
|
||||
return "$ReleaseTrain.$thirdComponent.0"
|
||||
}
|
||||
|
||||
function Get-PrivateVersion {
|
||||
param(
|
||||
[Parameter(Mandatory)][datetime]$Epoch,
|
||||
[Parameter(Mandatory)]$BuildStamp
|
||||
)
|
||||
|
||||
$extendedDay = ($BuildStamp.Date - $Epoch).Days + 1
|
||||
if ($extendedDay -lt 1) {
|
||||
throw "Build date '$($BuildStamp.Date.ToString("yyyy-MM-dd"))' is before ReleaseTrainEpoch '$($Epoch.ToString("yyyy-MM-dd"))'"
|
||||
}
|
||||
|
||||
$thirdComponent = ($extendedDay * 100) + $BuildStamp.Revision
|
||||
if ($thirdComponent -gt [UInt16]::MaxValue) {
|
||||
throw "Generated private version component '$thirdComponent' exceeds 65535"
|
||||
}
|
||||
|
||||
return "0.0.$thirdComponent.0"
|
||||
}
|
||||
|
||||
function Get-ReleaseDailySequence {
|
||||
param([AllowEmptyString()][string]$Sequence)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Sequence)) {
|
||||
throw "DailyVersionSequence is required for main and stable builds"
|
||||
}
|
||||
|
||||
if ($Sequence -notmatch "^\d+$") {
|
||||
throw "Daily release sequence '$Sequence' must be numeric"
|
||||
}
|
||||
|
||||
return [int]::Parse($Sequence)
|
||||
}
|
||||
|
||||
function Get-PreviewVersion {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ReleaseTrain,
|
||||
[AllowEmptyString()][string]$Override,
|
||||
[Parameter(Mandatory)][string]$GeneratedVersion
|
||||
)
|
||||
|
||||
$inputVersion = $Override.Trim()
|
||||
if ($inputVersion.EndsWith("-preview", [StringComparison]::OrdinalIgnoreCase)) {
|
||||
$inputVersion = $inputVersion.Substring(0, $inputVersion.Length - "-preview".Length)
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($inputVersion)) {
|
||||
return $GeneratedVersion
|
||||
}
|
||||
|
||||
if ($inputVersion -match "^(?<major>\d+)\.(?<minor>\d+)$") {
|
||||
if ($inputVersion -ne $ReleaseTrain) {
|
||||
throw "Preview version base '$inputVersion' does not match ReleaseTrainVersion '$ReleaseTrain'"
|
||||
}
|
||||
|
||||
return $GeneratedVersion
|
||||
}
|
||||
|
||||
if ($inputVersion -notmatch "^(?<major>\d+)\.(?<minor>\d+)\.(?<revision>\d+)\.(?<build>\d+)$") {
|
||||
throw "Preview version override must be major.minor or major.minor.YDDDB.0, optionally followed by -preview"
|
||||
}
|
||||
|
||||
if ("$($matches["major"]).$($matches["minor"])" -ne $ReleaseTrain) {
|
||||
throw "Preview version '$inputVersion' does not match ReleaseTrainVersion '$ReleaseTrain'"
|
||||
}
|
||||
|
||||
if ([int]::Parse($matches["build"]) -ne 0) {
|
||||
throw "Preview version '$inputVersion' must use 0 for the fourth component"
|
||||
}
|
||||
|
||||
Test-VersionParts -Parts @($matches["major"], $matches["minor"], $matches["revision"], $matches["build"])
|
||||
$parts = @($matches["major"], $matches["minor"], $matches["revision"], $matches["build"])
|
||||
return ($parts | ForEach-Object { [int]::Parse($_) }) -join "."
|
||||
}
|
||||
|
||||
function Get-MsiSafeVersionOverride {
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$Override,
|
||||
[Parameter(Mandatory)][string]$VersionKind
|
||||
)
|
||||
|
||||
$inputVersion = $Override.Trim()
|
||||
if ($inputVersion -notmatch "^(?<major>\d+)\.(?<minor>\d+)\.(?<revision>\d+)(?:\.(?<build>\d+))?$") {
|
||||
throw "$VersionKind version override must be numeric major.minor.patch or major.minor.patch.build"
|
||||
}
|
||||
|
||||
$parts = @($matches["major"], $matches["minor"], $matches["revision"])
|
||||
if ($matches["build"]) {
|
||||
$parts += $matches["build"]
|
||||
}
|
||||
else {
|
||||
$parts += "0"
|
||||
}
|
||||
|
||||
Test-VersionParts -Parts $parts
|
||||
if ([int]::Parse($parts[0]) -gt 255 -or [int]::Parse($parts[1]) -gt 255) {
|
||||
throw "$VersionKind version '$inputVersion' must keep major and minor within the MSI-supported range 0-255"
|
||||
}
|
||||
|
||||
if ([int]::Parse($parts[3]) -ne 0) {
|
||||
throw "$VersionKind version '$inputVersion' must use 0 for the fourth component"
|
||||
}
|
||||
|
||||
return ($parts | ForEach-Object { [int]::Parse($_) }) -join "."
|
||||
}
|
||||
|
||||
function Get-StableVersion {
|
||||
param(
|
||||
[Parameter(Mandatory)][AllowEmptyString()][string]$Override,
|
||||
[Parameter(Mandatory)][string]$GeneratedVersion
|
||||
)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Override)) {
|
||||
return $GeneratedVersion
|
||||
}
|
||||
|
||||
return Get-MsiSafeVersionOverride -Override $Override -VersionKind "Stable"
|
||||
}
|
||||
|
||||
$isMain = $SourceBranch -eq "refs/heads/main"
|
||||
$isStable = $SourceBranch -eq "refs/heads/stable"
|
||||
$isScheduled = $BuildReason -eq "Schedule"
|
||||
|
||||
if ($isScheduled -and -not $isMain) {
|
||||
throw "Scheduled release builds are only supported from refs/heads/main"
|
||||
}
|
||||
|
||||
$releaseMetadata = Get-ReleaseTrainMetadata -Path $VersionPropsPath
|
||||
$releaseTrain = $releaseMetadata.Version
|
||||
$buildStamp = Get-BuildStamp -PipelineBuildNumber $BuildNumber
|
||||
|
||||
if ($isMain) {
|
||||
if ($isScheduled -and -not [string]::IsNullOrWhiteSpace($VersionOverride)) {
|
||||
throw "Scheduled main builds must use the checked-in ReleaseTrainVersion and cannot specify a version override"
|
||||
}
|
||||
|
||||
$intent = if ($isScheduled) { "preview-release" } else { "preview-validation" }
|
||||
$channel = "preview"
|
||||
$buildStamp.Date = Get-VersionDate -DateOverride $BuildDate -BuildStamp $buildStamp
|
||||
$releaseDailySequence = Get-ReleaseDailySequence -Sequence $DailyVersionSequence
|
||||
$generatedVersion = Get-ReleaseVersion -ReleaseTrain $releaseTrain -Epoch $releaseMetadata.Epoch -BuildStamp $buildStamp -DailySequence $releaseDailySequence
|
||||
$version = Get-PreviewVersion -ReleaseTrain $releaseTrain -Override $VersionOverride -GeneratedVersion $generatedVersion
|
||||
$allowPublicSymbols = $false
|
||||
$shouldPublishPreview = $isScheduled
|
||||
}
|
||||
elseif ($isStable) {
|
||||
if ($isScheduled) {
|
||||
throw "Stable release builds must be queued manually"
|
||||
}
|
||||
|
||||
$intent = "stable-release"
|
||||
$channel = "stable"
|
||||
$buildStamp.Date = Get-VersionDate -DateOverride $BuildDate -BuildStamp $buildStamp
|
||||
$releaseDailySequence = Get-ReleaseDailySequence -Sequence $DailyVersionSequence
|
||||
$generatedVersion = Get-ReleaseVersion -ReleaseTrain $releaseTrain -Epoch $releaseMetadata.Epoch -BuildStamp $buildStamp -DailySequence $releaseDailySequence
|
||||
$version = Get-StableVersion -Override $VersionOverride -GeneratedVersion $generatedVersion
|
||||
$allowPublicSymbols = $true
|
||||
$shouldPublishPreview = $false
|
||||
}
|
||||
else {
|
||||
$intent = "private-validation"
|
||||
$channel = "private"
|
||||
$version = if ([string]::IsNullOrWhiteSpace($VersionOverride)) {
|
||||
Get-PrivateVersion -Epoch $releaseMetadata.Epoch -BuildStamp $buildStamp
|
||||
}
|
||||
else {
|
||||
Get-MsiSafeVersionOverride -Override $VersionOverride -VersionKind "Private"
|
||||
}
|
||||
$allowPublicSymbols = $false
|
||||
$shouldPublishPreview = $false
|
||||
}
|
||||
|
||||
Test-VersionParts -Parts ($version -split "\.")
|
||||
|
||||
Write-Host "Resolved build intent: $intent"
|
||||
Write-Host "Resolved release channel: $channel"
|
||||
Write-Host "Resolved version: $version"
|
||||
|
||||
[pscustomobject]@{
|
||||
Intent = $intent
|
||||
Channel = $channel
|
||||
Version = $version
|
||||
AllowPublicSymbols = $allowPublicSymbols
|
||||
ShouldPublishPreview = $shouldPublishPreview
|
||||
}
|
||||
268
.pipelines/tests/resolveBuildMetadata.Tests.ps1
Normal file
268
.pipelines/tests/resolveBuildMetadata.Tests.ps1
Normal file
@@ -0,0 +1,268 @@
|
||||
# Copyright (c) Microsoft Corporation
|
||||
# The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
|
||||
$scriptPath = Join-Path $PSScriptRoot "..\resolveBuildMetadata.ps1"
|
||||
|
||||
function New-VersionProps {
|
||||
param(
|
||||
[string]$ReleaseTrain = "0.100",
|
||||
[string]$Epoch = "2026-01-01"
|
||||
)
|
||||
|
||||
$path = Join-Path $TestDrive "Version.props"
|
||||
@"
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ReleaseTrainVersion>$ReleaseTrain</ReleaseTrainVersion>
|
||||
<ReleaseTrainEpoch>$Epoch</ReleaseTrainEpoch>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
"@ | Set-Content -LiteralPath $path
|
||||
return $path
|
||||
}
|
||||
|
||||
function Assert-Throws {
|
||||
param([scriptblock]$Action)
|
||||
|
||||
$threw = $false
|
||||
try {
|
||||
& $Action | Out-Null
|
||||
}
|
||||
catch {
|
||||
$threw = $true
|
||||
}
|
||||
|
||||
$threw | Should Be $true
|
||||
}
|
||||
|
||||
Describe "resolveBuildMetadata" {
|
||||
It "generates the canonical preview version for main" {
|
||||
$result = & $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Schedule" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30099-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
$result.Intent | Should Be "preview-release"
|
||||
$result.Channel | Should Be "preview"
|
||||
$result.Version | Should Be "0.100.2111.0"
|
||||
}
|
||||
|
||||
It "uses the generated version by default for stable" {
|
||||
$result = & $scriptPath `
|
||||
-SourceBranch "refs/heads/stable" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30099-stable" `
|
||||
-DailyVersionSequence "2" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
$result.Intent | Should Be "stable-release"
|
||||
$result.Channel | Should Be "stable"
|
||||
$result.Version | Should Be "0.100.2112.0"
|
||||
}
|
||||
|
||||
It "keeps private builds independent from the release counter" {
|
||||
$result = & $scriptPath `
|
||||
-SourceBranch "refs/heads/user/feature" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30003-feature" `
|
||||
-BuildDate "20260731" `
|
||||
-DailyVersionSequence "9" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
$result.Version | Should Be "0.0.21103.0"
|
||||
}
|
||||
|
||||
It "allows an explicit MSI-safe version for private validation" {
|
||||
$result = & $scriptPath `
|
||||
-VersionOverride "0.100.2151.0" `
|
||||
-SourceBranch "refs/heads/LegendaryBlair/preview-version" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2608.03001-preview-version" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
$result.Intent | Should Be "private-validation"
|
||||
$result.Channel | Should Be "private"
|
||||
$result.Version | Should Be "0.100.2151.0"
|
||||
$result.AllowPublicSymbols | Should Be $false
|
||||
$result.ShouldPublishPreview | Should Be $false
|
||||
}
|
||||
|
||||
It "rejects a private override with a nonzero fourth component" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-VersionOverride "0.100.2151.1" `
|
||||
-SourceBranch "refs/heads/LegendaryBlair/preview-version" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2608.03001-preview-version" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
}
|
||||
}
|
||||
|
||||
It "increments the year digit across a calendar year boundary" {
|
||||
$result = & $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2701.02001-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
$result.Version | Should Be "0.100.10021.0"
|
||||
}
|
||||
|
||||
It "resets the year digit after the epoch advances with the release train" {
|
||||
$result = & $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2701.02001-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps -ReleaseTrain "0.101" -Epoch "2027-01-01")
|
||||
|
||||
$result.Version | Should Be "0.101.21.0"
|
||||
}
|
||||
|
||||
It "preserves monotonicity at the year boundary" {
|
||||
$lastBuildOfYear = & $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2612.31099-main" `
|
||||
-DailyVersionSequence "9" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
$firstBuildOfNextYear = & $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2701.01001-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
[int]($firstBuildOfNextYear.Version -split "\.")[2] | Should BeGreaterThan ([int]($lastBuildOfYear.Version -split "\.")[2])
|
||||
}
|
||||
|
||||
It "preserves an explicit stable override" {
|
||||
$result = & $scriptPath `
|
||||
-VersionOverride "0.100.2" `
|
||||
-SourceBranch "refs/heads/stable" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-stable" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
$result.Version | Should Be "0.100.2.0"
|
||||
}
|
||||
|
||||
It "rejects a stable override with a nonzero fourth component" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-VersionOverride "0.100.2.1" `
|
||||
-SourceBranch "refs/heads/stable" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-stable" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
}
|
||||
}
|
||||
|
||||
It "rejects a stable override outside MSI major and minor limits" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-VersionOverride "0.256.2.0" `
|
||||
-SourceBranch "refs/heads/stable" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-stable" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
}
|
||||
}
|
||||
|
||||
It "preserves a canonical full preview override" {
|
||||
$result = & $scriptPath `
|
||||
-VersionOverride "0.100.2111.0" `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
$result.Version | Should Be "0.100.2111.0"
|
||||
}
|
||||
|
||||
It "rejects a preview override with a nonzero fourth component" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-VersionOverride "0.100.2111.1" `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
}
|
||||
}
|
||||
|
||||
It "rejects a preview override from a different release train" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-VersionOverride "0.101.2111.0" `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
}
|
||||
}
|
||||
|
||||
It "rejects daily release sequences above 9" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-main" `
|
||||
-DailyVersionSequence "10" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
}
|
||||
}
|
||||
|
||||
It "requires a release sequence for main and stable" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-main" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
}
|
||||
}
|
||||
|
||||
It "uses the pipeline date for both YDDD and counter alignment" {
|
||||
$result = & $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-main" `
|
||||
-BuildDate "20260731" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
|
||||
$result.Version | Should Be "0.100.2121.0"
|
||||
}
|
||||
|
||||
It "requires the epoch to be January 1" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_2607.30001-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps -Epoch "2026-02-01")
|
||||
}
|
||||
}
|
||||
|
||||
It "rejects release trains that exceed the YDDDB year range" {
|
||||
Assert-Throws {
|
||||
& $scriptPath `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber "PowerToys Signed YAML Release Build_3301.01001-main" `
|
||||
-DailyVersionSequence "1" `
|
||||
-VersionPropsPath (New-VersionProps)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,9 +16,9 @@ parameters:
|
||||
default: false
|
||||
|
||||
- name: versionNumber
|
||||
displayName: "Version Number"
|
||||
displayName: "Version Override (optional; main and stable default to the release-train version)"
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
default: ''
|
||||
|
||||
- name: buildConfigurations
|
||||
displayName: "Build Configurations"
|
||||
@@ -42,6 +42,14 @@ name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
|
||||
|
||||
variables:
|
||||
- template: templates/variables-nuget-package-version.yml
|
||||
- name: versionDate
|
||||
value: $[format('{0:yyyyMMdd}', pipeline.startTime)]
|
||||
- ${{ if or(eq(variables['Build.SourceBranch'], 'refs/heads/main'), eq(variables['Build.SourceBranch'], 'refs/heads/stable')) }}:
|
||||
- name: dailyVersionSequence
|
||||
value: $[counter(format('release-{0:yyyyMMdd}', pipeline.startTime), 1)]
|
||||
- ${{ else }}:
|
||||
- name: dailyVersionSequence
|
||||
value: 0
|
||||
|
||||
extends:
|
||||
template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates
|
||||
@@ -110,7 +118,24 @@ extends:
|
||||
|
||||
# Sets versions for all PowerToy created DLLs
|
||||
- pwsh: |-
|
||||
.pipelines/versionSetting.ps1 -versionNumber '${{ parameters.versionNumber }}' -DevEnvironment ''
|
||||
$metadata = .pipelines/resolveBuildMetadata.ps1 `
|
||||
-VersionOverride '${{ parameters.versionNumber }}' `
|
||||
-BuildDate '$(versionDate)' `
|
||||
-DailyVersionSequence '$(dailyVersionSequence)'
|
||||
$publishSymbolsToPublic = $${{ parameters.publishSymbolsToPublic }}
|
||||
if ($publishSymbolsToPublic -and -not $metadata.AllowPublicSymbols) {
|
||||
throw "Public symbols are only supported for manually queued stable-branch releases"
|
||||
}
|
||||
|
||||
.pipelines/versionSetting.ps1 -versionNumber $metadata.Version -DevEnvironment '' -Channel $metadata.Channel
|
||||
[xml]$versionProps = Get-Content 'src\Version.props'
|
||||
$resolvedVersion = [string]$versionProps.Project.PropertyGroup.Version
|
||||
Write-Host "Resolved PowerToys version: $resolvedVersion"
|
||||
Write-Host "##vso[task.setvariable variable=ResolvedVersionNumber]$resolvedVersion"
|
||||
Write-Host "##vso[task.setvariable variable=EffectiveVersionNumber]$resolvedVersion"
|
||||
Write-Host "##vso[task.setvariable variable=ResolvedReleaseChannel]$($metadata.Channel)"
|
||||
Write-Host "##vso[task.setvariable variable=ResolvedBuildIntent]$($metadata.Intent)"
|
||||
Write-Host "##vso[task.setvariable variable=ShouldPublishPreview]$($metadata.ShouldPublishPreview)"
|
||||
displayName: Prepare versioning
|
||||
|
||||
# Prepare the localizations and telemetry config before the release build
|
||||
|
||||
@@ -65,6 +65,9 @@ parameters:
|
||||
- name: versionNumber
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
- name: resolvedVersionNumber
|
||||
type: string
|
||||
default: ''
|
||||
- name: useLatestWinAppSDK
|
||||
type: boolean
|
||||
default: false
|
||||
@@ -140,6 +143,10 @@ jobs:
|
||||
JobOutputDirectory: $(Build.ArtifactStagingDirectory)
|
||||
LogOutputDirectory: $(Build.ArtifactStagingDirectory)\logs
|
||||
JobOutputArtifactName: build-$(BuildPlatform)-$(BuildConfiguration)${{ parameters.artifactStem }}
|
||||
${{ if eq(parameters.resolvedVersionNumber, '') }}:
|
||||
EffectiveVersionNumber: ${{ parameters.versionNumber }}
|
||||
${{ if ne(parameters.resolvedVersionNumber, '') }}:
|
||||
EffectiveVersionNumber: ${{ parameters.resolvedVersionNumber }}
|
||||
NUGET_RESTORE_MSBUILD_ARGS: /p:Platform=$(BuildPlatform) # Required for nuget to work due to self contained
|
||||
NODE_OPTIONS: --max_old_space_size=16384
|
||||
${{ if or(eq(parameters.runTests, true), eq(parameters.buildTests, true)) }}:
|
||||
@@ -599,7 +606,7 @@ jobs:
|
||||
parameters:
|
||||
codeSign: ${{ parameters.codeSign }}
|
||||
signingIdentity: ${{ parameters.signingIdentity }}
|
||||
versionNumber: ${{ parameters.versionNumber }}
|
||||
versionNumber: $(EffectiveVersionNumber)
|
||||
additionalBuildOptions: ${{ parameters.additionalBuildOptions }}
|
||||
|
||||
# This saves ~1GiB per architecture. We won't need these later.
|
||||
@@ -663,7 +670,7 @@ jobs:
|
||||
|
||||
# Publishing the GPO files
|
||||
- pwsh: |-
|
||||
$GpoArchive = "$(JobOutputDirectory)\GroupPolicyObjectFiles-${{ parameters.versionNumber }}.zip"
|
||||
$GpoArchive = "$(JobOutputDirectory)\GroupPolicyObjectFiles-$(EffectiveVersionNumber).zip"
|
||||
tar -c -v --format=zip -C .\src\gpo\assets -f $GpoArchive *
|
||||
displayName: Stage GPO files
|
||||
|
||||
|
||||
@@ -11,6 +11,9 @@ parameters:
|
||||
- name: versionNumber
|
||||
type: string
|
||||
default: '0.0.1'
|
||||
- name: resolvedVersionNumber
|
||||
type: string
|
||||
default: ''
|
||||
- name: artifactStem
|
||||
type: string
|
||||
default: ''
|
||||
@@ -39,7 +42,11 @@ jobs:
|
||||
dependsOn: ${{ parameters.dependsOn }}
|
||||
variables:
|
||||
${{ insert }}: ${{ parameters.variables }}
|
||||
SymbolsArtifactName: "PowerToys_${{parameters.versionNumber}}_$(Build.BuildNumber)"
|
||||
${{ if eq(parameters.resolvedVersionNumber, '') }}:
|
||||
EffectiveVersionNumber: ${{ parameters.versionNumber }}
|
||||
${{ if ne(parameters.resolvedVersionNumber, '') }}:
|
||||
EffectiveVersionNumber: ${{ parameters.resolvedVersionNumber }}
|
||||
SymbolsArtifactName: "PowerToys_$(EffectiveVersionNumber)_$(Build.BuildNumber)"
|
||||
steps:
|
||||
- checkout: self
|
||||
clean: true
|
||||
@@ -48,6 +55,28 @@ jobs:
|
||||
submodules: true
|
||||
persistCredentials: True
|
||||
|
||||
- pwsh: |-
|
||||
$effectiveVersionNumber = '${{ parameters.resolvedVersionNumber }}'
|
||||
if ([string]::IsNullOrWhiteSpace($effectiveVersionNumber) -or $effectiveVersionNumber.StartsWith('$(')) {
|
||||
$metadata = .pipelines/resolveBuildMetadata.ps1 `
|
||||
-VersionOverride '${{ parameters.versionNumber }}' `
|
||||
-BuildDate '$(versionDate)' `
|
||||
-DailyVersionSequence '$(dailyVersionSequence)'
|
||||
if ($${{ parameters.includePublicSymbolServer }} -and -not $metadata.AllowPublicSymbols) {
|
||||
throw "Public symbols are only supported for manually queued stable-branch releases"
|
||||
}
|
||||
|
||||
$effectiveVersionNumber = $metadata.Version
|
||||
}
|
||||
|
||||
if ($effectiveVersionNumber -match '^(?<numeric>\d+\.\d+(?:\.\d+){0,2})-preview$') {
|
||||
$effectiveVersionNumber = $matches['numeric']
|
||||
}
|
||||
|
||||
Write-Host "##vso[task.setvariable variable=EffectiveVersionNumber]$effectiveVersionNumber"
|
||||
Write-Host "##vso[task.setvariable variable=SymbolsArtifactName]PowerToys_${effectiveVersionNumber}_$(Build.BuildNumber)"
|
||||
displayName: Resolve symbol version
|
||||
|
||||
- task: DownloadPipelineArtifact@2
|
||||
displayName: Download all PDBs from all prior build phases
|
||||
inputs:
|
||||
@@ -83,7 +112,7 @@ jobs:
|
||||
SymbolsMaximumWaitTime: 30
|
||||
SymbolServerType: 'TeamServices'
|
||||
SymbolsProduct: 'PowerToys Converged Symbols'
|
||||
SymbolsVersion: '${{ parameters.versionNumber }}'
|
||||
SymbolsVersion: '$(EffectiveVersionNumber)'
|
||||
SymbolsArtifactName: $(SymbolsArtifactName)
|
||||
SymbolExpirationInDays: ${{ parameters.symbolExpiryTime }}
|
||||
env:
|
||||
|
||||
@@ -5,37 +5,85 @@ Param(
|
||||
|
||||
[Parameter(Mandatory=$True,Position=2)]
|
||||
[AllowEmptyString()]
|
||||
[string]$DevEnvironment = "Local"
|
||||
[string]$DevEnvironment = "Local",
|
||||
|
||||
[ValidateSet("stable", "preview", "private")]
|
||||
[string]$Channel = "stable",
|
||||
|
||||
[string]$SourceCommit = $env:BUILD_SOURCEVERSION,
|
||||
|
||||
[string]$BuildNumber = $env:BUILD_BUILDNUMBER,
|
||||
|
||||
[string]$BuildDate = $env:VERSIONDATE,
|
||||
|
||||
[string]$DailyVersionSequence = $env:DAILYVERSIONSEQUENCE
|
||||
)
|
||||
|
||||
Write-Host $PSScriptRoot
|
||||
$versionRegex = "(\d+)\.(\d+)\.(\d+)"
|
||||
|
||||
if($versionNumber -match $versionRegEx)
|
||||
{
|
||||
#$buildDayOfYear = (Get-Date).DayofYear;
|
||||
#$buildTime = Get-Date -Format HH;
|
||||
#$buildTime = Get-Date -Format HHmmss;
|
||||
#$buildYear = Get-Date -Format yy;
|
||||
#$revision = [string]::Format("{0}{1}{2}", $buildYear, $buildDayOfYear, $buildTime )
|
||||
function Get-NormalizedVersion {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$InputVersion,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ReleaseChannel,
|
||||
[string]$PipelineBuildNumber,
|
||||
[string]$PipelineBuildDate,
|
||||
[string]$PipelineDailyVersionSequence
|
||||
)
|
||||
|
||||
# max UInt16, 65535
|
||||
#$revision = [string]::Format("{0}{1}", $buildDayOfYear, $buildTime )
|
||||
#Write-Host "Revision" $revision
|
||||
if ($InputVersion -match "^(?<numeric>\d+\.\d+(?:\.\d+){0,2})-(?<suffix>preview)$") {
|
||||
if ($ReleaseChannel -ne "preview") {
|
||||
throw "Version suffix '-preview' can only be used with the preview release channel"
|
||||
}
|
||||
|
||||
$versionNumber = [int]::Parse($matches[1]).ToString() + "." + [int]::Parse($matches[2]).ToString() + "." + [int]::Parse($matches[3]).ToString() # + "." + $revision
|
||||
Write-Host "Version Number" $versionNumber
|
||||
$InputVersion = $matches["numeric"]
|
||||
}
|
||||
|
||||
if ($ReleaseChannel -eq "preview" -and $InputVersion -match "^(\d+)\.(\d+)$") {
|
||||
$metadata = & (Join-Path $PSScriptRoot "resolveBuildMetadata.ps1") `
|
||||
-VersionOverride $InputVersion `
|
||||
-SourceBranch "refs/heads/main" `
|
||||
-BuildReason "Manual" `
|
||||
-BuildNumber $PipelineBuildNumber `
|
||||
-BuildDate $PipelineBuildDate `
|
||||
-DailyVersionSequence $PipelineDailyVersionSequence
|
||||
return $metadata.Version
|
||||
}
|
||||
|
||||
if ($InputVersion -match "^(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?$") {
|
||||
$versionParts = @([int]::Parse($matches[1]), [int]::Parse($matches[2]), [int]::Parse($matches[3]))
|
||||
if ($matches[4]) {
|
||||
$versionParts += [int]::Parse($matches[4])
|
||||
}
|
||||
|
||||
return $versionParts -join "."
|
||||
}
|
||||
|
||||
throw "Build format does not match the expected pattern (w.x, w.x.y, w.x.y.z, or w.x.y.z-preview for preview channel)"
|
||||
}
|
||||
else
|
||||
{
|
||||
throw "Build format does not match the expected pattern (buildName_w.x.y.z)"
|
||||
|
||||
$versionNumber = Get-NormalizedVersion `
|
||||
-InputVersion $versionNumber `
|
||||
-ReleaseChannel $Channel `
|
||||
-PipelineBuildNumber $BuildNumber `
|
||||
-PipelineBuildDate $BuildDate `
|
||||
-PipelineDailyVersionSequence $DailyVersionSequence
|
||||
foreach ($part in ($versionNumber -split '\.')) {
|
||||
$value = [int]::Parse($part)
|
||||
if ($value -lt 0 -or $value -gt [UInt16]::MaxValue) {
|
||||
throw "Version component '$value' is outside the supported Windows version range 0-65535"
|
||||
}
|
||||
}
|
||||
Write-Host "Version Number" $versionNumber
|
||||
|
||||
$verPropWriteFileLocation = $PSScriptRoot + '/../src/Version.props';
|
||||
$verPropReadFileLocation = $verPropWriteFileLocation;
|
||||
|
||||
[XML]$verProps = Get-Content $verPropReadFileLocation
|
||||
$verProps.Project.PropertyGroup.Version = $versionNumber;
|
||||
$verProps.Project.PropertyGroup.VersionChannel = $Channel;
|
||||
$verProps.Project.PropertyGroup.SourceCommit = if ([string]::IsNullOrWhiteSpace($SourceCommit)) { "" } else { $SourceCommit };
|
||||
$verProps.Project.PropertyGroup.DevEnvironment = $DevEnvironment;
|
||||
|
||||
Write-Host "xml" $verProps.Project.PropertyGroup.Version
|
||||
@@ -51,12 +99,14 @@ Write-Host "xml" $verProps.Project.PropertyGroup.Version
|
||||
$verProps.Save($verPropWriteFileLocation);
|
||||
#######
|
||||
|
||||
$manifestVersionNumber = if (($versionNumber.ToCharArray() | Where-Object { $_ -eq '.' }).Count -eq 2) { $versionNumber + '.0' } else { $versionNumber }
|
||||
|
||||
# Set PowerRenameContextMenu package version in AppManifest.xml
|
||||
$powerRenameContextMenuAppManifestWriteFileLocation = $PSScriptRoot + '/../src/modules/powerrename/PowerRenameContextMenu/AppxManifest.xml';
|
||||
$powerRenameContextMenuAppManifestReadFileLocation = $powerRenameContextMenuAppManifestWriteFileLocation;
|
||||
|
||||
[XML]$powerRenameContextMenuAppManifest = Get-Content $powerRenameContextMenuAppManifestReadFileLocation
|
||||
$powerRenameContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
|
||||
$powerRenameContextMenuAppManifest.Package.Identity.Version = $manifestVersionNumber
|
||||
Write-Host "PowerRenameContextMenu version" $powerRenameContextMenuAppManifest.Package.Identity.Version
|
||||
$powerRenameContextMenuAppManifest.Save($powerRenameContextMenuAppManifestWriteFileLocation);
|
||||
|
||||
@@ -65,7 +115,7 @@ $imageResizerContextMenuAppManifestWriteFileLocation = $PSScriptRoot + '/../src/
|
||||
$imageResizerContextMenuAppManifestReadFileLocation = $imageResizerContextMenuAppManifestWriteFileLocation;
|
||||
|
||||
[XML]$imageResizerContextMenuAppManifest = Get-Content $imageResizerContextMenuAppManifestReadFileLocation
|
||||
$imageResizerContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
|
||||
$imageResizerContextMenuAppManifest.Package.Identity.Version = $manifestVersionNumber
|
||||
Write-Host "ImageResizerContextMenu version" $imageResizerContextMenuAppManifest.Package.Identity.Version
|
||||
$imageResizerContextMenuAppManifest.Save($imageResizerContextMenuAppManifestWriteFileLocation);
|
||||
|
||||
@@ -74,7 +124,7 @@ $fileLocksmithContextMenuAppManifestWriteFileLocation = $PSScriptRoot + '/../src
|
||||
$fileLocksmithContextMenuAppManifestReadFileLocation = $fileLocksmithContextMenuAppManifestWriteFileLocation;
|
||||
|
||||
[XML]$fileLocksmithContextMenuAppManifest = Get-Content $fileLocksmithContextMenuAppManifestReadFileLocation
|
||||
$fileLocksmithContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
|
||||
$fileLocksmithContextMenuAppManifest.Package.Identity.Version = $manifestVersionNumber
|
||||
Write-Host "FileLocksmithContextMenu version" $fileLocksmithContextMenuAppManifest.Package.Identity.Version
|
||||
$fileLocksmithContextMenuAppManifest.Save($fileLocksmithContextMenuAppManifestWriteFileLocation);
|
||||
|
||||
@@ -83,6 +133,6 @@ $newPlusContextMenuAppManifestWriteFileLocation = $PSScriptRoot + '/../src/modul
|
||||
$newPlusContextMenuAppManifestReadFileLocation = $newPlusContextMenuAppManifestWriteFileLocation;
|
||||
|
||||
[XML]$newPlusContextMenuAppManifest = Get-Content $newPlusContextMenuAppManifestReadFileLocation
|
||||
$newPlusContextMenuAppManifest.Package.Identity.Version = $versionNumber + '.0'
|
||||
$newPlusContextMenuAppManifest.Package.Identity.Version = $manifestVersionNumber
|
||||
Write-Host "NewPlusContextMenu version" $newPlusContextMenuAppManifest.Package.Identity.Version
|
||||
$newPlusContextMenuAppManifest.Save($newPlusContextMenuAppManifestWriteFileLocation);
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<AnalysisMode>Recommended</AnalysisMode>
|
||||
<_SkipUpgradeNetAnalyzersNuGetWarning>true</_SkipUpgradeNetAnalyzersNuGetWarning>
|
||||
<NuGetAuditMode>direct</NuGetAuditMode>
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Don't add source revision hash to the product version of binaries. -->
|
||||
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion> <!-- Source commit is exposed explicitly via Version.props/version_gen.h. -->
|
||||
<PlatformTarget>$(Platform)</PlatformTarget>
|
||||
<RestoreEnablePackagePruning Condition=" '$(VisualStudioVersion)' == '17.0'">false </RestoreEnablePackagePruning>
|
||||
|
||||
@@ -74,7 +74,9 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(MSBuildProjectExtension)' == '.csproj'">
|
||||
<Version>$(Version).0</Version>
|
||||
<VersionBuildSuffix Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(Version)', '^\d+\.\d+\.\d+$'))">.0</VersionBuildSuffix>
|
||||
<VersionBuildSuffix Condition="'$(VersionBuildSuffix)' == ''"></VersionBuildSuffix>
|
||||
<Version>$(Version)$(VersionBuildSuffix)</Version>
|
||||
<RepositoryUrl>https://github.com/microsoft/PowerToys</RepositoryUrl>
|
||||
<RepositoryType>GitHub</RepositoryType>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
|
||||
@@ -83,11 +83,11 @@
|
||||
<Directory Id="WindowsPowerShellFolder" Name="WindowsPowerShell">
|
||||
<Directory Id="PowerShellModulesFolder" Name="Modules">
|
||||
<Directory Id="PowerToysDscFolder" Name="Microsoft.PowerToys.Configure">
|
||||
<Directory Id="PowerToysDscVerFolder" Name="$(var.Version).0">
|
||||
<Directory Id="PowerToysDscVerFolder" Name="$(var.DscModuleVersion)">
|
||||
<Component Id="PowerToysDSC" Guid="C52AECA0-DA73-49B8-BB49-31EF6640FF1F" Bitness="always64">
|
||||
<!-- Don't fail installation because of DSC. Files are marked as not vital. -->
|
||||
<File Vital="no" Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version).0\Microsoft.PowerToys.Configure.psd1" Id="PTConf.psd1" />
|
||||
<File Vital="no" Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version).0\Microsoft.PowerToys.Configure.psm1" Id="PTConf.psm1" />
|
||||
<File Vital="no" Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.DscModuleVersion)\Microsoft.PowerToys.Configure.psd1" Id="PTConf.psd1" />
|
||||
<File Vital="no" Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.DscModuleVersion)\Microsoft.PowerToys.Configure.psm1" Id="PTConf.psm1" />
|
||||
</Component>
|
||||
</Directory>
|
||||
</Directory>
|
||||
|
||||
@@ -12,8 +12,8 @@
|
||||
<RegistryKey Root="$(var.RegistryScope)" Key="Software\Classes\powertoys\components">
|
||||
<RegistryValue Type="string" Name="DSCModulesReference" Value="" KeyPath="yes" />
|
||||
</RegistryKey>
|
||||
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version).0\Microsoft.PowerToys.Configure.psd1" Id="PTConfReference.psd1" />
|
||||
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.Version).0\Microsoft.PowerToys.Configure.psm1" Id="PTConfReference.psm1" />
|
||||
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.DscModuleVersion)\Microsoft.PowerToys.Configure.psd1" Id="PTConfReference.psd1" />
|
||||
<File Source="$(var.RepoDir)\src\dsc\Microsoft.PowerToys.Configure\Generated\Microsoft.PowerToys.Configure\$(var.DscModuleVersion)\Microsoft.PowerToys.Configure.psm1" Id="PTConfReference.psm1" />
|
||||
</Component>
|
||||
|
||||
<!-- DSC v3 JSON manifest files - Generated by generateAllFileComponents.ps1 -->
|
||||
|
||||
@@ -3,8 +3,13 @@
|
||||
<PropertyGroup>
|
||||
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<DscModuleVersionSuffix Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(Version)', '^\d+\.\d+\.\d+$'))">.0</DscModuleVersionSuffix>
|
||||
<DscModuleVersionSuffix Condition="'$(DscModuleVersionSuffix)' == ''"></DscModuleVersionSuffix>
|
||||
<DscModuleVersion>$(Version)$(DscModuleVersionSuffix)</DscModuleVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)' == 'x64'">
|
||||
<DefineConstants>Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\x64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion)</DefineConstants> <!-- THIS IS AN INNER LOOP OPTIMIZATION
|
||||
<DefineConstants>Version=$(Version);DscModuleVersion=$(DscModuleVersion);MonacoSRCHarvestPath=$(ProjectDir)..\..\x64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion)</DefineConstants> <!-- THIS IS AN INNER LOOP OPTIMIZATION
|
||||
The build pipeline builds the Settings and Launcher projects for Publication
|
||||
using a specific profile. If you're doing local installer builds, this will
|
||||
simulate the build pipeline doing that for you. -->
|
||||
@@ -17,7 +22,7 @@ call powershell.exe -NonInteractive -executionpolicy Unrestricted -File $(MSBuil
|
||||
</PreBuildEvent>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Platform)' != 'x64'">
|
||||
<DefineConstants>Version=$(Version);MonacoSRCHarvestPath=$(ProjectDir)..\..\ARM64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion)</DefineConstants>
|
||||
<DefineConstants>Version=$(Version);DscModuleVersion=$(DscModuleVersion);MonacoSRCHarvestPath=$(ProjectDir)..\..\ARM64\$(Configuration)\Assets\Monaco\monacoSRC;CmdPalVersion=$(CmdPalVersion)</DefineConstants>
|
||||
<PreBuildEvent>IF NOT DEFINED IsPipeline (
|
||||
call "$([MSBuild]::GetVsInstallRoot())\Common7\Tools\VsDevCmd.bat" -arch=arm64 -host_arch=amd64 -winsdk=10.0.19041.0 -vcvars_ver=$(VCToolsVersion)
|
||||
SET PTRoot=$(SolutionDir)\..
|
||||
|
||||
@@ -146,7 +146,8 @@ std::optional<fs::path> ObtainInstaller(bool& isUpToDate)
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const auto new_version_info = std::move(get_github_version_info_async()).get();
|
||||
const bool include_prerelease_updates = PTSettingsHelper::load_general_settings().GetNamedBoolean(L"include_prerelease_updates", false);
|
||||
const auto new_version_info = std::move(get_github_version_info_async(include_prerelease_updates)).get();
|
||||
|
||||
// Check for error BEFORE dereferencing — the old code crashed here
|
||||
// when GitHub API was unreachable (new_version_info held an error string).
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<Version>0.0.1</Version>
|
||||
<VersionChannel>private</VersionChannel>
|
||||
<!-- Update once when main moves to the next stable release train. -->
|
||||
<ReleaseTrainVersion>0.100</ReleaseTrainVersion>
|
||||
<!-- Y in YDDDB counts calendar years from this January 1. Reset it on the first release-train minor change of a new year. -->
|
||||
<ReleaseTrainEpoch>2026-01-01</ReleaseTrainEpoch>
|
||||
<SourceCommit></SourceCommit>
|
||||
<DevEnvironment>Local</DevEnvironment>
|
||||
|
||||
<!-- Forcing for every DLL on by default -->
|
||||
|
||||
@@ -180,6 +180,10 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getDisableAutomaticUpdateDownloadValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetDisablePreviewUpdatesValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getDisablePreviewUpdatesValue());
|
||||
}
|
||||
GpoRuleConfigured GPOWrapper::GetDisableShowWhatsNewAfterUpdatesValue()
|
||||
{
|
||||
return static_cast<GpoRuleConfigured>(powertoys_gpo::getDisableShowWhatsNewAfterUpdatesValue());
|
||||
|
||||
@@ -51,6 +51,7 @@ namespace winrt::PowerToys::GPOWrapper::implementation
|
||||
static GpoRuleConfigured GetConfiguredPeekEnabledValue();
|
||||
static GpoRuleConfigured GetDisableNewUpdateToastValue();
|
||||
static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue();
|
||||
static GpoRuleConfigured GetDisablePreviewUpdatesValue();
|
||||
static GpoRuleConfigured GetDisableShowWhatsNewAfterUpdatesValue();
|
||||
static GpoRuleConfigured GetAllowExperimentationValue();
|
||||
static GpoRuleConfigured GetRunPluginEnabledValue(winrt::hstring const& pluginID);
|
||||
|
||||
@@ -55,6 +55,7 @@ namespace PowerToys
|
||||
static GpoRuleConfigured GetConfiguredPeekEnabledValue();
|
||||
static GpoRuleConfigured GetDisableNewUpdateToastValue();
|
||||
static GpoRuleConfigured GetDisableAutomaticUpdateDownloadValue();
|
||||
static GpoRuleConfigured GetDisablePreviewUpdatesValue();
|
||||
static GpoRuleConfigured GetDisableShowWhatsNewAfterUpdatesValue();
|
||||
static GpoRuleConfigured GetAllowExperimentationValue();
|
||||
static GpoRuleConfigured GetRunPluginEnabledValue(String pluginID);
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace UnitTestsVersionHelper
|
||||
Assert::AreEqual(MAJOR_VERSION_0, sut.major);
|
||||
Assert::AreEqual(MINOR_VERSION_12, sut.minor);
|
||||
Assert::AreEqual(REVISION_VERSION_0, sut.revision);
|
||||
Assert::AreEqual(0ull, sut.build);
|
||||
}
|
||||
TEST_METHOD (integerConstructorShouldProperlyInitializationWithDifferentVersionNumbers)
|
||||
{
|
||||
@@ -40,6 +41,16 @@ namespace UnitTestsVersionHelper
|
||||
Assert::AreEqual(testcaseMajor, sut.major);
|
||||
Assert::AreEqual(testcaseMinor, sut.minor);
|
||||
Assert::AreEqual(testcaseRevision, sut.revision);
|
||||
Assert::AreEqual(0ull, sut.build);
|
||||
}
|
||||
TEST_METHOD (integerConstructorShouldProperlyInitializeBuildVersionNumber)
|
||||
{
|
||||
VersionHelper sut(0, 100, 2607, 8001);
|
||||
|
||||
Assert::AreEqual(0ull, sut.major);
|
||||
Assert::AreEqual(100ull, sut.minor);
|
||||
Assert::AreEqual(2607ull, sut.revision);
|
||||
Assert::AreEqual(8001ull, sut.build);
|
||||
}
|
||||
TEST_METHOD (stringConstructorShouldProperlyInitializationVersionNumbers)
|
||||
{
|
||||
@@ -48,6 +59,25 @@ namespace UnitTestsVersionHelper
|
||||
Assert::AreEqual(0ull, sut->major);
|
||||
Assert::AreEqual(12ull, sut->minor);
|
||||
Assert::AreEqual(3ull, sut->revision);
|
||||
Assert::AreEqual(0ull, sut->build);
|
||||
}
|
||||
TEST_METHOD (stringConstructorShouldProperlyInitializationFourPartVersionNumbers)
|
||||
{
|
||||
auto sut = VersionHelper::fromString("v0.100.2607.08001");
|
||||
Assert::IsTrue(sut.has_value());
|
||||
Assert::AreEqual(0ull, sut->major);
|
||||
Assert::AreEqual(100ull, sut->minor);
|
||||
Assert::AreEqual(2607ull, sut->revision);
|
||||
Assert::AreEqual(8001ull, sut->build);
|
||||
}
|
||||
TEST_METHOD (stringConstructorShouldIgnorePreviewSuffix)
|
||||
{
|
||||
auto sut = VersionHelper::fromString("v0.100.2607.08001-preview");
|
||||
Assert::IsTrue(sut.has_value());
|
||||
Assert::AreEqual(0ull, sut->major);
|
||||
Assert::AreEqual(100ull, sut->minor);
|
||||
Assert::AreEqual(2607ull, sut->revision);
|
||||
Assert::AreEqual(8001ull, sut->build);
|
||||
}
|
||||
TEST_METHOD (stringConstructorShouldProperlyInitializationWithDifferentVersionNumbers)
|
||||
{
|
||||
@@ -121,6 +151,12 @@ namespace UnitTestsVersionHelper
|
||||
|
||||
Assert::IsFalse(sut.has_value());
|
||||
}
|
||||
TEST_METHOD (tooManyVersionPartsNotAccepted)
|
||||
{
|
||||
auto sut = VersionHelper::fromString(L"v1.2.3.4.5");
|
||||
|
||||
Assert::IsFalse(sut.has_value());
|
||||
}
|
||||
TEST_METHOD (parsedWithoutLeadingV)
|
||||
{
|
||||
VersionHelper expected{ 12ull, 13ull, 111ull };
|
||||
@@ -129,6 +165,14 @@ namespace UnitTestsVersionHelper
|
||||
Assert::IsTrue(actual.has_value());
|
||||
Assert::AreEqual(*actual, expected);
|
||||
}
|
||||
TEST_METHOD (parsedFourPartVersionWithoutLeadingV)
|
||||
{
|
||||
VersionHelper expected{ 12ull, 13ull, 111ull, 5ull };
|
||||
auto actual = VersionHelper::fromString(L"12.13.111.5");
|
||||
|
||||
Assert::IsTrue(actual.has_value());
|
||||
Assert::AreEqual(*actual, expected);
|
||||
}
|
||||
TEST_METHOD (whenMajorVersionIsGreaterComparisonOperatorShouldReturnProperValue)
|
||||
{
|
||||
VersionHelper lhs(MAJOR_VERSION_0 + 1, MINOR_VERSION_12, REVISION_VERSION_0);
|
||||
@@ -179,5 +223,24 @@ namespace UnitTestsVersionHelper
|
||||
|
||||
Assert::IsFalse(lhs > rhs);
|
||||
}
|
||||
TEST_METHOD (whenMajorMinorAndRevisionAreEqualComparisonOperatorShouldCompareBuildValue)
|
||||
{
|
||||
VersionHelper lhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0, 1);
|
||||
VersionHelper rhs(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
|
||||
|
||||
Assert::IsTrue(lhs > rhs);
|
||||
}
|
||||
TEST_METHOD (zeroBuildVersionShouldFormatAsThreePartVersion)
|
||||
{
|
||||
VersionHelper sut(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0);
|
||||
|
||||
Assert::AreEqual(std::wstring(L"v0.12.0"), sut.toWstring());
|
||||
}
|
||||
TEST_METHOD (nonZeroBuildVersionShouldFormatAsFourPartVersion)
|
||||
{
|
||||
VersionHelper sut(MAJOR_VERSION_0, MINOR_VERSION_12, REVISION_VERSION_0, 1);
|
||||
|
||||
Assert::AreEqual(std::wstring(L"v0.12.0.1"), sut.toWstring());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,4 +9,14 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
{
|
||||
return hstring{ get_product_version() };
|
||||
}
|
||||
|
||||
hstring CommonManaged::GetProductVersionChannel()
|
||||
{
|
||||
return hstring{ get_product_version_channel() };
|
||||
}
|
||||
|
||||
hstring CommonManaged::GetProductVersionSourceCommit()
|
||||
{
|
||||
return hstring{ get_product_version_source_commit() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ namespace winrt::PowerToys::Interop::implementation
|
||||
CommonManaged() = default;
|
||||
|
||||
static hstring GetProductVersion();
|
||||
static hstring GetProductVersionChannel();
|
||||
static hstring GetProductVersionSourceCommit();
|
||||
};
|
||||
}
|
||||
namespace winrt::PowerToys::Interop::factory_implementation
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace PowerToys
|
||||
{
|
||||
[default_interface] static runtimeclass CommonManaged {
|
||||
static String GetProductVersion();
|
||||
static String GetProductVersionChannel();
|
||||
static String GetProductVersionSourceCommit();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -41,11 +41,11 @@ namespace updating
|
||||
try
|
||||
{
|
||||
auto packages = package_manager.FindPackagesForUser({}, MSIX_PACKAGE_NAME, MSIX_PACKAGE_PUBLISHER);
|
||||
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
|
||||
VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD);
|
||||
|
||||
for (auto package : packages)
|
||||
{
|
||||
VersionHelper msix_version(package.Id().Version().Major, package.Id().Version().Minor, package.Id().Version().Revision);
|
||||
VersionHelper msix_version(package.Id().Version().Major, package.Id().Version().Minor, package.Id().Version().Build, package.Id().Version().Revision);
|
||||
|
||||
if (msix_version < current_version)
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace
|
||||
{
|
||||
const wchar_t PERSISTENT_STATE_FILENAME[] = L"\\UpdateState.json";
|
||||
const wchar_t UPDATE_STATE_MUTEX[] = L"Local\\PowerToysRunnerUpdateStateMutex";
|
||||
const VersionHelper CURRENT_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
|
||||
const VersionHelper CURRENT_VERSION(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD);
|
||||
}
|
||||
|
||||
UpdateState deserialize(const json::JsonObject& json)
|
||||
@@ -22,6 +22,7 @@ UpdateState deserialize(const json::JsonObject& json)
|
||||
result.releasePageUrl = json.GetNamedString(L"releasePageUrl", L"");
|
||||
result.githubUpdateLastCheckedDate = timeutil::from_string(json.GetNamedString(L"githubUpdateLastCheckedDate", L"invalid").c_str());
|
||||
result.downloadedInstallerFilename = json.GetNamedString(L"downloadedInstallerFilename", L"");
|
||||
result.isPrerelease = json.GetNamedBoolean(L"isPrerelease", false);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -36,6 +37,7 @@ json::JsonObject serialize(const UpdateState& state)
|
||||
json.SetNamedValue(L"releasePageUrl", json::value(state.releasePageUrl));
|
||||
json.SetNamedValue(L"state", json::value(static_cast<double>(state.state)));
|
||||
json.SetNamedValue(L"downloadedInstallerFilename", json::value(state.downloadedInstallerFilename));
|
||||
json.SetNamedValue(L"isPrerelease", json::value(state.isPrerelease));
|
||||
|
||||
json.SetNamedValue(L"updateStateFileVersion", json::value(CURRENT_VERSION.toWstring()));
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ struct UpdateState
|
||||
std::wstring releasePageUrl;
|
||||
std::optional<std::time_t> githubUpdateLastCheckedDate;
|
||||
std::wstring downloadedInstallerFilename;
|
||||
bool isPrerelease = false;
|
||||
|
||||
// To prevent concurrent modification of the file, we enforce this interface, which locks the file while
|
||||
// the state_modifier is active.
|
||||
|
||||
@@ -16,7 +16,7 @@ using namespace registry::install_scope;
|
||||
namespace // Strings in this namespace should not be localized
|
||||
{
|
||||
const wchar_t LATEST_RELEASE_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases/latest";
|
||||
const wchar_t ALL_RELEASES_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases";
|
||||
const wchar_t ALL_RELEASES_ENDPOINT[] = L"https://api.github.com/repos/microsoft/PowerToys/releases?per_page=100";
|
||||
|
||||
const wchar_t LOCAL_BUILD_ERROR[] = L"Local build cannot be updated";
|
||||
const wchar_t NETWORK_ERROR[] = L"Network error";
|
||||
@@ -82,7 +82,7 @@ namespace updating
|
||||
// prevent the warning that may show up depend on the value of the constants (#defines)
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4702)
|
||||
wil::task<github_version_result> get_github_version_info_async(const bool prerelease)
|
||||
wil::task<github_version_result> get_github_version_info_async(const bool include_prerelease)
|
||||
{
|
||||
// If the current version starts with 0.0.*, it means we're on a local build from a farm and shouldn't check for updates.
|
||||
if constexpr (VERSION_MAJOR == 0 && VERSION_MINOR == 0)
|
||||
@@ -94,18 +94,17 @@ namespace updating
|
||||
{
|
||||
http::HttpClient client;
|
||||
json::JsonObject release_object;
|
||||
const VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION);
|
||||
const VersionHelper current_version(VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD);
|
||||
VersionHelper github_version = current_version;
|
||||
|
||||
if (prerelease)
|
||||
if (include_prerelease)
|
||||
{
|
||||
const auto body = co_await client.request(Uri{ ALL_RELEASES_ENDPOINT });
|
||||
for (const auto& json : json::JsonValue::Parse(body).GetArray())
|
||||
{
|
||||
auto potential_release_object = json.GetObjectW();
|
||||
const bool is_prerelease = potential_release_object.GetNamedBoolean(L"prerelease", false);
|
||||
auto extracted_version = extract_version_from_release_object(potential_release_object);
|
||||
if (!is_prerelease || !extracted_version || *extracted_version <= github_version)
|
||||
if (!extracted_version || *extracted_version <= github_version)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -134,7 +133,8 @@ namespace updating
|
||||
co_return new_version_download_info{ extract_release_page_url(release_object),
|
||||
std::move(github_version),
|
||||
std::move(installer_download_url),
|
||||
std::move(installer_filename) };
|
||||
std::move(installer_filename),
|
||||
release_object.GetNamedBoolean(L"prerelease", false) };
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
|
||||
@@ -19,14 +19,15 @@ namespace updating
|
||||
struct new_version_download_info
|
||||
{
|
||||
Uri release_page_uri = nullptr;
|
||||
VersionHelper version{ 0, 0, 0 };
|
||||
VersionHelper version{ 0, 0, 0, 0 };
|
||||
Uri installer_download_url = nullptr;
|
||||
std::wstring installer_filename;
|
||||
bool is_prerelease = false;
|
||||
};
|
||||
using github_version_info = std::variant<new_version_download_info, version_up_to_date>;
|
||||
using github_version_result = std::expected<github_version_info, std::wstring>;
|
||||
|
||||
wil::task<github_version_result> get_github_version_info_async(bool prerelease = false);
|
||||
wil::task<github_version_result> get_github_version_info_async(bool include_prerelease = false);
|
||||
wil::task<std::optional<std::filesystem::path>> download_new_version_async(new_version_download_info new_version);
|
||||
std::filesystem::path get_pending_updates_path();
|
||||
void cleanup_updates();
|
||||
|
||||
@@ -80,6 +80,7 @@ namespace powertoys_gpo
|
||||
const std::wstring POLICY_SUSPEND_NEW_UPDATE_TOAST = L"SuspendNewUpdateAvailableToast";
|
||||
const std::wstring POLICY_DISABLE_NEW_UPDATE_TOAST = L"DisableNewUpdateAvailableToast";
|
||||
const std::wstring POLICY_DISABLE_SHOW_WHATS_NEW_AFTER_UPDATES = L"DoNotShowWhatsNewAfterUpdates";
|
||||
const std::wstring POLICY_DISABLE_PREVIEW_UPDATES = L"PreviewUpdatesDisabled";
|
||||
|
||||
// The registry value names for other PowerToys policies.
|
||||
const std::wstring POLICY_ALLOW_EXPERIMENTATION = L"AllowExperimentation";
|
||||
@@ -548,6 +549,11 @@ namespace powertoys_gpo
|
||||
return getConfiguredValue(POLICY_DISABLE_SHOW_WHATS_NEW_AFTER_UPDATES);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getDisablePreviewUpdatesValue()
|
||||
{
|
||||
return getConfiguredValue(POLICY_DISABLE_PREVIEW_UPDATES);
|
||||
}
|
||||
|
||||
inline gpo_rule_configured_t getAllowExperimentationValue()
|
||||
{
|
||||
return getConfiguredValue(POLICY_ALLOW_EXPERIMENTATION);
|
||||
|
||||
@@ -182,7 +182,7 @@ namespace package
|
||||
if (packageFullName.contains(packageDisplayName))
|
||||
{
|
||||
// If checkVersion is true, verify if the package has the same version as PowerToys.
|
||||
if ((!checkVersion) || (packageVersion.Major == VERSION_MAJOR && packageVersion.Minor == VERSION_MINOR && packageVersion.Revision == VERSION_REVISION))
|
||||
if ((!checkVersion) || (packageVersion.Major == VERSION_MAJOR && packageVersion.Minor == VERSION_MINOR && packageVersion.Build == VERSION_REVISION && packageVersion.Revision == VERSION_BUILD))
|
||||
{
|
||||
return { package };
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
#include "../utils/string_utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
VersionHelper::VersionHelper(const size_t major, const size_t minor, const size_t revision) :
|
||||
VersionHelper::VersionHelper(const size_t major, const size_t minor, const size_t revision, const size_t build) :
|
||||
major{ major },
|
||||
minor{ minor },
|
||||
revision{ revision }
|
||||
revision{ revision },
|
||||
build{ build }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -40,17 +40,42 @@ std::optional<VersionHelper> fromString(std::basic_string_view<CharT> str)
|
||||
{
|
||||
str = left_trim<CharT>(trim<CharT>(str), Constants<CharT>::LOWER_V);
|
||||
str = left_trim<CharT>(trim<CharT>(str), Constants<CharT>::UPPER_V);
|
||||
std::basic_string<CharT> spacedStr{ str };
|
||||
replace_chars<CharT>(spacedStr, Constants<CharT>::DOT, Constants<CharT>::SPACE);
|
||||
|
||||
std::basic_istringstream<CharT> ss{ spacedStr };
|
||||
VersionHelper result{ 0, 0, 0 };
|
||||
ss >> result.major;
|
||||
ss >> result.minor;
|
||||
ss >> result.revision;
|
||||
if (!ss.fail() && ss.eof())
|
||||
if (const auto suffixPos = str.find(static_cast<CharT>('-')); suffixPos != std::basic_string_view<CharT>::npos)
|
||||
{
|
||||
return result;
|
||||
str = str.substr(0, suffixPos);
|
||||
}
|
||||
|
||||
size_t parts[4]{};
|
||||
size_t partCount = 0;
|
||||
size_t start = 0;
|
||||
while (start <= str.size() && partCount < std::size(parts))
|
||||
{
|
||||
const auto dot = str.find(Constants<CharT>::DOT[0], start);
|
||||
const auto end = dot == std::basic_string_view<CharT>::npos ? str.size() : dot;
|
||||
const auto part = str.substr(start, end - start);
|
||||
if (part.empty() || !std::all_of(part.begin(), part.end(), [](const CharT c) { return c >= static_cast<CharT>('0') && c <= static_cast<CharT>('9'); }))
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
parts[partCount++] = static_cast<size_t>(std::stoull(std::basic_string<CharT>{ part }));
|
||||
|
||||
if (dot == std::basic_string_view<CharT>::npos)
|
||||
{
|
||||
start = str.size() + 1;
|
||||
break;
|
||||
}
|
||||
start = dot + 1;
|
||||
}
|
||||
|
||||
if (partCount == 3 && start > str.size())
|
||||
{
|
||||
return VersionHelper{ parts[0], parts[1], parts[2] };
|
||||
}
|
||||
|
||||
if (partCount == 4 && start > str.size())
|
||||
{
|
||||
return VersionHelper{ parts[0], parts[1], parts[2], parts[3] };
|
||||
}
|
||||
}
|
||||
catch (...)
|
||||
@@ -77,6 +102,11 @@ std::wstring VersionHelper::toWstring() const
|
||||
result += std::to_wstring(minor);
|
||||
result += L'.';
|
||||
result += std::to_wstring(revision);
|
||||
if (build != 0)
|
||||
{
|
||||
result += L'.';
|
||||
result += std::to_wstring(build);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -88,5 +118,10 @@ std::string VersionHelper::toString() const
|
||||
result += std::to_string(minor);
|
||||
result += '.';
|
||||
result += std::to_string(revision);
|
||||
if (build != 0)
|
||||
{
|
||||
result += '.';
|
||||
result += std::to_string(build);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
struct VersionHelper
|
||||
{
|
||||
VersionHelper(const size_t major, const size_t minor, const size_t revision);
|
||||
VersionHelper(const size_t major, const size_t minor, const size_t revision, const size_t build = 0);
|
||||
|
||||
auto operator<=>(const VersionHelper&) const = default;
|
||||
|
||||
@@ -16,6 +16,7 @@ struct VersionHelper
|
||||
size_t major;
|
||||
size_t minor;
|
||||
size_t revision;
|
||||
size_t build;
|
||||
|
||||
std::wstring toWstring() const;
|
||||
std::string toString() const;
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
#include "Generated Files\version_gen.h"
|
||||
|
||||
#define FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, 0
|
||||
#define FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION, VERSION_BUILD
|
||||
#define FILE_VERSION_STRING \
|
||||
STRINGIZE(VERSION_MAJOR) \
|
||||
"." STRINGIZE(VERSION_MINOR) "." STRINGIZE(VERSION_REVISION) ".0"
|
||||
"." STRINGIZE(VERSION_MINOR) "." STRINGIZE(VERSION_REVISION) "." STRINGIZE(VERSION_BUILD)
|
||||
#define PRODUCT_VERSION FILE_VERSION
|
||||
#define PRODUCT_VERSION_STRING FILE_VERSION_STRING
|
||||
|
||||
@@ -29,18 +29,41 @@ const wchar_t* get_architecture_string(const version_architecture);
|
||||
|
||||
inline std::wstring get_product_version(bool includeV = true)
|
||||
{
|
||||
static std::wstring version = (includeV ? L"v" : L"") + std::to_wstring(VERSION_MAJOR) +
|
||||
L"." + std::to_wstring(VERSION_MINOR) +
|
||||
L"." + std::to_wstring(VERSION_REVISION);
|
||||
std::wstring version = includeV ? L"v" : L"";
|
||||
version += std::to_wstring(VERSION_MAJOR);
|
||||
version += L".";
|
||||
version += std::to_wstring(VERSION_MINOR);
|
||||
version += L".";
|
||||
version += std::to_wstring(VERSION_REVISION);
|
||||
if constexpr (VERSION_BUILD != 0)
|
||||
{
|
||||
version += L".";
|
||||
version += std::to_wstring(VERSION_BUILD);
|
||||
}
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
inline std::wstring get_std_product_version(bool includeV = true)
|
||||
{
|
||||
static std::wstring version = (includeV ? L"v" : L"") + std::to_wstring(VERSION_MAJOR) +
|
||||
L"." + std::to_wstring(VERSION_MINOR) +
|
||||
L"." + std::to_wstring(VERSION_REVISION) + L".0";
|
||||
std::wstring version = includeV ? L"v" : L"";
|
||||
version += std::to_wstring(VERSION_MAJOR);
|
||||
version += L".";
|
||||
version += std::to_wstring(VERSION_MINOR);
|
||||
version += L".";
|
||||
version += std::to_wstring(VERSION_REVISION);
|
||||
version += L".";
|
||||
version += std::to_wstring(VERSION_BUILD);
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
inline std::wstring get_product_version_channel()
|
||||
{
|
||||
return VERSION_CHANNEL;
|
||||
}
|
||||
|
||||
inline std::wstring get_product_version_source_commit()
|
||||
{
|
||||
return VERSION_SOURCE_COMMIT;
|
||||
}
|
||||
|
||||
@@ -8,11 +8,23 @@
|
||||
BeforeTargets="PrepareForBuild"
|
||||
Inputs="$(RepoRoot)src\Version.props"
|
||||
Outputs="$(MSBuildProjectDirectory)\Generated Files\version_gen.h">
|
||||
<PropertyGroup>
|
||||
<VersionBuild>0</VersionBuild>
|
||||
<VersionBuild Condition="$([System.Text.RegularExpressions.Regex]::IsMatch('$(Version)', '^\d+\.\d+\.\d+\.\d+$'))">$([System.String]::Copy('$(Version)').Split('.')[3])</VersionBuild>
|
||||
<VersionSourceCommit Condition="'$(SourceCommit)' != ''">$(SourceCommit)</VersionSourceCommit>
|
||||
<VersionSourceCommit Condition="'$(VersionSourceCommit)' == '' and '$(BUILD_SOURCEVERSION)' != ''">$(BUILD_SOURCEVERSION)</VersionSourceCommit>
|
||||
<VersionSourceCommit Condition="'$(VersionSourceCommit)' == '' and '$(GITHUB_SHA)' != ''">$(GITHUB_SHA)</VersionSourceCommit>
|
||||
<VersionSourceCommit Condition="'$(VersionSourceCommit)' == ''">unknown</VersionSourceCommit>
|
||||
<VersionChannel Condition="'$(VersionChannel)' == ''">private</VersionChannel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<HeaderLines Include="#pragma once" />
|
||||
<HeaderLines Include="#define VERSION_MAJOR $(Version.Split('.')[0])" />
|
||||
<HeaderLines Include="#define VERSION_MINOR $(Version.Split('.')[1])" />
|
||||
<HeaderLines Include="#define VERSION_REVISION $(Version.Split('.')[2])" />
|
||||
<HeaderLines Include="#define VERSION_BUILD $(VersionBuild)" />
|
||||
<HeaderLines Include="#define VERSION_CHANNEL L"$(VersionChannel)"" />
|
||||
<HeaderLines Include="#define VERSION_SOURCE_COMMIT L"$(VersionSourceCommit)"" />
|
||||
</ItemGroup>
|
||||
<WriteLinesToFile File="Generated Files\version_gen.h" Lines="@(HeaderLines)" Overwrite="true" Encoding="Unicode" WriteOnlyWhenDifferent="true" />
|
||||
</Target>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT License. -->
|
||||
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.20" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<policyDefinitions xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.21" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<policyNamespaces>
|
||||
<target prefix="powertoys" namespace="Microsoft.Policies.PowerToys" />
|
||||
</policyNamespaces>
|
||||
<resources minRequiredRevision="1.20"/><!-- Last changed with PowerToys v0.98.0 -->
|
||||
<resources minRequiredRevision="1.21"/><!-- Last changed with PowerToys v0.100.0 -->
|
||||
<supportedOn>
|
||||
<definitions>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_64_0" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0)"/>
|
||||
@@ -30,6 +30,7 @@
|
||||
<definition name="SUPPORTED_POWERTOYS_0_97_0" displayName="$(string.SUPPORTED_POWERTOYS_0_97_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_98_0" displayName="$(string.SUPPORTED_POWERTOYS_0_98_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_99_0" displayName="$(string.SUPPORTED_POWERTOYS_0_99_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_100_0" displayName="$(string.SUPPORTED_POWERTOYS_0_100_0)"/>
|
||||
<definition name="SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1" displayName="$(string.SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1)"/>
|
||||
</definitions>
|
||||
</supportedOn>
|
||||
@@ -561,6 +562,16 @@
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="DisablePreviewUpdates" class="Both" displayName="$(string.DisablePreviewUpdates)" explainText="$(string.DisablePreviewUpdatesDescription)" key="Software\Policies\PowerToys" valueName="PreviewUpdatesDisabled">
|
||||
<parentCategory ref="InstallerUpdates" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_100_0" />
|
||||
<enabledValue>
|
||||
<decimal value="1" />
|
||||
</enabledValue>
|
||||
<disabledValue>
|
||||
<decimal value="0" />
|
||||
</disabledValue>
|
||||
</policy>
|
||||
<policy name="SuspendNewUpdateToast" class="Both" displayName="$(string.SuspendNewUpdateToast)" explainText="$(string.SuspendNewUpdateToastDescription)" key="Software\Policies\PowerToys" valueName="SuspendNewUpdateAvailableToast">
|
||||
<parentCategory ref="InstallerUpdates" />
|
||||
<supportedOn ref="SUPPORTED_POWERTOYS_0_68_0" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Copyright (c) Microsoft Corporation.
|
||||
Licensed under the MIT License. -->
|
||||
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.20" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<policyDefinitionResources xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" revision="1.21" schemaVersion="1.0" xmlns="http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions">
|
||||
<displayName>PowerToys</displayName>
|
||||
<description>PowerToys</description>
|
||||
<resources>
|
||||
@@ -37,6 +37,7 @@
|
||||
<string id="SUPPORTED_POWERTOYS_0_97_0">PowerToys version 0.97.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_98_0">PowerToys version 0.98.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_99_0">PowerToys version 0.99.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_100_0">PowerToys version 0.100.0 or later</string>
|
||||
<string id="SUPPORTED_POWERTOYS_0_64_0_TO_0_87_1">From PowerToys version 0.64.0 until PowerToys version 0.87.1</string>
|
||||
|
||||
<string id="ConfigureAllUtilityGlobalEnabledStateDescription">This policy configures the enabled state for all PowerToys utilities.
|
||||
@@ -78,6 +79,11 @@ If enabled, per-user installation is not allowed.
|
||||
|
||||
If disabled or not configured, per-user installation is allowed.
|
||||
</string>
|
||||
<string id="DisablePreviewUpdatesDescription">This policy configures whether users can opt in to preview (prerelease) build updates.
|
||||
|
||||
If you enable this policy, preview build updates are turned off: the per-user setting to include preview/prerelease updates is ignored and cannot be changed. Only stable updates are offered.
|
||||
|
||||
If you disable or do not configure this policy, users can choose whether to receive preview build updates. Stable updates are unaffected either way.</string>
|
||||
<string id="DisableAutomaticUpdateDownloadDescription">This policy configures whether or not the automatic download and installation of available updates is disabled. (On metered connections updates are never downloaded.)
|
||||
|
||||
If enabled, automatic download and installation is disabled.
|
||||
@@ -289,6 +295,7 @@ If you don't configure this policy, the user will be able to control the setting
|
||||
<string id="ConfigureEnabledUtilityZoomIt">Zoom It: Configure enabled state</string>
|
||||
<string id="DisablePerUserInstallation">Disable per-user installation</string>
|
||||
<string id="DisableAutomaticUpdateDownload">Disable automatic downloads</string>
|
||||
<string id="DisablePreviewUpdates">Disable preview build updates</string>
|
||||
<string id="DoNotShowWhatsNewAfterUpdates">Do not show the release notes after updates</string>
|
||||
<string id="SuspendNewUpdateToast">Suspend Action Center notification for new updates</string>
|
||||
<string id="DisableNewUpdateToast">Disable Action Center notification for new updates</string>
|
||||
|
||||
@@ -153,6 +153,9 @@
|
||||
<data name="GITHUB_NEW_VERSION_AVAILABLE" xml:space="preserve">
|
||||
<value>An update to PowerToys is available.</value>
|
||||
</data>
|
||||
<data name="GITHUB_NEW_PREVIEW_VERSION_AVAILABLE" xml:space="preserve">
|
||||
<value>A PowerToys Preview update is available.</value>
|
||||
</data>
|
||||
<data name="GITHUB_NEW_VERSION_UPDATE_NOW" xml:space="preserve">
|
||||
<value>Update now</value>
|
||||
</data>
|
||||
|
||||
@@ -28,24 +28,54 @@ namespace
|
||||
// How many minor versions to suspend the toast notification (example: installed=0.60.0, suspend=2, next notification=0.63.*)
|
||||
// Attention: When changing this value please update the ADML file to.
|
||||
const int UPDATE_NOTIFICATION_TOAST_SUSPEND_MINOR_VERSION_COUNT = 2;
|
||||
|
||||
// The per-user "include prerelease updates" opt-in, additionally gated by the DisablePreviewUpdates
|
||||
// group policy: when that policy is Enabled, preview (prerelease) updates are forced off regardless
|
||||
// of the user's setting. Stable updates are unaffected.
|
||||
bool effective_include_prerelease_updates()
|
||||
{
|
||||
if (powertoys_gpo::getDisablePreviewUpdatesValue() == powertoys_gpo::gpo_rule_configured_enabled)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return get_general_settings().includePrereleaseUpdates;
|
||||
}
|
||||
}
|
||||
using namespace notifications;
|
||||
using namespace updating;
|
||||
|
||||
std::wstring AvailableVersionToWstring(const new_version_download_info& info)
|
||||
{
|
||||
auto result = info.version.toWstring();
|
||||
if (info.is_prerelease)
|
||||
{
|
||||
result += L"-preview";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring CurrentVersionToNextVersion(const new_version_download_info& info)
|
||||
{
|
||||
auto result = VersionHelper{ VERSION_MAJOR, VERSION_MINOR, VERSION_REVISION }.toWstring();
|
||||
result += L" \u2192 "; // Right arrow
|
||||
result += info.version.toWstring();
|
||||
result += AvailableVersionToWstring(info);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring UpdateAvailableMessage(const new_version_download_info& info)
|
||||
{
|
||||
return info.is_prerelease ?
|
||||
GET_RESOURCE_STRING(IDS_GITHUB_NEW_PREVIEW_VERSION_AVAILABLE) :
|
||||
GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE);
|
||||
}
|
||||
|
||||
void ShowNewVersionAvailable(const new_version_download_info& info)
|
||||
{
|
||||
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
|
||||
|
||||
toast_params toast_params{ UPDATING_PROCESS_TOAST_TAG, false };
|
||||
std::wstring contents = GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE);
|
||||
std::wstring contents = UpdateAvailableMessage(info);
|
||||
contents += L'\n';
|
||||
contents += CurrentVersionToNextVersion(info);
|
||||
|
||||
@@ -60,7 +90,7 @@ void ShowNewVersionAvailable(const new_version_download_info& info)
|
||||
L"powertoys://open_overview/");
|
||||
}
|
||||
|
||||
void ShowOpenSettingsForUpdate()
|
||||
void ShowOpenSettingsForUpdate(const new_version_download_info& info)
|
||||
{
|
||||
remove_toasts_by_tag(UPDATING_PROCESS_TOAST_TAG);
|
||||
|
||||
@@ -70,7 +100,10 @@ void ShowOpenSettingsForUpdate()
|
||||
link_button{ GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_MORE_INFO),
|
||||
L"powertoys://open_overview/" },
|
||||
};
|
||||
show_toast_with_activations(GET_RESOURCE_STRING(IDS_GITHUB_NEW_VERSION_AVAILABLE),
|
||||
auto contents = UpdateAvailableMessage(info);
|
||||
contents += L'\n';
|
||||
contents += AvailableVersionToWstring(info);
|
||||
show_toast_with_activations(std::move(contents),
|
||||
GET_RESOURCE_STRING(IDS_TOAST_TITLE),
|
||||
{},
|
||||
std::move(actions),
|
||||
@@ -130,12 +163,14 @@ void ProcessNewVersionInfo(const github_version_info& version_info,
|
||||
state.state = UpdateState::upToDate;
|
||||
state.releasePageUrl = {};
|
||||
state.downloadedInstallerFilename = {};
|
||||
state.isPrerelease = false;
|
||||
Logger::trace(L"Version is up to date");
|
||||
dispatch_run_on_main_ui_thread([](PVOID) { set_tray_icon_update_available(false); }, nullptr);
|
||||
return;
|
||||
}
|
||||
const auto new_version_info = std::get<new_version_download_info>(version_info);
|
||||
state.releasePageUrl = new_version_info.release_page_uri.ToString().c_str();
|
||||
state.isPrerelease = new_version_info.is_prerelease;
|
||||
Logger::trace(L"Discovered new version {}", new_version_info.version.toWstring());
|
||||
|
||||
const bool already_downloaded = state.state == UpdateState::readyToInstall && state.downloadedInstallerFilename == new_version_info.installer_filename;
|
||||
@@ -203,7 +238,7 @@ void ProcessNewVersionInfo(const github_version_info& version_info,
|
||||
dispatch_run_on_main_ui_thread([](PVOID) { set_tray_icon_update_available(true); }, nullptr);
|
||||
if (show_notifications)
|
||||
{
|
||||
ShowOpenSettingsForUpdate();
|
||||
ShowOpenSettingsForUpdate(new_version_info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -237,7 +272,7 @@ void PeriodicUpdateWorker()
|
||||
bool version_info_obtained = false;
|
||||
try
|
||||
{
|
||||
const auto new_version_info = std::move(get_github_version_info_async()).get();
|
||||
const auto new_version_info = std::move(get_github_version_info_async(effective_include_prerelease_updates())).get();
|
||||
if (new_version_info.has_value())
|
||||
{
|
||||
version_info_obtained = true;
|
||||
@@ -277,7 +312,7 @@ void CheckForUpdatesCallback()
|
||||
auto state = UpdateState::read();
|
||||
try
|
||||
{
|
||||
auto new_version_info = std::move(get_github_version_info_async()).get();
|
||||
auto new_version_info = std::move(get_github_version_info_async(effective_include_prerelease_updates())).get();
|
||||
if (!new_version_info)
|
||||
{
|
||||
// We couldn't get a new version from github for some reason, log error
|
||||
|
||||
@@ -71,6 +71,7 @@ static bool show_theme_adaptive_tray_icon = false;
|
||||
static bool run_as_elevated = false;
|
||||
static bool show_new_updates_toast_notification = true;
|
||||
static bool download_updates_automatically = true;
|
||||
static bool include_prerelease_updates = false;
|
||||
static bool show_whats_new_after_updates = true;
|
||||
static bool enable_experimentation = true;
|
||||
static bool enable_warnings_elevated_apps = true;
|
||||
@@ -105,6 +106,7 @@ json::JsonObject GeneralSettings::to_json()
|
||||
result.SetNamedValue(L"run_elevated", json::value(isRunElevated));
|
||||
result.SetNamedValue(L"show_new_updates_toast_notification", json::value(showNewUpdatesToastNotification));
|
||||
result.SetNamedValue(L"download_updates_automatically", json::value(downloadUpdatesAutomatically));
|
||||
result.SetNamedValue(L"include_prerelease_updates", json::value(includePrereleaseUpdates));
|
||||
result.SetNamedValue(L"show_whats_new_after_updates", json::value(showWhatsNewAfterUpdates));
|
||||
result.SetNamedValue(L"enable_experimentation", json::value(enableExperimentation));
|
||||
result.SetNamedValue(L"dashboard_sort_order", json::value(static_cast<int>(dashboardSortOrder)));
|
||||
@@ -133,6 +135,7 @@ json::JsonObject load_general_settings()
|
||||
run_as_elevated = loaded.GetNamedBoolean(L"run_elevated", false);
|
||||
show_new_updates_toast_notification = loaded.GetNamedBoolean(L"show_new_updates_toast_notification", true);
|
||||
download_updates_automatically = loaded.GetNamedBoolean(L"download_updates_automatically", true) && check_user_is_admin();
|
||||
include_prerelease_updates = loaded.GetNamedBoolean(L"include_prerelease_updates", false);
|
||||
show_whats_new_after_updates = loaded.GetNamedBoolean(L"show_whats_new_after_updates", true);
|
||||
enable_experimentation = loaded.GetNamedBoolean(L"enable_experimentation", true);
|
||||
enable_warnings_elevated_apps = loaded.GetNamedBoolean(L"enable_warnings_elevated_apps", true);
|
||||
@@ -172,6 +175,7 @@ GeneralSettings get_general_settings()
|
||||
.quickAccessShortcut = quick_access_shortcut,
|
||||
.showNewUpdatesToastNotification = show_new_updates_toast_notification,
|
||||
.downloadUpdatesAutomatically = download_updates_automatically && is_user_admin,
|
||||
.includePrereleaseUpdates = include_prerelease_updates,
|
||||
.showWhatsNewAfterUpdates = show_whats_new_after_updates,
|
||||
.enableExperimentation = enable_experimentation,
|
||||
.dashboardSortOrder = dashboard_sort_order,
|
||||
@@ -335,6 +339,7 @@ void apply_general_settings(const json::JsonObject& general_configs, bool save)
|
||||
show_new_updates_toast_notification = general_configs.GetNamedBoolean(L"show_new_updates_toast_notification", true);
|
||||
|
||||
download_updates_automatically = general_configs.GetNamedBoolean(L"download_updates_automatically", true);
|
||||
include_prerelease_updates = general_configs.GetNamedBoolean(L"include_prerelease_updates", false);
|
||||
show_whats_new_after_updates = general_configs.GetNamedBoolean(L"show_whats_new_after_updates", true);
|
||||
|
||||
enable_experimentation = general_configs.GetNamedBoolean(L"enable_experimentation", true);
|
||||
@@ -585,4 +590,3 @@ void start_enabled_powertoys()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ struct GeneralSettings
|
||||
PowerToysSettings::HotkeyObject quickAccessShortcut;
|
||||
bool showNewUpdatesToastNotification;
|
||||
bool downloadUpdatesAutomatically;
|
||||
bool includePrereleaseUpdates;
|
||||
bool showWhatsNewAfterUpdates;
|
||||
bool enableExperimentation;
|
||||
DashboardSortOrder dashboardSortOrder;
|
||||
|
||||
@@ -111,6 +111,7 @@ std::optional<std::wstring> dispatch_json_action_to_module(const json::JsonObjec
|
||||
}
|
||||
else if (action == L"check_for_updates")
|
||||
{
|
||||
apply_general_settings(value);
|
||||
bool expected_isUpdateCheckThreadRunning = false;
|
||||
if (isUpdateCheckThreadRunning.compare_exchange_strong(expected_isUpdateCheckThreadRunning, true))
|
||||
{
|
||||
|
||||
@@ -49,6 +49,7 @@ void Trace::SettingsChanged(const GeneralSettings& settings)
|
||||
TraceLoggingWideString(enabledModules.c_str(), "ModulesEnabled"),
|
||||
TraceLoggingBoolean(settings.isRunElevated, "AlwaysRunElevated"),
|
||||
TraceLoggingBoolean(settings.downloadUpdatesAutomatically, "DownloadUpdatesAutomatically"),
|
||||
TraceLoggingBoolean(settings.includePrereleaseUpdates, "IncludePrereleaseUpdates"),
|
||||
TraceLoggingBoolean(settings.enableExperimentation, "EnableExperimentation"),
|
||||
TraceLoggingWideString(settings.theme.c_str(), "Theme"),
|
||||
ProjectTelemetryPrivacyDataTag(ProjectTelemetryTag_ProductAndServicePerformance),
|
||||
|
||||
@@ -89,6 +89,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("download_updates_automatically")]
|
||||
public bool AutoDownloadUpdates { get; set; }
|
||||
|
||||
[JsonPropertyName("include_prerelease_updates")]
|
||||
public bool IncludePrereleaseUpdates { get; set; }
|
||||
|
||||
[JsonPropertyName("show_whats_new_after_updates")]
|
||||
public bool ShowWhatsNewAfterUpdates { get; set; }
|
||||
|
||||
@@ -112,6 +115,7 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
IsElevated = false;
|
||||
ShowNewUpdatesToastNotification = true;
|
||||
AutoDownloadUpdates = true;
|
||||
IncludePrereleaseUpdates = false;
|
||||
EnableExperimentation = true;
|
||||
DashboardSortOrder = DashboardSortOrder.Alphabetical;
|
||||
Theme = "system";
|
||||
|
||||
@@ -38,6 +38,9 @@ namespace Microsoft.PowerToys.Settings.UI.Library
|
||||
[JsonPropertyName("downloadedInstallerFilename")]
|
||||
public string DownloadedInstallerFilename { get; set; }
|
||||
|
||||
[JsonPropertyName("isPrerelease")]
|
||||
public bool IsPrerelease { get; set; }
|
||||
|
||||
// Non-localizable strings: Files
|
||||
public const string SettingsFilePath = "\\Microsoft\\PowerToys\\";
|
||||
public const string SettingsFile = "UpdateState.json";
|
||||
|
||||
71
src/settings-ui/Settings.UI.UnitTests/ScoobeReleaseTests.cs
Normal file
71
src/settings-ui/Settings.UI.UnitTests/ScoobeReleaseTests.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) Microsoft Corporation
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace Settings.UI.UnitTests
|
||||
{
|
||||
[TestClass]
|
||||
public class ScoobeReleaseTests
|
||||
{
|
||||
[TestMethod]
|
||||
public void CreateReleaseGroupsHidesPrereleasesByDefault()
|
||||
{
|
||||
var groups = ScoobeWindow.CreateReleaseGroups(CreateReleases(), false);
|
||||
|
||||
Assert.AreEqual(2, groups.Count);
|
||||
Assert.IsTrue(groups.SelectMany(group => group).All(release => !release.IsPrerelease));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void CreateReleaseGroupsSeparatesPrereleasesWhenEnabled()
|
||||
{
|
||||
var groups = ScoobeWindow.CreateReleaseGroups(CreateReleases(), true);
|
||||
|
||||
Assert.AreEqual(3, groups.Count);
|
||||
Assert.IsTrue(groups[0].All(release => release.IsPrerelease));
|
||||
Assert.IsTrue(groups.Skip(1).SelectMany(group => group).All(release => !release.IsPrerelease));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void UpdatingSettingsReadsPrereleaseState()
|
||||
{
|
||||
var settings = JsonSerializer.Deserialize<UpdatingSettings>("""{"state":2,"isPrerelease":true}""");
|
||||
|
||||
Assert.IsNotNull(settings);
|
||||
Assert.IsTrue(settings.IsPrerelease);
|
||||
}
|
||||
|
||||
private static IList<PowerToysReleaseInfo> CreateReleases()
|
||||
{
|
||||
return
|
||||
[
|
||||
new PowerToysReleaseInfo
|
||||
{
|
||||
TagName = "v0.100.2607.27001-preview",
|
||||
IsPrerelease = true,
|
||||
PublishedDate = new DateTimeOffset(2026, 7, 27, 0, 0, 0, TimeSpan.Zero),
|
||||
},
|
||||
new PowerToysReleaseInfo
|
||||
{
|
||||
TagName = "v0.100.2",
|
||||
PublishedDate = new DateTimeOffset(2026, 6, 26, 0, 0, 0, TimeSpan.Zero),
|
||||
},
|
||||
new PowerToysReleaseInfo
|
||||
{
|
||||
TagName = "v0.99.1",
|
||||
PublishedDate = new DateTimeOffset(2026, 4, 15, 0, 0, 0, TimeSpan.Zero),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,6 +84,7 @@ namespace ViewModelTests
|
||||
|
||||
// Verify that the old settings persisted
|
||||
Assert.AreEqual(originalGeneralSettings.AutoDownloadUpdates, viewModel.AutoDownloadUpdates);
|
||||
Assert.AreEqual(originalGeneralSettings.IncludePrereleaseUpdates, viewModel.IncludePrereleaseUpdates);
|
||||
Assert.AreEqual(originalGeneralSettings.PowertoysVersion, viewModel.PowerToysVersion);
|
||||
Assert.AreEqual(originalGeneralSettings.RunElevated, viewModel.RunElevated);
|
||||
Assert.AreEqual(originalGeneralSettings.Startup, viewModel.Startup);
|
||||
@@ -93,6 +94,67 @@ namespace ViewModelTests
|
||||
BackCompatTestProperties.VerifyGeneralSettingsIOProviderWasRead(fileMock, expectedCallCount);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IncludePrereleaseUpdatesShouldSendUpdatedSettingWhenSuccessful()
|
||||
{
|
||||
bool sawExpectedIpcPayload = false;
|
||||
bool sawExpectedUpdateCheckPayload = false;
|
||||
Func<string, int> sendMockIPCConfigMSG = msg =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
OutGoingGeneralSettings snd = JsonSerializer.Deserialize<OutGoingGeneralSettings>(msg);
|
||||
if (snd?.GeneralSettings is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Assert.IsTrue(snd.GeneralSettings.IncludePrereleaseUpdates);
|
||||
sawExpectedIpcPayload = true;
|
||||
return 0;
|
||||
};
|
||||
|
||||
Func<string, int> sendRestartAdminIPCMessage = msg => { return 0; };
|
||||
Func<string, int> sendCheckForUpdatesIPCMessage = msg =>
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(msg))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
GeneralSettingsCustomAction action = JsonSerializer.Deserialize<GeneralSettingsCustomAction>(msg);
|
||||
if (action?.GeneralSettingsAction?.GeneralSettings is null)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
Assert.IsTrue(action.GeneralSettingsAction.GeneralSettings.IncludePrereleaseUpdates);
|
||||
Assert.AreEqual("check_for_updates", action.GeneralSettingsAction.GeneralSettings.CustomActionName);
|
||||
sawExpectedUpdateCheckPayload = true;
|
||||
return 0;
|
||||
};
|
||||
GeneralViewModel viewModel = new TestGeneralViewModel(
|
||||
settingsRepository: SettingsRepository<GeneralSettings>.GetInstance(mockGeneralSettingsUtils.Object),
|
||||
"GeneralSettings_RunningAsAdminText",
|
||||
"GeneralSettings_RunningAsUserText",
|
||||
false,
|
||||
false,
|
||||
sendMockIPCConfigMSG,
|
||||
sendRestartAdminIPCMessage,
|
||||
sendCheckForUpdatesIPCMessage,
|
||||
GeneralSettingsFileName);
|
||||
|
||||
Assert.IsFalse(viewModel.IncludePrereleaseUpdates);
|
||||
|
||||
viewModel.IncludePrereleaseUpdates = true;
|
||||
|
||||
Assert.IsTrue(sawExpectedIpcPayload);
|
||||
Assert.IsTrue(sawExpectedUpdateCheckPayload);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void IsElevatedShouldUpdateRunasAdminStatusAttrsWhenSuccessful()
|
||||
{
|
||||
|
||||
@@ -25,5 +25,8 @@ namespace Microsoft.PowerToys.Settings.UI.Helpers
|
||||
|
||||
[JsonPropertyName("body")]
|
||||
public string ReleaseNotes { get; set; }
|
||||
|
||||
[JsonPropertyName("prerelease")]
|
||||
public bool IsPrerelease { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,6 +108,10 @@
|
||||
<ProjectCapability Include="Msix" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="Settings.UI.UnitTests" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- HACK: Common.UI is referenced, even if it is not used, to force dll versions to be the same as in other projects that use it. It's still unclear why this is the case, but this is need for flattening the install directory. -->
|
||||
<ProjectReference Include="..\..\common\Common.Search\Common.Search.csproj" />
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
|
||||
@@ -30,6 +31,8 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
/// </summary>
|
||||
public string DateText { get; }
|
||||
|
||||
public bool IsPreview { get; }
|
||||
|
||||
public ScoobeReleaseGroupViewModel(IList<PowerToysReleaseInfo> releases)
|
||||
{
|
||||
Releases = releases ?? throw new ArgumentNullException(nameof(releases));
|
||||
@@ -37,8 +40,11 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
if (releases.Count > 0)
|
||||
{
|
||||
var latestRelease = releases[0];
|
||||
IsPreview = releases.All(release => release.IsPrerelease);
|
||||
VersionText = GetVersionFromRelease(latestRelease);
|
||||
DateText = latestRelease.PublishedDate.ToString("MMMM yyyy", CultureInfo.CurrentCulture);
|
||||
DateText = IsPreview ?
|
||||
ResourceLoaderInstance.ResourceLoader.GetString("ScoobeReleaseGroup_Preview") :
|
||||
latestRelease.PublishedDate.ToString("MMMM yyyy", CultureInfo.CurrentCulture);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -118,6 +118,12 @@ namespace Microsoft.PowerToys.Settings.UI.OOBE.Views
|
||||
|
||||
isFirst = false;
|
||||
|
||||
if (release.IsPrerelease)
|
||||
{
|
||||
releaseNotesHtmlBuilder.AppendLine(CultureInfo.InvariantCulture, $"**{ResourceLoaderInstance.ResourceLoader.GetString("ScoobeReleaseNotes_PreviewBadge")}**");
|
||||
releaseNotesHtmlBuilder.AppendLine();
|
||||
}
|
||||
|
||||
var releaseUrl = string.Format(CultureInfo.InvariantCulture, GitHubReleaseLinkTemplate, release.TagName);
|
||||
releaseNotesHtmlBuilder.AppendLine(CultureInfo.InvariantCulture, $"# {release.Name}");
|
||||
string formattedDate = release.PublishedDate.ToString($"{CultureInfo.CurrentCulture.DateTimeFormat.MonthDayPattern}, yyyy", CultureInfo.CurrentCulture);
|
||||
|
||||
@@ -11,6 +11,7 @@ using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.OOBE.Views;
|
||||
using Microsoft.PowerToys.Settings.UI.SerializationContext;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -110,7 +111,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
try
|
||||
{
|
||||
var releases = await FetchReleasesFromGitHubAsync();
|
||||
ReleaseGroups = GroupReleasesByMajorMinor(releases);
|
||||
ReleaseGroups = CreateReleaseGroups(releases, ShouldShowPrereleases());
|
||||
PopulateNavigationItems();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -137,7 +138,7 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
using var httpClient = new HttpClient(proxyClientHandler);
|
||||
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", "PowerToys");
|
||||
|
||||
string json = await httpClient.GetStringAsync("https://api.github.com/repos/microsoft/PowerToys/releases?per_page=20");
|
||||
string json = await httpClient.GetStringAsync("https://api.github.com/repos/microsoft/PowerToys/releases?per_page=100");
|
||||
var allReleases = JsonSerializer.Deserialize<IList<PowerToysReleaseInfo>>(json, SourceGenerationContextContext.Default.IListPowerToysReleaseInfo);
|
||||
|
||||
if (allReleases is null || allReleases.Count == 0)
|
||||
@@ -150,12 +151,45 @@ namespace Microsoft.PowerToys.Settings.UI
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static IList<IList<PowerToysReleaseInfo>> GroupReleasesByMajorMinor(IList<PowerToysReleaseInfo> releases)
|
||||
internal static IList<IList<PowerToysReleaseInfo>> CreateReleaseGroups(IList<PowerToysReleaseInfo> releases, bool showPrereleases)
|
||||
{
|
||||
return releases
|
||||
var groups = new List<IList<PowerToysReleaseInfo>>();
|
||||
if (showPrereleases)
|
||||
{
|
||||
var previewReleases = releases
|
||||
.Where(r => r.IsPrerelease)
|
||||
.OrderByDescending(r => r.PublishedDate)
|
||||
.Take(10)
|
||||
.ToList();
|
||||
if (previewReleases.Count > 0)
|
||||
{
|
||||
groups.Add(previewReleases);
|
||||
}
|
||||
}
|
||||
|
||||
var stableGroups = releases
|
||||
.Where(r => !r.IsPrerelease)
|
||||
.OrderByDescending(r => r.PublishedDate)
|
||||
.Take(20)
|
||||
.GroupBy(GetMajorMinorVersion)
|
||||
.Select(g => g.OrderByDescending(r => r.PublishedDate).ToList() as IList<PowerToysReleaseInfo>)
|
||||
.ToList();
|
||||
foreach (var stableGroup in stableGroups)
|
||||
{
|
||||
groups.Add(stableGroup);
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
private static bool ShouldShowPrereleases()
|
||||
{
|
||||
var generalSettings = SettingsRepository<GeneralSettings>.GetInstance(SettingsUtils.Default).SettingsConfig;
|
||||
bool isPreviewBuild = string.Equals(
|
||||
global::PowerToys.Interop.CommonManaged.GetProductVersionChannel(),
|
||||
"preview",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
return generalSettings.IncludePrereleaseUpdates || isPreviewBuild;
|
||||
}
|
||||
|
||||
private static string GetMajorMinorVersion(PowerToysReleaseInfo release)
|
||||
|
||||
@@ -22,7 +22,22 @@
|
||||
<controls:SettingsPageControl.ModuleContent>
|
||||
<StackPanel ChildrenTransitions="{StaticResource SettingsCardsAnimations}" Orientation="Vertical">
|
||||
<controls:SettingsGroup x:Uid="General_VersionAndUpdate" Margin="0,-32,0,0">
|
||||
<tkcontrols:SettingsExpander Header="{x:Bind ViewModel.PowerToysVersion, Mode=OneWay}" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<tkcontrols:SettingsExpander HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<tkcontrols:SettingsExpander.Header>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.PowerToysVersion, Mode=OneWay}" />
|
||||
<Border
|
||||
Padding="6,2"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="4"
|
||||
Visibility="{x:Bind ViewModel.IsCurrentVersionPreview, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="General_PreviewBadge"
|
||||
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
</Border>
|
||||
</StackPanel>
|
||||
</tkcontrols:SettingsExpander.Header>
|
||||
<tkcontrols:SettingsExpander.Description>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<TextBlock Style="{StaticResource SecondaryTextStyle}">
|
||||
@@ -77,6 +92,9 @@
|
||||
Visibility="{x:Bind ViewModel.IsAdmin, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<ptcontrols:CheckBoxWithDescriptionControl x:Uid="GeneralPage_AutoDownloadAndInstallUpdates" IsChecked="{Binding Mode=TwoWay, Path=AutoDownloadUpdates}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsIncludePrereleaseUpdatesCardEnabled}">
|
||||
<ptcontrols:CheckBoxWithDescriptionControl x:Uid="GeneralPage_IncludePrereleaseUpdates" IsChecked="{Binding Mode=TwoWay, Path=IncludePrereleaseUpdates}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
<tkcontrols:SettingsCard ContentAlignment="Left" IsEnabled="{x:Bind Mode=OneWay, Path=ViewModel.IsShowNewUpdatesToastNotificationCardEnabled}">
|
||||
<ptcontrols:CheckBoxWithDescriptionControl x:Uid="GeneralPage_ShowNewUpdatesToast" IsChecked="{Binding Mode=TwoWay, Path=ShowNewUpdatesToastNotification}" />
|
||||
</tkcontrols:SettingsCard>
|
||||
@@ -104,7 +122,7 @@
|
||||
|
||||
<!-- New version available -->
|
||||
<InfoBar
|
||||
x:Uid="General_NewVersionAvailable"
|
||||
Title="{x:Bind ViewModel.NewVersionAvailableTitle, Mode=OneWay}"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.PowerToysUpdatingState, Mode=OneWay, Converter={StaticResource UpdateStateToBoolConverter}, ConverterParameter=ReadyToDownload}"
|
||||
IsTabStop="{x:Bind ViewModel.PowerToysUpdatingState, Mode=OneWay, Converter={StaticResource UpdateStateToBoolConverter}, ConverterParameter=ReadyToDownload}"
|
||||
@@ -113,6 +131,17 @@
|
||||
|
||||
<InfoBar.Content>
|
||||
<StackPanel Spacing="16">
|
||||
<Border
|
||||
Padding="6,2"
|
||||
HorizontalAlignment="Left"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="4"
|
||||
Visibility="{x:Bind ViewModel.IsPrereleaseUpdate, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="General_PreviewBadge"
|
||||
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
</Border>
|
||||
<Button
|
||||
x:Uid="General_DownloadAndInstall"
|
||||
Margin="0,0,0,16"
|
||||
@@ -145,19 +174,32 @@
|
||||
|
||||
<!-- Ready to install -->
|
||||
<InfoBar
|
||||
x:Uid="General_NewVersionReadyToInstall"
|
||||
Title="{x:Bind ViewModel.NewVersionReadyToInstallTitle, Mode=OneWay}"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.PowerToysUpdatingState, Mode=OneWay, Converter={StaticResource UpdateStateToBoolConverter}, ConverterParameter=ReadyToInstall}"
|
||||
IsTabStop="{x:Bind ViewModel.PowerToysUpdatingState, Mode=OneWay, Converter={StaticResource UpdateStateToBoolConverter}, ConverterParameter=ReadyToInstall}"
|
||||
Message="{x:Bind ViewModel.PowerToysNewAvailableVersion, Mode=OneWay}"
|
||||
Severity="Warning">
|
||||
<InfoBar.Content>
|
||||
<Button
|
||||
x:Uid="General_InstallNow"
|
||||
Margin="0,0,0,16"
|
||||
Command="{Binding UpdateNowButtonEventHandler}"
|
||||
IsEnabled="{Binding IsDownloadAllowed}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
<StackPanel Spacing="16">
|
||||
<Border
|
||||
Padding="6,2"
|
||||
HorizontalAlignment="Left"
|
||||
Background="{ThemeResource AccentFillColorDefaultBrush}"
|
||||
CornerRadius="4"
|
||||
Visibility="{x:Bind ViewModel.IsPrereleaseUpdate, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<TextBlock
|
||||
x:Uid="General_PreviewBadge"
|
||||
Foreground="{ThemeResource TextOnAccentFillColorPrimaryBrush}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}" />
|
||||
</Border>
|
||||
<Button
|
||||
x:Uid="General_InstallNow"
|
||||
Margin="0,0,0,16"
|
||||
Command="{Binding UpdateNowButtonEventHandler}"
|
||||
IsEnabled="{Binding IsDownloadAllowed}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</StackPanel>
|
||||
</InfoBar.Content>
|
||||
<InfoBar.ActionButton>
|
||||
<HyperlinkButton
|
||||
|
||||
@@ -1425,6 +1425,12 @@ opera.exe</value>
|
||||
<data name="GeneralPage_AutoDownloadAndInstallUpdates.Description" xml:space="preserve">
|
||||
<value>Except on metered connections</value>
|
||||
</data>
|
||||
<data name="GeneralPage_IncludePrereleaseUpdates.Header" xml:space="preserve">
|
||||
<value>Include prerelease updates</value>
|
||||
</data>
|
||||
<data name="GeneralPage_IncludePrereleaseUpdates.Description" xml:space="preserve">
|
||||
<value>Allow update checks to include preview builds. You may stay ahead of stable until a later stable release is available.</value>
|
||||
</data>
|
||||
<data name="GeneralSettings_AlwaysRunAsAdminText.Header" xml:space="preserve">
|
||||
<value>Always run as administrator</value>
|
||||
</data>
|
||||
@@ -2418,6 +2424,27 @@ From there, simply click on one of the supported files in the File Explorer and
|
||||
<data name="General_NewVersionAvailable.Title" xml:space="preserve">
|
||||
<value>An update is available:</value>
|
||||
</data>
|
||||
<data name="General_UpdateAvailableTitle" xml:space="preserve">
|
||||
<value>An update is available:</value>
|
||||
</data>
|
||||
<data name="General_PreviewUpdateAvailableTitle" xml:space="preserve">
|
||||
<value>Preview update available:</value>
|
||||
</data>
|
||||
<data name="General_UpdateReadyToInstallTitle" xml:space="preserve">
|
||||
<value>An update is ready to install:</value>
|
||||
</data>
|
||||
<data name="General_PreviewUpdateReadyToInstallTitle" xml:space="preserve">
|
||||
<value>A Preview update is ready to install:</value>
|
||||
</data>
|
||||
<data name="General_PreviewBadge.Text" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
</data>
|
||||
<data name="ScoobeReleaseGroup_Preview" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
</data>
|
||||
<data name="ScoobeReleaseNotes_PreviewBadge" xml:space="preserve">
|
||||
<value>Preview release</value>
|
||||
</data>
|
||||
<data name="General_Downloading.Text" xml:space="preserve">
|
||||
<value>Downloading...</value>
|
||||
</data>
|
||||
|
||||
@@ -167,6 +167,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_showThemeAdaptiveSysTrayIcon = GeneralSettingsConfig.ShowThemeAdaptiveTrayIcon;
|
||||
_showNewUpdatesToastNotification = GeneralSettingsConfig.ShowNewUpdatesToastNotification;
|
||||
_autoDownloadUpdates = GeneralSettingsConfig.AutoDownloadUpdates;
|
||||
_includePrereleaseUpdates = GeneralSettingsConfig.IncludePrereleaseUpdates;
|
||||
_showWhatsNewAfterUpdates = GeneralSettingsConfig.ShowWhatsNewAfterUpdates;
|
||||
_enableExperimentation = GeneralSettingsConfig.EnableExperimentation;
|
||||
|
||||
@@ -188,10 +189,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
_updatingState = UpdatingSettingsConfig.State;
|
||||
_newAvailableVersion = UpdatingSettingsConfig.NewVersion;
|
||||
_newAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink;
|
||||
_isPrereleaseUpdate = UpdatingSettingsConfig.IsPrerelease;
|
||||
_updateCheckedDate = FriendlyDateHelper.Format(UpdatingSettingsConfig.LastCheckedDateTime);
|
||||
|
||||
_newUpdatesToastIsGpoDisabled = GPOWrapper.GetDisableNewUpdateToastValue() == GpoRuleConfigured.Enabled;
|
||||
_autoDownloadUpdatesIsGpoDisabled = GPOWrapper.GetDisableAutomaticUpdateDownloadValue() == GpoRuleConfigured.Enabled;
|
||||
_includePrereleaseUpdatesIsGpoDisabled = GPOWrapper.GetDisablePreviewUpdatesValue() == GpoRuleConfigured.Enabled;
|
||||
_experimentationIsGpoDisallowed = GPOWrapper.GetAllowExperimentationValue() == GpoRuleConfigured.Disabled;
|
||||
_showWhatsNewAfterUpdatesIsGpoDisabled = GPOWrapper.GetDisableShowWhatsNewAfterUpdatesValue() == GpoRuleConfigured.Enabled;
|
||||
_enableDataDiagnosticsIsGpoDisallowed = GPOWrapper.GetAllowDataDiagnosticsValue() == GpoRuleConfigured.Disabled;
|
||||
@@ -269,6 +272,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
private bool _newUpdatesToastIsGpoDisabled;
|
||||
private bool _autoDownloadUpdates;
|
||||
private bool _autoDownloadUpdatesIsGpoDisabled;
|
||||
private bool _includePrereleaseUpdatesIsGpoDisabled;
|
||||
private bool _includePrereleaseUpdates;
|
||||
private bool _showWhatsNewAfterUpdates;
|
||||
private bool _showWhatsNewAfterUpdatesIsGpoDisabled;
|
||||
private bool _enableExperimentation;
|
||||
@@ -282,6 +287,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
private UpdatingSettings.UpdatingState _updatingState = UpdatingSettings.UpdatingState.UpToDate;
|
||||
private string _newAvailableVersion = string.Empty;
|
||||
private string _newAvailableVersionLink = string.Empty;
|
||||
private bool _isPrereleaseUpdate;
|
||||
private string _updateCheckedDate = string.Empty;
|
||||
|
||||
private bool _isNewVersionDownloading;
|
||||
@@ -318,7 +324,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
string installScope = GetCurrentInstallScope() == InstallScope.PerMachine ? "per machine (system)" : "per user";
|
||||
|
||||
var info = $"OS Version: {GetOSVersion()} \n.NET Version: {GetDotNetVersion()}\n{isElevatedString}\nInstall scope: {installScope}\nOperating System Language: {CultureInfo.InstalledUICulture.DisplayName}\nSystem locale: {CultureInfo.InstalledUICulture.Name}";
|
||||
var info = $"OS Version: {GetOSVersion()} \n.NET Version: {GetDotNetVersion()}\n{isElevatedString}\nInstall scope: {installScope}\nVersion channel: {GetPowerToysVersionChannel()}\nSource commit: {GetPowerToysSourceCommit()}\nOperating System Language: {CultureInfo.InstalledUICulture.DisplayName}\nSystem locale: {CultureInfo.InstalledUICulture.Name}";
|
||||
|
||||
var gitHubURL = "https://github.com/microsoft/PowerToys/issues/new?template=bug_report.yml&labels=Issue-Bug%2CTriage-Needed" +
|
||||
"&version=" + version + "&additionalInfo=" + System.Web.HttpUtility.UrlEncode(info);
|
||||
@@ -331,6 +337,16 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
return Helper.GetProductVersion().TrimStart('v');
|
||||
}
|
||||
|
||||
private string GetPowerToysVersionChannel()
|
||||
{
|
||||
return global::PowerToys.Interop.CommonManaged.GetProductVersionChannel();
|
||||
}
|
||||
|
||||
private string GetPowerToysSourceCommit()
|
||||
{
|
||||
return global::PowerToys.Interop.CommonManaged.GetProductVersionSourceCommit();
|
||||
}
|
||||
|
||||
private string GetOSVersion()
|
||||
{
|
||||
return Environment.OSVersion.VersionString;
|
||||
@@ -582,6 +598,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
{
|
||||
return _newUpdatesToastIsGpoDisabled ||
|
||||
(_isAdmin && _autoDownloadUpdatesIsGpoDisabled) ||
|
||||
_includePrereleaseUpdatesIsGpoDisabled ||
|
||||
_showWhatsNewAfterUpdatesIsGpoDisabled;
|
||||
}
|
||||
}
|
||||
@@ -632,6 +649,27 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
get => !_isDevBuild && !_autoDownloadUpdatesIsGpoDisabled;
|
||||
}
|
||||
|
||||
public bool IncludePrereleaseUpdates
|
||||
{
|
||||
get => _includePrereleaseUpdates && !_includePrereleaseUpdatesIsGpoDisabled;
|
||||
|
||||
set
|
||||
{
|
||||
if (_includePrereleaseUpdates != value)
|
||||
{
|
||||
_includePrereleaseUpdates = value;
|
||||
GeneralSettingsConfig.IncludePrereleaseUpdates = value;
|
||||
NotifyPropertyChanged();
|
||||
CheckForUpdatesClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsIncludePrereleaseUpdatesCardEnabled
|
||||
{
|
||||
get => !_isDevBuild && !_includePrereleaseUpdatesIsGpoDisabled;
|
||||
}
|
||||
|
||||
public bool ShowWhatsNewAfterUpdates
|
||||
{
|
||||
get
|
||||
@@ -798,6 +836,12 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsCurrentVersionPreview => string.Equals(GetPowerToysVersionChannel(), "preview", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public string NewVersionAvailableTitle => GetResourceString(IsPrereleaseUpdate ? "General_PreviewUpdateAvailableTitle" : "General_UpdateAvailableTitle");
|
||||
|
||||
public string NewVersionReadyToInstallTitle => GetResourceString(IsPrereleaseUpdate ? "General_PreviewUpdateReadyToInstallTitle" : "General_UpdateReadyToInstallTitle");
|
||||
|
||||
public string UpdateCheckedDate
|
||||
{
|
||||
get
|
||||
@@ -1016,6 +1060,25 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPrereleaseUpdate
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isPrereleaseUpdate;
|
||||
}
|
||||
|
||||
private set
|
||||
{
|
||||
if (value != _isPrereleaseUpdate)
|
||||
{
|
||||
_isPrereleaseUpdate = value;
|
||||
NotifyPropertyChanged();
|
||||
NotifyPropertyChanged(nameof(NewVersionAvailableTitle));
|
||||
NotifyPropertyChanged(nameof(NewVersionReadyToInstallTitle));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsNewVersionDownloading
|
||||
{
|
||||
get
|
||||
@@ -1391,6 +1454,7 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
PowerToysUpdatingState = UpdatingSettingsConfig.State;
|
||||
PowerToysNewAvailableVersion = UpdatingSettingsConfig.NewVersion;
|
||||
PowerToysNewAvailableVersionLink = UpdatingSettingsConfig.ReleasePageLink;
|
||||
IsPrereleaseUpdate = UpdatingSettingsConfig.IsPrerelease;
|
||||
UpdateCheckedDate = FriendlyDateHelper.Format(UpdatingSettingsConfig.LastCheckedDateTime);
|
||||
|
||||
_isNoNetwork = PowerToysUpdatingState == UpdatingSettings.UpdatingState.NetworkError;
|
||||
|
||||
@@ -281,8 +281,8 @@ try {
|
||||
$versionPropsPath = Join-Path $repoRoot "src\Version.props"
|
||||
[xml]$versionProps = Get-Content $versionPropsPath
|
||||
$ptVersion = $versionProps.Project.PropertyGroup.Version
|
||||
# Directory.Build.props appends .0 to the version for .csproj files
|
||||
$ptVersionFull = "$ptVersion.0"
|
||||
# Package versions need four components. Preserve explicit preview build components.
|
||||
$ptVersionFull = if (($ptVersion.ToCharArray() | Where-Object { $_ -eq '.' }).Count -eq 2) { "$ptVersion.0" } else { $ptVersion }
|
||||
|
||||
# 2. Build the Generator
|
||||
$generatorProj = Join-Path $repoRoot "src\dsc\PowerToys.Settings.DSC.Schema.Generator\PowerToys.Settings.DSC.Schema.Generator.csproj"
|
||||
|
||||
@@ -5,37 +5,81 @@ Param(
|
||||
|
||||
[Parameter(Mandatory=$True,Position=2)]
|
||||
[AllowEmptyString()]
|
||||
[string]$DevEnvironment = "Local"
|
||||
[string]$DevEnvironment = "Local",
|
||||
|
||||
[ValidateSet("stable", "preview", "private")]
|
||||
[string]$Channel = "stable",
|
||||
|
||||
[string]$SourceCommit = $env:BUILD_SOURCEVERSION,
|
||||
|
||||
[string]$BuildNumber = $env:BUILD_BUILDNUMBER
|
||||
)
|
||||
|
||||
Write-Host $PSScriptRoot
|
||||
$versionRegex = "(\d+)\.(\d+)\.(\d+)"
|
||||
|
||||
if($versionNumber -match $versionRegEx)
|
||||
{
|
||||
$buildDayOfYear = (Get-Date).DayofYear;
|
||||
$buildTime = Get-Date -Format HH;
|
||||
# $buildTime = Get-Date -Format HHmmss;
|
||||
# $buildYear = Get-Date -Format yy;
|
||||
# $revision = [string]::Format("{0}{1}{2}", $buildYear, $buildDayOfYear, $buildTime )
|
||||
function Get-NormalizedVersion {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$InputVersion,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ReleaseChannel,
|
||||
[string]$PipelineBuildNumber
|
||||
)
|
||||
|
||||
# max UInt16, 65535
|
||||
#$revision = [string]::Format("{0}{1}", $buildDayOfYear, $buildTime )
|
||||
#Write-Host "Revision" $revision
|
||||
if ($InputVersion -match "^(?<numeric>\d+\.\d+(?:\.\d+){0,2})-(?<suffix>preview)$") {
|
||||
if ($ReleaseChannel -ne "preview") {
|
||||
throw "Version suffix '-preview' can only be used with the preview release channel"
|
||||
}
|
||||
|
||||
$versionNumber = [int]::Parse($matches[1]).ToString() + "." + [int]::Parse($matches[2]).ToString() + "." + [int]::Parse($matches[3]).ToString() # + "." + $revision
|
||||
Write-Host "Version Number" $versionNumber
|
||||
$InputVersion = $matches["numeric"]
|
||||
}
|
||||
|
||||
if ($ReleaseChannel -eq "preview" -and $InputVersion -match "^(\d+)\.(\d+)$") {
|
||||
$major = [int]::Parse($matches[1])
|
||||
$minor = [int]::Parse($matches[2])
|
||||
$now = Get-Date
|
||||
$yyMM = [int]::Parse($now.ToString("yyMM"))
|
||||
$day = $now.ToString("dd")
|
||||
$rev = "001"
|
||||
if ($PipelineBuildNumber -match "_(?<yyMM>\d{4})\.(?<day>\d{2})(?<rev>\d{3})") {
|
||||
$yyMM = [int]::Parse($matches["yyMM"])
|
||||
$day = $matches["day"]
|
||||
$rev = $matches["rev"]
|
||||
}
|
||||
|
||||
$build = [int]::Parse("$day$rev")
|
||||
return "$major.$minor.$yyMM.$build"
|
||||
}
|
||||
|
||||
if ($InputVersion -match "^(\d+)\.(\d+)\.(\d+)(?:\.(\d+))?$") {
|
||||
$versionParts = @([int]::Parse($matches[1]), [int]::Parse($matches[2]), [int]::Parse($matches[3]))
|
||||
if ($matches[4]) {
|
||||
$versionParts += [int]::Parse($matches[4])
|
||||
}
|
||||
|
||||
return $versionParts -join "."
|
||||
}
|
||||
|
||||
throw "Build format does not match the expected pattern (w.x, w.x.y, w.x.y.z, or w.x.y.z-preview for preview channel)"
|
||||
}
|
||||
else{
|
||||
throw "Build format does not match the expected pattern (buildName_w.x.y.z)"
|
||||
|
||||
$versionNumber = Get-NormalizedVersion -InputVersion $versionNumber -ReleaseChannel $Channel -PipelineBuildNumber $BuildNumber
|
||||
foreach ($part in ($versionNumber -split '\.')) {
|
||||
$value = [int]::Parse($part)
|
||||
if ($value -lt 0 -or $value -gt [UInt16]::MaxValue) {
|
||||
throw "Version component '$value' is outside the supported Windows version range 0-65535"
|
||||
}
|
||||
}
|
||||
Write-Host "Version Number" $versionNumber
|
||||
|
||||
$verPropWriteFileLocation = $PSScriptRoot + '/../../src/Version.props';
|
||||
$verPropReadFileLocation = $verPropWriteFileLocation;
|
||||
|
||||
[XML]$verProps = Get-Content $verPropReadFileLocation
|
||||
$verProps.Project.PropertyGroup.Version = $versionNumber;
|
||||
$verProps.Project.PropertyGroup.VersionChannel = $Channel;
|
||||
$verProps.Project.PropertyGroup.SourceCommit = if ([string]::IsNullOrWhiteSpace($SourceCommit)) { "" } else { $SourceCommit };
|
||||
$verProps.Project.PropertyGroup.DevEnvironment = $DevEnvironment;
|
||||
|
||||
Write-Host "xml" $verProps.Project.PropertyGroup.Version
|
||||
$verProps.Save($verPropWriteFileLocation);
|
||||
$verProps.Save($verPropWriteFileLocation);
|
||||
|
||||
Reference in New Issue
Block a user