1

我正在尝试使用 CreateProcessAsUser 启动服务,但由于某种原因,在调试时会创建多个(30 多个)EXE 实例。这些进程开始在这行代码上产生:

ret = CreateProcessAsUser(DupedToken, Path, null, ref sa, ref sa, false, 0, (IntPtr)0, "c:\\", ref si, out pi);

我使用了此示例中的代码 - http://support.microsoft.com/default.aspx?scid=kb;EN-US;889251

    [StructLayout(LayoutKind.Sequential)]
    public struct STARTUPINFO
    {
        public int cb;
        public String lpReserved;
        public String lpDesktop;
        public String lpTitle;
        public uint dwX;
        public uint dwY;
        public uint dwXSize;
        public uint dwYSize;
        public uint dwXCountChars;
        public uint dwYCountChars;
        public uint dwFillAttribute;
        public uint dwFlags;
        public short wShowWindow;
        public short cbReserved2;
        public IntPtr lpReserved2;
        public IntPtr hStdInput;
        public IntPtr hStdOutput;
        public IntPtr hStdError;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct PROCESS_INFORMATION
    {
        public IntPtr hProcess;
        public IntPtr hThread;
        public uint dwProcessId;
        public uint dwThreadId;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct SECURITY_ATTRIBUTES
    {
        public int Length;
        public IntPtr lpSecurityDescriptor;
        public bool bInheritHandle;
    }

    [DllImport("kernel32.dll", EntryPoint = "CloseHandle", SetLastError = true, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    public extern static bool CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)]
    public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes,
        ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment,
        String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation);

    [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")]
    public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess,
        ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType,
        int ImpersonationLevel, ref IntPtr DuplicateTokenHandle);





      string curFile2 = AppDomain.CurrentDomain.BaseDirectory + "OnStart.txt";

   public void createProcessAsUser()
   {
       IntPtr Token = new IntPtr(0);
        IntPtr DupedToken = new IntPtr(0);
        bool      ret;
        //Label2.Text+=WindowsIdentity.GetCurrent().Name.ToString();


        SECURITY_ATTRIBUTES sa  = new SECURITY_ATTRIBUTES();
        sa.bInheritHandle       = false;
        sa.Length               = Marshal.SizeOf(sa);
        sa.lpSecurityDescriptor = (IntPtr)0;

        Token = WindowsIdentity.GetCurrent().Token;

        const uint GENERIC_ALL = 0x10000000;

        const int SecurityImpersonation = 2;
        const int TokenType = 1;

        ret = DuplicateTokenEx(Token, GENERIC_ALL, ref sa, SecurityImpersonation, TokenType, ref DupedToken);

        if (ret == false)
             File.AppendAllText(curFile2, "DuplicateTokenEx failed with " + Marshal.GetLastWin32Error());

        else
             File.AppendAllText(curFile2,  "DuplicateTokenEx SUCCESS");

        STARTUPINFO si          = new STARTUPINFO();
        si.cb                   = Marshal.SizeOf(si);
        si.lpDesktop            = "";

        string Path;
        Path = @"C:\myEXEpath";

        PROCESS_INFORMATION pi  = new PROCESS_INFORMATION();
        ret = CreateProcessAsUser(DupedToken, Path, null, ref sa, ref sa, false, 0, (IntPtr)0, "c:\\", ref si, out pi);

        if (ret == false)
             File.AppendAllText(curFile2, "CreateProcessAsUser failed with " + Marshal.GetLastWin32Error());
        else
        {
             File.AppendAllText(curFile2, "CreateProcessAsUser SUCCESS.  The child PID is" + pi.dwProcessId);

            CloseHandle(pi.hProcess);
            CloseHandle(pi.hThread);
        }

        ret = CloseHandle(DupedToken);
        if (ret == false)
             File.AppendAllText(curFile2, Marshal.GetLastWin32Error().ToString() );
        else
             File.AppendAllText(curFile2, "CloseHandle SUCCESS");
    }

在此处输入图像描述

4

1 回答 1

1

您上面概述的步骤将在每次执行该方法时生成一个进程createProcessAsUser()。现在此方法不包含任何终止或终止进程的代码,因此重复调用此方法将生成多个进程。当您的代码显示时,该方法将只生成一个进程。

我认为真正的答案是您如何调用此方法。正如你在评论中所说

我正在尝试在用户会话中启动 .exe

我只能假设您可能从一Session开始就调用此过程,Application_BeginRequest 或其他可能多次执行的方法,具体取决于您的应用程序的设计方式(此方法的调用代码作为编辑会很棒)。

正如我之前所说,exe每次调用方法时都会执行而不是终止。如果您只希望应用程序的一个实例运行,则必须检查进程树以确定该进程是否已在运行。现在,如果您应该为每个用户运行一个进程,您将需要执行上述操作,但还要维护一个引用,即在应用程序第一次启动时创建的进程 ID。

查看下面的代码以了解更改(简化)

public void createProcessAsUser()
{
    //one process per session
    object sessionPID = Session["_servicePID"];
    if (sessionPID != null && sessionPID is int && Process.GetProcessById((int)sessionPID) != null)
        return; //<-- Return process already running for session
    else
        Session.Remove("_servicePID");

    //one process per application
    object applicationPID = Application["_applicationPID"];
    if (applicationPID != null && applicationPID is int && Process.GetProcessById((int)applicationPID) != null)
        return; //<-- Process running for application
    else
        Application.Remove("_applicationPID");

    //omitted starting code

    if (ret == false)
        // omitted log failed
    else
    {
        // omitted log started

        //for one process per session
        Session["_servicePID"] = Convert.ToInt32(pi.dwProcessId);

        //for one process per application
        Application["_applicationPID"] = Convert.ToInt32(pi.dwProcessId);

        //close handles
    }

    // omitted the rest of the method
}

这个简单的保存对为应用程序创建的进程 ID 的引用到Session每个用户一个进程的Application状态或每个应用程序实例一个进程的状态。

现在,如果这是预期的结果,您可能还想查看在应用程序关闭(正常)或会话结束时终止进程。这与我们的第一次检查非常相似,但可以如下所示完成。*请注意,这没有考虑工作进程在不调用会话 \ 应用程序结束事件的情况下关闭,这些事件也可能在应用程序启动时处理。

//session end
void Session_End(object sender, EventArgs e)
{
    object sessionPID = Session["_servicePID"];
    if (sessionPID != null && sessionPID is int)
    {
        Process runningProcess = Process.GetProcessById((int)sessionPID);
        if (runningProcess != null)
            runningProcess.Kill();
    }
}

//application end
void Application_End(object sender, EventArgs e)
{
    object applicationPID = Application["_applicationPID"];
    if (applicationPID != null && applicationPID is int && Process.GetProcessById((int)applicationPID) != null)
    {
        Process runningProcess = Process.GetProcessById((int)applicationPID);
        if (runningProcess != null)
            runningProcess.Kill();
    }
}

再次回到最初的问题,如何停止多个实例。答案是通过检查您如何启动实例(即方法的调用代码createProcessAsUser())来停止生成多个实例的能力,并相应地调整您的方法以避免多次调用。

createProcessAsUser()如果此 inst 对有关如何调用该方法的详细信息有帮助,请发布编辑。

更新1:

上下文中不存在会话\应用程序。如果该方法createProcessUser()与 ASPX 页面位于不同的类中(如教程中所示),则会发生这种情况。

正因为如此,您将需要更改一个 this 的存在,HttpContext这可以简单地通过调用来完成

HttpContext.Currrent

我已经调整了上面的方法,包括检查HttpContext

public void createProcessAsUser()
{
    //find the http context
    var ctx = HttpContext.Current;
    if (ctx == null)
        throw new Exception("No Http Context");

    //use the following code for 1 process per user session
    object sessionPID = ctx.Session["_servicePID"];
    if (sessionPID != null && sessionPID is int && Process.GetProcessById((int)sessionPID) != null)
        return; //<-- Return process already running for session
    else
        ctx.Session.Remove("_servicePID");

    //use the following code for 1 process per application instance
    object applicationPID = ctx.Application["_applicationPID"];
    if (applicationPID != null && applicationPID is int && Process.GetProcessById((int)sessionPID) != null)
        return; //<-- Process running for application
    else
        ctx.Application.Remove("_applicationPID");

    // omitted code

    if (ret == false)
    {
        //omitted logging
    }
    else
    {
        //omitted logging

        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);


        //for one process per session
        ctx.Session["_servicePID"] = Convert.ToInt32(pi.dwProcessId);

        //for one process per application
        ctx.Application["_applicationPID"] = Convert.ToInt32(pi.dwProcessId);
    }

    //omitted the rest
}

