Task Scheduler alternatives?

After fighting with the Windows 10 Task Scheduler to run a Python script automatically and reading that this is a common issue, I'm ready to give up and adopt a third-party scheduler.

Given that the requirements are quite basic (run Python with one argument), is there an inexpensive software some of you use that you'd be happy to recommend?

Thanks in advance! :smiley:

1 Like

Just out of curiosity, are you trying to run your Python script before the user has logged on, or upon the OS shutdown? I had problems with that too (found some solutions though).

No special conditions, running while I'm logged on, every two hours.
Just tried and discarded:

  • z-cron
  • Task Till Dawn

Will now try Advanced Task Scheduler

Does it always fail? Even if you trigger the task manually? Perhaps you just need to set the working directory [Start in (optional) field] in the scheduled task to the path of the script so that the script's CWD is correct (if it loads or writes into its directory or relies on relative paths).

Anyway, let us know once you find a good third party Task Scheduler, the Windows' one is indeed often inconsistent in its operation.

Thank you very much for your suggestions.

Yes, it works perfectly when run manually.
I played with all the options and experimented with many things. Apparently lots of people can't get the scheduler to work. The task shows it will run at 14:20 (for instance) but it never runs.

Advanced Task Scheduler worked, ended up buying it to have a working solution.
Also tried visualcron but didn't understand the interface, apart from the price tag way out of my league.

If you're logged on anyway, why don't you use a simple Autohotkey script which starts at logon? I have an AHK script which among other things closes some hidden Windows apps in background every 15 seconds, detects resolution changes after max 30 secs, etc. Never had a problem.

If you check AHK documentation example #4, Tick() should do exactly what you need. In fact you can easily convert it to as a general purpose scheduler. I can post mine if you're interested.

@cylimaz - I'm interested in your script. Thanks!

@Chuck
It's a small wrapper/library and assumes you know some OO-type AHK. Put it into your Lib directory. Usage example is below.
The $log, $dump, etc. are for my logger, you can safely delete them.

#Warn All
#Warn UseUnsetLocal, Off
#Warn LocalSameAsGlobal, Off

; #Include <cCuLogger>

