Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# GitHub Copilot Instructions (WindowsAppSDK-Samples)

Read scripts first; improve their headers instead of duplicating detail here.

## Build
Use only `build.ps1` / `build.cmd`. Read `build.ps1` before invoking. It auto: detects platform, picks solutions (current dir > selected sample > all), initializes VS DevShell, restores via local nuget, emits `.binlog` per solution. Do NOT hand-roll msbuild/nuget restore logic.

## Versions
Run `UpdateVersions.ps1` only after reading it. Get the WinAppSDK version string from: https://www.nuget.org/packages/Microsoft.WindowsAppSdk/ (stable/preview/servicing) and pass as `-WinAppSDKVersion`. If script params unclear, fix that script.

## Coding Guidelines
- Small, focused diffs; no mass reformatting; ensure SOLID principles.
- Preserve APIs, encoding, line endings, relative paths.
- Support arm64 & x64 paths.
- PowerShell: approved verbs; each new script has Synopsis/Description/Parameters/Examples header.
- C#/C++: follow existing style; lean headers; forward declare where practical; keep samples illustrative.
- Minimal necessary comments; avoid noise. Centralize user strings for localization.
- Never log secrets or absolute external paths.

## PR Guidance
- One intent per PR. Update script README/header if behavior changes.
- Provide summary: what / why / validation.
- Run a targeted build (e.g. `pwsh -File build.ps1 -Sample AppLifecycle`).
- For version bumps: inspect at least one changed project file.
- No new warnings/errors or large cosmetic churn.

## Design Docs (Large / Cross-Sample)
Before broad edits create `DESIGN-<topic>.md`:
- Single sample: `Samples/<Sample>/DESIGN-<topic>.md`
- Multi-sample/shared: repo root.
Include: Problem, Goals/Non-Goals, Affected Areas, Approach, Risks, Validation Plan. Reference doc in PR.

## When Unsure
Draft a design doc or WIP PR summarizing assumptions—don't guess.

---
Keep this file lean; source-of-truth for behavior lives in script headers.
44 changes: 43 additions & 1 deletion UpdateVersions.ps1
Original file line number Diff line number Diff line change
@@ -1,8 +1,50 @@
Param(
[string]$WinAppSDKVersion = "",
[Parameter(Mandatory=$true)]
[string]$WinAppSDKVersion,
[string]$NuGetPackagesFolder = ""
)

# Prerequisites
# If the NuGetPackagesFolder parameter wasn't provided,
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment has a trailing comma that should be removed for proper grammar.

Suggested change
# If the NuGetPackagesFolder parameter wasn't provided,
# If the NuGetPackagesFolder parameter wasn't provided

Copilot uses AI. Check for mistakes.

# Install the version of WinAppSDK in 'packages' folder first,
if ($NuGetPackagesFolder -eq "") {
$NuGetPackagesFolder = Join-Path $PSScriptRoot "packages"
Write-Host "NuGetPackagesFolder not supplied. Using default: $NuGetPackagesFolder"

if (!(Test-Path $NuGetPackagesFolder)) {
Write-Host "Packages folder not found. Will perform a minimal restore to populate it."
}

$nugetToolDir = Join-Path $PSScriptRoot ".nuget"
$nugetExe = Join-Path $nugetToolDir "nuget.exe"
if (!(Test-Path $nugetExe)) {
if (!(Test-Path $nugetToolDir)) { New-Item -ItemType Directory -Path $nugetToolDir | Out-Null }
Write-Host "Downloading nuget.exe..."
Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile $nugetExe
}

# If the WinAppSDK package (any variant) isn't already present, install just that
# package (and its dependencies) at the requested version directly.
$hasWinAppSdk = Get-ChildItem -Path $NuGetPackagesFolder -ErrorAction SilentlyContinue | Where-Object { $_.Name -like "Microsoft.WindowsAppSDK.*" } | Select-Object -First 1
if (-not $hasWinAppSdk) {
if ([string]::IsNullOrWhiteSpace($WinAppSDKVersion)) {
Write-Warning "WinAppSDKVersion not supplied; cannot install Microsoft.WindowsAppSDK package automatically."
} else {
if (!(Test-Path $NuGetPackagesFolder)) { New-Item -ItemType Directory -Path $NuGetPackagesFolder | Out-Null }
Write-Host "Installing Microsoft.WindowsAppSDK $WinAppSDKVersion into $NuGetPackagesFolder (running inside folder)"
Push-Location $NuGetPackagesFolder
try {
& $nugetExe install Microsoft.WindowsAppSDK -Version $WinAppSDKVersion -OutputDirectory . -Prerelease -DependencyVersion Highest | Write-Host
}
finally {
Pop-Location
}
}
} else {
Write-Host "Detected existing Microsoft.WindowsAppSDK package; skipping install."
}
}

