我该如何使用C#从Windows服务中运行一个EXE
程序?
这是我的代码:
System.Diagnostics.Process.Start(@"E:\PROJECT XL\INI SQLLOADER\ConsoleApplication2\ConsoleApplication2\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe");
当我运行这个服务时,应用程序没有启动。
我的代码有什么问题?
我该如何使用C#从Windows服务中运行一个EXE
程序?
这是我的代码:
System.Diagnostics.Process.Start(@"E:\PROJECT XL\INI SQLLOADER\ConsoleApplication2\ConsoleApplication2\ConsoleApplication2\bin\Debug\ConsoleApplication2.exe");
当我运行这个服务时,应用程序没有启动。
我的代码有什么问题?
由于Windows服务没有运行在任何特定用户的上下文中,因此无法启动其他应用程序。与常规Windows应用程序不同,服务现在在隔离的会话中运行,并且禁止与用户或桌面交互。这使得该应用程序无处可运行。
有关这些相关问题的答案,请参阅以下信息:
你可能已经意识到,最好的解决方案是创建一个标准的Windows应用程序,而不是服务。这些应用程序旨在由特定用户运行,并与该用户的桌面相关联。这样,您可以随时运行其他应用程序,使用您已经展示的代码。我尝试了这篇文章Code Project,对我来说运行得很好。我也使用了这段代码。这篇文章通过截图进行了很好的解释。
我正在为这种情况添加必要的解释。
当您登录时,系统会为您分配一个唯一的Session ID。在Windows Vista中,第一个登录计算机的用户被操作系统分配Session ID 1。下一个登录的用户将被分配Session ID 2,依此类推。您可以从任务管理器的“用户”选项卡中查看分配给每个登录用户的Session ID。
但是,您的Windows服务被带入Session ID 0。该会话与其他会话隔离。这最终会防止Windows服务调用在用户会话下运行的应用程序,例如1或2。
为了从Windows服务调用应用程序,您需要从winlogon.exe中复制控件,该控件作为当前登录的用户,如下面的屏幕截图所示。
重要代码
// obtain the process id of the winlogon process that
// is running within the currently active session
Process[] processes = Process.GetProcessesByName("winlogon");
foreach (Process p in processes)
{
if ((uint)p.SessionId == dwSessionId)
{
winlogonPid = (uint)p.Id;
}
}
// obtain a handle to the winlogon process
hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid);
// obtain a handle to the access token of the winlogon process
if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken))
{
CloseHandle(hProcess);
return false;
}
// Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser
// I would prefer to not have to use a security attribute variable and to just
// simply pass null and inherit (by default) the security attributes
// of the existing token. However, in C# structures are value types and therefore
// cannot be assigned the null value.
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);
// copy the access token of the winlogon process;
// the newly created token will be a primary token
if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup))
{
CloseHandle(hProcess);
CloseHandle(hPToken);
return false;
}
STARTUPINFO si = new STARTUPINFO();
si.cb = (int)Marshal.SizeOf(si);
// interactive window station parameter; basically this indicates
// that the process created can display a GUI on the desktop
si.lpDesktop = @"winsta0\default";
// flags that specify the priority and creation method of the process
int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE;
// create a new process in the current User's logon session
bool result = CreateProcessAsUser(hUserTokenDup, // client's access token
null, // file to execute
applicationName, // command line
ref sa, // pointer to process SECURITY_ATTRIBUTES
ref sa, // pointer to thread SECURITY_ATTRIBUTES
false, // handles are not inheritable
dwCreationFlags, // creation flags
IntPtr.Zero, // pointer to new environment block
null, // name of current directory
ref si, // pointer to STARTUPINFO structure
out procInfo // receives information about new process
);
您可以使用Windows任务计划程序来实现此目的,有很多类库可以帮助您,例如TaskScheduler。
例如,考虑我们想要安排一个任务,在五秒钟后执行一次:
using (var ts = new TaskService())
{
var t = ts.Execute("notepad.exe")
.Once()
.Starting(DateTime.Now.AddSeconds(5))
.AsTask("myTask");
}
notepad.exe将会在五秒钟后被执行。
如需详细信息和更多资讯,请访问wiki
如果您知道需要哪个程序集中的类及方法,您可以像下面这样自行调用:
Assembly assembly = Assembly.LoadFrom("yourApp.exe");
Type[] types = assembly.GetTypes();
foreach (Type t in types)
{
if (t.Name == "YourClass")
{
MethodInfo method = t.GetMethod("YourMethod",
BindingFlags.Public | BindingFlags.Instance);
if (method != null)
{
ParameterInfo[] parameters = method.GetParameters();
object classInstance = Activator.CreateInstance(t, null);
var result = method.Invoke(classInstance, parameters.Length == 0 ? null : parameters);
break;
}
}
}
得票最高的答案并没有错,但与我会发表的相反。我认为这完全可行,可以启动一个exe文件,并且你可以在任何用户的上下文中执行此操作。逻辑上讲,你不能有任何用户界面或要求用户输入......
以下是我的建议:
例如,启动plink.exe。 甚至可以监听输出:
var psi = new ProcessStartInfo()
{
FileName = "./Client/plink.exe", //path to your *.exe
Arguments = "-telnet -P 23 127.0.0.1 -l myUsername -raw", //arguments
RedirectStandardError = true,
RedirectStandardOutput = true,
RedirectStandardInput = true,
UseShellExecute = false,
CreateNoWindow = true //no window, you can't show it anyway
};
var p = Process.Start(psi);
我选择了默认的LocalSystem帐户,比Local Service更好。没有必要输入特定用户的登录信息,它可以正常工作。甚至没有勾选“允许服务与桌面交互”复选框,如果您需要更高的权限,则可以勾选。
最后,我只想说答案排名第一的回答与我的回答完全相反,但我们两个都是正确的,这只是问题的解释方式不同而已 :-D。如果你现在说你不能使用Windows服务项目类型-你可以,但我以前遇到过安装不稳定的情况,直到我找到了NSSM。
[StructLayout(LayoutKind.Sequential)]
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[StructLayout(LayoutKind.Sequential)]
internal struct SECURITY_ATTRIBUTES
{
public uint nLength;
public IntPtr lpSecurityDescriptor;
public bool bInheritHandle;
}
[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public uint 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;
}
internal enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}
internal enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}
public static class ProcessAsUser
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool CreateProcessAsUser(
IntPtr hToken,
string lpApplicationName,
string lpCommandLine,
ref SECURITY_ATTRIBUTES lpProcessAttributes,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx", SetLastError = true)]
private static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
uint dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
UInt32 DesiredAccess,
ref IntPtr TokenHandle);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(
ref IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);
[DllImport("userenv.dll", SetLastError = true)]
private static extern bool DestroyEnvironmentBlock(
IntPtr lpEnvironment);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(
IntPtr hObject);
private const short SW_SHOW = 5;
private const uint TOKEN_QUERY = 0x0008;
private const uint TOKEN_DUPLICATE = 0x0002;
private const uint TOKEN_ASSIGN_PRIMARY = 0x0001;
private const int GENERIC_ALL_ACCESS = 0x10000000;
private const int STARTF_USESHOWWINDOW = 0x00000001;
private const int STARTF_FORCEONFEEDBACK = 0x00000040;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private static bool LaunchProcessAsUser(string cmdLine, IntPtr token, IntPtr envBlock)
{
bool result = false;
PROCESS_INFORMATION pi = new PROCESS_INFORMATION();
SECURITY_ATTRIBUTES saProcess = new SECURITY_ATTRIBUTES();
SECURITY_ATTRIBUTES saThread = new SECURITY_ATTRIBUTES();
saProcess.nLength = (uint)Marshal.SizeOf(saProcess);
saThread.nLength = (uint)Marshal.SizeOf(saThread);
STARTUPINFO si = new STARTUPINFO();
si.cb = (uint)Marshal.SizeOf(si);
//if this member is NULL, the new process inherits the desktop
//and window station of its parent process. If this member is
//an empty string, the process does not inherit the desktop and
//window station of its parent process; instead, the system
//determines if a new desktop and window station need to be created.
//If the impersonated user already has a desktop, the system uses the
//existing desktop.
si.lpDesktop = @"WinSta0\Default"; //Modify as needed
si.dwFlags = STARTF_USESHOWWINDOW | STARTF_FORCEONFEEDBACK;
si.wShowWindow = SW_SHOW;
//Set other si properties as required.
result = CreateProcessAsUser(
token,
null,
cmdLine,
ref saProcess,
ref saThread,
false,
CREATE_UNICODE_ENVIRONMENT,
envBlock,
null,
ref si,
out pi);
if (result == false)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
FilesUtilities.WriteLog(message,FilesUtilities.ErrorType.Info);
}
return result;
}
private static IntPtr GetPrimaryToken(int processId)
{
IntPtr token = IntPtr.Zero;
IntPtr primaryToken = IntPtr.Zero;
bool retVal = false;
Process p = null;
try
{
p = Process.GetProcessById(processId);
}
catch (ArgumentException)
{
string details = String.Format("ProcessID {0} Not Available", processId);
FilesUtilities.WriteLog(details, FilesUtilities.ErrorType.Info);
throw;
}
//Gets impersonation token
retVal = OpenProcessToken(p.Handle, TOKEN_DUPLICATE, ref token);
if (retVal == true)
{
SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES();
sa.nLength = (uint)Marshal.SizeOf(sa);
//Convert the impersonation token into Primary token
retVal = DuplicateTokenEx(
token,
TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
ref sa,
(int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int)TOKEN_TYPE.TokenPrimary,
ref primaryToken);
//Close the Token that was previously opened.
CloseHandle(token);
if (retVal == false)
{
string message = String.Format("DuplicateTokenEx Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
}
else
{
string message = String.Format("OpenProcessToken Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
//We'll Close this token after it is used.
return primaryToken;
}
private static IntPtr GetEnvironmentBlock(IntPtr token)
{
IntPtr envBlock = IntPtr.Zero;
bool retVal = CreateEnvironmentBlock(ref envBlock, token, false);
if (retVal == false)
{
//Environment Block, things like common paths to My Documents etc.
//Will not be created if "false"
//It should not adversley affect CreateProcessAsUser.
string message = String.Format("CreateEnvironmentBlock Error: {0}", Marshal.GetLastWin32Error());
FilesUtilities.WriteLog(message, FilesUtilities.ErrorType.Info);
}
return envBlock;
}
public static bool Launch(string appCmdLine /*,int processId*/)
{
bool ret = false;
//Either specify the processID explicitly
//Or try to get it from a process owned by the user.
//In this case assuming there is only one explorer.exe
Process[] ps = Process.GetProcessesByName("explorer");
int processId = -1;//=processId
if (ps.Length > 0)
{
processId = ps[0].Id;
}
if (processId > 1)
{
IntPtr token = GetPrimaryToken(processId);
if (token != IntPtr.Zero)
{
IntPtr envBlock = GetEnvironmentBlock(token);
ret = LaunchProcessAsUser(appCmdLine, token, envBlock);
if (envBlock != IntPtr.Zero)
DestroyEnvironmentBlock(envBlock);
CloseHandle(token);
}
}
return ret;
}
}
执行代码,只需这样调用:
string szCmdline = "AbsolutePathToYourExe\\ExeNameWithoutExtension";
ProcessAsUser.Launch(szCmdline);
我认为您正在将.exe文件复制到不同的位置。这可能是问题所在。当您复制exe时,未复制其依赖项。
因此,您可以将所有依赖的dll放入GAC中,以便任何.net exe都可以访问它
否则,请勿将exe复制到新位置。只需创建一个环境变量并在c#中调用exe。由于路径在环境变量中定义,因此c#程序可以访问exe。
更新:
以前我在我的c#.net 3.5项目中遇到了类似的问题,我试图从c#.net代码运行一个.exe文件,而那个exe实际上是另一个项目exe(我添加了一些支持dll来支持我的功能),我在我的exe应用程序中使用了那些dll方法。最后,我通过将该应用程序创建为同一解决方案的单独项目,并将该项目输出添加到我的部署项目中来解决了这个问题。根据这种情况,我回答说,如果这不是他想要的,那么我非常抱歉。
System.Diagnostics.Process.Start("Exe 名称");