diff --git a/Source/CMakeLists.txt b/Source/CMakeLists.txt
index 5f2cbc3c056a9e4688d106d323727114e6c06e2e..59920f8b6c0bde411a3d6c13dea2ae2cb3b9017b 100644
--- a/Source/CMakeLists.txt
+++ b/Source/CMakeLists.txt
@@ -383,6 +383,8 @@ set(SRCS
   cmVariableWatch.h
   cmVersion.cxx
   cmVersion.h
+  cmWorkingDirectory.cxx
+  cmWorkingDirectory.h
   cmXMLParser.cxx
   cmXMLParser.h
   cmXMLSafe.cxx
diff --git a/Source/CPack/OSXScriptLauncher.cxx b/Source/CPack/OSXScriptLauncher.cxx
index b159e64f3fa92d89ac3c711d77663c4ef77fa520..aeabde9dd550ce5891d9e7d8f10c95b9b0a856cd 100644
--- a/Source/CPack/OSXScriptLauncher.cxx
+++ b/Source/CPack/OSXScriptLauncher.cxx
@@ -20,7 +20,6 @@
 int main(int argc, char* argv[])
 {
   // if ( cmsys::SystemTools::FileExists(
-  std::string cwd = cmsys::SystemTools::GetCurrentWorkingDirectory();
   cmsys::ofstream ofs("/tmp/output.txt");
 
   CFStringRef fileName;
diff --git a/Source/CPack/cmCPackArchiveGenerator.cxx b/Source/CPack/cmCPackArchiveGenerator.cxx
index 9d9cd66daccd862aed216d0a9ca8f1ab0525e489..cc01b0c9eefd610fc3b8ffff440f15df3bc26be5 100644
--- a/Source/CPack/cmCPackArchiveGenerator.cxx
+++ b/Source/CPack/cmCPackArchiveGenerator.cxx
@@ -7,6 +7,7 @@
 #include "cmCPackLog.h"
 #include "cmGeneratedFileStream.h"
 #include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
 
 #include <map>
 #include <ostream>
@@ -37,9 +38,8 @@ int cmCPackArchiveGenerator::addOneComponentToArchive(
   // Add the files of this component to the archive
   std::string localToplevel(this->GetOption("CPACK_TEMPORARY_DIRECTORY"));
   localToplevel += "/" + component->Name;
-  std::string dir = cmSystemTools::GetCurrentWorkingDirectory();
   // Change to local toplevel
-  cmSystemTools::ChangeDirectory(localToplevel);
+  cmWorkingDirectory workdir(localToplevel);
   std::string filePrefix;
   if (this->IsOn("CPACK_COMPONENT_INCLUDE_TOPLEVEL_DIRECTORY")) {
     filePrefix = this->GetOption("CPACK_PACKAGE_FILE_NAME");
@@ -64,8 +64,6 @@ int cmCPackArchiveGenerator::addOneComponentToArchive(
       return 0;
     }
   }
-  // Go back to previous dir
-  cmSystemTools::ChangeDirectory(dir);
   return 1;
 }
 
@@ -227,8 +225,7 @@ int cmCPackArchiveGenerator::PackageFiles()
   // CASE 3 : NON COMPONENT package.
   DECLARE_AND_OPEN_ARCHIVE(packageFileNames[0], archive);
   std::vector<std::string>::const_iterator fileIt;
-  std::string dir = cmSystemTools::GetCurrentWorkingDirectory();
-  cmSystemTools::ChangeDirectory(toplevel);
+  cmWorkingDirectory workdir(toplevel);
   for (fileIt = files.begin(); fileIt != files.end(); ++fileIt) {
     // Get the relative path to the file
     std::string rp =
@@ -241,7 +238,6 @@ int cmCPackArchiveGenerator::PackageFiles()
       return 0;
     }
   }
-  cmSystemTools::ChangeDirectory(dir);
   // The destructor of cmArchiveWrite will close and finish the write
   return 1;
 }
diff --git a/Source/CPack/cmCPackGenerator.cxx b/Source/CPack/cmCPackGenerator.cxx
index e1a4a2a196b911a39d7ce21375ffd17d3794bcdf..f6ea8cf0997902329ebc639699230ef351837a99 100644
--- a/Source/CPack/cmCPackGenerator.cxx
+++ b/Source/CPack/cmCPackGenerator.cxx
@@ -16,6 +16,7 @@
 #include "cmGlobalGenerator.h"
 #include "cmMakefile.h"
 #include "cmStateSnapshot.h"
+#include "cmWorkingDirectory.h"
 #include "cmXMLSafe.h"
 #include "cm_auto_ptr.hxx"
 #include "cmake.h"