# First, add the metapackage
$nugetPackageToVersionTable = @{"Microsoft.WindowsAppSDK" = $WinAppSDKVersion}

Expand Down
82 changes: 12 additions & 70 deletions build.cmd
Original file line number Diff line number Diff line change
@@ -1,79 +1,21 @@
@echo off
:: Thin wrapper calling PowerShell implementation
set SCRIPT_DIR=%~dp0
set PS_SCRIPT=%SCRIPT_DIR%build.ps1

if "%1"=="/?" goto :usage
if "%1"=="-?" goto :usage
if "%VSINSTALLDIR%" == "" goto :usage

setlocal enabledelayedexpansion enableextensions

set BUILDCMDSTARTTIME=%time%

set platform=%1
set configuration=%2
set sample_filter=%3\

if "%platform%"=="" set platform=x64
if "%configuration%"=="" set configuration=Release

if not exist ".\.nuget" mkdir ".\.nuget"
if not exist ".\.nuget\nuget.exe" powershell -Command "Invoke-WebRequest https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile .\.nuget\nuget.exe"

set NUGET_RESTORE_MSBUILD_ARGS=/p:PublishReadyToRun=true

for /f "delims=" %%D in ('dir /s/b samples\%sample_filter%*.sln') do (
call .nuget\nuget.exe restore "%%D" -configfile Samples\nuget.config -PackagesDirectory %~dp0packages
call msbuild /warnaserror /p:platform=%platform% /p:configuration=%configuration% /p:NugetPackageDirectory=%~dp0packages /bl:"%%~nD.binlog" "%%D"

if ERRORLEVEL 1 goto :eof
)

:showDurationAndExit
set BUILDCMDENDTIME=%time%
:: Note: The '1's in this line are to convert a value like "08" to "108", since numbers which
:: begin with '0' are interpreted as octal, which makes "08" and "09" invalid. Adding the
:: '1's effectively adds 100 to both sides of the subtraction, avoiding this issue.
:: Hours has a leading space instead of 0, so the '1's trick isn't used on that one.
set /a BUILDDURATION_HRS= %BUILDCMDENDTIME:~0,2%- %BUILDCMDSTARTTIME:~0,2%
set /a BUILDDURATION_MIN=1%BUILDCMDENDTIME:~3,2%-1%BUILDCMDSTARTTIME:~3,2%
set /a BUILDDURATION_SEC=1%BUILDCMDENDTIME:~6,2%-1%BUILDCMDSTARTTIME:~6,2%
set /a BUILDDURATION_HSC=1%BUILDCMDENDTIME:~9,2%-1%BUILDCMDSTARTTIME:~9,2%
if %BUILDDURATION_HSC% lss 0 (
set /a BUILDDURATION_HSC=!BUILDDURATION_HSC!+100
set /a BUILDDURATION_SEC=!BUILDDURATION_SEC!-1
)
if %BUILDDURATION_SEC% lss 0 (
set /a BUILDDURATION_SEC=!BUILDDURATION_SEC!+60
set /a BUILDDURATION_MIN=!BUILDDURATION_MIN!-1
)
if %BUILDDURATION_MIN% lss 0 (
set /a BUILDDURATION_MIN=!BUILDDURATION_MIN!+60
set /a BUILDDURATION_HRS=!BUILDDURATION_HRS!-1
)
if %BUILDDURATION_HRS% lss 0 (
set /a BUILDDURATION_HRS=!BUILDDURATION_HRS!+24
)
:: Add a '0' at the start to ensure at least two digits. The output will then just
:: show the last two digits for each.
set BUILDDURATION_HRS=0%BUILDDURATION_HRS%
set BUILDDURATION_MIN=0%BUILDDURATION_MIN%
set BUILDDURATION_SEC=0%BUILDDURATION_SEC%
set BUILDDURATION_HSC=0%BUILDDURATION_HSC%
echo ---
echo Start time: %BUILDCMDSTARTTIME%. End time: %BUILDCMDENDTIME%
echo Elapsed: %BUILDDURATION_HRS:~-2%:%BUILDDURATION_MIN:~-2%:%BUILDDURATION_SEC:~-2%.%BUILDDURATION_HSC:~-2%
endlocal

goto :eof
:: Forward all arguments directly; build.ps1 handles defaults/validation
powershell -NoProfile -ExecutionPolicy Bypass -File "%PS_SCRIPT%" %*
exit /b %ERRORLEVEL%

