terPythonInterpreter _interpreter;
        private readonly string _expectedEnvFolderPath;
        private string _envFolderPath;
        private string _envInterpreterPath;
        private readonly Redirector _redirector;


        internal string DefaultBasePath { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);

        public CookiecutterClient(IServiceProvider provider, CookiecutterPythonInterpreter interpreter, Redirector redirector) {
            _provider = provider;
            _interpreter = interpreter;
            var localAppDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);

            _redirector = redirector;

            // This is where the env *should* be created, but not necessarily where it will live on disk.
            // See the GetRealPath function for more details
            _expectedEnvFolderPath = Path.Combine(localAppDataFolderPath, "Microsoft", "CookiecutterTools", "env");
        }

        public async Task<bool> IsCookiecutterInstalled() {

            // update for redirection
            await UpdateEnvPathForRedirection(_expectedEnvFolderPath);

            try {
                var result = await RunCheckScript(_envInterpreterPath);
                return result.StandardOutputLines.FirstOrDefault() == "ok";
            } catch (Exception e) {
                Trace.WriteLine(e.Message);
                return false;
            }
        }

        public async Task CreateCookiecutterEnv() {

            // Create a virtual environment using the global interpreter
            try {
                await CreateVenvWithoutPipThenInstallPip();
            } catch (ProcessException ex) {
                // critical exception is needed for EnsureCookiecutterIsInstalledAsync() to fail properly
                var errMsg = Strings.InstallingCookiecutterCreateEnvFailed.FormatUI(_expectedEnvFolderPath);
                throw new CriticalException(errMsg, ex);
            }
        }

        // Checks if the specified path has been redirected by python
        // and updates _envFolderPath and _envInterpreterPath if they are not already set.
        private async Task UpdateEnvPathForRedirection(string path, bool overwrite = false) {
            
            // if the real path has already been detected, and we're not overwriting it, we're done
            if (!string.IsNullOrEmpty(_envFolderPath) &&!overwrite) {
                return;
            }

            var realPath = await GetRealPath(path);
            if (!string.IsNullOrEmpty(realPath)) {
                if (Directory.Exists(realPath) || File.Exists(realPath)) {
                    _envFolderPath = realPath;
                    _envInterpreterPath = GetInterpreterPathFromEnvFolderPath(_envFolderPath);
                }
            }
        }

        private string GetInterpreterPathFromEnvFolderPath(string envFolderPath) {
            return Path.Combine(envFolderPath, "scripts", "python.exe");
        }

        // If the global interpreter comes from the Microsoft Store, and you try to create a venv
        // under %localappdata%, the venv will actually be created under the python install localcache,
        // and a redirect will be put in place that is only understood by the python.exe used to create
        // the venv.
        // Therefore, we have to use the python intepreter to check the real path of the venv,
        // in case it's been redirected.
        private async Task<string> GetRealPath(string path) {

            // if we can see the path, it hasn't been redirected, so no need to call into python
            if (Directory.Exists(path) || File.Exists(path)) {
                return path;
            }

            // get the redirected path from python
            _redirector.WriteLine(Strings.LookingForRedirectedEnv.FormatUI(path));
            var command = $"import os; print(os.path.realpath(r\"{ path }\"))";
            var output = ProcessOutput.RunHiddenAndCapture (
                _interpreter.InterpreterExecutablePath,
                new[] { "-c", command }
            );

            try {
                var result = await WaitForOutput(_interpreter.InterpreterExecutablePath, output);
                var redirectedPath = result.StandardOutputLines.FirstOrDefault();
                if (!string.IsNullOrEmpty(redirectedPath) && Directory.Exists(redirectedPath)) {
                    _redirector.WriteLine(Strings.LookingForRedirectedEnvFound.FormatUI(redirectedPath));
                }
                return redirectedPath;
            } catch (ProcessException p){
                _redirector.WriteLine(Strings.LookingForRedirectedEnvFailed.FormatUI(path));
                foreach (var line in p.Result.StandardErrorLines) {
                    _redirector.WriteLine(line);
                }
                throw;
            }
        }

        private async Task CreateVenvWithoutPipThenInstallPip() {

            // check for redirection
            await UpdateEnvPathForRedirection(_expectedEnvFolderPath);

            // The venv is not guaranteed to be created where expected, see GetRealPath() for more information.

            // Also, Python has a bug (https://bugs.python.org/issue45337) where it doesn't
            // keep track of the real location of the redirected venv when creating a venv with pip installed.
            // In this case, the call to python.exe will have an exit code of 106.

            // Therefore, the workaround is the following:
            // 1. Create the venv WITHOUT PIP every time
            // 2. Run python and check os.path.realpath against the expected venv path
            // 3. If the real path comes back different, that's the real venv path.
            // 5. Install pip using python.exe and the real venv path.

            RemoveExistingVenv(_envFolderPath);

            // create the venv without pip installed
            _redirector.WriteLine(Strings.InstallingCookiecutterCreateEnvWithoutPip.FormatUI(_expectedEnvFolderPath));
            var output = ProcessOutput.Run(
                _interpreter.InterpreterExecutablePath,
                new[] { "-m", "venv", _expectedEnvFolderPath, "--without-pip" },
                null,
                null,
                false,
                _redirector
            );
            await WaitForOutput(_interpreter.InterpreterExecutablePath, output);

            // If we get here, the environment was created successfully.
            // Check for redirection again, overwriting the existing value if there is
            await UpdateEnvPathForRedirection(_expectedEnvFolderPath, true);

            // install pip in the new environment, wherever it is
            _redirector.WriteLine(Strings.InstallingCookiecutterInstallPip.FormatUI(_envFolderPath));
            var pipScriptPath = PythonToolsInstallPath.GetFile("pip_downloader.py");
            output = ProcessOutput.Run(
                _envInterpreterPath,
                new[] { pipScriptPath },
                _interpreter.PrefixPath,
                null,
                false,
                _redirector
            );
            await WaitForOutput(_interpreter.InterpreterExecutablePath, output);
        }

        private void RemoveExistingVenv(string envPath) {
            if (Directory.Exists(envPath)) {
                _redirector.WriteLine(Strings.InstallingCookiecutterDeleteEnv.FormatUI(envPath));
                try {
                    Directory.Delete(envPath, true);
                } catch (DirectoryNotFoundException) {
                }
            }
        }

        public async Task InstallPackage() {

            // check for redirection
            await UpdateEnvPathForRedirection(_expectedEnvFolderPath);

            _redirector.WriteLine(Strings.InstallingCookiecutterInstallPackages.FormatUI(_envFolderPath));
            var output = ProcessOutput.Run(
                _envInterpreterPath,
                new[] { "-m", "pip", "install", "cookiecutter<1.5" },
                null,
                null,
                false,
                _redirector
            );

            await WaitForOutput(_envInterpreterPath, output);
        }

        public async Task<TemplateContext> LoadUnrenderedContextAsync(string localTemplateFolder, string userConfigFilePath) {
            if (localTemplateFolder == null) {
                throw new ArgumentNullException(nameof(localTemplateFolder));
            }

            // check for redirection
            await UpdateEnvPathForRedirection(_expectedEnvFolderPath);

            var unrenderedContext = new TemplateContext();

            var result = await RunGenerateContextScript(_redirector, _envInterpreterPath, localTemplateFolder, userConfigFilePath);
            var contextJson = string.Join(Environment.NewLine, result.StandardOutputLines