![](/img/trans.png)
[英]How can I get the MainWindowHandle of a Windows 7 application running as user <foo> from within a service running as Local System?
[英]Starting application from service running as SYSTEM that can interact with the user
我目前只有一个应用程序,需要从我在.net 3.5中编码的Windows服务中启动。 该应用程序当前以运行服务的用户身份运行,在本例中为SYSTEM用户。 如果以SYSTEM用户身份运行,则不会在用户桌面上显示该应用程序。 有什么想法吗? 建议?
//constructor
private Process ETCHNotify = new Process();
//StartService()
ETCHNotify.StartInfo.FileName = baseDir + "\\EtchNotify.exe";
ETCHNotify.StartInfo.UseShellExecute = false;
//BackgroundWorkerThread_DoWork()
if (!systemData.GetUserName().Equals(""))
{
// start ETCHNotify
try {
ETCHNotify.Start();
}
catch (Exception ex)
{
systemData.Run("ERR: Notify can't start: " + ex.Message);
}
}
如果我编写的函数GetUserName()(确定运行explorer.exe的用户的用户名)不为空,则仅执行try / catch
再次重申:所需的功能是,它以允许用户与GetUserName()确定的当前登录用户进行交互的状态启动ETCHNotify
请注意,从Windows Vista开始,严格禁止服务与用户直接交互 :
重要:从Windows Vista开始,服务无法直接与用户交互。 因此,标题为“使用交互式服务”的部分中提到的技术不应在新代码中使用。
这种“功能”已被打破,传统的观点表明您无论如何都不应该依赖它。 服务并非旨在提供UI或允许任何类型的直接用户交互。 Microsoft一直在警告,由于可能的安全风险,自Windows NT诞生以来就应避免使用此功能。
但是,如果您绝对必须具有此功能,则有一些可能的解决方法。 但我强烈敦促您仔细考虑其必要性,并为您的服务探索替代设计。
使用WTSEnumerateSessions查找正确的桌面,然后使用CreateProcessAsUser在该桌面上启动应用程序(将其句柄作为STARTUPINFO结构的一部分传递给它)是正确的。
但是,我强烈建议您不要这样做。 在某些环境中,例如具有许多活动用户的终端服务器主机,确定哪个桌面是“活动”桌面并不容易,甚至可能无法实现。
一种更传统的方法是在全局启动组中为您的服务提供指向小型客户端应用程序的快捷方式。 然后,该应用程序将与每个用户会话一起启动,并且可以用于启动其他应用程序(如果需要的话),而无需花费用户凭据,会话和/或桌面的麻烦。
我不会回答这个问题,因为您已经回答了(哦,什么?现在已经有2.5岁了!!)但是总有一些人在寻找相同的主题并阅读答案。 。
为了使我的服务与桌面交互,无论是什么桌面,也没有多少桌面,或者该服务是否甚至已经在相同的计算机上作为桌面应用程序运行! 一切与我到这里来都没有关系...我不会给您带来任何细节,我只给您肉和土豆,您让我知道是否想了解更多...
好。 我要做的第一件事是创建广告服务 。 这是服务运行的线程,打开UDP套接字以侦听网络上的广播。 然后,使用同一段代码,我与客户端应用程序共享了该段代码,但是它调用了Advertise.CLIENT而不是Advertise.SERVER ... CLIENT打开了我希望该服务打开的端口,并广播了一条消息,“你好...那里有人吗?”,询问他们是否在监听任何服务器,如果是,请使用您的计算机名,IP地址和端口号回复此IP地址,我可以在其中找到.NET remoting Services ...”,然后它等待一小段超时时间,收集它得到的响应,如果超过一个,它将向用户显示一个对话框和一个响应的服务列表。然后,客户端选择一个,或者,如果仅响应一个,它将选择Connect( (TServerResponse) res);在该位置上进行连接,此时,服务器正在使用带有WellKnownClientType和WellKnownServerType的 Remoting Services。把自己放在那里
我认为您对我的“自动服务定位器”不太感兴趣,因为很多人都不喜欢UDP,而当您的应用开始在大型网络上广播时,这种情况就更是如此。 因此,我假设您对我的RemotingHelper更加感兴趣,它将使客户端连接到服务器。 看起来像这样:
public static Object GetObject(Type type)
{
try {
if(_wellKnownTypes == null) {
InitTypeCache();
}
WellKnownClientTypeEntry entr = (WellKnownClientTypeEntry)_wellKnownTypes[type];
if(entr == null) {
throw new RemotingException("Type not found!");
}
return System.Activator.GetObject(entr.ObjectType, entr.ObjectUrl);
} catch(System.Net.Sockets.SocketException sex) {
DebugHelper.Debug.OutputDebugString("SocketException occured in RemotingHelper::GetObject(). Error: {0}.", sex.Message);
Disconnect();
if(Connect()) {
return GetObject(type);
}
}
return null;
}
private static void InitTypeCache()
{
if(m_AdvertiseServer == null) {
throw new RemotingException("AdvertisementServer cannot be null when connecting to a server.");
}
_wellKnownTypes = new Dictionary<Type, WellKnownClientTypeEntry>();
Dictionary<string, object> channelProperties = new Dictionary<string, object>();
channelProperties["port"] = 0;
channelProperties["name"] = m_AdvertiseServer.ChannelName;
Dictionary<string, object> binFormatterProperties = new Dictionary<string, object>();
binFormatterProperties["typeFilterLevel"] = "Full";
if(Environment.UserInteractive) {
BinaryServerFormatterSinkProvider binFormatterProvider = new BinaryServerFormatterSinkProvider(binFormatterProperties, null);
_serverChannel = new TcpServerChannel(channelProperties, binFormatterProvider);
// LEF: Only if we are coming form OUTSIDE the SERVICE do we want to register the channel, since the SERVICE already has this
// channel registered in this AppDomain.
ChannelServices.RegisterChannel(_serverChannel, false);
}
System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IPawnStatServiceStatus)));
RegisterType(typeof(IPawnStatServiceStatus),m_AdvertiseServer.RunningStatusURL.ToString());
System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IPawnStatService)));
RegisterType(typeof(IPawnStatService), m_AdvertiseServer.RunningServerURL.ToString());
System.Diagnostics.Debug.Write(string.Format("Registering: {0}...\n", typeof(IServiceConfiguration)));
RegisterType(typeof(IServiceConfiguration), m_AdvertiseServer.RunningConfigURL.ToString());
}
[SecurityPermission(SecurityAction.Demand, Flags=SecurityPermissionFlag.RemotingConfiguration, RemotingConfiguration=true)]
public static void RegisterType(Type type, string serviceUrl)
{
WellKnownClientTypeEntry clientType = new WellKnownClientTypeEntry(type, serviceUrl);
if(clientType != RemotingConfiguration.IsWellKnownClientType(type)) {
RemotingConfiguration.RegisterWellKnownClientType(clientType);
}
_wellKnownTypes[type] = clientType;
}
public static bool Connect()
{
// Init the Advertisement Service, and Locate any listening services out there...
m_AdvertiseServer.InitClient();
if(m_AdvertiseServer.LocateServices(iTimeout)) {
if(!Connected) {
bConnected = true;
}
} else {
bConnected = false;
}
return Connected;
}
public static void Disconnect()
{
if(_wellKnownTypes != null) {
_wellKnownTypes.Clear();
}
_wellKnownTypes = null;
if(_serverChannel != null) {
if(Environment.UserInteractive) {
// LEF: Don't unregister the channel, because we are running from the service, and we don't want to unregister the channel...
ChannelServices.UnregisterChannel(_serverChannel);
// LEF: If we are coming from the SERVICE, we do *NOT* want to unregister the channel, since it is already registered!
_serverChannel = null;
}
}
bConnected = false;
}
}
因此,这就是我的远程处理代码的精髓,它使我可以编写一个客户端,而不必知道服务的安装位置或网络上运行了多少服务。 这使我可以通过网络或在本地计算机上与其通信。 而且有两个或两个以上的人运行该应用程序不是问题,但是,您可能会遇到问题。 现在,我的代码中有一些复杂的回调代码,我在其中注册了通过远程处理通道进行的事件,因此,在将通知发送给客户端之前,我必须要有代码来检查客户端是否仍然处于连接状态。 另外,如果您要为多个用户运行,则可能不想使用Singleton对象。 对我来说很好,因为服务器拥有对象,而无论服务器说什么,它们都是对象。 因此,例如,我的STATS对象是一个Singleton。 当每个人都将看到相同的数据时,没有理由为每个连接创建实例吗?
如有必要,我可以提供更多代码。 当然,这只是使该工作原理全面发展的一小部分……更不用说订阅提供商了。
为了完整起见,我包括了代码块,以在整个过程中保持您的服务连接。
public override object InitializeLifetimeService()
{
ILease lease = (ILease)base.InitializeLifetimeService();
if(lease.CurrentState == LeaseState.Initial) {
lease.InitialLeaseTime = TimeSpan.FromHours(24);
lease.SponsorshipTimeout = TimeSpan.FromSeconds(30);
lease.RenewOnCallTime = TimeSpan.FromHours(1);
}
return lease;
}
#region ISponsor Members
[SecurityPermissionAttribute(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.Infrastructure)]
public TimeSpan Renewal(ILease lease)
{
return TimeSpan.FromHours(12);
}
#endregion
如果将ISponsor接口作为服务器对象的一部分包含在内,则可以实现上述代码。
希望其中一些有用。
注册服务时,可以告诉它允许与桌面进行交互。 您可以阅读此旧版链接http://www.codeproject.com/KB/install/cswindowsservicedesktop.aspx
另外,请不要忘记您可以同时登录多个用户。
显然,在Windows Vista和更高版本上,与桌面的交互变得更加困难。 请阅读以下内容以获得潜在的解决方案: http : //www.codeproject.com/KB/cs/ServiceDesktopInteraction.aspx
最终为了解决这个问题,我接受了@marco的建议和他提到的帖子。 我创建的服务完全独立于与用户交互的任务栏应用程序。 但是,我确实通过服务的注册表“启动”方法安装了Tray应用程序。 服务安装程序现在还将安装与用户交互的应用程序。这是最安全,最完整的方法。
谢谢大家的帮助。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.