Skip to content

Commit 59bb8ac

Browse files
committed
v3.0.0: Merge develop into main. Add documentation, add exception handling and service status updates, add session change events.
2 parents 71543c0 + 35e4674 commit 59bb8ac

26 files changed

Lines changed: 981 additions & 211 deletions
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
name: Delete Old Workflow Runs
2+
3+
on:
4+
schedule:
5+
# Run daily at 08:00
6+
- cron: '0 8 * * *'
7+
workflow_dispatch:
8+
inputs:
9+
retain_days:
10+
description: 'Days of workflow runs to keep'
11+
required: true
12+
default: '180'
13+
retain_runs:
14+
description: 'Minimum number of workflow runs to keep'
15+
required: true
16+
default: '10'
17+
18+
jobs:
19+
delete_old_workflow_runs:
20+
name: Delete Old Workflow Runs
21+
runs-on: ubuntu-latest
22+
steps:
23+
- name: Set environment variables
24+
env:
25+
DEFAULT_RETAIN_DAYS: '180'
26+
DEFAULT_RETAIN_RUNS: '10'
27+
run: |
28+
echo "RETAIN_DAYS=${{ github.event.inputs.retain_days || env.DEFAULT_RETAIN_DAYS }}" >> $GITHUB_ENV
29+
echo "RETAIN_RUNS=${{ github.event.inputs.retain_runs || env.DEFAULT_RETAIN_RUNS }}" >> $GITHUB_ENV
30+
- name: Delete workflow runs older than ${{ env.RETAIN_DAYS }} days, keeping ${{ env.RETAIN_RUNS }} runs
31+
uses: Mattraks/delete-workflow-runs@v1.2.3
32+
with:
33+
token: ${{ secrets.GITHUB_TOKEN }}
34+
repository: ${{ github.repository }}
35+
retain_days: ${{ env.RETAIN_DAYS }}
36+
keep_minimum_runs: ${{ env.RETAIN_RUNS }}

.github/workflows/Publish-Package.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ jobs:
2020
- name: Setup .NET
2121
uses: actions/setup-dotnet@v1
2222
with:
23-
dotnet-version: 5.0.x
23+
dotnet-version: 6.0.x
2424

2525
- name: Restore dependencies
26-
run: dotnet restore
26+
run: dotnet restore CodeCaster.WindowsServiceExtensions.sln
2727

2828
- name: Build
29-
run: dotnet build --no-restore --configuration=Release -p:Version=${{ steps.git_version.outputs.version-without-v }}
29+
run: dotnet build CodeCaster.WindowsServiceExtensions.sln --no-restore --configuration=Release -p:Version=${{ steps.git_version.outputs.version-without-v }}
3030

3131
- name: Release to GitHub
3232
uses: softprops/action-gh-release@v1

CodeCaster.WindowsServiceExtensions.sln

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,32 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 16
4-
VisualStudioVersion = 16.0.31229.75
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.1.32414.318
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CodeCaster.WindowsServiceExtensions", "src\CodeCaster.WindowsServiceExtensions\CodeCaster.WindowsServiceExtensions.csproj", "{CC230E5D-8600-4048-8F10-E84A80F77DDC}"
77
EndProject
88
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4B4ADAD2-7E93-44E5-8376-3B11173671EE}"
99
ProjectSection(SolutionItems) = preProject
10+
.gitignore = .gitignore
11+
docs\Docs.shproj = docs\Docs.shproj
1012
LICENSE = LICENSE
1113
Readme.md = Readme.md
1214
EndProjectSection
1315
EndProject
1416
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Workflows", "GitHub Workflows", "{37061E24-6F75-46D3-A86D-2B35A945A7A7}"
1517
ProjectSection(SolutionItems) = preProject
18+
.github\workflows\Delete-Old-Workflow-Runs.yml = .github\workflows\Delete-Old-Workflow-Runs.yml
1619
.github\workflows\Publish-Package.yml = .github\workflows\Publish-Package.yml
1720
EndProjectSection
1821
EndProject
1922
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestServiceThatThrows", "test\TestServiceThatThrows\TestServiceThatThrows.csproj", "{A426812E-CB26-47F2-B914-17D87312C51F}"
2023
EndProject
24+
Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Docs", "Docs.shproj", "{62D35936-704A-4F38-9882-E3209079EA47}"
25+
EndProject
2126
Global
27+
GlobalSection(SharedMSBuildProjectFiles) = preSolution
28+
Docs.projitems*{62d35936-704a-4f38-9882-e3209079ea47}*SharedItemsImports = 13
29+
EndGlobalSection
2230
GlobalSection(SolutionConfigurationPlatforms) = preSolution
2331
Debug|Any CPU = Debug|Any CPU
2432
Release|Any CPU = Release|Any CPU

