Repository: microsoft/terminal (language: cpp)

Issue:
Implement "Nightly" build
We need a second CI/build variation that happens on a less frequent cadence than the usual CI.

This is because we have tasks we want to run that are too expensive to run on every pull/PR/commit, but we still want to run on a regular basis.

Two examples:
1. Performance gate tests
2. Static code analysis

----

Related PRs
* #15865 
* #15869 


```[tasklist]
### Remaining TODOs
- [ ] Fix icons - remove CAN from small ones
- [ ] Fix the High Contrast icons so they have CAN as the text
- [x] Publish artifacts to a publicly accessible place
```



Discussion / hints:

Fix patch:
diff --git a/.github/actions/spelling/allow/microsoft.txt b/.github/actions/spelling/allow/microsoft.txt
index b79f49c3781..ccfd0fec287 100644
--- a/.github/actions/spelling/allow/microsoft.txt
+++ b/.github/actions/spelling/allow/microsoft.txt
@@ -4,6 +4,7 @@ advapi
 altform
 altforms
 appendwttlogging
+appinstaller
 appx
 appxbundle
 appxerror
diff --git a/build/config/template.appinstaller b/build/config/template.appinstaller
new file mode 100644
index 00000000000..7977fd950b8
--- /dev/null
+++ b/build/config/template.appinstaller
@@ -0,0 +1,38 @@
+﻿<?xml version="1.0" encoding="utf-8"?>
+<AppInstaller
+  xmlns="http://schemas.microsoft.com/appx/appinstaller/2017/2"
+  Version="1.0.0.0"
+  Uri="$$ROOT$$$$NAME$$.appinstaller">
+
+  <MainBundle
+    Name="$$NAME$$"
+    Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
+    Version="$$VERSION$$"
+    Uri="$$ROOT$$$$PACKAGE$$" />
+
+  <Dependencies>
+    <Package
+      Name="Microsoft.UI.Xaml.2.8"
+      Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
+      Version="8.2305.5001.0"
+      ProcessorArchitecture="x64"
+      Uri="https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.4/Microsoft.UI.Xaml.2.8.x64.appx" />
+    <Package
+      Name="Microsoft.UI.Xaml.2.8"
+      Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
+      Version="8.2305.5001.0"
+      ProcessorArchitecture="x86"
+      Uri="https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.4/Microsoft.UI.Xaml.2.8.x86.appx" />
+    <Package
+      Name="Microsoft.UI.Xaml.2.8"
+      Publisher="CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"
+      Version="8.2305.5001.0"
+      ProcessorArchitecture="arm64"
+      Uri="https://github.com/microsoft/microsoft-ui-xaml/releases/download/v2.8.4/Microsoft.UI.Xaml.2.8.arm64.appx" />
+  </Dependencies>
+
+  <UpdateSettings>
+    <OnLaunch
+      HoursBetweenUpdateChecks="6" />
+  </UpdateSettings>
+</AppInstaller>
diff --git a/build/pipelines/nightly.yml b/build/pipelines/nightly.yml
index 025a643509f..6ec737157fd 100644
--- a/build/pipelines/nightly.yml
+++ b/build/pipelines/nightly.yml
@@ -10,6 +10,12 @@ schedules:
 
 name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr)
 
+parameters:
+  - name: publishToAzure
+    displayName: "Deploy to **PUBLIC** Azure Storage"
+    type: boolean
+    default: true
+
 extends:
   template: templates-v2\pipeline-full-release-build.yml
   parameters:
@@ -21,3 +27,17 @@ extends:
     publishSymbolsToPublic: true
     publishVpackToWindows: false
     symbolExpiryTime: 15 # Nightly builds do not keep symbols for very long!
