Repository: pypa/pip

Issue:
pip install --upgrade pip on v22.0 broken on windows
### Description

`pip install --upgrade pip` is failing on windows GitHub actions runners with v22.0

See https://github.com/pyvista/pyvista/pull/2113

See example workflow shared below

See https://github.com/pyvista/pyvista/runs/4998090770?check_suite_focus=true#step:4:1

### Expected behavior

Pip should be able to upgrade itself on windows for Python 3.7 and greater

### pip version

22.0

### Python version

3.7+

### OS

Windows

### How to Reproduce

Use this GitHub Actions workflow:

```yml
name: Unit Testing

on:

jobs:
  windows:
    name: Windows Unit Testing
    runs-on: windows-2019
    strategy:
      fail-fast: false
      matrix:
        python-version: [3.6, 3.7, 3.8, 3.9]
    steps:
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: ${{ matrix.python-version }}

      - name: Upgrade pip
        run: pip install --upgrade pip
```

### Output

```sh-session
pip install --upgrade pip
  pip install --upgrade pip
  shell: C:\Program Files\PowerShell\7\pwsh.EXE -command ". '{0}'"
  env:
    ALLOW_PLOTTING: true
    SHELLOPTS: errexit:pipefail
    CI_WINDOWS: true
    pythonLocation: C:\hostedtoolcache\windows\Python\3.7.9\x64
Requirement already satisfied: pip in c:\hostedtoolcache\windows\python\3.7.9\x64\lib\site-packages (21.3.1)
Collecting pip
  Downloading pip-22.0-py3-none-any.whl (2.1 MB)
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 21.3.1
    Uninstalling pip-21.3.1:
      Successfully uninstalled pip-21.3.1
ERROR: Could not install packages due to an OSError: [WinError 5] Access is denied: 'C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\pip-uninstall-2ea7t7u5\\pip.exe'
Consider using the `--user` option or check the permissions.

Error: Process completed with exit code 1.
```


### Code of Conduct

- [X] I agree to follow the [PSF Code of Conduct](https://www.python.org/psf/conduct/).


Discussion:
You'll want to run `python -m pip ...` for that. I'm not sure why pip's detection logic is broken on Windows, since we'd added a dedicated warning to detect this situation.
Thanks for the tip!!

Also, I want to make sure it's clear in my issue that this broke on v22 but works fine on v21


Noticed something very similar today on MinGW x64.  This had also worked until upgrading from v21 to v22.
```
$ pip install --upgrade pip
Requirement already satisfied: pip in c:/msys64/mingw64/lib/python3.9/site-packages (21.3.1)
Collecting pip
  Downloading pip-22.0.2-py3-none-any.whl (2.1 MB)
     |################################| 2.1 MB 3.3 MB/s
Installing collected packages: pip
  Attempting uninstall: pip
    Found existing installation: pip 21.3.1
    Uninstalling pip-21.3.1:
      Successfully uninstalled pip-21.3.1
ERROR: Could not install packages due to an OSError: [WinError 5] Access is denied: 'C:/Users/test/AppData/Local/Temp/pip-uninstall-bau5mi39\\pip.exe'
Consider using the `--user` option or check the permissions.
```

seems the path `C:/Users/test/AppData/Local/Temp/pip-uninstall-bau5mi39\\pip.exe` is mangled, mixing Windows and POSIX path separators

Resolving patch:
diff --git a/src/pip/_internal/utils/misc.py b/src/pip/_internal/utils/misc.py
--- a/src/pip/_internal/utils/misc.py
+++ b/src/pip/_internal/utils/misc.py
@@ -556,9 +556,9 @@ def protect_pip_from_modification_on_windows(modifying_pip: bool) -> None:
         python -m pip ...
     """
     pip_names = [
-        "pip.exe",
-        "pip{}.exe".format(sys.version_info[0]),
-        "pip{}.{}.exe".format(*sys.version_info[:2]),
+        "pip",
+        f"pip{sys.version_info.major}",
+        f"pip{sys.version_info.major}.{sys.version_info.minor}",
     ]
 
     # See https://github.com/pypa/pip/issues/1299 for more discussion


Test patch:
diff --git a/tests/functional/test_install_upgrade.py b/tests/functional/test_install_upgrade.py
--- a/tests/functional/test_install_upgrade.py
+++ b/tests/functional/test_install_upgrade.py
@@ -1,5 +1,6 @@
 import itertools
 import os
+import sys
 import textwrap
 
 import pytest
@@ -411,3 +412,14 @@ def test_install_find_existing_package_canonicalize(
     )
     satisfied_message = f"Requirement already satisfied: {req2}"
     assert satisfied_message in result.stdout, str(result)
+
+
+@pytest.mark.network
+@pytest.mark.skipif(sys.platform != "win32", reason="Windows-only test")
+def test_modifying_pip_presents_error(script: PipTestEnvironment) -> None:
+    result = script.pip(
+        "install", "pip", "--force-reinstall", use_module=False, expect_error=True
+    )
+
+    assert "python.exe" in result.stderr or "python.EXE" in result.stderr, str(result)
+    assert " -m " in result.stderr, str(result)
diff --git a/tests/lib/__init__.py b/tests/lib/__init__.py
--- a/tests/lib/__init__.py
+++ b/tests/lib/__init__.py
@@ -666,7 +666,7 @@ def run(
         if expect_error and not allow_error:
             if result.returncode == 0:
                 __tracebackhide__ = True
-                raise AssertionError("Script passed unexpectedly.")
+                raise AssertionError(f"Script passed unexpectedly:\n{result}")
 
         _check_stderr(
             result.stderr,