Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions config/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ type ScheduleBaseSection struct {
ScheduleIgnoreOnBattery maybe.Bool `mapstructure:"schedule-ignore-on-battery" show:"noshow" default:"false" description:"Don't start this schedule when running on battery"`
ScheduleIgnoreOnBatteryLessThan int `mapstructure:"schedule-ignore-on-battery-less-than" show:"noshow" default:"" examples:"20;33;50;75" description:"Don't start this schedule when running on battery and the state of charge is less than this percentage"`
ScheduleAfterNetworkOnline maybe.Bool `mapstructure:"schedule-after-network-online" show:"noshow" description:"Don't start this schedule when the network is offline (supported in \"systemd\")"`
ScheduleSchtasksHideWindow maybe.Bool `mapstructure:"schedule-schtasks-hide-window" show:"noshow" default:"false" description:"Hide schedule window when running in foreground (\"schtasks\" only)"`
Comment thread
zumm marked this conversation as resolved.
Outdated
}

func (s *ScheduleBaseSection) setRootPath(_ *Profile, _ string) {
Expand Down
5 changes: 5 additions & 0 deletions config/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type ScheduleBaseConfig struct {
IgnoreOnBatteryLessThan int `mapstructure:"ignore-on-battery-less-than" default:"" examples:"20;33;50;75" description:"Don't start this schedule when running on battery and the state of charge is less than this percentage"`
AfterNetworkOnline maybe.Bool `mapstructure:"after-network-online" description:"Don't start this schedule when the network is offline (supported in \"systemd\")"`
SystemdDropInFiles []string `mapstructure:"systemd-drop-in-files" default:"" description:"Files containing systemd drop-in (override) files - see https://creativeprojects.github.io/resticprofile/schedules/systemd/"`
SchtasksHideWindow maybe.Bool `mapstructure:"schtasks-hide-window" default:"false" description:"Hide schedule window when running in foreground (\"schtasks\" only)"`
Comment thread
zumm marked this conversation as resolved.
Outdated
}

// scheduleBaseConfigDefaults declares built-in scheduling defaults
Expand Down Expand Up @@ -91,6 +92,9 @@ func (s *ScheduleBaseConfig) init(defaults *ScheduleBaseConfig) {
if s.SystemdDropInFiles == nil {
s.SystemdDropInFiles = slices.Clone(defaults.SystemdDropInFiles)
}
if !s.SchtasksHideWindow.HasValue() {
s.SchtasksHideWindow = defaults.SchtasksHideWindow
}
}

func (s *ScheduleBaseConfig) applyOverrides(section *ScheduleBaseSection) {
Expand All @@ -105,6 +109,7 @@ func (s *ScheduleBaseConfig) applyOverrides(section *ScheduleBaseSection) {
s.EnvCapture = slices.Clone(section.ScheduleEnvCapture)
s.IgnoreOnBattery = section.ScheduleIgnoreOnBattery
s.AfterNetworkOnline = section.ScheduleAfterNetworkOnline
s.SchtasksHideWindow = section.ScheduleSchtasksHideWindow
// re-init with defaults
s.init(&defaults)
}
Expand Down
7 changes: 7 additions & 0 deletions docs/content/schedules/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,13 @@ If set to `true`, the schedule won't start if the system is running on battery (

If set to a number, the schedule won't start if the system is running on battery and the charge is less than or equal to the specified number.

## schedule-schtasks-hide-window

When `schedule-permission` is set to `user_logged_on`, Windows Task Scheduler runs tasks in the foreground.
This behavior may interrupt the user's activity and is often undesirable.

To prevent that, set this option to `true` to hide the task window by wrapping the execution in `conhost.exe --headless`.

## Example

Here's an example of a scheduling configuration:
Expand Down
1 change: 1 addition & 0 deletions schedule/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type Config struct {
Flags map[string]string // flags added to the command line
AfterNetworkOnline bool
SystemdDropInFiles []string
SchtasksHideWindow bool
removeOnly bool
}

Expand Down
1 change: 1 addition & 0 deletions schedule/handler_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func (h *HandlerWindows) CreateJob(job *Config, schedules []*calendar.Event, per
Arguments: job.Arguments.String(),
WorkingDirectory: job.WorkingDirectory,
JobDescription: job.JobDescription,
HideWindow: job.SchtasksHideWindow,
}
err := schtasks.Create(jobConfig, schedules, perm)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions schedule_jobs.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,5 +237,6 @@ func scheduleToConfig(sched *config.Schedule) *schedule.Config {
Flags: sched.Flags,
AfterNetworkOnline: sched.AfterNetworkOnline.IsTrue(),
SystemdDropInFiles: sched.SystemdDropInFiles,
SchtasksHideWindow: sched.SchtasksHideWindow.IsTrue(),
}
}
1 change: 1 addition & 0 deletions schtasks/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ type Config struct {
Arguments string
WorkingDirectory string
JobDescription string
HideWindow bool
}
19 changes: 19 additions & 0 deletions schtasks/taskscheduler.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,25 @@ func Create(config *Config, schedules []*calendar.Event, permission Permission)
return fmt.Errorf("cannot delete existing task to replace it: %w", err)
}
}

if config.HideWindow {
if permission != UserLoggedOnAccount {
clog.Warning("hiding window makes sense only with \"user_logged_on\" permission")
}

arguments := fmt.Sprintf(
"--headless '%s' %s",
config.Command,
config.Arguments,
)

config.Command = "conhost.exe"
config.Arguments = arguments
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
// reset the HideWindow flag to prevent duplicate command wrapping
// if this config object is reused
config.HideWindow = false
}

task := createTaskDefinition(config, schedules)
task.RegistrationInfo.URI = taskPath

Expand Down
40 changes: 40 additions & 0 deletions schtasks/taskscheduler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,46 @@ func TestCanCreateTwice(t *testing.T) {
assert.NoError(t, err)
}

func TestHideWindowOption(t *testing.T) {
task := Config{
ProfileName: "TestHideWindowOption",
CommandName: "backup",
Command: "echo",
Arguments: "hello there",
WorkingDirectory: "C:\\",
JobDescription: "TestHideWindowOption",
HideWindow: true,
}

event := calendar.NewEvent()
err := event.Parse("2020-01-02 03:04") // will never get triggered
require.NoError(t, err)

err = Create(&task, []*calendar.Event{event}, UserLoggedOnAccount)
assert.NoError(t, err)

defer func() {
_ = Delete(task.ProfileName, task.CommandName)
}()

registeredTasks, err := Registered()
assert.NoError(t, err)

found := false
for _, registeredTask := range registeredTasks {
if registeredTask.ProfileName == "TestHideWindowOption" {
found = true

assert.Equal(t, registeredTask.Command, "conhost.exe")
assert.Equal(t, registeredTask.Arguments, "--headless 'echo' hello there")

break
}
}

assert.Equal(t, found, true)
}

func TestTaskSchedulerIntegration(t *testing.T) {
// some tests are using the 1st day of the month as a reference,
// but this cause issues when we're running the tests on the first day of the month.
Expand Down
Loading