diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e25a544bd07d..cda28b148f19 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1733,6 +1733,24 @@ jobs:
       - name: "Validate global Python install"
         run: py -3.13 ./scripts/check_system_python.py --uv ./uv.exe
 
+  # Test our PEP 514 integration that installs Python into the Windows registry.
+  system-test-windows-registry:
+    timeout-minutes: 10
+    needs: build-binary-windows-x86_64
+    name: "check system | windows registry"
+    runs-on: windows-latest
+    steps:
+      - uses: actions/checkout@v4
+
+      - name: "Download binary"
+        uses: actions/download-artifact@v4
+        with:
+          name: uv-windows-x86_64-${{ github.sha }}
+
+      # NB: Run this last, we are modifying the registry
+      - name: "Test PEP 514 registration"
+        run: python ./scripts/check_registry.py --uv ./uv.exe
+
   system-test-choco:
     timeout-minutes: 10
     needs: build-binary-windows-x86_64
diff --git a/crates/uv-python/Cargo.toml b/crates/uv-python/Cargo.toml
index fcea7d5ecb60..7b9b98d6b6fb 100644
--- a/crates/uv-python/Cargo.toml
+++ b/crates/uv-python/Cargo.toml
@@ -64,9 +64,9 @@ which = { workspace = true }
 procfs = { workspace = true }
 
 [target.'cfg(target_os = "windows")'.dependencies]
-windows-sys = { workspace = true }
 windows-registry = { workspace = true }
 windows-result = { workspace = true }
+windows-sys = { workspace = true }
 
 [dev-dependencies]
 anyhow = { version = "1.0.89" }
diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs
index b4c35095c9bd..262e00311cce 100644
--- a/crates/uv-python/src/discovery.rs
+++ b/crates/uv-python/src/discovery.rs
@@ -24,13 +24,13 @@ use crate::interpreter::{StatusCodeError, UnexpectedResponseError};
 use crate::managed::ManagedPythonInstallations;
 #[cfg(windows)]
 use crate::microsoft_store::find_microsoft_store_pythons;
-#[cfg(windows)]
-use crate::py_launcher::{registry_pythons, WindowsPython};
 use crate::virtualenv::Error as VirtualEnvError;
 use crate::virtualenv::{
     conda_environment_from_env, virtualenv_from_env, virtualenv_from_working_dir,
     virtualenv_python_executable, CondaEnvironmentKind,
 };
+#[cfg(windows)]
+use crate::windows_registry::{registry_pythons, WindowsPython};
 use crate::{Interpreter, PythonVersion};
 
 /// A request to find a Python installation.
@@ -324,7 +324,7 @@ fn python_executables_from_installed<'a>(
                         }
                     })
                     .inspect(|installation| debug!("Found managed installation `{installation}`"))
-                    .map(|installation| (PythonSource::Managed, installation.executable())))
+                    .map(|installation| (PythonSource::Managed, installation.executable(false))))
             })
     })
     .flatten_ok();
diff --git a/crates/uv-python/src/downloads.rs b/crates/uv-python/src/downloads.rs
index b607149f66cc..54b20d6c8790 100644
--- a/crates/uv-python/src/downloads.rs
+++ b/crates/uv-python/src/downloads.rs
@@ -453,7 +453,7 @@ impl ManagedPythonDownload {
             .filter(|download| download.key.libc != Libc::Some(target_lexicon::Environment::Musl))
     }
 
-    pub fn url(&self) -> &str {
+    pub fn url(&self) -> &'static str {
         self.url
     }
 
@@ -465,7 +465,7 @@ impl ManagedPythonDownload {
         self.key.os()
     }
 
-    pub fn sha256(&self) -> Option<&str> {
+    pub fn sha256(&self) -> Option<&'static str> {
         self.sha256
     }
 
diff --git a/crates/uv-python/src/installation.rs b/crates/uv-python/src/installation.rs
index 12a32b0c6c09..4e9f8eac3ea3 100644
--- a/crates/uv-python/src/installation.rs
+++ b/crates/uv-python/src/installation.rs
@@ -161,7 +161,7 @@ impl PythonInstallation {
             DownloadResult::Fetched(path) => path,
         };
 
-        let installed = ManagedPythonInstallation::new(path)?;
+        let installed = ManagedPythonInstallation::new(path, download);
         installed.ensure_externally_managed()?;
         installed.ensure_sysconfig_patched()?;
         installed.ensure_canonical_executables()?;
@@ -171,7 +171,7 @@ impl PythonInstallation {
 
         Ok(Self {
             source: PythonSource::Managed,
-            interpreter: Interpreter::query(installed.executable(), cache)?,
+            interpreter: Interpreter::query(installed.executable(false), cache)?,
         })
     }
 
@@ -282,7 +282,7 @@ impl PythonInstallationKey {
         }
     }
 
