Repository: certbot/certbot

Issue:
Windows should be setting CLI --preconfigured-renewal
We're going to add some CLI messaging soon which varies by `--preconfigured-renewal`, which we added a while ago (#8241).

Just looking at `windows-installer/`, it wouldn't appear that the flag is being set. It should be, since the installer sets up a Scheduled Task. 

What would be a good way to set this?

1. Debian [ships its own `cli.ini`](https://salsa.debian.org/letsencrypt-team/certbot/certbot/-/blob/master/debian/cli.ini). 
    - The Windows installer could do that.
2. The Certbot snap injects the argument to the end of the argument list, in the wrapper.
    - I'm not sure we can do that in `run.bat` but there might be some Windows tricks to achieve it idk.



Discussion:
I've been trying to do this via creating or appending to `cli.ini`.

The second approach seems possible via `doskey`, but the problem is that Certbot may be invoked outside of the `run.bat` wrapper. It'd be preferable to have something reliable.

@adferrand could I please get your input on a couple of things:

1. On Windows, is `C:\` guaranteed to exist? I notice this is hardcoded rather than using `%SYSTEMDRIVE%`:

https://github.com/certbot/certbot/blob/2622a700e0a83e0de0994c970929b624b98dad40/certbot/certbot/compat/misc.py#L70-L71

2. Would it be appropriate to precreate `C:\Certbot` in NSIS if it does not already exist? Currently the directory is only created *after* the user runs Certbot (possibly with an entirely different `--config-dir`). I wonder if this will fail to meet Windows users' expectations about installers.

For 1.

You are right, even if the vast majority of the systems will have their system drive attached to C:, it is not guaranteed. The hardcoded value is not great. In general, I have a discussion on-going to choose a better default Certbot path on Windows, that would also consume the well-known environment variables for that matter. See https://github.com/certbot/certbot/issues/7883.

Without going that far and change the actual Certbot path, I think it would benefit to use `%SYSTEMDRIVE%` instead of the hardcoded `C:` value as soon as possible.

For 2.

It is arguable that the config dir would be created during the installation phase. On Linux it is created on demand. In general, `C:\Certbot` is _just_ a standard folder where Certbot could store a specific instance, and it happens to be the default one. So I do not think it is a fault on expectations to the installer, given that Certbot installation path is still created an in an expected place.
On your first post. Globally I also think that solution 2. is the best one, more portable and in sync with how it is done for the other deployed versions of Certbot.

However `run.bat` is not the good place to put the flag. In fact this script does not start Certbot itself, but a command line that ensures admin privileged are acquired, since Certbot needs them, and display an introduction message. The real Certbot executable is located at `bin\certbot.exe`.

This executable is in fact the concatenation of a binary + a python script. The binary gives the overall native executability of the exe, and is responsible to read the shebang of the python script located at the end of the file, find the appropriate Python interpreter in the system, invoke it and pass the Python script as input.

Yes I know this is weird, but it is the standard solution provided by [distlib](https://distlib.readthedocs.io/en/stable/) to run self-executable Python script despite Windows does not have native support for shebangs ...).

The python script is responsible for preparing Certbot environment (in particular its `PYTHONPATH` to load the packages from the installation path) and launching `certbot.main.main()` to actually run Certbot. This script is the central entrypoint to run the installed Certbot instance on Windows, and I think this is this script we need to modify to pass the `--preconfigured-renewal` flag.

This script is generated by the [pynsist](https://github.com/takluyver/pynsist) project, which is responsible here to build the installer. See https://github.com/takluyver/pynsist/blob/master/nsist/commands.py.

Hopefully `pynsist` as an `extra_preamble` parameter in its configuration file (https://pynsist.readthedocs.io/en/stable/cfgfile.html) that can be used to inject extra Python commands before `certbot.main.main()` is invoked. We could use this extra preamble to add the flag to `sys.argv` before it is read by the Certbot `main` function. To do that, we would need to edit `windows-installer\windows_installer\construct.py` since this script is responsible to set the pynsist configuration file and launch the build.

If @bmw and you are fine with this plan, I can also write the PR if you want.
> This executable is in fact the concatenation of a binary + a python script

Great! I had no idea it worked like this. This `extra_preamble` approach gets a big :+1: from me; I really didn't like the downsides of the other ways. 

A PR would be great, but I will have a go maybe next week, if you are busy.

Thank you for looking into this!!!
> > This executable is in fact the concatenation of a binary + a python script
> 
> Great! I had no idea it worked like this.

Yes if you are curious, open the exe file in a text editor. You will see at the beginning a big bunch of binary code, and suddenly at the end, a Python script in plain text. It works also for any exe file in the `Scripts` folder of a virtual environment, since `setuptools` also uses `distlib` to generate the wrappers for the console scripts defined in `setup.py`/`pyproject.toml`.

> If @bmw and you are fine with this plan, I can also write the PR if you want.

Assuming neither of you have significant concerns about this plan that you didn't mention here, using `extra_preamble` sounds like a great approach to me!

Resolving patch:
diff --git a/windows-installer/assets/preamble.py b/windows-installer/assets/preamble.py
new file mode 100644
--- /dev/null
+++ b/windows-installer/assets/preamble.py
@@ -0,0 +1,12 @@
+"""Pynsist extra_preamble for the Certbot entry point.
+
+This preamble ensures that Certbot on Windows always runs with the --preconfigured-renewal
+flag set. Since Pynsist creates a Scheduled Task for renewal, we want this flag to be set
+so that we can provide the right automated renewal advice to Certbot on Windows users.
+
+"""
+
+
+import sys
+
+sys.argv += ["--preconfigured-renewal"]
diff --git a/windows-installer/windows_installer/construct.py b/windows-installer/windows_installer/construct.py
--- a/windows-installer/windows_installer/construct.py
+++ b/windows-installer/windows_installer/construct.py
@@ -82,6 +82,7 @@ def _copy_assets(build_path, repo_path):
     shutil.copy(os.path.join(repo_path, 'windows-installer', 'assets', 'template.nsi'), build_path)
     shutil.copy(os.path.join(repo_path, 'windows-installer', 'assets', 'renew-up.ps1'), build_path)
     shutil.copy(os.path.join(repo_path, 'windows-installer', 'assets', 'renew-down.ps1'), build_path)
+    shutil.copy(os.path.join(repo_path, 'windows-installer', 'assets', 'preamble.py'), build_path)
 
 
 def _generate_pynsist_config(repo_path, build_path):
@@ -121,6 +122,7 @@ def _generate_pynsist_config(repo_path, build_path):
 
 [Command certbot]
 entry_point=certbot.main:main
+extra_preamble=preamble.py
 '''.format(certbot_version=certbot_version,
            installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32',
            python_bitness=PYTHON_BITNESS,


Test patch: