能否在.NET Framework DLL中使用PowerShell实例?快速访问操作异常求助
解决.NET Framework DLL中实现Windows 10快速访问文件夹的固定/取消固定问题
问题背景
尝试将Stack Overflow中「Programatically Pin\UnPin the folder from quick access menu in windows 10」的代码集成到.NET Framework DLL时,遇到以下两个问题:
问题1:PowerShell调用Shell.Application抛出CLR无效程序错误
执行代码时抛出错误:Common Language Runtime detected an invalid program.,代码如下:
private void PinFolderToQuickAccess(string path) { try { using (var runspace = RunspaceFactory.CreateRunspace()) { runspace.Open(); var ps = PowerShell.Create(); var shellApplication = ps.AddCommand("New-Object").AddParameter("ComObject", "shell.application").Invoke(); dynamic nameSpace = shellApplication.FirstOrDefault()?.Methods["NameSpace"].Invoke(path); nameSpace?.Self.InvokeVerb("pintohome"); } } catch (Exception e) { Console.Error.WriteLine("Err: {0}", e); } } private void UnpinFolderFromQuickAccess(string path) { try { using (var runspace = RunspaceFactory.CreateRunspace()) { runspace.Open(); var ps = PowerShell.Create(); var removeScript = $"((New-Object -ComObject shell.application).Namespace(\"shell:::\{{679f85cb-0220-4080-b29b-5540cc05aab6}}\").Items() | Where-Object {{ $_.Path -EQ \"{path}\" }}).InvokeVerb(\"unpinfromhome\")"; ps.AddScript(removeScript); ps.Invoke(); } } catch (Exception e) { Console.Error.WriteLine("Err: {0}", e); } } [TestMethod] public void RemoveFromQuickAccess_WithAddFirst() { QuickAccessHandler handler = new QuickAccessHandler(); string testPath = @"C:\Users\hp\Downloads\Compressed"; handler.RemoveFromQuickAccess(testPath); }
问题2:直接调用Shell32方案卡在InvokeVerb("pintohome")
执行代码时,程序卡在f.Self.InvokeVerb("pintohome")处无响应,代码如下:
private void PinFolderToQuickAccess(string path) { try { Type shellAppType = Type.GetTypeFromProgID("Shell.Application"); Object shell = Activator.CreateInstance(shellAppType); Shell32.Folder2 f = (Shell32.Folder2)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { path }); f.Self.InvokeVerb("pintohome"); } catch (Exception e) { Console.Error.WriteLine("Err: {0}", e); } } private void UnpinFolderFromQuickAccess(string path) { try { Type shellAppType = Type.GetTypeFromProgID("Shell.Application"); Object shell = Activator.CreateInstance(shellAppType); Shell32.Folder2 f2 = (Shell32.Folder2)shellAppType.InvokeMember("NameSpace", System.Reflection.BindingFlags.InvokeMethod, null, shell, new object[] { "shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}" }); foreach (FolderItem fi in f2.Items()) { if (fi.Path == path) { ((FolderItem)fi).InvokeVerb("unpinfromhome"); } } } catch (Exception e) { Console.Error.WriteLine("Err: {0}", e); } }
解决方案
关于.NET Framework DLL中使用PowerShell实例的可行性
完全可以,但需要注意PowerShell运行空间的配置以及COM对象跨边界调用的类型问题,避免直接在C#中动态操作PowerShell返回的COM对象。
问题1的修正方案
错误根源是直接在C#中调用PowerShell返回的COM对象方法,导致CLR类型校验失败。正确做法是将所有Shell操作逻辑放在PowerShell脚本内执行,避免跨边界的类型交互:
private void PinFolderToQuickAccess(string path) { try { using (var runspace = RunspaceFactory.CreateRunspace(RunspaceConfiguration.Create())) { runspace.Open(); using (var ps = PowerShell.Create()) { ps.Runspace = runspace; ps.AddScript($@" $shell = New-Object -ComObject shell.application $folder = $shell.NameSpace('{path}') if ($folder) {{ $folder.Self.InvokeVerb('pintohome') }} "); var results = ps.Invoke(); if (ps.HadErrors) { foreach (var err in ps.Streams.Error) { Console.Error.WriteLine("PS Err: {0}", err.ToString()); } } } } } catch (Exception e) { Console.Error.WriteLine("Err: {0}", e); } } private void UnpinFolderFromQuickAccess(string path) { try { using (var runspace = RunspaceFactory.CreateRunspace(RunspaceConfiguration.Create())) { runspace.Open(); using (var ps = PowerShell.Create()) { ps.Runspace = runspace; ps.AddScript($@" $shell = New-Object -ComObject shell.application $quickAccess = $shell.NameSpace('shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}') foreach ($item in $quickAccess.Items()) {{ if ($item.Path -eq '{path}') {{ $item.InvokeVerb('unpinfromhome') break }} }} "); var results = ps.Invoke(); if (ps.HadErrors) { foreach (var err in ps.Streams.Error) { Console.Error.WriteLine("PS Err: {0}", err.ToString()); } } } } } catch (Exception e) { Console.Error.WriteLine("Err: {0}", e); } }
问题2的修正方案
卡住的主要原因是COM对象要求STA线程,以及部分系统(如中文系统)中InvokeVerb的参数需要使用本地化名称。以下是修正后的代码:
前置要求
- 在项目中添加引用:右键项目 → 添加引用 → COM → 选择「Microsoft Shell Controls And Automation」(对应Shell32.dll)
- 确保代码在STA线程中执行(如果DLL被非STA线程调用,需要切换线程)
// 辅助方法:在STA线程中执行操作 private void RunInSTAThread(Action action) { if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) { action(); return; } var thread = new Thread(() => action()); thread.SetApartmentState(ApartmentState.STA); thread.Start(); thread.Join(); } private void PinFolderToQuickAccess(string path) { RunInSTAThread(() => { try { Type shellAppType = Type.GetTypeFromProgID("Shell.Application"); object shell = Activator.CreateInstance(shellAppType); Shell32.Folder folder = (Shell32.Folder)shellAppType.InvokeMember( "NameSpace", BindingFlags.InvokeMethod, null, shell, new object[] { path }); if (folder != null) { Shell32.FolderItem folderItem = folder.Self; // 优先使用本地化动词名称,避免系统语言差异 var verb = folderItem.Verbs().Cast<Shell32.FolderItemVerb>() .FirstOrDefault(v => v.Name.Equals("固定到快速访问", StringComparison.OrdinalIgnoreCase) || v.Name.Equals("pintohome", StringComparison.OrdinalIgnoreCase)); verb?.DoIt(); } } catch (Exception e) { Console.Error.WriteLine("Err: {0}", e); } }); } private void UnpinFolderFromQuickAccess(string path) { RunInSTAThread(() => { try { Type shellAppType = Type.GetTypeFromProgID("Shell.Application"); object shell = Activator.CreateInstance(shellAppType); const string quickAccessClsid = "shell:::{679f85cb-0220-4080-b29b-5540cc05aab6}"; Shell32.Folder quickAccessFolder = (Shell32.Folder)shellAppType.InvokeMember( "NameSpace", BindingFlags.InvokeMethod, null, shell, new object[] { quickAccessClsid }); if (quickAccessFolder != null) { foreach (Shell32.FolderItem item in quickAccessFolder.Items()) { if (item.Path.Equals(path, StringComparison.OrdinalIgnoreCase)) { var verb = item.Verbs().Cast<Shell32.FolderItemVerb>() .FirstOrDefault(v => v.Name.Equals("从快速访问取消固定", StringComparison.OrdinalIgnoreCase) || v.Name.Equals("unpinfromhome", StringComparison.OrdinalIgnoreCase)); verb?.DoIt(); break; } } } } catch (Exception e) { Console.Error.WriteLine("Err: {0}", e); } }); }
关键注意事项
- STA线程要求:Shell COM对象必须在STA线程中运行,否则会出现无响应或调用失败的情况
- 本地化动词:不同语言版本的Windows中,右键菜单的动词名称不同,优先枚举Verbs集合查找对应命令
- PowerShell运行空间配置:创建Runspace时使用默认配置,避免权限或环境变量缺失的问题
- 路径合法性:确保传入的文件夹路径是绝对路径且存在,否则Shell操作会失败
内容的提问来源于stack exchange,提问作者Hellagur
相关产品推荐
相关产品推荐

