You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否在.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的参数需要使用本地化名称。以下是修正后的代码:

前置要求

  1. 在项目中添加引用:右键项目 → 添加引用 → COM → 选择「Microsoft Shell Controls And Automation」(对应Shell32.dll)
  2. 确保代码在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);
        }
    });
}

关键注意事项

  1. STA线程要求:Shell COM对象必须在STA线程中运行,否则会出现无响应或调用失败的情况
  2. 本地化动词:不同语言版本的Windows中,右键菜单的动词名称不同,优先枚举Verbs集合查找对应命令
  3. PowerShell运行空间配置:创建Runspace时使用默认配置,避免权限或环境变量缺失的问题
  4. 路径合法性:确保传入的文件夹路径是绝对路径且存在,否则Shell操作会失败

内容的提问来源于stack exchange,提问作者Hellagur

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 19:23:10