从作为SYSTEM运行的服务启动应用程序,该服务可以与用户交互
本文关键字:服务 交互 用户 启动 SYSTEM 运行 应用程序 | 更新日期: 2023-09-27 18:15:34
我目前有一个单一的应用程序,需要从一个windows服务启动,我在。net 3.5编码。这个应用程序当前以运行服务的用户身份运行,在我的例子中是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
再次重申:期望的功能是,这启动ETCHNotify的状态,允许它与当前登录的用户进行交互,由GetUserName()确定
拼贴的一些帖子发现(这个和这个)
请注意,在Windows Vista中,服务是严格禁止直接与用户交互的:
重要:服务不能直接与Windows用户交互Vista。因此,在"使用"一节中提到的技术交互式服务不应该在新代码中使用。
这个"特性"是坏的,传统观念告诉你无论如何都不应该依赖它。服务并不意味着提供UI或允许任何类型的直接用户交互。由于可能存在安全风险,微软从Windows NT早期开始就一直警告要避免使用此功能。
但是,如果您绝对必须具有此功能,则有一些可能的解决方案。但我强烈建议您仔细考虑它的必要性,并为您的服务探索其他设计。使用wtsenumeratessessions找到正确的桌面,然后CreateProcessAsUser在该桌面上启动应用程序(您将桌面句柄作为STARTUPINFO结构的一部分传递给它)是正确的。
但是,我强烈建议不要这样做。在某些环境中,例如终端服务器主机有许多活跃用户,确定哪个桌面是"活跃"的并不容易,甚至可能不可能。
更传统的方法是在全局启动组中为你的服务添加一个小客户端应用程序的快捷方式。这个应用程序将启动每一个用户会话,并可以用来启动其他应用程序(如果需要的话),没有任何杂耍的用户凭证,会话和/或桌面。
最终为了解决这个问题,我采纳了@marco和他提到的帖子的建议。我已经创建了完全独立于与用户交互的托盘应用程序的服务。然而,我确实通过注册表的"启动"方法安装了托盘应用程序。服务安装程序现在将安装与用户交互的应用程序…这是最安全、最完整的方法。
谢谢大家的帮助。
我本来不想回答这个问题的,因为你已经回答过了,(哦,什么?现在已经2.5岁了!?)但总有一些人在寻找同样的话题,并阅读答案……
为了让我的服务与桌面交互,无论是什么桌面,也不是,有多少桌面,也不是服务甚至与桌面应用程序在同一台计算机上运行!!这些都跟我现在得到的东西无关我就不跟你讲细节了,我只给你肉和土豆,还有你,如果你想看更多的话,告诉我。
Ok。我做的第一件事是创建一个广告服务。这是一个线程,服务运行,打开一个UDP套接字监听网络上的广播。然后,使用同一段代码,我将其与客户端应用程序共享,但它调用Advertise。CLIENT,而不是 advertising . server …客户端打开我期望服务开启的端口,并广播一条消息:"Hello…有人在那里吗?? ?",询问他们是否有任何服务器在监听,如果是,回复这个IP地址与您的计算机名称,IP地址和端口#,我可以找到。net远程服务…然后它等待一小段超时时间,收集得到的响应,如果多于一个,它会向用户显示一个对话框和响应的服务列表……然后客户端选择一个,或者,如果只有一个响应,它将调用Connect((TServerResponse) res);在那上面,连接起来。在这一点上,服务器正在使用远程服务与WellKnownClientType和WellKnownServerType把自己放在那里…
我认为你对我的"自动服务定位器"不太感兴趣,因为很多人不喜欢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