-    pub fn new_from_version(
+    fn new_from_version(
         implementation: LenientImplementationName,
         version: &PythonVersion,
         os: Os,
@@ -320,6 +320,11 @@ impl PythonInstallationKey {
         .expect("Python installation keys must have valid Python versions")
     }
 
+    /// The version in `x.y.z` format.
+    pub fn sys_version(&self) -> String {
+        format!("{}.{}.{}", self.major, self.minor, self.patch)
+    }
+
     pub fn arch(&self) -> &Arch {
         &self.arch
     }
diff --git a/crates/uv-python/src/lib.rs b/crates/uv-python/src/lib.rs
index 114415043371..48f661ab196f 100644
--- a/crates/uv-python/src/lib.rs
+++ b/crates/uv-python/src/lib.rs
@@ -37,13 +37,18 @@ mod microsoft_store;
 pub mod platform;
 mod pointer_size;
 mod prefix;
-#[cfg(windows)]
-mod py_launcher;
 mod python_version;
 mod sysconfig;
 mod target;
 mod version_files;
 mod virtualenv;
+#[cfg(windows)]
+pub mod windows_registry;
+
+#[cfg(windows)]
+pub(crate) const COMPANY_KEY: &str = "Astral";
+#[cfg(windows)]
+pub(crate) const COMPANY_DISPLAY_NAME: &str = "Astral Software Inc.";
 
 #[cfg(not(test))]
 pub(crate) fn current_dir() -> Result<std::path::PathBuf, std::io::Error> {
diff --git a/crates/uv-python/src/macos_dylib.rs b/crates/uv-python/src/macos_dylib.rs
index 7294497b3c9c..a77284da0c1a 100644
--- a/crates/uv-python/src/macos_dylib.rs
+++ b/crates/uv-python/src/macos_dylib.rs
@@ -56,7 +56,7 @@ impl Error {
         };
         warn_user!(
             "Failed to patch the install name of the dynamic library for {}. This may cause issues when building Python native extensions.{}",
-            installation.executable().simplified_display(),
+            installation.executable(false).simplified_display(),
             error
         );
     }
diff --git a/crates/uv-python/src/managed.rs b/crates/uv-python/src/managed.rs
index 1efafa64850e..885f92cb1401 100644
--- a/crates/uv-python/src/managed.rs
+++ b/crates/uv-python/src/managed.rs
@@ -16,7 +16,7 @@ use uv_state::{StateBucket, StateStore};
 use uv_static::EnvVars;
 use uv_trampoline_builder::{windows_python_launcher, Launcher};
 
-use crate::downloads::Error as DownloadError;
+use crate::downloads::{Error as DownloadError, ManagedPythonDownload};
 use crate::implementation::{
     Error as ImplementationError, ImplementationName, LenientImplementationName,
 };
@@ -229,7 +229,7 @@ impl ManagedPythonInstallations {
                     .unwrap_or(true)
             })
             .filter_map(|path| {
-                ManagedPythonInstallation::new(path)
+                ManagedPythonInstallation::from_path(path)
                     .inspect_err(|err| {
                         warn!("Ignoring malformed managed Python entry:\n    {err}");
                     })
@@ -294,10 +294,27 @@ pub struct ManagedPythonInstallation {
     path: PathBuf,
     /// An install key for the Python version.
     key: PythonInstallationKey,
+    /// The URL with the Python archive.
+    ///
+    /// Empty when self was constructed from a path.
+    url: Option<&'static str>,
+    /// The SHA256 of the Python archive at the URL.
+    ///
+    /// Empty when self was constructed from a path.
+    sha256: Option<&'static str>,
 }
 
 impl ManagedPythonInstallation {
-    pub fn new(path: PathBuf) -> Result<Self, Error> {
+    pub fn new(path: PathBuf, download: &ManagedPythonDownload) -> Self {
+        Self {
+            path,
+            key: download.key().clone(),
+            url: Some(download.url()),
+            sha256: download.sha256(),
+        }
+    }
+
+    pub(crate) fn from_path(path: PathBuf) -> Result<Self, Error> {
         let key = PythonInstallationKey::from_str(
             path.file_name()
                 .ok_or(Error::NameError("name is empty".to_string()))?
@@ -307,7 +324,12 @@ impl ManagedPythonInstallation {
 
         let path = std::path::absolute(&path).map_err(|err| Error::AbsolutePath(path, err))?;
 
-        Ok(Self { path, key })
+        Ok(Self {
+            path,
+            key,
+            url: None,
+            sha256: None,
+        })
     }
 
     /// The path to this managed installation's Python executable.
@@ -315,7 +337,10 @@ impl ManagedPythonInstallation {
     /// If the installation has multiple execututables i.e., `python`, `python3`, etc., this will
     /// return the _canonical_ executable name which the other names link to. On Unix, this is
     /// `python{major}.{minor}{variant}` and on Windows, this is `python{exe}`.
-    pub fn executable(&self) -> PathBuf {
+    ///
+    /// If windowed is true, `pythonw.exe` is selected over `python.exe` on windows, with no changes
+    /// on non-windows.
+    pub fn executable(&self, windowed: bool) -> PathBuf {
         let implementation = match self.implementation() {
             ImplementationName::CPython => "python",
             ImplementationName::PyPy => "pypy",
@@ -342,6 +367,9 @@ impl ManagedPythonInstallation {
         // On Windows, the executable is just `python.exe` even for alternative variants
         let variant = if cfg!(unix) {
             self.key.variant.suffix()
+        } else if cfg!(windows) && windowed {
+            // Use windowed Python that doesn't open a terminal.
+            "w"
         } else {
             ""
         };
@@ -412,11 +440,11 @@ impl ManagedPythonInstallation {
 
     pub fn satisfies(&self, request: &PythonRequest) -> bool {
         match request {
-            PythonRequest::File(path) => self.executable() == *path,
+            PythonRequest::File(path) => self.executable(false) == *path,
             PythonRequest::Default | PythonRequest::Any => true,
             PythonRequest::Directory(path) => self.path() == *path,
             PythonRequest::ExecutableName(name) => self
-                .executable()
+                .executable(false)
                 .file_name()
                 .is_some_and(|filename| filename.to_string_lossy() == *name),
             PythonRequest::Implementation(implementation) => {
@@ -432,7 +460,7 @@ impl ManagedPythonInstallation {
 
     /// Ensure the environment contains the canonical Python executable names.
     pub fn ensure_canonical_executables(&self) -> Result<(), Error> {
-        let python = self.executable();
+        let python = self.executable(false);
 
         let canonical_names = &["python"];
 
@@ -539,7 +567,7 @@ impl ManagedPythonInstallation {
     ///
     /// If the file already exists at the target path, an error will be returned.
     pub fn create_bin_link(&self, target: &Path) -> Result<(), Error> {
-        let python = self.executable();
+        let python = self.executable(false);
 
         let bin = target.parent().ok_or(Error::NoExecutableDirectory)?;
         fs_err::create_dir_all(bin).map_err(|err| Error::ExecutableDirectory {
@@ -585,7 +613,7 @@ impl ManagedPythonInstallation {
     /// [`ManagedPythonInstallation::create_bin_link`].
     pub fn is_bin_link(&self, path: &Path) -> bool {
         if cfg!(unix) {
-            is_same_file(path, self.executable()).unwrap_or_default()
+            is_same_file(path, self.executable(false)).unwrap_or_default()
         } else if cfg!(windows) {
             let Some(launcher) = Launcher::try_from_path(path).unwrap_or_default() else {
                 return false;
@@ -593,7 +621,7 @@ impl ManagedPythonInstallation {
             if !matches!(launcher.kind, uv_trampoline_builder::LauncherKind::Python) {
                 return false;
             }
-            launcher.python_path == self.executable()
+            launcher.python_path == self.executable(false)
         } else {
             unreachable!("Only Windows and Unix are supported")
         }
@@ -627,6 +655,14 @@ impl ManagedPythonInstallation {
         // Do not upgrade if the patch versions are the same
         self.key.patch != other.key.patch
     }
+
+    pub fn url(&self) -> Option<&'static str> {
+        self.url
+    }
+
+    pub fn sha256(&self) -> Option<&'static str> {
+        self.sha256
+    }
 }
 
 /// Generate a platform portion of a key from the environment.
diff --git a/crates/uv-python/src/microsoft_store.rs b/crates/uv-python/src/microsoft_store.rs
index c0226733c03d..3709f54f17e2 100644
--- a/crates/uv-python/src/microsoft_store.rs
+++ b/crates/uv-python/src/microsoft_store.rs
@@ -3,7 +3,7 @@
 //!
 //! Effectively a port of <https://github.com/python/cpython/blob/58ce131037ecb34d506a613f21993cde2056f628/PC/launcher2.c#L1744>
 
-use crate::py_launcher::WindowsPython;
+use crate::windows_registry::WindowsPython;
 use crate::PythonVersion;
 use itertools::Either;
 use std::env;
diff --git a/crates/uv-python/src/platform.rs b/crates/uv-python/src/platform.rs
index ec6abfeaa72d..7ebc892c816e 100644
--- a/crates/uv-python/src/platform.rs
+++ b/crates/uv-python/src/platform.rs
@@ -102,6 +102,10 @@ impl Arch {
             variant: None,
         }
     }
+
+    pub fn family(&self) -> target_lexicon::Architecture {
+        self.family
+    }
 }
 
 impl Display for Libc {
diff --git a/crates/uv-python/src/py_launcher.rs b/crates/uv-python/src/py_launcher.rs
deleted file mode 100644
index af7482d19def..000000000000
--- a/crates/uv-python/src/py_launcher.rs
+++ /dev/null
@@ -1,98 +0,0 @@
-use crate::PythonVersion;
-use std::cmp::Ordering;
-use std::path::PathBuf;
-use std::str::FromStr;
-use tracing::debug;
-use windows_registry::{Key, CURRENT_USER, LOCAL_MACHINE};
-
-/// A Python interpreter found in the Windows registry through PEP 514 or from a known Microsoft
-/// Store path.
-///
-/// There are a lot more (optional) fields defined in PEP 514, but we only care about path and
-/// version here, for everything else we probe with a Python script.
-#[derive(Debug, Clone)]
-pub(crate) struct WindowsPython {
-    pub(crate) path: PathBuf,
-    pub(crate) version: Option<PythonVersion>,
-}
-
-/// Find all Pythons registered in the Windows registry following PEP 514.
-pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows_result::Error> {
-    let mut registry_pythons = Vec::new();
-    for root_key in [CURRENT_USER, LOCAL_MACHINE] {
-        let Ok(key_python) = root_key.open(r"Software\Python") else {
-            continue;
-        };
-        for company in key_python.keys()? {
-            // Reserved name according to the PEP.
-            if company == "PyLauncher" {
-                continue;
-            }
-            let Ok(company_key) = key_python.open(&company) else {
-                // Ignore invalid entries
-                continue;
-            };
-            for tag in company_key.keys()? {
-                let tag_key = company_key.open(&tag)?;
-
-                if let Some(registry_python) = read_registry_entry(&company, &tag, &tag_key) {
-                    registry_pythons.push(registry_python);
-                }
-            }
-        }
-    }
-
-    // The registry has no natural ordering, so we're processing the latest version first.
-    registry_pythons.sort_by(|a, b| {
-        match (&a.version, &b.version) {
-            // Place entries with a version before those without a version.
-            (Some(_), None) => Ordering::Greater,
-            (None, Some(_)) => Ordering::Less,
-            // We want the highest version on top, which is the inverse from the regular order. The
-            // path is an arbitrary but stable tie-breaker.
-            (Some(version_a), Some(version_b)) => {
-                version_a.cmp(version_b).reverse().then(a.path.cmp(&b.path))
-            }
-            // Sort the entries without a version arbitrarily, but stable (by path).
-            (None, None) => a.path.cmp(&b.path),
-        }
-    });
-
-    Ok(registry_pythons)
-}
-
-fn read_registry_entry(company: &str, tag: &str, tag_key: &Key) -> Option<WindowsPython> {
-    // `ExecutablePath` is mandatory for executable Pythons.
-    let Ok(executable_path) = tag_key
-        .open("InstallPath")
-        .and_then(|install_path| install_path.get_value("ExecutablePath"))
-        .and_then(String::try_from)
-    else {
-        debug!(
-            r"Python interpreter in the registry is not executable: `Software\Python\{}\{}",
-            company, tag
-        );
-        return None;
-    };
-
-    // `SysVersion` is optional.
-    let version = tag_key
-        .get_value("SysVersion")
-        .and_then(String::try_from)
-        .ok()
-        .and_then(|s| match PythonVersion::from_str(&s) {
-            Ok(version) => Some(version),
-            Err(err) => {
-                debug!(
-                    "Skipping Python interpreter ({executable_path}) \
-                    with invalid registry version {s}: {err}",
-                );
-                None
-            }
-        });
-
-    Some(WindowsPython {
-        path: PathBuf::from(executable_path),
-        version,
-    })
-}
diff --git a/crates/uv-python/src/windows_registry.rs b/crates/uv-python/src/windows_registry.rs
new file mode 100644
index 000000000000..67d1f784a953
--- /dev/null
+++ b/crates/uv-python/src/windows_registry.rs
@@ -0,0 +1,275 @@
+//! PEP 514 interactions with the Windows registry.
+
+use crate::managed::ManagedPythonInstallation;
+use crate::platform::Arch;
+use crate::{PythonInstallationKey, PythonVersion, COMPANY_DISPLAY_NAME, COMPANY_KEY};
+use std::cmp::Ordering;
+use std::collections::HashSet;
+use std::path::PathBuf;
+use std::str::FromStr;
+use target_lexicon::PointerWidth;
+use thiserror::Error;
+use tracing::debug;
+use uv_warnings::{warn_user, warn_user_once};
+use windows_registry::{Key, Value, CURRENT_USER, HSTRING, LOCAL_MACHINE};
+use windows_result::HRESULT;
+use windows_sys::Win32::Foundation::ERROR_FILE_NOT_FOUND;
+
+/// Code returned when the registry key doesn't exist.
+const ERROR_NOT_FOUND: HRESULT = HRESULT::from_win32(ERROR_FILE_NOT_FOUND);
+
+/// A Python interpreter found in the Windows registry through PEP 514 or from a known Microsoft
+/// Store path.
+///
+/// There are a lot more (optional) fields defined in PEP 514, but we only care about path and
+/// version here, for everything else we probe with a Python script.
+#[derive(Debug, Clone)]
+pub(crate) struct WindowsPython {
+    pub(crate) path: PathBuf,
+    pub(crate) version: Option<PythonVersion>,
+}
+
+/// Find all Pythons registered in the Windows registry following PEP 514.
+pub(crate) fn registry_pythons() -> Result<Vec<WindowsPython>, windows_result::Error> {
+    let mut registry_pythons = Vec::new();
+    // Prefer `HKEY_CURRENT_USER` over `HKEY_LOCAL_MACHINE`
+    for root_key in [CURRENT_USER, LOCAL_MACHINE] {
+        let Ok(key_python) = root_key.open(r"Software\Python") else {
+            continue;
+        };
+        for company in key_python.keys()? {
+            // Reserved name according to the PEP.
+            if company == "PyLauncher" {
+                continue;
+            }
+            let Ok(company_key) = key_python.open(&company) else {
+                // Ignore invalid entries
+                continue;
+            };
+            for tag in company_key.keys()? {
+                let tag_key = company_key.open(&tag)?;
+
+                if let Some(registry_python) = read_registry_entry(&company, &tag, &tag_key) {
+                    registry_pythons.push(registry_python);
+                }
+            }
+        }
+    }
+
+    // The registry has no natural ordering, so we're processing the latest version first.
+    registry_pythons.sort_by(|a, b| {
+        match (&a.version, &b.version) {
+            // Place entries with a version before those without a version.
+            (Some(_), None) => Ordering::Greater,
+            (None, Some(_)) => Ordering::Less,
+            // We want the highest version on top, which is the inverse from the regular order. The
+            // path is an arbitrary but stable tie-breaker.
+            (Some(version_a), Some(version_b)) => {
+                version_a.cmp(version_b).reverse().then(a.path.cmp(&b.path))
+            }
+            // Sort the entries without a version arbitrarily, but stable (by path).
+            (None, None) => a.path.cmp(&b.path),
+        }
+    });
+
+    Ok(registry_pythons)
+}
+
+fn read_registry_entry(company: &str, tag: &str, tag_key: &Key) -> Option<WindowsPython> {
+    // `ExecutablePath` is mandatory for executable Pythons.
+    let Ok(executable_path) = tag_key
+        .open("InstallPath")
+        .and_then(|install_path| install_path.get_value("ExecutablePath"))
+        .and_then(String::try_from)
+    else {
+        debug!(
+            r"Python interpreter in the registry is not executable: `Software\Python\{}\{}",
+            company, tag
+        );
+        return None;
+    };
+
+    // `SysVersion` is optional.
+    let version = tag_key
+        .get_value("SysVersion")
+        .and_then(String::try_from)
+        .ok()
+        .and_then(|s| match PythonVersion::from_str(&s) {
+            Ok(version) => Some(version),
+            Err(err) => {
+                debug!(
+                    "Skipping Python interpreter ({executable_path}) \
+                    with invalid registry version {s}: {err}",
+                );
+                None
+            }
+        });
+
+    Some(WindowsPython {
+        path: PathBuf::from(executable_path),
+        version,
+    })
+}
+
+#[derive(Debug, Error)]
+pub enum ManagedPep514Error {
+    #[error("Windows has an unknown pointer width for arch: `{_0}`")]
+    InvalidPointerSize(Arch),
+}
+
+/// Register a managed Python installation in the Windows registry following PEP 514.
+pub fn create_registry_entry(
+    installation: &ManagedPythonInstallation,
+    errors: &mut Vec<(PythonInstallationKey, anyhow::Error)>,
+) -> Result<(), ManagedPep514Error> {
+    let pointer_width = match installation.key().arch().family().pointer_width() {
+        Ok(PointerWidth::U32) => 32,
+        Ok(PointerWidth::U64) => 64,
+        _ => {
+            return Err(ManagedPep514Error::InvalidPointerSize(
+                *installation.key().arch(),
+            ));
+        }
+    };
+
+    if let Err(err) = write_registry_entry(installation, pointer_width) {
+        errors.push((installation.key().clone(), err.into()));
+    }
+
+    Ok(())
+}
+
+fn write_registry_entry(
+    installation: &ManagedPythonInstallation,
+    pointer_width: i32,
+) -> windows_registry::Result<()> {
+    // We currently just overwrite all known keys, without removing prior entries first
+
+    // Similar to using the bin directory in HOME on Unix, we only install for the current user
+    // on Windows.
+    let company = CURRENT_USER.create(format!("Software\\Python\\{COMPANY_KEY}"))?;
+    company.set_string("DisplayName", COMPANY_DISPLAY_NAME)?;
+    company.set_string("SupportUrl", "https://github.com/astral-sh/uv")?;
+
+    // Ex) CPython3.13.1
+    let tag = company.create(registry_python_tag(installation.key()))?;
+    let display_name = format!(
+        "{} {} ({}-bit)",
+        installation.key().implementation().pretty(),
+        installation.key().version(),
+        pointer_width
+    );
+    tag.set_string("DisplayName", &display_name)?;
+    tag.set_string("SupportUrl", "https://github.com/astral-sh/uv")?;
+    tag.set_string("Version", &installation.key().version().to_string())?;
+    tag.set_string("SysVersion", &installation.key().sys_version())?;
+    tag.set_string("SysArchitecture", &format!("{pointer_width}bit"))?;
+    // Store `python-build-standalone` release
+    if let Some(url) = installation.url() {
+        tag.set_string("DownloadUrl", url)?;
+    }
+    if let Some(sha256) = installation.sha256() {
+        tag.set_string("DownloadSha256", sha256)?;
+    }
+
+    let install_path = tag.create("InstallPath")?;
+    install_path.set_value(
+        "",
+        &Value::from(&HSTRING::from(installation.path().as_os_str())),
+    )?;
+    install_path.set_value(
+        "ExecutablePath",
+        &Value::from(&HSTRING::from(installation.executable(false).as_os_str())),
+    )?;
+    install_path.set_value(
+        "WindowedExecutablePath",
+        &Value::from(&HSTRING::from(installation.executable(true).as_os_str())),
+    )?;
+    Ok(())
+}
+
+fn registry_python_tag(key: &PythonInstallationKey) -> String {
+    format!("{}{}", key.implementation().pretty(), key.version())
+}
+
+/// Remove requested Python entries from the Windows Registry (PEP 514).
+pub fn remove_registry_entry<'a>(
+    installations: impl IntoIterator<Item = &'a ManagedPythonInstallation>,
+    all: bool,
+    errors: &mut Vec<(PythonInstallationKey, anyhow::Error)>,
+) {
+    let astral_key = format!("Software\\Python\\{COMPANY_KEY}");
+    if all {
+        debug!("Removing registry key HKCU:\\{}", astral_key);
+        if let Err(err) = CURRENT_USER.remove_tree(&astral_key) {
+            if err.code() == ERROR_NOT_FOUND {
+                debug!("No registry entries to remove, no registry key {astral_key}");
+            } else {
+                warn_user!("Failed to clear registry entries under {astral_key}: {err}");
+            }
+        }
+        return;
+    }
+
+    for installation in installations {
+        let python_tag = registry_python_tag(installation.key());
+        let python_entry = format!("{astral_key}\\{python_tag}");
+        debug!("Removing registry key HKCU:\\{}", python_entry);
+        if let Err(err) = CURRENT_USER.remove_tree(&python_entry) {
+            if err.code() == ERROR_NOT_FOUND {
+                debug!(
+                    "No registry entries to remove for {}, no registry key {}",
+                    installation.key(),
+                    python_entry
+                );
+            } else {
+                errors.push((
+                    installation.key().clone(),
+                    anyhow::Error::new(err)
+                        .context("Failed to clear registry entries under HKCU:\\{python_entry}"),
+                ));
+            }
+        };
+    }
+}
+
+/// Remove Python entries from the Windows Registry (PEP 514) that are not matching any
+/// installation.
+pub fn remove_orphan_registry_entries(installations: &[ManagedPythonInstallation]) {
+    let keep: HashSet<_> = installations
+        .iter()
+        .map(|installation| registry_python_tag(installation.key()))
+        .collect();
+    let astral_key = format!("Software\\Python\\{COMPANY_KEY}");
+    let key = match CURRENT_USER.open(&astral_key) {
+        Ok(subkeys) => subkeys,
+        Err(err) if err.code() == ERROR_NOT_FOUND => {
+            return;
+        }
+        Err(err) => {
+            // TODO(konsti): We don't have an installation key here.
+            warn_user_once!("Failed to open HKCU:\\{astral_key}: {err}");
+            return;
+        }
+    };
+    // Separate assignment since `keys()` creates a borrow.
+    let subkeys = match key.keys() {
+        Ok(subkeys) => subkeys,
+        Err(err) => {
+            // TODO(konsti): We don't have an installation key here.
+            warn_user_once!("Failed to list subkeys of HKCU:\\{astral_key}: {err}");
+            return;
+        }
+    };
+    for subkey in subkeys {
+        if keep.contains(&subkey) {
+            continue;
+        }
+        let python_entry = format!("{astral_key}\\{subkey}");
+        debug!("Removing orphan registry key HKCU:\\{}", python_entry);
+        if let Err(err) = CURRENT_USER.remove_tree(&python_entry) {
+            // TODO(konsti): We don't have an installation key here.
+            warn_user_once!("Failed to remove orphan registry key HKCU:\\{python_entry}: {err}");
+        };
+    }
+}
diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs
index 229fd9bb631e..69aa4a5d879e 100644
--- a/crates/uv/src/commands/python/install.rs
+++ b/crates/uv/src/commands/python/install.rs
@@ -2,7 +2,7 @@ use std::fmt::Write;
 use std::io::ErrorKind;
 use std::path::{Path, PathBuf};
 
-use anyhow::Result;
+use anyhow::{Error, Result};
 use futures::stream::FuturesUnordered;
 use futures::StreamExt;
 use itertools::{Either, Itertools};
@@ -258,7 +258,7 @@ pub(crate) async fn install(
     for download in &downloads {
         tasks.push(async {
             (
-                download.key(),
+                *download,
                 download
                     .fetch_with_retry(
                         &client,
@@ -276,16 +276,16 @@ pub(crate) async fn install(
 
     let mut errors = vec![];
     let mut downloaded = Vec::with_capacity(downloads.len());
-    while let Some((key, result)) = tasks.next().await {
+    while let Some((download, result)) = tasks.next().await {
         match result {
-            Ok(download) => {
-                let path = match download {
+            Ok(download_result) => {
+                let path = match download_result {
                     // We should only encounter already-available during concurrent installs
                     DownloadResult::AlreadyAvailable(path) => path,
                     DownloadResult::Fetched(path) => path,
                 };
 
-                let installation = ManagedPythonInstallation::new(path)?;
+                let installation = ManagedPythonInstallation::new(path, download);
                 changelog.installed.insert(installation.key().clone());
                 if changelog.existing.contains(installation.key()) {
                     changelog.uninstalled.insert(installation.key().clone());
@@ -293,7 +293,7 @@ pub(crate) async fn install(
                 downloaded.push(installation);
             }
             Err(err) => {
-                errors.push((key, anyhow::Error::new(err)));
+                errors.push((download.key().clone(), anyhow::Error::new(err)));
             }
         }
     }
@@ -326,172 +326,24 @@ pub(crate) async fn install(
             .expect("We should have a bin directory with preview enabled")
             .as_path();
 
-        let targets = if (default || is_default_install)
-            && first_request.matches_installation(installation)
-        {
-            vec![
-                installation.key().executable_name_minor(),
-                installation.key().executable_name_major(),
-                installation.key().executable_name(),
-            ]
-        } else {
-            vec![installation.key().executable_name_minor()]
-        };
-
-        for target in targets {
-            let target = bin.join(target);
-            match installation.create_bin_link(&target) {
-                Ok(()) => {
-                    debug!(
-                        "Installed executable at `{}` for {}",
-                        target.simplified_display(),
-                        installation.key(),
-                    );
-                    changelog.installed.insert(installation.key().clone());
-                    changelog
-                        .installed_executables
-                        .entry(installation.key().clone())
-                        .or_default()
-                        .insert(target.clone());
-                }
-                Err(uv_python::managed::Error::LinkExecutable { from: _, to, err })
-                    if err.kind() == ErrorKind::AlreadyExists =>
-                {
-                    debug!(
-                        "Inspecting existing executable at `{}`",
-                        target.simplified_display()
-                    );
-
-                    // Figure out what installation it references, if any
-                    let existing = find_matching_bin_link(
-                        installations
-                            .iter()
-                            .copied()
-                            .chain(existing_installations.iter()),
-                        &target,
-                    );
-
-                    match existing {
-                        None => {
-                            // Determine if the link is valid, i.e., if it points to an existing
-                            // Python we don't manage. On Windows, we just assume it is valid because
-                            // symlinks are not common for Python interpreters.
-                            let valid_link = cfg!(windows)
-                                || target
-                                    .read_link()
-                                    .and_then(|target| target.try_exists())
-                                    .inspect_err(|err| {
-                                        debug!("Failed to inspect executable with error: {err}");
-                                    })
-                                    // If we can't verify the link, assume it is valid.
-                                    .unwrap_or(true);
-
-                            // There's an existing executable we don't manage, require `--force`
-                            if valid_link {
-                                if !force {
-                                    errors.push((
-                                        installation.key(),
-                                        anyhow::anyhow!(
-                                            "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it",
-                                            to.simplified_display()
-                                        ),
-                                    ));
-                                    continue;
-                                }
-                                debug!(
-                                    "Replacing existing executable at `{}` due to `--force`",
-                                    target.simplified_display()
-                                );
-                            } else {
-                                debug!(
-                                    "Replacing broken symlink at `{}`",
-                                    target.simplified_display()
-                                );
-                            }
-                        }
-                        Some(existing) if existing == *installation => {
-                            // The existing link points to the same installation, so we're done unless
-                            // they requested we reinstall
-                            if !(reinstall || force) {
-                                debug!(
-                                    "Executable at `{}` is already for `{}`",
-                                    target.simplified_display(),
-                                    installation.key(),
-                                );
-                                continue;
-                            }
-                            debug!(
-                                "Replacing existing executable for `{}` at `{}`",
-                                installation.key(),
-                                target.simplified_display(),
-                            );
-                        }
-                        Some(existing) => {
-                            // The existing link points to a different installation, check if it
-                            // is reasonable to replace
-                            if force {
-                                debug!(
-                                    "Replacing existing executable for `{}` at `{}` with executable for `{}` due to `--force` flag",
-                                    existing.key(),
-                                    target.simplified_display(),
-                                    installation.key(),
-                                );
-                            } else {
-                                if installation.is_upgrade_of(existing) {
-                                    debug!(
-                                        "Replacing existing executable for `{}` at `{}` with executable for `{}` since it is an upgrade",
-                                        existing.key(),
-                                        target.simplified_display(),
-                                        installation.key(),
-                                    );
-                                } else if default {
-                                    debug!(
-                                        "Replacing existing executable for `{}` at `{}` with executable for `{}` since `--default` was requested`",
-                                        existing.key(),
-                                        target.simplified_display(),
-                                        installation.key(),
-                                    );
-                                } else {
-                                    debug!(
-                                        "Executable already exists for `{}` at `{}`. Use `--force` to replace it",
-                                        existing.key(),
-                                        to.simplified_display()
-                                    );
-                                    continue;
-                                }
-                            }
-                        }
-                    }
-
-                    // Replace the existing link
-                    fs_err::remove_file(&to)?;
-
-                    if let Some(existing) = existing {
-                        // Ensure we do not report installation of this executable for an existing
-                        // key if we undo it
-                        changelog
-                            .installed_executables
-                            .entry(existing.key().clone())
-                            .or_default()
-                            .remove(&target);
-                    }
+        create_bin_links(
+            installation,
+            bin,
+            reinstall,
+            force,
+            default,
+            is_default_install,
+            first_request,
+            &existing_installations,
+            &installations,
+            &mut changelog,
+            &mut errors,
+        )?;
 
-                    installation.create_bin_link(&target)?;
-                    debug!(
-                        "Updated executable at `{}` to {}",
-                        target.simplified_display(),
-                        installation.key(),
-                    );
-                    changelog.installed.insert(installation.key().clone());
-                    changelog
-                        .installed_executables
-                        .entry(installation.key().clone())
-                        .or_default()
-                        .insert(target.clone());
-                }
-                Err(err) => {
-                    errors.push((installation.key(), anyhow::Error::new(err)));
-                }
+        if preview.is_enabled() {
+            #[cfg(windows)]
+            {
+                uv_python::windows_registry::create_registry_entry(installation, &mut errors)?;
             }
         }
     }
@@ -601,6 +453,191 @@ pub(crate) async fn install(
     Ok(ExitStatus::Success)
 }
 
+/// Link the binaries of a managed Python installation to the bin directory.
+#[allow(clippy::fn_params_excessive_bools)]
+fn create_bin_links(
+    installation: &ManagedPythonInstallation,
+    bin: &Path,
+    reinstall: bool,
+    force: bool,
+    default: bool,
+    is_default_install: bool,
+    first_request: &InstallRequest,
+    existing_installations: &[ManagedPythonInstallation],
+    installations: &[&ManagedPythonInstallation],
+    changelog: &mut Changelog,
+    errors: &mut Vec<(PythonInstallationKey, Error)>,
+) -> Result<(), Error> {
+    let targets =
+        if (default || is_default_install) && first_request.matches_installation(installation) {
+            vec![
+                installation.key().executable_name_minor(),
+                installation.key().executable_name_major(),
+                installation.key().executable_name(),
+            ]
+        } else {
+            vec![installation.key().executable_name_minor()]
+        };
+
+    for target in targets {
+        let target = bin.join(target);
+        match installation.create_bin_link(&target) {
+            Ok(()) => {
+                debug!(
+                    "Installed executable at `{}` for {}",
+                    target.simplified_display(),
+                    installation.key(),
+                );
+                changelog.installed.insert(installation.key().clone());
+                changelog
+                    .installed_executables
+                    .entry(installation.key().clone())
+                    .or_default()
+                    .insert(target.clone());
+            }
+            Err(uv_python::managed::Error::LinkExecutable { from: _, to, err })
+                if err.kind() == ErrorKind::AlreadyExists =>
+            {
+                debug!(
+                    "Inspecting existing executable at `{}`",
+                    target.simplified_display()
+                );
+
+                //  Figure out what installation it references, if any
+                let existing = find_matching_bin_link(
+                    installations
+                        .iter()
+                        .copied()
+                        .chain(existing_installations.iter()),
+                    &target,
+                );
+
+                match existing {
+                    None => {
+                        // Determine if the link is valid, i.e., if it points to an existing
+                        // Python we don't manage. On Windows, we just assume it is valid because
+                        // symlinks are not common for Python interpreters.
+                        let valid_link = cfg!(windows)
+                            || target
+                                .read_link()
+                                .and_then(|target| target.try_exists())
+                                .inspect_err(|err| {
+                                    debug!("Failed to inspect executable with error: {err}");
+                                })
+                                // If we can't verify the link, assume it is valid.
+                                .unwrap_or(true);
+
+                        // There's an existing executable we don't manage, require `--force`
+                        if valid_link {
+                            if !force {
+                                errors.push((
+                                    installation.key().clone(),
+                                    anyhow::anyhow!(
+                                        "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it",
+                                        to.simplified_display()
+                                    ),
+                                ));
+                                continue;
+                            }
+                            debug!(
+                                "Replacing existing executable at `{}` due to `--force`",
+                                target.simplified_display()
+                            );
+                        } else {
+                            debug!(
+                                "Replacing broken symlink at `{}`",
+                                target.simplified_display()
+                            );
+                        }
+                    }
+                    Some(existing) if existing == installation => {
+                        // The existing link points to the same installation, so we're done unless
+                        // they requested we reinstall
+                        if !(reinstall || force) {
+                            debug!(
+                                "Executable at `{}` is already for `{}`",
+                                target.simplified_display(),
+                                installation.key(),
+                            );
+                            continue;
+                        }
+                        debug!(
+                            "Replacing existing executable for `{}` at `{}`",
+                            installation.key(),
+                            target.simplified_display(),
+                        );
+                    }
+                    Some(existing) => {
+                        // The existing link points to a different installation, check if it
+                        // is reasonable to replace
+                        if force {
+                            debug!(
+                                "Replacing existing executable for `{}` at `{}` with executable for `{}` due to `--force` flag",
+                                existing.key(),
+                                target.simplified_display(),
+                                installation.key(),
+                            );
+                        } else {
+                            if installation.is_upgrade_of(existing) {
+                                debug!(
+                                    "Replacing existing executable for `{}` at `{}` with executable for `{}` since it is an upgrade",
+                                    existing.key(),
+                                    target.simplified_display(),
+                                    installation.key(),
+                                );
+                            } else if default {
+                                debug!(
+                                    "Replacing existing executable for `{}` at `{}` with executable for `{}` since `--default` was requested`",
+                                    existing.key(),
+                                    target.simplified_display(),
+                                    installation.key(),
+                                );
+                            } else {
+                                debug!(
+                                    "Executable already exists for `{}` at `{}`. Use `--force` to replace it",
+                                    existing.key(),
+                                    to.simplified_display()
+                                );
+                                continue;
+                            }
+                        }
+                    }
+                }
+
+                // Replace the existing link
+                fs_err::remove_file(&to)?;
+
+                if let Some(existing) = existing {
+                    // Ensure we do not report installation of this executable for an existing
+                    // key if we undo it
+                    changelog
+                        .installed_executables
+                        .entry(existing.key().clone())
+                        .or_default()
+                        .remove(&target);
+                }
+
+                installation.create_bin_link(&target)?;
+                debug!(
+                    "Updated executable at `{}` to {}",
+                    target.simplified_display(),
+                    installation.key(),
+                );
+                changelog.installed.insert(installation.key().clone());
+                changelog
+                    .installed_executables
+                    .entry(installation.key().clone())
+                    .or_default()
+                    .insert(target.clone());
+            }
+            Err(err) => {
+                errors.push((installation.key().clone(), anyhow::Error::new(err)));
+            }
+        }
+    }
+    Ok(())
+}
+
 pub(crate) fn format_executables(
     event: &ChangeEvent,
     executables: &FxHashMap<PythonInstallationKey, FxHashSet<PathBuf>>,
@@ -681,5 +718,5 @@ fn find_matching_bin_link<'a>(
         unreachable!("Only Windows and Unix are supported")
     };
 
-    installations.find(|installation| installation.executable() == target)
+    installations.find(|installation| installation.executable(false) == target)
 }
diff --git a/crates/uv/src/commands/python/uninstall.rs b/crates/uv/src/commands/python/uninstall.rs
index 12556382ec74..e506a47ace6b 100644
--- a/crates/uv/src/commands/python/uninstall.rs
+++ b/crates/uv/src/commands/python/uninstall.rs
@@ -7,9 +7,10 @@ use futures::stream::FuturesUnordered;
 use futures::StreamExt;
 use itertools::Itertools;
 use owo_colors::OwoColorize;
-
 use rustc_hash::{FxHashMap, FxHashSet};
 use tracing::{debug, warn};
+
+use uv_configuration::PreviewMode;
 use uv_fs::Simplified;
 use uv_python::downloads::PythonDownloadRequest;
 use uv_python::managed::{python_executable_dir, ManagedPythonInstallations};
@@ -25,15 +26,15 @@ pub(crate) async fn uninstall(
     install_dir: Option<PathBuf>,
     targets: Vec<String>,
     all: bool,
-
     printer: Printer,
+    preview: PreviewMode,
 ) -> Result<ExitStatus> {
     let installations = ManagedPythonInstallations::from_settings(install_dir)?.init()?;
 
     let _lock = installations.lock().await?;
 
     // Perform the uninstallation.
-    do_uninstall(&installations, targets, all, printer).await?;
+    do_uninstall(&installations, targets, all, printer, preview).await?;
 
     // Clean up any empty directories.
     if uv_fs::directories(installations.root()).all(|path| uv_fs::is_temporary(&path)) {
@@ -62,6 +63,7 @@ async fn do_uninstall(
     targets: Vec<String>,
     all: bool,
     printer: Printer,
+    preview: PreviewMode,
 ) -> Result<ExitStatus> {
     let start = std::time::Instant::now();
 
@@ -107,6 +109,16 @@ async fn do_uninstall(
             matching_installations.insert(installation.clone());
         }
         if !found {
+            // Clear any remnants in the registry
+            if preview.is_enabled() {
+                #[cfg(windows)]
+                {
+                    uv_python::windows_registry::remove_orphan_registry_entries(
+                        &installed_installations,
+                    );
+                }
+            }
+
             if matches!(requests.as_slice(), [PythonRequest::Default]) {
                 writeln!(printer.stderr(), "No Python installations found")?;
                 return Ok(ExitStatus::Failure);
@@ -190,12 +202,22 @@ async fn do_uninstall(
     let mut errors = vec![];
     while let Some((key, result)) = tasks.next().await {
         if let Err(err) = result {
-            errors.push((key.clone(), err));
+            errors.push((key.clone(), anyhow::Error::new(err)));
         } else {
             uninstalled.push(key.clone());
         }
     }
 
+    #[cfg(windows)]
+    if preview.is_enabled() {
+        uv_python::windows_registry::remove_registry_entry(
+            &matching_installations,
+            all,
+            &mut errors,
+        );
+        uv_python::windows_registry::remove_orphan_registry_entries(&installed_installations);
+    }
+
     // Report on any uninstalled installations.
     if !uninstalled.is_empty() {
         if let [uninstalled] = uninstalled.as_slice() {
diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs
index 3af174ca7d29..94b4f9ee23bd 100644
--- a/crates/uv/src/lib.rs
+++ b/crates/uv/src/lib.rs
@@ -1179,7 +1179,14 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
             let args = settings::PythonUninstallSettings::resolve(args, filesystem);
             show_settings!(args);
 
-            commands::python_uninstall(args.install_dir, args.targets, args.all, printer).await
+            commands::python_uninstall(
+                args.install_dir,
+                args.targets,
+                args.all,
+                printer,
+                globals.preview,
+            )
+            .await
         }
         Commands::Python(PythonNamespace {
             command: PythonCommand::Find(args),
diff --git a/crates/uv/tests/it/common/mod.rs b/crates/uv/tests/it/common/mod.rs
index 51f8670b2824..cef8a0746e5f 100644
--- a/crates/uv/tests/it/common/mod.rs
+++ b/crates/uv/tests/it/common/mod.rs
@@ -1077,7 +1077,7 @@ pub fn get_python(version: &PythonVersion) -> PathBuf {
                 .expect("Tests are run on a supported platform")
                 .next()
                 .as_ref()
-                .map(uv_python::managed::ManagedPythonInstallation::executable)
+                .map(|python| python.executable(false))
         })
         // We'll search for the request Python on the PATH if not found in the python versions
         // We hack this into a `PathBuf` to satisfy the compiler but it's just a string
diff --git a/scripts/check_registry.py b/scripts/check_registry.py
new file mode 100644
index 000000000000..14bf144af75a
--- /dev/null
+++ b/scripts/check_registry.py
@@ -0,0 +1,219 @@
+"""Check that adding uv's python-build-standalone distributions are successfully added
+and removed from the Windows registry following PEP 514."""
+
+import re
+import subprocess
+import sys
+from argparse import ArgumentParser
+
+# We apply the same download URL/hash redaction to the actual output, too. We don't
+# redact the path inside the runner, if the runner configuration changes
+# (or uv's installation paths), please update the snapshots.
+expected_registry = [
+    # Our company key
+    r"""
+Name                           Property
+----                           --------
+Astral                         DisplayName : Astral Software Inc.
+                               SupportUrl  : https://github.com/astral-sh/uv
+""",
+    # The actual Python installations
+    r"""
+    Hive: HKEY_CURRENT_USER\Software\Python
+
+
+Name                           Property
+----                           --------
+Astral                         DisplayName : Astral Software Inc.
+                               SupportUrl  : https://github.com/astral-sh/uv
+
+
+    Hive: HKEY_CURRENT_USER\Software\Python\Astral
+
+
+Name                           Property
+----                           --------
+CPython3.11.11                 DisplayName     : CPython 3.11.11 (64-bit)
+                               SupportUrl      : https://github.com/astral-sh/uv
+                               Version         : 3.11.11
+                               SysVersion      : 3.11.11
+                               SysArchitecture : 64bit
+                               DownloadUrl     : <downloadUrl>
+                               DownloadSha256  : <downloadSha256>
+
+
+    Hive: HKEY_CURRENT_USER\Software\Python\Astral\CPython3.11.11
+
+
+Name                           Property
+----                           --------
+InstallPath                    (default)              : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none
+                               ExecutablePath         : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\python.exe
+                               WindowedExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\pythonw.exe
+""",
+    r"""
+    Hive: HKEY_CURRENT_USER\Software\Python\Astral
+
+
+Name                           Property
+----                           --------
+CPython3.12.8                  DisplayName     : CPython 3.12.8 (64-bit)
+                               SupportUrl      : https://github.com/astral-sh/uv
+                               Version         : 3.12.8
+                               SysVersion      : 3.12.8
+                               SysArchitecture : 64bit
+                               DownloadUrl     : <downloadUrl>
+                               DownloadSha256  : <downloadSha256>
+
+
+    Hive: HKEY_CURRENT_USER\Software\Python\Astral\CPython3.12.8
+
+
+Name                           Property
+----                           --------
+InstallPath                    (default)              : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none
+                               ExecutablePath         : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none\python.exe
+                               WindowedExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none\pythonw.exe
+""",
+    r"""
+    Hive: HKEY_CURRENT_USER\Software\Python\Astral
+
+
+Name                           Property
+----                           --------
+CPython3.13.1                  DisplayName     : CPython 3.13.1 (64-bit)
+                               SupportUrl      : https://github.com/astral-sh/uv
+                               Version         : 3.13.1
+                               SysVersion      : 3.13.1
+                               SysArchitecture : 64bit
+                               DownloadUrl     : <downloadUrl>
+                               DownloadSha256  : <downloadSha256>
+
+
+    Hive: HKEY_CURRENT_USER\Software\Python\Astral\CPython3.13.1
+
+
+Name                           Property
+----                           --------
+InstallPath                    (default)              : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none
+                               ExecutablePath         : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\python.exe
+                               WindowedExecutablePath : C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\pythonw.exe
+""",
+]
+
+
+def filter_snapshot(snapshot: str) -> str:
+    # Trim only newlines, there's leading whitespace before the `Hive:` entry
+    snapshot = snapshot.strip("\n\r")
+    # Trim trailing whitespace, Windows pads lines up to length
+    snapshot = "\n".join(line.rstrip() for line in snapshot.splitlines())
+    # Long URLs can be wrapped into multiple lines
+    snapshot = re.sub(
+        "DownloadUrl ( *): .*(\n.*)+?(\n +)DownloadSha256",
+        r"DownloadUrl \1: <downloadUrl>\3DownloadSha256",
+        snapshot,
+    )
+    snapshot = re.sub(
+        "DownloadSha256 ( *): .*", r"DownloadSha256 \1: <downloadSha256>", snapshot
+    )
+    return snapshot
+
+
+def main(uv: str):
+    # `py --list-paths` output
+    py_311_line = r" -V:Astral/CPython3.11.11 C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.11.11-windows-x86_64-none\python.exe"
+    py_312_line = r" -V:Astral/CPython3.12.8 C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none\python.exe"
+    py_313_line = r" -V:Astral/CPython3.13.1 C:\Users\runneradmin\AppData\Roaming\uv\python\cpython-3.13.1-windows-x86_64-none\python.exe"
+
+    # Use the powershell command to get an outside view on the registry values we wrote
+    # By default, powershell wraps the output at terminal size
+    list_registry_command = r"Get-ChildItem -Path HKCU:\Software\Python -Recurse | Format-Table | Out-String -width 1000"
+
+    # Check 1: Install interpreters and check that all their keys are set in the
+    # registry and that the Python launcher for Windows finds it.
+    # Check 1a: Install new interpreters.
+    # Check 1b: Request installation of already installed interpreters.
+    for _ in range(2):
+        print("Installing Python 3.11.11, 3.12.8, and 3.13.1")
+        subprocess.check_call([uv, "python", "install", "-v", "--preview", "3.11.11"])
+        subprocess.check_call([uv, "python", "install", "-v", "--preview", "3.12.8"])
+        subprocess.check_call([uv, "python", "install", "-v", "--preview", "3.13.1"])
+        # The default shell for a subprocess is not powershell
+        actual_registry = subprocess.check_output(
+            ["powershell", "-Command", list_registry_command], text=True
+        )
+        for expected in expected_registry:
+            if filter_snapshot(expected) not in filter_snapshot(actual_registry):
+                print("Registry mismatch:")
+                print("Expected Snippet:")
+                print("=" * 80)
+                print(filter_snapshot(expected))
+                print("=" * 80)
+                print("Actual:")
+                print("=" * 80)
+                print(filter_snapshot(actual_registry))
+                print("=" * 80)
+                sys.exit(1)
+        listed_interpreters = subprocess.check_output(["py", "--list-paths"], text=True)
+        py_listed = set(listed_interpreters.splitlines())
+        if (
+            py_311_line not in py_listed
+            or py_312_line not in py_listed
+            or py_313_line not in py_listed
+        ):
+            print(
+                "Python launcher interpreter mismatch: "
+                f"{py_listed} vs. {py_311_line}, {py_312_line}, {py_313_line}"
+            )
+            sys.exit(1)
+
+    # Check 2: Remove a single interpreter and check that its gone.
+    # Check 2a: Removing an existing interpreter.
+    # Check 2b: Remove a missing interpreter.
+    for _ in range(2):
+        print("Removing Python 3.11.11")
+        subprocess.check_call([uv, "python", "uninstall", "-v", "--preview", "3.11.11"])
+        listed_interpreters = subprocess.check_output(["py", "--list-paths"], text=True)
+        py_listed = set(listed_interpreters.splitlines())
+        if (
+            py_311_line in py_listed
+            or py_312_line not in py_listed
+            or py_313_line not in py_listed
+        ):
+            print(
+                "Python launcher interpreter not removed: "
+                f"{py_listed} vs. {py_312_line}, {py_313_line}"
+            )
+            sys.exit(1)
+
+    # Check 3: Remove all interpreters and check that they are all gone.
+    # Check 3a: Clear a used registry.
+    # Check 3b: Clear an empty registry.
+    subprocess.check_call([uv, "python", "uninstall", "-v", "--preview", "--all"])
+    for _ in range(2):
+        print("Removing all Pythons")
+        empty_registry = subprocess.check_output(
+            ["powershell", "-Command", list_registry_command], text=True
+        )
+        if empty_registry.strip():
+            print("Registry not cleared:")
+            print("=" * 80)
+            print(empty_registry)
+            print("=" * 80)
+            sys.exit(1)
+        listed_interpreters = subprocess.check_output(["py", "--list-paths"], text=True)
+        py_listed = set(listed_interpreters.splitlines())
+        if (
+            py_311_line in py_listed
+            or py_312_line in py_listed
+            or py_313_line in py_listed
+        ):
+            print(f"Python launcher interpreter not cleared: {py_listed}")
+            sys.exit(1)
+
+
+if __name__ == "__main__":
+    parser = ArgumentParser()
+    parser.add_argument("--uv", default="./uv.exe")
+    args = parser.parse_args()
+    main(args.uv)