Why is Environment.CurrentDirectory bad?

It is bad in general for various reasons, but it’s especially bad in build tools and related space (like CI servers and such).

MSBuild could have had fine-grained parallelism where it could execute multiple tasks on different threads in parallel within the same process, significantly speeding up builds. However it can’t do that because many tasks rely on Environment.CurrentDirectory which is per process, and so to make sure the current directory is deterministic and well-known at the time a task executes, there can only be one task running at a time within the same process.

Additionally, it is terrible for discoverability and ability to reason about assumptions. Relying on Environment.CurrentDirectory makes an implicit assumption that is not recorded anywhere. For instance, here’s a field in Team Build that lets you select unit-test assemblies to run:


Where is this pattern rooted? To be able to write a pattern you need to know if its relative (to what?) and in which directory it is rooted. It obviously can’t be a full path since on the CI server the path varies by build agent id. After reading the source code for the build activity that calculates the files from this spec I found this:


This code relies on the current directory being set… where? To what? It’s a nightmare scenario for maintainers.

Last but not least, Environment.CurrentDirectory is global mutable state. Worse, it is global mutable state that makes it impossible to track causality. Who set this last? Where to put a breakpoint to intercept mutation?

I’m sure there are multiple reasons I’m missing but I don’t even care to enumerate exhaustively. The above is terrifying enough to send a clear message: avoid at all costs!

But, a reasonable response to that would be: what to do instead? Use patterns like $(SolutionRoot)\bin\Release\*.Tests.dll instead of just bin\Release. Every path should be rooted (maybe with a variable that is well defined) so that its clear where it is. Pass known variables around, avoid the untraceable global mutable state from the 80’s.

Same goes for environment variables. Avoid at all costs! If a build requires an environment variable set to work correctly, it has failed.

P.S. Also riddle me this. Why on earth is there a Directory.GetCurrentDirectory()?