@@ -383,7 +384,7 @@ int cmCPackGenerator::InstallProjectViaInstalledDirectories(
         goToDir += "/" + subdir;
         cmCPackLogger(cmCPackLog::LOG_DEBUG, "Change dir to: " << goToDir
                                                                << std::endl);
-        cmSystemTools::ChangeDirectory(goToDir);
+        cmWorkingDirectory workdir(goToDir);
         for (symlinkedIt = symlinkedFiles.begin();
              symlinkedIt != symlinkedFiles.end(); ++symlinkedIt) {
           cmCPackLogger(cmCPackLog::LOG_DEBUG, "Will create a symlink: "
@@ -408,7 +409,6 @@ int cmCPackGenerator::InstallProjectViaInstalledDirectories(
         }
         cmCPackLogger(cmCPackLog::LOG_DEBUG, "Going back to: " << curDir
                                                                << std::endl);
-        cmSystemTools::ChangeDirectory(curDir);
       }
     }
   }
diff --git a/Source/CTest/cmCTestBuildAndTestHandler.cxx b/Source/CTest/cmCTestBuildAndTestHandler.cxx
index 6780a0e91f24887fbf46daab7e5482f4aa50b5aa..6f81429b9e48f32d9051398d820bd1578cacdc09 100644
--- a/Source/CTest/cmCTestBuildAndTestHandler.cxx
+++ b/Source/CTest/cmCTestBuildAndTestHandler.cxx
@@ -6,6 +6,7 @@
 #include "cmCTestTestHandler.h"
 #include "cmGlobalGenerator.h"
 #include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
 #include "cmake.h"
 
 #include <cmsys/Process.h>
@@ -42,7 +43,7 @@ int cmCTestBuildAndTestHandler::ProcessHandler()
 int cmCTestBuildAndTestHandler::RunCMake(std::string* outstring,
                                          std::ostringstream& out,
                                          std::string& cmakeOutString,
-                                         std::string& cwd, cmake* cm)
+                                         cmake* cm)
 {
   unsigned int k;
   std::vector<std::string> args;
@@ -85,8 +86,6 @@ int cmCTestBuildAndTestHandler::RunCMake(std::string* outstring,
   if (cm->Run(args) != 0) {
     out << "Error: cmake execution failed\n";
     out << cmakeOutString << "\n";
-    // return to the original directory
-    cmSystemTools::ChangeDirectory(cwd);
     if (outstring) {
       *outstring = out.str();
     } else {
@@ -99,8 +98,6 @@ int cmCTestBuildAndTestHandler::RunCMake(std::string* outstring,
     if (cm->Run(args) != 0) {
       out << "Error: cmake execution failed\n";
       out << cmakeOutString << "\n";
-      // return to the original directory
-      cmSystemTools::ChangeDirectory(cwd);
       if (outstring) {
         *outstring = out.str();
       } else {
@@ -199,13 +196,12 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring)
   double clock_start = cmSystemTools::GetTime();
 
   // make sure the binary dir is there
-  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
   out << "Internal cmake changing into directory: " << this->BinaryDir
       << std::endl;
   if (!cmSystemTools::FileIsDirectory(this->BinaryDir)) {
     cmSystemTools::MakeDirectory(this->BinaryDir.c_str());
   }
-  cmSystemTools::ChangeDirectory(this->BinaryDir);
+  cmWorkingDirectory workdir(this->BinaryDir);
 
   if (this->BuildNoCMake) {
     // Make the generator available for the Build call below.
@@ -217,7 +213,7 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring)
     cm.LoadCache(this->BinaryDir);
   } else {
     // do the cmake step, no timeout here since it is not a sub process
-    if (this->RunCMake(outstring, out, cmakeOutString, cwd, &cm)) {
+    if (this->RunCMake(outstring, out, cmakeOutString, &cm)) {
       return 1;
     }
   }
@@ -304,8 +300,6 @@ int cmCTestBuildAndTestHandler::RunCMakeAndTest(std::string* outstring)
     } else {
       cmCTestLog(this->CTest, ERROR_MESSAGE, out.str());
     }
-    // return to the original directory
-    cmSystemTools::ChangeDirectory(cwd);
     return 1;
   }
 
diff --git a/Source/CTest/cmCTestBuildAndTestHandler.h b/Source/CTest/cmCTestBuildAndTestHandler.h
index 5885738e4962fcc14a9417ac1768d7afe9d9a366..af082a3984f5de78f03abf57163507830f8558c9 100644
--- a/Source/CTest/cmCTestBuildAndTestHandler.h
+++ b/Source/CTest/cmCTestBuildAndTestHandler.h
@@ -46,7 +46,7 @@ protected:
   ///! Run CMake and build a test and then run it as a single test.
   int RunCMakeAndTest(std::string* output);
   int RunCMake(std::string* outstring, std::ostringstream& out,
-               std::string& cmakeOutString, std::string& cwd, cmake* cm);
+               std::string& cmakeOutString, cmake* cm);
 
   std::string Output;
 
diff --git a/Source/CTest/cmCTestCoverageHandler.cxx b/Source/CTest/cmCTestCoverageHandler.cxx
index 989c096573155e31d5f52cb88bb2bd2ae676a78d..120c5d9821ebb207a80f51c99f48c9541463589f 100644
--- a/Source/CTest/cmCTestCoverageHandler.cxx
+++ b/Source/CTest/cmCTestCoverageHandler.cxx
@@ -12,6 +12,7 @@
 #include "cmParseJacocoCoverage.h"
 #include "cmParsePHPCoverage.h"
 #include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
 #include "cmXMLWriter.h"
 #include "cmake.h"
 
@@ -969,9 +970,8 @@ int cmCTestCoverageHandler::HandleGCovCoverage(
 
   std::string testingDir = this->CTest->GetBinaryDir() + "/Testing";
   std::string tempDir = testingDir + "/CoverageInfo";
-  std::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory();
   cmSystemTools::MakeDirectory(tempDir.c_str());
-  cmSystemTools::ChangeDirectory(tempDir);
+  cmWorkingDirectory workdir(tempDir);
 
   int gcovStyle = 0;
 
@@ -1294,7 +1294,6 @@ int cmCTestCoverageHandler::HandleGCovCoverage(
     }
   }
 
-  cmSystemTools::ChangeDirectory(currentDirectory);
   return file_count;
 }
 
@@ -1340,7 +1339,6 @@ int cmCTestCoverageHandler::HandleLCovCoverage(
     return 0;
   }
   std::string testingDir = this->CTest->GetBinaryDir();
-  std::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory();
 
   std::set<std::string> missingFiles;
 
@@ -1362,7 +1360,7 @@ int cmCTestCoverageHandler::HandleLCovCoverage(
     cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, "." << std::flush,
                        this->Quiet);
     std::string fileDir = cmSystemTools::GetFilenamePath(*it);
-    cmSystemTools::ChangeDirectory(fileDir);
+    cmWorkingDirectory workdir(fileDir);
     std::string command = "\"" + lcovCommand + "\" " + lcovExtraFlags + " ";
 
     cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
@@ -1552,7 +1550,6 @@ int cmCTestCoverageHandler::HandleLCovCoverage(
     }
   }
 
-  cmSystemTools::ChangeDirectory(currentDirectory);
   return file_count;
 }
 
