Windows Automation skill for Codex
Hotkeys and scheduled scripts that never click blind. Codex picks the sturdiest layer first, cmdlets or COM before UI Automation and keystrokes as a last resort, then writes AutoHotkey v2 or PowerShell with logging, -WhatIf dry runs and window waits instead of sleeps. Scheduled tasks get absolute paths and the right logon mode, and nothing is registered or deleted on the machine without your go-ahead.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- windows-automation Installs to ~/.agents/skills/windows-automation/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install windows-automation The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project windows-automation Installs to .agents/skills/windows-automation/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/windows-automation.zipunzip -o windows-automation.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) windows-automation PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $windows-automation, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Automates Windows desktops with AutoHotkey v2 hotkeys and window control, PowerShell scripts, Task Scheduler jobs and UI Automation, choosing the API-level route before simulated keystrokes. Use when the user wants a hotkey or text expander, to automate a repetitive clicking task in a Windows app, schedule a PowerShell script, launch and arrange windows, or fix an AHK v1 script that breaks under v2.
Use it when
- A repetitive task in a Windows desktop app needs automating
- You want global or app-specific hotkeys and hotstrings
- A PowerShell script should run on a schedule or at logon
- An AutoHotkey v1 script needs porting to v2
Use something else when
- The task is in a web browser: use browser-automation
- You are on a Mac: use applescript-macos
- You only need PowerShell language help, not desktop automation: use powershell-scripting
How to check the result
Run PowerShell with -WhatIf and read the plan, load the AHK script and confirm no load error, then trigger the scheduled task once and check its log and LastTaskResult.
The mistake it prevents
Scheduling a UI-driving script to run whether or not the user is logged on, so it runs with no desktop and silently does nothing.
Done well means: the automation uses the most reliable layer available (file, COM, CLI or API before UI clicks), runs unattended without stealing focus at the wrong moment, logs what it did, and fails loudly instead of typing into the wrong window.
Pick the layer before writing code
| Goal | Default | Avoid |
|---|---|---|
| Move, rename, copy files; registry; services | PowerShell cmdlets | Clicking through Explorer |
| Drive Excel, Word, Outlook | COM from PowerShell, or $office-macros | Sending keystrokes to Office |
| Hotkeys, text expansion, remap keys | AutoHotkey v2 | Polling loops in PowerShell |
| Click buttons in a desktop app with no API | UI Automation (PowerShell/.NET) or AHK ControlClick | Screen-coordinate clicks |
| Run something on a schedule or at logon | Task Scheduler via Register-ScheduledTask | A script that sleeps forever |
| Browser tasks | Playwright: $browser-automation | AHK sending keys to Chrome |
Coordinate-based clicking and blind Send are the last resort: they break with DPI
scaling, window position, theme changes and a second monitor.
Workflow
- [ ] 1. Read AGENTS.md / README for where scripts live and how they are run
- [ ] 2. Identify the target: exe name, window title, class (AHK Window Spy or Get-Process)
- [ ] 3. Choose the layer (table above); prefer one with a real API
- [ ] 4. Write the script with logging and a dry-run or confirmation mode
- [ ] 5. Run it once interactively and watch it; check the log
- [ ] 6. Only then schedule it or bind it to a hotkey
Codex may run natively on Windows or inside WSL. From WSL, call powershell.exe -NoProfile -File script.ps1 (Windows PowerShell 5.1) or pwsh.exe (PowerShell 7) and pass Windows
paths (wslpath -w). A sandboxed Codex session cannot see or click the user’s desktop:
write the script, explain how to run it, and ask the user to report the log.
AutoHotkey v2
Every script starts with the version directive so v1 syntax fails fast:
#Requires AutoHotkey v2.0
#SingleInstance Force
SetTitleMatchMode 2 ; substring match on window titles
; Ctrl+Alt+N opens Notepad or focuses the existing one
^!n:: {
if WinExist("ahk_exe notepad.exe")
WinActivate
else
Run "notepad.exe"
}
; Hotkeys that only apply in one app
#HotIf WinActive("ahk_exe EXCEL.EXE")
^+v:: Send "{Ctrl down}{Alt down}v{Alt up}{Ctrl up}" ; paste special
#HotIf
; Hotstring: type ;sig then space/enter
:*:;sig::Best regards,`nAtif
Waiting for windows instead of sleeping:
Run "C:\Tools\report.exe"
if !WinWait("Export complete ahk_exe report.exe", , 30) { ; 30 s timeout
MsgBox "Export window never appeared", "report", "Icon!"
ExitApp 1
}
ControlClick "Button1" ; click by control, not by x,y
v1 to v2 traps:
| v1 | v2 |
|---|---|
Send, ^c | Send "^c" (function call, quoted string) |
MsgBox, Hello %name% | MsgBox "Hello " name |
IfWinExist, Title | if WinExist("Title") |
%var% in commands | Plain expressions everywhere; % only for dynamic names |
Return ends hotkey | Use { } blocks; single-line hotkeys end at line end |
StringReplace | StrReplace(haystack, needle, replacement) |
Senddefaults to SendInput; if an app drops keys, trySendEventwithSetKeyDelay.- A non-elevated script cannot send input to an elevated window (UIPI). Running the script as admin fixes it but widens risk; ask before suggesting it.
- Compile with Ahk2Exe only when the user needs a portable .exe; antivirus often flags compiled AHK binaries.
PowerShell automation
#Requires -Version 5.1
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Mandatory)] [string] $Source,
[string] $Archive = "$env:USERPROFILE\Archive"
)
$ErrorActionPreference = 'Stop'
$log = Join-Path $env:LOCALAPPDATA 'automation\archive.log'
New-Item -ItemType Directory -Force -Path (Split-Path $log) | Out-Null
Get-ChildItem -Path $Source -File -Filter *.pdf |
Where-Object LastWriteTime -lt (Get-Date).AddDays(-30) |
ForEach-Object {
$dest = Join-Path $Archive $_.LastWriteTime.ToString('yyyy-MM')
if ($PSCmdlet.ShouldProcess($_.FullName, "Move to $dest")) {
New-Item -ItemType Directory -Force -Path $dest | Out-Null
Move-Item -LiteralPath $_.FullName -Destination $dest
Add-Content $log "$(Get-Date -Format o) moved $($_.Name) -> $dest"
}
}
SupportsShouldProcessgives free-WhatIfand-Confirm; run with-WhatIffirst.- Use
-LiteralPathfor user files:[and]in names are wildcards to-Path. $ErrorActionPreference = 'Stop'makes non-terminating errors catchable intry/catch.- Script language details belong to
$powershell-scripting; this skill is about the automation around it.
UI Automation when there is no API
Add-Type -AssemblyName UIAutomationClient, UIAutomationTypes
$root = [System.Windows.Automation.AutomationElement]::RootElement
$cond = New-Object System.Windows.Automation.PropertyCondition(
[System.Windows.Automation.AutomationElement]::NameProperty, 'Invoice Tool')
$win = $root.FindFirst([System.Windows.Automation.TreeScope]::Children, $cond)
if (-not $win) { throw 'Invoice Tool window not found' }
$btnCond = New-Object System.Windows.Automation.PropertyCondition(
[System.Windows.Automation.AutomationElement]::AutomationIdProperty, 'btnExport')
$btn = $win.FindFirst([System.Windows.Automation.TreeScope]::Descendants, $btnCond)
$btn.GetCurrentPattern([System.Windows.Automation.InvokePattern]::Pattern).Invoke()
Find AutomationIds with Accessibility Insights or Inspect.exe. These assemblies load in
Windows PowerShell 5.1; under PowerShell 7 they may need the .NET desktop runtime, so
test both if the user’s machine runs pwsh.
Scheduling
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -NonInteractive -File "C:\Scripts\archive.ps1" -Source "D:\Inbox"'
$trigger = New-ScheduledTaskTrigger -Daily -At 7:30am
$settings = New-ScheduledTaskSettingsSet -StartWhenAvailable -ExecutionTimeLimit (New-TimeSpan -Hours 1)
Register-ScheduledTask -TaskName 'Archive old PDFs' -Action $action -Trigger $trigger `
-Settings $settings -Description 'Moves PDFs older than 30 days'
- Registering, replacing or deleting a scheduled task changes the user’s machine: show the exact command and get a yes first.
- Tasks run with a different working directory (
C:\Windows\System32): use absolute paths. - “Run whether user is logged on or not” runs without a desktop, so UI automation and AHK
Sendsilently do nothing. UI tasks need “run only when user is logged on”. -StartWhenAvailablecatches runs missed while the laptop slept.- Check results with
Get-ScheduledTaskInfo -TaskName '...'(LastTaskResult0 = success).
Execution policy and safety
- If scripts are blocked, prefer
Unblock-File script.ps1for a downloaded file, orSet-ExecutionPolicy -Scope CurrentUser RemoteSignedafter the user agrees. Never change the machine-wide policy or disable Defender or SmartScreen. - Never store passwords in scripts. Use Windows Credential Manager (
SecretManagementmodule) or prompt withGet-Credential. - Anything that deletes files, kills processes or sends mail runs in
-WhatIfmode first, and the real run waits for the user’s confirmation.
Gotchas
- Window titles change with the open document; match
ahk_exeplus a title fragment. - DPI scaling above 100 % shifts coordinates; per-monitor DPI makes it worse. Use controls.
- Focus stealing: a popup during
Sendredirects keystrokes. Re-checkWinActiveright before sending anything destructive. - OneDrive “Known Folder Move” relocates Desktop and Documents; resolve them with
[Environment]::GetFolderPath('Desktop'), not a hard-coded path. - PowerShell 5.1 writes UTF-16 by default with
>; useOut-File -Encoding utf8orSet-Content -Encoding utf8.
Verification
- Run PowerShell scripts with
-WhatIfand read the planned actions. - Lint with PSScriptAnalyzer if installed:
Invoke-ScriptAnalyzer -Path .\script.ps1. - For AHK, launch the script and check the tray icon has no load error; v2 reports syntax errors at load time with a line number.
- After scheduling, trigger once with
Start-ScheduledTaskand read the log file.
Handover note
**Script:** C:\Scripts\archive.ps1 (PowerShell 5.1, no modules)
**Trigger:** Task "Archive old PDFs", daily 07:30, runs only when logged on
**Dry run:** `.\archive.ps1 -Source D:\Inbox -WhatIf`
**Log:** %LOCALAPPDATA%\automation\archive.log
**Undo:** Unregister-ScheduledTask -TaskName 'Archive old PDFs'; files are moved, not deleted What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Windows Automation"
short_description: "Hotkeys and scheduled scripts that never click blind"
default_prompt: "$windows-automation Make a Ctrl+Alt+N hotkey that opens or focuses Notepad, and a ;sig text expander" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.