1

Windows 7 で .NET ウィンドウ サービスを使用してメッセージを表示するときに問題が発生しました。 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_SESSIONWindows 7 以降のサービスの場合、セッション 0 に相当し、ユーザーの操作は許可されません。すべてのアクティブなセッションを列挙し、それぞれにメッセージを送信する必要があります (コンピューターがターミナル サービスを実行していない限り、セッションは 1 つだけです)。次のようなもの:

署名:

    [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;

これは、( VistaEG: 2008, Vista, 2008 R2, Win7, Win 2012,Win 8など) ID が単に最初にログインしたユーザーが常に 1 であり、次のユーザーが 2 などであるためです。Win 2016Win Ten

同様に(..そして、したがって)、逆のことを行うことで、最後にログインしたユーザーを確認できます。番号を下げると、それがナフだとわかっていますが、エラーが発生しないまでintをループダウンするだけです。WTSSendMessage呼び出しが手続き的に結果を待っているため、ダウンする必要があります (つまり、現在のスレッドをロック/待機します)。

したがって、ログインしているすべてのユーザーにメッセージを送信し ( を to に設定しtimeoutて)、すべての異なるスレッドを介して並行して数字zeroの推測にディスパッチし、デッド スレッドを単純に無視するソリューションです。

NuGet パッケージを使用して、このチュートリアルに従い、TopShelf を介してコンソール コードをサービスに変換しこれc#機能c#することを確認します。Windows Ten では 1 人だけである、最後にログインしたユーザーを常に呼び出します。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 に設定することで有効にできます。 Windows 7が「nul」ユーザーアクセスを許可していないことが原因だと推測しています。この問題を回避する方法はいくつか考えられますが、私はそれらのいずれも試していません。1 つの方法は、リモート マシンから送信側マシンの資格情報キャッシュに資格情報を追加することですが、それがどれほど実用的かはわかりません。「nul」ユーザーアクセスを許可する方法があるかもしれませんが、それもおそらく良い考えではありません。

そして、このMSDN の記事: Windows 7 のようなものでこれを実行するための長い作業例を示しています。

于 2013-09-11T02:44:18.780 に答える