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.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- windows-automation

Installs to ~/.agents/skills/windows-automation/ for every project on this machine.

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

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.

~/.agents/skills/windows-automation/SKILL.md

--- name: windows-automation description: Automates Windows desktops with AutoHotkey v2 hotkeys and window control, PowerShell scripts, Task Scheduler jobs and UI Automation, choosing the… license: MIT ---

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

GoalDefaultAvoid
Move, rename, copy files; registry; servicesPowerShell cmdletsClicking through Explorer
Drive Excel, Word, OutlookCOM from PowerShell, or $office-macrosSending keystrokes to Office
Hotkeys, text expansion, remap keysAutoHotkey v2Polling loops in PowerShell
Click buttons in a desktop app with no APIUI Automation (PowerShell/.NET) or AHK ControlClickScreen-coordinate clicks
Run something on a schedule or at logonTask Scheduler via Register-ScheduledTaskA script that sleeps forever
Browser tasksPlaywright: $browser-automationAHK 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:

v1v2
Send, ^cSend "^c" (function call, quoted string)
MsgBox, Hello %name%MsgBox "Hello " name
IfWinExist, Titleif WinExist("Title")
%var% in commandsPlain expressions everywhere; % only for dynamic names
Return ends hotkeyUse { } blocks; single-line hotkeys end at line end
StringReplaceStrReplace(haystack, needle, replacement)
  • Send defaults to SendInput; if an app drops keys, try SendEvent with SetKeyDelay.
  • 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"
        }
    }
  • SupportsShouldProcess gives free -WhatIf and -Confirm; run with -WhatIf first.
  • Use -LiteralPath for user files: [ and ] in names are wildcards to -Path.
  • $ErrorActionPreference = 'Stop' makes non-terminating errors catchable in try/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 Send silently do nothing. UI tasks need “run only when user is logged on”.
  • -StartWhenAvailable catches runs missed while the laptop slept.
  • Check results with Get-ScheduledTaskInfo -TaskName '...' (LastTaskResult 0 = success).

Execution policy and safety

  • If scripts are blocked, prefer Unblock-File script.ps1 for a downloaded file, or Set-ExecutionPolicy -Scope CurrentUser RemoteSigned after the user agrees. Never change the machine-wide policy or disable Defender or SmartScreen.
  • Never store passwords in scripts. Use Windows Credential Manager (SecretManagement module) or prompt with Get-Credential.
  • Anything that deletes files, kills processes or sends mail runs in -WhatIf mode first, and the real run waits for the user’s confirmation.

Gotchas

  • Window titles change with the open document; match ahk_exe plus a title fragment.
  • DPI scaling above 100 % shifts coordinates; per-monitor DPI makes it worse. Use controls.
  • Focus stealing: a popup during Send redirects keystrokes. Re-check WinActive right 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 >; use Out-File -Encoding utf8 or Set-Content -Encoding utf8.

Verification

  • Run PowerShell scripts with -WhatIf and 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-ScheduledTask and 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.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem