通过编程从Windows服务锁定工作站

3

我正在尝试使用以下代码从我的Windows服务锁定工作站:

[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool LockWorkStation();

if (!LockWorkStation()){
 //Workstation was unable to lock(Write this on event log)
}

但是上面的代码没有起作用。有人有解决方法吗?
3个回答

5
你可以使用WTSDisconnectSession Windows API实现此功能,它将以与LockWorkStation相同的方式注销用户。
但是由于服务在特殊会话中,因此您不能仅断开WTS_CURRENT_SESSION并且必须断开计算机上的每个活动会话。
using System;
using System.Runtime.InteropServices;

public class LockWorkstation
{
    [DllImport("wtsapi32.dll", SetLastError = true)]
    static extern bool WTSDisconnectSession(IntPtr hServer, int sessionId, bool bWait);

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

    [DllImport("wtsapi32.dll")]
    static extern void WTSFreeMemory(IntPtr pMemory);

    [StructLayout(LayoutKind.Sequential)]
    private struct WTS_SESSION_INFO
    {
        public Int32 SessionID;

        [MarshalAs(UnmanagedType.LPStr)]
        public String pWinStationName;

        public WTS_CONNECTSTATE_CLASS State;
    }

    private enum WTS_INFO_CLASS
    {
        WTSInitialProgram,
        WTSApplicationName,
        WTSWorkingDirectory,
        WTSOEMId,
        WTSSessionId,
        WTSUserName,
        WTSWinStationName,
        WTSDomainName,
        WTSConnectState,
        WTSClientBuildNumber,
        WTSClientName,
        WTSClientDirectory,
        WTSClientProductId,
        WTSClientHardwareId,
        WTSClientAddress,
        WTSClientDisplay,
        WTSClientProtocolType
    }

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

    public static void LockWorkStation()
    {
        IntPtr ppSessionInfo = IntPtr.Zero;
        Int32 count = 0;
        Int32 retval = WTSEnumerateSessions(IntPtr.Zero, 0, 1, ref ppSessionInfo, ref count);
        Int32 dataSize = Marshal.SizeOf(typeof(WTS_SESSION_INFO));
        IntPtr currentSession = ppSessionInfo;

        if (retval == 0) return;

        for (int i = 0; i < count; i++)
        {
            WTS_SESSION_INFO si = (WTS_SESSION_INFO)Marshal.PtrToStructure(currentSession, typeof(WTS_SESSION_INFO));
            if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) WTSDisconnectSession(IntPtr.Zero, si.SessionID, false);
            currentSession += dataSize;
        }
        WTSFreeMemory(ppSessionInfo);
    }
}

你确定这和 Windows+L 是一样的吗? - JobaDiniz
@JobaDiniz 这与在每个打开的会话上执行Windows+L相同。 - mad
@mad 我们也可以解锁系统吗? - Sesen

3
根据文档中明确说明,该API函数只能从运行在交互式桌面中的进程中调用。因此,你无法从Windows服务中执行此操作,因为Windows服务不在交互式桌面中运行。
如果你必须这样做,你应该先检查用户是否已经登录,然后在模拟要锁定的用户下生成进程。但我认为这似乎是一个相当“hackish”的解决方案。也许更好的解决方案是在用户登录时启动隐藏或托盘应用程序,然后从该应用程序执行该任务。

如果我没有将你的答案标记为正确,那么很抱歉,但我会给你的答案投一票。 - Polar
在用户会话中启动一个进程来代表您执行工作是一种相当成熟的技术。我不会称其为黑客技巧。(与在每个用户会话中永久运行的应用程序相比,有几个优点,其中最重要的可能是没有真正可靠的方法来实现这一点。) - Harry Johnston
我并不是说在用户会话中启动进程是一种黑客行为。我是在谈论从服务中锁定用户会话,我会更新我的回答。 - Antonio Bakula

2
一个Windows服务不在桌面上运行,因此你无法从服务中调用它。根据LockWorkStation文档,只有在交互式桌面上运行的进程才能调用LockWorkStation函数。此外,用户必须已登录,工作站不能已经被锁定。也许这是一种hack方法,但你可以创建一个托盘应用程序,以响应来自服务的某种进程间调用,调用LockWorkStation。如果你不喜欢托盘应用程序的可见性,请考虑创建一个控制台应用程序,生成一个线程等待调用,并在用户登录时无窗口运行。另一个可能性是创建一个从未创建UI窗口的Windows应用程序。如果你使用数据复制API,这绝对是你想要做的事情。关键在于,必须有某些东西在用户的交互式上下文中运行。有关IPC的信息,请参阅Microsoft的MSDN主题

除了创建托盘应用程序,还有其他解决方案吗?如果可能,请给我一个链接。 - Polar
我更新了我的答案,提供了另一种选择。我认为你不能从服务中生成一个应用程序,即使你可以,这可能会更加不可靠,正如Antonio所提到的那样。 - Kit
仅供澄清,我可以在Windows服务下调用控制台应用程序吗? - Polar
您可以使用某种形式的IPC调用控制台应用程序或无头Win32进程。搜索“Windows进程间通信”以获取一些想法。更高级的方法是使用消息总线或套接字,但这可能过于复杂,因为您知道进程在同一台机器上。您可以在控制台应用程序中实现文件监视器来监视服务是否删除了文件,但这有点笨拙。 - Kit
可能不是控制台应用程序,因为它们是可见的。(即使您立即隐藏控制台,它仍会短暂地弹出。)它们还使用更多资源。最好将其配置为Windows应用程序,然后永远不要创建任何窗口。 - Harry Johnston

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接