/ .pipelines / UpdateVersions.ps1
UpdateVersions.ps1
  1  Param(
  2    # Using the default value of 1.7 for winAppSdkVersionNumber and useExperimentalVersion as false
  3    [Parameter(Mandatory=$False,Position=1)]
  4    [string]$winAppSdkVersionNumber = "1.8",
  5  
  6    # When the pipeline calls the PS1 file, the passed parameters are converted to string type
  7    [Parameter(Mandatory=$False,Position=2)]
  8    [boolean]$useExperimentalVersion = $False,
  9  
 10    # Root folder Path for processing
 11    [Parameter(Mandatory=$False,Position=3)]
 12    [string]$rootPath = $(Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)),
 13  
 14    # Root folder Path for processing
 15    [Parameter(Mandatory=$False,Position=4)]
 16    [string]$sourceLink = "https://microsoft.pkgs.visualstudio.com/ProjectReunion/_packaging/Project.Reunion.nuget.internal/nuget/v3/index.json"
 17  )
 18  
 19  
 20  
 21  function Read-FileWithEncoding {
 22      param (
 23          [string]$Path
 24      )
 25  
 26      $reader = New-Object System.IO.StreamReader($Path, $true)  # auto-detect encoding
 27      $content = $reader.ReadToEnd()
 28      $encoding = $reader.CurrentEncoding
 29      $reader.Close()
 30  
 31      return [PSCustomObject]@{
 32          Content  = $content
 33          Encoding = $encoding
 34      }
 35  }
 36  
 37  function Write-FileWithEncoding {
 38      param (
 39          [string]$Path,
 40          [string]$Content,
 41          [System.Text.Encoding]$Encoding
 42      )
 43  
 44      $writer = New-Object System.IO.StreamWriter($Path, $false, $Encoding)
 45      $writer.Write($Content)
 46      $writer.Close()
 47  }
 48  
 49  
 50  function Add-NuGetSourceAndMapping {
 51      param (
 52          [xml]$Xml,
 53          [string]$Key,
 54          [string]$Value,
 55          [string[]]$Patterns
 56      )
 57  
 58      # Ensure packageSources exists
 59      if (-not $Xml.configuration.packageSources) {
 60          $Xml.configuration.AppendChild($Xml.CreateElement("packageSources")) | Out-Null
 61      }
 62      $sources = $Xml.configuration.packageSources
 63  
 64      # Add/Update Source
 65      $sourceNode = $sources.SelectSingleNode("add[@key='$Key']")
 66      if (-not $sourceNode) {
 67          $sourceNode = $Xml.CreateElement("add")
 68          $sourceNode.SetAttribute("key", $Key)
 69          $sources.AppendChild($sourceNode) | Out-Null
 70      }
 71      $sourceNode.SetAttribute("value", $Value)
 72  
 73      # Ensure packageSourceMapping exists
 74      if (-not $Xml.configuration.packageSourceMapping) {
 75          $Xml.configuration.AppendChild($Xml.CreateElement("packageSourceMapping")) | Out-Null
 76      }
 77      $mapping = $Xml.configuration.packageSourceMapping
 78  
 79      # Remove invalid packageSource nodes (missing key or empty key)
 80      $invalidNodes = $mapping.SelectNodes("packageSource[not(@key) or @key='']")
 81      if ($invalidNodes) {
 82          foreach ($node in $invalidNodes) {
 83              $mapping.RemoveChild($node) | Out-Null
 84          }
 85      }
 86  
 87      # Add/Update Mapping Source
 88      $mappingSource = $mapping.SelectSingleNode("packageSource[@key='$Key']")
 89      if (-not $mappingSource) {
 90          $mappingSource = $Xml.CreateElement("packageSource")
 91          $mappingSource.SetAttribute("key", $Key)
 92          # Insert at top for priority
 93          if ($mapping.HasChildNodes) {
 94              $mapping.InsertBefore($mappingSource, $mapping.FirstChild) | Out-Null
 95          } else {
 96              $mapping.AppendChild($mappingSource) | Out-Null
 97          }
 98      }
 99      
100      # Double check and force attribute
101      if (-not $mappingSource.HasAttribute("key")) {
102           $mappingSource.SetAttribute("key", $Key)
103      }
104  
105      # Update Patterns
106      # RemoveAll() removes all child nodes AND attributes, so we must re-set the key afterwards
107      $mappingSource.RemoveAll()
108      $mappingSource.SetAttribute("key", $Key)
109  
110      foreach ($pattern in $Patterns) {
111          $pkg = $Xml.CreateElement("package")
112          $pkg.SetAttribute("pattern", $pattern)
113          $mappingSource.AppendChild($pkg) | Out-Null
114      }
115  }
116  
117  function Resolve-WinAppSdkSplitDependencies {
118      Write-Host "Version $WinAppSDKVersion detected. Resolving split dependencies..."
119      $installDir = Join-Path $rootPath "localpackages\output"
120      New-Item -ItemType Directory -Path $installDir -Force | Out-Null
121  
122      # Create a temporary nuget.config to avoid interference from the repo's config
123      $tempConfig = Join-Path $env:TEMP "nuget_$(Get-Random).config"
124      Set-Content -Path $tempConfig -Value "<?xml version='1.0' encoding='utf-8'?><configuration><packageSources><clear /><add key='TempSource' value='$sourceLink' /></packageSources></configuration>"
125  
126      try {
127          # Extract BuildTools version from Directory.Packages.props to ensure we have the required version
128          $dirPackagesProps = Join-Path $rootPath "Directory.Packages.props"
129          if (Test-Path $dirPackagesProps) {
130              $propsContent = Get-Content $dirPackagesProps -Raw
131              if ($propsContent -match '<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="([^"]+)"') {
132                  $buildToolsVersion = $Matches[1]
133                  Write-Host "Downloading Microsoft.Windows.SDK.BuildTools version $buildToolsVersion..."
134                  $nugetArgsBuildTools = "install Microsoft.Windows.SDK.BuildTools -Version $buildToolsVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
135                  Invoke-Expression "nuget $nugetArgsBuildTools" | Out-Null
136              }
137          }
138  
139          # Download package to inspect nuspec and keep it for the build
140          $nugetArgs = "install Microsoft.WindowsAppSDK -Version $WinAppSDKVersion -ConfigFile $tempConfig -OutputDirectory $installDir -NonInteractive -NoCache"
141          Invoke-Expression "nuget $nugetArgs" | Out-Null
142  
143          # Parse dependencies from the installed folders
144          # Folder structure is typically {PackageId}.{Version}
145          $directories = Get-ChildItem -Path $installDir -Directory
146          $allLocalPackages = @()
147          foreach ($dir in $directories) {
148              # Match any package pattern: PackageId.Version
149              if ($dir.Name -match "^(.+?)\.(\d+\..*)$") {
150                  $pkgId = $Matches[1]
151                  $pkgVer = $Matches[2]
152                  $allLocalPackages += $pkgId
153  
154                  $packageVersions[$pkgId] = $pkgVer
155                  Write-Host "Found dependency: $pkgId = $pkgVer"
156              }
157          }
158  
159          # Update repo's nuget.config to use localpackages
160          $nugetConfig = Join-Path $rootPath "nuget.config"
161          $configData = Read-FileWithEncoding -Path $nugetConfig
162          [xml]$xml = $configData.Content
163  
164          Add-NuGetSourceAndMapping -Xml $xml -Key "localpackages" -Value $installDir -Patterns $allLocalPackages
165  
166          $xml.Save($nugetConfig)
167          Write-Host "Updated nuget.config with localpackages mapping."
168      } catch {
169          Write-Warning "Failed to resolve dependencies: $_"
170      } finally {
171          Remove-Item $tempConfig -Force -ErrorAction SilentlyContinue
172      }
173  }
174  
175  # Execute nuget list and capture the output
176  if ($useExperimentalVersion) {
177      # The nuget list for experimental versions will cost more time
178      # So, we will not use -AllVersions to wast time
179      # But it can only get the latest experimental version
180      Write-Host "Fetching WindowsAppSDK with experimental versions"
181      $nugetOutput = nuget list Microsoft.WindowsAppSDK `
182          -Source  $sourceLink `
183          -Prerelease
184      # Filter versions based on the specified version prefix
185      $escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
186      $filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
187      $latestVersions = $filteredVersions
188  } else {
189      Write-Host "Fetching stable WindowsAppSDK versions for $winAppSdkVersionNumber"
190      $nugetOutput = nuget list Microsoft.WindowsAppSDK `
191          -Source $sourceLink `
192          -AllVersions
193      # Filter versions based on the specified version prefix
194      $escapedVersionNumber = [regex]::Escape($winAppSdkVersionNumber)
195      $filteredVersions = $nugetOutput | Where-Object { $_ -match "Microsoft.WindowsAppSDK $escapedVersionNumber\." }
196      $latestVersions = $filteredVersions | Sort-Object { [version]($_ -split ' ')[1] } -Descending | Select-Object -First 1
197  }
198  
199  Write-Host "Latest versions found: $latestVersions"
200  # Extract the latest version number from the output
201  $latestVersion = $latestVersions -split "`n" | `
202      Select-String -Pattern 'Microsoft.WindowsAppSDK\s*([0-9]+\.[0-9]+\.[0-9]+-*[a-zA-Z0-9]*)' | `
203      ForEach-Object { $_.Matches[0].Groups[1].Value } | `
204      Sort-Object -Descending | `
205      Select-Object -First 1
206  
207  if ($latestVersion) {
208      $WinAppSDKVersion = $latestVersion
209      Write-Host "Extracted version: $WinAppSDKVersion"
210      Write-Host "##vso[task.setvariable variable=WinAppSDKVersion]$WinAppSDKVersion"
211  } else {
212      Write-Host "Failed to extract version number from nuget list output"
213      exit 1
214  }
215  
216  # Resolve dependencies for 1.8+
217  $packageVersions = @{ "Microsoft.WindowsAppSDK" = $WinAppSDKVersion }
218  
219  Resolve-WinAppSdkSplitDependencies
220  
221  # Update Directory.Packages.props file
222  Get-ChildItem -Path $rootPath -Recurse "Directory.Packages.props" | ForEach-Object {
223      $file = Read-FileWithEncoding -Path $_.FullName
224      $content = $file.Content
225      $isModified = $false
226      
227      foreach ($pkgId in $packageVersions.Keys) {
228          $ver = $packageVersions[$pkgId]
229          # Escape dots in package ID for regex
230          $pkgIdRegex = $pkgId -replace '\.', '\.'
231          
232          $newVersionString = "<PackageVersion Include=""$pkgId"" Version=""$ver"" />"
233          $oldVersionString = "<PackageVersion Include=""$pkgIdRegex"" Version=""[-.0-9a-zA-Z]*"" />"
234  
235          if ($content -match "<PackageVersion Include=""$pkgIdRegex""") {
236              # Update existing package
237              if ($content -notmatch [regex]::Escape($newVersionString)) {
238                  $content = $content -replace $oldVersionString, $newVersionString
239                  $isModified = $true
240              }
241          }
242      }
243  
244      if ($isModified) {
245          Write-FileWithEncoding -Path $_.FullName -Content $content -Encoding $file.encoding
246          Write-Host "Modified " $_.FullName
247      }
248  }