@@ -1591,13 +1588,8 @@ bool cmCTestCoverageHandler::FindLCovFiles(std::vector<std::string>& files)
   gl.RecurseOff(); // No need of recurse if -prof_dir${BUILD_DIR} flag is
                    // used while compiling.
   gl.RecurseThroughSymlinksOff();
-  std::string prevBinaryDir;
   std::string buildDir = this->CTest->GetCTestConfiguration("BuildDirectory");
-  if (cmSystemTools::ChangeDirectory(buildDir)) {
-    cmCTestLog(this->CTest, ERROR_MESSAGE, "Error changing directory to "
-                 << buildDir << std::endl);
-    return false;
-  }
+  cmWorkingDirectory workdir(buildDir);
 
   // Run profmerge to merge all *.dyn files into dpi files
   if (!cmSystemTools::RunSingleCommand("profmerge")) {
@@ -1605,11 +1597,9 @@ bool cmCTestCoverageHandler::FindLCovFiles(std::vector<std::string>& files)
     return false;
   }
 
-  prevBinaryDir = cmSystemTools::GetCurrentWorkingDirectory();
-
   // DPI file should appear in build directory
   std::string daGlob;
-  daGlob = prevBinaryDir;
+  daGlob = buildDir;
   daGlob += "/*.dpi";
   cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                      "   looking for dpi files in: " << daGlob << std::endl,
@@ -1646,11 +1636,7 @@ int cmCTestCoverageHandler::HandleTracePyCoverage(
 
   std::string testingDir = this->CTest->GetBinaryDir() + "/Testing";
   std::string tempDir = testingDir + "/CoverageInfo";
-  std::string currentDirectory = cmSystemTools::GetCurrentWorkingDirectory();
   cmSystemTools::MakeDirectory(tempDir.c_str());
-  cmSystemTools::ChangeDirectory(tempDir);
-
-  cmSystemTools::ChangeDirectory(currentDirectory);
 
   std::vector<std::string>::iterator fileIt;
   int file_count = 0;
@@ -1737,7 +1723,6 @@ int cmCTestCoverageHandler::HandleTracePyCoverage(
     }
     ++file_count;
   }
-  cmSystemTools::ChangeDirectory(currentDirectory);
   return file_count;
 }
 
diff --git a/Source/CTest/cmCTestHandlerCommand.cxx b/Source/CTest/cmCTestHandlerCommand.cxx
index 2a67d47d9589300cf853eb666797c5b7aa8d0ff2..c99e450d05bd4bf2f28e6591c028e81549798e9b 100644
--- a/Source/CTest/cmCTestHandlerCommand.cxx
+++ b/Source/CTest/cmCTestHandlerCommand.cxx
@@ -6,6 +6,7 @@
 #include "cmCTestGenericHandler.h"
 #include "cmMakefile.h"
 #include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
 #include "cmake.h"
 
 #include <sstream>
@@ -216,8 +217,7 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
       handler->SetSubmitIndex(atoi(this->Values[ct_SUBMIT_INDEX]));
     }
   }
