What are these strange =C: environment variables?

You won’t see them when you execute a SET command, but if you write a program that manually enumerates all the environment variables and prints them out, and if you launch it from a command prompt, then you’ll see weird variables with names like =C: and whose values correspond to directories on that drive. What are these things?

These variables are part of the private bookkeeping of the command processor cmd.exe. That’s why I added if you launch it from a command prompt to the steps above, because if you run the program from Explorer’s Run dialog, you won’t see them. If a cmd.exe is not in the chain of custody of your environment block, then you won’t see the weird cmd.exe bookkeeping variables.

Okay, so the command processor sets these things, but what are they? They are a leftover from the command processor’s attempt to mimic the old MS-DOS way that drives and directories were handled. Whereas in Win32, there is only one current directory, in MS-DOS, there was one current directory for each drive. Consider the following sequence of commands:

// current directory for drive A is A:\SUBDIR
A> B:
// current directory for drive B is B:\TWO
B> A:
// shows a directory listing for A:\SUBDIR

During this sequence of commands, we start with A: as the current drive and set its current directory to A:\SUBDIR. Next, we set the current drive to B: and set B:\TWO as its current directory. Finally, we set the current drive back to A:, and when we ask for a listing, we get the contents of A:\SUBDIR because that is the current directory on the current drive.

Win32 does not have the concept of a separate current directory for each drive, but the command processor wanted to preserve the old MS-DOS behavior because people were accustomed to it (and batch files relied upon it). The solution was to store this “per-drive current directory” in the environment, using a weird-o environment variable name so it wouldn’t conflict with normal environment variables.

If you repeated the above commands in cmd.exe, the output is the same, but it is accomplished in a very different way.

// Environment variable =A: set to A:\SUBDIR
// Current Win32 directory set to A:\SUBDIR
A> B:
// Environment variable =B: set to B:\TWO
// current Win32 directory set to B:\TWO
B> A:
// Current Win32 directory set to A:\SUBDIR
// shows a directory listing for A:\SUBDIR

When we switch back to drive A:, the command processor says, “Hey, what was the current directory on drive A: the last time I was there?” It looks into its environment and finds the =A: variable, which tells it, “Oh, it was A:\SUBDIR”. And that’s the Win32 directory that it sets as current.

But why put these internal variables in the environment? Can’t they just be regular variables inside the cmd.exe process?

The variables are exported into the environment because you want these “fake per-drive current directories” to be inherited by child processes. For example, consider that you are sitting at your command prompt, you run emacs, then from emacs, you shell out to another command prompt. You would expect that the nested command prompt will have the same “per-drive current directories” that you set back in the outer command prompt.

D:\> emacs
M-x shell
D:\> C:
// the "current directory on drive C" was inherited as expected

What should you do about these variables, then?

Nothing. Just let them be and do their jobs. I’m just mentioning them here so you won’t freak out when you see them.

