Win7中如何在远程服务器的windows服务中启动一个软件进程

时间:2022-02-13 06:26:35

最近在做一个功能,在windows服务中启动一个自己的程序。

xp中很简单。但是在win7中却出现问题?

搜索了资料,具体情况如下链接所示:解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离

这样在本机上功能基本实现。

在使用过程中遇到两个问题:

1.因为我的程序是放在远程服务器上使用的,

服务器的系统是win2008server。在服务器上运行服务出现了问题。程序还是启动在session0中?

本机正常,服务器不正常,难道远程桌面的session与本机session还不相同?

查看了一下服务器的session,默认是2,本地是1。

那么我猜测可能是WTSGetActiveConsoleSessionId这个函数的问题

WTSGetActiveConsoleSessionId返回的是当前用户的SessionID,远程桌面session不算是当前?

那么我直接将远程桌面的sessionID给他赋值过去。

修改后果然可以运行。

2.第二个问题就是程序启动需要添加参数的问题。百度了半天也没有类似的说法。没办法看MSDN吧。。CreateProcessAsUser function

感觉应该是在CreateProcessAsUser 函数中,

我们先来看一下他的说明吧:

BOOL WINAPI CreateProcessAsUser(
_In_opt_ HANDLE hToken,
_In_opt_ LPCTSTR lpApplicationName,
_Inout_opt_ LPTSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCTSTR lpCurrentDirectory,
_In_ LPSTARTUPINFO lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);

仔细研究了一下第二个和第三个参数的说明:

lpApplicationName [in, optional]
The name of the module to be executed. This module can be a Windows-based application. It can be some other type of module (for example, MS-DOS or OS/2) if the appropriate subsystem is available on the local computer.
The string can specify the full path and file name of the module to execute or it can specify a partial name. In the case of a partial name, the function uses the current drive and current directory to complete the specification. The function will not use the search path. This parameter must include the file name extension; no default extension is assumed.
The lpApplicationName parameter can be NULL. In that case, the module name must be the first white space–delimited token in the lpCommandLine string. If you are using a long file name that contains a space, use quoted strings to indicate where the file name ends and the arguments begin; otherwise, the file name is ambiguous. For example, consider the string "c:\program files\sub dir\program name". This string can be interpreted in a number of ways. The system tries to interpret the possibilities in the following order:
c:\program.exe files\sub dir\program name
c:\program files\sub.exe dir\program name
c:\program files\sub dir\program.exe name
c:\program files\sub dir\program name.exe
If the executable module is a 16-bit application, lpApplicationName should be NULL, and the string pointed to by lpCommandLine should specify the executable module as well as its arguments. By default, all 16-bit Windows-based applications created by CreateProcessAsUser are run in a separate VDM (equivalent to CREATE_SEPARATE_WOW_VDM inCreateProcess).
lpCommandLine [in, out, optional]
The command line to be executed. The maximum length of this string is 32K characters. If lpApplicationName is NULL, the module name portion of lpCommandLine is limited toMAX_PATH characters.
The Unicode version of this function, CreateProcessAsUserW, can modify the contents of this string. Therefore, this parameter cannot be a pointer to read-only memory (such as aconst variable or a literal string). If this parameter is a constant string, the function may cause an access violation.
The lpCommandLine parameter can be NULL. In that case, the function uses the string pointed to by lpApplicationName as the command line.
If both lpApplicationName and lpCommandLine are non-NULL, *lpApplicationName specifies the module to execute, and *lpCommandLine specifies the command line. The new process can use GetCommandLine to retrieve the entire command line. Console processes written in C can use the argc and argv arguments to parse the command line. Because argv[0] is the module name, C programmers generally repeat the module name as the first token in the command line.
<span style="color:#ff6666;"><strong>If lpApplicationName is NULL, the first white space–delimited token of the command line specifies the module name.</strong></span> If you are using a long file name that contains a space, <strong><span style="color:#ff6666;">use quoted strings to indicate where the file name ends and the arguments begin</span> </strong>(see the explanation for the lpApplicationName parameter). If the file name does not contain an extension, .exe is appended. Therefore, if the file name extension is .com, this parameter must include the .com extension. If the file name ends in a period (.) with no extension, or if the file name contains a path, .exe is not appended. If the file name does not contain a directory path, the system searches for the executable file in the following sequence:
The directory from which the application loaded.
The current directory for the parent process.
The 32-bit Windows system directory. Use the GetSystemDirectory function to get the path of this directory.
The 16-bit Windows system directory. There is no function that obtains the path of this directory, but it is searched.
The Windows directory. Use the GetWindowsDirectory function to get the path of this directory.
The directories that are listed in the PATH environment variable. Note that this function does not search the per-application path specified by the App Paths registry key. To include this per-application path in the search sequence, use the ShellExecute function.
The system adds a null character to the command line string to separate the file name from the arguments. This divides the original string into two strings for internal processing.

