Ein Wartungsskript läuft seit Monaten unbeaufsichtigt im Aufgabenplaner – und scheitert eines Nachts, weil das Anmeldeformular der überwachten Anwendung jetzt ein reCAPTCHA zeigt. Dafür braucht es kein Framework und kein Zusatzmodul: Zwei Invoke-RestMethod-Aufrufe liefern ein Token, das Ihr Skript wie jeden anderen Formularwert weiterreicht.
Der Leitfaden zeigt den kompletten Weg in Windows PowerShell 5.1 und PowerShell 7 – übermitteln, abfragen, Token einsetzen – für reCAPTCHA v2 und v3, Turnstile und Bild-CAPTCHAs.
Voraussetzungen
- PowerShell 5.1 (in Windows enthalten) oder PowerShell 7+ (auch unter Linux und macOS)
- Ein CaptchaAI-Konto mit API-Schlüssel (Schlüssel abrufen)
- Ausgehende HTTPS-Verbindungen zu
ocr.captchaai.com– Module oder SDKs sind nicht nötig
Zwei Endpunkte, ein Ablauf
Die API kennt nur zwei Adressen. An in.php übermitteln Sie die Aufgabe – Methode, Sitekey, Seiten-URL – und erhalten eine Task-ID. Von res.php fragen Sie das Ergebnis ab, bis statt CAPCHA_NOT_READY das Token oder der erkannte Text zurückkommt.
Die Wartezeit hängt vom Typ ab: Bild-CAPTCHAs unter 0,5 Sekunden, Turnstile unter 10 Sekunden, reCAPTCHA v2 unter 60 Sekunden – jeweils mit hoher Erfolgsquote auf den unterstützten Typen. Ein Intervall von 5 Sekunden und ein Timeout von 300 Sekunden decken die Spanne ab.
Warum PowerShell für CAPTCHA-Automatisierung passt
- Ohne Installation verfügbar – ab PowerShell 5.1 auf jedem Windows-System
Invoke-RestMethodspricht REST-APIs nativ an und wandelt JSON in Objekte um- Aufgabenplaner startet CAPTCHA-abhängige Skripte zeitgesteuert, ohne Zusatzdienst
- Pipeline-tauglich – das Token geht direkt an nachgelagerte Cmdlets weiter
- Plattformübergreifend – dieselben Skripte laufen mit PowerShell 7 auf Linux-Buildagenten
Bausteine: Aufgabe übermitteln
Die erste Funktion kapselt in.php. Schlüssel und json = 1 sind immer gleich, alles Typspezifische kommt als Hashtable dazu. Ein Status ungleich 1 wirft sofort eine Ausnahme.
function Submit-CaptchaTask {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[hashtable]$TaskParams
)
$body = @{
key = $ApiKey
json = 1
} + $TaskParams
$response = Invoke-RestMethod -Uri "https://ocr.captchaai.com/in.php" `
-Method Post `
-Body $body `
-ContentType "application/x-www-form-urlencoded"
if ($response.status -ne 1) {
throw "Submit failed: $($response.request)"
}
return $response.request
}
Ergebnis abfragen
Das Gegenstück fragt res.php ab, bis eine Lösung vorliegt oder die Frist abläuft. Wichtig ist die Reihenfolge: erst warten, dann abfragen – eine Anfrage direkt nach dem Übermitteln liefert ohnehin nur CAPCHA_NOT_READY.
function Get-CaptchaResult {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$TaskId,
[int]$MaxWaitSeconds = 300,
[int]$PollIntervalSeconds = 5
)
$deadline = (Get-Date).AddSeconds($MaxWaitSeconds)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Seconds $PollIntervalSeconds
$response = Invoke-RestMethod -Uri "https://ocr.captchaai.com/res.php" `
-Method Get `
-Body @{
key = $ApiKey
action = "get"
id = $TaskId
json = 1
}
if ($response.request -eq "CAPCHA_NOT_READY") {
Write-Verbose "Waiting for solution..."
continue
}
if ($response.status -ne 1) {
throw "Solve failed: $($response.request)"
}
return $response.request
}
throw "Timeout: CAPTCHA not solved within $MaxWaitSeconds seconds"
}
reCAPTCHA v2 in PowerShell lösen
Damit bleibt die eigentliche Lösefunktion kurz. Sie brauchen zwei Werte von der Zielseite: den Sitekey aus data-sitekey und die vollständige Seiten-URL. Beide gehen als googlekey und pageurl an die Methode userrecaptcha.
function Solve-RecaptchaV2 {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$SiteUrl,
[Parameter(Mandatory)]
[string]$SiteKey
)
Write-Host "Submitting reCAPTCHA v2 task..."
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "userrecaptcha"
googlekey = $SiteKey
pageurl = $SiteUrl
}
Write-Host "Task ID: $taskId"
Write-Host "Polling for solution..."
$token = Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
Write-Host "Solved! Token: $($token.Substring(0, [Math]::Min(50, $token.Length)))..."
return $token
}
# Usage
$apiKey = "YOUR_API_KEY"
$token = Solve-RecaptchaV2 `
-ApiKey $apiKey `
-SiteUrl "https://example.com/login" `
-SiteKey "6Le-wvkSAAAAAPBMRTvw0Q4Muexq9bi0DJwx_mJ-"
Cloudflare Turnstile lösen
Für Turnstile ändern sich nur Methode und Parametername. Das Ergebnis landet im versteckten Feld cf-turnstile-response statt in g-recaptcha-response; Turnstile ist meist in unter 10 Sekunden gelöst – deutlich schneller als reCAPTCHA v2.
function Solve-Turnstile {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$SiteUrl,
[Parameter(Mandatory)]
[string]$SiteKey
)
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "turnstile"
key = $SiteKey
pageurl = $SiteUrl
}
return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}
# Usage
$token = Solve-Turnstile `
-ApiKey "YOUR_API_KEY" `
-SiteUrl "https://example.com/form" `
-SiteKey "0x4AAAAAAAB5..."
reCAPTCHA v3: Version und Action mitgeben
reCAPTCHA v3 zeigt nichts an, sondern bewertet im Hintergrund. Dafür kommen zwei Parameter dazu: version = "v3" und die action aus dem grecaptcha.execute-Aufruf im Quelltext. Beide müssen zur Zielseite passen – ein Token mit falscher Action wird serverseitig anders bewertet.
function Solve-RecaptchaV3 {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$SiteUrl,
[Parameter(Mandatory)]
[string]$SiteKey,
[string]$Action = "verify",
)
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "userrecaptcha"
googlekey = $SiteKey
pageurl = $SiteUrl
version = "v3"
action = $Action
}
return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}
Bild-CAPTCHAs aus Datei und URL lösen
Bild-CAPTCHAs laufen über die Methode base64: Datei einlesen, umwandeln, als body übermitteln. Zurück kommt kein Token, sondern der erkannte Text – unter 0,5 Sekunden und damit auch für Massenläufe geeignet.
function Solve-ImageCaptcha {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$ImagePath
)
if (-not (Test-Path $ImagePath)) {
throw "Image file not found: $ImagePath"
}
$imageBytes = [System.IO.File]::ReadAllBytes($ImagePath)
$base64 = [Convert]::ToBase64String($imageBytes)
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "base64"
body = $base64
}
return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}
# Usage
$text = Solve-ImageCaptcha -ApiKey "YOUR_API_KEY" -ImagePath "C:\captcha.png"
Write-Host "CAPTCHA text: $text"
Direkt von einer URL
Liegt das Bild nicht auf der Platte, holt Invoke-WebRequest die Bytes direkt – die Zwischendatei entfällt.
function Solve-ImageCaptchaFromUrl {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[string]$ImageUrl
)
$imageBytes = (Invoke-WebRequest -Uri $ImageUrl).Content
$base64 = [Convert]::ToBase64String($imageBytes)
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams @{
method = "base64"
body = $base64
}
return Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
}
Ein wiederverwendbares Modul statt Copy-Paste
Sobald mehrere Skripte auf einem Server CAPTCHAs lösen, gehört die Logik in ein Modul. Die folgende Klasse bündelt Übermittlung, Polling und Guthabenabfrage; Intervall und Timeout sind Eigenschaften. Speichern Sie die Datei als CaptchaAI.psm1 in einem versionierten Verzeichnis, das GitLab CI oder Ansible verteilt.
class CaptchaAISolver {
[string]$ApiKey
[string]$BaseUrl = "https://ocr.captchaai.com"
[int]$PollInterval = 5
[int]$MaxWait = 300
CaptchaAISolver([string]$apiKey) {
$this.ApiKey = $apiKey
}
[string] SolveRecaptchaV2([string]$siteUrl, [string]$siteKey) {
return $this.Solve(@{
method = "userrecaptcha"
googlekey = $siteKey
pageurl = $siteUrl
})
}
[string] SolveTurnstile([string]$siteUrl, [string]$siteKey) {
return $this.Solve(@{
method = "turnstile"
key = $siteKey
pageurl = $siteUrl
})
}
[string] SolveImage([string]$imagePath) {
$bytes = [System.IO.File]::ReadAllBytes($imagePath)
$base64 = [Convert]::ToBase64String($bytes)
return $this.Solve(@{
method = "base64"
body = $base64
})
}
[double] GetBalance() {
$response = Invoke-RestMethod -Uri "$($this.BaseUrl)/res.php" `
-Body @{ key = $this.ApiKey; action = "getbalance"; json = 1 }
return [double]$response.request
}
hidden [string] Solve([hashtable]$params) {
$taskId = $this.Submit($params)
return $this.Poll($taskId)
}
hidden [string] Submit([hashtable]$params) {
$body = @{ key = $this.ApiKey; json = 1 } + $params
$response = Invoke-RestMethod -Uri "$($this.BaseUrl)/in.php" `
-Method Post -Body $body
if ($response.status -ne 1) { throw "Submit: $($response.request)" }
return $response.request
}
hidden [string] Poll([string]$taskId) {
$deadline = (Get-Date).AddSeconds($this.MaxWait)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Seconds $this.PollInterval
$response = Invoke-RestMethod -Uri "$($this.BaseUrl)/res.php" `
-Body @{ key = $this.ApiKey; action = "get"; id = $taskId; json = 1 }
if ($response.request -eq "CAPCHA_NOT_READY") { continue }
if ($response.status -ne 1) { throw "Solve: $($response.request)" }
return $response.request
}
throw "Timeout"
}
}
# Export
Export-ModuleMember
Modul einbinden und Guthaben prüfen
using module lädt die Klasse, danach genügt eine Zeile pro Lösung. Die Guthabenabfrage gehört an den Anfang jedes geplanten Laufs – ein nachts an ERROR_ZERO_BALANCE gescheitertes Skript fällt sonst erst morgens auf.
using module .\CaptchaAI.psm1
$solver = [CaptchaAISolver]::new("YOUR_API_KEY")
# Check balance
$balance = $solver.GetBalance()
Write-Host "Balance: `$$balance"
# Solve reCAPTCHA v2
$token = $solver.SolveRecaptchaV2("https://example.com/login", "SITEKEY")
Write-Host "Token: $($token.Substring(0, 50))..."
Token in das Formular eintragen und absenden
Das Token ist nur ein weiterer Formularwert: bei reCAPTCHA heißt das Feld g-recaptcha-response, bei Turnstile cf-turnstile-response. Zwei Punkte entscheiden über den Erfolg – lösen Sie unmittelbar vor dem Absenden, denn Tokens gelten nur rund zwei Minuten, und verwenden Sie jedes Token genau einmal.
function Submit-FormWithToken {
param(
[string]$Url,
[string]$Token,
[hashtable]$FormData
)
$body = $FormData + @{
"g-recaptcha-response" = $Token
}
$response = Invoke-WebRequest -Uri $Url `
-Method Post `
-Body $body `
-ContentType "application/x-www-form-urlencoded"
return $response
}
# Usage
$token = Solve-RecaptchaV2 -ApiKey "YOUR_API_KEY" `
-SiteUrl "https://example.com/login" `
-SiteKey "SITEKEY"
$result = Submit-FormWithToken `
-Url "https://example.com/login" `
-Token $token `
-FormData @{
username = "[email protected]"
password = "password"
}
Write-Host "Response: $($result.StatusCode)"
Mehrere CAPTCHAs parallel lösen – und was Ihr Tarif hergibt
Ein Polling besteht überwiegend aus Warten, deshalb skaliert der Durchsatz fast linear mit der Parallelität. Start-Job startet je Zielseite einen Prozess, Wait-Job und Receive-Job sammeln die Ergebnisse ein.
Die Obergrenze setzt nicht PowerShell, sondern Ihr Tarif. CaptchaAI rechnet Thread-basiert ab, nicht pro Lösung: BASIC 15 $/Monat mit 5 Threads, ADVANCE 90 $/Monat mit 50 Threads, ENTERPRISE 300 $/Monat mit 200 Threads – jeweils mit unbegrenzten Lösungen je Thread. Ein Thread ist eine laufende Abfrage; überzählige Jobs quittiert die API mit ERROR_NO_SLOT_AVAILABLE. Preise in US-Dollar.
$apiKey = "YOUR_API_KEY"
$tasks = @(
@{ Url = "https://site-a.com"; Key = "SITEKEY_A" },
@{ Url = "https://site-b.com"; Key = "SITEKEY_B" },
@{ Url = "https://site-c.com"; Key = "SITEKEY_C" }
)
$jobs = $tasks | ForEach-Object {
$task = $_
Start-Job -ScriptBlock {
param($ApiKey, $Url, $SiteKey)
$taskId = (Invoke-RestMethod -Uri "https://ocr.captchaai.com/in.php" -Method Post -Body @{
key = $ApiKey; json = 1; method = "userrecaptcha"
googlekey = $SiteKey; pageurl = $Url
}).request
$deadline = (Get-Date).AddSeconds(300)
while ((Get-Date) -lt $deadline) {
Start-Sleep -Seconds 5
$result = Invoke-RestMethod -Uri "https://ocr.captchaai.com/res.php" -Body @{
key = $ApiKey; action = "get"; id = $taskId; json = 1
}
if ($result.request -ne "CAPCHA_NOT_READY" -and $result.status -eq 1) {
return @{ Url = $Url; Token = $result.request }
}
}
return @{ Url = $Url; Error = "Timeout" }
} -ArgumentList $apiKey, $task.Url, $task.Key
}
# Wait and collect results
$results = $jobs | Wait-Job | Receive-Job
$results | ForEach-Object {
if ($_.Token) {
Write-Host "$($_.Url): $($_.Token.Substring(0, 50))..."
} else {
Write-Host "$($_.Url): $($_.Error)" -ForegroundColor Red
}
}
$jobs | Remove-Job
Wiederholungslogik für den unbeaufsichtigten Betrieb
Nicht jeder Fehler verdient einen zweiten Versuch. ERROR_NO_SLOT_AVAILABLE und ERROR_CAPTCHA_UNSOLVABLE sind vorübergehend – hier hilft exponentielles Backoff mit Zufallsanteil, damit mehrere Skripte nicht im Gleichtakt anfragen. ERROR_WRONG_USER_KEY und ERROR_ZERO_BALANCE sollten dagegen sofort durchschlagen.
function Solve-WithRetry {
param(
[Parameter(Mandatory)]
[string]$ApiKey,
[Parameter(Mandatory)]
[hashtable]$TaskParams,
[int]$MaxRetries = 3
)
$retryableErrors = @(
"ERROR_NO_SLOT_AVAILABLE",
"ERROR_CAPTCHA_UNSOLVABLE"
)
for ($attempt = 0; $attempt -le $MaxRetries; $attempt++) {
if ($attempt -gt 0) {
$delay = [Math]::Pow(2, $attempt) + (Get-Random -Maximum 3)
Write-Host "Retry $attempt/$MaxRetries after $($delay)s..."
Start-Sleep -Seconds $delay
}
try {
$taskId = Submit-CaptchaTask -ApiKey $ApiKey -TaskParams $TaskParams
$result = Get-CaptchaResult -ApiKey $ApiKey -TaskId $taskId
return $result
}
catch {
$errorMsg = $_.Exception.Message
$isRetryable = $retryableErrors | Where-Object { $errorMsg -like "*$_*" }
if (-not $isRetryable -or $attempt -eq $MaxRetries) {
throw
}
Write-Warning "Retryable error: $errorMsg"
}
}
}
Im Aufgabenplaner produktiv betreiben
Ein Beispiel aus dem DACH-Alltag: Ein Shop-Team betreibt seine Shopware-Staging-Instanz auf einem Windows-Server bei Hetzner oder IONOS. Nachts prüft ein PowerShell-Skript Login und Bestellstrecke der eigenen Testumgebung; seit das Formular durch Turnstile geschützt ist, löst dasselbe Skript die Abfrage über die API.
Für den Dauerbetrieb haben sich drei Punkte bewährt:
- Schlüssel nicht im Skript. API-Schlüssel als Umgebungsvariable des Dienstkontos oder in
Microsoft.PowerShell.SecretManagementablegen. - Ausführungskontext klären. Aufgabe mit eigenem Dienstkonto und der Option „Unabhängig von der Benutzeranmeldung ausführen“ registrieren.
- Datensparsam protokollieren. Tokens und Anmeldedaten gehören nicht in Klartext-Logs; wer fremde Systeme abfragt, klärt Rechtsgrundlage und Umgang mit IP-Adressen nach DSGVO vorab.
# Create a scheduled task that runs CAPTCHA automation daily
$action = New-ScheduledTaskAction `
-Execute "powershell.exe" `
-Argument "-ExecutionPolicy Bypass -File C:\Scripts\captcha-automation.ps1"
$trigger = New-ScheduledTaskTrigger -Daily -At "08:00"
Register-ScheduledTask `
-TaskName "CaptchaAutomation" `
-Action $action `
-Trigger $trigger `
-Description "Run daily CAPTCHA automation mit CaptchaAI"
Typische Fehler und ihre Ursache
| Meldung | Ursache | Lösung |
|---|---|---|
ERROR_WRONG_USER_KEY |
Schlüssel falsch kopiert | Schlüssel im Dashboard prüfen |
ERROR_ZERO_BALANCE |
Guthaben aufgebraucht | Konto aufladen, Guthabenabfrage an den Skriptanfang |
ERROR_NO_SLOT_AVAILABLE |
Mehr Jobs als Threads | Parallelität an den Tarif anpassen |
Invoke-RestMethod: SSL/TLS |
5.1 handelt noch TLS 1.0 aus | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 setzen |
The response content cannot be parsed |
Antwort ist kein JSON | Mit Invoke-WebRequest den Rohtext auswerten |
Execution policy-Fehler |
Ausführungsrichtlinie blockiert | Set-ExecutionPolicy -Scope CurrentUser RemoteSigned |
| Token wird abgelehnt | Abgelaufen oder pageurl weicht ab |
Kurz vor dem Absenden lösen, URL exakt übergeben |
Häufige Fragen
Wie viele CAPTCHAs kann ich gleichzeitig lösen?
So viele, wie Ihr Tarif Threads umfasst – von 5 bei BASIC bis 5.000 bei VIP-3. Mehr Start-Job-Instanzen als Threads bringen keinen Durchsatz.
Wie lange ist ein gelöstes Token gültig?
Etwa zwei Minuten. Lösen Sie deshalb erst, wenn das Formular fertig befüllt ist, und senden Sie unmittelbar danach ab.
Welche CAPTCHA-Typen deckt die API ab?
reCAPTCHA v2 und v3 (inklusive Invisible und Enterprise), Cloudflare Turnstile und Challenge, GeeTest v3, Bild- und Rasterbild-CAPTCHAs sowie BLS; CaptchaFox, Friendly Captcha und Lemin in der Beta. hCaptcha und FunCaptcha werden nicht unterstützt, GeeTest v4 gilt als „bald verfügbar“.
Wie hinterlege ich den API-Schlüssel in einer geplanten Aufgabe?
Über Microsoft.PowerShell.SecretManagement oder eine Umgebungsvariable des ausführenden Dienstkontos. So steht der Schlüssel weder im Skript noch in der Aufgabendefinition und lässt sich rotieren, ohne den Code anzufassen.
Warum meldet Windows PowerShell 5.1 einen TLS-Fehler?
Weil 5.1 ältere Protokollversionen aushandelt. Setzen Sie [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 an den Skriptanfang; in PowerShell 7 entfällt das.
Verwandte Leitfäden
Automatisieren Sie CAPTCHA-Abfragen direkt aus Ihren Windows-Skripten – mit CaptchaAI.