Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Referencing Assemblies from PowerShell and Binding Redirects #342

Closed
atlastodor opened this issue Apr 15, 2018 · 10 comments
Closed

Referencing Assemblies from PowerShell and Binding Redirects #342

atlastodor opened this issue Apr 15, 2018 · 10 comments
Labels

Comments

@atlastodor
Copy link

I am writing a custom build task, where the PowerShell script will need to access additional .Net assemblies. I am having problems referencing those, especially the binding redirects that are normally given in the .config file.

What is the proper way to handle this? I've seen examples using the ResolveAssembly event, but this to me sounds as a work around. Also, there may be other settings in the .config file that may be relevant and can't be ignored.

PS: Running on TFS 2018 Update 2, on premise.

@ericsciple
Copy link
Contributor

.config is for the entry assembly. In this case, powershell.exe is the entry assembly.

Here is an example where the task lib calls Add-Type to load a DLL.

Here is an example where a binding redirect is applied, and removed.

Tasks carry a known set of specific dependencies. If other settings need to be accommodated, it is always a known set.

@atlastodor
Copy link
Author

Thanks Eric. I was aware that .config affects the whole assembly, in this case PowerShell, which if modified will have influence on the whole machine. I was secretly hoping that there was a way to dynamically load a .config file so that only the current process would be affected.

I will look at the resolve assembly examples. This is also what I concluded was probably the way to go. Just a stupid question: What will happen if I explicitly loaded the depending assemblies in the correct version before loading the main assembly? In my case, System.Net.Http 4.1.1.2 instead of the default System.Net.Http 4.0.0.0.

Also, what do you mean by "Tasks carry a known set of specific dependencies"? Do you mean that I as the developer know exactly what the dependencies are?

@atlastodor
Copy link
Author

atlastodor commented Apr 16, 2018

I just tried with the assembly-resolve event. I can't get it to work. The resolve handler only fires for Newtonsoft.Json and for none of the other assemblies. Are there any other resolve handlers registered by the system or TFS task system?

Some diagnostics:
_# Assemblies in current domain:
_# Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
_# Microsoft.Management.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.PowerShell.Commands.Management, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.PowerShell.Commands.Utility, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.PowerShell.ConsoleHost, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.PowerShell.Security, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Configuration.Install, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Transactions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# VstsTaskSdk, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

_# Adding assembly resolver.

Add-Type -Path (Join-Path $assemblyFolder "TfsVersionHistoryLib.dll")

$service = New-Object TfsVersionHistoryLib.Service($env:SYSTEM_TEAMFOUNDATIONCOLLECTIONURI, $env:SYSTEM_TEAMPROJECT);

_# Resolving 'Newtonsoft.Json, Version=8.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed'

_# Assemblies in current domain:
_# Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
_# Microsoft.Management.Infrastructure, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.PowerShell.Commands.Management, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.PowerShell.Commands.Utility, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.PowerShell.ConsoleHost, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.PowerShell.Security, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# Microsoft.TeamFoundation.Client, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# Microsoft.TeamFoundation.Common, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# Microsoft.TeamFoundation.Core.WebApi, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# Microsoft.TeamFoundation.VersionControl.Client, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# Microsoft.VisualStudio.Services.Client.Interactive, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# Microsoft.VisualStudio.Services.Common, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# Microsoft.VisualStudio.Services.WebApi, Version=15.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# Newtonsoft.Json, Version=11.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed
_# PresentationCore, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# PresentationFramework, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# SMDiagnostics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Configuration.Install, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Data, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.DirectoryServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Management, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Management.Automation, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# System.Net.Http, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Numerics, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Runtime.Serialization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.ServiceModel.Internals, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# System.Transactions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
_# System.Web.Services, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# System.Xaml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# System.Xml.Linq, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
_# TfsVersionHistoryLib, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
_# VstsTaskSdk, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
_# WindowsBase, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35

@nigurr
Copy link

nigurr commented Apr 16, 2018

@atlastodor
Copy link
Author

atlastodor commented Apr 16, 2018

Hi. This is basically what I do. Except, in the example, I don't see where the on-resolve-assembly handler is hooked.

My code (see below) calls the handler, but only once and only for the Newtonsoft.Json. Not for any other assemblies.

