Script alternatives to the Windows-L keyboard shortcut (SwitchUser / LockWorkstation)
Posted by jpluimers on 2024/05/23
More than a decade ago I wrote about Programmatic alternatives to Windows-L keyboard shortcut (SwitchUser / LockWorkstation).
Still, I see many scripts invoke rundll32.exe
or to call the [Wayback/Archive] LockWorkStation
function (winuser.h
) inside user32.dll
. Don’t!
The BOOL LockWorkStation()
function has a calling convention that is incompatible with rundll32.exe
() which will corrupt the call stack likely will lead to random problems as after two decades, this post from Raymond Chen still holds: [Wayback/Archive] What can go wrong when you mismatch the calling convention? – The Old New Thing
Rundll32.exe
entry pointsThe function signature required for functions called by
rundll32.exe
is documented in this Knowledge Base article. That hasn’t stopped people from usingrundll32
to call random functions that weren’t designed to be called by rundll32, like user32LockWorkStation
or user32ExitWindowsEx
.Let’s walk through what happens when you try to use
rundll32.exe
to call a function likeExitWindowsEx
:The
rundll32.exe
program parses its command line and calls theExitWindowsEx
function on the assumption that the function is written like this:void CALLBACK ExitWindowsEx(HWND hwnd, HINSTANCE hinst, LPSTR pszCmdLine, int nCmdShow);
But it isn’t. The actual function signature for
ExitWindowsEx
isBOOL WINAPI ExitWindowsEx(UINT uFlags, DWORD dwReserved);
What happens? Well, on entry to ExitWindowsEx, the stack looks like this:
.. rest of stack ..
nCmdShow
pszCmdLine
hinst
hwnd
return address
<- ESP
However, the function is expecting to see
.. rest of stack ..
dwReserved
uFlags
return address
<- ESP
What happens? The
hwnd
passed byrundll32.exe
gets misinterpreted asuFlags
and thehinst
gets misinterpreted asdwReserved
. Since window handles are pseudorandom, you end up passing random flags toExitWindowsEx.
Maybe today it’sEWX_LOGOFF
, tomorrow it’sEWX_FORCE
, the next time it might beEWX_POWEROFF
.Now suppose that the function manages to return. (For example, the exit fails.) The
ExitWindowsEx
function cleans two parameters off the stack, unaware that it was passed four. The resulting stack is
.. rest of stack ..
nCmdShow
(garbage not cleaned up) pszCmdLine
<- ESP
(garbage not cleaned up)Now the stack is corrupted and really fun things happen. For example, suppose the thing at “
.. rest of the stack ..
” is a return address. Well, the original code is going to execute a “return
” instruction to return through that return address, but with this corrupted stack, the “return
” instruction will instead return to a command line and attempt to execute it as if it were code.
The not so cool thing is that despite this still failing, all but one of the archived links in the above post by Raymond Chen still work due to link rot.
Fixed links
While writing this in 2022, hopefully while posting in 2024 these links still work, but like always I have included the archived versions as well:
- [Wayback/Archive] rundll32 | Microsoft Docs (which lacks crucial information)
- [Wayback/Archive: Brian Desmond’s Blog – Shortcut to Lock Computer on Win2k] (which is OK not to be on-line any more as it coined the unsafe
rundll32.exe
solution) - [Wayback/Archive] Create Lock Desktop Icon (which is bad as it still shows the unsafe
rundll32.exe
solution)
The first fixed link above show another internet problem besides link rot
, namely information loss.
Fewer and fewer people are familiar with old documentation that they decide to update it while throwing away crucial parts maybe thinking that part is still covered elsewhere, while the maintainer in the part elsewhere things the same: [Wayback/Archive] assumption is the mother of all (insert your favourite swear word here).
That is why I quoted the most important bits of the archived [Wayback/Archive] INFO: Windows Rundll
and Rundll32
Interface
This article was previously published under
Q164787
…
Summary
Microsoft Windows 95, Windows 98, and Windows Millennium Edition (Me) contains two command-line utility programs named
Rundll.exe
andRundll32.exe
that allow you to invoke a function exported from a DLL, either 16-bit or 32-bit. However,Rundll
andRundll32
programs do not allow you to call any exported function from any DLL. For example, you can not use these utility programs to call the Win32 API (Application Programming Interface) calls exported from the system DLLs. The programs only allow you to call functions from a DLL that are explicitly written to be called by them. This article provides more details on the use ofRundll
andRundll32
programs under the Windows operating systems listed above.MIcrosoft Windows NT 4.0, Windows 2000, and Windows XP ship with only
Rundll32
. There is no support forRundll
(the Win16 utility) on either platform.The
Rundll
andRundll32
utility programs were originally designed only for internal use at Microsoft. But the functionality provided by them is sufficiently generic that they are now available for general use. Note that Windows NT 4.0 ships only with theRundll32
utility program and supports onlyRundll32
.…
More information
…
Rundll
vs.Rundll32
Rundll
loads and runs 16-bit DLLs, whereasRundll32
loads and runs 32-bit DLLs. If you pass the wrong type of DLL toRundll
orRundll32
, it may fail to run without indicating any error messages.…
Rundll
command lineThe command line for Rundll is as follows:
RUNDLL.EXE <dllname>,<entrypoint> <optional arguments>
…
How to Write Your DLL
In your DLL, write the
<entrypoint>
function with the following prototype:…
32-bit DLL:
void CALLBACK
EntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);
…
The parameters to the
Rundll
entry point are as follows:
hwnd
– window handle that should be used as the owner window for
any windows your DLL createshinst
– your DLL’s instance handlelpszCmdLine
– ASCIIZ command line your DLL should parsenCmdShow
– describes how your DLL’s windows should be displayedIn the following example:
RUNDLL.EXE SETUPX.DLL,InstallHinfSection 132 C:\WINDOWS\INF\SHELL.INF
Rundll
would call theInstallHinfSection()
entrypoint function inSetupx.dll
and pass it the following parameters:
hwnd
= (parent window handle)hinst
=HINSTANCE
ofSETUPX.DLL
lpszCmdLine
="132 C:\WINDOWS\INF\SHELL.INF"
nCmdShow
= (whatever thenCmdShow
was passed toCreateProcess
)…
The above quotes are from the archived 2016 article which more or less has recnognisable bits of Q164787 as base part: support.microsoft.com/en-us/kb/164787.
As of 2017, the URL got changed substantially into [Wayback] support.microsoft.com/en-us/help/164787/info-windows-rundll-and-rundll32-interface as part of a large support.microsoft.com site overhaul (also causing most Wayback Machine and archive.is sites to become empty).
Then in 2020, support.microsoft.com got overhauled and integrated into docs.microsoft.com as
[Wayback/Archive] rundll32 | Microsoft Docs, and also shortening title, content and URL changed to reflect the title: docs.microsoft.com/en-us/windows-server/administration/windows-commands/rundll32.
The only Microsoft documentation article (via [WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW/Archive] ncmdshow rundll32 EntryPoint site:microsoft.com – Google Search) now explaining the function declaration expected by rundll32
is [Wayback/Archive] PassportWizardRunDll
function – Win32 apps | Microsoft Docs:
[This function is available through Windows XP with Service Pack 2 (SP2) and Windows Server 2003. It might be altered or unavailable in subsequent versions of Windows.]
Launches the Passport Wizard when used with
Rundll32.exe
.Syntax
void PassportWizardRunDll(
_In_ HWND hwndStub,
_In_ HINSTANCE hAppInstance,
_In_ LPTSTR lpszCmdLine,
_In_ int nCmdShow
);
…
Remarks
…
Using
PassportWizardRunDll
as an entry point into theNetplwiz.dll
file through a Rundll32 command allows you to launch the Passport Wizard from a command line as though it were an executable file.
PassportWizardRunDll
is used solely in the context of aRundll32.exe
command as follows:rundll32.exe netplwiz.dll, PassportWizardRunDll
Using an entry point function with
Rundll32.exe
does not resemble a normal function call. The function name and the name of the.dll
file where it is stored are used only as command-line parameters. The function definition shown under Syntax is only a standard prototype for all functions that you can call usingRundll32
. The specific values forhwndStub
,hAppInstance
, andnCmdShow
are not provided by the user, but are handled behind the scenes byRundll32
.PassportWizardRunDll
does not use thelpszCmdLine
value, so no additional data is required.
Problem
From a script like a batch file, execute the LockWorkstation
API function in user32.dll
that is incompatible with what rundll32.exe
expects.
Solution
Back when writing the original blog-post, few tools wrapped the LockWorkstation
API.
In the mean time, by default Windows ships with sufficiently recent versions of C# and PowerShell and both [Wayback/Archive] PsTools
and NirCmd are readily available through Chocolatey.
C# solution
The fastes solution is likely to compile this with any csc.exe
(C# compiler) on your path once, then execute the resulting LockWorkstation.exe
:
using System.ComponentModel; using System.Runtime.InteropServices; namespace LockWorkstation { class Program { // https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportattribute [DllImport("user32.dll", SetLastError = true)] static extern bool LockWorkStation(); static void Main() { // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-lockworkstation if (!LockWorkStation()) // not sure when this could fail, but just in case: throw new Win32Exception(Marshal.GetLastWin32Error()); // https://docs.microsoft.com/en-us/dotnet/api/system.componentmodel.win32exception } } }
On my system compiling is through this:
%windir%\Microsoft.NET\Framework64\v4.0.30319\csc.exe LockWorkstation.cs
It leaves LockWorkstation.exe
which you can execute and book the workstation is locked, even in an RDP session (which is cool as you can lock sessions you do not immediately need but still keep them active so it is easier to go back to them).
I built the above solution based on:
- [Wayback/Archive] C# lockworkstation – Google Search
- [Wayback/Archive] c# – How do I lock a windows workstation programmatically? – Stack Overflow ->
- [Wayback/Archive] Lock Windows workstation programmatically in C# – Stack Overflow (thanks [Wayback/Archive] Kara and [Wayback/Archive] JaredPar):
I ran into this example for locking Windows workstation:using System.Runtime.InteropServices; ... [DllImport("user32.dll", SetLastError = true)] static extern bool LockWorkStation(); ... if (!LockWorkStation()) throw new Win32Exception(Marshal.GetLastWin32Error()); // or any other thing
Is there a pure managed alternative to this snippet? Namely, without P-Invoke.…
No there is not. This is the best way to achieve this action.
- [Wayback/Archive] Lock Windows workstation programmatically in C# – Stack Overflow (thanks [Wayback/Archive] Kara and [Wayback/Archive] JaredPar):
PowerShell solution
PowerShell has gained a lot of functionality including the possibility to import external functions similar do DllImport
in C#.
From the links below, I first created this PowerShell script that has the correct double quotes and tested it:
function Lock-WorkStation { $signature = @" [DllImport("user32.dll", SetLastError = true)] public static extern bool LockWorkStation(); "@ $LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru $LockWorkStation::LockWorkStation() | Out-Null } Lock-WorkStation
Then I condensed into this:
function Lock-WorkStation { $signature = '[DllImport("user32.dll", SetLastError = true)] public static extern bool LockWorkStation();'; $LockWorkStation = Add-Type -memberDefinition $signature -name "Win32LockWorkStation" -namespace Win32Functions -passthru; $LockWorkStation::LockWorkStation() | Out-Null } Lock-WorkStation
and further into this:
(Add-Type -memberDefinition '[DllImport("user32.dll", SetLastError = true)] public static extern bool LockWorkStation();' -name "Win32LockWorkStation" -namespace Win32Functions -passthru)::LockWorkStation() | Out-Null
Indeed: it is a 200+ character line, but now everything is on one line, so now I was able to put it inside the below batch file as a PowerShell call by escaping all "
into \"
:
:: https://wiert.wordpress.com/?p=107892 :: requires administrative UAC elevation: psshutdown -l :: NirCmd can run without elevation: nircmd lockws :: PowerShell can run without elevation: PowerShell "(Add-Type -memberDefinition '[DllImport(\"user32.dll\", SetLastError = true)] public static extern bool LockWorkStation();' -name \"Win32LockWorkStation\" -namespace Win32Functions -passthru)::LockWorkStation() | Out-Null"
And yes, the last line is still below 240 characters, so [Wayback/Archive] Jeroen Wiert Pluimers on Twitter: It fits in a Tweet! PowerShell "(Add-Type -memberDefinition '[DllImport(\"user32.dll\", SetLastError = true)] public static extern bool LockWorkStation();' -name \"Win32LockWorkStation\" -namespace Win32Functions -passthru)::LockWorkStation() | Out-Null"
I built the above solution based on:
- [Wayback/Archive] -rundll32 “LockWorkStation” batch file – Google Search
- [Wayback/Archive] Command to turn off monitor in Windows 10 | myByways
powershell (Add-Type '[DllImport(\"user32.dll\")]public static extern int PostMessage(int h,int m,int w,int l);' -Name a -Pas)::PostMessage(-1,0x0112,0xF170,2)
- [Wayback/Archive] Command to turn off monitor in Windows 10 | myByways
- [Wayback/Archive] powershell LockWorkStation – Google Search
- [Wayback/Archive] Lock your workstation using PowerShell (note the TechNet link died because of link rot and the double quotes all being wrong)):
I grabbed this script from TechNet Library
Function Lock-WorkStation {
$signature = @”
[DllImport(“user32.dll”, SetLastError = true)]
public static extern bool LockWorkStation();
“@
$LockWorkStation = Add-Type -memberDefinition $signature -name “Win32LockWorkStation” -namespace Win32Functions -passthru
$LockWorkStation::LockWorkStation() | Out-Null
}
After executing above code, invoking “
Lock-WorkStation
” command from PowerShell window will lock your PC. - [Wayback/Archive] powershell dllimport idiom – Google Search
- a
- [Wayback/Archive] powershell – Error: Import DLL from run dialog – Stack Overflow (thanks [Wayback/Archive] Msegling, [Wayback/Archive] Ansgar Wiechers and and [Wayback/Archive] mklement0):
Welcome to the wonderful world of nesting string literals across different languages.Put double quotes around the entire command line you want executed by PowerShell and escape nested double quotes with backslashes:powershell "$k=Add-Type -MemberDefinition '[DllImport(\"user32.dll\")]pub...;" # ^ ^ ^ ^
Addendum: note that the above commandline will not work when invoked from a PowerShell console. In that case you need additional (PowerShell) escapes for$
and"
characters inside the command string:powershell "`$k=Add-Type -MemberDefinition '[DllImport(\`"user32.dll\`")]pub...;" # ^ ^ ^
…
Indeed. It’s also worth recommending explicit use of
-Command
, given that PSv6+ will default to-File
Batch file solution
One application that can call the LockWorkstation
function is PsShutdown
, however it requires an administrative UAC elevation token, so it usually cannot be called from batch-files.
NirCmd
does not require UAC elevation and also executes quickly , but sometimes is disallowed to be installed (for instance by stringent antivirus software).
It is included as it is the fastest way. If it fails or not installed, the below batch file uses the above PowerShell solution.
Note:
- You can install
PsTools
using Chocolatey as described in [Wayback/Archive] Chocolatey Software |PsTools
1.2012.04.12. - You can install
NirCmd
using Chocolatey as described in [Wayback/Archive] Chocolatey Software |NirCmd
2.86
I built the above solution based on the above PowerShell solution and these links:
- [Wayback/Archive] -rundll32 “LockWorkStation” batch file – Google Search
- [Wayback/Archive] Trying to lock my laptop with a script (no keypress) – Ask for Help – AutoHotkey Community ->
- [Wayback/Archive] PsTools – Windows Sysinternals | Microsoft Docs
- [Wayback/Archive] PsShutdown – Windows Sysinternals | Microsoft Docs
You can use PsShutdown to initiate a shutdown of the local or a remote computer, logoff a user, lock a system, or to abort an imminent shutdown.Usage:psshutdown [[\\computer[,computer[,..] | @file
[-u user [-p psswd]]] -s|-r|-h|-d|-k|-a|-l|-o [-f] [-c] [-t nn|h:m] [-n s] [-v nn] [-e [u|p]:xx:yy] [-m "message"]
Parameter Description … … -l
Lock the computer. … …
- [Wayback/Archive] PsShutdown – Windows Sysinternals | Microsoft Docs
- [Wayback/Archive] NirCmd – Windows command line tool
Versions History
Date Version Description … … … 10/10/2003 1.10 New commands: killprocess
,service
,memdump
,win
,lockws
.… … …
- [Wayback/Archive] PsTools – Windows Sysinternals | Microsoft Docs
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
Similar alternative
In my previous article, I mentioned tsdiscon.exe which kind of locks your workstation but – if you are on a Remote Desktop Connection – also terminates that connection. For most people that certainly is not the preferred behaviour.
–jeroen
Leave a comment