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.exeentry pointsThe function signature required for functions called by
rundll32.exeis documented in this Knowledge Base article. That hasn’t stopped people from usingrundll32to call random functions that weren’t designed to be called by rundll32, like user32LockWorkStationor user32ExitWindowsEx.Let’s walk through what happens when you try to use
rundll32.exeto call a function likeExitWindowsEx:The
rundll32.exeprogram parses its command line and calls theExitWindowsExfunction 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
ExitWindowsExisBOOL WINAPI ExitWindowsEx(UINT uFlags, DWORD dwReserved);What happens? Well, on entry to ExitWindowsEx, the stack looks like this:
.. rest of stack ..nCmdShowpszCmdLinehinsthwndreturn address<- ESPHowever, the function is expecting to see
.. rest of stack ..dwReserveduFlagsreturn address<- ESPWhat happens? The
hwndpassed byrundll32.exegets misinterpreted asuFlagsand thehinstgets 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
ExitWindowsExfunction 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.exesolution) - [Wayback/Archive] Create Lock Desktop Icon (which is bad as it still shows the unsafe
rundll32.exesolution)
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.exeandRundll32.exethat allow you to invoke a function exported from a DLL, either 16-bit or 32-bit. However,RundllandRundll32programs 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 ofRundllandRundll32programs 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
RundllandRundll32utility 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 theRundll32utility program and supports onlyRundll32.…
More information
…
Rundllvs.Rundll32
Rundllloads and runs 16-bit DLLs, whereasRundll32loads and runs 32-bit DLLs. If you pass the wrong type of DLL toRundllorRundll32, it may fail to run without indicating any error messages.…
Rundllcommand 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 CALLBACKEntryPoint(HWND hwnd, HINSTANCE hinst, LPSTR lpszCmdLine, int nCmdShow);…
The parameters to the
Rundllentry 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
Rundllwould call theInstallHinfSection()entrypoint function inSetupx.dlland pass it the following parameters:
hwnd= (parent window handle)hinst=HINSTANCEofSETUPX.DLLlpszCmdLine="132 C:\WINDOWS\INF\SHELL.INF"nCmdShow= (whatever thenCmdShowwas 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
PassportWizardRunDllas an entry point into theNetplwiz.dllfile through a Rundll32 command allows you to launch the Passport Wizard from a command line as though it were an executable file.
PassportWizardRunDllis used solely in the context of aRundll32.execommand as follows:rundll32.exe netplwiz.dll, PassportWizardRunDllUsing an entry point function with
Rundll32.exedoes not resemble a normal function call. The function name and the name of the.dllfile 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, andnCmdShoware not provided by the user, but are handled behind the scenes byRundll32.PassportWizardRunDlldoes not use thelpszCmdLinevalue, 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 thingIs 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
PsToolsusing Chocolatey as described in [Wayback/Archive] Chocolatey Software |PsTools1.2012.04.12. - You can install
NirCmdusing Chocolatey as described in [Wayback/Archive] Chocolatey Software |NirCmd2.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 … … -lLock 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