Comments (30)
  1. John says:

    Awesome.  I always wondered what these were used for.

  2. Gabe says:

    Per-drive-letter current directories are an awesome convenience when combined with SUBST (for local drives) or PUSHD (for network shares) to make it very easy to work with deeply nested directory trees.

  3. Mark Jonson says:

    Shouldn’t this be filed under History, as this is a behavior in order to preserve past MS-DOS behavior.

  4. GregM says:

    Thanks, I ran into this about a month or so ago when I had to write a program that called another program with a subset of the current environment, and was a bit confused by them.  I thought they were something specific to the other program, since I saw them for the first time after I installed that other program.  I guess I had launched Visual Studio from a command prompt that time.

  5. Alexandre Grigoriev says:

    @Raymond & Leo Davidson:

    Win32 apparently uses them under the covers. How else would it convert d:file.txt to canonicalized name d:d_current_dirfile.txt

  6. Alexandre Grigoriev says:
    • I mean, in CreateFile cal and other such.
  7. Dan says:

    I’m pretty sure this stuff used to work in Save dialogs too (you could type in C: to jump to the current directory on C: from the current app).  Has this capability been removed from Windows and is only present in cmd.exe now?

  8. WndSks says:

    "You won’t see them when you execute a SET command" not 100% true, if you execute set " (set space doublequote)it will display those hidden variables (Drive paths and =ExitCode)

  9. Teo says:

    "Win32 does not have the concept of a separate current directory for each drive" – is it a contract thing, or it is a implementation detail of the way NT creates the dos devices which in reality are links in the Object manager namespace?

  10. Mike Caron says:

    Teo, how would you ask Win32 about different drives? GetCurrentDirectory() doesn’t have an argument to specify the drive, and a process doesn’t have more than one active directory.

    And, frankly, I don’t see why a process (that is: process, not human) would need more than one active directory.

  11. James says:


    I think that this is not cmd.exe but Win32 that creates and tracks these environment variables.  I tried this in PowerShell and in Python and both seem to preserve the current directory per-drive if you chdir between "c:" and "d:".

  12. Ishai says:

    Actually, Win32 DOES keep per volume current directory.  And this is even documented.


  13. Random832 says:

    Win32 does track a per-drive current directory (this is documented at http://msdn.microsoft.com/en-us/library/aa363806.aspx), but on a test program I wrote it doesn’t touch the environment variables, so it seems to be a different mechanism. Why the difference? Are the win32 ones not inherited by child processes?

    (Also, what’s =ExitCode? I mean, it’s obvious what it is, but why not just make an actual environment variable of %ErrorLevel% – or, really what’s the benefit of making this information inherit to child processes?)

    PS: Interestingly, these also don’t show up in the CRT’s environ/_wenviron – maybe something to do with the fact that _[w]putenv would interpret them as having an empty name?

    "Teo, how would you ask Win32 about different drives?"

    SetCurrentDirectory("A:"); GetCurrentDirectory(…); SetCurrentDirectory("B:"); GetCurrentDirectory(…);

  14. Timothy Byrd says:

    Also works in Windows PowerShell, so not quite legacy, yet.

  15. Leo Davidson says:

    For what it’s worth, the CRT _chdir function still maintains these env-vars.

    From reading the CRT source it appears that Win32 SetCurrentDirectory does not set the env-vars (not surprising) *but* Win32 functions like GetFullPathName apparently do use the env-vars if they exist and you pass them paths like "C:" (without a slash on the end).

    (This came up on Usenet earlier this week, coincidentally: http://groups.google.com.au/group/microsoft.public.win32.programmer.kernel/browse_thread/thread/df3c6d658f98e3d3# )

  16. Blake says:

    I can’t speak for Python, but PowerShell maintains it’s own concept of a current location on each drive, even for ‘drives’ that aren’t at all filesystem related, like the registry or certificate store.   The way it does so does not interop with cmd.exe – if you start a cmd subshell it will inherit the correct Win32 current directory on the current drive, but none of the others.   Sounds like I should go add a feature request on Connect.

  17. Scott says:

    When I wrote my little CD replacement utility, it took me a long time to figure out these variables.  I don’t remember how I stumbled on them, but I was a very happy camper when I finally did.  If CMD’s working directory is changed without these variables being updated it’s not too happy in some situations.  Of course, that’s probably what I get for mucking with another process’ working directory to begin with :)

  18. unekdoud says:

    Placing either = or : in any short string makes me interpret it as an emoticon.

  19. Larry Hosken says:

    "I’m just mentioning them here so you won’t freak out when you see them."

    I won’t have occasion to see them any time soon but I’M GOING TO FREAK OUT ANYWAY knowing that they’re in there, lurking out of sight.

  20. Andrei Vajna says:

    Ah, this explains the following behaviour.

    I’m on drive C and execute the following command:


    The current dir wouldn’t change, I’d still be on drive C, but then if I do

    C> D:

    that will get me to drive D in D:SOMEDIR.

  21. Bert Huijben says:

    The nicest issue I found, related to this examples is if you use

    CD d:SomeDir (note the lower case D)


    Then your current working directory is "d:SomeDir" instead of "D:SomeDir" as virtually all software expects without knowing.

  22. Another John says:

    Shouldn’t that be:

    B> CD TWO

    // current directory for drive B is B:TWO

    *B*> A:


    B> CD TWO

    // Environment variable =B: set to B:TWO

    // current Win32 directory set to B:TWO

    *B*> A:

    Apologies for “nitpicker’s corner” comment – just want to be sure I understand wat is going on

    [Fixed, thanks. -Raymond]
  23. Médinoc says:

    The strangest thing about current directories is that they are apparently not thread-local.

  24. The way "cd d:example" doesn’t appear to change anything (unless you’re on D: already of course) seems confusing at first, but makes sense once you understand it’s changing "the current directory on D:" rather than "the current directory". The /D switch is quite handy for giving the results you might be expecting otherwise – very handy in batch files for changing to a complete path.

    Interesting the way the implementation seems split between cmd.exe’s use of environment variables and something else within Win32 – then down on the native level, no concept of "current directory" at all; if you want to open a relative pathname, you have to pass NtCreateFile an explicit "root" directory to resolve it against at the same time.

  25. Joshua says:

    @Médinoc: You’re right, current directory is not thread local. In MS-DOS, it wasn’t even process local (which XTree abused quite effectively).

  26. Random832 says:

    The current directory on unix systems isn’t thread-local – you’re supposed to maintain an open handle to any directory you’re interested in on multithreaded apps.

    On MS-DOS, was it per-drive or per-disk?

  27. SuperKoko says:

    To get the current directory on X drive, use GetFullPathName on the "X:." string.

  28. Wince says:

    Win32 on WinCE doesn’t have current dir at all.

  29. Myria says:

    The NT kernel doesn’t even have current directories at all; they are entirely a user-mode concept.  The current directory is implemented by kernel32.dll and ntdll.dll as an open handle to the directory that is referenced whenever a relative path is used.  This directory is inherited by child processes through the process parameters block.

    If you’ve ever programmed the NT kernel, you may have heard of the kernel call ZwCreateFile (or NtCreateFile):


    When opening a relative path, the stored current directory handle is passed by ntdll.dll in the RootDirectory field of the ObjectAttributes parameter when calling NtCreateFile to complete your request.

    Other operating systems, in particular Linux, have a similar concept.  Linux’s *at calls – openat, chmodat, unlinkat, … – allow you to specify a directory handle from which relative paths are interpreted.  (Linux has a current directory for each process at the kernel level, however.)

  30. Random832 says:

    A later test seems to show that win32 doesn’t maintain per-drive current directories after all (so that documentation I linked earlier is wrong)

    SetCurrentDirectory/GetFullPathName will pick them up from the environment variables (which are maintained by MSVCRT in addition to cmd) if they are set and you are specifying a relative path, but SetCurrentDirectory neither sets them nor does anything else to save the current directory per-drive.

Comments are closed.