+    ${{ if eq(true, parameters.publishToAzure) }}:
+      extraPublishJobs:
+        - template: job-deploy-to-azure-storage.yml
+          parameters:
+            pool:
+              name: SHINE-INT-S
+            dependsOn: [PublishSymbols]
+            storagePublicRootURL: $(AppInstallerRootURL)
+            subscription: $(AzureSubscriptionName)
+            storageAccount: $(AzureStorageAccount)
+            storageContainer: $(AzureStorageContainer)
+            buildConfiguration: Release
+            environment: production-canary
+
diff --git a/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml b/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml
new file mode 100644
index 00000000000..140a513ee14
--- /dev/null
+++ b/build/pipelines/templates-v2/job-deploy-to-azure-storage.yml
@@ -0,0 +1,70 @@
+parameters:
+  - name: buildConfiguration
+    type: string
+  - name: pool
+    type: object
+    default: []
+  - name: dependsOn
+    type: object
+    default: null
+  - name: artifactStem
+    type: string
+    default: ''
+  - name: variables
+    type: object
+    default: {}
+  - name: environment
+    type: string
+  - name: storagePublicRootURL
+    type: string
+  - name: subscription
+    type: string
+  - name: storageAccount
+    type: string
+  - name: storageContainer
+    type: string
+
+jobs:
+- deployment: DeployAzure
+  ${{ if ne(length(parameters.pool), 0) }}:
+    pool: ${{ parameters.pool }}
+  displayName: Publish to Azure Storage (Prod)
+  dependsOn: ${{ parameters.dependsOn }}
+  variables:
+    ${{ insert }}: ${{ parameters.variables }}
+  environment: ${{ parameters.environment }}
+  strategy:
+    runOnce:
+      deploy:
+        steps:
+        - download: none
+
+        - checkout: self
+          clean: true
+          fetchDepth: 1
+          fetchTags: false # Tags still result in depth > 1 fetch; we don't need them here
+          submodules: true
+          persistCredentials: True
+
+        - task: DownloadPipelineArtifact@2
+          displayName: Download MSIX Bundle Artifact
+          inputs:
+            artifactName: appxbundle-${{ parameters.buildConfiguration }}${{ parameters.artifactStem }}
+            downloadPath: '$(Build.SourcesDirectory)/_out'
+            itemPattern: '**/*.msixbundle'
+
+        - pwsh: |-
+            $b = Get-Item _out/*.msixbundle
+            ./build/scripts/New-AppInstallerFromTemplateAndBundle.ps1 -BundlePath $b.FullName -AppInstallerTemplatePath ./build/config/template.appinstaller -AppInstallerRoot "${{ parameters.storagePublicRootURL }}" -OutputPath _out/Microsoft.WindowsTerminalCanary.appinstaller
+          displayName: "Produce AppInstaller for MSIX bundle"
+
+        - task: AzureFileCopy@5
+          displayName: Publish to Storage Account
+          inputs:
+            sourcePath: _out/*
+            Destination: AzureBlob
+            azureSubscription: ${{ parameters.subscription }}
+            storage: ${{ parameters.storageAccount }}
+            ContainerName: ${{ parameters.storageContainer }}
+            AdditionalArgumentsForBlobCopy: "--content-type application/octet-stream"
+
diff --git a/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml b/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml
index 121e07b4417..1457178f973 100644
--- a/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml
+++ b/build/pipelines/templates-v2/job-merge-msix-into-bundle.yml
@@ -48,6 +48,8 @@ jobs:
       BundleStemName: Microsoft.WindowsTerminal
     ${{ elseif eq(parameters.branding, 'Preview') }}:
       BundleStemName: Microsoft.WindowsTerminalPreview
+    ${{ elseif eq(parameters.branding, 'Canary') }}:
+      BundleStemName: Microsoft.WindowsTerminalCanary
     ${{ else }}:
       BundleStemName: WindowsTerminalDev
     JobOutputDirectory: '$(System.ArtifactsDirectory)/bundle'
diff --git a/build/pipelines/templates-v2/pipeline-full-release-build.yml b/build/pipelines/templates-v2/pipeline-full-release-build.yml
index 42c5c6250d8..b785deef64e 100644
--- a/build/pipelines/templates-v2/pipeline-full-release-build.yml
+++ b/build/pipelines/templates-v2/pipeline-full-release-build.yml
@@ -51,6 +51,9 @@ parameters:
     type: boolean
     default: false
 
+  - name: extraPublishJobs
+    type: object
+    default: []
   - name: pool
     type: object
     default:
@@ -193,4 +196,5 @@ stages:
           includePublicSymbolServer: ${{ parameters.publishSymbolsToPublic }}
           symbolExpiryTime: ${{ parameters.symbolExpiryTime }}
 
+      - ${{ parameters.extraPublishJobs }}
 ...
diff --git a/build/scripts/New-AppInstallerFromTemplateAndBundle.ps1 b/build/scripts/New-AppInstallerFromTemplateAndBundle.ps1
new file mode 100644
index 00000000000..b49ad244ffb
--- /dev/null
+++ b/build/scripts/New-AppInstallerFromTemplateAndBundle.ps1
@@ -0,0 +1,42 @@
+[CmdletBinding()]
+Param(
+    [Parameter(Mandatory,
+      HelpMessage="Path to the .msixbundle")]
+    [string]
+    $BundlePath,
+
+    [Parameter(Mandatory,
+      HelpMessage="Path to the .appinstaller template")]
+    [string]
+    $AppInstallerTemplatePath,
+
+    [string]
+    $AppInstallerRoot,
+
+    [Parameter(Mandatory,
+      HelpMessage="Output Path")]
+    [string]
+    $OutputPath
+)
+
+$ErrorActionPreference = "Stop"
+
+$sentinelFile = New-TemporaryFile
+$directory = New-Item -Type Directory "$($sentinelFile.FullName)_Package"
+Remove-Item $sentinelFile -Force -EA:Ignore
+
+$bundle = (Get-Item $BundlePath)
+& tar.exe -x -f $bundle.FullName -C $directory AppxMetadata/AppxBundleManifest.xml
+
+$xml = [xml](Get-Content (Join-Path $directory "AppxMetadata\AppxBundleManifest.xml"))
+$name = $xml.Bundle.Identity.Name
+$version = $xml.Bundle.Identity.Version
+
+$doc = (Get-Content -ReadCount 0 $AppInstallerTemplatePath)
+$doc = $doc -Replace '\$\$ROOT\$\$',$AppInstallerRoot
+$doc = $doc -Replace '\$\$NAME\$\$',$name
+$doc = $doc -Replace '\$\$VERSION\$\$',$version
+$doc = $doc -Replace '\$\$PACKAGE\$\$',$bundle.Name
+$doc | Out-File -Encoding utf8NoBOM $OutputPath
+
+Get-Item $OutputPath


Test patch: