Skip to content

Advanced Paste: Add Python Script Extension Support#49034

Open
MuyuanMS wants to merge 11 commits into
mainfrom
user/muyuanli/PythonExtV2Interface-clean
Open

Advanced Paste: Add Python Script Extension Support#49034
MuyuanMS wants to merge 11 commits into
mainfrom
user/muyuanli/PythonExtV2Interface-clean

Conversation

@MuyuanMS

@MuyuanMS MuyuanMS commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR adds Python script extension support to Advanced Paste, enabling users to write custom clipboard transformation scripts in Python that appear as new paste format options in the Advanced Paste menu (Win+Shift+V).

Motivation

Advanced Paste currently supports a fixed set of clipboard transformations (plain text, markdown, JSON, etc.). Users frequently request the ability to add their own custom transformations. Python script extensions allow power users to create arbitrarily complex clipboard transformations without modifying PowerToys source code.

Feature Overview

How It Works

  1. User places .py scripts in a configurable scripts folder
  2. Each script declares a function like advanced_paste_from_text_to_markdown(text) - the function name encodes input/output formats
  3. Advanced Paste auto-discovers scripts and shows them as paste options
  4. When selected, the script runs via a bundled _runner.py that handles JSON I/O between Windows and the Python process

Script Interface

Scripts follow a naming convention for automatic format matching:

def advanced_paste_from_text_to_custom_format(text: str) -> str:
    """Convert clipboard text to my custom format."""
    return text.upper()  # Example transformation

Supported input formats: text, html, image, audio, video, files
Scripts can output: text, HTML, image paths, audio/video paths, or file lists.

Platform Support

Users choose one of three modes in Settings:

  • Disabled - Python extensions off (default)
  • Windows - Runs scripts using native Windows Python (auto-detected from registry via PEP 514, or manually configured path)
  • WSL - Runs scripts inside Windows Subsystem for Linux

WSL mode features:

  • Distribution selection via dropdown (populated from wsl --list --quiet)
  • Automatic /mnt/ path translation between Windows and WSL
  • UNC path support (\\wsl.localhost\...)
  • Proper quoting for distribution names containing spaces
  • pip dependency auto-install inside WSL

Dependency Management

Scripts can declare pip dependencies via # requires: comments:

# requires: pandas, numpy
def advanced_paste_from_text_to_dataframe(text: str) -> str:
    ...

Advanced Paste auto-detects missing packages and installs them before execution.

Settings UI

  • Mode dropdown: Disabled / Windows / WSL
  • Scripts folder: Configurable path (defaults to %LOCALAPPDATA%\Microsoft\PowerToys\AdvancedPaste\Scripts)
  • Python interpreter path: For Windows mode, auto-detects or manually specified
  • WSL distribution: ComboBox populated from installed distros
  • Discovered scripts list: Read-only view showing detected scripts with their input/output formats

Error Handling

  • Script errors show a details popup with full stderr for debugging
  • Timeout handling prevents frozen scripts from blocking the UI
  • Missing Python / WSL shows clear error messages guiding users to install prerequisites

Architecture

User's script folder (configurable)
  +-- my_script.py              # declares advanced_paste_from_text_to_json()
  +-- image_resize.py           # declares advanced_paste_from_image_to_image()
      |
      v (auto-discovered on startup / file change)
PythonScriptService
  +-- Parses function signatures and metadata from script files
  +-- Infers input/output formats from function name
  +-- Checks/installs pip dependencies
  +-- Executes via:
      +-- Windows: python3.exe _runner.py <script> < input.json
      +-- WSL: wsl.exe -d <distro> -- bash -l -c "python3 _runner.py ..."
          |
          v
      _runner.py (bundled with PowerToys)
        - Loads script module dynamically
        - Calls the matching advanced_paste_from_*() function
        - Returns JSON: { "text": ..., "html": ..., "image_path": ..., etc. }

Files Changed

Area Key Files Description
Script Engine Services/PythonScripts/PythonScriptService.cs Core execution engine: script discovery, metadata parsing, WSL/Windows execution, dependency management
Runner Services/PythonScripts/_runner.py Python-side script loader, JSON I/O protocol
Interfaces IPythonScriptService.cs, IPythonScriptTrustService.cs Service contracts
Settings Model AdvancedPastePythonScriptSettings.cs, PythonScriptWslSettings.cs, PythonScriptWindowsSettings.cs Settings schema
Settings ViewModel AdvancedPasteViewModel.cs WSL distro enumeration, mode switching logic
Settings Page AdvancedPastePage.xaml / .xaml.cs UI: mode dropdown, distro ComboBox, script list
User Settings UserSettings.cs, IUserSettings.cs Runtime settings consumption in the module
Module Interface dllmain.cpp Registration of Python-based paste actions with the runner
Unit Tests PythonScriptServiceTests.cs Format inference, metadata parsing tests
Documentation advancedpaste-python-scripts.md Developer documentation

How to Test

  1. Build the solution (or just the AdvancedPaste + Settings.UI projects)
  2. Launch PowerToys -> Settings -> Advanced Paste
  3. Under Python scripts, set the runtime mode to Windows or WSL
  4. Create a .py file in the scripts folder with a function like:
    def advanced_paste_from_text_to_uppercase(text: str) -> str:
        return text.upper()
  5. Copy some text to clipboard
  6. Press Win+Shift+V - the new "uppercase" option should appear
  7. Select it - clipboard is pasted as uppercase text

Security Considerations

  • Scripts run with the user's own permissions (no elevation)
  • A trust service (PythonScriptTrustService) tracks which scripts the user has approved
  • Scripts folder is user-writable by design (it's the user's own scripts)
  • WSL distribution name is quoted to prevent shell injection

Screenshots on main functionalities

Settings page (Windows)

image ### Settings page (WSL) image

Advanced Paste Menu

image image

Example Python extension script

# @advancedpaste:name   Reverse Text
# @advancedpaste:desc   Reverses text character by character


def advanced_paste_from_text_to_text(text):
    """Reverse clipboard text character by character."""
    return text[::-1]
# @advancedpaste:name   Image Resize 50%
# @advancedpaste:desc   Resize a clipboard image to 50% of its original size
# @advancedpaste:requires Pillow

from pathlib import Path

def advanced_paste_from_image_to_image(image_path: str):
    """Resize a clipboard image to 50% of its original dimensions."""
    from PIL import Image

    img = Image.open(image_path)
    new_size = (img.width // 2, img.height // 2)
    resized = img.resize(new_size, Image.LANCZOS)

    out_path = Path(image_path).with_stem(Path(image_path).stem + "_resized")
    resized.save(out_path)
    return out_path

@github-actions github-actions Bot added the Product-Advanced Paste Refers to the Advanced Paste module label Jun 30, 2026
MuyuanMS and others added 5 commits June 30, 2026 15:42
Enable users to write custom clipboard transformation scripts in Python
that integrate directly into the Advanced Paste menu. Scripts are auto-
discovered from a configurable folder and can run on native Windows
Python or inside WSL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion, docs

- Restore FuzzTests to use UTF-8 decoding and GetAwaiter().GetResult()
- Restore defensive try/catch in JsonHelper.ToJsonFromXmlOrCsvAsync
- Add null guard for ReadMetadata in PasteFormatExecutor
- Restore is_enabled_by_default() override in dllmain.cpp
- Localize hard-coded strings in AdvancedPastePage.xaml via x:Uid
- Fix _runner.py docstring to include audio/video input types
- Fix typos in UITestAdvancedPaste.md
- Filter Settings script list to match runtime discovery behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…emove unused file write

- Register Python script settings types in SettingsSerializationContext for AOT safety
- Align Settings discovery with runtime: scan entire file, reject multi-function scripts
- Remove unused ap_input.json file write on Windows execution path (stdin is used)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…s format matching

- Remove KernelFunctionDescription from PythonScript to prevent broken AI kernel registration
- Normalize file/files format names in _runner.py so files-input scripts match correctly

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…base)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MuyuanMS MuyuanMS force-pushed the user/muyuanli/PythonExtV2Interface-clean branch from 8a07a6e to 950ee6a Compare June 30, 2026 09:17
@MuyuanMS MuyuanMS requested a review from Copilot July 1, 2026 06:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a Python-based extension mechanism to the Advanced Paste module so users can drop .py scripts into a folder and have them appear as additional paste actions (Windows Python or WSL), with trust prompting, dependency install prompts, and Settings UI integration.

