We experienced an issue with a PowerShell script that loaded several SharePoint assemblies such as Microsoft.SharePoint.Client.dll that were shipped along with the script in the same folder. The PS script loaded the assemblies, but loaded the WRONG version on some environments. We needed Microsoft.SharePoint.Client.dll version 15.0.4797.1000 or higher, and what we we got was version 188.8.131.52. This happened on environments that didn’t contain the latest CU update so still had the older version in the GAC. Since we were in a situation where we were not allowed to install the CU update on the machine, but we still wanted to run code with the newer assembly version, we were trying to find a way to specify the exact assembly that we wanted to load from within a PS script.
A quick tip: we quickly became annoyed with the fact that we needed to close the PS cmd prompt after loading assemblies, because the loaded dll’s are not unloaded from the app domain otherwise. Instead, we did the following:
1 – Open a PS cmd prompt
2 – Type ‘powershell’ which starts a new PowerShell session.
3 – Then, execute the code that loads the assembly and test it.
4 – Type ‘exit’ to cloase the PS session and unload the dll’s.
Repeat this procedure as much as needed. This way, you don’t have to close the PS cmd prompt itself anymore which is a time saver.
We came up with the following ways to load assemblies within a PS script:
– Via Add-Type -Path, which allows you yo specify the path to assembly DLL files that contain the needed types.
Example: Add-Type -Path ‘D:\LCTest\Microsoft.SharePoint.Client.dll’
– Via Add-Type -LiteralPath, which also allows you to specify the path to assembly files. The difference with the Path parameter, according to the documentation (https://technet.microsoft.com/en-us/library/hh849914.aspx), is that the value of the LiteralPath parameter is used exactly as typed and no characters are interpreted as wildcards.
Example: Add-Type -LiteralPath ‘D:\LCTest\LoisAndClark.Microsoft.SharePoint.Client.dll’
Please note: we altered the name of the SharePoint client assembly to LoisAndClark.Microsoft.SharePoint.Client.dll to make absolutely sure that we were loading the assembly we intended.
– Via reflection and the LoadFile method, which loads the contents of an assembly file.
– Via reflection and the LoadFrom method, which loads an assembly given its file name or path.
– Via reflection and the Load method, which loads an assembly based on its FQDN:
[reflection.assembly]::Load(‘Microsoft.SharePoint.Client, Version=15.0.4797.1000, Culture=neutral, PublicKeyToken=71e9bce111e9429c’)
By the way, we found an easy way to check if the assembly was recent enough for our purposes by loading the assembly and executing the following PS code:
[Microsoft.SharePoint.Client.AuditMaskType] $test = 0
If that line of code worked, the assembly was new enough for us.
Although we now had various ways to load assemblies within a PS script none of them worked correctly, because they all loaded the old assembly version. The thing is that the various assembly loading methods imply that you can specify a specific assembly location, when in fact, you cannot. As soon as the code tries to load the assembly, the CLR checks if there is an assembly in the GAC with the same strong name. If there is, the CLR loads that one instead. We came up with a couple of ideas to try and circumvent this:
– Configuration via app.config
– Use ReflectionOnlyLoadFrom
– Remove strong name
– Uninstall SharePoint DLLs from GAC
– Use DevPath
Configuratie via app.config file
The CLR assembly probing mechanism can be influenced via an app.config file. Since we’re dealing with a PS script that loads assemblies, the next question is how you should load an app.config file. As it turns out, the app.config file can be loaded in the app domain that executes the PS code. Unfortunately, the app.config file cannot influence the behavior of the assembly resolving process and using the app config would only have a chance of succeeding if the assembly version (and not the assembly file version) would have been different. But that was not the case, so we had to abandon this idea.
Use the ReflectionOnlyLoadFrom method via reflection
As opposed to the other methods via reflection discussed previously, reflection does offer a method that allows you to load an assembly from a specific location. That method is called ReflectionOnlyLoadFrom and can be used like this:
However, this method was useless to us because assemblies that are loaded this way are not executable. You can use this method to find out information about a specific assembly but we were not interested in that.
Remove strong name
By definition assemblies that are NOT strong named don’t match with assemblies in the GAC. One approach would be to remove the strong name of the required SharePoint assemblies using tools such as http://www.nirsoft.net/dot_net_tools/snremove.zip. This way, it should be possible to prevent the loading of the GAC version of the assembly. Although this should work and is valid as a train of thought in a brainstorm, we didn’t pursue this approach because it is silly.
Remove SharePoint DLLs from the GAC
We had a test environment that shouldn’t contain SharePoint assemblies but did nonetheless. It was certain that those DLLs were not required for the machine, it was unclear how they got there and they couldn’t be updated to a newer version because of an obscure error that occurred. So, we tried what would happen if we’d remove the SharePoint DLLs from the GAC directly, assuming that if we succeeded in doing that the CLR would have no choice but to load the intended assembly version. Alas, every time we did that after a short while the DLLs that were removed from the GAC were restored. Allegedly, Windows Installer is responsible for this. It keeps track of a reference count and if the count is > 0 there are still applications depending on a dll. You can only really remove the assembly from the GAC if the reference count has reached 0. This is made clearer by removing a DLL via a Visual Studio command prompt, like so:
gacutil /u “microsoft.sharePoint.client,Version=184.108.40.206, Culture=neutral, PublicKeyToken=71e9bce111e9429c”
This fails and the error message states the dll cannot be removed because there are applications that depend upon it. Since we were unsure which applications were still dependent upon the DLL (which should be deductible by exploring the registry), we had to abandon this approach.
The DevPath setting is a bit esoteric, but proved useful in our case. You can use it to indicate that a specific machine is a development machine thus allowing you to choose a specific path where assemblies should be loaded from thereby bypassing the GAC. Doing this also means that assembly version numbers are not taken into account anymore, the .NET assembly resolver just loads the first assembly it finds. You can set the DevPath setting by opening the machine.config file (C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Config\machine.config) and replace the <runtime /> element with:
<developmentMode developerInstallation=”true” />
Once you’ve done that, the .NET assembly resolver looks if there is a DEVPATH system environment variable and uses that path to load assemblies (e.g. you can set the DEVPATH environment variable to D:\LCTest\).
This approach works, but you also have designated a machine as being a DEV machine. That would only be acceptable rarely.
You can force the correct assembly to be loaded from within a PS script bypassing the one located in the GAC, but you’ll have to jump through some hoops and live with concessions. In our case we decided the only valid way of going forward was to create a clean machine without any SharePoint DLLs in the GAC thereby ensuring that the issue where the wrong assembly version is loaded is prevented.