<# .SYNOPSIS Register a Windows scheduled task that runs `ai-chat-exporter sync` daily. .DESCRIPTION The Windows counterpart to install-systemd-timer.sh. Creates a per-user task (no admin rights needed) that runs the exporter from this repository, with the working directory set to the repo so .env, cache\ and exports\ resolve exactly as they do for an interactive run. -Provider is repeatable. The CLI takes one provider per run, so each becomes its own action, executed in order. List only the providers that work unattended on this machine: a web provider whose session token has expired fails the task every day, which trains you to ignore the failures you actually want to notice. .EXAMPLE .\scheduling\Register-AiChatSyncTask.ps1 -Provider chatgpt,claude .EXAMPLE .\scheduling\Register-AiChatSyncTask.ps1 -Provider all -Time 21:30 .EXAMPLE .\scheduling\Register-AiChatSyncTask.ps1 -Unregister #> [CmdletBinding()] param( [string[]]$Provider = @('all'), [string]$Time = '09:00', [string]$TaskName = 'AiChatExporterSync', [switch]$Unregister ) $ErrorActionPreference = 'Stop' $repo = Split-Path -Parent $PSScriptRoot $launcher = Join-Path $repo 'ai-chat-exporter.cmd' if ($Unregister) { Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false -ErrorAction SilentlyContinue Write-Host "Removed scheduled task '$TaskName'." return } if (-not (Test-Path $launcher)) { throw "Launcher not found at $launcher" } # A single action looping over the providers, rather than one action each. # Task Scheduler runs multiple actions in order but reports only the last one's # result, so a failure in an earlier provider would be invisible. The loop keeps # going after a failure and propagates a non-zero exit code. # # /v:on and !RC! are required, not stylistic: cmd expands every %VAR% on a # command line *before* running any of it, so "exit /b %RC%" would report the # value RC had before the loop ever ran - i.e. always success. Delayed expansion # reads it at the point of use. $loop = ($Provider | ForEach-Object { "`"$launcher`" sync --provider $_ --joplin-optional || set RC=1" }) -join ' & ' $taskArgs = "/v:on /c set RC=0 & $loop & exit /b !RC!" $actions = New-ScheduledTaskAction -Execute 'cmd.exe' ` -Argument $taskArgs ` -WorkingDirectory $repo $trigger = New-ScheduledTaskTrigger -Daily -At $Time # StartWhenAvailable is the counterpart of systemd's Persistent=true: a machine # that was asleep at the scheduled time runs the archive when it wakes, rather # than skipping the day entirely. $settings = New-ScheduledTaskSettingsSet ` -StartWhenAvailable ` -DontStopIfGoingOnBatteries ` -AllowStartIfOnBatteries ` -ExecutionTimeLimit (New-TimeSpan -Hours 2) Register-ScheduledTask -TaskName $TaskName ` -Action $actions ` -Trigger $trigger ` -Settings $settings ` -Description 'Export AI chat history and sync it to Joplin' ` -Force | Out-Null Write-Host "Registered '$TaskName' - daily at $Time for: $($Provider -join ', ')" Write-Host '' Write-Host 'Command the task will run:' Write-Host " cmd.exe $taskArgs" Write-Host " (working directory: $repo)" Write-Host '' Write-Host 'Next steps:' Write-Host " * Run it once now: Start-ScheduledTask -TaskName $TaskName" Write-Host " * Check the result: Get-ScheduledTaskInfo -TaskName $TaskName" Write-Host " * Read the log: Get-Content '$repo\cache\logs\exporter.log' -Tail 50" Write-Host ' * The terms-of-service notice must have been acknowledged' Write-Host ' interactively once on this machine, or the task exits 1.'