Team Ai
Datasetpublic

MegaBites-AI/Windows-powershell

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes372downloads
findMissingNotices.ps1663 linesDownload Raw Back to tools
1# Copyright (c) Microsoft Corporation.2# Licensed under the MIT License.3 4# This script is used to completely rebuild the cgmanifgest.json file,5# which is used to generate the notice file.6# Requires the module dotnet.project.assets from the PowerShell Gallery authored by @TravisEz137 8param(9    [switch] $Fix,10    [switch] $IsStable,11    [switch] $ForceHarvestedOnly12)13 14Import-Module dotnet.project.assets15Import-Module "$PSScriptRoot\..\.github\workflows\GHWorkflowHelper" -Force16. "$PSScriptRoot\..\tools\buildCommon\startNativeExecution.ps1"17. "$PSScriptRoot\clearlyDefined\Find-LastHarvestedVersion.ps1"18 19$targetsConfigPath = Join-Path -Path $PSScriptRoot -ChildPath 'findMissingNotices.targets.json'20if (-not (Test-Path -LiteralPath $targetsConfigPath)) {21    throw "Missing target framework config file '$targetsConfigPath'. Add '/tools/findMissingNotices.targets.json' with 'dotnetTargetName' and 'windowsTargetNames' entries."22}23 24try {25    $targetsConfig = Get-Content -LiteralPath $targetsConfigPath -Raw -ErrorAction Stop | ConvertFrom-Json -AsHashtable -ErrorAction Stop26} catch {27    throw "Failed to load target framework config from '$targetsConfigPath'. Ensure the file contains valid JSON. Error: $($_.Exception.Message)"28}29 30if ($targetsConfig -isnot [hashtable]) {31    throw "Invalid target framework config '$targetsConfigPath': expected a JSON object with 'dotnetTargetName' and 'windowsTargetNames'."32}33 34if (-not $targetsConfig.ContainsKey('dotnetTargetName') -or [string]::IsNullOrWhiteSpace($targetsConfig['dotnetTargetName'])) {35    throw "Invalid target framework config '$targetsConfigPath': 'dotnetTargetName' must be a non-empty string."36}37 38if (-not $targetsConfig.ContainsKey('windowsTargetNames')) {39    throw "Invalid target framework config '$targetsConfigPath': 'windowsTargetNames' must be present and must be an array."40}41 42if ($null -eq $targetsConfig['windowsTargetNames'] -or $targetsConfig['windowsTargetNames'] -isnot [array]) {43    throw "Invalid target framework config '$targetsConfigPath': 'windowsTargetNames' must be an array (empty array is allowed)."44}45 46$script:dotnetTargetName = [string]$targetsConfig['dotnetTargetName']47$script:windowsTargetNames = @()48foreach ($windowsTargetName in $targetsConfig['windowsTargetNames']) {49    if ($windowsTargetName -isnot [string] -or [string]::IsNullOrWhiteSpace($windowsTargetName)) {50        throw "Invalid target framework config '$targetsConfigPath': every entry in 'windowsTargetNames' must be a non-empty string."51    }52 53    $script:windowsTargetNames += $windowsTargetName54}55 56# Empty windowsTargetNames is valid and means "use base target fallback only".57 58$packageSourceName = 'findMissingNoticesNugetOrg'59if (!(Get-PackageSource -Name $packageSourceName -ErrorAction SilentlyContinue)) {60    $null = Register-PackageSource -Name $packageSourceName -Location https://www.nuget.org/api/v2 -ProviderName NuGet61}62 63$existingRegistrationTable = @{}64$cgManifestPath = (Resolve-Path -Path $PSScriptRoot\cgmanifest\main\cgmanifest.json).ProviderPath65$existingRegistrationsJson = Get-Content $cgManifestPath | ConvertFrom-Json -AsHashtable66$existingRegistrationsJson.Registrations | ForEach-Object {67    $registration = [Registration]$_68    if ($registration.Component) {69        $name = $registration.Component.Name()70        if (!$existingRegistrationTable.ContainsKey($name)) {71            $existingRegistrationTable.Add($name, $registration)72        }73    }74}75 76Class Registration {77    [Component]$Component78    [bool]$DevelopmentDependency79}80 81Class Component {82    [ValidateSet("nuget")]83    [String] $Type84    [Nuget]$Nuget85 86    [string]ToString() {87        $message = "Type: $($this.Type)"88        if ($this.Type -eq "nuget") {89            $message += "; $($this.Nuget)"90        }91        return $message92    }93 94    [string]Name() {95        switch ($this.Type) {96            "nuget" {97                return $($this.Nuget.Name)98            }99            default {100                throw "Unknown component type: $($this.Type)"101            }102        }103        throw "How did we get here?!?"104    }105 106    [string]Version() {107        switch ($this.Type) {108            "nuget" {109                return $($this.Nuget.Version)110            }111            default {112                throw "Unknown component type: $($this.Type)"113            }114        }115        throw "How did we get here?!?"116    }117}118 119Class Nuget {120    [string]$Name121    [string]$Version122 123    [string]ToString() {124        return "$($this.Name) - $($this.Version)"125    }126}127 128$winDesktopSdk = 'Microsoft.NET.Sdk.WindowsDesktop'129if (!$IsWindows) {130    $winDesktopSdk = 'Microsoft.NET.Sdk'131    Write-Warning "Always using $winDesktopSdk since this is not windows!!!"132}133 134function ConvertTo-SemVer {135    param(136        [String] $Version137    )138 139    [System.Management.Automation.SemanticVersion]$desiredVersion = [System.Management.Automation.SemanticVersion]::Empty140 141    try {142        $desiredVersion = $Version143    } catch {144        <#145            Json.More.Net broke the rules and published 2.0.1.2 as 2.0.1.146            So, I'm making the logic work for that scenario by147            thorwing away any part that doesn't match non-pre-release semver portion148        #>149        $null = $Version -match '^(\d+\.\d+\.\d+).*'150        $desiredVersion = $matches[1]151    }152 153    return $desiredVersion154}155 156function New-NugetComponent {157    param(158        [string]$name,159        [string]$version,160        [switch]$DevelopmentDependency161    )162 163    $nuget = [Nuget]@{164        Name    = $name165        Version = $version166    }167    $Component = [Component]@{168        Type  = "nuget"169        Nuget = $nuget170    }171 172    $registration = [Registration]@{173        Component             = $Component174        DevelopmentDependency = $DevelopmentDependency175    }176 177    return $registration178}179 180$nugetPublicVersionCache = [System.Collections.Generic.Dictionary[string, string]]::new()181function Get-NuGetPublicVersion {182    param(183        [parameter(Mandatory)]184        [string]$Name,185        [parameter(Mandatory)]186        [string]$Version187    )188 189    if($nugetPublicVersionCache.ContainsKey($Name)) {190        return $nugetPublicVersionCache[$Name]191    }192 193    [System.Management.Automation.SemanticVersion]$desiredVersion = ConvertTo-SemVer -Version $Version194 195    $publicVersion = $null196    $publicVersion = Find-Package -Name $Name -AllowPrereleaseVersions -source $packageSourceName -AllVersions -ErrorAction SilentlyContinue | ForEach-Object {197        [System.Management.Automation.SemanticVersion]$packageVersion = ConvertTo-SemVer -Version $_.Version198        $_ | Add-Member -Name SemVer -MemberType NoteProperty -Value $packageVersion -PassThru199    } | Where-Object {200            $_.SemVer -le $desiredVersion201        } | Sort-Object -Property semver -Descending | Select-Object -First 1 -ExpandProperty Version202 203    if(!$publicVersion) {204        Write-Warning "No public version found for $Name, using $Version"205        $publicVersion = $Version206    }207 208    if(!$nugetPublicVersionCache.ContainsKey($Name)) {209        $nugetPublicVersionCache.Add($Name, $publicVersion)210    }211 212    return $publicVersion213}214 215function Get-CGRegistrations {216    param(217        [Parameter(Mandatory)]218        [ValidateSet(219            "linux-musl-x64",220            "linux-arm",221            "linux-arm64",222            "linux-x64",223            "osx-arm64",224            "osx-x64",225            "win-arm64",226            "win-x64",227            "win-x86",228            "modules")]229        [string]$Runtime,230 231        [Parameter(Mandatory)]232        [System.Collections.Generic.Dictionary[string, Registration]] $RegistrationTable233    )234 235    $registrationChanged = $false236 237    $baseTargetName = $script:dotnetTargetName238    $unixProjectName = 'powershell-unix'239    $windowsProjectName = 'powershell-win-core'240    $actualRuntime = $Runtime241 242    switch -regex ($Runtime) {243        "alpine-.*" {244            $folder = $unixProjectName245            $target = "$baseTargetName|$Runtime"246            $neutralTarget = "$baseTargetName"247        }248        "linux-.*" {249            $folder = $unixProjectName250            $target = "$baseTargetName|$Runtime"251            $neutralTarget = "$baseTargetName"252        }253        "osx-.*" {254            $folder = $unixProjectName255            $target = "$baseTargetName|$Runtime"256            $neutralTarget = "$baseTargetName"257        }258        "win-.*" {259            $sdkToUse = $winDesktopSdk260            $folder = $windowsProjectName261            $target = "$baseTargetName|$actualRuntime"262            $neutralTarget = "$baseTargetName"263        }264        "modules" {265            $folder = "modules"266            $actualRuntime = 'linux-x64'267            $target = "$baseTargetName|$actualRuntime"268            $neutralTarget = "$baseTargetName"269        }270        Default {271            throw "Invalid runtime name: $Runtime"272        }273    }274 275    Write-Verbose "Getting registrations for $folder - $actualRuntime ..." -Verbose276    Get-PSDrive -Name $folder -ErrorAction Ignore | Remove-PSDrive277    Push-Location $PSScriptRoot\..\src\$folder278    try {279        Start-NativeExecution -VerboseOutputOnError -sb {280            dotnet restore --runtime $actualRuntime  "/property:SDKToUse=$sdkToUse"281        }282        $null = New-PADrive -Path $PSScriptRoot\..\src\$folder\obj\project.assets.json -Name $folder283 284        if ($Runtime -like "win-*") {285            # Windows target selection is optional and ordered:286            # 1. Try full Windows TFMs from config in order.287            # 2. Fall back to the base non-Windows TFM if present.288            try {289                $availableTargets = @(Get-ChildItem -Path "${folder}:/targets" -ErrorAction Stop | Select-Object -ExpandProperty Name)290            } catch {291                throw "Unable to enumerate available targets for runtime '$Runtime' in '$folder'. Ensure dotnet restore succeeded and project.assets.json contains target data. Error: $($_.Exception.Message)"292            }293 294            $selectedTargetName = $null295 296            foreach ($windowsTargetName in $script:windowsTargetNames) {297                if ($windowsTargetName -in $availableTargets) {298                    $selectedTargetName = $windowsTargetName299                    break300                }301            }302 303            if (-not $selectedTargetName -and $baseTargetName -in $availableTargets) {304                Write-Verbose "No configured windows target matched for '$Runtime'. Falling back to base target '$baseTargetName'." -Verbose305                $selectedTargetName = $baseTargetName306            }307 308            if (-not $selectedTargetName) {309                Write-Verbose "Available targets for '$folder': $($availableTargets -join ', ')" -Verbose310                if ($script:windowsTargetNames.Count -eq 0) {311                    throw "Unable to find a target for '$Runtime'. Tried fallback base target '$baseTargetName' (no windowsTargetNames configured). Ensure project.assets.json contains this target or update dotnetTargetName in '$targetsConfigPath'."312                }313 314                throw "Unable to find a target for '$Runtime'. Tried configured windowsTargetNames '$($script:windowsTargetNames -join "', '")' and fallback base target '$baseTargetName'. Update '$targetsConfigPath' with a valid windows target from the available list."315            }316 317            $target = "$selectedTargetName|$actualRuntime"318            $neutralTarget = $selectedTargetName319        }320 321        # Defensive check: non-Windows paths set targets in the switch block,322        # Windows path may override them after inspecting available assets targets.323        if (-not $target -or -not $neutralTarget) {324            throw "Unable to determine restore targets for runtime '$Runtime'."325        }326 327        try {328            $targets = Get-ChildItem -Path "${folder}:/targets/$target" -ErrorAction Stop | Where-Object { $_.Type -eq 'package' }  | select-object -ExpandProperty name329            $targets += Get-ChildItem -Path "${folder}:/targets/$neutralTarget" -ErrorAction Stop | Where-Object { $_.Type -eq 'project' }  | select-object -ExpandProperty name330        } catch {331            Get-ChildItem -Path "${folder}:/targets" | Out-String | Write-Verbose -Verbose332            throw333        }334    } finally {335        Pop-Location336        Get-PSDrive -Name $folder -ErrorAction Ignore | Remove-PSDrive337    }338 339    # Name to skip for TPN generation340    $skipNames = @(341        "Microsoft.PowerShell.Native"342        "Microsoft.Management.Infrastructure.Runtime.Unix"343        "Microsoft.Management.Infrastructure"344        "Microsoft.PowerShell.Commands.Diagnostics"345        "Microsoft.PowerShell.Commands.Management"346        "Microsoft.PowerShell.Commands.Utility"347        "Microsoft.PowerShell.ConsoleHost"348        "Microsoft.PowerShell.SDK"349        "Microsoft.PowerShell.Security"350        "Microsoft.Management.Infrastructure.CimCmdlets"351        "Microsoft.WSMan.Management"352        "Microsoft.WSMan.Runtime"353        "System.Management.Automation"354        "Microsoft.PowerShell.GraphicalHost"355        "Microsoft.PowerShell.CoreCLR.Eventing"356    )357 358    Write-Verbose "Found $($targets.Count) targets to process..." -Verbose359    $targets | ForEach-Object {360        $target = $_361        $parts = ($target -split '\|')362        $name = $parts[0]363 364        if ($name -in $skipNames) {365            Write-Verbose "Skipping $name..."366 367        } else {368            $targetVersion = $parts[1]369            $publicVersion = Get-NuGetPublicVersion -Name $name -Version $targetVersion370 371            # Add the registration to the cgmanifest if the TPN does not contain the name of the target OR372            # the exisitng CG contains the registration, because if the existing CG contains the registration,373            # that might be the only reason it is in the TPN.374            if (!$RegistrationTable.ContainsKey($target)) {375                $DevelopmentDependency = $false376                if (!$existingRegistrationTable.ContainsKey($name) -or $existingRegistrationTable.$name.Component.Version() -ne $publicVersion) {377                    $registrationChanged = $true378                }379                if ($existingRegistrationTable.ContainsKey($name) -and $existingRegistrationTable.$name.DevelopmentDependency) {380                    $DevelopmentDependency = $true381                }382 383                $registration = New-NugetComponent -Name $name -Version $publicVersion -DevelopmentDependency:$DevelopmentDependency384                $RegistrationTable.Add($target, $registration)385            }386        }387    }388 389    return $registrationChanged390}391 392$registrations = [System.Collections.Generic.Dictionary[string, Registration]]::new()393$lastCount = 0394$registrationChanged = $false395foreach ($runtime in "win-x64", "linux-x64", "osx-x64", "linux-musl-x64", "linux-arm", "linux-arm64", "osx-arm64", "win-arm64", "win-x86") {396    $registrationChanged = (Get-CGRegistrations -Runtime $runtime -RegistrationTable $registrations) -or $registrationChanged397    $count = $registrations.Count398    $newCount = $count - $lastCount399    $lastCount = $count400    Write-Verbose "$newCount new registrations, $count total..." -Verbose401}402 403$newRegistrations = $registrations.Keys | Sort-Object | ForEach-Object { $registrations[$_] }404 405if ($IsStable) {406    foreach ($registion in $newRegistrations) {407        $name = $registion.Component.Name()408        $version = $registion.Component.Version()409        $developmentDependency = $registion.DevelopmentDependency410        if ($version -match '-' -and !$developmentDependency) {411            throw "Version $version of $name is preview.  This is not allowed."412        }413    }414}415 416$count = $newRegistrations.Count417$registrationsToSave = $newRegistrations418$tpnRegistrationsToSave = $null419 420# If -ForceHarvestedOnly is specified with -Fix, only include harvested packages421# and revert non-harvested packages to their previous versions422if ($Fix -and $ForceHarvestedOnly) {423    Write-Verbose "Checking harvest status and filtering to harvested packages with reversion..." -Verbose424 425    # Import ClearlyDefined module to check harvest status426    Import-Module -Name "$PSScriptRoot/clearlyDefined/src/ClearlyDefined" -Force427 428    # Import cache from previous runs to speed up lookups429    Import-ClearlyDefinedCache430 431    # Get harvest data for all registrations432    $fullCgList = $newRegistrations |433        ForEach-Object {434            [PSCustomObject]@{435                type           = $_.Component.Type436                Name           = $_.Component.Nuget.Name437                PackageVersion = $_.Component.Nuget.Version438            }439        }440 441    $fullList = $fullCgList | Get-ClearlyDefinedData442 443    # Build a lookup table of harvest status by package name + version444    $harvestStatus = @{}445    foreach ($item in $fullList) {446        $key = "$($item.Name)|$($item.PackageVersion)"447        $harvestStatus[$key] = $item.harvested448    }449 450    # Build a lookup table of old versions from existing manifest451    $oldVersions = @{}452    foreach ($registration in $existingRegistrationsJson.Registrations) {453        $name = $registration.Component.Nuget.Name454        if (!$oldVersions.ContainsKey($name)) {455            $oldVersions[$name] = $registration456        }457    }458 459    # Process each new registration: keep harvested, revert non-harvested460    $tpnRegistrationsToSave = @()461    $harvestedCount = 0462    $revertedCount = 0463 464    foreach ($reg in $newRegistrations) {465        $name = $reg.Component.Nuget.Name466        $version = $reg.Component.Nuget.Version467        $key = "$name|$version"468 469        if ($harvestStatus.ContainsKey($key) -and $harvestStatus[$key]) {470            # Package is harvested, include it471            $tpnRegistrationsToSave += $reg472            $harvestedCount++473        } else {474            # Package not harvested, find last harvested version475            $lastHarvestedVersion = Find-LastHarvestedVersion -Name $name -CurrentVersion $version476 477            # Use last harvested version if found, otherwise use old version as fallback478            if ($lastHarvestedVersion) {479                if ($lastHarvestedVersion -ne $version) {480                    $revertedReg = New-NugetComponent -Name $name -Version $lastHarvestedVersion -DevelopmentDependency:$reg.DevelopmentDependency481                    $tpnRegistrationsToSave += $revertedReg482                    $revertedCount++483                    Write-Verbose "Reverted $name from v$version to last harvested v$lastHarvestedVersion" -Verbose484                } else {485                    $tpnRegistrationsToSave += $reg486                }487            } elseif ($oldVersions.ContainsKey($name)) {488                $tpnRegistrationsToSave += $oldVersions[$name]489                $revertedCount++490                Write-Verbose "Reverted $name to previous version (no harvested version found)" -Verbose491            } else {492                Write-Warning "$name v$version not harvested and no previous version found. Excluding from manifest."493            }494        }495    }496 497    Write-Verbose "Completed filtering for TPN: $harvestedCount harvested + $revertedCount reverted = $($tpnRegistrationsToSave.Count) total" -Verbose498}499 500$newJson = @{501    Registrations = $registrationsToSave502     '$schema' = "https://json.schemastore.org/component-detection-manifest.json"503} | ConvertTo-Json -depth 99504 505if ($Fix -and $registrationChanged) {506    $newJson | Set-Content $cgManifestPath507    Set-GWVariable -Name CGMANIFEST_PATH -Value $cgManifestPath508}509 510# If -ForceHarvestedOnly was used, write the TPN manifest with filtered registrations511if ($Fix -and $ForceHarvestedOnly -and $tpnRegistrationsToSave.Count -gt 0) {512    $tpnManifestDir = Join-Path -Path $PSScriptRoot -ChildPath "cgmanifest\tpn"513    New-Item -ItemType Directory -Path $tpnManifestDir -Force | Out-Null514    $tpnManifestPath = Join-Path -Path $tpnManifestDir -ChildPath "cgmanifest.json"515 516    $tpnManifest = @{517        Registrations = @($tpnRegistrationsToSave)518        '$schema'     = "https://json.schemastore.org/component-detection-manifest.json"519    }520 521    $tpnJson = $tpnManifest | ConvertTo-Json -depth 99522    $tpnJson | Set-Content $tpnManifestPath -Encoding utf8NoBOM523    Write-Verbose "TPN manifest created/updated with $($tpnRegistrationsToSave.Count) registrations (filtered for harvested packages)" -Verbose524}525 526# Skip legacy TPN update when -ForceHarvestedOnly already produced a filtered manifest527if ($Fix -and $registrationChanged -and -not $ForceHarvestedOnly) {528    # Import ClearlyDefined module to check harvest status529    Write-Verbose "Checking harvest status for newly added packages..." -Verbose530    Import-Module -Name "$PSScriptRoot/clearlyDefined/src/ClearlyDefined" -Force531 532    # Get harvest data for all registrations533    $fullCgList = $newRegistrations |534        ForEach-Object {535            [PSCustomObject]@{536                type           = $_.Component.Type537                Name           = $_.Component.Nuget.Name538                PackageVersion = $_.Component.Nuget.Version539            }540        }541 542    $fullList = $fullCgList | Get-ClearlyDefinedData543    $needHarvest = $fullList | Where-Object { !$_.harvested }544 545    if ($needHarvest.Count -gt 0) {546        Write-Verbose "Found $($needHarvest.Count) packages that need harvesting. Starting harvest..." -Verbose547        $needHarvest | Select-Object -ExpandProperty coordinates | ConvertFrom-ClearlyDefinedCoordinates | Start-ClearlyDefinedHarvest548    } else {549        Write-Verbose "All packages are already harvested." -Verbose550    }551 552    # After manifest update and harvest, update TPN manifest with individual package status553    Write-Verbose "Updating TPN manifest with individual package harvest status..." -Verbose554    $tpnManifestDir = Join-Path -Path $PSScriptRoot -ChildPath "cgmanifest\tpn"555    $tpnManifestPath = Join-Path -Path $tpnManifestDir -ChildPath "cgmanifest.json"556 557    # Load current TPN manifest to get previous versions558    $currentTpnManifest = @()559    if (Test-Path $tpnManifestPath) {560        $currentTpnJson = Get-Content $tpnManifestPath | ConvertFrom-Json -AsHashtable561        $currentTpnManifest = $currentTpnJson.Registrations562    }563 564    # Build a lookup table of old versions565    $oldVersions = @{}566    foreach ($registration in $currentTpnManifest) {567        $name = $registration.Component.Nuget.Name568        if (!$oldVersions.ContainsKey($name)) {569            $oldVersions[$name] = $registration570        }571    }572 573    # Note: Do not recheck harvest status here. Harvesting is an async process that takes a significant amount of time.574    # Use the harvest data from the initial check. Newly triggered harvests will be captured575    # on the next run of this script after harvesting completes.576    $finalHarvestData = $fullList577 578    # Update packages individually based on harvest status579    $tpnRegistrations = @()580    $harvestedCount = 0581    $restoredCount = 0582 583    foreach ($item in $finalHarvestData) {584        $matchingNewRegistration = $newRegistrations | Where-Object {585            $_.Component.Nuget.Name -eq $item.Name -and586            $_.Component.Nuget.Version -eq $item.PackageVersion587        }588 589        if ($matchingNewRegistration) {590            if ($item.harvested) {591                # Use new harvested version592                $tpnRegistrations += $matchingNewRegistration593                $harvestedCount++594            } else {595                # Package not harvested - find the last harvested version from ClearlyDefined API596                Write-Verbose "Finding last harvested version for $($item.Name)..." -Verbose597 598                $lastHarvestedVersion = $null599                try {600                    # Search through all versions of this package to find the last harvested one601                    # Create a list of versions we know about from all runtimes602                    $packageVersionsToCheck = $newRegistrations | Where-Object {603                        $_.Component.Nuget.Name -eq $item.Name604                    } | ForEach-Object { $_.Component.Nuget.Version } | Sort-Object -Unique -Descending605 606                    foreach ($versionToCheck in $packageVersionsToCheck) {607                        $versionCheckList = [PSCustomObject]@{608                            type           = "nuget"609                            Name           = $item.Name610                            PackageVersion = $versionToCheck611                        }612 613                        $versionStatus = $versionCheckList | Get-ClearlyDefinedData614                        if ($versionStatus -and $versionStatus.harvested) {615                            $lastHarvestedVersion = $versionToCheck616                            break  # Found the most recent harvested version617                        }618                    }619                } catch {620                    Write-Verbose "Error checking harvested versions for $($item.Name): $_" -Verbose621                }622 623                # Use last harvested version if found, otherwise use old version as fallback624                if ($lastHarvestedVersion) {625                    $revertedReg = New-NugetComponent -Name $item.Name -Version $lastHarvestedVersion -DevelopmentDependency:$matchingNewRegistration.DevelopmentDependency626                    $tpnRegistrations += $revertedReg627                    $restoredCount++628                    Write-Verbose "Reverted $($item.Name) from v$($item.PackageVersion) to last harvested v$lastHarvestedVersion" -Verbose629                } elseif ($oldVersions.ContainsKey($item.Name)) {630                    $tpnRegistrations += $oldVersions[$item.Name]631                    $restoredCount++632                    Write-Verbose "Reverted $($item.Name) to previous version in TPN (no harvested version found)" -Verbose633                } else {634                    Write-Warning "$($item.Name) v$($item.PackageVersion) not harvested and no harvested version found. Excluding from TPN manifest."635                }636            }637        }638    }639 640    # Save updated TPN manifest641    if ($tpnRegistrations.Count -gt 0) {642        $tpnManifest = @{643            Registrations = @($tpnRegistrations)644            '$schema'     = "https://json.schemastore.org/component-detection-manifest.json"645        }646 647        $tpnJson = $tpnManifest | ConvertTo-Json -depth 99648        $tpnJson | Set-Content $tpnManifestPath -Encoding utf8NoBOM649        Write-Verbose "TPN manifest updated: $harvestedCount new harvested + $restoredCount reverted to last harvested versions" -Verbose650    }651}652 653if (!$Fix -and $registrationChanged) {654    $temp = Get-GWTempPath655 656    $tempJson = Join-Path -Path $temp -ChildPath "cgmanifest$((Get-Date).ToString('yyyMMddHHmm')).json"657    $newJson | Set-Content $tempJson -Encoding utf8NoBOM658    Set-GWVariable -Name CGMANIFEST_PATH -Value $tempJson659    throw "cgmanifest is out of date.  run ./tools/findMissingNotices.ps1 -Fix.  Generated cgmanifest is here: $tempJson"660}661 662Write-Verbose "$count registrations created!" -Verbose663