-  std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
-  cmSystemTools::ChangeDirectory(
+  cmWorkingDirectory workdir(
     this->CTest->GetCTestConfiguration("BuildDirectory"));
   int res = handler->ProcessHandler();
   if (this->Values[ct_RETURN_VALUE] && *this->Values[ct_RETURN_VALUE]) {
@@ -243,7 +243,6 @@ bool cmCTestHandlerCommand::InitialPass(std::vector<std::string> const& args,
     this->Makefile->AddDefinition(this->Values[ct_CAPTURE_CMAKE_ERROR],
                                   returnString);
   }
-  cmSystemTools::ChangeDirectory(current_dir);
   return true;
 }
 
diff --git a/Source/CTest/cmCTestMultiProcessHandler.cxx b/Source/CTest/cmCTestMultiProcessHandler.cxx
index c1724ab79be8093871bdb321eab04ac246e612c2..ff465ab26a1b821a194cfa227c88fbeb37e85488 100644
--- a/Source/CTest/cmCTestMultiProcessHandler.cxx
+++ b/Source/CTest/cmCTestMultiProcessHandler.cxx
@@ -7,6 +7,7 @@
 #include "cmCTestScriptHandler.h"
 #include "cmCTestTestHandler.h"
 #include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
 
 #include <algorithm>
 #include <cmsys/FStream.hxx>
@@ -138,8 +139,7 @@ void cmCTestMultiProcessHandler::StartTestProcess(int test)
     }
   }
 
-  std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
-  cmSystemTools::ChangeDirectory(this->Properties[test]->Directory);
+  cmWorkingDirectory workdir(this->Properties[test]->Directory);
 
   // Lock the resources we'll be using
   this->LockResources(test);
@@ -166,7 +166,6 @@ void cmCTestMultiProcessHandler::StartTestProcess(int test)
     this->Failed->push_back(this->Properties[test]->Name);
     delete testRun;
   }
-  cmSystemTools::ChangeDirectory(current_dir);
 }
 
 void cmCTestMultiProcessHandler::LockResources(int index)
@@ -683,9 +682,7 @@ void cmCTestMultiProcessHandler::PrintTestList()
     count++;
     cmCTestTestHandler::cmCTestTestProperties& p = *it->second;
 
-    // push working dir
-    std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
-    cmSystemTools::ChangeDirectory(p.Directory);
+    cmWorkingDirectory workdir(p.Directory);
 
     cmCTestRunTest testRun(this->TestHandler);
     testRun.SetIndex(p.Index);
@@ -724,8 +721,6 @@ void cmCTestMultiProcessHandler::PrintTestList()
     cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, " ", this->Quiet);
     cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, p.Name << std::endl,
                        this->Quiet);
-    // pop working dir
-    cmSystemTools::ChangeDirectory(current_dir);
   }
 
   cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT, std::endl
diff --git a/Source/CTest/cmCTestRunTest.cxx b/Source/CTest/cmCTestRunTest.cxx
index ac1644f0da79e9c5b901175af4f89099604bfd6a..f148f30c5d9b89875bc8faab1aa8ee6a552731a8 100644
--- a/Source/CTest/cmCTestRunTest.cxx
+++ b/Source/CTest/cmCTestRunTest.cxx
@@ -7,6 +7,7 @@
 #include "cmCTestTestHandler.h"
 #include "cmProcess.h"
 #include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
 
 #include <cmConfigure.h>
 #include <cm_curl.h>
@@ -270,14 +271,11 @@ bool cmCTestRunTest::EndTest(size_t completed, size_t total, bool started)
     *this->TestHandler->LogFile << "Test time = " << buf << std::endl;
   }
 
-  // Set the working directory to the tests directory
-  std::string oldpath = cmSystemTools::GetCurrentWorkingDirectory();
-  cmSystemTools::ChangeDirectory(this->TestProperties->Directory);
-
-  this->DartProcessing();
-
-  // restore working directory
-  cmSystemTools::ChangeDirectory(oldpath);
+  // Set the working directory to the tests directory to process Dart files.
+  {
+    cmWorkingDirectory workdir(this->TestProperties->Directory);
+    this->DartProcessing();
+  }
 
   // if this is doing MemCheck then all the output needs to be put into
   // Output since that is what is parsed by cmCTestMemCheckHandler
@@ -356,11 +354,8 @@ bool cmCTestRunTest::StartAgain()
   }
   this->RunAgain = false; // reset
   // change to tests directory
-  std::string current_dir = cmSystemTools::GetCurrentWorkingDirectory();
-  cmSystemTools::ChangeDirectory(this->TestProperties->Directory);
+  cmWorkingDirectory workdir(this->TestProperties->Directory);
   this->StartTest(this->TotalNumberOfTests);
-  // change back
-  cmSystemTools::ChangeDirectory(current_dir);
   return true;
 }
 
