Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to install the Bcrypt.Net-Next assembly in to the GAC on Windows 10 using Powershell

I need to implement BCrypt hashing in an SSIS package's data flow. I would like to do this by deploying the Bcrypt assembly to the GAC and then calling it in a script component. I downloaded the project from Git, built the strong named project (I created a key), and deployed the assembly to the GAC using Powershell. I used Powershell instead of gacutil.exe because I am running Windows 10, which does not have gacutil.exe.

https://github.com/BcryptNet/bcrypt.net

# NOTE: Run powershell as administrator
[Reflection.Assembly]::LoadWithPartialName("System.EnterpriseServices") | Out-Null         
[System.EnterpriseServices.Internal.Publish] $publish = New-Object System.EnterpriseServices.Internal.Publish

# to install a dll
$publish.GacInstall("C:\temp\Bcrypt.net\BCrypt.Net-Next.dll")

I do not get any error, in fact I do not get any output - it just goes to the next line. But the assembly is not installed in %windir%\Microsoft.NET\assembly

Any idea why this is not working?

Note: I am doing it this way as opposed to using NuGet because I am going to be using this in an SSIS script component. Apparently, NuGet does not work with SSIS solutions.

like image 347
J Weezy Avatar asked Mar 06 '20 18:03

J Weezy


2 Answers

To complement your own answer:

I'm surprised that [Reflection.Assembly]::LoadWithPartialName('System.EnterpriseServices') didn't work - it does for me, but it's a moot point, because in PowerShell it's preferable to use
Add-Type -AssemblyName
, which:

  • is more PowerShell-idiomatic in terms of its syntax, and
  • reports a (statement-terminating) error if the assembly cannot be loaded (whereas [Reflection.Assembly]::LoadWithPartialName() is a quiet no-op in case of failure to load the assembly).
# Try to load the latest System.EnterpriseServices.dll assembly
# from the GAC.
Add-Type -AssemblyName System.EnterpriseServices

Like [Reflection.Assembly]::LoadWithPartialName(), Add-Type -AssemblyName allows you to load GAC assemblies by their simple name (as also reflected in the DLL/executable file name without extension), which neither requires you to know the assembly's version number nor its public key (however, Add-Type -AssemblyName does not also first look in the application directory, which in PowerShell would presumably be the location of the PowerShell executable itself).

Note that [Reflection.Assembly]::LoadWithPartialName() is officially declared obsolete, because loading assemblies by simple name can break existing code later, due to installation of incompatible versions or assemblies with duplicate simple names.

However, in a late-bound scripting language such as PowerShell, loading by simple name may be acceptable (Add-Type -AssemblyName is not obsolete), and simplifies loading (you can specify an assembly's unambiguous full name, however - see below).

Of course, if [Reflection.Assembly]::LoadWithPartialName('System.EnterpriseServices') inexplicably didn't work for you, Add-Type -AssemblyName System.EnterpriseServices may similarly fail, but the general point stands.

The recommended replacement for [Reflection.Assembly]::LoadWithPartialName() is [Reflection.Assembly]::Load(), which is what you ended up using. It requires you to know the assembly's full name, which must include the assembly's full version number and its public key - although a lower version number than the one actually present in the GAC is seemingly accepted.

Note that Add-Type -AssemblyName accepts (strong-named) full assembly names too:

# Load from the GAC by *full assembly name*.
# Equivalent of [Reflection.Assembly]::Load()
Add-Type -AssemblyName 'System.EnterpriseServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'

Taking a step back:

I suggest avoiding putting a custom assembly in the GAC:, for two reasons:

  • PowerShell [Core] 6+, which will eventually make Windows PowerShell obsolete, is built on .NET Core, which doesn't have a GAC anymore, so you'll need a different approach when you migrate.

    • In PowerShell [Core], Add-Type -AssemblyName looks for assemblies given by simple name among the assemblies that ship with PowerShell itself, though it looks in the current directory first.
  • The System.EnterpriseServices.Internal.Publish type you're using isn't officially supported for direct use: "Publish is used internally by the .NET Framework. You do not need to use it directly in your code.", and the only officially supported way to install assemblies in the GAC is via Windows Installer (gacutil.exe is only meant to be used during development).


Instead, I recommend the following approach:

  • Author an auxiliary PowerShell module that wraps the assembly of interest, named, say, BCrypt.

  • Place that module in one of the directories listed in $env:PSModulePath, so that any script executing Import-Module BCrypt implicitly loads the assembly of interest into the session.

Such a module is easy to author:

  • Choose a suitable directory listed in $env:PSModulePath and create a subdirectory named, say, BCrypt in it.

  • Copy your assembly DLL (BCrypt.Net-Next.dll) into that subdirectory.

  • Change to the subdirectory and create a module manifest BCrypt.psd1 there:

      New-ModuleManifest BCrypt.psd1 -RequiredAssemblies BCrypt.Net-Next.dll -ModuleVersion 1.0
    

Provide additional New-ModuleManifest arguments as needed and/or edit the module manifest after the fact, as needed.

like image 52
mklement0 Avatar answered Nov 20 '22 05:11

mklement0


The Powershell script I was using is incorrect.

Instead of using LoadWithPartialName, we must use Load

# NOTE: Run powershell as administrator
[System.Reflection.Assembly]::Load("System.EnterpriseServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")
$publisher = New-Object System.EnterpriseServices.Internal.Publish

# to uninstall a dll
$publisher.GacRemove("C:\temp\Bcrypt.net\BCrypt.Net-Next.StrongName.dll")

# to install a dll
$publisher.GacInstall("C:\temp\Bcrypt.net\BCrypt.Net-Next.StrongName.dll")

This will install the assembly into the following path:

%windir%\Microsoft.NET\assembly\GAC_MSIL\BCrypt.Net-Next.StrongName\v4.0_3.2.1.0__cb41ca561ed0708f\BCrypt.Net-Next.StrongName.dll

Note: I am using the most recent version available on Git, which is 3.2.1. So, future releases will result in a different sub-folder path as seen above (i.e., v4.0_*).

The challenge here is that I still do not see the assembly available in any of the script component's references. I will continue to research this and post a new question if needed.

like image 4
J Weezy Avatar answered Nov 20 '22 05:11

J Weezy