:usage
echo Usage:
echo This script should be run under a Visual Studio Developer Command Prompt.
echo.
echo build.cmd [Platform] [Configuration] [Sample]
echo.
echo [Platform] Either x86, x64, or arm64. Default is x64.
echo [Configuration] Either Debug or Release. Default is Release.
echo [Sample] The sample folder under Samples to build. If none specified, all samples are built.
echo.
echo If no parameters are specified, all samples are built for x64 Release.

exit /b /1
echo (Wrapper over build.ps1)
echo Platform: x86|x64|arm64 (default x64)
echo Configuration: Debug|Release (default Release)
echo Sample: Optional sample folder name under Samples
exit /b 1
163 changes: 163 additions & 0 deletions build.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<#!
.SYNOPSIS
Builds WindowsAppSDK Samples solutions (or local solution(s)).
.DESCRIPTION
PowerShell port of the original build.cmd logic.
* Ensures Visual Studio Developer environment (DevShell) is initialized.
* Downloads a local nuget.exe if not present and restores packages.
* Builds one or more .sln files with MSBuild producing a .binlog per solution.
Solution discovery order:
1. If -Sample is specified: build all .sln files under Samples/<Sample>.
2. Else if the current working directory (where you invoked the script) contains one or more .sln files: build those only (no recursion).
3. Else build all .sln files under Samples (recursive).
.PARAMETER Platform
Target platform (x86, x64, arm64, auto). Default: auto (arm64 on ARM64 OS, else x64).
.PARAMETER Configuration
Build configuration (Debug or Release). Default: Release.
.PARAMETER Sample
Optional sample folder name (child of Samples) to restrict the build scope.
.PARAMETER Help
Show usage information.
.EXAMPLE
./build.ps1
(Auto-detect platform, build local solutions or all samples.)
.EXAMPLE
./build.ps1 -Platform arm64 -Configuration Debug -Sample AppLifecycle
(Build only the AppLifecycle sample solutions for arm64 Debug.)
#>
#[CmdletBinding()] parameters
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment '#[CmdletBinding()] parameters' is grammatically incorrect. It should be '#[CmdletBinding()] param' or a more descriptive comment like '# Parameters definition'.

Suggested change
#[CmdletBinding()] parameters
# Parameters definition

Copilot uses AI. Check for mistakes.

[CmdletBinding()] param(
[Parameter(Position=0)] [ValidateSet('x86','x64','arm64','auto')] [string]$Platform = 'auto',
[Parameter(Position=1)] [ValidateSet('Debug','Release')] [string]$Configuration = 'Release',
[Parameter(Position=2)] [string]$Sample = '',
[switch]$Help
)

# --- Functions (grouped at top for clarity) ---

# Show-Usage: Display help/usage information.
function Show-Usage {
Write-Host 'Usage:'
Write-Host ' build.ps1 [-Platform x86|x64|arm64|auto] [-Configuration Debug|Release] [-Sample <SampleFolder>]'
Write-Host ''
Write-Host 'Platform:'
Write-Host ' auto (default) Detects host OS arch: arm64 on ARM64, else x64.'
Write-Host ''
Write-Host 'Solution selection:'
Write-Host ' -Sample <Name> Build solutions under Samples/<Name>'
Write-Host ' (no Sample) If current directory has .sln file(s), build only those; otherwise build all Samples solutions.'
Write-Host ''
Write-Host 'Examples:'
Write-Host ' ./build.ps1'
Write-Host ' ./build.ps1 -Platform arm64 -Configuration Debug'
Write-Host ' ./build.ps1 -Sample AppLifecycle'
}

# Initialize-VSEnvironment: Ensure VS dev environment & MSBuild are available.
function Initialize-VSEnvironment {
if (Get-Command msbuild -ErrorAction SilentlyContinue) { Write-Host 'MSBuild already on PATH; assuming VS env initialized.'; return }
if ($env:VSINSTALLDIR) { Write-Host "VS environment already initialized: $env:VSINSTALLDIR"; return }
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
if (-not (Test-Path $vswhere)) { Write-Error 'vswhere.exe not found. Install Visual Studio 2022 with MSBuild.'; exit 1 }
$installationPath = & $vswhere -latest -prerelease -products * -requires Microsoft.Component.MSBuild -property installationPath | Select-Object -First 1
if (-not $installationPath) { Write-Error 'Unable to locate a suitable Visual Studio installation.'; exit 1 }
$devShellModule = Join-Path $installationPath 'Common7\Tools\Microsoft.VisualStudio.DevShell.dll'
if (Test-Path $devShellModule) {
Write-Host "Initializing VS DevShell from: $devShellModule" -ForegroundColor Yellow
try { Import-Module $devShellModule -ErrorAction Stop; Enter-VsDevShell -VsInstallPath $installationPath -SkipAutomaticLocation | Out-Null }
catch { Write-Warning "DevShell initialization failed: $($_.Exception.Message)" }
}
}

