Skip to content

feat(schedule): introduce schtasks-hide-window option #541

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

zumm
Copy link
Contributor

@zumm zumm commented Jul 29, 2025

Resolves #474

This pr introduces new schedule-schtasks-hide-window option. Option allows to hide task window by wrapping it in conhost --headless.

I chose name schedule-schtasks-hide-window over schedule-hidden bcz hidden can be confused with schtasks built-in "hidden" feature (which hides task in the task manager UI). schtasks prefix indicates that it works only with schtasks (like systemd in systemd-drop-in-files option), i'm not sure if it's needed tho.

I'm also not sure if schtasks/tasksheduler/Create good place for this logic.

I will update docs if everyting is ok.

Copy link

coderabbitai bot commented Jul 29, 2025

Walkthrough

A new configuration option to hide the backup window when running scheduled tasks via Windows Task Scheduler has been introduced. This includes configuration, documentation, and logic to wrap the scheduled command with conhost.exe --headless when the HideWindow flag is set, along with tests to verify the behaviour.

Changes

Cohort / File(s) Change Summary
Config Structs and Propagation
config/profile.go, config/schedule.go, schedule/config.go, schedule_jobs.go
Added HideWindow boolean fields to scheduling-related config structs and ensured propagation of this flag through the configuration pipeline. Updated the init and applyOverrides methods to handle the new field.
Windows Scheduler Handler Logic
schedule/handler_windows.go
Modified CreateJob to wrap the scheduled command in conhost.exe --headless if HideWindow is true and the permission is correct, altering the command and arguments used for the scheduled task. Added logging for unsupported permission scenarios.
Documentation
docs/content/schedules/configuration.md
Documented the new schedule-hide-window configuration option, its use case, and limitations (Windows Task Scheduler, user_logged_on permission only).
Testing
schedule/handler_windows_test.go
Added a test for the HideWindow option, verifying that the scheduled job is created with the correct command and arguments when the flag is set.
Whitespace Change
schtasks/taskscheduler.go
Added a blank line for readability after a task deletion block.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ConfigLoader
    participant ScheduleHandler
    participant WindowsHandler
    participant TaskScheduler

    User->>ConfigLoader: Loads config with schedule-hide-window=true
    ConfigLoader->>ScheduleHandler: Passes HideWindow flag
    ScheduleHandler->>WindowsHandler: Calls CreateJob with HideWindow=true
    WindowsHandler->>WindowsHandler: Wraps command in conhost.exe --headless
    WindowsHandler->>TaskScheduler: Schedules job with modified command
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15 minutes

Assessment against linked issues

