public/Invoke-OctaScheduler.ps1

function Invoke-OctaScheduler {
    <#
        .SYNOPSIS
        Creates/lists/removes a Windows Task Scheduler entry that runs an Octa category or
        Quick Clean non-interactively on a schedule. 007 US5. Every scheduled run invokes the
        exported cmdlets directly (never Invoke-Octa/the interactive menu), consistent with the
        non-interactive guard already in Show-OctaMenu. Runs elevated (RunLevel Highest) always,
        disclosed here before creating the task, since most categories require it anyway and a
        scheduled run has no one present to grant elevation on demand.
    #>

    [CmdletBinding()]
    param(
        [switch]$Create,
        [string]$Category,
        [ValidateSet('Daily', 'Weekly', 'Monthly')]
        [string]$Frequency,
        [string]$Remove
    )

    $taskPath = '\Octa\'

    if ($Remove) {
        $existing = Get-ScheduledTask -TaskPath $taskPath -TaskName $Remove -ErrorAction SilentlyContinue
        if (-not $existing) {
            return [pscustomobject]@{ Status = 'NotFound'; Message = "No scheduled Octa run named: $Remove" }
        }
        Unregister-ScheduledTask -TaskPath $taskPath -TaskName $Remove -Confirm:$false
        return [pscustomobject]@{ Status = 'Success'; Message = "Removed scheduled run: $Remove" }
    }

    if ($Create) {
        if (-not $Category -or -not $Frequency) {
            return [pscustomobject]@{ Status = 'Error'; Message = 'Both -Category and -Frequency are required to create a scheduled run.' }
        }

        # ponytail: validate against the real catalog before registering anything. Without this,
        # an unknown/misspelled category happily created a scheduled task that could only ever
        # fail, silently, forever - the user would never see the failure because the task runs
        # hidden and non-interactively. It also keeps $Category (which gets interpolated into the
        # -Command string below) constrained to known-good ids instead of arbitrary text.
        $validCategories = @(Get-OctaCategory -SkipRiskScan | Select-Object -ExpandProperty Id) + 'quick-clean'
        if ($Category -notin $validCategories) {
            return [pscustomobject]@{
                Status  = 'UnknownCategory'
                Message = "Unknown category: $Category. Valid values: $($validCategories -join ', ')"
            }
        }

        $cmdletCall = if ($Category -eq 'quick-clean') {
            'Invoke-OctaQuickClean -Apply -Yes -Quiet'
        }
        else {
            "Invoke-OctaCategory -CategoryId '$Category' -Apply -Yes -Quiet"
        }
        $command = "Import-Module Octa -Force; $cmdletCall"

        Write-Host 'This scheduled task will run elevated (Administrator), since most categories require it.'

        $taskName = "Octa-$Category-$Frequency-$([guid]::NewGuid().ToString('N').Substring(0, 8))"
        $arguments = "-NoProfile -WindowStyle Hidden -Command `"$command`""
        $startBoundary = (Get-Date -Hour 3 -Minute 0 -Second 0).ToString('yyyy-MM-ddTHH:mm:ss')

        try {
            if ($Frequency -eq 'Monthly') {
                # ponytail: registered from task XML, not from an MSFT_TaskMonthlyTrigger CIM
                # instance. New-ScheduledTaskTrigger genuinely has no -Monthly parameter set
                # (only Once/Daily/Weekly/Startup/Logon exist in PS 5.1), but the CIM route that
                # replaced it never actually worked: Register-ScheduledTask rejected every single
                # variant with "El parametro no es correcto" (E_INVALIDARG) - plain assignment,
                # explicit [uint16] casts (the class really does declare DaysOfMonth/MonthOfYear
                # as UInt16), RunOnLastDayOfMonth set, timezone-qualified StartBoundary, and
                # New-ScheduledTask + -InputObject. All five failed 100% of the time, which is
                # why the "flaky" Monthly test was never flaky at all - it was reporting a real,
                # permanent break. Registering the same schedule as task XML works, and is still
                # a genuine calendar-monthly trigger (day 1 of all 12 months), not a drifting
                # "every 4 weeks" approximation.
                $escapedArguments = [System.Security.SecurityElement]::Escape($arguments)
                $escapedUser = [System.Security.SecurityElement]::Escape($env:USERNAME)
                $xml = @"
<?xml version="1.0" encoding="UTF-16"?>
<Task version="1.2" xmlns="http://schemas.microsoft.com/windows/2004/02/mit/task">
  <Triggers>
    <CalendarTrigger>
      <StartBoundary>$startBoundary</StartBoundary>
      <Enabled>true</Enabled>
      <ScheduleByMonth>
        <DaysOfMonth><Day>1</Day></DaysOfMonth>
        <Months><January/><February/><March/><April/><May/><June/><July/><August/><September/><October/><November/><December/></Months>
      </ScheduleByMonth>
    </CalendarTrigger>
  </Triggers>
  <Principals>
    <Principal id="Author">
      <UserId>$escapedUser</UserId>
      <LogonType>S4U</LogonType>
      <RunLevel>HighestAvailable</RunLevel>
    </Principal>
  </Principals>
  <Settings>
    <MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>
    <Enabled>true</Enabled>
    <StartWhenAvailable>true</StartWhenAvailable>
  </Settings>
  <Actions Context="Author">
    <Exec>
      <Command>powershell.exe</Command>
      <Arguments>$escapedArguments</Arguments>
    </Exec>
  </Actions>
</Task>
"@

                Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Xml $xml -ErrorAction Stop | Out-Null
            }
            else {
                $action = New-ScheduledTaskAction -Execute 'powershell.exe' -Argument $arguments
                $trigger = switch ($Frequency) {
                    'Daily' { New-ScheduledTaskTrigger -Daily -At 3am }
                    'Weekly' { New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday -At 3am }
                }
                $principal = New-ScheduledTaskPrincipal -UserId $env:USERNAME -LogonType S4U -RunLevel Highest
                Register-ScheduledTask -TaskName $taskName -TaskPath $taskPath -Action $action -Trigger $trigger -Principal $principal -ErrorAction Stop | Out-Null
            }
        }
        catch {
            # ponytail: -ErrorAction Stop + this catch, because Register-ScheduledTask only raises
            # a NON-terminating error on failure - execution used to continue straight to the
            # "Success" return below, so a task that was never created was reported as created
            # (exactly what the Monthly break looked like from the outside: Status = 'Success'
            # with a TaskName that could not be found or removed afterwards).
            return [pscustomobject]@{ Status = 'Error'; Message = "Could not register the scheduled task: $($_.Exception.Message)" }
        }

        return [pscustomobject]@{ Status = 'Success'; TaskName = $taskName }
    }

    $tasks = @(Get-ScheduledTask -TaskPath $taskPath -ErrorAction SilentlyContinue)
    foreach ($t in $tasks) {
        Write-Host ("{0,-10} {1}" -f $t.State, $t.TaskName)
    }
    return $tasks
}