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

WindowsIdentity.RunImpersonated中调用ServiceController遇FileNotFoundException求助

C#身份模拟中调用ServiceController出现程序集加载失败问题

我是C#新手,正在Visual Studio 2022中开发一款应用,其中部分功能需要访问远程电脑查询运行中的服务。单独运行获取本地服务的代码、身份模拟代码(均来自微软官方示例)都正常;在同一个类中先完成身份模拟再取消模拟后调用服务查询代码,也能正常运行。但将ServiceController查询远程服务的代码放入WindowsIdentity.RunImpersonated的执行委托中时,以普通用户身份运行会出现FileNotFoundException,提示无法加载System.ServiceProcess.ServiceController程序集。

错误信息

System.IO.FileNotFoundException
  HResult=0x80070002
  Message=Could not load file or assembly 'System.ServiceProcess.ServiceController, Version=7.0.0.1, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'. The system cannot find the file specified.
  Source=Identity
  StackTrace:
   at ImpersonationDemo.<>c.<Main>b__1_0() in C:\Users\me\source\repos\TestProjects\Identity\Program.cs:line 75
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Security.Principal.WindowsIdentity.RunImpersonatedInternal(SafeAccessTokenHandle token, Action action)
   at ImpersonationDemo.Main() in C:\Users\me\source\repos\TestProjects\Identity\Program.cs:line 54

完整代码

using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.ServiceProcess;

public class Test2
{
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
public static extern bool LogonUser(String lpszUsername, String lpszDomain, String lpszPassword,
                    int dwLogonType, int dwLogonProvider, out SafeAccessTokenHandle phToken);

public static void Main()
{
    // Get the user token for the specified user, domain, and password using the   
    // unmanaged LogonUser method.   
    // The local machine name can be used for the domain name to impersonate a user on this machine.  
    Console.Write("Enter the name of the domain on which to log on: ");
    string domainName = Console.ReadLine();

    Console.Write("Enter the login of a user on {0} that you wish to impersonate: ", domainName);
    string userName = Console.ReadLine();

    Console.Write("Enter the password for {0}: ", userName);

    const int LOGON32_PROVIDER_DEFAULT = 0;
    //This parameter causes LogonUser to create a primary token.   
    const int LOGON32_LOGON_INTERACTIVE = 2;

    // Call LogonUser to obtain a handle to an access token.   
    SafeAccessTokenHandle safeAccessTokenHandle;
    bool returnValue = LogonUser(userName, domainName, Console.ReadLine(),
        LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT,
        out safeAccessTokenHandle);

    if (false == returnValue)
    {
        int ret = Marshal.GetLastWin32Error();
        Console.WriteLine("LogonUser failed with error code : {0}", ret);
        throw new System.ComponentModel.Win32Exception(ret);
    }

    Console.WriteLine("Did LogonUser Succeed? " + (returnValue ? "Yes" : "No"));
    // Check the identity.  
    Console.WriteLine("Before impersonation: " + WindowsIdentity.GetCurrent().Name);

    WindowsIdentity.RunImpersonated(
        safeAccessTokenHandle,
        // User action  
        () =>
        {
            // Check the identity.  
            Console.WriteLine("During impersonation: " + WindowsIdentity.GetCurrent().Name);

            // **** Combined the following into RunImpersonated: ****
            string computerName = "db-test-2022";

            ServiceController[] scServices;
            scServices = ServiceController.GetServices(computerName);

            Console.WriteLine(String.Format("Services running on [{0}]:", computerName));
            foreach (ServiceController scTemp in scServices)
            {
                if (scTemp.Status == ServiceControllerStatus.Running)
                {
                    Console.WriteLine("Service name: {0}\tDisplay name: {1}", scTemp.ServiceName, scTemp.DisplayName);
                }
            }
        });

    // Check the identity again.  
    Console.WriteLine("After impersonation: " + WindowsIdentity.GetCurrent().Name);
}
}

问题总结

  • 普通用户账户单独运行两个微软示例代码均无问题
  • 普通用户账户先完成身份模拟再取消模拟后调用服务查询代码,可正常运行
  • 仅当服务查询代码放入WindowsIdentity.RunImpersonated的执行范围时,才会出现找不到ServiceController程序集的错误

不想通过以管理员身份运行Visual Studio规避问题,测试发现普通和管理员账户创建的项目都需要安装NuGet包System.ServiceProcess.ServiceController,应用部署后将使用具备相应权限的服务账户运行,目前处于测试代码阶段。


问题分析与解决办法

问题根源

这是由于程序集加载上下文的权限差异导致的:
当在WindowsIdentity.RunImpersonated委托内执行代码时,CLR会使用模拟用户的上下文加载所需程序集。如果模拟的用户没有访问本地NuGet缓存目录或项目输出目录的权限,就会加载失败。而在模拟上下文外调用代码时,是用当前普通用户的上下文加载程序集,该用户拥有本地文件的访问权限,因此能正常加载。

解决办法

1. 提前加载程序集

在进入身份模拟逻辑之前,先触发ServiceController相关类型的加载,让CLR在当前普通用户上下文下把程序集加载到内存中。修改代码,在WindowsIdentity.RunImpersonated调用前添加一行:

// 提前加载ServiceController程序集,避免模拟时权限不足无法加载
_ = typeof(ServiceController);

这样程序集会提前加载到内存,模拟执行时直接使用已加载的程序集,不会再触发加载操作,也就避开了模拟用户的权限限制。

2. 配置模拟用户的文件访问权限(可选)

如果必须在模拟上下文加载程序集,需要确保模拟的用户账户对以下目录有读取权限:

  • 项目的输出目录(比如bin\Debug)
  • NuGet包缓存目录(通常为%USERPROFILE%\.nuget\packages)
    不过这种方法配置繁琐,不如提前加载高效。

无需管理员身份测试高权限代码的方法

方法一:用runas命令运行编译后的程序

编译项目后,打开命令提示符,用目标服务账户身份运行程序:

runas /user:domain\serviceaccount "C:\path\to\your\project\bin\Debug\YourApp.exe"

方法二:配置调试时的启动身份

右键项目→属性→调试→勾选“使用其他用户身份运行”,输入服务账户的凭据。这样调试时会直接以该账户启动程序,无需编写模拟代码,更贴近最终部署场景。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 11:57:12