diff --git a/Source/CTest/cmCTestSubmitHandler.cxx b/Source/CTest/cmCTestSubmitHandler.cxx
index 5e5119d50fc2d4a44bcf032d0bbd574d5973a400..cc399b0f2051c5f5361fbddedf2e499c5d9194a0 100644
--- a/Source/CTest/cmCTestSubmitHandler.cxx
+++ b/Source/CTest/cmCTestSubmitHandler.cxx
@@ -19,6 +19,7 @@
 #include "cmState.h"
 #include "cmSystemTools.h"
 #include "cmThirdParty.h"
+#include "cmWorkingDirectory.h"
 #include "cmXMLParser.h"
 #include "cmake.h"
 
@@ -1519,7 +1520,6 @@ int cmCTestSubmitHandler::ProcessHandler()
 #endif
   } else if (dropMethod == "scp") {
     std::string url;
-    std::string oldWorkingDirectory;
     if (!this->CTest->GetCTestConfiguration("DropSiteUser").empty()) {
       url += this->CTest->GetCTestConfiguration("DropSiteUser") + "@";
     }
@@ -1528,19 +1528,16 @@ int cmCTestSubmitHandler::ProcessHandler()
 
     // change to the build directory so that we can uses a relative path
     // on windows since scp dosn't support "c:" a drive in the path
-    oldWorkingDirectory = cmSystemTools::GetCurrentWorkingDirectory();
-    cmSystemTools::ChangeDirectory(buildDirectory);
+    cmWorkingDirectory workdir(buildDirectory);
 
     if (!this->SubmitUsingSCP(this->CTest->GetCTestConfiguration("ScpCommand"),
                               "Testing/" + this->CTest->GetCurrentTag(), files,
                               prefix, url)) {
-      cmSystemTools::ChangeDirectory(oldWorkingDirectory);
       cmCTestLog(this->CTest, ERROR_MESSAGE,
                  "   Problems when submitting via SCP" << std::endl);
       ofs << "   Problems when submitting via SCP" << std::endl;
       return -1;
     }
-    cmSystemTools::ChangeDirectory(oldWorkingDirectory);
     cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
                        "   Submission successful" << std::endl, this->Quiet);
     ofs << "   Submission successful" << std::endl;
@@ -1550,22 +1547,18 @@ int cmCTestSubmitHandler::ProcessHandler()
 
     // change to the build directory so that we can uses a relative path
     // on windows since scp dosn't support "c:" a drive in the path
-    std::string oldWorkingDirectory =
-      cmSystemTools::GetCurrentWorkingDirectory();
-    cmSystemTools::ChangeDirectory(buildDirectory);
+    cmWorkingDirectory workdir(buildDirectory);
     cmCTestOptionalLog(this->CTest, HANDLER_VERBOSE_OUTPUT,
                        "   Change directory: " << buildDirectory << std::endl,
                        this->Quiet);
 
     if (!this->SubmitUsingCP("Testing/" + this->CTest->GetCurrentTag(), files,
                              prefix, location)) {
-      cmSystemTools::ChangeDirectory(oldWorkingDirectory);
       cmCTestLog(this->CTest, ERROR_MESSAGE,
                  "   Problems when submitting via CP" << std::endl);
       ofs << "   Problems when submitting via cp" << std::endl;
       return -1;
     }
-    cmSystemTools::ChangeDirectory(oldWorkingDirectory);
     cmCTestOptionalLog(this->CTest, HANDLER_OUTPUT,
                        "   Submission successful" << std::endl, this->Quiet);
     ofs << "   Submission successful" << std::endl;
diff --git a/Source/CTest/cmCTestTestHandler.cxx b/Source/CTest/cmCTestTestHandler.cxx
index 6175e50263909f4ed083ed5b5503d51f096004c4..9d2206532229e024bbc614398e525610eeaade5d 100644
--- a/Source/CTest/cmCTestTestHandler.cxx
+++ b/Source/CTest/cmCTestTestHandler.cxx
@@ -27,6 +27,7 @@
 #include "cmState.h"
 #include "cmStateSnapshot.h"
 #include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
 #include "cmXMLWriter.h"
 #include "cm_auto_ptr.hxx"
 #include "cm_utf8.h"
@@ -86,22 +87,24 @@ bool cmCTestSubdirCommand::InitialPass(std::vector<std::string> const& args,
       // No subdirectory? So what...
       continue;
     }
