diff --git a/.pipelines/PowerShell-Release-Official.yml b/.pipelines/PowerShell-Release-Official.yml index 986f803361b..36bee13a0c7 100644 --- a/.pipelines/PowerShell-Release-Official.yml +++ b/.pipelines/PowerShell-Release-Official.yml @@ -25,6 +25,10 @@ parameters: # parameters are shown up in ADO UI in a build queue time displayName: Skip Copying Archives and Installers to PSInfrastructure Public Location type: boolean default: false + - name: skipMSIXPublish + displayName: Skip MSIX Publish + type: boolean + default: false - name: OfficialBuild type: boolean default: false @@ -363,13 +367,12 @@ extends: - stage: PublishMsix dependsOn: PushGitTagAndMakeDraftPublic displayName: Publish MSIX to store + variables: + ob_release_environment: Production jobs: - - template: /.pipelines/templates/approvalJob.yml@self + - template: /.pipelines/templates/release-MSIX-Publish.yml@self parameters: - displayName: Publish the MSIX Bundle package to store - jobName: PublishMsix - instructions: | - Ask Steve to release MSIX bundle package to Store + skipMSIXPublish: ${{ parameters.skipMSIXPublish }} - stage: PublishVPack dependsOn: PushGitTagAndMakeDraftPublic diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Error.png b/.pipelines/store/PDP/PDP-Media/en-US/Error.png new file mode 100644 index 00000000000..48e96378055 Binary files /dev/null and b/.pipelines/store/PDP/PDP-Media/en-US/Error.png differ diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Experimental_Features.png b/.pipelines/store/PDP/PDP-Media/en-US/Experimental_Features.png new file mode 100644 index 00000000000..90420254a8e Binary files /dev/null and b/.pipelines/store/PDP/PDP-Media/en-US/Experimental_Features.png differ diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Feedback_Provider.png b/.pipelines/store/PDP/PDP-Media/en-US/Feedback_Provider.png new file mode 100644 index 00000000000..f4084360d5c Binary files /dev/null and b/.pipelines/store/PDP/PDP-Media/en-US/Feedback_Provider.png differ diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Predictor_Inline.png b/.pipelines/store/PDP/PDP-Media/en-US/Predictor_Inline.png new file mode 100644 index 00000000000..3b8d6228485 Binary files /dev/null and b/.pipelines/store/PDP/PDP-Media/en-US/Predictor_Inline.png differ diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Predictor_ListView.png b/.pipelines/store/PDP/PDP-Media/en-US/Predictor_ListView.png new file mode 100644 index 00000000000..1fb9a6247c5 Binary files /dev/null and b/.pipelines/store/PDP/PDP-Media/en-US/Predictor_ListView.png differ diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Prompt.png b/.pipelines/store/PDP/PDP-Media/en-US/Prompt.png new file mode 100644 index 00000000000..a40d6fddfdc Binary files /dev/null and b/.pipelines/store/PDP/PDP-Media/en-US/Prompt.png differ diff --git a/.pipelines/store/PDP/PDP-Media/en-US/Stable_Release.png b/.pipelines/store/PDP/PDP-Media/en-US/Stable_Release.png new file mode 100644 index 00000000000..2761a46a64f Binary files /dev/null and b/.pipelines/store/PDP/PDP-Media/en-US/Stable_Release.png differ diff --git a/.pipelines/store/PDP/PDP-Media/en-US/pwshLogo.png b/.pipelines/store/PDP/PDP-Media/en-US/pwshLogo.png new file mode 100644 index 00000000000..c531f719c85 Binary files /dev/null and b/.pipelines/store/PDP/PDP-Media/en-US/pwshLogo.png differ diff --git a/.pipelines/store/PDP/PDP/en-US/PDP.xml b/.pipelines/store/PDP/PDP/en-US/PDP.xml new file mode 100644 index 00000000000..15d0bdf5270 --- /dev/null +++ b/.pipelines/store/PDP/PDP/en-US/PDP.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + Shell + + PowerShell + + Terminal + + Command Line + + Automation + + Task Automation + + Scripting + + + PowerShell is a task-based command-line shell and scripting language built on .NET. PowerShell helps system administrators and power-users rapidly automate task that manage operating systems (Linux, macOS, and Windows) and processes. + +PowerShell commands let you manage computers from the command line. PowerShell providers let you access data stores, such as the registry and certificate store, as easily as you access the file system. PowerShell includes a rich expression parser and a fully developed scripting language. + +PowerShell is Open Source. See https://github.com/powershell/powershell + + + + + + + + + + + + + + + + + + + + + + Please see our GitHub releases page for additional details. + + + + + + Prompt + + + + Inline Prediction + + + + Prediction List View + + + + Error Feedback Provider + + + + Feedback Provider + + + + Experimental Features + + + + + + + + + + + + + + + + + + + Interactive Shell + + Scripting Language + + Remote Management + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft Corporation + + + + + https://github.com/PowerShell/PowerShell + + https://github.com/PowerShell/PowerShell/issues + + https://go.microsoft.com/fwlink/?LinkID=521839 + diff --git a/.pipelines/store/SBConfig.json b/.pipelines/store/SBConfig.json new file mode 100644 index 00000000000..002333cba1d --- /dev/null +++ b/.pipelines/store/SBConfig.json @@ -0,0 +1,67 @@ +{ + "helpUri": "https:\\\\aka.ms\\StoreBroker_Config", + "schemaVersion": 2, + "packageParameters": { + "PDPRootPath": "", + "Release": "", + "PDPInclude": [], + "PDPExclude": [], + "LanguageExclude": [ + "default", + "qps-ploc", + "qps-ploca", + "qps-plocm" + ], + "MediaRootPath": "", + "MediaFallbackLanguage": "en-US", + "PackagePath": [], + "OutPath": "", + "OutName": "", + "DisableAutoPackageNameFormatting": false + }, + "appSubmission": { + "productId": "", + "targetPublishMode": "NotSet", + "targetPublishDate": null, + "visibility": "NotSet", + "pricing": { + "priceId": "NotAvailable", + "trialPeriod": "NoFreeTrial", + "marketSpecificPricings": {}, + "sales": [] + }, + "allowTargetFutureDeviceFamilies": { + "Xbox": false, + "Team": false, + "Holographic": false, + "Desktop": false, + "Mobile": false + }, + "allowMicrosoftDecideAppAvailabilityToFutureDeviceFamilies": false, + "enterpriseLicensing": "None", + "applicationCategory": "NotSet", + "hardwarePreferences": [], + "hasExternalInAppProducts": false, + "meetAccessibilityGuidelines": false, + "canInstallOnRemovableMedia": false, + "automaticBackupEnabled": false, + "isGameDvrEnabled": false, + "gamingOptions": [ + { + "genres": [], + "isLocalMultiplayer": false, + "isLocalCooperative": false, + "isOnlineMultiplayer": false, + "isOnlineCooperative": false, + "localMultiplayerMinPlayers": 0, + "localMultiplayerMaxPlayers": 0, + "localCooperativeMinPlayers": 0, + "localCooperativeMaxPlayers": 0, + "isBroadcastingPrivilegeGranted": false, + "isCrossPlayEnabled": false, + "kinectDataForExternal": "Disabled" + } + ], + "notesForCertification": "" + } +} diff --git a/.pipelines/templates/channelSelection.yml b/.pipelines/templates/channelSelection.yml new file mode 100644 index 00000000000..0e352ef7558 --- /dev/null +++ b/.pipelines/templates/channelSelection.yml @@ -0,0 +1,31 @@ +steps: +- pwsh: | + # Determine LTS, Preview, or Stable + $metadata = Get-Content "$repoRoot/tools/metadata.json" -Raw | ConvertFrom-Json + $LTS = $metadata.LTSRelease.Latest + $Stable = $metadata.StableRelease.Latest + $isPreview = '$(OutputReleaseTag.releaseTag)' -match '-' + + $IsLTS = [bool]$LTS + $IsStable = [bool]$Stable + $IsPreview = [bool]$isPreview + + $channelVars = @{ + IsLTS = $IsLTS + IsStable = $IsStable + IsPreview = $IsPreview + } + + $trueCount = ($channelVars.Values | Where-Object { $_ }) | Measure-Object | Select-Object -ExpandProperty Count + if ($trueCount -gt 1) { + Write-Error "Only one of IsLTS, IsStable, or IsPreview can be true. Current values: IsLTS=$IsLTS, IsStable=$IsStable, IsPreview=$IsPreview" + exit 1 + } + + foreach ($name in $channelVars.Keys) { + $value = if ($channelVars[$name]) { 'true' } else { 'false' } + Write-Verbose -Message "Setting $name variable: $value" -Verbose + Write-Host "##vso[task.setvariable variable=$name;isOutput=true]$value" + } + name: ChannelSelection + displayName: Select Preview, Stable, or LTS Channel diff --git a/.pipelines/templates/package-create-msix.yml b/.pipelines/templates/package-create-msix.yml index 0ab2408e6a7..37a66362119 100644 --- a/.pipelines/templates/package-create-msix.yml +++ b/.pipelines/templates/package-create-msix.yml @@ -7,10 +7,16 @@ jobs: variables: - group: msixTools - group: 'Azure Blob variable group' + - group: 'Store Publish Variables' - name: ob_outputDirectory value: '$(Build.ArtifactStagingDirectory)/ONEBRANCH_ARTIFACT' steps: + - checkout: self + clean: true + env: + ob_restore_phase: true # This ensures checkout is done at the beginning of the restore phase + - template: release-SetReleaseTagandContainerName.yml@self - task: DownloadPipelineArtifact@2 @@ -108,3 +114,125 @@ jobs: Write-Verbose -Verbose "Uploaded Bundle:" Get-ChildItem -Path $(ob_outputDirectory) | Write-Verbose -Verbose displayName: Upload msixbundle to Artifacts + + - pwsh: | + Write-Verbose -Verbose "Pipeline.Workspace: $(Pipeline.Workspace)" + Get-ChildItem -Path $(Pipeline.Workspace) -Recurse | Select-Object -ExpandProperty FullName + Write-Verbose -Verbose "System.DefaultWorkingDirectory: $(System.DefaultWorkingDirectory)" + Get-ChildItem -Path $(System.DefaultWorkingDirectory) -Recurse | Select-Object -ExpandProperty FullName + Test-Path -Path '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP-Private.xml' | Write-Verbose -Verbose + displayName: Output Pipeline.Workspace and System.DefaultWorkingDirectory + + - template: channelSelection.yml@self + + - pwsh: | + $IsLTS = '$(ChannelSelection.IsLTS)' -eq 'true' + $IsStable = '$(ChannelSelection.IsStable)' -eq 'true' + $IsPreview = '$(ChannelSelection.IsPreview)' -eq 'true' + + Write-Verbose -Verbose "Channel Selection - LTS: $IsLTS, Stable: $IsStable, Preview: $IsPreview" + + # Define app configurations for each channel + $channelConfigs = @{ + 'LTS' = @{ + AppStoreName = 'PowerShell-LTS' + ProductId = '$(productId-LTS)' + ServiceEndpoint = "StoreAppPublish-Stable" + } + 'Stable' = @{ + AppStoreName = 'PowerShell' + ProductId = '$(productId-Stable)' + ServiceEndpoint = "StoreAppPublish-Stable" + } + 'Preview' = @{ + AppStoreName = 'PowerShell (Preview)' + ProductId = '$(productId-Preview)' + ServiceEndpoint = "StoreAppPublish-Preview" + } + } + + $currentChannel = if ($IsLTS) { 'LTS' } + elseif ($IsStable) { 'Stable' } + elseif ($IsPreview) { 'Preview' } + else { + Write-Error "No valid channel detected" + exit 1 + } + + $config = $channelConfigs[$currentChannel] + Write-Verbose -Verbose "Selected channel: $currentChannel" + Write-Verbose -Verbose "App Store Name: $($config.AppStoreName)" + Write-Verbose -Verbose "Product ID: $($config.ProductId)" + + # Update PDP.xml file + $pdpPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP/en-US/PDP.xml' + if (Test-Path $pdpPath) { + Write-Verbose -Verbose "Updating PDP file: $pdpPath" + + [xml]$pdpXml = Get-Content $pdpPath -Raw + + $appStoreNameElement = $pdpXml.SelectSingleNode("//AppStoreName[@_locID]") + if ($appStoreNameElement) { + $appStoreNameElement.InnerText = $config.AppStoreName + Write-Verbose -Verbose "Updated AppStoreName to: $($config.AppStoreName)" + } else { + Write-Warning "AppStoreName element not found in PDP file" + } + + $pdpXml.Save($pdpPath) + Write-Verbose -Verbose "PDP file updated successfully" + } else { + Write-Error "PDP file not found: $pdpPath" + exit 1 + } + + # Update SBConfig.json file + $sbConfigPath = '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/SBConfig.json' + if (Test-Path $sbConfigPath) { + Write-Verbose -Verbose "Updating SBConfig file: $sbConfigPath" + + $sbConfigJson = Get-Content $sbConfigPath -Raw | ConvertFrom-Json + + $sbConfigJson.appSubmission.productId = $config.ProductId + Write-Verbose -Verbose "Updated productId to: $($config.ProductId)" + + $sbConfigJson | ConvertTo-Json -Depth 100 | Set-Content $sbConfigPath -Encoding UTF8 + Write-Verbose -Verbose "SBConfig file updated successfully" + } else { + Write-Error "SBConfig file not found: $sbConfigPath" + exit 1 + } + + Write-Host "##vso[task.setvariable variable=ServiceConnection]$($config.ServiceEndpoint)" + Write-Host "##vso[task.setvariable variable=SBConfigPath]$($config.SBConfigPath)" + name: UpdateConfigs + displayName: Update PDPs and SBConfig.json + + - task: MS-RDX-MRO.windows-store-publish-dev.package-task.store-package@3 + displayName: 'Create StoreBroker Package' + inputs: + serviceEndpoint: '$(ServiceConnection)' + sbConfigPath: '$(SBConfigPath)' + sourceFolder: '$(BundleDir)' + contents: '*.msixBundle' + outSBName: 'PowerShellStorePackage' + pdpPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP' + pdpMediaPath: '$(System.DefaultWorkingDirectory)/PowerShell/.pipelines/store/PDP/PDP-Media' + + - pwsh: | + $submissionPackageDir = "$(System.DefaultWorkingDirectory)/SBOutDir" + $jsonFile = "$submissionPackageDir/PowerShellStorePackage.json" + $zipFile = "$submissionPackageDir/PowerShellStorePackage.zip" + + if ((Test-Path $jsonFile) -and (Test-Path $zipFile)) { + Write-Verbose -Verbose "Uploading StoreBroker Package files:" + Write-Verbose -Verbose "JSON File: $jsonFile" + Write-Verbose -Verbose "ZIP File: $zipFile" + + Copy-Item -Path $submissionPackageDir -Destination "$(ob_outputDirectory)" -Verbose -Recurse + } + + else { + Write-Error "Required files not found in $submissionPackageDir" + } + displayName: 'Upload StoreBroker Package' diff --git a/.pipelines/templates/release-MSIX-Publish.yml b/.pipelines/templates/release-MSIX-Publish.yml new file mode 100644 index 00000000000..9a0dba8833f --- /dev/null +++ b/.pipelines/templates/release-MSIX-Publish.yml @@ -0,0 +1,115 @@ +parameters: + - name: skipMSIXPublish + type: boolean + +jobs: +- job: Store_Publish_MSIX + displayName: Publish MSIX to the Microsoft Store + pool: + type: release + os: windows + templateContext: + inputs: + - input: pipelineArtifact + pipeline: PSPackagesOfficial + artifactName: drop_msixbundle_CreateMSIXBundle + variables: + - group: 'Store Publish Variables' + - template: ./variable/release-shared.yml@self + parameters: + RELEASETAG: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['OutputReleaseTag.releaseTag'] ] + LTS: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsLTS'] ] + STABLE: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsStable'] ] + PREVIEW: $[ stageDependencies.setReleaseTagAndChangelog.setTagAndChangelog.outputs['ChannelSelection.IsPreview'] ] + steps: + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Verbose -Verbose "Release Tag: $(ReleaseTag)" + Get-ChildItem $(Pipeline.Workspace) -Recurse | Select-Object -ExpandProperty FullName + displayName: 'Capture ReleaseTag and Downloaded Packages' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + if ("$(ReleaseTag)" -eq '') { + Write-Error "ReleaseTag is not set. Cannot proceed with publishing to the Store." + exit 1 + } + $middleURL = '' + $tagString = "$(ReleaseTag)" + if ($tagString -match '-') { + $middleURL = "preview" + } + elseif ($tagString -match '(\d+\.\d+)') { + $middleURL = $matches[1] + } + + $endURL = $tagString -replace '[v\.]','' + $message = "Changelog: https://github.com/PowerShell/PowerShell/blob/master/CHANGELOG/$middleURL.md#$endURL" + Write-Verbose -Verbose "Release Notes for the Store:" + Write-Verbose -Verbose "$message" + $jsonPath = "$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json" + $json = Get-Content $jsonPath -Raw | ConvertFrom-Json + + $json.listings.'en-us'.baseListing.releaseNotes = $message + + $json | ConvertTo-Json -Depth 100 | Set-Content $jsonPath -Encoding UTF8 + displayName: 'Update Release Notes in JSON' + + - task: PowerShell@2 + inputs: + targetType: inline + script: | + Write-Verbose -Verbose "Channel Selection - LTS: $(LTS), Stable: $(Stable), Preview: $(Preview)" + + # Define app configurations for each channel using secret variables + $channelConfigs = @{ + 'LTS' = @{ + AppId = '$(AppID-LTS)' + ServiceEndpoint = 'StoreAppPublish-Stable' + } + 'Stable' = @{ + AppId = '$(AppID-Stable)' + ServiceEndpoint = 'StoreAppPublish-Stable' + } + 'Preview' = @{ + AppId = '$(AppID-Preview)' + ServiceEndpoint = 'StoreAppPublish-Preview' + } + } + + # Determine the current channel + $currentChannel = if ($IsLTS) { 'LTS' } + elseif ($IsStable) { 'Stable' } + elseif ($IsPreview) { 'Preview' } + else { + Write-Error "No valid channel detected" + exit 1 + } + + $config = $channelConfigs[$currentChannel] + Write-Verbose -Verbose "Selected channel: $currentChannel" + Write-Verbose -Verbose "App ID: $($config.AppId)" + Write-Verbose -Verbose "Service Endpoint: $($config.ServiceEndpoint)" + + # Set pipeline variables for use in the store-publish task + Write-Host "##vso[task.setvariable variable=SelectedAppId]$($config.AppId)" + Write-Host "##vso[task.setvariable variable=SelectedServiceEndpoint]$($config.ServiceEndpoint)" + Write-Host "##vso[task.setvariable variable=SelectedChannel]$currentChannel" + displayName: 'Set StoreBroker Configurations' + + - task: MS-RDX-MRO.windows-store-publish.publish-task.store-publish@3 + displayName: 'Publish LTS StoreBroker Package' + condition: ne('${{ parameters.skipMSIXPublish }}', 'true') + inputs: + serviceEndpoint: '$(SelectedServiceEndpoint)' + appId: '$(SelectedAppId)' + inputMethod: JsonAndZip + jsonPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.json' + zipPath: '$(Pipeline.Workspace)\SBOutDir\PowerShellStorePackage.zip' + numberOfPackagesToKeep: 2 + jsonZipUpdateMetadata: true + targetPublishMode: 'Immediate' diff --git a/.pipelines/templates/release-SetTagAndChangelog.yml b/.pipelines/templates/release-SetTagAndChangelog.yml index f0c516dd28f..b33e652b3c7 100644 --- a/.pipelines/templates/release-SetTagAndChangelog.yml +++ b/.pipelines/templates/release-SetTagAndChangelog.yml @@ -19,7 +19,7 @@ jobs: clean: true env: ob_restore_phase: true - + - pwsh: | Write-Verbose -Verbose "Release Tag: $(OutputReleaseTag.releaseTag)" $releaseVersion = '$(OutputReleaseTag.releaseTag)' -replace '^v','' @@ -47,3 +47,5 @@ jobs: New-Item -Path $(ob_outputDirectory)/CHANGELOG -ItemType Directory -Force Copy-Item -Path $filePath -Destination $(ob_outputDirectory)/CHANGELOG displayName: Upload Changelog + + - template: channelSelection.yml@self diff --git a/tools/packaging/packaging.psm1 b/tools/packaging/packaging.psm1 index ef8fba4efb8..8e7fb3c4e08 100644 --- a/tools/packaging/packaging.psm1 +++ b/tools/packaging/packaging.psm1 @@ -515,6 +515,7 @@ function Start-PSPackage { Architecture = $WindowsRuntime.Split('-')[1] Force = $Force Private = $Private + LTS = $LTS } if ($PSCmdlet.ShouldProcess("Create MSIX Package")) { @@ -3670,6 +3671,9 @@ function New-MSIXPackage # Produce private package for testing in Store [Switch] $Private, + # Produce LTS package + [Switch] $LTS, + # Force overwrite of package [Switch] $Force, @@ -3714,6 +3718,9 @@ function New-MSIXPackage } elseif ($ProductSemanticVersion.Contains('-')) { $ProductName += 'Preview' $displayName += ' Preview' + } elseif ($LTS) { + $ProductName += '-LTS' + $displayName += '-LTS' } Write-Verbose -Verbose "ProductName: $productName" @@ -3733,6 +3740,10 @@ function New-MSIXPackage # This is the PhoneProductId for the "Microsoft.PowerShellPreview" package. $PhoneProductId = "67859fd2-b02a-45be-8fb5-62c569a3e8bf" Write-Verbose "Using Preview assets" -Verbose + } elseif ($LTS) { + # This is the PhoneProductId for the "Microsoft.PowerShell-LTS" package. + $PhoneProductId = "a9af273a-c636-47ac-bc2a-775edf80b2b9" + Write-Verbose "Using LTS assets" -Verbose } # Appx manifest needs to be in root of source path, but the embedded version needs to be updated