Skip to content

Extract PowerShell execution into static PowerShellExecutor class#158

Merged
Jeomon merged 3 commits intoCursorTouch:mainfrom
JezaChen:refactor/extract-powershell-executor
Mar 29, 2026
Merged

Extract PowerShell execution into static PowerShellExecutor class#158
Jeomon merged 3 commits intoCursorTouch:mainfrom
JezaChen:refactor/extract-powershell-executor

Conversation

@JezaChen
Copy link
Copy Markdown
Collaborator

Summary

  • Extract the execute_command method from the Desktop class into a new standalone static utility class PowerShellExecutor in desktop/powershell.py
  • Update all internal call sites in desktop/service.py and tools/shell.py to use PowerShellExecutor.execute_command directly
  • Retain Desktop.execute_command as a thin backward-compatible delegate

Motivation

The execute_command method in Desktop is a pure utility function — it does not depend on any instance state (self is unused). Coupling it to the Desktop class means callers (e.g., the PowerShell tool) must instantiate or obtain a Desktop object just to run a shell command. Extracting it into a static class:

  1. Improves cohesionDesktop focuses on window/UI orchestration; shell execution lives in its own module.
  2. Enables reuse — Any module can call PowerShellExecutor.execute_command(...) without a Desktop dependency.
  3. Simplifies testing — The executor can be unit-tested in isolation without mocking the full Desktop service.

Changes

File Change
src/windows_mcp/desktop/powershell.py NewPowerShellExecutor static class with execute_command
src/windows_mcp/desktop/service.py Replace inline implementation with delegate to PowerShellExecutor; update all self.execute_commandPowerShellExecutor.execute_command; remove unused imports (base64, shutil, subprocess)
src/windows_mcp/tools/shell.py Import PowerShellExecutor and call it directly instead of get_desktop().execute_command

…ecutor class

Move the `execute_command` method from `Desktop` into a standalone
`PowerShellExecutor` static utility class in `desktop/powershell.py`.
This decouples shell execution from the Desktop service, making it
reusable without instantiating a Desktop object.

- Create `desktop/powershell.py` with `PowerShellExecutor.execute_command`
- Update all call sites in `desktop/service.py` to use the new class
- Update `tools/shell.py` to import and call `PowerShellExecutor` directly
- Keep `Desktop.execute_command` as a thin delegate for backward compat
- Remove unused imports (`base64`, `shutil`, `subprocess`) from service.py

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings March 29, 2026 12:12
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extracts PowerShell command execution out of the Desktop service into a standalone PowerShellExecutor utility, and updates call sites to use it directly while keeping Desktop.execute_command as a delegate for backward compatibility.

Changes:

  • Added PowerShellExecutor.execute_command(...) in a new desktop/powershell.py module.
  • Replaced Desktop.execute_command implementation with a delegate and updated call sites in desktop/service.py.
  • Updated the PowerShell tool (tools/shell.py) to call the executor directly.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
src/windows_mcp/desktop/powershell.py Introduces a new static executor for PowerShell commands.
src/windows_mcp/desktop/service.py Delegates execute_command and redirects internal PowerShell call sites to the new executor.
src/windows_mcp/tools/shell.py Uses the new executor directly for the PowerShell tool.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +4 to +12
import logging
import os
import shutil
import subprocess

from windows_mcp.desktop.utils import run_with_graceful_timeout

logger = logging.getLogger(__name__)

Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

logger = logging.getLogger(__name__) is never used in this module, which will trigger Ruff/pyflakes unused-variable lint failures. Either remove the logging import + logger assignment or start using logger (e.g., to record command failures/timeouts).

Copilot uses AI. Check for mistakes.
"""Get installed apps. Tries Get-StartApps first, falls back to shortcut scanning."""
command = "Get-StartApps | ConvertTo-Csv -NoTypeInformation"
apps_info, status = self.execute_command(command)
apps_info, status = PowerShellExecutor.execute_command(command)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

Switching internal Desktop methods to call PowerShellExecutor.execute_command(...) directly bypasses Desktop.execute_command(...). That breaks existing tests/mocking patterns (e.g., tests/test_registry.py replaces desktop.execute_command with a MagicMock) and removes the ability to override execute_command in a Desktop subclass. Prefer calling self.execute_command(...) inside Desktop methods (keeping Desktop.execute_command as the delegate to PowerShellExecutor) so callers/tests can still intercept execution.

Suggested change
apps_info, status = PowerShellExecutor.execute_command(command)
apps_info, status = self.execute_command(command)

Copilot uses AI. Check for mistakes.
Comment on lines 1463 to +1464
command = f"Get-ItemProperty -Path {q_path} -Name {q_name} | Select-Object -ExpandProperty {q_name}"
response, status = self.execute_command(command)
response, status = PowerShellExecutor.execute_command(command)
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

Same issue as above: calling PowerShellExecutor.execute_command(...) here bypasses self.execute_command(...), which prevents tests from mocking command execution and makes it harder to swap/override execution behavior. Use self.execute_command(command, ...) inside Desktop methods and keep the delegation centralized in Desktop.execute_command.

Copilot uses AI. Check for mistakes.
Comment on lines 21 to 25
@with_analytics(get_analytics(), "Powershell-Tool")
def powershell_tool(command: str, timeout: int = 30, ctx: Context = None) -> str:
try:
response, status_code = get_desktop().execute_command(command, timeout)
response, status_code = PowerShellExecutor.execute_command(command, timeout)
return f"Response: {response}\nStatus Code: {status_code}"
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

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

This tool now bypasses the injected get_desktop() dependency and calls PowerShellExecutor directly. That’s inconsistent with the other tools’ pattern (e.g., tools/app.py and tools/registry.py call methods on get_desktop()), and it reduces testability/override-ability if a different Desktop implementation is ever provided. Prefer get_desktop().execute_command(...) here (which can remain a thin delegate to PowerShellExecutor).

Copilot uses AI. Check for mistakes.
@Jeomon
Copy link
Copy Markdown
Member

Jeomon commented Mar 29, 2026

Good idea to move the PowerShell as it is really long now..

@Jeomon Jeomon merged commit 67267b1 into CursorTouch:main Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants