PowerShell使用MimeKit/MailKit发邮件报依赖与程序集加载异常
在Win10系统中尝试使用PowerShell发送邮件,需通过587端口、TLS 1.2协议与SMTP服务器通信。出于安全风险考虑,微软不推荐使用System.Net.Mail.SmtpClient,因此选择使用NuGet Gallery提供的MailKit包及其依赖的MimeKit包实现功能。
操作过程中首先使用PowerShell的Install-Package cmdlet安装MailKit成功,但安装MimeKit时出现依赖循环错误,错误提示如下:
PS C:\bin\ps1\renew_certs> Install-Package -Name 'MimeKit' -Source "https://www.nuget.org/api/v2" -Force Install-Package : Dependency loop detected for package 'MimeKit'. At line:1 char:1 + Install-Package -Name 'MimeKit' -Source "https://www.nuget.org/api/v2 ... + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : Deadlock detected: (MimeKit:String) [Install-Package], Exception + FullyQualifiedErrorId : DependencyLoopDetected,Microsoft.PowerShell.PackageManagement.Cmdlets.InstallPackage PS C:\bin\ps1\renew_certs> ($error[0].Exception.GetBaseException()) Dependency loop detected for package 'MimeKit'.
参考同类问题解决方案,为Install-Package添加-SkipDependency参数后绕过了安装报错。但运行代码时出现新的异常,查看底层错误提示,发现无法加载MimeKit的依赖包Portable.BouncyCastle。
随后单独安装Portable.BouncyCastle包(未加-SkipDependencies参数,默认安装其依赖),并在代码中通过Add-Type加载对应DLL,该错误消失,但运行代码时出现本次咨询的核心异常。
测试代码如下:
Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\System.Text.Encoding.CodePages.6.0.0\lib\netstandard2.0\System.Text.Encoding.CodePages.dll" Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\Portable.BouncyCastle.1.9.0\lib\netstandard2.0\BouncyCastle.Crypto.dll" Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\MimeKit.3.3.0\lib\netstandard2.0\MimeKit.dll" Add-Type -Path "C:\Program Files\PackageManagement\NuGet\Packages\MailKit.3.3.0\lib\netstandard2.0\MailKit.dll" $SMTP = New-Object MailKit.Net.Smtp.SmtpClient $Message = New-Object MimeKit.MimeMessage <# $TextPart = [MimeKit.TextPart]::new("plain") $TextPart.Text = "There was a deployment" <#
运行时抛出异常如下:
PS C:\bin\ps1\renew_certs> . "c:\bin\ps1\renew_certs\send_email.ps1" New-Object : Exception calling ".ctor" with "0" argument(s): "The type initializer for 'MimeKit.ParserOptions' threw an exception." At C:\bin\ps1\renew_certs\send_email.ps1:9 char:13 + $Message = New-Object MimeKit.MimeMessage + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : InvalidOperation: (:) [New-Object], MethodInvocationException + FullyQualifiedErrorId : ConstructorInvokedThrowException,Microsoft.PowerShell.Commands.NewObjectCommand PS C:\bin\ps1\renew_certs> ($error[0].Exception.GetBaseException()) Could not load file or assembly 'System.Text.Encoding.CodePages, Version=4.1.1.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified. PS C:\bin\ps1\renew_certs>
System.Text.Encoding.CodePages是MimeKit标注的依赖项,参照Portable.BouncyCastle的解决思路尝试安装该包,但同样出现依赖循环错误,添加-SkipDependencies参数安装6.0.0版本后,在代码中手动加载对应DLL,异常仍未解决。MimeKit标注的System.Text.Encoding.CodePages依赖版本要求为>=4.5.1,但异常提示需要加载4.1.1.0版本,NuGet源中无4.1.x版本的安装包。后续尝试安装System.Text.Encoding.CodePages的依赖项System.Memory包,同样遇到依赖循环错误,其依赖层级复杂无法逐一手动处理。
- 如何追踪依赖循环错误的具体成因,确认实际缺失的文件?
- 当前异常是否存在其他根本原因?
- 异常提示系统找不到指定文件,如何查看缺失文件的具体路径?
- 在VS Code中使用PowerShell调试器时,异常在调用
[MimeKit.TextPart]::new("plain")时抛出,是否有方法深入DLL内部调试?
Win10自带的Windows PowerShell 5.1基于.NET Framework 4.x,其内置的Install-Package cmdlet对.NET Standard格式类库的依赖解析存在已知缺陷,处理多版本依赖、跨框架适配时经常误报依赖循环,也不会自动生成程序集绑定重定向规则,这是所有报错的核心诱因。以下对应解答问题:
- 依赖循环成因与缺失文件排查
直接弃用Install-Package处理带复杂依赖的.NET类库,改用官方nuget命令行工具执行包安装,工具会自动递归解析所有层级依赖,不会出现误报的依赖循环错误,安装完成后目录下的所有包就是完整依赖列表,不存在缺漏。
要验证具体缺失的文件,使用Windows SDK自带的程序集绑定日志查看器fuslogvw.exe,开启失败绑定日志后,每一次DLL加载失败都会留下完整记录,包含期望的程序集版本、公钥、所有尝试搜索的路径。 - 当前异常的根本原因
报错不是因为磁盘上不存在对应DLL,而是版本匹配失败:你加载的是6.0.0版本的System.Text.Encoding.CodePages,但MimeKit编译时引用的是4.1.1.0版本,.NET Framework的CLR默认不会自动加载高版本程序集替代低版本引用,哪怕文件已经存在,只要版本号不匹配就会报找不到文件。此外手动加载DLL如果顺序错误,没有按照「基础系统依赖→第三方底层依赖→上层业务库」的顺序加载,也会触发同类错误。 - 缺失文件搜索路径查看方法
最准确的方式是使用前面提到的fuslogvw.exe,日志会完整列出CLR依次尝试查找DLL的每一个物理路径,包括应用程序基目录、GAC全局程序集缓存、私有探测路径等。
临时快速排查可以在脚本开头注册程序集加载失败的回调事件,触发加载失败时直接打印信息:[AppDomain]::CurrentDomain.AssemblyResolve += { param($sender, $eventArgs) Write-Warning "加载程序集失败: $($eventArgs.Name)" return $null } - DLL内部调试方法
首先关闭VS Code PowerShell调试的「仅我的代码」选项,在调试配置launch.json里把justMyCode参数设为false,同时配置符号源,下载对应版本MailKit、MimeKit的调试符号pdb文件放到DLL同目录,触发异常时就可以单步进入DLL内部代码跟踪执行流程。如果觉得配置符号麻烦,可以直接拉取对应版本的MailKit、MimeKit源码到本地编译,脚本里直接引用本地编译输出的带调试信息的DLL,调试起来更顺畅。
避坑提示:不用手动折腾DLL加载和依赖适配,PowerShell画廊已有封装好的MailKit包装模块,直接执行Install-Module Mailozaurr安装,所有依赖和版本适配都已经处理完成,导入模块后直接调用发邮件命令即可,不需要自行编写DLL加载逻辑。
内容的提问来源于stack exchange,提问作者shihpah