Changes:

  • Introduces a Python script discovery/execution pipeline (C# service + bundled _runner.py) with Windows/WSL runtimes, timeout/error handling, and dependency installation.
  • Extends Settings UI + settings schema to configure Python script mode, paths, WSL distro selection, and to list discovered scripts.
  • Adds new paste formats plumbing to surface discovered scripts in the Advanced Paste UI, plus unit tests and documentation.

Reviewed changes

Copilot reviewed 38 out of 38 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs Adds Settings-side Python scripts mode/config, WSL distro enumeration, and script discovery for the Settings page.
src/settings-ui/Settings.UI/Strings/en-us/Resources.resw Adds localized strings for the new Python scripts Settings UI.
src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml.cs Wires new UI actions (browse/open/refresh) and WSL distro refresh on page load.
src/settings-ui/Settings.UI/SettingsXAML/Views/AdvancedPastePage.xaml Adds the Python scripts settings group (mode dropdown, folder/path pickers, distro picker, discovered list).
src/settings-ui/Settings.UI.UnitTests/ViewModelTests/AdvancedPaste.cs Adds a unit test validating Settings-side metadata parsing from .py headers/function name.
src/settings-ui/Settings.UI.Library/SettingsSerializationContext.cs Registers new settings types for source-generated JSON serialization.
src/settings-ui/Settings.UI.Library/PythonScriptWslSettings.cs Adds WSL-specific settings payload (folder + distro).
src/settings-ui/Settings.UI.Library/PythonScriptWindowsSettings.cs Adds Windows-specific settings payload (folder + python path).
src/settings-ui/Settings.UI.Library/AdvancedPastePythonScriptSettings.cs Adds the new python-scripts settings schema + legacy migration helper.
src/settings-ui/Settings.UI.Library/AdvancedPastePythonScriptAction.cs Adds the persisted per-script settings/action model (path, hotkey, show/enabled, formats, etc.).
src/settings-ui/Settings.UI.Library/AdvancedPasteProperties.cs Adds python-scripts to module settings and removes ShowAIPaste.
src/modules/AdvancedPaste/UITest-AdvancedPaste/TestFiles/settings.json Updates UI test settings payload for removed ShowAIPaste.
src/modules/AdvancedPaste/UITest-AdvancedPaste/AdvancedPaste-UITests.csproj Adds additional package references for the UI test project.
src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp Refactors auto-copy logic (WM_COPY + SendInput fallback) and reduces logging.
src/modules/AdvancedPaste/AdvancedPaste/ViewModels/OptionsViewModel.cs Adds python-script paste formats to the menu and integrates script discovery via IPythonScriptService.
src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw Adds user-facing error/trust/dependency-install strings for Python script execution.
src/modules/AdvancedPaste/AdvancedPaste/Services/PythonScripts/PythonScriptTrustService.cs Implements trust + package-install consent dialogs and script hashing.
src/modules/AdvancedPaste/AdvancedPaste/Services/PythonScripts/PythonScriptService.cs Core runtime: metadata parsing, dependency detection/install, Windows+WSL execution, path translation, error parsing.
src/modules/AdvancedPaste/AdvancedPaste/Services/PythonScripts/PythonScriptMetadata.cs Adds metadata record describing a discovered script.
src/modules/AdvancedPaste/AdvancedPaste/Services/PythonScripts/PythonRequirement.cs Adds requirement record representing import↔pip mapping.
src/modules/AdvancedPaste/AdvancedPaste/Services/PythonScripts/IPythonScriptTrustService.cs Defines trust/dependency install consent contract.
src/modules/AdvancedPaste/AdvancedPaste/Services/PythonScripts/IPythonScriptService.cs Defines script execution/discovery/dependency management contract.
src/modules/AdvancedPaste/AdvancedPaste/Services/PythonScripts/_runner.py Adds the shipped Python runner that loads the user script and performs JSON I/O.
src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs Hooks PasteFormats.PythonScript execution (trust prompt, dependency preflight, invoke service).
src/modules/AdvancedPaste/AdvancedPaste/Models/PasteFormats.cs Adds the PythonScript paste format metadata.
src/modules/AdvancedPaste/AdvancedPaste/Models/PasteFormat.cs Adds factory method to build Python script paste formats (name + path).
src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs Consumes new python-scripts settings, tracks trust hashes, and watches scripts folder for changes.
src/modules/AdvancedPaste/AdvancedPaste/Helpers/IUserSettings.cs Extends user settings contract for python-scripts configuration + trust storage.
src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml.cs Adds “show error details” dialog flow for script errors.
src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Pages/MainPage.xaml Adds script formats list section and refactors error presentation + prompt footer visibility.
src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/MainWindow.xaml.cs Updates height calculations to include Python script formats.
src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/PromptBox.xaml Removes embedded error grid (moved to MainPage).
src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/Controls/ClipboardHistoryItemPreviewControl.xaml Adjusts bindings to OneWay for updated UI behavior.
src/modules/AdvancedPaste/AdvancedPaste/AdvancedPasteXAML/App.xaml.cs Registers new Python script services in DI.
src/modules/AdvancedPaste/AdvancedPaste/AdvancedPaste.csproj Ships _runner.py as content in output.
src/modules/AdvancedPaste/AdvancedPaste.UnitTests/ServicesTests/PythonScriptServiceTests.cs Adds unit tests for import detection, metadata parsing, and error parsing helpers.
src/modules/AdvancedPaste/AdvancedPaste.UnitTests/Mocks/IntegrationTestUserSettings.cs Updates test mock to satisfy new IUserSettings contract.
doc/devdocs/modules/advancedpaste-python-scripts.md Adds developer documentation for the Python scripts feature and script conventions.

Comment thread src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs
Comment thread src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs Outdated
Comment thread src/settings-ui/Settings.UI/Strings/en-us/Resources.resw
Comment thread src/modules/AdvancedPaste/AdvancedPasteModuleInterface/dllmain.cpp
Comment thread src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs
- Fix ScriptsFolder to fall back to default folder when setting is blank
- Align Settings tag parsing with runtime: use @advancedpaste:disabled
- Remove misleading 'written back to headers' description string
- Add modifier release/restore to SendInput Ctrl+C path (matches Ctrl+V)
- Sanitize pip package names to prevent shell metacharacter injection
- Fix RefreshWslDistros to WaitForExit before ReadToEnd (prevents hang)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 38 out of 38 changed files in this pull request and generated 8 comments.

Comment thread src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs Outdated
Comment thread src/modules/AdvancedPaste/AdvancedPaste/Services/PythonScripts/_runner.py Outdated
Comment thread src/modules/AdvancedPaste/AdvancedPaste/Strings/en-us/Resources.resw Outdated
…d handling

- Fix TryParseTag to support presence-based @advancedpaste:disabled tag
- Change 'if not input_value' to 'if input_value is None' (empty string is valid)
- Wrap HTML output with HtmlFormatHelper.CreateHtmlFormat for CF_HTML compliance
- Add try-catch for FileNotFoundException during ComputeHash
- Change else-if to sequential ifs in DataPackageFromViewAsync (preserve all formats)
- Remove unused System.* PackageReferences from UITest project
- Fix '..' typo to '...' in search placeholder text

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated 5 comments.

Comment thread src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs
Comment thread src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs Outdated
Comment thread src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs
Comment thread src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs Outdated
…lifecycle

- Fix PythonScriptNotFound to format {0} placeholder and use PasteActionException
- Dispose old CancellationTokenSource before creating new one in debounce handler
- Remove .Wait() on UI-scheduled task (fire-and-forget is sufficient)
- Add WaitForExit after Kill() in RefreshWslDistros to prevent ReadToEnd blocking
- Localize '(System default)' WSL distro display name via resource string
- Kill child Python/WSL process on user cancellation to prevent orphaned processes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 37 out of 37 changed files in this pull request and generated 7 comments.

Comment thread src/settings-ui/Settings.UI/ViewModels/AdvancedPasteViewModel.cs
Comment thread src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs Outdated
Comment thread src/modules/AdvancedPaste/AdvancedPaste/Helpers/UserSettings.cs Outdated
Comment thread src/modules/AdvancedPaste/AdvancedPaste/Services/PasteFormatExecutor.cs Outdated
…st dialog

- Fix early return in RefreshWslDistros to still update with default-only list
- Store all PythonScriptActions (not just IsShown=true) so hide logic works
- Pass cancellation token to Task.Delay in debounce handler
- Use FileNotFoundException(message, fileName) constructor properly
- Display script SHA-256 hash in trust dialog for user verification
- Validate Python import names before embedding in shell commands

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MuyuanMS MuyuanMS marked this pull request as ready for review July 1, 2026 08:49
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@htcfreek htcfreek left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feature needs to have a group policy for the "Disabled/Windows/WSL" setting or at least to force disable the script feature.

If you implement the policy please add in in the Advanced Paste category and as a utility setting like the ai policy for Advanced Paste.

Enterprise administrators can now disable the Python scripts feature via
GPO policy 'AllowAdvancedPastePythonScripts' under the Advanced Paste
category (HKLM\SOFTWARE\Policies\PowerToys\AllowAdvancedPastePythonScripts).

When disabled:
- Settings UI mode selector is grayed out and forced to 'Disabled'
- Info bar shows that the setting is managed by organization policy
- Runtime enforces disabled state even if settings.json is manually edited

Implementation follows the same pattern as the existing AI models GPO policy.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@htcfreek htcfreek left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gpo commit looks good to me

@MuyuanMS MuyuanMS force-pushed the user/muyuanli/PythonExtV2Interface-clean branch from 6ffd10d to 64c6f35 Compare July 7, 2026 09:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Product-Advanced Paste Refers to the Advanced Paste module

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants