2

I'm trying to create a powershell script to clear a redis cache, it's in Azure but I don't think that's relevant. I've seen 2 examples which I'm trying to copy where people have loaded StackExchange.Redis.dll into their script: https://www.powershellgallery.com/packages/Saritasa.Redis/1.2.0/Content/Saritasa.Redis.psm1 and Clearing Azure Redis Cache using PowerShell during deployment.

I've downloaded the current StackExchange.Redis.dll from nuget.org. I've tried to load it on 2 servers, one with .Net 4.61 installed, the other with .Net 4.8. I get the same problem on both.

If I try to use [System.Reflection.Assembly]::LoadFrom I get as below:

PS E:\redis\stackexchange.redis.2.2.88\lib\net461> dir


    Directory: E:\redis\stackexchange.redis.2.2.88\lib\net461


Mode                LastWriteTime     Length Name
----                -------------     ------ ----
-a---        05/11/2021     00:42     637440 StackExchange.Redis.dll
-a---        05/11/2021     00:42     705989 StackExchange.Redis.xml


PS E:\redis\stackexchange.redis.2.2.88\lib\net461> [System.Reflection.Assembly]::LoadFrom("E:\redis\stackexchange.redis.
2.2.88\lib\net461\StackExchange.Redis.dll")

GAC    Version        Location
---    -------        --------
False  v4.0.30319     E:\redis\stackexchange.redis.2.2.88\lib\net461\StackExchange.Redis.dll


PS E:\redis\stackexchange.redis.2.2.88\lib\net461> [StackExchange.Redis.ConnectionMultiplexer]::Connect($myConnectionStr
ing)
Unable to find type [StackExchange.Redis.ConnectionMultiplexer]: make sure that the assembly containing this type is
loaded.
At line:1 char:1
+ [StackExchange.Redis.ConnectionMultiplexer]::Connect($myConnectionString)
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (StackExchange.R...tionMultiplexer:TypeName) [], RuntimeException
    + FullyQualifiedErrorId : TypeNotFound

PS E:\redis\stackexchange.redis.2.2.88\lib\net461>

If I try to use Add-Type I get:

PS E:\redis\stackexchange.redis.2.2.88\lib\net461> Add-Type -AssemblyName .\StackExchange.Redis.dll
Add-Type : Could not load file or assembly '.\\StackExchange.Redis.dll' or one of its dependencies. The given assembly
name or codebase was invalid. (Exception from HRESULT: 0x80131047)
At line:1 char:1
+ Add-Type -AssemblyName .\StackExchange.Redis.dll
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [Add-Type], FileLoadException
    + FullyQualifiedErrorId : System.IO.FileLoadException,Microsoft.PowerShell.Commands.AddTypeCommand

PS E:\redis\stackexchange.redis.2.2.88\lib\net461>

I've looked through the dependencies in nuget.org, I saw one non-Microsoft one called Pipelines.Sockets.Unofficial which I also downloaded and got the same thing. There's a whole hierarchy of other dependencies which I think are all part of .Net, surely I haven't got to download them all if .Net is installed on the server? Thanks for any help, I've been trying all day!

mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 1
    Try this: `Add-Type -LiteralPath 'E:\redis\stackexchange.redis.2.2.88\lib\net461\StackExchange.Redis.dll'` – zett42 Jan 17 '22 at 18:10
  • 1
    Thanks @zett42 this was definitely helpful. When using -LiteralPath I get a different error message "Retrieve the LoaderExceptions property for more information", and then I type `$error[0].Exception.GetBaseException().LoaderExceptions` (which I found googling) which shows the DLL and version which it is missing. I'm still trying things, I'll update again when I've come to some conclusion. – aberdeen angus Jan 24 '22 at 11:41

1 Answers1

2

I'll put some things I learned above my code in case someone as inexperienced as me reads this. These points aren't in any order:

  • To download a nuget package for use by powershell, the easiest way is to use the Install-Package cmdlet, for example: Install-Package -Name System.IO.Pipelines -ProviderName NuGet -SkipDependencies -Destination C:\Stackexchange.Redis-packages -RequiredVersion 5.0.1 Note that -SkipDependencies is needed because without it Install-Package gave me an error message about a circular dependency, described in https://endjin.com/blog/2020/12/how-to-consume-a-nuget-package-in-powershell. You have to download the dependencies yourself! The alternative is: in nuget.org click the download link to download the .nupkg file, rename it to .zip, extract the files, then in File Explorer right-click the dll file, click Properties, Unblock.

  • I thought this app was great for showing the DLL dependencies of a DLL, it shows the whole heirarchy of dependencies and if they're found or not https://github.com/lucasg/Dependencies

  • To get the assembly versions of dll files in powershell: Get-ChildItem -Filter *.dll -Recurse | Select-Object Name,@{n='FileVersion';e={$_.VersionInfo.FileVersion}},@{n='AssemblyVersion';e={[Reflection.AssemblyName]::GetAssemblyName($_.FullName).Version}} from Get file version and assembly version of DLL files in the current directory and all sub directories

  • When loading DLLs, Add-Type behaves differently from [System.Reflection.Assembly]::LoadFrom(). Add-Type seems to load dependencies which can be useful. If Add-Type -Literalpath <dllFileName> fails with an error message “Add-Type : Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information” you can get the DLL it was looking for with $error[0].Exception.GetBaseException().LoaderExceptions https://www.reddit.com/r/PowerShell/comments/7a4vw6/addtype_how_do_i_retrieve_the_loaderexceptions/

  • To get round the situation where DLLs in the heirarchy have a dependency on different versions of the same DLL (which seems to be very common) this guy's solution is simply fantastic. I couldn't find any alternative and it seems to work perfectly https://www.azurefromthetrenches.com/powershell-binding-redirects-and-visual-studio-team-services/

The DLLs I used I ended up with these DLL files in a folder:

Name                                       FileVersion   AssemblyVersion
----                                       -----------   ---------------
Microsoft.Bcl.AsyncInterfaces.dll          6.0.21.52210  6.0.0.0
Pipelines.Sockets.Unofficial.dll           2.2.0.45337   1.0.0.0
StackExchange.Redis.dll                    2.2.88.56325  2.0.0.0
System.Buffers.dll                         4.6.28619.01  4.0.3.0
System.IO.Pipelines.dll                    5.0.120.57516 5.0.0.1
System.Memory.dll                          4.6.28619.01  4.0.1.1
System.Numerics.Vectors.dll                4.6.26515.06  4.1.4.0
System.Runtime.CompilerServices.Unsafe.dll 6.0.21.52210  6.0.0.0
System.Threading.Channels.dll              6.0.21.52210  6.0.0.0
System.Threading.Tasks.Extensions.dll      4.6.28619.01  4.2.0.1

The code It's not finished, but shows the Stackexhange.Redis DLL loaded and used.

# Code to load the DLLs needed for Stackexchange.Redis.dll and clear the cache
# Basically copied from https://www.azurefromthetrenches.com/powershell-binding-redirects-and-visual-studio-team-services/

$DllPath = 'C:\Stackexchange.Redis-packages\combined DLLS'
$redisHostName = '<my cache name here>.redis.cache.windows.net'
$redisConnectionString = '<my cache name here>.redis.cache.windows.net:6380,password=<my cache password here>,ssl=True,abortConnect=False'

# Load DLL assemblies into memory, required by the event handler below
$SystemBuffersDll = [System.Reflection.Assembly]::LoadFrom("$DllPath\System.Buffers.dll")
$SystemRuntimeCompilerServicesUnsafeDll = [System.Reflection.Assembly]::LoadFrom("$DllPath\System.Runtime.CompilerServices.Unsafe.dll")
$SystemMemoryDll = [System.Reflection.Assembly]::LoadFrom("$DllPath\System.Memory.dll")
$SystemSystemThreadingTasksExtensionsDll = [System.Reflection.Assembly]::LoadFrom("$DllPath\System.Threading.Tasks.Extensions.dll")
$SystemIoPipelinesDll = [System.Reflection.Assembly]::LoadFrom("$DllPath\System.IO.Pipelines.dll")
$MicrosoftBclAsyncInterfacesDll = [System.Reflection.Assembly]::LoadFrom("$DllPath\Microsoft.Bcl.AsyncInterfaces.dll")
$PipelinesSocketsUnofficialDll = [System.Reflection.Assembly]::LoadFrom("$DllPath\Pipelines.Sockets.Unofficial.dll")
$SystemThreadingChannelsDll = [System.Reflection.Assembly]::LoadFrom("$DllPath\System.Threading.Channels.dll")

# Event handler to be run when the AssemblyResolve event occurs
$onAssemblyResolveEventHandler = [System.ResolveEventHandler] {
    param($sender, $e)

    Write-Verbose "Assembly resolve event for $($e.Name)"

    $dllName = $e.Name.Split(',')[0]
  
    switch ($dllName) {
        'System.Buffers' {return $SystemBuffersDll}
        'System.Runtime.CompilerServices.Unsafe' {return $SystemRuntimeCompilerServicesUnsafeDll}
        'System.Memory' {return $SystemMemoryDll}
        'System.Threading.Tasks.Extensions' {return $SystemSystemThreadingTasksExtensionsDll}
        'Microsoft.Bcl.AsyncInterfaces' {return $MicrosoftBclAsyncInterfacesDll}
        'Pipelines.Sockets.Unofficial' {return $PipelinesSocketsUnofficialDll}
        'System.Threading.Channels' {return $SystemThreadingChannelsDll}
        'System.Numerics.Vectors' {return $SystemNumericsVectorsDll}
        'System.IO.Pipelines' {return $SystemIoPipelinesDll}
    }

    foreach($assembly in [System.AppDomain]::CurrentDomain.GetAssemblies()) {
        if ($assembly.FullName -eq $e.Name) {
        return $assembly
        }
    }
      return $null
}

# Set up the handler above to be triggered when the AssemblyResolve event occurs
[System.AppDomain]::CurrentDomain.add_AssemblyResolve($onAssemblyResolveEventHandler)


# Load StackExchange.Redis.dll, prefer Add-Type because it seems to include dependencies, LoadFrom doesn't so might get an error later
Add-Type -LiteralPath  "$DllPath\StackExchange.Redis.dll"    

$redis = [StackExchange.Redis.ConnectionMultiplexer]::Connect("$redisConnectionString, allowAdmin=true")
$redisServer = $redis.GetServer($redisHostName, 6380)
# $rs.FlushAllDatabases(async=true)
$redisServer.FlushAllDatabases()

# Detach the event handler (not detaching can lead to stack overflow issues when closing PS)
[System.AppDomain]::CurrentDomain.remove_AssemblyResolve($onAssemblyResolveEventHandler)