# Resolve-Platform: Turn 'auto' into a concrete platform value.
function Resolve-Platform { param([string]$PlatformValue)
if ($PlatformValue -ne 'auto') { return $PlatformValue }
$detected = 'x64'
try {
$osArch = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture.ToString().ToLowerInvariant()
switch ($osArch) { 'arm64' { $detected = 'arm64' } 'x86' { $detected = 'x86' } default { $detected = 'x64' } }
if ($detected -eq 'x86' -and $env:PROCESSOR_ARCHITEW6432) { $detected = 'x64' }
} catch {}
Write-Host "Auto-detected platform: $detected" -ForegroundColor Yellow
return $detected
}

# Initialize-NuGetEnvironment: Create folders/download nuget.exe if needed.
function Initialize-NuGetEnvironment {
if (-not (Test-Path $nugetDir)) { New-Item -ItemType Directory -Path $nugetDir | Out-Null }
if (-not (Test-Path $nugetExe)) { Write-Host 'Downloading nuget.exe...'; Invoke-WebRequest -UseBasicParsing https://dist.nuget.org/win-x86-commandline/latest/nuget.exe -OutFile $nugetExe }
if (-not (Test-Path $packagesDir)) { New-Item -ItemType Directory -Path $packagesDir | Out-Null }
}

# Get-Solutions: Discover which solutions to build.
function Get-Solutions { param([string]$SampleFilter)
$targetRoot = $null; $solutions = @()
if (-not [string]::IsNullOrWhiteSpace($SampleFilter)) {
$targetRoot = Join-Path $samplesRoot $SampleFilter
if (-not (Test-Path $targetRoot)) { Write-Error "Sample path not found: $targetRoot"; exit 1 }
$solutions = Get-ChildItem -Path $targetRoot -Filter *.sln -Recurse | Sort-Object FullName
} else {
$currentDir = Get-Location
$localSolutions = Get-ChildItem -Path $currentDir -Filter *.sln -File -ErrorAction SilentlyContinue | Sort-Object FullName
if ($localSolutions) { Write-Host "Detected $($localSolutions.Count) solution(s) in current directory: $currentDir" -ForegroundColor Yellow; $solutions = $localSolutions }
else { $targetRoot = $samplesRoot; $solutions = Get-ChildItem -Path $targetRoot -Filter *.sln -Recurse | Sort-Object FullName }
}
return $solutions
}

# Restore-Solution: Perform NuGet restore for a solution (.sln) using repo nuget.exe.
function Restore-Solution { param([string]$SolutionPath)
Write-Host "Restoring: $SolutionPath" -ForegroundColor Cyan
& $nugetExe restore $SolutionPath -ConfigFile (Join-Path $samplesRoot 'nuget.config') -PackagesDirectory $packagesDir
if ($LASTEXITCODE -ne 0) { Write-Error 'NuGet restore failed.'; exit $LASTEXITCODE }
}

# Build-Solution: Invoke MSBuild on a solution with platform/config and emit binlog.
function Build-Solution { param([string]$SolutionPath,[string]$Platform,[string]$Configuration)
Comment on lines +119 to +120
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: this function has an extra leading space compared to other functions. All functions should use consistent indentation.

Suggested change
# Build-Solution: Invoke MSBuild on a solution with platform/config and emit binlog.
function Build-Solution { param([string]$SolutionPath,[string]$Platform,[string]$Configuration)
# Build-Solution: Invoke MSBuild on a solution with platform/config and emit binlog.
function Build-Solution { param([string]$SolutionPath,[string]$Platform,[string]$Configuration)

Copilot uses AI. Check for mistakes.

$binlog = Join-Path (Split-Path $SolutionPath -Parent) ("{0}.binlog" -f ([IO.Path]::GetFileNameWithoutExtension($SolutionPath)))
Write-Host "Building: $SolutionPath" -ForegroundColor Cyan
& msbuild /warnaserror /p:Platform=$Platform /p:Configuration=$Configuration /p:NugetPackageDirectory=$packagesDir /bl:"$binlog" "$SolutionPath"
if ($LASTEXITCODE -ne 0) { Write-Error 'MSBuild failed.'; exit $LASTEXITCODE }
}
Comment on lines +112 to +125
Copy link

Copilot AI Sep 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: this function and the following Build-Solution function have an extra leading space compared to other functions. All functions should use consistent indentation.

Suggested change
# Restore-Solution: Perform NuGet restore for a solution (.sln) using repo nuget.exe.
function Restore-Solution { param([string]$SolutionPath)
Write-Host "Restoring: $SolutionPath" -ForegroundColor Cyan
& $nugetExe restore $SolutionPath -ConfigFile (Join-Path $samplesRoot 'nuget.config') -PackagesDirectory $packagesDir
if ($LASTEXITCODE -ne 0) { Write-Error 'NuGet restore failed.'; exit $LASTEXITCODE }
}
# Build-Solution: Invoke MSBuild on a solution with platform/config and emit binlog.
function Build-Solution { param([string]$SolutionPath,[string]$Platform,[string]$Configuration)
$binlog = Join-Path (Split-Path $SolutionPath -Parent) ("{0}.binlog" -f ([IO.Path]::GetFileNameWithoutExtension($SolutionPath)))
Write-Host "Building: $SolutionPath" -ForegroundColor Cyan
& msbuild /warnaserror /p:Platform=$Platform /p:Configuration=$Configuration /p:NugetPackageDirectory=$packagesDir /bl:"$binlog" "$SolutionPath"
if ($LASTEXITCODE -ne 0) { Write-Error 'MSBuild failed.'; exit $LASTEXITCODE }
}
# Restore-Solution: Perform NuGet restore for a solution (.sln) using repo nuget.exe.
function Restore-Solution { param([string]$SolutionPath)
Write-Host "Restoring: $SolutionPath" -ForegroundColor Cyan
& $nugetExe restore $SolutionPath -ConfigFile (Join-Path $samplesRoot 'nuget.config') -PackagesDirectory $packagesDir
if ($LASTEXITCODE -ne 0) { Write-Error 'NuGet restore failed.'; exit $LASTEXITCODE }
}
# Build-Solution: Invoke MSBuild on a solution with platform/config and emit binlog.
function Build-Solution { param([string]$SolutionPath,[string]$Platform,[string]$Configuration)
$binlog = Join-Path (Split-Path $SolutionPath -Parent) ("{0}.binlog" -f ([IO.Path]::GetFileNameWithoutExtension($SolutionPath)))
Write-Host "Building: $SolutionPath" -ForegroundColor Cyan
& msbuild /warnaserror /p:Platform=$Platform /p:Configuration=$Configuration /p:NugetPackageDirectory=$packagesDir /bl:"$binlog" "$SolutionPath"
if ($LASTEXITCODE -ne 0) { Write-Error 'MSBuild failed.'; exit $LASTEXITCODE }
}

Copilot uses AI. Check for mistakes.


# --- Main Execution Flow ---
if ($Help -or $PSBoundParameters.ContainsKey('?')) { Show-Usage; return }
Initialize-VSEnvironment

if (-not $env:VSINSTALLDIR) { Write-Warning 'VSINSTALLDIR environment variable not found. Run this from a VS Developer PowerShell prompt.'; Show-Usage; exit 1 }

$Platform = Resolve-Platform -PlatformValue $Platform
$repoRoot = Split-Path -Parent $MyInvocation.MyCommand.Path
$nugetDir = Join-Path $repoRoot '.nuget'
$nugetExe = Join-Path $nugetDir 'nuget.exe'
$packagesDir = Join-Path $repoRoot 'packages'
$samplesRoot = Join-Path $repoRoot 'Samples'

Initialize-NuGetEnvironment
$solutions = Get-Solutions -SampleFilter $Sample

if (-not $solutions -or $solutions.Count -eq 0) {
Write-Warning 'No solution files found to build.'
exit 0
}

$stopwatch = [System.Diagnostics.Stopwatch]::StartNew()
$startTime = Get-Date

Write-Host "Building $($solutions.Count) solution(s) for Platform=$Platform Configuration=$Configuration" -ForegroundColor Green
foreach ($sln in $solutions) {
Write-Host '---' -ForegroundColor Cyan
Restore-Solution -SolutionPath $sln.FullName
Build-Solution -SolutionPath $sln.FullName -Platform $Platform -Configuration $Configuration
}

$stopwatch.Stop()
$endTime = Get-Date

Write-Host '---'
Write-Host ("Start time: {0}. End time: {1}" -f $startTime.ToLongTimeString(), $endTime.ToLongTimeString())
Write-Host (" Elapsed: {0}" -f [System.String]::Format('{0:hh\\:mm\\:ss\.ff}', $stopwatch.Elapsed))