What’s the guidance on when to use rundll32? Easy: Don’t use it


Occasionally, a customer will ask, “What is Rundll32.exe and when should I use it instead of just writing a standalone exe?”

The guidance is very simple: Don’t use rundll32. Just write your standalone exe.

Rundll32 is a leftover from Windows 95, and it has been deprecated since at least Windows Vista because it violates a lot of modern engineering guidelines. If you run something via Rundll32, then you lose the ability to tailor the execution environment to the thing you’re running. Instead, the environment is set up for whatever Rundll32 requests.

  • Data Execution Prevention policy cannot be applied to a specific Rundll32 command line. Any policy you set applies to all Rundll32 commands.
  • Address Space Layout Randomization cannot be applied to a specific Rundll32 command line. Any policy you set applies to all Rundll32 commands.
  • Application compatibility shims cannot be applied to a specific Rundll32 command line. Any application compatibilty shim you enable will be applied to all Rundll32 commands.
  • SAFER policy cannot be applied to a specific Rundll32 command line. Any policy you set applies to all Rundll32 commands.
  • The Description in Task Manager will be Rundll32’s description, which does not help users identify what the specific Rundll32 instance is doing.
  • You cannot apply a manifest to a specific Rundll32 command line. You have to use the manifest that comes with Rundll32. (In particular, this means that your code must be high DPI aware.)
  • The Fault Tolerant Heap cannot be enabled for a specific Rundll32 command line. Any policy you set applies to all Rundll32 commands.
  • All Rundll32.exe applications are treated as the same program for the purpose of determining which applications are most frequently run.
  • Explorer tracks various attributes of an application based on the executable name, so all Rundll32.exe commands will be treated as the same application. (For example, all windows hosted by Rundll32 will group together.)
  • You won’t get any Windows Error Reporting reports for crashes in your Rundll32.exe command line, because they all got sent to the registered owner of Rundll32.exe (the Windows team).
  • Many environmental settings are implied by the executable. If you use Rundll32, then those settings are not chosen by you since you didn’t control how Rundll32 configures its environment.
    • Rundll32 is marked as TSAWARE, so your Rundll32 command must be Terminal Services compatible.
    • Rundll32 is marked as LARGE­ADDRESS­AWARE, so your Rundll32 command must be 3GB-compatible.
    • Rundll32 specifies its preferred stack reserve and commit, so you don’t control your stack size.
    • Rundll32 is marked as compatible with the version of Windows it shipped with, so it has opted into all new behaviors (even the breaking ones), such as automatically getting the Heap­Enable­Termination­On­Corruption flag set on all its heaps.
  • Windows N+1 may add a new behavior that Rundll32 opts into, but which your Rundll32 command line does not support. (It can’t, because the new behavior didn’t exist at the time you wrote your Rundll32 command line.) As you can see, this has happened many times in the past (for example, high DPI, Terminal Services compatibility, 3GB compatibility), and it will certainly happen again in the future.

You get the idea.

Note also that Rundll32 assumes that the entry point you provide corresponds to a task which pumps messages, since it creates a window on your behalf and passes it as the first parameter. A common mistake is writing a Rundll32 entry point for a long-running task that does not pump messages. The result is an unresponsive window that clogs up broadcasts.

Digging deeper, one customer explained that they asked for guidance making this choice because they want to create a scheduled task that runs code inside a DLL, and they wanted to decide whether to create a Rundll32 entry point in their DLL, or whether they should just create a custom executable whose sole job is loading the DLL and calling the custom code.

By phrasing it as an either/or question, they missed the third (correct) option: Create your scheduled task with an ICom­Handler­Action that specifies a CLSID your DLL implements.

