---
name: windows-exec
description: Run Python scripts and PowerShell reliably through a Windows MCP that enforces a hard 60-second timeout: detached processes, file-based output, polling, and killing hung processes. Use before any Windows command that could exceed 10 seconds, or when a previous command timed out or hung. Not for Linux/macOS shells or for Gumroad UI flows (use gumroad-browser).
license: MIT
metadata:
  author: TechTide AI (Alex Cinovoj)
  provenance: original
  category: Agent Ops & Meta
---

# Windows Execution Patterns

The Windows MCP PowerShell tool kills any call at 60 seconds, no exceptions, and leaves hung processes behind. The rule that fixes everything: detach the process and communicate via files, not stdout.

Paths below use `<WORK_DIR>` for your working directory on the Windows box (e.g. `$env:USERPROFILE\work`). Set it once and stay inside it.

## What times out (all of these)

- `python script.py` when the script or interpreter startup runs long
- `Start-Process -Wait` and `Start-Process -RedirectStandardOutput` (both block)
- `Invoke-WebRequest` to a slow host with no `-TimeoutSec`
- `Get-Process | Stop-Process` against an already-hung process
- `Start-Sleep -Seconds 90`

## Pattern 1: long-running Python (primary)

1. Write the script to a `.py` file via the filesystem MCP. Never long inline `-c "..."` strings.
2. Delete the old output file first, so its presence is a trustworthy done-signal:

```powershell
$out = "<WORK_DIR>\script_output.json"
if (Test-Path $out) { Remove-Item $out }
```

3. Launch detached:

```powershell
$proc = [System.Diagnostics.Process]::new()
$proc.StartInfo.FileName = "python"
$proc.StartInfo.Arguments = "<WORK_DIR>\my_script.py"
$proc.StartInfo.WindowStyle = "Minimized"
$proc.StartInfo.UseShellExecute = $true   # CRITICAL: redirecting stdout forces a blocking wait
$proc.Start() | Out-Null
Write-Host "PID: $($proc.Id)"
```

4. The script writes its own results to a file, including errors:

```python
import json
results = {}
try:
    # work
    results["status"] = "ok"
except Exception as e:
    results = {"status": "error", "error": str(e)}
with open(r"<WORK_DIR>\script_output.json", "w") as f:
    json.dump(results, f, indent=2)
```

5. Poll with separate short calls, each under the 60s window:

```powershell
Start-Sleep -Seconds 30
if (Test-Path "<WORK_DIR>\script_output.json") { Get-Content "<WORK_DIR>\script_output.json" }
else { Get-Process python -EA SilentlyContinue | Select-Object Id, CPU }
```

Repeat the poll as needed. Keep each sleep at 45s or less.

## Pattern 2: quick commands (under 30s expected)

Run directly, but every web request gets `-TimeoutSec` under 20:

```powershell
Invoke-RestMethod -Uri "<API_URL>" -Headers @{Authorization = "Bearer <TOKEN>"} -TimeoutSec 15
```

If it might be slow, it is not a Pattern 2 job. Use Pattern 1.

## Pattern 3: killing hung processes

`Stop-Process` can hang on a hung process. `taskkill` is a direct OS call:

```powershell
taskkill /F /IM python.exe        # by image name
taskkill /F /PID <PID>            # by PID
taskkill /F /T /PID <PID>         # process tree, last resort
```

## Pattern 4: network I/O in Python

Any HTTP transfer that could exceed 5 seconds runs as a detached Python script (Pattern 1), stdlib `http.client`, `timeout=` always set on the connection. Reachability-check the host first with a 5-second HEAD request from PowerShell before committing to an upload.

## Verification

After launching a detached process, poll once. Expect either the output file (read it, check `status`) or a live PID from `Get-Process`. Neither present means the launch failed silently: re-check the script path and rerun the launch command reading `$proc` output. When done, `Get-Process python` should list nothing you started; leftover PIDs get Pattern 3.

## Completion checklist

- [ ] No command issued that can block past 60s
- [ ] Script written to file, not inline
- [ ] Old output file deleted before launch
- [ ] Output file read and status checked
- [ ] No orphan processes left (verified, not assumed)

Any box unchecked: not done. Fix or say so.

## Good vs bad

- Bad: `Start-Process python script.py -RedirectStandardOutput out.txt -Wait` ("I need the output"). Blocks on the pipe, times out at 60s, leaves python.exe orphaned.
- Good: detached launch with `UseShellExecute = $true`, script writes JSON to `<WORK_DIR>`, two 30s polls read the file.

## Footguns

- `UseShellExecute = $false` plus stdout redirection recreates the exact blocking behavior this skill exists to avoid. Keep it `$true` and use files.
- An output file left over from the last run makes a still-running script look finished. Always delete before launch.
- `Invoke-WebRequest` with no `-TimeoutSec` blocks on TCP to a dead host and eats the whole 60s window.
- Browser-MCP file uploads: CDP `file_upload` may return `{"code":-32000,"message":"Not allowed"}` for local paths (browser sandbox, no path fixes it). Workaround: navigate to the local `file:///` image, screenshot it to get an in-memory imageId, navigate to the target form, and pass the imageId to the browser MCP's upload_image with the file input's ref.
