Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I lock the workstation from a windows service?

I need to lock the workstation from a windows service written in VB.Net. I am writing the app on Windows 7 but it needs to work under Vista and XP as well.

User32 API LockWorkStation does not work as it requires an interactive desktop and I get return value of 0.

I tried calling %windir%\System32\rundll32.exe user32.dll,LockWorkStation from both a Process and from Shell, but still nothing happens.

Setting the service to interact with the desktop is a no-go as I am running the service under the admin account so it can do some other stuff that requires admin rights - like disabling the network, and you can only select the interact with desktop option if running under Local System Account.

That would be secondary question - how to run another app with admin rights from a service running under Local System Account without bugging the user.

I am writing an app to control my kids computer/internet access (which I plan to open source when done) so I need everything to happen as stealthily as possible.

I have a UI that handles settings and status notifications in the taskbar, but that is easy to kill and thus defeat the locking. I could make another hidden Windows Forms app to handle the locking, but that just seems a rather inelegant solution.

Better ideas anyone?

like image 216
Brad Mathews Avatar asked Mar 17 '10 23:03

Brad Mathews


People also ask

How do I lock my workstation without logging off?

Press Ctrl-Alt-Del , and then click Lock this computer, Lock Computer, or Lock.

What is the quickest way to lock your Windows workstation?

Using the Keyboard: Press Ctrl, Alt and Del at the same time. Then, select Lock from the options that appear on the screen.


2 Answers

I've been fighting with this over a week and after reading a lot of information about this issue, finally got the solution for this...

You have to use the CreateProcessAsUser function like this:

  Private Shared Sub Executer(ByVal content As String)
    Dim objProcess As System.Diagnostics.Process

    Dim filename As String
    filename = "e:\lock.bat" 
    'create a bat file with ''rundll32.exe user32.dll,LockWorkStation'' inside

    Dim UserTokenHandle As IntPtr = IntPtr.Zero
    WindowsApi.WTSQueryUserToken(WindowsApi.WTSGetActiveConsoleSessionId, UserTokenHandle)

    Dim ProcInfo As New WindowsApi.PROCESS_INFORMATION
    Dim StartInfo As New WindowsApi.STARTUPINFOW
    StartInfo.cb = CUInt(Marshal.SizeOf(StartInfo))

    WindowsApi.CreateProcessAsUser(UserTokenHandle, filename, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, False, 0, IntPtr.Zero, Nothing, StartInfo, ProcInfo)
    If Not UserTokenHandle = IntPtr.Zero Then
        WindowsApi.CloseHandle(UserTokenHandle)
    End If

End Sub

Got most of the code from here where you can, also, find the WindowsApi to use with this function. I'me still trying to find if can avoid the bat file but at least is a very decent solution.

EDIT: To avoid using an external *.bat file to execute the code just edit the WindowsApi Class and replace the CreateProcessAsUser and the advapi32.dll import part with this:

    <DllImport("Advapi32.dll", EntryPoint:="CreateProcessAsUser", ExactSpelling:=False,      SetLastError:=True, CharSet:=CharSet.Unicode)> _
    Public Shared Function CreateProcessAsUser( _
                       ByVal hToken As IntPtr, _
                       ByVal lpApplicationName As String, _
                       <[In](), Out(), [Optional]()> ByVal lpCommandLine As StringBuilder, _
                       ByVal lpProcessAttributes As IntPtr, _
                       ByVal lpThreadAttributes As IntPtr, _
                       <MarshalAs(UnmanagedType.Bool)> ByVal bInheritHandles As Boolean, _
                       ByVal dwCreationFlags As Integer, _
                       ByVal lpEnvironment As IntPtr, _
                       ByVal lpCurrentDirectory As String, _
                       <[In]()> ByRef lpStartupInfo As STARTUPINFOW, _
                       <Out()> ByRef lpProcessInformation As PROCESS_INFORMATION) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

And now you can use a stringbuilder as the thrid argument(comandline) for CreateProcessAsUser function and put the second(applicationame) as 'Nothing' like this:

Dim cmdline As New StringBuilder
cmdline.Append("rundll32.exe user32.dll,LockWorkStation")
WindowsApi.CreateProcessAsUser(UserTokenHandle, Nothing, cmdline, IntPtr.Zero, IntPtr.Zero, False, 0, IntPtr.Zero, Nothing, StartInfo, ProcInfo)

And it WILL WORK!!!!

Regards, AP

like image 153
Adriano Pedro Avatar answered Sep 21 '22 09:09

Adriano Pedro


What you're trying to do is actively blocked by Microsoft - if you did get it working, it'd be exploiting a loophole that will surely be closed soon.

What you can do though is the Friar Tuck/Robin Hood solution - have two programs running and monitoring each other. When one is killed, the other detects this and restarts it (or, just logs the current user out as punishment, depending on how severe you want this to be).

like image 25
overslacked Avatar answered Sep 20 '22 09:09

overslacked