CreateProcessAsUser Несколько экземпляров приложения?
Я пытаюсь запустить службу с помощью 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");
}
1 ответ
Шаги, описанные выше, будут генерировать один процесс на выполнение метода. createProcessAsUser()
, Теперь этот метод не содержит никакого кода для завершения или уничтожения процесса, поэтому повторный вызов этого метода приведет к созданию более одного процесса. Когда ваш код отображается, метод будет генерировать только один процесс.
Я думаю, что реальный ответ - как вы называете этот метод. Как вы заявили в комментарии
Я пытаюсь запустить.exe в пользовательской сессии
Я могу только предположить, что вы можете вызывать этот процесс из Session
start, Application_BeginRequest или другой метод, который может выполняться несколько раз в зависимости от того, как разработано ваше приложение (вызывающий код для этого метода был бы полезен для редактирования).
Как я уже говорил ранее exe
выполняется каждый раз, когда метод вызывается и не завершается. Если вам нужен только один экземпляр запущенного приложения, вам придется изучить дерево процессов, чтобы определить, запущен ли уже процесс. Теперь, если у вас должен быть один процесс, запущенный на пользователя, вам нужно будет выполнить вышеописанное, но также сохранить ссылку на идентификатор процесса, который был создан при первом запуске приложения.
Проверьте приведенный ниже код на предмет изменений (упрощенно)
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
}
Это просто сохраняет ссылку на идентификатор процесса, который был создан для приложения, либо в 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()
метод называется.
Обновление 1:
Сессия \ Приложение не существует в контексте. Это произойдет, если метод createProcessUser()
находится в другом классе, чем страница ASPX (как в учебнике).
Из-за этого вам нужно будет поменять на существование 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.Web
) позвонив var ctx = HttpContext.Current
Затем мы просто проверяем, что ctx
переменная не равна нулю. Если это ноль, я выбрасываю исключение, однако вы можете справиться с этим так, как пожелаете.
Оттуда вместо прямого звонка Session
а также Application
Я изменил ссылки на ctx.Session...
а также ctx.Application...
Обновление 2:
Это приложение для Windows, вызывающее метод выше. Теперь это меняет игру с мячом, так как приведенный выше код действительно предназначен для запуска процесса в качестве олицетворенной идентичности Windows. Теперь олицетворение обычно выполняется в веб-приложениях, а не в 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. В этом примере мы также храним статическую ссылку на process ID и проверяем, processID
(если найден) уже запущен.