1

在 Windows 7 上使用 .NET 窗口服务显示消息时出现问题。它在 Windows XP 上正常工作。我知道它在 Windows 7 上不起作用,如 Microsoft 网站和包括 Stackoverflow 在内的一些论坛中所述。我已经关注如 Stackoverflow 中所述,来自 Pinvoke.NET 的以下示例(使用 WTSSendMessage)。但它也不起作用。该示例在 Windows XP 上正常工作。有人可以帮助我,因为这是一个需要修复的非常大的问题尽快,因为我们已经迁移到 Windows7。

签名:

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

变量声明:

public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
public static int WTS_CURRENT_SESSION = -1;

代码:

  bool result = false;
    String title = "Hello";
    int tlen = title.Length;
    String msg = "Terminal Service!";
    int mlen = msg.Length;
    int resp = 0;
    result = WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, WTS_CURRENT_SESSION, title, tlen, 
    msg, mlen, 0, 0, out resp, false);
    int err = Marshal.GetLastWin32Error();
    System.Console.WriteLine("result:{0}, errorCode:{1}, response:{2}", result, err, resp);
4

4 回答 4

5

WTS_CURRENT_SESSION对于 Windows 7 或更高版本上的服务,将等同于会话 0,不允许用户交互。您必须枚举所有活动会话并向每个会话发送消息(应该只是一个,除非计算机正在运行终端服务)。类似于以下内容:

签名:

    [DllImport("wtsapi32.dll", SetLastError = true)]
    static extern int WTSEnumerateSessions(
                    System.IntPtr hServer,
                    int Reserved,
                    int Version,
                    ref System.IntPtr ppSessionInfo,
                    ref int pCount);

    public enum WTS_CONNECTSTATE_CLASS
    {
        WTSActive,
        WTSConnected,
        WTSConnectQuery,
        WTSShadow,
        WTSDisconnected,
        WTSIdle,
        WTSListen,
        WTSReset,
        WTSDown,
        WTSInit
    }

辅助功能:

public static List<Int32> GetActiveSessions(System.IntPtr server)
{
    List<Int32> ret = new List<int>();
    IntPtr ppSessionInfo = IntPtr.Zero;

    Int32 count = 0;
    Int32 retval = WTSEnumerateSessions(server, 0, 1, ref ppSessionInfo, ref count);
    Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));

    Int64 current = (int)ppSessionInfo;

    if (retval != 0)
    {
        for (int i = 0; i < count; i++)
        {
            WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure((System.IntPtr)current, typeof(WTS_SESSION_INFO));
            current += dataSize;

            if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive)
                ret.Add(si.SessionID);
        }

        WTSFreeMemory(ppSessionInfo);
    }

    return ret;
}

在您的代码中:

    List<Int32> sessions = GetActiveSessions(WTS_CURRENT_SERVER_HANDLE);
    if (sessions.Count < 1)
    {
        int err = Marshal.GetLastWin32Error();
        _SUDSEventLog.WriteEntry("No active sessions found: errorCode:", err);
    }
    else
    {
        bool result = false;
        String title = "Hello";
        int tlen = title.Length;
        String msg = "Terminal Service!";
        int mlen = msg.Length;
        int resp = 0;
        result = WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, sessions[0], title, tlen,
        msg, mlen, 0, 0, out resp, false);
        int err = Marshal.GetLastWin32Error();
        System.Console.WriteLine("result:{0}, errorCode:{1}, response:{2}", result, err, resp);
    }
于 2014-06-13T19:00:50.697 回答
2

是的,它可以做到!

我知道这有点晚了,而且这个建议看起来有点可怕,但实际上并非如此:您只需更改线路即可确保从服务中向第一个当前登录的用户发送消息

 public static int WTS_CURRENT_SESSION = -1;

 public static int WTS_CURRENT_SESSION = 1;

这是因为Vista(EG: 2008, Vista, 2008 R2, Win7, Win 2012, Win 8, Win 2016,Win Ten等) ID 只是第一个登录的用户总是 1,接下来是 2 等等。

