Param( # Using the default value of 1.7 for winAppSdkVersionNumber and useExperimentalVersion as false [Parameter(Mandatory=$False,Position=1)] [string]$winAppSdkVersionNumber = "1.8", # When the pipeline calls the PS1 file, the passed parameters are converted to string type [Parameter(Mandatory=$False,Position=2)] [boolean]$useExperimentalVersion = $False, # Root folder Path for processing [Parameter(Mandatory=$False,Position=3)] [string]$rootPath = $(Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)), # Root folder Path for processing [Parameter(Mandatory=$False,Position=4)] [string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json", # Use Azure Pipeline artifact as source for metapackage [Parameter(Mandatory=$False,Position=5)] [boolean]$useArtifactSource = $False, # Azure DevOps organization URL [Parameter(Mandatory=$False,Position=6)] [string]$azureDevOpsOrg = "https://dev.azure.com/microsoft", # Azure DevOps project name [Parameter(Mandatory=$False,Position=7)] [string]$azureDevOpsProject = "ProjectReunion", # Pipeline build ID (or "latest" for latest build) [Parameter(Mandatory=$False,Position=8)] [string]$buildId = "", # Artifact name containing the NuGet packages [Parameter(Mandatory=$False,Position=9)] [string]$artifactName = "WindowsAppSDK_Nuget_And_MSIX", # Metapackage name to look for in artifact [Parameter(Mandatory=$False,Position=10)] [string]$metaPackageName = "Microsoft.WindowsAppSDK" ) # Script-level constants $script:PackageVersionRegex = '^(.+?)\.(\d+\..*)$' function Read-FileWithEncoding { param ( [string]$Path ) $reader = New-Object System.IO.StreamReader($Path, $true) # auto-detect encoding $content = $reader.ReadToEnd() $encoding = $reader.CurrentEncoding $reader.Close() return [PSCustomObject]@{ Content = $content Encoding = $encoding } } function Write-FileWithEncoding { param ( [string]$Path, [string]$Content, [System.Text.Encoding]$Encoding ) $writer = New-Object System.IO.StreamWriter($Path, $false, $Encoding) $writer.Write($Content) $writer.Close() } function Add-NuGetSourceAndMapping { param ( [xml]$Xml, [string]$Key, [string]$Value, [string[]]$Patterns ) # Ensure packageSources exists if (-not $Xml.configuration.packageSources) { $null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSources")) } $sources = $Xml.configuration.packageSources # Add/Update Source $sourceNode = $sources.SelectSingleNode("add[@key='$Key']") if (-not $sourceNode) { $sourceNode = $Xml.CreateElement("add") $sourceNode.SetAttribute("key", $Key) $null = $sources.AppendChild($sourceNode) } $sourceNode.SetAttribute("value", $Value) # Ensure packageSourceMapping exists if (-not $Xml.configuration.packageSourceMapping) { $null = $Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping")) } $mapping = $Xml.configuration.packageSourceMapping # Remove invalid packageSource nodes (missing key or empty key) $invalidNodes = $mapping.SelectNodes("packageSource[not(@key) or @key='']") if ($invalidNodes) { foreach ($node in $invalidNodes) { $null = $mapping.RemoveChild($node) } } # Add/Update Mapping Source $mappingSource = $mapping.SelectSingleNode("packageSource[@key='$Key']") if (-not $mappingSource) { $mappingSource = $Xml.CreateElement("packageSource") $mappingSource.SetAttribute("key", $Key) # Insert at top for priority if ($mapping.HasChildNodes) { $null = $mapping.InsertBefore($mappingSource, $mapping.FirstChild) } else { $null = $mapping.AppendChild($mappingSource) } } # Double check and force attribute if (-not $mappingSource.HasAttribute("key")) { $mappingSource.SetAttribute("key", $Key) } # Update Patterns # RemoveAll() removes all child nodes AND attributes, so we must re-set the key afterwards $mappingSource.RemoveAll() $mappingSource.SetAttribute("key", $Key) foreach ($pattern in $Patterns) { $pkg = $Xml.CreateElement("package") $pkg.SetAttribute("pattern", $pattern) $null = $mappingSource.AppendChild($pkg) } } function Download-ArtifactFromPipeline { param ( [string]$Organization, [string]$Project, [string]$BuildId, [string]$ArtifactName, [string]$OutputDir ) Write-Host "Downloading artifact '$ArtifactName' from build $BuildId..." $null = New-Item -ItemType Directory -Path $OutputDir -Force try { # Authenticate with Azure DevOps using System Access Token (if available) if ($env:SYSTEM_ACCESSTOKEN) { Write-Host "Authenticating with Azure DevOps using System Access Token..." $env:AZURE_DEVOPS_EXT_PAT = $env:SYSTEM_ACCESSTOKEN } else { Write-Host "No SYSTEM_ACCESSTOKEN found, assuming az CLI is already authenticated..." } # Use az CLI to download artifact & az pipelines runs artifact download ` --organization $Organization ` --project $Project ` --run-id $BuildId ` --artifact-name $ArtifactName ` --path $OutputDir if ($LASTEXITCODE -eq 0) { Write-Host "Successfully downloaded artifact to $OutputDir" return $true } else { Write-Warning "Failed to download artifact. Exit code: $LASTEXITCODE" return $false } } catch { Write-Warning "Error downloading artifact: $_" return $false } } function Get-NuspecDependencies { param ( [string]$NupkgPath, [string]$TargetFramework = "" ) $tempDir = Join-Path $env:TEMP "nuspec_parse_$(Get-Random)" try { # Extract .nupkg (it's a zip file) # Workaround: Expand-Archive may not recognize .nupkg extension, so copy to .zip first $tempZip = Join-Path $env:TEMP "temp_$(Get-Random).zip" Copy-Item $NupkgPath -Destination $tempZip -Force Expand-Archive -Path $tempZip -DestinationPath $tempDir -Force Remove-Item $tempZip -Force -ErrorAction SilentlyContinue # Find .nuspec file $nuspecFile = Get-ChildItem -Path $tempDir -Filter "*.nuspec" -Recurse | Select-Object -First 1 if (-not $nuspecFile) { Write-Warning "No .nuspec file found in $NupkgPath" return @{} } [xml]$nuspec = Get-Content $nuspecFile.FullName # Extract package info $packageId = $nuspec.package.metadata.id $version = $nuspec.package.metadata.version Write-Host "Parsing $packageId version $version" # Parse dependencies $dependencies = @{} $depGroups = $nuspec.package.metadata.dependencies.group if ($depGroups) { # Dependencies are grouped by target framework foreach ($group in $depGroups) { $fx = $group.targetFramework Write-Host " Target Framework: $fx" foreach ($dep in $group.dependency) { $depId = $dep.id $depVer = $dep.version # Remove version range brackets if present (e.g., "[2.0.0]" -> "2.0.0") $depVer = $depVer -replace '[\[\]]', '' $dependencies[$depId] = $depVer Write-Host " - ${depId} : ${depVer}" } } } else { # No grouping, direct dependencies $deps = $nuspec.package.metadata.dependencies.dependency if ($deps) { foreach ($dep in $deps) { $depId = $dep.id $depVer = $dep.version $depVer = $depVer -replace '[\[\]]', '' $dependencies[$depId] = $depVer Write-Host " - ${depId} : ${depVer}" } } } return $dependencies } catch { Write-Warning "Failed to parse nuspec: $_" return @{} } finally { Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue } } function Resolve-ArtifactBasedDependencies { param ( [string]$ArtifactDir, [string]$MetaPackageName, [string]$SourceUrl, [string]$OutputDir ) Write-Host "Resolving dependencies from artifact-based metapackage..." $null = New-Item -ItemType Directory -Path $OutputDir -Force # Find the metapackage in artifact $metaNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.*.nupkg" | Where-Object { $_.Name -notmatch "Runtime" } | Select-Object -First 1 if (-not $metaNupkg) { Write-Warning "Metapackage $MetaPackageName not found in artifact" return @{} } # Extract version from filename if ($metaNupkg.Name -match "$MetaPackageName\.(.+)\.nupkg") { $metaVersion = $Matches[1] Write-Host "Found metapackage: $MetaPackageName version $metaVersion" } else { Write-Warning "Could not extract version from $($metaNupkg.Name)" return @{} } # Parse dependencies from metapackage $dependencies = Get-NuspecDependencies -NupkgPath $metaNupkg.FullName # Copy metapackage to output directory Copy-Item $metaNupkg.FullName -Destination $OutputDir -Force Write-Host "Copied metapackage to $OutputDir" # Prepare package versions hashtable - initialize with metapackage version $packageVersions = @{ $MetaPackageName = $metaVersion } # Copy Runtime package from artifact (it's not in feed) and extract its version $runtimeNupkg = Get-ChildItem -Path $ArtifactDir -Recurse -Filter "$MetaPackageName.Runtime.*.nupkg" | Select-Object -First 1 if ($runtimeNupkg) { Copy-Item $runtimeNupkg.FullName -Destination $OutputDir -Force Write-Host "Copied Runtime package to $OutputDir" # Extract version from Runtime package filename if ($runtimeNupkg.Name -match "$MetaPackageName\.Runtime\.(.+)\.nupkg") { $runtimeVersion = $Matches[1] $packageVersions["$MetaPackageName.Runtime"] = $runtimeVersion Write-Host "Extracted Runtime package version: $runtimeVersion" } else { Write-Warning "Could not extract version from Runtime package: $($runtimeNupkg.Name)" } } # Download other dependencies from feed (excluding Runtime as it's already copied) # Create temp nuget.config that includes both local packages and remote feed # This allows NuGet to find packages already copied from artifact $tempConfig = Join-Path $env:TEMP "nuget_artifact_$(Get-Random).config" $tempConfigContent = @" "@ Set-Content -Path $tempConfig -Value $tempConfigContent try { foreach ($depId in $dependencies.Keys) { # Skip Runtime as it's already copied from artifact if ($depId -like "*Runtime*") { # Don't overwrite the version we extracted from the Runtime package filename if (-not $packageVersions.ContainsKey($depId)) { $packageVersions[$depId] = $dependencies[$depId] } Write-Host "Skipping $depId (already in artifact)" continue } $depVersion = $dependencies[$depId] Write-Host "Downloading dependency: $depId version $depVersion from feed..." & nuget install $depId ` -Version $depVersion ` -ConfigFile $tempConfig ` -OutputDirectory $OutputDir ` -NonInteractive ` -NoCache ` | Out-Null if ($LASTEXITCODE -eq 0) { $packageVersions[$depId] = $depVersion Write-Host " Successfully downloaded $depId" } else { Write-Warning " Failed to download $depId version $depVersion" } } } finally { Remove-Item $tempConfig -Force -ErrorAction SilentlyContinue } # Parse all downloaded packages to get actual versions $directories = Get-ChildItem -Path $OutputDir -Directory $allLocalPackages = @() # Add metapackage and runtime to the list (they are .nupkg files, not directories) $allLocalPackages += $MetaPackageName if ($packageVersions.ContainsKey("$MetaPackageName.Runtime")) { $allLocalPackages += "$MetaPackageName.Runtime" } foreach ($dir in $directories) { if ($dir.Name -match $script:PackageVersionRegex) { $pkgId = $Matches[1] $pkgVer = $Matches[2] $allLocalPackages += $pkgId if (-not $packageVersions.ContainsKey($pkgId)) { $packageVersions[$pkgId] = $pkgVer } } } # Update nuget.config dynamically during pipeline execution # This modification is temporary and won't be committed back to the repo $nugetConfig = Join-Path $rootPath "nuget.config" $configData = Read-FileWithEncoding -Path $nugetConfig [xml]$xml = $configData.Content Add-NuGetSourceAndMapping -Xml $xml -Key "localpackages" -Value $OutputDir -Patterns $allLocalPackages $xml.Save($nugetConfig) Write-Host "Updated nuget.config with localpackages mapping (temporary, for pipeline execution only)." return ,$packageVersions } function Resolve-WinAppSdkSplitDependencies { Write-Host "Version $WinAppSDKVersion detected. Resolving split dependencies..." $installDir = Join-Path $rootPath "localpackages\output" $null = New-Item -ItemType Directory -Path $installDir -Force # Create a temporary nuget.config to avoid interference from the repo's config $tempConfig = Join-Path $env:TEMP "nuget_$(Get-Random).config" Set-Content -Path $tempConfig -Value "" try { # Extract BuildTools version from Directory.Packages.props to ensure we have the required version $dirPackagesProps = Join-Path $rootPath "Directory.Packages.props" if (Test-Path $dirPackagesProps) { $propsContent = Get-Content $dirPackagesProps -Raw if ($propsContent -match '" $oldVersionString = "" if ($content -match "