Docs.projitems

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup>
4+
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
5+
<HasSharedItems>true</HasSharedItems>
6+
<SharedGUID>62d35936-704a-4f38-9882-e3209079ea47</SharedGUID>
7+
</PropertyGroup>
8+
<PropertyGroup Label="Configuration">
9+
<Import_RootNamespace>Docs</Import_RootNamespace>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<Content Include="$(MSBuildThisFileDirectory)docs\downloads.md" />
13+
<Content Include="$(MSBuildThisFileDirectory)docs\roadmap.md" />
14+
<Content Include="$(MSBuildThisFileDirectory)docs\upgrading-v2-v3.md" />
15+
<Content Include="$(MSBuildThisFileDirectory)docs\_layouts\cayman-with-menu.html" />
16+
<Content Include="$(MSBuildThisFileDirectory)docs\_config.yml" />
17+
<Content Include="$(MSBuildThisFileDirectory)docs\index.md" />
18+
</ItemGroup>
19+
</Project>

Docs.shproj

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<PropertyGroup Label="Globals">
4+
<ProjectGuid>62d35936-704a-4f38-9882-e3209079ea47</ProjectGuid>
5+
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
6+
</PropertyGroup>
7+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
8+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
9+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
10+
<PropertyGroup />
11+
<Import Project="Docs.projitems" Label="Shared" />
12+
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
13+
</Project>

Readme.md

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,48 @@
1-
# WindowsServiceExtensions
2-
Make a .NET Core Windows Service that runs `IHostedService` background services power event aware. On consumer OS Windows 10, shutting down the computer will actually hibernate the OS. Services won't get another OnStart call.
1+
# Windows Service Extensions
2+
Building a basic Windows Service that does some long-running background work is trivial, using .NET's [`UseWindowsService()`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.windowsservicelifetimehostbuilderextensions.usewindowsservice?view=dotnet-plat-ext-6.0) (from the Platform Extensions package `Microsoft.Extensions.Hosting.WindowsServices`) and [`Microsoft.Extensions.Hosting.BackgroundService`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.backgroundservice?view=dotnet-plat-ext-6.0) from `Microsoft.Extensions.Hosting.Abstractions`.
33