Objective Addressed Explanation
Add a configuration option to hide the backup window for scheduled tasks on Windows 11 when running as non-admin user (#474)
Ensure scheduled command is wrapped in conhost.exe --headless when hide window is enabled (#474)
Document the new configuration option and its applicability/limitations (#474)
Provide automated test coverage for the hide window option (#474)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Whitespace addition after task deletion block (schtasks/taskscheduler.go) This is a non-functional change and not related to the objectives, but it does not affect behaviour.

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cedaf3d and e9a785c.

📒 Files selected for processing (1)
  • docs/content/schedules/configuration.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • docs/content/schedules/configuration.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Build and test (1.24, ubuntu-latest)
  • GitHub Check: Build and test (1.24, macos-latest)
  • GitHub Check: Build and test (1.24, windows-latest)
  • GitHub Check: build
  • GitHub Check: Analyze (go)
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 99e6d33 and 87c5a6d.

📒 Files selected for processing (7)
  • config/profile.go (1 hunks)
  • config/schedule.go (3 hunks)
  • schedule/config.go (1 hunks)
  • schedule/handler_windows.go (1 hunks)
  • schedule_jobs.go (1 hunks)
  • schtasks/config.go (1 hunks)
  • schtasks/taskscheduler.go (1 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: creativeprojects
PR: creativeprojects/resticprofile#459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the `schtasks` package, `tasksPath` is defined as a constant with value `\resticprofile backup\` in `taskscheduler.go`. It's used as a prefix for managing task paths in the Windows Task Scheduler.
schedule/handler_windows.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

config/profile.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

config/schedule.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

schedule_jobs.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

🔇 Additional comments (7)
schtasks/config.go (1)

12-12: LGTM!

The addition of the HideWindow boolean field to the Config struct is clean and follows the existing naming conventions.

schedule/config.go (1)

26-26: LGTM!

The SchtasksHideWindow field is appropriately named to indicate its specific application to the schtasks scheduler, avoiding confusion with other hiding mechanisms.

schedule_jobs.go (1)

240-240: LGTM!

The field assignment correctly propagates the configuration value using the IsTrue() method, consistent with the pattern used for other optional boolean fields in the same struct.

schedule/handler_windows.go (1)

65-65: LGTM!

The field assignment correctly propagates the SchtasksHideWindow configuration from the schedule config to the schtasks config, maintaining the configuration flow through the scheduling layers.

config/schedule.go (3)

48-48: LGTM! Field declaration is properly structured.

The field follows the established pattern with correct mapstructure tag, default value, and description.


95-97: LGTM! Initialization logic is consistent.

The initialization logic properly follows the pattern used by other maybe.Bool fields in the struct, checking if the value has been set and applying defaults when needed.


112-112: LGTM! Override logic correctly implemented.

The field assignment in applyOverrides follows the established pattern and correctly maps from the corresponding field in ScheduleBaseSection.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
docs/content/schedules/configuration.md (1)

185-191: Clarify scope, default, and OS prerequisites of the new flag

The paragraph does not mention (a) that the flag defaults to false, (b) that it has no effect under schedule-permission ≠ user_logged_on, and (c) that conhost.exe --headless is available only on recent Windows versions (10 / Server 2019+). Stating these limitations up-front helps users avoid silent misconfiguration.

Suggested tweak:

@@
-To prevent that, set this option to `true` to hide the task window by wrapping the execution in `conhost.exe --headless`.
+Set this option to `true` to hide the task window by wrapping the execution in `conhost.exe --headless`.  
+This flag is honoured only when `schedule-permission` is `user_logged_on`; it is ignored (with a warning) for `system` or `user`.  
+`conhost.exe --headless` is supported starting with Windows 10 / Windows Server 2019 – on older versions the task will fail to start.  
+Default: `false`.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 87c5a6d and ecf11b1.

📒 Files selected for processing (3)
  • config/profile.go (1 hunks)
  • docs/content/schedules/configuration.md (1 hunks)
  • schtasks/taskscheduler.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • config/profile.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • schtasks/taskscheduler.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: creativeprojects
PR: creativeprojects/resticprofile#459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the `schtasks` package, `tasksPath` is defined as a constant with value `\resticprofile backup\` in `taskscheduler.go`. It's used as a prefix for managing task paths in the Windows Task Scheduler.
Learnt from: zumm
PR: creativeprojects/resticprofile#541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.603Z
Learning: In the schtasks package, `config.Command` is guaranteed to be just a path to executable, making single quotes sufficient for wrapping in command construction.
Learnt from: zumm
PR: creativeprojects/resticprofile#541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.602Z
Learning: In the schtasks package, `config.Arguments` is already properly escaped by the caller, so additional escaping is not needed when constructing command arguments.
Learnt from: zumm
PR: creativeprojects/resticprofile#541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.603Z
Learning: In the schtasks package, double quotes cannot be used for command wrapping because `getTaskInfo` can't process them and unschedule commands fail when double quotes are used.
docs/content/schedules/configuration.md (3)

Learnt from: zumm
PR: #541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.602Z
Learning: In the schtasks package, config.Arguments is already properly escaped by the caller, so additional escaping is not needed when constructing command arguments.

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

Learnt from: zumm
PR: #541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.603Z
Learning: In the schtasks package, double quotes cannot be used for command wrapping because getTaskInfo can't process them and unschedule commands fail when double quotes are used.

Copy link

codecov bot commented Jul 29, 2025

Codecov Report

❌ Patch coverage is 69.56522% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 79.66%. Comparing base (720d7ee) to head (cedaf3d).
⚠️ Report is 3 commits behind head on master.

Files with missing lines Patch % Lines
schedule/handler_windows.go 61.11% 6 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #541      +/-   ##
==========================================
+ Coverage   79.33%   79.66%   +0.32%     
==========================================
  Files         136      136              
  Lines       13305    13404      +99     
==========================================
+ Hits        10555    10677     +122     
+ Misses       2332     2302      -30     
- Partials      418      425       +7     
Flag Coverage Δ
unittests 79.66% <69.57%> (+0.32%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
schtasks/taskscheduler_test.go (3)

138-138: Fix indentation inconsistency.

Line 138 has incorrect indentation (appears to use tabs instead of spaces or vice versa).

Apply this diff to fix the indentation:

-				found = true
+			found = true

140-141: Consider using more descriptive assertion messages.

The assertions could benefit from more descriptive messages to aid debugging when they fail.

Apply this diff to improve assertion messages:

-			assert.Equal(t, registeredTask.Command, "conhost.exe")
-			assert.Equal(t, registeredTask.Arguments, "--headless 'echo' hello there")
+			assert.Equal(t, "conhost.exe", registeredTask.Command, "Command should be wrapped with conhost.exe when HideWindow is enabled")
+			assert.Equal(t, "--headless 'echo' hello there", registeredTask.Arguments, "Arguments should be prefixed with --headless when HideWindow is enabled")

125-125: Consider testing the permission warning scenario.

Based on the implementation in taskscheduler.go, a warning is logged when HideWindow is used with permissions other than UserLoggedOnAccount. Consider adding a test case to verify this behaviour for completeness.

You could add a separate test function that uses a different permission type and captures the warning log output to ensure the warning is properly displayed.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2c99ca5 and 21aaacb.

📒 Files selected for processing (2)
  • schtasks/taskscheduler.go (1 hunks)
  • schtasks/taskscheduler_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • schtasks/taskscheduler.go
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: creativeprojects
PR: creativeprojects/resticprofile#459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the `schtasks` package, `tasksPath` is defined as a constant with value `\resticprofile backup\` in `taskscheduler.go`. It's used as a prefix for managing task paths in the Windows Task Scheduler.
Learnt from: zumm
PR: creativeprojects/resticprofile#541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.603Z
Learning: In the schtasks package, `config.Command` is guaranteed to be just a path to executable, making single quotes sufficient for wrapping in command construction.
Learnt from: zumm
PR: creativeprojects/resticprofile#541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.602Z
Learning: In the schtasks package, `config.Arguments` is already properly escaped by the caller, so additional escaping is not needed when constructing command arguments.
Learnt from: zumm
PR: creativeprojects/resticprofile#541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.603Z
Learning: In the schtasks package, double quotes cannot be used for command wrapping because `getTaskInfo` can't process them and unschedule commands fail when double quotes are used.
schtasks/taskscheduler_test.go (1)

Learnt from: creativeprojects
PR: #459
File: schtasks/schtasks.go:29-30
Timestamp: 2025-02-14T22:53:42.689Z
Learning: In the schtasks package, tasksPath is defined as a constant with value \resticprofile backup\ in taskscheduler.go. It's used as a prefix for managing task paths in the Windows Task Scheduler.

🧬 Code Graph Analysis (1)
schtasks/taskscheduler_test.go (3)
schtasks/config.go (1)
  • Config (5-13)
schtasks/taskscheduler.go (2)
  • Create (36-112)
  • Registered (145-182)
schtasks/permission.go (1)
  • UserLoggedOnAccount (19-19)
🔇 Additional comments (1)
schtasks/taskscheduler_test.go (1)

110-148: Well-structured test for the HideWindow feature.

The test properly verifies that when HideWindow: true is set, the task command is wrapped with conhost.exe --headless. The test setup, execution, and cleanup follow existing patterns in the file.

@zumm
Copy link
Contributor Author

zumm commented Jul 30, 2025

I decided to move hide-window option from schtasks scope to windows scope. Logic moved to schedule/handler_windows/CreateJob. This allows us to reuse option in other schedulers on Windows and handle argument escaping in single place.

@zumm
Copy link
Contributor Author

zumm commented Aug 2, 2025

I did some testing on old Windows 10 (very old, version 1607). It seems like conhost doesn't work here (it just doesn't run passed commands). Everything is good on my Windows 11 tho.

Should we mention this in docs?

@creativeprojects
Copy link
Owner

Windows 10 support is ending in October so I'm not worried about no longer supporting it (although Microsoft might extend it)

We should definitely mention it in the documentation though, thanks for testing it out 👍🏻

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
docs/content/schedules/configuration.md (2)

187-191: Clarify the usage sentence and mention the default value

The paragraph mixes the why and the how in one sentence and doesn’t state the default value. A slightly re-ordered wording improves readability and mirrors the phrasing used for the other options.

-To prevent that, set this option to `true` to hide the task window by wrapping the execution in `conhost.exe --headless`.
+Set `schedule-hide-window = true` to prevent the task window from appearing.  
+When enabled, resticprofile wraps the command in `conhost.exe --headless`, which runs the task without showing a console window.  
+(Default: `false`)

194-196: Fix missing preposition and keep spelling consistent

Minor grammar and consistency touch-ups:

  • Add the missing “to” after “According”.
  • Use “Behavior”, as the document already employs American spelling earlier.
  • Collapse the two sentences into one to remove repetition.
-Note: Behaviour of `conhost.exe` is not stable across Windows versions, so it may not work on some of them, especially older ones.
-According our tests, it works on Windows 11 (version 24H2) and doesn't work on Windows 10 (version 1607).
+Note: The behavior of `conhost.exe` varies between Windows versions. It has been confirmed to work on Windows 11 (24H2) but not on Windows 10 (1607) and earlier.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf8ab2e and cedaf3d.

📒 Files selected for processing (1)
  • docs/content/schedules/configuration.md (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: zumm
PR: creativeprojects/resticprofile#541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.636Z
Learning: In the schtasks package, `config.Arguments` is already properly escaped by the caller, so additional escaping is not needed when constructing command arguments.
Learnt from: zumm
PR: creativeprojects/resticprofile#541
File: schtasks/taskscheduler.go:60-67
Timestamp: 2025-07-29T16:14:02.636Z
Learning: In the schtasks package, `config.Command` is guaranteed to be just a path to executable, making single quotes sufficient for wrapping in command construction.
🪛 LanguageTool
docs/content/schedules/configuration.md

[grammar] ~194-~194: It appears that a preposition is missing after ‘According’.
Context: ...on some of them, especially older ones. According our tests, it works on Windows 11 (version ...

(MISSING_PREPOSITION)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Windows 11 hide backup window when backup runs as Windows Task for non-admin user
2 participants