从作为SYSTEM运行的服务启动应用程序,可以与用户交互

时间:2011-11-24 20:35:29

标签: c# .net windows service

我目前只有一个应用程序,需要从我在.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);
        }
    }

我只执行try / catch如果我编写的函数GetUserName()(它确定运行explorer.exe的用户的用户名)不为null

再次重申:所需的功能是,这将启动ETCHNotify,使其能够与GetUserName()确定的当前登录用户进行交互

4 个答案:

答案 0 :(得分:4)

围绕(thisthis

发现的一些帖子的拼贴

请注意,自Windows Vista起,严禁将服务直接与用户进行交互

  

重要提示:自Windows起,服务无法直接与用户进行交互   Vista系统。因此,标题为使用的部分中提到的技术   不应在新代码中使用交互式服务。

这个“特征”被打破了,传统的智慧决定你不应该依赖它。服务不是为了提供UI或允许任何类型的直接用户交互。微软一直警告说,自Windows NT早期以来就可以避免这一功能,因为存在安全隐患。

但是,如果您绝对必须具备此功能,则有一些可能的解决方法。但我强烈建议您仔细考虑其必要性,并为您的服务探索其他设计。

使用WTSEnumerateSessions找到合适的桌面,然后CreateProcessAsUser启动该桌面上的应用程序(将STARTUPINFO结构的一部分传递给桌面的句柄)是正确的

但是,我强烈建议不要这样做。在某些环境中,例如具有许多活动用户的终端服务器主机,确定哪个桌面是“活动”桌面并不容易,甚至可能无法实现。

更传统的方法是在全局启动组中为您的服务添加一个小客户端应用程序的快捷方式。然后,此应用程序将与每个用户会话一起启动,并且可以在不使用任何用户凭据,会话和/或桌面的情况下启动其他应用程序(如果需要)。

答案 1 :(得分:1)

我不会回答这个问题,因为你已经回答了这个问题,(现在是什么?现在已经过了2。5年了?)但是总有那些正在寻找同一主题的人,并且阅读了答案......

为了让我的服务与桌面交互,无论 WHAT 桌面,MANY桌面如何,甚至服务甚至都作为桌面应用程序在SAME COMPUTER上运行!!这与我在这里所得到的一切都不重要......我不会厌烦你的细节,我只会给你肉和土豆,如果你想看到更多的话,请告诉我......

确定。我做的第一件事是创建一个广告服务。这是服务运行的一个线程,打开一个UDP套接字来监听网络上的广播。然后,使用相同的代码,我与客户端应用程序共享它,但它调用 Advertise.CLIENT ,而不是 Advertise.SERVER ...客户端打开我希望服务端口打开,然后广播一条消息:“你好......那里有人吗?”,询问他们是否有任何服务器在监听,如果有,请回复到这个IP地址你的计算机名称,IP地址和端口#我可以在哪里找到.NET远程服务...“然后它等待少量的超时时间,收集它得到的响应,如果它不止一个,它会呈现用户有一个对话框和一个响应的服务列表...客户端然后选择一个,或者,如果只有一个响应,它将调用Connect((TServerResponse) res);在那,此时,服务器正在使用带有WellKnownClientType的Remoting Services和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对象。这对我来说很好,因为服务器OWNS对象,它们是服务器SAYS的任何东西。因此,我的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接口作为服务器对象的一部分包含在内,则可以实现上述代码。

希望其中一些有用。

答案 2 :(得分:0)

注册服务时,您可以告诉它允许与桌面进行交互。您可以阅读此旧链接http://www.codeproject.com/KB/install/cswindowsservicedesktop.aspx

另外,请不要忘记您可以同时登录多个用户。

显然在Windows Vista上,与桌面进行较新的交互变得更加困难。阅读本文以寻找可能的解决方案:http://www.codeproject.com/KB/cs/ServiceDesktopInteraction.aspx

答案 3 :(得分:0)

最终为了解决这个问题,我接受了@marco的建议和他提到的帖子。我创建的服务完全独立于与用户交互的托盘应用程序。然而,我通过注册表“启动”方法安装了托盘应用程序。服务安装程序现在将安装与用户交互的应用程序......这是最安全和最完整的方法。

感谢大家的帮助。