4-
This project exists of a few classes that make building reliable Windows Services easier. The Lifetime class also include [kmcclellan's fixes](https://github.com/dotnet/runtime/issues/50019#issuecomment-678658133) that make the service throw when something fails on startup, instead of reprorting it started successfully.
4+
Running as a _Windows Service_ and _running BackgroundServices_ are two separate things though, and they are not connected in any way. A non-service console app can run background services, and so can and do web applications. Each of those are different hosting environments, with their own lifetimes.
5+
6+
This project exists of a few classes that make building reliable Windows Services easier, to gap this disconnect.
7+
8+
## Lifetime
9+
The following improvements are included in this library:
10+
11+
* `OnStart()` can fail because of invalid service configuration, quit the application when that happens (by [kmcclellan](https://github.com/dotnet/runtime/issues/50019#issuecomment-678658133)).
12+
* On consumer OS Windows 10+, shutting down the computer will actually hibernate the OS. Services won't get another `OnStart()` call when the computer starts again, nor will your background services be notified. Now they will.
13+
* It also can notify your BackgroundServices about user session changes, i.e. logon, logoff and others.
14+
* When an exception occurs during your BackgroundService's lifetime, .NET Platform Extensions < 6 didn't stop the application host. Now it does, but it doesn't report an error to the Service Control Manager. With this extension, it does, as well as setting a process exit code: 13 in both cases ("invalid data").
515

616
## Installation
717
Through [NuGet](https://www.nuget.org/packages/CodeCaster.WindowsServiceExtensions/):
818

919
> Install-Package CodeCaster.WindowsServiceExtensions
1020

1121
## Usage
12-
These extensions allow your IHostedServices to respond to this power state change.:
22+
These methods from this package allow your `IHostedService`s to tell the Windows Service Control Manager about errors, and respond to Windows Service events relating to sessions (user logon/logoff) and power state (shutdown/hibernate/resume):
23+
24+
* On your Host Builder, call `UseWindowsServiceExtensions()` instead of `UseWindowsService()`.
25+
* Instead of letting your service inherit `BackgroundService`, inherit from `CodeCaster.WindowsServiceExtensions.WindowsServiceBackgroundService`.
26+
* Implement `protected Task TryExecuteAsync(CancellationToken stoppingToken)` instead of `ExecuteAsync(CancellationToken stoppingToken)`.
27+
* Implement the method `public override bool OnPowerEvent(PowerBroadcastStatus powerStatus) { ... }` and do your thing when it's called with a certain status.
28+
* Implement the method `public override bool OnSessionChange(SessionChangeDescription changeDescription) { ... }` and do your thing when it's called with a certain status.
29+
30+
Do note that the statuses received can vary. You get either `ResumeSuspend`, `ResumeAutomatic` or both reported to `OnPowerEvent()`, never neither, after a machine wake, reboot or boot.
31+
32+
## Documentation
33+
For examples and more specific documentation, see https://codecasternl.github.io/WindowsServiceExtensions/.
34+
35+
## Upgrading from v2 to v3
36+
If you're one of the souls that use this library (who _are_ you?), you'll want to upgrade to v3.0 after upgrading your projects to .NET 6.
37+
38+
Changes:
39+
40+
* The DI extension method `IHostBuilder.UsePowerEventAwareWindowsService()` is now called `UseWindowsServiceExtensions()` because we do more than power events now.
41+
* The long-running hosted service base class `CodeCaster.WindowsServiceExtensions.PowerEventAwareBackgroundService` was renamed to `CodeCaster.WindowsServiceExtensions.Service.WindowsServiceBackgroundService`, because the former didn't have enough "Service" in its name.
42+
* Instead of `BackgroundService.ExecuteAsync()`, which is now sealed, override `WindowsServiceBackgroundService.TryExecuteAsync()` to do your long-running work.
43+
44+
Extended upgrading docs: see https://codecasternl.github.io/WindowsServiceExtensions/upgrading-v2-v3.
1345

14-
* On your Host Builder, call `UsePowerEventAwareWindowsService()` instead of [`UseWindowsService()`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.windowsservicelifetimehostbuilderextensions.usewindowsservice?view=dotnet-plat-ext-3.1).
15-
* Instead of letting your service inherit [`Microsoft.Extensions.Hosting.BackgroundService`](https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.hosting.backgroundservice?view=dotnet-plat-ext-5.0), inherit from `CodeCaster.WindowsServiceExtensions.PowerEventAwareBackgroundService`.
16-
* Implement the method `public override bool OnPowerEvent(PowerBroadcastStatus powerStatus)` and do your thing when it's called with a certain status.
46+
# Contributing
47+
Please file an issue or PR. Even if you use this and are happy with it.
1748

18-
Do note that the statuses received can vary. You get either `ResumeSuspend`, `ResumeAutomatic` or both, never neither, after a machine wake, reboot or boot.

docs/_config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
theme: "jekyll-theme-cayman"
2+
description: "CodeCaster.WindowsServices documentation"
3+
url: "https://codecasternl.github.io/WindowsServiceExtensions/"
4+
defaults:
5+
-
6+
scope:
7+
path: "" # everything
8+
values:
9+
layout: "cayman-with-menu"
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!DOCTYPE html>
2+
<html lang="{{ site.lang | default: "en-US" }}">
3+
<head>
4+
<meta charset="UTF-8">
5+
6+
{% seo %}
7+
<link rel="preconnect" href="https://fonts.gstatic.com">
8+
<link rel="preload" href="https://fonts.googleapis.com/css?family=Open+Sans:400,700&display=swap" as="style" type="text/css" crossorigin>
9+
<meta name="viewport" content="width=device-width, initial-scale=1">
10+
<meta name="theme-color" content="#157878">
11+
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
12+
<link rel="stylesheet" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
13+
{% include head-custom.html %}
14+
</head>
15+
<body>
16+
<a id="skip-to-content" href="#content">Skip to the content.</a>
17+
18+
<header class="page-header" role="banner">
19+
<h1 class="project-name">{{ page.title | default: site.title | default: site.github.repository_name }}</h1>
20+
<h2 class="project-tagline">{{ page.description | default: site.description | default: site.github.project_tagline }}</h2>
21+
22+
{% if site.github.is_project_page %}
23+
<a href="{{ site.github.repository_url }}" class="btn">View on GitHub</a>
24+
{% endif %}
25+
26+
{% if site.show_downloads %}
27+
<a href="{{ site.github.zip_url }}" class="btn">Download .zip</a>
28+
<a href="{{ site.github.tar_url }}" class="btn">Download .tar.gz</a>
29+
{% endif %}
30+
31+
<p>
32+
{% assign sorted_pages = site.pages | sort:"order" %}
33+
|
34+
{% for node in sorted_pages %}
35+
{% if node.title %}
36+
<span><a href=".{{node.url}}" style="color: inherit" title="{{node.title}}">{{node.title}}</a></span> |
37+
{% endif %}
38+
{% endfor %}
39+
</p>
40+
</header>
41+
42+
<main id="content" class="main-content" role="main">
43+
{{ content }}
44+
45+
<footer class="site-footer">
46+
{% if site.github.is_project_page %}
47+
<span class="site-footer-owner"><a href="{{ site.github.repository_url }}">{{ site.github.repository_name }}</a> is maintained by <a href="{{ site.github.owner_url }}">{{ site.github.owner_name }}</a>.</span>
48+
{% endif %}
49+
<span class="site-footer-credits">This page was generated by <a href="https://pages.github.com">GitHub Pages</a>.</span>
50+
</footer>
51+
</main>
52+
</body>
53+
</html>

docs/downloads.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
title: Downloads
3+
order: 4
4+
---
5+
## Get it now on NuGet
6+
Through [NuGet](https://www.nuget.org/packages/CodeCaster.WindowsServiceExtensions/):
7+
8+
> Install-Package CodeCaster.WindowsServiceExtensions
9+
10+
## Releases
11+
You can download the packages or source from the [GitHub Releases page](https://github.com/CodeCasterNL/WindowsServiceExtensions/releases).

0 commit comments

Comments
 (0)