When working with .NET assemblies in PowerShell, you essentially have three main approaches to load them:
# Method 1: Add-Type (traditional approach)
Add-Type -Path "C:\rnd\CloudBerry.Backup.API.dll"
# Method 2: Reflection (more flexible)
[System.Reflection.Assembly]::LoadFrom("C:\rnd\CloudBerry.Backup.API.dll")
# Method 3: Alternative reflection method
[System.Reflection.Assembly]::LoadFile("C:\rnd\CloudBerry.Backup.API.dll")
The error message you're seeing typically indicates one of these common scenarios:
try {
Add-Type -Path $dllpath -ErrorAction Stop
}
catch [System.Reflection.ReflectionTypeLoadException] {
$_.Exception.LoaderExceptions | ForEach-Object {
Write-Warning $_.Message
}
}
Once you've successfully loaded the assembly via reflection, here's how to properly access its types:
$asm = [System.Reflection.Assembly]::LoadFrom($dllpath)
# Method 1: Using the full type name with namespace
$type = $asm.GetType("CloudBerryLab.Backup.API.BackupProvider")
$type::GetAccounts()
# Method 2: Creating an instance first if needed
$instance = [Activator]::CreateInstance($type)
$instance.MethodName()
# Method 3: Using dynamic approach (PowerShell 5+)
$accounts = $asm.GetType("CloudBerryLab.Backup.API.BackupProvider")::GetAccounts()
If you're still having issues, consider these troubleshooting steps:
# 1. Check assembly dependencies
$asm.GetReferencedAssemblies() | ForEach-Object {
try {
[System.Reflection.Assembly]::Load($_)
}
catch {
Write-Warning "Failed to load dependency: $($_.FullName)"
}
}
# 2. Verify the assembly is actually loaded
[AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object Location -like "*CloudBerry*" |
Select-Object FullName, Location, GlobalAssemblyCache
For complex scenarios where types still aren't accessible:
# Using PowerShell's type acceleration
$accelerators = [PSObject].Assembly.GetType('System.Management.Automation.TypeAccelerators')
$accelerators::Add('CBBProvider', "CloudBerryLab.Backup.API.BackupProvider")
# Now you can use the shortcut
[CBBProvider]::GetAccounts()
# Alternative using Set-Type
$asm.GetExportedTypes() | ForEach-Object {
Set-Type -TypeDefinition "using namespace $($_.Namespace);"
}
When working with .NET assemblies in PowerShell, the most common pain point manifests through the infamous ReflectionTypeLoadException
. The error message you're seeing typically indicates one of these scenarios:
# Common failure patterns
Add-Type -Path "C:\rnd\CloudBerry.Backup.API.dll" # Fails with ReflectionTypeLoadException
[CloudBerryLab.Backup.API.BackupProvider]::GetAccounts() # TypeNotFound error
Traditional Add-Type
might fail where reflection succeeds because of these key differences:
# Method 1: Add-Type (Strict loading)
Add-Type -Path $dllpath # Requires all dependencies present in same directory
# Method 2: Assembly.LoadFrom (More tolerant)
[Reflection.Assembly]::LoadFrom($dllpath) # Searches dependencies in loading context
# Method 3: Assembly.LoadFile (Isolated context)
[Reflection.Assembly]::LoadFile($dllpath) # Treats assembly as independent unit
For CloudBerry's specific case, here's a proven approach:
# Step 1: Create proper loading context
$dllpath = "C:\rnd\CloudBerry.Backup.API.dll"
$resolvedPath = Resolve-Path $dllpath
# Step 2: Load with error handling
try {
$asm = [Reflection.Assembly]::LoadFrom($resolvedPath)
# Verify loaded types
$asm.GetExportedTypes() | ForEach-Object {
Write-Host "Available type: $($_.FullName)"
}
# Step 3: Access types through reflection first
$backupProviderType = $asm.GetType("CloudBerryLab.Backup.API.BackupProvider")
# Step 4: Invoke static methods
$accounts = $backupProviderType::GetType().GetMethod("GetAccounts").Invoke($null, $null)
$accounts | Format-Table -AutoSize
} catch [System.Reflection.ReflectionTypeLoadException] {
Write-Warning "Loader exceptions encountered:"
$_.LoaderExceptions | ForEach-Object {
Write-Host " - $($_.Message)"
}
}
When LoaderExceptions occur, examine missing dependencies:
# Analysis command for any .NET assembly
[Reflection.Assembly]::LoadFrom($dllpath).GetReferencedAssemblies() |
ForEach-Object {
try {
[Reflection.Assembly]::Load($_) | Out-Null
Write-Host "$($_.Name) - LOADED"
} catch {
Write-Warning "$($_.Name) - MISSING"
}
}
For production environments, consider registration in GAC:
# Requires elevated session
[System.EnterpriseServices.Internal.Publish]::GacInstall($dllpath)
# Then reference via full strong name
Add-Type -AssemblyName "CloudBerry.Backup.API, Version=1.0.0.1, Culture=neutral, PublicKeyToken=..."
Here's a complete script pattern that handles most edge cases:
function Import-DotNetAssembly {
param(
[string]$Path,
[switch]$Global
)
$resolvedPath = Resolve-Path $Path -ErrorAction Stop
# Temporary event handler for assembly resolve
$onAssemblyResolve = [System.ResolveEventHandler] {
param($sender, $e)
$assemblyPath = Join-Path (Split-Path $resolvedPath) "$($e.Name.Split(',')[0]).dll"
if (Test-Path $assemblyPath) {
return [Reflection.Assembly]::LoadFrom($assemblyPath)
}
return $null
}
try {
# Add resolver
[System.AppDomain]::CurrentDomain.add_AssemblyResolve($onAssemblyResolve)
# Load main assembly
$assembly = [Reflection.Assembly]::LoadFrom($resolvedPath)
if ($Global) {
# Make types globally available
$assembly.GetExportedTypes() | ForEach-Object {
[pscustomobject]@{
TypeName = $_.FullName
Assembly = $assembly
} | Add-Member -MemberType ScriptMethod -Name "GetInstance" -Value {
param($Arguments = @())
[Activator]::CreateInstance($this.TypeName, $Arguments)
} -PassThru
} | ForEach-Object {
$script:ExecutionContext.SessionState.PSVariable.Set($_.TypeName.Split('.')[-1], $_)
}
}
return $assembly
} finally {
[System.AppDomain]::CurrentDomain.remove_AssemblyResolve($onAssemblyResolve)
}
}
# Usage:
$cloudBerry = Import-DotNetAssembly -Path "C:\rnd\CloudBerry.Backup.API.dll" -Global
$BackupProvider.GetAccounts() # Now works globally