[appdomain]::currentdomain.getassemblies() | sort -property fullname | format-table fullname | Out-String|% {Write-Host $_}

$currentScriptDirectory = Get-Location
$assemblyFolder = Join-Path $currentScriptDirectory "Tfs"
[System.IO.Directory]::SetCurrentDirectory($assemblyFolder)

Write-Host "Adding assembly resolver."
$onAssemblyResolve = [System.ResolveEventHandler] {
param($sender, $e)
if ($e.Name -like 'Newtonsoft.Json, *') {
Write-Host "Resolving '$($e.Name)'"
$assemblyPath = Join-Path $assemblyFolder "Newtonsoft.Json.dll"
return [System.Reflection.Assembly]::LoadFrom($assemblyPath)
}
if ($e.Name -like 'System.Net.Http, *') {
Write-Host "Resolving '$($e.Name)'"
$assemblyPath = Join-Path $assemblyFolder "System.Net.Http.dll"
return [System.Reflection.Assembly]::LoadFrom($assemblyPath)
}

Write-Host "Unable to resolve assembly name '$($e.Name)'"
return $null

}
[System.AppDomain]::CurrentDomain.add_AssemblyResolve($onAssemblyResolve)

Add-Type -Path (Join-Path $assemblyFolder "MyLib.dll")
Write-Host "Loaded MyLib"

$service = New-Object MyLib.Service("...", "...")
Write-Host "Service created"

[appdomain]::currentdomain.getassemblies() | sort -property fullname | format-table fullname | Out-String|% {Write-Host $_}

..# Crashes below with: [A]System.Net.Http.Headers.MediaTypeHeaderValue cannot be cast to [B]System.Net.Http.Headers.MediaTypeHeaderValue. kk e context 'Default' at location 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Net.Http\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.Net.Http.dll'. Type B originates from 'System.Net.Http, Version=4.1.1.2, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' in the context 'LoadFrom' at location 'C:\TFSAgent-Default-17_work_tasks\AtlasGenerateReleaseNotes_130d1a10-3cd0-11e8-81c9-a13fe19f6bca\0.1.42\Tfs\System.Net.Http.dll'.
2018-04-16T07:07:29.4819451Z System.InvalidCastException

$query = $service.Query(1234)

@nigurr
Copy link

nigurr commented Apr 16, 2018

@atlastodor it's handled in the same line as pointed.

$jsonAssembly = [reflection.assembly]::LoadFrom($PSScriptRoot + "\modules\Newtonsoft.Json.dll")
$onAssemblyResolve = [System.ResolveEventHandler] {
param($sender, $e)
if ($e.Name -eq "Newtonsoft.Json, Version=6.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed") { return $jsonAssembly }
foreach($a in [System.AppDomain]::CurrentDomain.GetAssemblies())
{
if($a.FullName -eq $e.Name) { return $a } else { return $null }
}
return $null
}

All you need to do is, instead of Newtonsoft, you can change it to System.Net.Http assembly properties

@atlastodor
Copy link
Author

I already have the System.Net.Http in my handler. Only that it is not called. Probably something with the load context for the assembly. I am investigating.

I was asking where the $onAssemblyResolve variable is used. I can't see that this handler is used anywhere.

@nigurr
Copy link

nigurr commented Apr 16, 2018

@atlastodor the variable is not required. essentially its' EventHandler, will be invoked whenever system invokes that Assembly Load Event.

@atlastodor
Copy link
Author

After reading the .Net documentation, I came to the conclusion that the AssemblyResolve event is only raised when .Net cannot find the assembly in question.

The problem is that .Net loads the assemblies in the appdomain's default context, and if it can find any assembly that matches the requested assembly, it will be loaded. In my case System.Net.Http, Version=4.1.1.2 is requested but for some reason the system finds System.Net.Http, Version=4.0.0.0 in the GAC and loads that assembly. The event is not raised, since the system found a match. It is only raised if it cannot find the assembly.

I ended up loading an additional AppDomain that is configured to use my custom .config file. This was the simplest approach for me.

@MiaoXuHui
Copy link

MiaoXuHui commented Jun 3, 2020

"[System.AppDomain]::CurrentDomain.add_AssemblyResolve($OnAssemblyResolve)"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants