diff --git a/CHANGELOG.md b/CHANGELOG.md index 93213d7..a48b959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # MaksIT.UScheduler Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## v1.0.1 - 2026-02-02 + +### Added +- **CLI service management**: Added command-line arguments for service installation and management + - `--install`, `-i`: Install the Windows service + - `--uninstall`, `-u`: Uninstall the Windows service + - `--start`: Start the service + - `--stop`: Stop the service + - `--status`: Query service status + - `--help`, `-h`: Show help message +- **Relative path support**: Script and process paths can now be relative to the application directory +- **OS guard**: Application now checks for Windows at startup and exits with error on unsupported platforms +- **Release script enhancement**: Example scripts are now automatically added to `appsettings.json` in disabled state during release build +- **Unit tests**: Added comprehensive test project `MaksIT.UScheduler.Tests` with tests for background services and configuration + +### Changed +- Default value for `IsSigned` in PowerShell script configuration changed from `false` to `true` for improved security +- PSScriptService now implements `IDisposable` for proper RunspacePool cleanup +- Method signatures updated: `RunScript` → `RunScriptAsync`, `RunProcess` → `RunProcessAsync` +- Service is now installed with `start=auto` for automatic startup on boot +- Updated package dependencies: + - MaksIT.Core: 1.6.0 → 1.6.1 + - Microsoft.Extensions.Hosting: 10.0.0 → 10.0.2 + - Microsoft.Extensions.Hosting.WindowsServices: 10.0.0 → 10.0.2 + - System.Diagnostics.PerformanceCounter: 10.0.0 → 10.0.2 + +### Removed +- `Install.cmd` and `Uninstall.cmd` files (replaced by CLI arguments) + +### Fixed +- **Parallel execution**: Restored parallel execution of PowerShell scripts and processes (broken during .NET Framework to .NET migration) + - PSScriptService now uses RunspacePool (up to CPU core count) for concurrent script execution + - Background services use `Task.WhenAll` to launch all tasks simultaneously + ## v1.0.0 - 2025-12-06 ### Major Changes @@ -13,7 +52,7 @@ - `ProcessBackgroundService` for process management. - Enhanced PowerShell script execution with signature validation and script unblocking. - Improved process management with restart-on-failure logic. -- Updated install/uninstall scripts (`Install.cmd`, `Uninstall.cmd`) for service management. +- Added install/uninstall scripts (`Install.cmd`, `Uninstall.cmd`) for service management (removed in v1.0.1). - Added comprehensive README with usage, configuration, and scheduling examples. - MIT License included. @@ -21,3 +60,27 @@ - Old solution, project, and service files removed. - Configuration format and service naming conventions updated. - Scheduling logic for console applications is not yet implemented (runs every 10 seconds). + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..19fac68 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,236 @@ +# Contributing to MaksIT.UScheduler + +Thank you for your interest in contributing to MaksIT.UScheduler! + +## Table of Contents + +- [Contributing to MaksIT.UScheduler](#contributing-to-maksituscheduler) + - [Table of Contents](#table-of-contents) + - [Development Setup](#development-setup) + - [Branch Strategy](#branch-strategy) + - [Making Changes](#making-changes) + - [Versioning](#versioning) + - [Release Process](#release-process) + - [Prerequisites](#prerequisites) + - [Version Files](#version-files) + - [Branch-Based Release Behavior](#branch-based-release-behavior) + - [Development Build Workflow](#development-build-workflow) + - [Production Release Workflow](#production-release-workflow) + - [Release Script Details](#release-script-details) + - [Changelog Guidelines](#changelog-guidelines) + - [AI-Powered Changelog Generation (Optional)](#ai-powered-changelog-generation-optional) + +--- + +## Development Setup + +1. Clone the repository: + ```bash + git clone https://github.com/MaksIT/uscheduler.git + cd uscheduler + ``` + +2. Open the solution in Visual Studio or your preferred IDE: + ``` + src/MaksIT.UScheduler/MaksIT.UScheduler.sln + ``` + +3. Build the project: + ```bash + dotnet build src/MaksIT.UScheduler/MaksIT.UScheduler.csproj + ``` + +--- + +## Branch Strategy + +- `main` - Production-ready code +- `dev` - Active development branch +- Feature branches - Created from `dev` for specific features + +--- + +## Making Changes + +1. Create a feature branch from `dev` +2. Make your changes +3. Update `CHANGELOG.md` with your changes +4. Update version in `.csproj` if needed +5. Test your changes locally using dev tags +6. Submit a pull request to `dev` + +--- + +## Versioning + +This project follows [Semantic Versioning](https://semver.org/): + +- **MAJOR** - Incompatible API changes +- **MINOR** - New functionality (backwards compatible) +- **PATCH** - Bug fixes (backwards compatible) + +Version format: `X.Y.Z` (e.g., `1.0.1`) + +--- + +## Release Process + +### Prerequisites + +- .NET SDK installed +- Git CLI +- GitHub CLI (`gh`) - required only for production releases +- GitHub token set in environment variable (configured in `scriptsettings.json`) + +### Version Files + +Before creating a release, ensure version consistency across: + +1. **`.csproj`** - Update `` element: + ```xml + 1.0.1 + ``` + +2. **`CHANGELOG.md`** - Add version entry at the top: + ```markdown + ## v1.0.1 + + ### Added + - New feature description + + ### Fixed + - Bug fix description + ``` + +### Branch-Based Release Behavior + +The release script behavior is controlled by the current branch (configurable in `scriptsettings.json`): + +| Branch | Tag Required | Uncommitted Changes | Behavior | +|--------|--------------|---------------------|----------| +| Dev (`dev`) | No | Allowed | Local build only (version from .csproj) | +| Release (`main`) | Yes | Not allowed | Full release to GitHub | +| Other | - | - | Blocked | + +Branch names can be customized in `scriptsettings.json`: +```json +"branches": { + "release": "main", + "dev": "dev" +} +``` + +### Development Build Workflow + +Test builds on the `dev` branch - no tag needed: + +```bash +# 1. On dev branch: Update version in .csproj and CHANGELOG.md +git checkout dev + +# 2. Commit your changes +git add . +git commit -m "Prepare v1.0.1 release" + +# 3. Run the release script (no tag needed!) +cd src/scripts/Release-ToGitHub +.\Release-ToGitHub.ps1 + +# Output: DEV BUILD COMPLETE +# Creates: release/maksit.uscheduler-1.0.1.zip (local only) +``` + +### Production Release Workflow + +When ready to publish, merge to `main`, create tag, and run: + +```bash +# 1. Merge to main +git checkout main +git merge dev + +# 2. Create tag (required on main) +git tag v1.0.1 + +# 3. Run the release script +cd src/scripts/Release-ToGitHub +.\Release-ToGitHub.ps1 + +# Output: RELEASE COMPLETE +# Creates: release/maksit.uscheduler-1.0.1.zip +# Pushes tag to GitHub +# Creates GitHub release with assets +``` + +### Release Script Details + +The `Release-ToGitHub.ps1` script performs these steps: + +**Pre-flight checks:** +- Detects current branch (`main` or `dev`) +- On `main`: requires clean working directory; on `dev`: uncommitted changes allowed +- Reads version from `.csproj` (source of truth) +- On `main`: requires tag matching the version +- Ensures `CHANGELOG.md` has matching version entry +- Checks GitHub CLI authentication (main branch only) + +**Build process:** +- Publishes .NET project in Release configuration +- Copies `Scripts` folder into the release +- Creates versioned ZIP archive +- Extracts release notes from `CHANGELOG.md` + +**GitHub release (main branch only):** +- Pushes tag to remote if not present +- Creates (or recreates) GitHub release with assets + +**Configuration:** + +The script reads settings from `scriptsettings.json`: + +```json +{ + "github": { + "tokenEnvVar": "GITHUB_MAKS_IT_COM" + }, + "paths": { + "csprojPath": "..\\..\\MaksIT.UScheduler\\MaksIT.UScheduler.csproj", + "changelogPath": "..\\..\\..\\CHANGELOG.md", + "releaseDir": "..\\..\\release" + }, + "release": { + "zipNamePattern": "maksit.uscheduler-{version}.zip", + "releaseTitlePattern": "Release {version}" + } +} +``` + +--- + +## Changelog Guidelines + +Follow [Keep a Changelog](https://keepachangelog.com/) format: + +```markdown +## v1.0.1 + +### Added +- New features + +### Changed +- Changes to existing functionality + +### Deprecated +- Features to be removed in future + +### Removed +- Removed features + +### Fixed +- Bug fixes + +### Security +- Security-related changes +``` + +--- diff --git a/LICENSE.md b/LICENSE.md index c43e08a..f5aa10f 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 Maksym Sadovnychyy (Maks-IT) +Copyright (c) 2017 - 2026 Maksym Sadovnychyy (Maks-IT) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 35a56f4..9813a7f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ - - # MaksIT Unified Scheduler Service +![Line Coverage](badges/coverage-lines.svg) ![Branch Coverage](badges/coverage-branches.svg) ![Method Coverage](badges/coverage-methods.svg) + A modern, fully rewritten Windows service built on **.NET 10** for scheduling and running PowerShell scripts and console applications. Designed for system administrators — and also for those who *feel like* system administrators — who need a predictable, resilient, and secure background execution environment. @@ -14,38 +14,52 @@ Designed for system administrators — and also for those who *feel like* system - [Scripts Examples](#scripts-examples) - [Features at a Glance](#features-at-a-glance) - [Installation](#installation) - - [Recommended (using bundled scripts)](#recommended-using-bundled-scripts) - - [Manual Installation](#manual-installation) + - [Using CLI Commands](#using-cli-commands) + - [Using sc.exe](#using-scexe) - [Configuration (`appsettings.json`)](#configuration-appsettingsjson) + - [Path Resolution](#path-resolution) + - [Log Levels](#log-levels) - [PowerShell Scripts](#powershell-scripts) - [Processes](#processes) - [How It Works](#how-it-works) - [PowerShell Execution Parameters](#powershell-execution-parameters) - - [Thread Layout](#thread-layout) + - [Execution Model](#execution-model) - [Reusable Scheduler Module (`SchedulerTemplate.psm1`)](#reusable-scheduler-module-schedulertemplatepsm1) + - [Exported Functions](#exported-functions) + - [Module Version](#module-version) - [Example usage](#example-usage) - [Security](#security) - [Logging](#logging) + - [Testing](#testing) + - [Running Tests](#running-tests) + - [Code Coverage](#code-coverage) + - [Test Structure](#test-structure) - [Contact](#contact) - [License](#license) ## Scripts Examples -- [Hyper-V Backup](./examples/HyperV-Backup/README.md) - Production-ready Hyper-V VM backup solution with scheduling and retention management -- [Native-Sync](./examples/Native-Sync/README.md) - Production-ready file synchronization solution using pure PowerShell with no external dependencies -- [File-Sync](./examples/File-Sync/README.md) - [FreeFileSync](https://freefilesync.org/) batch job execution -- [Scheduler Template Module](./examples/SchedulerTemplate.psm1) + +> **Note:** These examples are **bundled with the release** and included in the default `appsettings.json`, but are **disabled by default**. To enable an example, set `"Disabled": false` in the configuration. + +- [Hyper-V Backup](./src/Scripts/HyperV-Backup/README.md) - Production-ready Hyper-V VM backup solution with scheduling and retention management +- [Native-Sync](./src/Scripts/Native-Sync/README.md) - Production-ready file synchronization solution using pure PowerShell with no external dependencies +- [File-Sync](./src/Scripts/File-Sync/README.md) - [FreeFileSync](https://freefilesync.org/) batch job execution +- [Windows-Update](./src/Scripts/Windows-Update/README.md) - Production-ready Windows Update automation solution using pure PowerShell +- [Scheduler Template Module](./src/Scripts/SchedulerTemplate.psm1) --- ## Features at a Glance * **.NET 10 Worker Service** – clean, robust, stable. +* **Windows only** – designed specifically for Windows services. * **Strongly typed configuration** via `appsettings.json`. -* **Run PowerShell scripts & executables concurrently** (each in its own thread). +* **Parallel execution** – PowerShell scripts & executables run concurrently using RunspacePool and Task.WhenAll. +* **Relative path support** – script and process paths can be relative to the application directory. * **Signature enforcement** (AllSigned by default). * **Automatic restart-on-failure** for supervised processes. -* **Extensible logging** (file + console). -* **Simple Install.cmd / Uninstall.cmd**. +* **Extensible logging** (file + console + Windows EventLog). +* **Built-in CLI** for service management (`--install`, `--uninstall`, `--start`, `--stop`, `--status`). * **Reusable scheduling module**: `SchedulerTemplate.psm1`. * **Thread-isolated architecture** — individual failures do not affect others. @@ -53,30 +67,55 @@ Designed for system administrators — and also for those who *feel like* system ## Installation -### Recommended (using bundled scripts) +### Using CLI Commands -```bat -cd /d path\to\src\MaksIT.UScheduler -Install.cmd +The executable includes built-in service management commands. Run as Administrator: + +```powershell +# Install the service (auto-start enabled) +MaksIT.UScheduler.exe --install + +# Start the service +MaksIT.UScheduler.exe --start + +# Check service status +MaksIT.UScheduler.exe --status + +# Stop the service +MaksIT.UScheduler.exe --stop + +# Uninstall the service +MaksIT.UScheduler.exe --uninstall + +# Show help +MaksIT.UScheduler.exe --help +``` + +| Command | Short | Description | +|---------|-------|-------------| +| `--install` | `-i` | Install the Windows service (auto-start) | +| `--uninstall` | `-u` | Stop and remove the Windows service | +| `--start` | | Start the service | +| `--stop` | | Stop the service | +| `--status` | | Query service status | +| `--help` | `-h` | Show help message | + +> **Note:** Service management commands require administrator privileges. + +### Using sc.exe + +Alternatively, use Windows Service Control Manager directly: + +```powershell +sc.exe create "MaksIT.UScheduler" binpath="C:\Path\To\MaksIT.UScheduler.exe" start=auto +sc.exe start "MaksIT.UScheduler" ``` To uninstall: -```bat -Uninstall.cmd -``` - -### Manual Installation - ```powershell -sc.exe create "MaksIT.UScheduler Service" binpath="C:\Path\To\MaksIT.UScheduler.exe" -sc.exe start "MaksIT.UScheduler Service" -``` - -Manual uninstall: - -```powershell -sc.exe delete "MaksIT.UScheduler Service" +sc.exe stop "MaksIT.UScheduler" +sc.exe delete "MaksIT.UScheduler" ``` --- @@ -85,31 +124,79 @@ sc.exe delete "MaksIT.UScheduler Service" ```json { + "Logging": { + "LogLevel": { + "Default": "Information" + }, + "EventLog": { + "SourceName": "MaksIT.UScheduler", + "LogName": "Application", + "LogLevel": { + "Microsoft": "Information", + "Microsoft.Hosting.Lifetime": "Information" + } + } + }, "Configuration": { "ServiceName": "MaksIT.UScheduler", "LogDir": "C:\\Logs", "Powershell": [ - { "Path": "C:\\Scripts\\MyScript.ps1", "IsSigned": true } + { "Path": "../Scripts/MyScript.ps1", "IsSigned": true, "Disabled": false }, + { "Path": "C:\\Scripts\\AnotherScript.ps1", "IsSigned": false, "Disabled": true } ], "Processes": [ - { "Path": "C:\\Programs\\MyApp.exe", "Args": ["--option"], "RestartOnFailure": true } + { "Path": "../Tools/MyApp.exe", "Args": ["--option"], "RestartOnFailure": true, "Disabled": false } ] } } ``` +> **Note:** `ServiceName` and `LogDir` are optional. Defaults: `"MaksIT.UScheduler"` and `Logs` folder in app directory. + +### Path Resolution + +Paths can be either absolute or relative: + +| Path Type | Example | Resolved To | +|-----------|---------|-------------| +| Absolute | `C:\Scripts\backup.ps1` | `C:\Scripts\backup.ps1` | +| Relative | `../Scripts/backup.ps1` | `{AppDirectory}\..\Scripts\backup.ps1` | +| Relative | `scripts/backup.ps1` | `{AppDirectory}\scripts\backup.ps1` | + +Relative paths are resolved against the application's base directory (where `MaksIT.UScheduler.exe` is located). + +### Log Levels + +The `"Default": "Information"` setting controls the minimum severity of messages that get logged. Available levels (from most to least verbose): + +| Level | Description | +|-------|-------------| +| `Trace` | Most detailed, for debugging internals | +| `Debug` | Debugging information | +| `Information` | General operational events (recommended default) | +| `Warning` | Abnormal or unexpected events | +| `Error` | Errors and exceptions | +| `Critical` | Critical failures requiring immediate attention | +| `None` | Disables logging | + ### PowerShell Scripts -* `Path` — full `.ps1` file path -* `IsSigned` — `true` enforces AllSigned, `false` runs unrestricted +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `Path` | string | required | Path to `.ps1` file (absolute or relative) | +| `IsSigned` | bool | `true` | `true` enforces AllSigned, `false` runs unrestricted | +| `Disabled` | bool | `false` | `true` skips this script during execution | ### Processes -* `Path` — executable -* `Args` — command-line arguments -* `RestartOnFailure` — restart logic handled by service +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `Path` | string | required | Path to executable (absolute or relative) | +| `Args` | string[] | `null` | Command-line arguments | +| `RestartOnFailure` | bool | `false` | Restart process if it exits with non-zero code | +| `Disabled` | bool | `false` | `true` skips this process during execution | --- @@ -133,21 +220,28 @@ param ( ) ``` -### Thread Layout +### Execution Model + +Scripts and processes run **in parallel** using: + +- **PowerShell**: `RunspacePool` (up to CPU core count concurrent runspaces) +- **Processes**: `Task.WhenAll` for concurrent process execution ``` Unified Scheduler Service -├── PowerShell -│ ├── ScriptA.ps1 Thread -│ ├── ScriptB.ps1 Thread -│ └── ... -└── Processes - ├── ProgramA.exe Thread - ├── ProgramB.exe Thread - └── ... +├── PSScriptBackgroundService (RunspacePool) +│ ├── ScriptA.ps1 ─┐ +│ ├── ScriptB.ps1 ─┼─ Parallel execution +│ └── ScriptC.ps1 ─┘ +└── ProcessBackgroundService (Task.WhenAll) + ├── ProgramA.exe ─┐ + ├── ProgramB.exe ─┼─ Parallel execution + └── ProgramC.exe ─┘ ``` -A crash in one thread **never stops the service** or other components. +- A failure in one script/process **never stops the service** or other components. +- The same script/process won't run twice concurrently (protected by "already running" check). +- Execution cycle repeats every 10 seconds. --- @@ -164,7 +258,20 @@ This module provides: * Automatic lock file (no concurrent execution) * Last-run file tracking * Unified callback execution pattern -* Logging helpers (Write-Log) + +### Exported Functions + +| Function | Description | +|----------|-------------| +| `Write-Log` | Logging with timestamp, level (Info/Success/Warning/Error), and color support | +| `Invoke-ScheduledExecution` | Main scheduler — checks schedule, manages locks, runs callback | +| `Get-CredentialFromEnvVar` | Retrieves credentials from Base64-encoded machine environment variables | +| `Test-UNCPath` | Validates whether a path is a UNC path | +| `Send-EmailNotification` | Sends SMTP email with optional SSL and credential support | + +### Module Version + +The module exports `$ModuleVersion` and `$ModuleDate` for version tracking. ### Example usage @@ -205,20 +312,65 @@ That’s it — the full scheduling engine is reused automatically. ## Security -* Signed scripts required by default. -* Scripts are auto-unblocked before execution. -* Unrestricted execution can be enabled if needed (not recommended on production systems). +* Scripts run with **AllSigned** execution policy by default. +* Set `IsSigned: false` to use **Unrestricted** policy (not recommended for production). +* Scripts are auto-unblocked before execution (Zone.Identifier removed). +* Signature validation ensures only trusted scripts execute. --- ## Logging -* Console logging -* File logging under the directory specified by `LogDir` +* **Console logging** — standard output +* **File logging** — written to `LogDir` (default: `Logs` folder in app directory) +* **Windows EventLog** — events logged to Application log under `MaksIT.UScheduler` source * All events (start, stop, crash, restart, error, skip) are logged --- +## Testing + +The project includes a comprehensive test suite using **xUnit** and **Moq** for unit testing. + +### Running Tests + +```powershell +# Run all tests +dotnet test src/MaksIT.UScheduler.Tests + +# Run with verbose output +dotnet test src/MaksIT.UScheduler.Tests --verbosity normal +``` + +### Code Coverage + +Coverage badges are generated locally using [ReportGenerator](https://github.com/danielpalme/ReportGenerator). + +**Prerequisites:** + +```powershell +dotnet tool install --global dotnet-reportgenerator-globaltool +``` + +**Generate coverage report and badges:** + +```powershell +.\src\scripts\Run-Coverage\Run-Coverage.ps1 + +# With HTML report opened in browser +.\src\scripts\Run-Coverage\Run-Coverage.ps1 -OpenReport +``` + +### Test Structure + +| Test Class | Coverage | +|------------|----------| +| `ConfigurationTests` | Configuration POCOs and default values | +| `ProcessBackgroundServiceTests` | Process execution lifecycle and error handling | +| `PSScriptBackgroundServiceTests` | PowerShell script execution and signature validation | + +--- + ## Contact Maksym Sadovnychyy – MAKS-IT, 2025 diff --git a/badges/coverage-branches.svg b/badges/coverage-branches.svg new file mode 100644 index 0000000..f8e8de3 --- /dev/null +++ b/badges/coverage-branches.svg @@ -0,0 +1,21 @@ + + Branch Coverage: 9.6% + + + + + + + + + + + + + + + Branch Coverage + + 9.6% + + diff --git a/badges/coverage-lines.svg b/badges/coverage-lines.svg new file mode 100644 index 0000000..00b2fee --- /dev/null +++ b/badges/coverage-lines.svg @@ -0,0 +1,21 @@ + + Line Coverage: 18.6% + + + + + + + + + + + + + + + Line Coverage + + 18.6% + + diff --git a/badges/coverage-methods.svg b/badges/coverage-methods.svg new file mode 100644 index 0000000..2f8480e --- /dev/null +++ b/badges/coverage-methods.svg @@ -0,0 +1,21 @@ + + Method Coverage: 43.2% + + + + + + + + + + + + + + + Method Coverage + + 43.2% + + diff --git a/src/MaksIT.UScheduler.ScheduleManager/App.xaml b/src/MaksIT.UScheduler.ScheduleManager/App.xaml new file mode 100644 index 0000000..eded4c6 --- /dev/null +++ b/src/MaksIT.UScheduler.ScheduleManager/App.xaml @@ -0,0 +1,8 @@ + + + + + diff --git a/src/MaksIT.UScheduler.ScheduleManager/App.xaml.cs b/src/MaksIT.UScheduler.ScheduleManager/App.xaml.cs new file mode 100644 index 0000000..89fbef8 --- /dev/null +++ b/src/MaksIT.UScheduler.ScheduleManager/App.xaml.cs @@ -0,0 +1,7 @@ +using System.Windows; + +namespace MaksIT.UScheduler.ScheduleManager; + +public partial class App : Application +{ +} diff --git a/src/MaksIT.UScheduler.ScheduleManager/MainWindow.xaml b/src/MaksIT.UScheduler.ScheduleManager/MainWindow.xaml new file mode 100644 index 0000000..63ce6d3 --- /dev/null +++ b/src/MaksIT.UScheduler.ScheduleManager/MainWindow.xaml @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +