Skip to content
Closed
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
171 changes: 13 additions & 158 deletions eng/verify-binding-redirects.ps1
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
$ErrorActionPreference = "Stop"
Set-StrictMode -Version Latest

$script:isCI = $env:TF_BUILD -eq 'true' -or $env:CI -eq 'true'

# Verifies that binding redirects in source app.config files match the actual
# assembly versions of the DLLs shipped in the extracted nupkg packages.
#
# In CI: validates and fails with instructions to run locally.
# Locally: auto-fixes the source app.config files with the correct versions.
# Verifies that no binding redirects exist in source app.config files.
# Binding redirects are a legacy mechanism that causes version mismatches
# and should not be used. See https://github.com/microsoft/vstest/issues/15765.

# Each source app.config maps to a specific exe that ships in the packages.
$script:AppConfigs = @(
Expand All @@ -16,36 +12,6 @@ $script:AppConfigs = @(
@{ Config = "src/datacollector/app.config"; ExeName = "datacollector.exe" }
)

function Find-ExeInPackages {
param(
[string] $ExeName,
[string[]] $PackageDirs
)

foreach ($dir in $PackageDirs) {
$found = Get-ChildItem $dir -Recurse -Filter $ExeName -File -ErrorAction SilentlyContinue
if ($found) {
# Prefer the one closest to a net462 or root layout (not nested in TestHostNetFramework).
$preferred = $found | Where-Object { $_.FullName -notlike "*TestHostNetFramework*" } | Select-Object -First 1
if ($preferred) { return $preferred.DirectoryName }
return $found[0].DirectoryName
}
}

return $null
}

function Get-ManagedAssemblyVersion {
param([string] $DllPath)

try {
return [System.Reflection.AssemblyName]::GetAssemblyName($DllPath).Version.ToString()
}
catch {
return $null
}
}

function Verify-BindingRedirects {
param(
[Parameter(Mandatory)]
Expand All @@ -58,7 +24,6 @@ function Verify-BindingRedirects {

$repoRoot = Resolve-Path "$PSScriptRoot/.."
$errors = @()
$configsToFix = @{}

foreach ($entry in $script:AppConfigs) {
$configPath = Join-Path $repoRoot $entry.Config
Expand All @@ -67,145 +32,35 @@ function Verify-BindingRedirects {
continue
}

$deployDir = Find-ExeInPackages -ExeName $entry.ExeName -PackageDirs $PackageDirs
if (-not $deployDir) {
Write-Host "Skipping $($entry.ExeName): not found in any extracted package."
continue
}

Write-Host "Checking assembly redirects for $($entry.ExeName) (from '$deployDir')..."
Write-Host "Checking $($entry.Config) has no binding redirects..."

[xml]$xml = Get-Content $configPath -Raw
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xml.NameTable)
$nsMgr.AddNamespace("asm", "urn:schemas-microsoft-com:asm.v1")

# Build search directories from probing paths in the config.
$searchDirs = @($deployDir)
$probingNodes = $xml.SelectNodes("//asm:probing", $nsMgr)
foreach ($probing in $probingNodes) {
$privatePath = $probing.GetAttribute("privatePath")
if ($privatePath) {
foreach ($subPath in $privatePath -split ";") {
$probingDir = Join-Path $deployDir $subPath.Trim()
if (Test-Path $probingDir) { $searchDirs += $probingDir }
}
}
}

$dependentAssemblies = $xml.SelectNodes("//asm:dependentAssembly", $nsMgr)
foreach ($dep in $dependentAssemblies) {
$identity = $dep.SelectSingleNode("asm:assemblyIdentity", $nsMgr)
$redirect = $dep.SelectSingleNode("asm:bindingRedirect", $nsMgr)
if (-not $identity -or -not $redirect) { continue }

Comment on lines 41 to 46
$assemblyName = $identity.GetAttribute("name")
$currentNewVersion = $redirect.GetAttribute("newVersion")
$currentOldVersion = $redirect.GetAttribute("oldVersion")

# Look for the assembly DLL in each search directory.
$dllPath = $null
foreach ($dir in $searchDirs) {
$candidate = Join-Path $dir "$assemblyName.dll"
if (Test-Path $candidate) { $dllPath = $candidate; break }
}

if (-not $dllPath) {
Write-Host " $assemblyName - not found in package layout, skipping."
continue
}

$actualVersion = Get-ManagedAssemblyVersion -DllPath $dllPath
if (-not $actualVersion) {
Write-Host " $assemblyName - could not read version (native or corrupt?), skipping."
continue
}

if ($currentNewVersion -eq $actualVersion) {
Write-Host " $assemblyName - OK ($actualVersion)"
continue
}

# newVersion needs updating, and the upper bound of oldVersion range too.
$newOldVersion = $currentOldVersion
if ($currentOldVersion -match '^(.*)-(.*)$') {
$newOldVersion = "$($Matches[1])-$actualVersion"
}

$errors += "$($entry.ExeName): $assemblyName redirect newVersion is '$currentNewVersion' but actual assembly version is '$actualVersion'"

if (-not $configsToFix.ContainsKey($configPath)) {
$configsToFix[$configPath] = @()
}

$configsToFix[$configPath] += @{
AssemblyName = $assemblyName
OldNewVersion = $currentNewVersion
NewNewVersion = $actualVersion
OldOldVersion = $currentOldVersion
NewOldVersion = $newOldVersion
}

if ($script:isCI) {
Write-Host " $assemblyName - MISMATCH: expected $actualVersion, found $currentNewVersion" -ForegroundColor Red
}
else {
Write-Host " $assemblyName - FIXING: $currentNewVersion -> $actualVersion (oldVersion: $currentOldVersion -> $newOldVersion)" -ForegroundColor Yellow
}
$errors += "$($entry.Config): has binding redirect for '$assemblyName'. Remove it."
Write-Host " FAIL: found binding redirect for '$assemblyName'" -ForegroundColor Red
}
}

# Apply fixes using XML DOM with whitespace preservation.
if (-not $script:isCI) {
foreach ($configPath in $configsToFix.Keys) {
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $true
$xmlDoc.Load($configPath)

$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$nsMgr.AddNamespace("asm", "urn:schemas-microsoft-com:asm.v1")

foreach ($fix in $configsToFix[$configPath]) {
$nodes = $xmlDoc.SelectNodes("//asm:dependentAssembly[asm:assemblyIdentity[@name='$($fix.AssemblyName)']]/asm:bindingRedirect", $nsMgr)
$applied = $false
foreach ($node in $nodes) {
if ($node.GetAttribute("newVersion") -eq $fix.OldNewVersion) {
$node.SetAttribute("oldVersion", $fix.NewOldVersion)
$node.SetAttribute("newVersion", $fix.NewNewVersion)
$applied = $true
}
}

if (-not $applied) {
Write-Error "Failed to apply binding redirect fix for '$($fix.AssemblyName)' in '$configPath'. The expected redirect node was not found."
}
}

# Preserve the original BOM if present.
$bom = [System.IO.File]::ReadAllBytes($configPath)
$hasBom = $bom.Length -ge 3 -and $bom[0] -eq 0xEF -and $bom[1] -eq 0xBB -and $bom[2] -eq 0xBF
$encoding = if ($hasBom) { New-Object System.Text.UTF8Encoding($true) } else { New-Object System.Text.UTF8Encoding($false) }
$writer = New-Object System.IO.StreamWriter($configPath, $false, $encoding)
$xmlDoc.Save($writer)
$writer.Dispose()
Write-Host "Updated '$configPath'." -ForegroundColor Green
if (-not $errors) {
Write-Host " OK - no binding redirects."
}
}
Comment on lines +52 to 55

if ($errors) {
if ($script:isCI) {
$message = "Assembly binding redirect mismatches detected:`n"
$message += ($errors -join "`n")
$message += "`n`nTo fix this, run the following command locally after building and packing:`n"
$message += " .\build.cmd -c $Configuration`n"
$message += "This will rebuild, pack, and auto-update the app.config files with the correct versions.`n"
$message += "Then commit the updated app.config files."
Write-Error $message
}
else {
Write-Host "`nFixed $($errors.Count) binding redirect(s). Please commit the updated app.config files." -ForegroundColor Green
}
$message = "Binding redirects are not allowed in app.config files:`n"
$message += ($errors -join "`n")
$message += "`n`nSee https://github.com/microsoft/vstest/issues/15765 for details."
Write-Error $message
}
else {
Write-Host "All binding redirects match their DLL versions."
Write-Host "No binding redirects found in any app.config - good."
}
}
30 changes: 0 additions & 30 deletions src/datacollector/app.config
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,6 @@
<AppContextSwitchOverrides value="Switch.System.Diagnostics.IgnorePortablePDBsInStackTraces=false" />

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- Test adapters compiled against version 11-14, need to be redirected to version 15. -->
<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualStudio.TestPlatform.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="11.0.0.0-14.0.0.0" newVersion="15.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.FileSystemGlobbing" publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>

<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
38 changes: 0 additions & 38 deletions src/testhost.x86/app.config
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,6 @@
<AppContextSwitchOverrides value="Switch.System.Diagnostics.IgnorePortablePDBsInStackTraces=false" />
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<probing privatePath="Extensions" />
<!-- Test adapters compiled against version 11-14, need to be redirected to version 15. -->
<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualStudio.TestPlatform.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="11.0.0.0-14.0.0.0" newVersion="15.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualStudio.TestWindow.Interfaces" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="11.0.0.0-18.0.0.0" newVersion="18.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualStudio.QualityTools.UnitTestFramework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="10.1.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.FileSystemGlobbing" publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>

<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<system.diagnostics>
Expand Down
41 changes: 0 additions & 41 deletions src/vstest.console/app.config
Original file line number Diff line number Diff line change
Expand Up @@ -11,47 +11,6 @@
<AppContextSwitchOverrides value="Switch.System.Diagnostics.IgnorePortablePDBsInStackTraces=false" />

<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<!-- Test adapters compiled against version 11-14, need to be redirected to version 15. -->
<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualStudio.TestPlatform.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="11.0.0.0-15.0.0.0" newVersion="15.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Extensions.FileSystemGlobbing" publicKeyToken="adb9793829ddae60" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-2.0.0.0" newVersion="2.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-6.0.3.0" newVersion="6.0.3.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Metadata" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="1.0.0.0-8.0.0.0" newVersion="8.0.0.0" />
</dependentAssembly>

<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>

<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-4.0.5.0" newVersion="4.0.5.0" />
</dependentAssembly>

<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.DiagnosticSource" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-8.0.0.1" newVersion="8.0.0.0" />
</dependentAssembly>

<dependentAssembly>
<assemblyIdentity name="System.Text.Encodings.Web" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-6.0.0.1" newVersion="6.0.0.1" />
</dependentAssembly>
</assemblyBinding>
</runtime>
<appSettings>
Expand Down
Loading