返现可以这样:我们把第二个参数设为null,第三个参数commandline,我们用:比如我们的参数为 auto,
那我们的路径就可以写为:
if (!string.IsNullOrEmpty(para))
{
para = app + @" \" + para;
app = null;
}
下面就放出整个类的封装吧:

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace Vevisoft.Utility.Win7
{
/// <summary>
/// 解决vista和win7在windows服务中交互桌面权限问题:穿透Session 0 隔离
/// 用于windows服务 启动外部程序 或者截取图片等
/// 默认windows服务的权限是在session0中
/// </summary>
public class SessionUtility
{
#region 如果服务只是简单的向桌面用户Session 发送消息窗口,则可以使用WTSSendMessage 函数实现

public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;

public static void ShowMessageBox(string message, string title)
{
int resp = 0;
WTSSendMessage(
WTS_CURRENT_SERVER_HANDLE,
WTSGetActiveConsoleSessionId(),
title, title.Length,
message, message.Length,
0, 0, out resp, false);
}

[DllImport("kernel32.dll", SetLastError = true)]
public static extern int WTSGetActiveConsoleSessionId();

[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSSendMessage(
IntPtr hServer,
int SessionId,
String pTitle,
int TitleLength,
String pMessage,
int MessageLength,
int Style,
int Timeout,
out int pResponse,
bool bWait);

//在ShowMessageBox 函数中调用了WTSSendMessage 来发送信息窗口,这样我们就可以在Service 的OnStart 函数中使用,打开Service1.cs 加入下面代码:
//protected override void OnStart(string[] args)
//{
// Interop.ShowMessageBox("This a message from AlertService.",
// "AlertService Message");
//}

#endregion

/*
* 如果想通过服务向桌面用户Session 创建一个复杂UI 程序界面,
* 则需要使用CreateProcessAsUser 函数为用户创建一个新进程用来运行相应的程序。
*/

#region 复杂进程

public static void CreateProcess(string app,string para, int sessionID)
{
if (!string.IsNullOrEmpty(para))
{
para = app + @" \" + para;
app = null;
}
bool result;
IntPtr hToken = WindowsIdentity.GetCurrent().Token;
IntPtr hDupedToken = IntPtr.Zero;

var pi = new PROCESS_INFORMATION();
var sa = new SECURITY_ATTRIBUTES();
sa.Length = Marshal.SizeOf(sa);

var si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);

int dwSessionID = sessionID;
if (sessionID < 0)
dwSessionID = WTSGetActiveConsoleSessionId();
result = WTSQueryUserToken(dwSessionID, out hToken);

if (!result)
{
ShowMessageBox("WTSQueryUserToken failed", "AlertService Message");
}

result = DuplicateTokenEx(
hToken,
GENERIC_ALL_ACCESS,
ref sa,
(int) SECURITY_IMPERSONATION_LEVEL.SecurityIdentification,
(int) TOKEN_TYPE.TokenPrimary,
ref hDupedToken
);

if (!result)
{
ShowMessageBox("DuplicateTokenEx failed", "AlertService Message");
}

IntPtr lpEnvironment = IntPtr.Zero;
result = CreateEnvironmentBlock(out lpEnvironment, hDupedToken, false);

if (!result)
{
ShowMessageBox("CreateEnvironmentBlock failed", "AlertService Message");
}

result = CreateProcessAsUser(
hDupedToken,
app,
para,
ref sa, ref sa,
false, 0, IntPtr.Zero,
null, ref si, ref pi);

if (!result)
{
int error = Marshal.GetLastWin32Error();
string message = String.Format("CreateProcessAsUser Error: {0}", error);
ShowMessageBox(message, "AlertService Message");
}

if (pi.hProcess != IntPtr.Zero)
CloseHandle(pi.hProcess);
if (pi.hThread != IntPtr.Zero)
CloseHandle(pi.hThread);
if (hDupedToken != IntPtr.Zero)
CloseHandle(hDupedToken);
}

[StructLayout(LayoutKind.Sequential)]
public struct STARTUPINFO
{
public Int32 cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public Int32 dwX;
public Int32 dwY;
public Int32 dwXSize;
public Int32 dwXCountChars;
public Int32 dwYCountChars;
public Int32 dwFillAttribute;
public Int32 dwFlags;
public Int16 wShowWindow;
public Int16 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 Int32 dwProcessID;
public Int32 dwThreadID;
}

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

public enum SECURITY_IMPERSONATION_LEVEL
{
SecurityAnonymous,
SecurityIdentification,
SecurityImpersonation,
SecurityDelegation
}

public enum TOKEN_TYPE
{
TokenPrimary = 1,
TokenImpersonation
}

public const int GENERIC_ALL_ACCESS = 0x10000000;

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

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

[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool DuplicateTokenEx(
IntPtr hExistingToken,
Int32 dwDesiredAccess,
ref SECURITY_ATTRIBUTES lpThreadAttributes,
Int32 ImpersonationLevel,
Int32 dwTokenType,
ref IntPtr phNewToken);

[DllImport("wtsapi32.dll", SetLastError = true)]
public static extern bool WTSQueryUserToken(
Int32 sessionId,
out IntPtr Token);

[DllImport("userenv.dll", SetLastError = true)]
private static extern bool CreateEnvironmentBlock(
out IntPtr lpEnvironment,
IntPtr hToken,
bool bInherit);

#endregion
}

}


调用的方法如下:

 Interop.CreateProcess(_softPath, "auto", _softSessionId);

记得添加引用:

using Interop = Vevisoft.Utility.Win7.SessionUtility;


另外感谢一下Alvin Huang 的文章Win7中如何在服务中启动一个当前用户的进程——函数CreateProcessAsUser()的一次使用记录给了我灵感