-    cmSystemTools::ChangeDirectory(fname);
-    const char* testFilename;
-    if (cmSystemTools::FileExists("CTestTestfile.cmake")) {
-      // does the CTestTestfile.cmake exist ?
-      testFilename = "CTestTestfile.cmake";
-    } else if (cmSystemTools::FileExists("DartTestfile.txt")) {
-      // does the DartTestfile.txt exist ?
-      testFilename = "DartTestfile.txt";
-    } else {
-      // No CTestTestfile? Who cares...
-      continue;
+    bool readit = false;
+    {
+      cmWorkingDirectory workdir(fname);
+      const char* testFilename;
+      if (cmSystemTools::FileExists("CTestTestfile.cmake")) {
+        // does the CTestTestfile.cmake exist ?
+        testFilename = "CTestTestfile.cmake";
+      } else if (cmSystemTools::FileExists("DartTestfile.txt")) {
+        // does the DartTestfile.txt exist ?
+        testFilename = "DartTestfile.txt";
+      } else {
+        // No CTestTestfile? Who cares...
+        continue;
+      }
+      fname += "/";
+      fname += testFilename;
+      readit = this->Makefile->ReadDependentFile(fname.c_str());
     }
-    fname += "/";
-    fname += testFilename;
-    bool readit = this->Makefile->ReadDependentFile(fname.c_str());
-    cmSystemTools::ChangeDirectory(cwd);
     if (!readit) {
       std::string m = "Could not find include file: ";
       m += fname;
@@ -109,7 +112,6 @@ bool cmCTestSubdirCommand::InitialPass(std::vector<std::string> const& args,
       return false;
     }
   }
-  cmSystemTools::ChangeDirectory(cwd);
   return true;
 }
 
@@ -149,9 +151,7 @@ bool cmCTestAddSubdirectoryCommand::InitialPass(
     return false;
   }
 
-  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
-  cmSystemTools::ChangeDirectory(cwd);
-  std::string fname = cwd;
+  std::string fname = cmSystemTools::GetCurrentWorkingDirectory();
   fname += "/";
   fname += args[0];
 
@@ -159,23 +159,23 @@ bool cmCTestAddSubdirectoryCommand::InitialPass(
     // No subdirectory? So what...
     return true;
   }