Comments (28)
  1. MItaly says:

    I'm no task scheduler expert, but doesn't using IComHandlerAction give you the same disadvantages of rundll32 – namely, you are a guest in another process, that is set up however it likes best?

    (on the other hand, if you wrote a COM component you should be ready for this in first place…)

  2. Maurits says:

    I sometimes see people suggesting the use of rundll32 to open control panels: for example

    rundll32.exe Shell32.dll,Control_RunDLL mmsys.cpl,,recording

    For the specific case of opening a control panel, there's no need to create an .exe; just use control.exe.  Replace "rundll32.exe Shell32.dll,Control_RunDLL" with "control.exe":

    control.exe mmsys.cpl,,recording

    [I've covered that specific case multiple times. -Raymond]
  3. Cesar says:

    What would be the non-rundll32 equivalent of something like:

    rundll32.exe printui.dll,PrintUIEntry /ia /f "…ghostpdfghostpdf.inf" /m "Ghostscript PDF"

    All documentation I could find pointed to rundll32 being the official way to do this. It did not seem very elegant to me (compared to AddPrinter(…)), having to shell out to rundll32 (this is not a batch file, it is being called from an executable written in C), but I found no other documented way to do this.

    [That is not the same question asked by this customer. The customer was asking to choose between writing a standalone exe or writing a DLL that is called via rundll32. Your question is about using an existing DLL designed for rundll32. -Raymond]
  4. Joshua says:

    I use rundll32 for one purpose – to create a self-deleting image for uninstaller. As you know, an .EXE cannot delete itself because the file is locked (the handle is no longer a userspace handle starting in XP so cannot be closed by CloseHandle). A .DLL loaded by rundll32 can by setting up a ROP sled that goes FreeLibrary -> DeleteFile -> RemoveDirectory -> ExitProcess.

  5. Michiel says:

    For uninstallation, using CMD.EXE as the host is far easier. Just write a batch file.

  6. Joshua says:

     For uninstallation, using CMD.EXE as the host is far easier. Just write a batch file.

    If anything's a weird dependency on an implementation detail, that is. That depends on the fact that cmd.exe closes and reopens the batch file between each command.

  7. nitpickery says:

    Rundll32 is marked as LARGE­ADDRESS­AWARE, so your Rundll32 command must be 3GB-compatible.

    Probably best to just stick with "large address aware", since it's not strictly a 3GB thing – on x64 Windows, your 32bit LAA executables get pretty close to 4GB to play with. (It's nitpickery, though, since anything >2GB means bit31, which is what LAA is all about – not causing pointer-tagging code to crash and burn.)

  8. Matt says:

    @Joshua: If using a ROP sled is the answer, you're asking the wrong question. If you try and do this in C, you're at the mercy of the compiler choosing not to try and return to you after FreeLibrary – and that might change if you decide to upgrade Visual Studio, compile for ARM or x64 or change any setting at all anywhere in the compiler. Hell it might break because it's a Tuesday for all you know. If you do it in hand-coded assembler, you've now got an architecture-dependent and horrendously unmaintainable uninstaller.

    Also, don't write a batch file for the reasons you mentioned; "CMD.EXE /c" is more reliable – but MSIs are a better and more supported solution for this problem.

  9. AC says:

    "ROP sled that goes FreeLibrary -> DeleteFile -> RemoveDirectory -> ExitProcess."

    That's at the same time ingenious and a horribly, horribly wrong hackery.

    Makes you wonder how somebody that even knows how to do it does not realize this.

  10. Joshua says:

    @AC: The MSDN article about how to make a self-deleting executable from the time when I first wrote it advocated injecting a DLL into explorer. [Look in the MSDN Library that came with VC6 if you want to find it.] Now you tell me which is the bad idea.

  11. Matt says:

    Just because injecting a DLL into explorer is a bad idea, doesn't mean using a ROP is a good (or even a better) one.

    Firstly, the use of a batch file is documented in MSDN and is sufficiently utilized that Microsoft won't be able to ever kill it. Self-modifying batch files run the entire US financial system for example, so it's unlikely that Microsoft would ever dare touch this "feature".

    Secondly, if you really want a better and arguably more supported solution, you can do it thus:

    1. Create a  selfdeleteonexit.bat in %TEMP% with FILE_FLAG_DELETE_ON_CLOSE.
    2. Write the following:

    :Repeat

    mkdir "C:pathbatchfilestarted"

    del "C:pathme.exe"

    if exist "C:pathme.exe" goto Repeat

    rmdir "C:path"

    rmdir "C:pathbatchfilestarted"

    1. Launch CMD.EXE selfdeleteonexit.bat
  12. Wait for C:pathbatchfilestarted to come into existence.

  13. Only now close the handle. The file will now be deleted either now if CMD.EXE has already dropped it's handle, or when CMD.EXE closes the handle or dies later.

  14. Exit current process.

  15. So with this system, regardless of whether CMD.EXE retains a handle to the batch file or not, you get to delete your own process from disk and the batch file will now also be gone. The only way this will break is if some future version of CMD.exe closes and re-opens the file after the mkdir on line (1), but before the del on line (2), or if the power blows – and in those worst cases you've left a <1KB file in %TEMP%.

  • David Candy says:

    I suppose similar comments app;y to dllgost?

  • Maurits says:

    There's also the idea of spawning a process like cmd.exe /c "timeout /t 10" & del me.exe

  • Joshua says:

    The only way this will break is if some future version of CMD.exe closes and re-opens the file after the mkdir on line (1), but before the del on line (2), or if the power blows – and in those worst cases you've left a <1KB file in %TEMP%.

    Which is exactly what it did when I tested it.

  • Killer{R} says:

    All that restricions are not restrictitons of rundll32, its a common restictions for any dll thats host app is not yours. So COM servervs, hooks, tasks etc – they all have same problems.

    Topic must be 'if you need to run your own process – write your exe and run it, do not run your dll with rundll32 cuz it will be not your process'

  • Anonymous Coward says:

    AC, in all fairness it's just a bunch of arguments pushed on the stack. Hardly unmaintainable and it generalises to every architecture I've ever used, although for some you'd have to use a define to use a different mnemonic.

    It's also by far the simplest and cleanest method I've found anywhere.

  • Anonymous Coward says:

    s/arguments/arguments and function pointers/

    Sorry, it seemed self-evident when I posted it, but on a second reading I thought I should clarify.

  • Owen Shepherd says:

    Not sure how it generalises to, say, AMD64 or ARM with their register calling conventions…

  • Anonymous Coward says:

    Not at all of course, but if I'd ever find myself in those environments (and assuming that the puzzle is to write a self-deleting executable as such, id est not part of a bigger problem which can be solved with MSI) I'd probably use FILE_FLAG_DELETE_ON_CLOSE and some careful synchronisation.

    [In other words "every architecture I've ever used" is "all one of them." Oh, and actually zero, because even x86 passes in registers sometimes. -Raymond]
  • A self-deleting executable would have been a piece of cake, if we used the super-secret time machine, and instead of media-dependent /SWAPRUN had Microsoft implement unconditional /SWAPRUN (with no options).

  • Joshua says:

    @Owen Shepard: It doesn't. What I'd do if I had to solve the same problem for x64 is VirtualAlloc(…, PAGE_EXECUTE_READWRITE, …), copy the compiled function there and call it. My compiler will generate position independent code with a certain compiler option. The only trick to remember is to pass its string arguments as heap-allocated pointers rather than constants.

    alegrl1 has a point.

    [Don't forget to call Rtl­Add­Function­Table for any code you dynamically generate. -Raymond]
  • Rick C says:

    [Your question is about using an existing DLL designed for rundll32. -Raymond]

    How does this statement interact with your comment to not use rundll32, since it's been deprecated?  Will printui.dll be rewriten/replaced?

  • Anonymous Coward says:

    Sure, IA-32, x86-64 and ARM are the only architectures out there. *rolls eyes* Next thing you're going to tell me that Windows is the only operating system.

  • steg says:

    Looks like Raymond has written the definitive guidance for rundll32. I could not find any useful information in a couple of minutes of searching for the answer. The top hit was a very out-of-date Microsoft KB. Based on my research (perhaps as much as 120 seconds) there's nothing out there that is this succinct. Perhaps Raymond, should he tire of whatever he does during the day, could move into documentation? I think I'm kidding — he should start a blog.

    This is my second attempt to post. Perhaps it failed because I linked to msdn documentation or mentioned the big G?

  • Matt says:

    @Rick C "How does this statement interact with your comment to not use rundll32, since it's been deprecated?  Will printui.dll be rewriten/replaced?"

    Two different things entirely.

    1. If you a developer (outside of Microsoft) and you are wondering if you should write your application to be an EXE or a DLL powered by rundll, use the EXE.
    2. If you a user of a different program that is powered by rundll32, it is still acceptable for you to call rundll32 to power it.

    Consequently it is fine for you to type rundll32 printui.dll into CMD.exe, and it is fine for you to invoke rundll32 printui from your EXE. But if you work for Contoso and they have written an awesome app in the form MyAweswomeApp.dll that is to be invoked rundll32.dll MyAwesomeApp.dll, they should change as soon as possible to deploying an EXE (MyAwesomeApp.EXE) instead.

  • Gechurch says:

    @Cesar

    "What would be the non-rundll32 equivalent of something like:

    rundll32.exe printui.dll,PrintUIEntry /ia /f "…ghostpdfghostpdf.inf" /m "Ghostscript PDF"

    Microsoft wrote their own .exe to handle this, starting with Windows 7 (or Vista). The paramaters are the same, but no more rundll32. You'd instead run:

    PrintUI.exe /ia /f "…ghostpdfghostpdf.inf" /m "Ghostscript PDF"

  • Rick C says:

    @Gechurch not only that, but if PrintUIEntry is written properly it should be a pretty simple matter to write a program to call it instead of using rundll32:  just write a short main function that calls LoadLibrary and GetProcAddress.

    Matt, my question was more along the line of "if rundll32 has been deprecated, doesn't that mean calling PrintUIEntry be done some other way now?  And Gechurch answered that there is a new way.

  • Medinoc says:

    @nitpickery: I hate the fact that /LARGEADDRESSAWARE is the same flag for /3GB and for 64-bit, despite not being the same problem: The problem with /3GB is the possibility of "negative" pointers, while the problem with 64-bit is the risk of storing a 64-bit pointer in a DWORD…

    So if you only support 64-bit you have to be sure to set the flag in your 64-bit build, but not in your 32-bit build…

  • Comments are closed.