类似地(.. 因此)您可以通过反向操作来确保最后登录的用户,将数字向下我知道它是 naff,但您只需循环整数,直到您得到一个非错误。您需要关闭,因为WTSSendMessage调用在程序上等待结果(即锁定/等待当前线程)

因此:一种解决方案,您可以通过所有不同的线程向所有登录的用户发送消息(通过设置timeoutto zero)并调度到并行的数字猜测,并简单地忽略死线程。

我按照本教程使用 NuGet 包通过 TopShelf将我的c#控制台代码转换为c#服务,并使用确保它有效。它总是会调用在 Windows 10 中只有一个人的最后登录用户。Thread[STAThread]

class Service
{
    [DllImport("wtsapi32.dll", SetLastError = true)]
    static extern bool WTSSendMessage(
            IntPtr hServer,
            [MarshalAs(UnmanagedType.I4)] int SessionId,
            String pTitle,
            [MarshalAs(UnmanagedType.U4)] int TitleLength,
            String pMessage,
            [MarshalAs(UnmanagedType.U4)] int MessageLength,
            [MarshalAs(UnmanagedType.U4)] int Style,
            [MarshalAs(UnmanagedType.U4)] int Timeout,
            [MarshalAs(UnmanagedType.U4)] out int pResponse,
            bool bWait);
    public static IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero;
    public static int WTS_CURRENT_SESSION = 1;
    public void Start()
    {
        for (int user_session = 10; user_session>0; user_session--)
        {
            Thread t = new Thread(() => {
                try
                {                        
                    bool result = false;
                    String title = "Alert";
                    int tlen = title.Length;
                    String msg = "hi";
                    int mlen = msg.Length;
                    int resp = 7;
                    result = WTSSendMessage(WTS_CURRENT_SERVER_HANDLE, user_session, title, tlen, msg, mlen, 4, 0, out resp, true);
                    int err = Marshal.GetLastWin32Error();
                    if (err == 0)
                    {
                        if (result) //user responded to box
                        {
                            if (resp == 7) //user clicked no
                            {

                            }
                            else if (resp == 6) //user clicked yes
                            {

                            }
                            Debug.WriteLine("user_session:" + user_session + " err:" + err + " resp:" + resp);
                        }
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine("no such thread exists", ex);
                }
                //Application App = new Application();
                //App.Run(new MessageForm());
            });
            t.SetApartmentState(ApartmentState.STA);
            t.Start();
        }

    }
    public void Stop()
    {
        // write code here that runs when the Windows Service stops.  
    }
}

如果您想知道这确实有效,并且如果您遵循样板TopShelf示例,则只需要上面的代码

于 2018-04-18T10:28:08.803 回答
1

我相信这可能是 Windows 7 中不存在的 Windows Messenger 的一部分。

链接显示了原因(默认情况下禁用 RPC)

和可能的解决方案:

默认情况下,远程 RPC 在 Windows 7 上被禁用。可以通过将“HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Terminal Server”值“AllowRemoteRPC”设置为 1 来启用它。但是,这只会给你带来另一个错误 - 0x721 - 我我猜测是由 Windows 7 不允许“nul”用户访问引起的。有几种可能的方法可以解决这个问题,但我没有尝试过任何一种。一种方法是将来自远程机器的凭据添加到发送机器上的凭据缓存中,但我不知道这有多实用。可能有一种方法可以允许“nul”用户访问,但这也可能不是一个好主意。

而这篇MSDN 文章:展示了一个在看起来像 Windows 7 上执行此操作的长期示例。

于 2013-09-11T02:44:18.780 回答
1

您可以从您的 Windows 服务向用户显示一个非阻塞消息框。为此,首先,从“services.msc”启动一个名为“Interactive Service Detection”的服务。然后从您的 Windows 服务启动一个线程。在线程方法中使用“MessageBox.Show()”方法向用户显示您的消息框。而不是手动启动“交互式服务检测”服务,您可以启动它 c# 程序。

于 2015-03-26T11:15:43.663 回答