-  cmSystemTools::ChangeDirectory(fname);
-  const char* testFilename;
-  if (cmSystemTools::FileExists("CTestTestfile.cmake")) {
-    // does the CTestTestfile.cmake exist ?
-    testFilename = "CTestTestfile.cmake";
-  } else if (cmSystemTools::FileExists("DartTestfile.txt")) {
-    // does the DartTestfile.txt exist ?
-    testFilename = "DartTestfile.txt";
-  } else {
-    // No CTestTestfile? Who cares...
-    cmSystemTools::ChangeDirectory(cwd);
-    return true;
+  bool readit = false;
+  {
+    const char* testFilename;
+    if (cmSystemTools::FileExists("CTestTestfile.cmake")) {
+      // does the CTestTestfile.cmake exist ?
+      testFilename = "CTestTestfile.cmake";
+    } else if (cmSystemTools::FileExists("DartTestfile.txt")) {
+      // does the DartTestfile.txt exist ?
+      testFilename = "DartTestfile.txt";
+    } else {
+      // No CTestTestfile? Who cares...
+      return true;
+    }
+    fname += "/";
+    fname += testFilename;
+    readit = this->Makefile->ReadDependentFile(fname.c_str());
   }
-  fname += "/";
-  fname += testFilename;
-  bool readit = this->Makefile->ReadDependentFile(fname.c_str());
-  cmSystemTools::ChangeDirectory(cwd);
   if (!readit) {
     std::string m = "Could not find include file: ";
     m += fname;
diff --git a/Source/cmCTest.cxx b/Source/cmCTest.cxx
index 559275e0ea56e51c5f8917771d4113b94c6e44fe..e6e50e9e709b96c80a81063112a43d8678d19575 100644
--- a/Source/cmCTest.cxx
+++ b/Source/cmCTest.cxx
@@ -1122,7 +1122,6 @@ int cmCTest::RunTest(std::vector<const char*> argv, std::string* output,
     if (log) {
       *log << "* Run internal CTest" << std::endl;
     }
-    std::string oldpath = cmSystemTools::GetCurrentWorkingDirectory();
 
     CM_AUTO_PTR<cmSystemTools::SaveRestoreEnvironment> saveEnv;
     if (modifyEnv) {
@@ -1137,7 +1136,6 @@ int cmCTest::RunTest(std::vector<const char*> argv, std::string* output,
     if (log && output) {
       *log << *output;
     }
-    cmSystemTools::ChangeDirectory(oldpath);
     if (output) {
       cmCTestLog(this, HANDLER_VERBOSE_OUTPUT,
                  "Internal cmCTest object used to run test." << std::endl
diff --git a/Source/cmDepends.cxx b/Source/cmDepends.cxx
index c189419de892f783c1915175a0f36cad3bc84c32..b8c76b92dd80efbd48e2d3a62970370d21fd42c0 100644
--- a/Source/cmDepends.cxx
+++ b/Source/cmDepends.cxx
@@ -7,6 +7,7 @@
 #include "cmLocalGenerator.h"
 #include "cmMakefile.h"
 #include "cmSystemTools.h"
+#include "cmWorkingDirectory.h"
 
 #include <cmsys/FStream.hxx>
 #include <sstream>
@@ -75,13 +76,7 @@ bool cmDepends::Check(const char* makeFile, const char* internalFile,
                       std::map<std::string, DependencyVector>& validDeps)
 {
   // Dependency checks must be done in proper working directory.
-  std::string oldcwd = ".";
-  if (this->CompileDirectory != ".") {
-    // Get the CWD but do not call CollapseFullPath because
-    // we only need it to cd back, and the form does not matter
-    oldcwd = cmSystemTools::GetCurrentWorkingDirectory(false);
-    cmSystemTools::ChangeDirectory(this->CompileDirectory);
-  }
+  cmWorkingDirectory workdir(this->CompileDirectory);
 
   // Check whether dependencies must be regenerated.
   bool okay = true;
@@ -93,11 +88,6 @@ bool cmDepends::Check(const char* makeFile, const char* internalFile,
     okay = false;
   }
 
-  // Restore working directory.
-  if (oldcwd != ".") {
-    cmSystemTools::ChangeDirectory(oldcwd);
-  }
-
   return okay;
 }
 
diff --git a/Source/cmGlobalGenerator.cxx b/Source/cmGlobalGenerator.cxx
index b6b7d9e84ab7eb7c244f265b9685ca765eea3841..1f5e6246df42f8c8a15765fe14fd16d95a61e9ce 100644
--- a/Source/cmGlobalGenerator.cxx
+++ b/Source/cmGlobalGenerator.cxx
@@ -42,6 +42,7 @@
 #include "cmStateDirectory.h"
 #include "cmStateTypes.h"
 #include "cmVersion.h"
+#include "cmWorkingDirectory.h"
 #include "cmake.h"
 
 #if defined(CMAKE_BUILD_WITH_CMAKE)
@@ -1763,8 +1764,7 @@ int cmGlobalGenerator::Build(const std::string& /*unused*/,
   /**
    * Run an executable command and put the stdout in output.
    */
-  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
-  cmSystemTools::ChangeDirectory(bindir);
+  cmWorkingDirectory workdir(bindir);
   output += "Change Dir: ";
   output += bindir;
   output += "\n";
@@ -1804,8 +1804,6 @@ int cmGlobalGenerator::Build(const std::string& /*unused*/,
       output += *outputPtr;
       output += "\nGenerator: execution of make clean failed.\n";
 
-      // return to the original directory
-      cmSystemTools::ChangeDirectory(cwd);
       return 1;
     }
     output += *outputPtr;
@@ -1828,8 +1826,6 @@ int cmGlobalGenerator::Build(const std::string& /*unused*/,
     output += "\nGenerator: execution of make failed. Make command was: " +
       makeCommandStr + "\n";
 
-    // return to the original directory
-    cmSystemTools::ChangeDirectory(cwd);
     return 1;
   }
   output += *outputPtr;
@@ -1842,7 +1838,6 @@ int cmGlobalGenerator::Build(const std::string& /*unused*/,
     retVal = 1;
   }
 
-  cmSystemTools::ChangeDirectory(cwd);
   return retVal;
 }
 
diff --git a/Source/cmMakefile.cxx b/Source/cmMakefile.cxx
index c75d101ddc42843b9a35228ba918656ecece3ee8..204fd8f461fa1f2a2f9344104acf8ae9cfd66115 100644
--- a/Source/cmMakefile.cxx
+++ b/Source/cmMakefile.cxx
@@ -36,6 +36,7 @@
 #include "cmTest.h"
 #include "cmTestGenerator.h" // IWYU pragma: keep
 #include "cmVersion.h"
+#include "cmWorkingDirectory.h"
 #include "cm_auto_ptr.hxx"
 #include "cmake.h"
 
@@ -3147,8 +3148,7 @@ int cmMakefile::TryCompile(const std::string& srcdir,
 
   // change to the tests directory and run cmake
   // use the cmake object instead of calling cmake
-  std::string cwd = cmSystemTools::GetCurrentWorkingDirectory();
-  cmSystemTools::ChangeDirectory(bindir);
+  cmWorkingDirectory workdir(bindir);
 
   // make sure the same generator is used
   // use this program as the cmake to be run, it should not
@@ -3162,8 +3162,6 @@ int cmMakefile::TryCompile(const std::string& srcdir,
                          this->GetGlobalGenerator()->GetName() +
                          "' could not be created.");
     cmSystemTools::SetFatalErrorOccured();
-    // return to the original directory
-    cmSystemTools::ChangeDirectory(cwd);
     this->IsSourceFileTryCompile = false;
     return 1;
   }
@@ -3227,8 +3225,6 @@ int cmMakefile::TryCompile(const std::string& srcdir,
     this->IssueMessage(cmake::FATAL_ERROR,
                        "Failed to configure test project build system.");
     cmSystemTools::SetFatalErrorOccured();
-    // return to the original directory
-    cmSystemTools::ChangeDirectory(cwd);
     this->IsSourceFileTryCompile = false;
     return 1;
   }
@@ -3237,8 +3233,6 @@ int cmMakefile::TryCompile(const std::string& srcdir,
     this->IssueMessage(cmake::FATAL_ERROR,
                        "Failed to generate test project build system.");
     cmSystemTools::SetFatalErrorOccured();
-    // return to the original directory
-    cmSystemTools::ChangeDirectory(cwd);
     this->IsSourceFileTryCompile = false;
     return 1;
   }
@@ -3247,7 +3241,6 @@ int cmMakefile::TryCompile(const std::string& srcdir,
   int ret = this->GetGlobalGenerator()->TryCompile(
     srcdir, bindir, projectName, targetName, fast, output, this);
 
-  cmSystemTools::ChangeDirectory(cwd);
   this->IsSourceFileTryCompile = false;
   return ret;
 }
diff --git a/Source/cmWorkingDirectory.cxx b/Source/cmWorkingDirectory.cxx
new file mode 100644
index 0000000000000000000000000000000000000000..99c9ba86a44611aae49cad2ccc69dc1c9fc3c60c
--- /dev/null
+++ b/Source/cmWorkingDirectory.cxx
@@ -0,0 +1,24 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#include "cmWorkingDirectory.h"
+
+#include "cmSystemTools.h"
+
+cmWorkingDirectory::cmWorkingDirectory(std::string const& newdir)
+{
+  this->OldDir = cmSystemTools::GetCurrentWorkingDirectory();
+  cmSystemTools::ChangeDirectory(newdir);
+}
+
+cmWorkingDirectory::~cmWorkingDirectory()
+{
+  this->Pop();
+}
+
+void cmWorkingDirectory::Pop()
+{
+  if (!this->OldDir.empty()) {
+    cmSystemTools::ChangeDirectory(this->OldDir);
+    this->OldDir.clear();
+  }
+}
diff --git a/Source/cmWorkingDirectory.h b/Source/cmWorkingDirectory.h
new file mode 100644
index 0000000000000000000000000000000000000000..af0fd447dd5d120aeb57fb3bcdd468ce474dd567
--- /dev/null
+++ b/Source/cmWorkingDirectory.h
@@ -0,0 +1,25 @@
+/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
+   file Copyright.txt or https://cmake.org/licensing for details.  */
+#ifndef cmWorkingDirectory_h
+#define cmWorkingDirectory_h
+
+#include <cmConfigure.h> // IWYU pragma: keep
+
+#include <string>
+
+/** \class cmWorkingDirectory
+ * \brief An RAII class to manipulate the working directory.
+ */
+class cmWorkingDirectory
+{
+public:
+  cmWorkingDirectory(std::string const& newdir);
+  ~cmWorkingDirectory();
+
+  void Pop();
+
+private:
+  std::string OldDir;
+};
+
+#endif
diff --git a/Source/cmake.cxx b/Source/cmake.cxx
index b2384cdca6856a64aaf29ba90004a032b22e0985..3af3be67b62e6de9ba4bb7f0b3314d18f4dc5d23 100644
--- a/Source/cmake.cxx
+++ b/Source/cmake.cxx
@@ -23,6 +23,7 @@
 #include "cmTargetLinkLibraryType.h"
 #include "cmUtils.hxx"
 #include "cmVersionConfig.h"
+#include "cmWorkingDirectory.h"
 #include "cm_auto_ptr.hxx"
 
 #if defined(CMAKE_BUILD_WITH_CMAKE)
@@ -2199,24 +2200,23 @@ int cmake::GetSystemInformation(std::vector<std::string>& args)
     resultFile += "/__cmake_systeminformation/results.txt";
   }
 
-  // now run cmake on the CMakeLists file
-  cmSystemTools::ChangeDirectory(destPath);
-  std::vector<std::string> args2;
-  args2.push_back(args[0]);
-  args2.push_back(destPath);
-  std::string resultArg = "-DRESULT_FILE=";
-  resultArg += resultFile;
-  args2.push_back(resultArg);
-  int res = this->Run(args2, false);
+  {
+    // now run cmake on the CMakeLists file
+    cmWorkingDirectory workdir(destPath);
+    std::vector<std::string> args2;
+    args2.push_back(args[0]);
+    args2.push_back(destPath);
+    std::string resultArg = "-DRESULT_FILE=";
+    resultArg += resultFile;
+    args2.push_back(resultArg);
+    int res = this->Run(args2, false);
 
-  if (res != 0) {
-    std::cerr << "Error: --system-information failed on internal CMake!\n";
-    return res;
+    if (res != 0) {
+      std::cerr << "Error: --system-information failed on internal CMake!\n";
+      return res;
+    }
   }
 
-  // change back to the original directory
-  cmSystemTools::ChangeDirectory(cwd);
-
   // echo results to stdout if needed
   if (writeToStdout) {
     FILE* fin = cmsys::SystemTools::Fopen(resultFile, "r");
diff --git a/bootstrap b/bootstrap
index 8063edb19bf7415ef15c4b42447d19f313bcfd62..4f5836537d8523e8fe35e5fb2384b6bc3f46c76a 100755
--- a/bootstrap
+++ b/bootstrap
@@ -404,6 +404,7 @@ CMAKE_CXX_SOURCES="\
   cmUnsetCommand \
   cmVersion \
   cmWhileCommand \
+  cmWorkingDirectory \
   cmake  \
   cmakemain \
   cmcmd  \