class cScheduler {
    /*
        Helper class which automatically
        - starts a timer at given frequency
        - calls given callback functions for the time points 'Start', 'Update', 'Stop'
        - calls 'ValidCondition' callback to determine whether the timer should continue or not
        - IMPORTANT: if an exit_after (max runtime) is specified, the caller script's 'fn_stop()' will be called and script will exit via ExitApp!

        It also has an ExitHandler() method which can be bound at the top of any script as
        OnExit(ObjBindMethod(cScheduler, "ExitHandler"))

        Sample call:
        this.server_scheduler := new cScheduler({ check_freq  : 1000
                                                , exit_after  : 5000
                                                , fn_start    : false
                                                , fn_update   : false
                                                , fn_is_valid : ObjBindMethod(cMovieManager.oServer, "IsIdleTooLong")
                                                , fn_stop     : ObjBindMethod(cMovieManager.oServer, "Cleanup")
                                                , fn_exit     : ObjBindMethod(cMovieManager.oServer, "ExitHandler")
                                                , auto_start  : true })
        OnExit(ObjBindMethod(this.server_scheduler, "ExitHandler"))

        This class relies on the fact that it will be included by other classes,
        thus A_ScriptHwnd will be set to the caller's handle.

    */
    ; __New(pInterval := 15000, pFNStart := false, pFNUpdate := false, pFNValidCondition := false, pFNStop := false, pAutoStart := true) {
    __New(pParamsObj) {
        ; $dbg(A_ThisFunc, "Starting Scheduler", "handle: " A_ScriptHwnd ", pid: " (this._GetPID()))
        this.timer := ObjBindMethod(this, "Tick")

        this.interval       := pParamsObj.check_freq > 0        ? pParamsObj.check_freq  : 15000
        this.exit_after     := pParamsObj.exit_after > 0        ? pParamsObj.exit_after  : 0
        this.fn_start       := IsObject(pParamsObj.fn_start)    ? pParamsObj.fn_start    : false
        this.fn_update      := IsObject(pParamsObj.fn_update)   ? pParamsObj.fn_update   : false
        this.fn_is_valid    := IsObject(pParamsObj.fn_is_valid) ? pParamsObj.fn_is_valid : false
        this.fn_stop        := IsObject(pParamsObj.fn_stop)     ? pParamsObj.fn_stop     : false
        this.fn_exit        := IsObject(pParamsObj.fn_exit)     ? pParamsObj.fn_exit     : false
        this.auto_start     := pParamsObj.auto_start            ? true                   : false

        if (this.auto_start) {
            this.Start()
        }
    }
    _GetPID() {
        _prevDetectHiddenWindows := A_DetectHiddenWindows
        _prevTitleMatchMode      := A_TitleMatchMode
        DetectHiddenWindows On
        SetTitleMatchMode 2
        WinGet, _pid, PID, ahk_id %A_ScriptHwnd%
        DetectHiddenWindows %_prevDetectHiddenWindows%
        SetTitleMatchMode   %_prevTitleMatchMode%
        return _pid
    }
    Start() {
        ; $verbose(A_ThisFunc)
        this.cancelled      := false
        this.time_exceeded  := false
        this.start_ts       := A_TickCount
        timer               := this.timer
        if (IsObject(this.fn_start)) {
            this.fn_start.call()
        }
        SetTimer % timer, % this.interval
    }
    IsTimerValid() {
        ; $verbose(A_ThisFunc)
        if (IsObject(this.fn_is_valid) && !this.fn_is_valid.call()) {
            ; check for special key combos to abort the action, etc.
            ; $dbg(A_ThisFunc, "timer invalidated")
            this.cancelled := true
            return false
        }
        if (this.exit_after && (A_TickCount - this.start_ts) > this.exit_after) {
            ; check if max running time has been exceeded
            ; $dbg(A_ThisFunc, "timer exceeded max runtime: " this.exit_after)
            this.time_exceeded := true
            return false
        }
        return true
    }
    Update() {
        ; $verbose(A_ThisFunc)
        if (IsObject(this.fn_update)) {
            this.fn_update.call()
        }
    }
    Stop() {
        ; $verbose(A_ThisFunc)
        if (IsObject(this.fn_stop)) {
            this.fn_stop.call()
        }
    }
    Tick() {
        ; $verbose(A_ThisFunc)
        if(!this.IsTimerValid()) {
            this.Stop()
            timer := this.timer
            SetTimer % timer, Off
            ; note: unlike all other methods in this class, here we trigger ExitHandler() on behalf of the client
            if (this.time_exceeded) {
                this.ExitHandler()
            }
        } else {
            this.Update()
        }
    }
    SuspendHandler(pParams := false) {
        ; $verbose(A_ThisFunc)
        Suspend
    }
    ReloadHandler(pParams := false) {
        ; $verbose(A_ThisFunc)
        Reload
    }
    PauseHandler(pParams := false) {
        ; $verbose(A_ThisFunc)
        Pause
    }
    ExitHandler(pParams := false) {
        DetectHiddenWindows On
        SetTitleMatchMode 2
        WinGet, _pid, PID, ahk_id %A_ScriptHwnd%
        ; $dbg(A_ThisFunc, "Exiting Scheduler", "handle: " A_ScriptHwnd ", pid: " (this._GetPID()))
        if (pParams.Length()) {
            ; $dump(pParams)
        }
        if (IsObject(this.fn_exit)) {
            this.fn_exit.call()
        }
        ExitApp
    }
}

Great thanks!

1 Like

Fantastic idea. Should have thought of AHK, always the lifesaver. :slight_smile:
Even though I use ahk a lot on a daily basis for a variety of purposes recently I haven't touched the code so it's wasn't even on my radar!
Thank you! :pray: :smiley:

1 Like