您不会在前几行中通过调用获取当前HttpContext(您必须添加)更改using System.Webvar ctx = HttpContext.Current

接下来我们只检查ctx变量是否不为空。如果它是空的,我会抛出一个异常,但是你可以随心所欲地处理它。

从那里而不是直接调用SessionApplication我已经将引用更改为ctx.Session...ctx.Application...

更新 2:

这是一个调用上述方法的 Windows 应用程序。现在这改变了球赛,因为上面的代码实际上是要以模拟的 Windows 身份启动一个进程。现在模拟通常在 WebApplications 而不是 WinForms 中完成(虽然可以完成)。

如果您没有模拟与运行应用程序的用户不同的用户。这意味着登录的用户是运行应用程序的用户。如果是这样,那么您的代码会变得容易得多。

以下是如何实现这一目标的示例。

/// <summary>
/// static process ID value
/// </summary>
static int? processID = null;

public void startProcess()
{
    //check if the processID has a value and if the process ID is active
    if (processID.HasValue && Process.GetProcessById(processID.Value) != null)
        return;

    //start a new process
    var process = new Process();
    var processStartInfo = new ProcessStartInfo(@"C:\myProg.exe");
    processStartInfo.CreateNoWindow = true;
    processStartInfo.UseShellExecute = false;
    process.StartInfo = processStartInfo;
    process.Start();
    //set the process id
    processID = process.Id;
}

同样,由于这是一个 Win Forms 应用程序,您可以使用该Process对象来启动一个进程,此 Windows 应用程序将作为运行 Windows Forms 应用程序的用户运行。在这个例子中,我们还持有对 processID 的静态引用,并检查processID(如果找到)是否已经在运行。

于 2014-01-28T03:11:46.210 回答