├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── Solution └── DotNetCore.WindowsService.sln └── Source ├── PeterKottas.DotNetCore.WindowsService.Example ├── ExampleService.cs ├── ExampleServiceTimer.cs ├── PeterKottas.DotNetCore.WindowsService.Example.csproj ├── Program.cs └── Properties │ └── AssemblyInfo.cs ├── PeterKottas.DotNetCore.WindowsService ├── Base │ ├── MicroService.cs │ ├── Timer.cs │ └── Timers.cs ├── Configurators │ └── Service │ │ └── ServiceConfigurator.cs ├── ConsoleServiceHost.cs ├── Enums │ └── ActionEnum.cs ├── ExitCode.cs ├── HostConfiguration.cs ├── HostConfigurator.cs ├── InnerService.cs ├── Interfaces │ ├── IMicroService.cs │ ├── IMicroServiceController.cs │ └── IShutdownableWin32Service.cs ├── MicroServiceController.cs ├── PeterKottas.DotNetCore.WindowsService.csproj ├── PeterKottas.DotNetCore.WindowsService.nuspec ├── Properties │ └── AssemblyInfo.cs ├── ServiceRunner.cs └── StateMachines │ └── ShutdownableServiceStateMachine.cs └── Templates ├── PeterKottas.DotNetCore.WindowsService.MinimalTemplate ├── .template.config │ └── template.json ├── ExampleService.cs ├── PeterKottas.DotNetCore.WindowsService.MinimalTemplate.csproj └── Program.cs ├── PeterKottas.DotNetCore.WindowsService.StandardTemplate ├── .template.config │ └── template.json ├── ExampleService.cs ├── LogFileProvider.cs ├── PeterKottas.DotNetCore.WindowsService.StandardTemplate.csproj ├── Program.cs ├── appsettings.Development.json └── appsettings.json └── PeterKottas.DotNetCore.WindowsService.Templates.nuspec /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cs] 4 | indent_style = space 5 | indent_size = 4 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 |  2 | # Created by https://www.gitignore.io/api/visualstudio,windows 3 | 4 | ### Windows ### 5 | # Windows image file caches 6 | Thumbs.db 7 | ehthumbs.db 8 | 9 | # Folder config file 10 | Desktop.ini 11 | 12 | # Recycle Bin used on file shares 13 | $RECYCLE.BIN/ 14 | 15 | # Windows Installer files 16 | *.cab 17 | *.msi 18 | *.msm 19 | *.msp 20 | 21 | # Windows shortcuts 22 | *.lnk 23 | 24 | 25 | ### VisualStudio ### 26 | ## Ignore Visual Studio temporary files, build results, and 27 | ## files generated by popular Visual Studio add-ons. 28 | 29 | # User-specific files 30 | *.suo 31 | *.user 32 | *.userosscache 33 | *.sln.docstates 34 | 35 | # User-specific files (MonoDevelop/Xamarin Studio) 36 | *.userprefs 37 | 38 | # Build results 39 | [Dd]ebug/ 40 | [Dd]ebugPublic/ 41 | [Rr]elease/ 42 | [Rr]eleases/ 43 | x64/ 44 | x86/ 45 | bld/ 46 | [Bb]in/ 47 | [Oo]bj/ 48 | [Ll]og/ 49 | 50 | # Visual Studio 2015 cache/options directory 51 | Solution/.vs/ 52 | # Uncomment if you have tasks that create the project's static files in wwwroot 53 | #wwwroot/ 54 | 55 | # MSTest test Results 56 | [Tt]est[Rr]esult*/ 57 | [Bb]uild[Ll]og.* 58 | 59 | # NUNIT 60 | *.VisualState.xml 61 | TestResult.xml 62 | 63 | # Build Results of an ATL Project 64 | [Dd]ebugPS/ 65 | [Rr]eleasePS/ 66 | dlldata.c 67 | 68 | # DNX 69 | project.lock.json 70 | project.fragment.lock.json 71 | artifacts/ 72 | **/Properties/launchSettings.json 73 | 74 | *_i.c 75 | *_p.c 76 | *_i.h 77 | *.ilk 78 | *.meta 79 | *.obj 80 | *.pch 81 | *.pdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *.log 92 | *.vspscc 93 | *.vssscc 94 | .builds 95 | *.pidb 96 | *.svclog 97 | *.scc 98 | 99 | # Chutzpah Test files 100 | _Chutzpah* 101 | 102 | # Visual C++ cache files 103 | ipch/ 104 | *.aps 105 | *.ncb 106 | *.opendb 107 | *.opensdf 108 | *.sdf 109 | *.cachefile 110 | *.VC.db 111 | *.VC.VC.opendb 112 | 113 | # Visual Studio profiler 114 | *.psess 115 | *.vsp 116 | *.vspx 117 | *.sap 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # JustCode is a .NET coding add-in 131 | .JustCode 132 | 133 | # TeamCity is a build add-in 134 | _TeamCity* 135 | 136 | # DotCover is a Code Coverage Tool 137 | *.dotCover 138 | 139 | # Visual Studio code coverage results 140 | *.coverage 141 | *.coveragexml 142 | 143 | # NCrunch 144 | _NCrunch_* 145 | .*crunch*.local.xml 146 | nCrunchTemp_* 147 | 148 | # MightyMoose 149 | *.mm.* 150 | AutoTest.Net/ 151 | 152 | # Web workbench (sass) 153 | .sass-cache/ 154 | 155 | # Installshield output folder 156 | [Ee]xpress/ 157 | 158 | # DocProject is a documentation generator add-in 159 | DocProject/buildhelp/ 160 | DocProject/Help/*.HxT 161 | DocProject/Help/*.HxC 162 | DocProject/Help/*.hhc 163 | DocProject/Help/*.hhk 164 | DocProject/Help/*.hhp 165 | DocProject/Help/Html2 166 | DocProject/Help/html 167 | 168 | # Click-Once directory 169 | publish/ 170 | 171 | # Publish Web Output 172 | *.[Pp]ublish.xml 173 | *.azurePubxml 174 | # TODO: Comment the next line if you want to checkin your web deploy settings 175 | # but database connection strings (with potential passwords) will be unencrypted 176 | *.pubxml 177 | *.publishproj 178 | 179 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 180 | # checkin your Azure Web App publish settings, but sensitive information contained 181 | # in these scripts will be unencrypted 182 | PublishScripts/ 183 | 184 | # NuGet Packages 185 | *.nupkg 186 | # The packages folder can be ignored because of Package Restore 187 | **/packages/* 188 | # except build/, which is used as an MSBuild target. 189 | !**/packages/build/ 190 | # Uncomment if necessary however generally it will be regenerated when needed 191 | #!**/packages/repositories.config 192 | # NuGet v3's project.json files produces more ignoreable files 193 | *.nuget.props 194 | *.nuget.targets 195 | 196 | # Microsoft Azure Build Output 197 | csx/ 198 | *.build.csdef 199 | 200 | # Microsoft Azure Emulator 201 | ecf/ 202 | rcf/ 203 | 204 | # Windows Store app package directories and files 205 | AppPackages/ 206 | BundleArtifacts/ 207 | Package.StoreAssociation.xml 208 | _pkginfo.txt 209 | 210 | # Visual Studio cache files 211 | # files ending in .cache can be ignored 212 | *.[Cc]ache 213 | # but keep track of directories ending in .cache 214 | !*.[Cc]ache/ 215 | 216 | # Others 217 | ClientBin/ 218 | ~$* 219 | *~ 220 | *.dbmdl 221 | *.dbproj.schemaview 222 | *.jfm 223 | *.pfx 224 | *.publishsettings 225 | node_modules/ 226 | orleans.codegen.cs 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | 243 | # SQL Server files 244 | *.mdf 245 | *.ldf 246 | 247 | # Business Intelligence projects 248 | *.rdl.data 249 | *.bim.layout 250 | *.bim_*.settings 251 | 252 | # Microsoft Fakes 253 | FakesAssemblies/ 254 | 255 | # GhostDoc plugin setting file 256 | *.GhostDoc.xml 257 | 258 | # Node.js Tools for Visual Studio 259 | .ntvs_analysis.dat 260 | 261 | # Visual Studio 6 build log 262 | *.plg 263 | 264 | # Visual Studio 6 workspace options file 265 | *.opt 266 | 267 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 268 | *.vbw 269 | 270 | # Visual Studio LightSwitch build output 271 | **/*.HTMLClient/GeneratedArtifacts 272 | **/*.DesktopClient/GeneratedArtifacts 273 | **/*.DesktopClient/ModelManifest.xml 274 | **/*.Server/GeneratedArtifacts 275 | **/*.Server/ModelManifest.xml 276 | _Pvt_Extensions 277 | 278 | # Paket dependency manager 279 | .paket/paket.exe 280 | paket-files/ 281 | 282 | # FAKE - F# Make 283 | .fake/ 284 | 285 | # JetBrains Rider 286 | .idea/ 287 | *.sln.iml 288 | 289 | # CodeRush 290 | .cr/ 291 | 292 | # Python Tools for Visual Studio (PTVS) 293 | __pycache__/ 294 | *.pyc 295 | 296 | # Cake - Uncomment if you are using it 297 | # tools/ 298 | 299 | ### VisualStudio Patch ### 300 | build/ 301 | project.lock.json 302 | /Source/Hosts/Frontend/wwwroot/dist/vendor-manifest.json 303 | /.vs 304 | /Source/PeterKottas.DotNetCore.WindowsService/pack.bat 305 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Peter Kottas 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DotNetCore.WindowsService 2 | 3 | Simple library that allows one to host dot net core application as windows services. Perfect solution to power micro-services architecture. 4 | 5 | ## Important note 6 | 7 | This library was created to enable one to host CONSOLE dot net core applications. If you want to host a WEBSITE as a service, you're better of following https://docs.microsoft.com/en-us/aspnet/core/hosting/windows-service 8 | 9 | ## Installation 10 | 11 | Using NuGet: 12 | 13 | `Install-Package PeterKottas.DotNetCore.WindowsService` 14 | 15 | ## Quick start 16 | 17 | Easiest way to start is using a brand new template. Just do: 18 | ``` 19 | dotnet new -i PeterKottas.DotNetCore.WindowsService.Templates::* 20 | ``` 21 | This will add one template at the moment. 22 | 23 | Follow up with this 24 | ``` 25 | mkdir NameOfYourProject 26 | cd NameOfYourProject 27 | dotnet new [ mcrsvc-min | mcrsvc-std ] 28 | ``` 29 | This will create a sample project for you. Next chapter explains its features in more details especially points 6 onwards if you used the template. 30 | 31 | Community, feel encouraged to add more templates if you find something missing/usefull. I'll be more than happy to add these. Just copy the project in https://github.com/PeterKottas/DotNetCore.WindowsService/tree/master/Source/Templates/PeterKottas.DotNetCore.WindowsService.MinimalTemplate and follow instructions in https://github.com/dotnet/templating if you need more specific behvaiour. 32 | 33 | ## Usage 34 | 35 | 1. Create .NETCore console app. 36 | 37 | 2. Create your first service, something like this: 38 | ```cs 39 | public class ExampleService : IMicroService 40 | { 41 | public void Start() 42 | { 43 | Console.WriteLine("I started"); 44 | } 45 | 46 | public void Stop() 47 | { 48 | Console.WriteLine("I stopped"); 49 | } 50 | } 51 | ``` 52 | 2. You can also inherit MicroService base class and take advantage of built in timers: 53 | ```cs 54 | public class ExampleService : MicroService, IMicroService 55 | { 56 | public void Start() 57 | { 58 | this.StartBase(); 59 | Timers.Start("Poller", 1000, () => 60 | { 61 | Console.WriteLine("Polling at {0}\n", DateTime.Now.ToString("o")); 62 | }, 63 | (e) => 64 | { 65 | Console.WriteLine("Exception while polling: {0}\n", e.ToString()); 66 | }); 67 | Console.WriteLine("I started"); 68 | } 69 | 70 | public void Stop() 71 | { 72 | this.StopBase(); 73 | Console.WriteLine("I stopped"); 74 | } 75 | } 76 | ``` 77 | 3. Api for services (and yeah, it's similar to Topshelf, thanks for inspiration, I just couldn't wait for you guys to implement this): 78 | ```cs 79 | ServiceRunner.Run(config => 80 | { 81 | var name = config.GetDefaultName(); 82 | config.Service(serviceConfig => 83 | { 84 | serviceConfig.ServiceFactory((extraArguments, microServiceController) => 85 | { 86 | return new ExampleService(); 87 | }); 88 | serviceConfig.OnStart((service, extraArguments) => 89 | { 90 | Console.WriteLine("Service {0} started", name); 91 | service.Start(); 92 | }); 93 | 94 | serviceConfig.OnStop(service => 95 | { 96 | Console.WriteLine("Service {0} stopped", name); 97 | service.Stop(); 98 | }); 99 | 100 | serviceConfig.OnInstall(service => 101 | { 102 | Console.WriteLine("Service {0} installed", name); 103 | }); 104 | 105 | serviceConfig.OnUnInstall(service => 106 | { 107 | Console.WriteLine("Service {0} uninstalled", name); 108 | }); 109 | 110 | serviceConfig.OnPause(service => 111 | { 112 | Console.WriteLine("Service {0} paused", name); 113 | }); 114 | 115 | serviceConfig.OnContinue(service => 116 | { 117 | Console.WriteLine("Service {0} continued", name); 118 | }); 119 | 120 | serviceConfig.OnShutdown(service => 121 | { 122 | Console.WriteLine("Service {0} shutdown", name); 123 | }); 124 | 125 | serviceConfig.OnError(e => 126 | { 127 | Console.WriteLine("Service {0} errored with exception : {1}", name, e.Message); 128 | }); 129 | }); 130 | }); 131 | ``` 132 | 4. Optionally set the name of the service like this: 133 | 134 | ```cs 135 | ServiceRunner.Run(config => 136 | { 137 | config.SetName("MyTestService"); 138 | }); 139 | ``` 140 | 5. Run the service without arguments and it runs like console app. 141 | 6. Run the service with **action:install** and it will install the service. 142 | 7. Run the service with **action:uninstall** and it will uninstall the service. 143 | 8. Run the service with **action:start** and it will start the service. 144 | 9. Run the service with **action:stop** and it will stop the service. 145 | 9. Run the service with **action:pause** and it will pause the service. 146 | 9. Run the service with **action:continue** and it will continue the service. 147 | 10. Run the service with **username:YOUR_USERNAME**, **password:YOUR_PASSWORD** and **action:install** which installs it for the given account. 148 | 11. Run the service with **built-in-account:(NetworkService|LocalService|LocalSystem)** and **action:install** which installs it for the given built in account. Defaults to **LocalSystem**. 149 | 12. Run the service with **description:YOUR_DESCRIPTION** and it setup description for the service. 150 | 13. Run the service with **display-name:YOUR_DISPLAY_NAME** and it setup Display name for the service. 151 | 14. Run the service with **name:YOUR_NAME** and it setup name for the service. 152 | 15. Run the service with **start-immediately:(true|false)** to start service immediately after install. Defaults to **true**. 153 | 16. You can find the complete example in [PeterKottas.DotNetCore.Example](https://github.com/PeterKottas/DotNetCore.WindowsService/tree/master/Source/PeterKottas.DotNetCore.WindowsService.Example) project. 154 | 17. Install the service using powershell: `dotnet.exe $serviceDllPath action:install` 155 | 156 | ## Created and sponsored by 157 | 158 | - [GuestBell](https://guestbell.com/) - Customer centric online POS for Hotels and short terms stays. 159 | 160 | ## Contributing 161 | 162 | 1. Fork it! 163 | 2. Create your feature branch: `git checkout -b my-new-feature` 164 | 3. Commit your changes: `git commit -am 'Add some feature'` 165 | 4. Push to the branch: `git push origin my-new-feature` 166 | 5. Submit a [pull request](https://github.com/PeterKottas/DotNetCore.WindowsService/pulls) :D 167 | 168 | ## License 169 | 170 | [MIT](https://github.com/PeterKottas/DotNetCore.WindowsService/blob/master/LICENSE) 171 | 172 | ## Credit 173 | 174 | Huge thanks goes to [@dasMulli](https://github.com/dasMulli) the guy behind a useful [lib](https://github.com/dasMulli/dotnet-win32-service) which is one of the dependecies for this library. 175 | -------------------------------------------------------------------------------- /Solution/DotNetCore.WindowsService.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2005 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PeterKottas.DotNetCore.WindowsService", "..\Source\PeterKottas.DotNetCore.WindowsService\PeterKottas.DotNetCore.WindowsService.csproj", "{19F85232-0FED-439E-90BF-BDCD6567F2B3}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PeterKottas.DotNetCore.WindowsService.Example", "..\Source\PeterKottas.DotNetCore.WindowsService.Example\PeterKottas.DotNetCore.WindowsService.Example.csproj", "{63C67004-73D7-4D49-8A3F-3CBC6554BB46}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PeterKottas.DotNetCore.WindowsService.MinimalTemplate", "..\Source\Templates\PeterKottas.DotNetCore.WindowsService.MinimalTemplate\PeterKottas.DotNetCore.WindowsService.MinimalTemplate.csproj", "{D5FE4BD5-85F0-42D8-89D7-C8AF272278F3}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{7DFFD5B6-AE3F-478B-9893-5010AEA29096}" 13 | ProjectSection(SolutionItems) = preProject 14 | ..\.editorconfig = ..\.editorconfig 15 | EndProjectSection 16 | EndProject 17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PeterKottas.DotNetCore.WindowsService.StandardTemplate", "..\Source\Templates\PeterKottas.DotNetCore.WindowsService.StandardTemplate\PeterKottas.DotNetCore.WindowsService.StandardTemplate.csproj", "{6B26C0CE-CB7C-49FA-92F6-4B13D3881E51}" 18 | EndProject 19 | Global 20 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 21 | Debug|Any CPU = Debug|Any CPU 22 | Release|Any CPU = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {19F85232-0FED-439E-90BF-BDCD6567F2B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {19F85232-0FED-439E-90BF-BDCD6567F2B3}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {19F85232-0FED-439E-90BF-BDCD6567F2B3}.Release|Any CPU.ActiveCfg = Release|Any CPU 28 | {19F85232-0FED-439E-90BF-BDCD6567F2B3}.Release|Any CPU.Build.0 = Release|Any CPU 29 | {63C67004-73D7-4D49-8A3F-3CBC6554BB46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 30 | {63C67004-73D7-4D49-8A3F-3CBC6554BB46}.Debug|Any CPU.Build.0 = Debug|Any CPU 31 | {63C67004-73D7-4D49-8A3F-3CBC6554BB46}.Release|Any CPU.ActiveCfg = Release|Any CPU 32 | {63C67004-73D7-4D49-8A3F-3CBC6554BB46}.Release|Any CPU.Build.0 = Release|Any CPU 33 | {D5FE4BD5-85F0-42D8-89D7-C8AF272278F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {D5FE4BD5-85F0-42D8-89D7-C8AF272278F3}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {D5FE4BD5-85F0-42D8-89D7-C8AF272278F3}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {D5FE4BD5-85F0-42D8-89D7-C8AF272278F3}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {6B26C0CE-CB7C-49FA-92F6-4B13D3881E51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {6B26C0CE-CB7C-49FA-92F6-4B13D3881E51}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {6B26C0CE-CB7C-49FA-92F6-4B13D3881E51}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {6B26C0CE-CB7C-49FA-92F6-4B13D3881E51}.Release|Any CPU.Build.0 = Release|Any CPU 41 | EndGlobalSection 42 | GlobalSection(SolutionProperties) = preSolution 43 | HideSolutionNode = FALSE 44 | EndGlobalSection 45 | GlobalSection(ExtensibilityGlobals) = postSolution 46 | SolutionGuid = {6DD17605-730C-4A8D-8875-05FD5096DAEF} 47 | EndGlobalSection 48 | EndGlobal 49 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService.Example/ExampleService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.PlatformAbstractions; 2 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 3 | using System; 4 | using System.IO; 5 | using System.Timers; 6 | 7 | namespace PeterKottas.DotNetCore.WindowsService.Example 8 | { 9 | public class ExampleService : IMicroService 10 | { 11 | private IMicroServiceController controller; 12 | 13 | private Timer timer = new Timer(1000); 14 | 15 | public ExampleService() 16 | { 17 | controller = null; 18 | } 19 | 20 | public ExampleService(IMicroServiceController controller) 21 | { 22 | this.controller = controller; 23 | } 24 | 25 | private string fileName = Path.Combine(System.AppContext.BaseDirectory, "log.txt"); 26 | public void Start() 27 | { 28 | Console.WriteLine("I started"); 29 | Console.WriteLine(fileName); 30 | File.AppendAllText(fileName, "Started\n"); 31 | 32 | /** 33 | * A timer is a simple example. But this could easily 34 | * be a port or messaging queue client 35 | */ 36 | timer.Elapsed += _timer_Elapsed; 37 | timer.Start(); 38 | } 39 | 40 | private void _timer_Elapsed(object sender, ElapsedEventArgs e) 41 | { 42 | File.AppendAllText(fileName, string.Format("Polling at {0}\n", DateTime.Now.ToString("o"))); 43 | } 44 | 45 | public void Stop() 46 | { 47 | timer.Stop(); 48 | File.AppendAllText(fileName, "Stopped\n"); 49 | Console.WriteLine("I stopped"); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService.Example/ExampleServiceTimer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.PlatformAbstractions; 2 | using PeterKottas.DotNetCore.WindowsService.Base; 3 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 4 | using System; 5 | using System.IO; 6 | 7 | namespace PeterKottas.DotNetCore.WindowsService.Example 8 | { 9 | public class ExampleServiceTimer : MicroService, IMicroService 10 | { 11 | private IMicroServiceController controller; 12 | 13 | public ExampleServiceTimer() 14 | { 15 | controller = null; 16 | } 17 | 18 | public ExampleServiceTimer(IMicroServiceController controller) 19 | { 20 | this.controller = controller; 21 | } 22 | 23 | private string fileName = Path.Combine(System.AppContext.BaseDirectory, "log.txt"); 24 | public void Start() 25 | { 26 | StartBase(); 27 | Timers.Start("Poller", 1000, () => 28 | { 29 | File.AppendAllText(fileName, string.Format("Polling at {0}\n", DateTime.Now.ToString("o"))); 30 | }); 31 | Console.WriteLine("I started"); 32 | File.AppendAllText(fileName, "Started\n"); 33 | } 34 | 35 | public void Stop() 36 | { 37 | StopBase(); 38 | File.AppendAllText(fileName, "Stopped\n"); 39 | Console.WriteLine("I stopped"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService.Example/PeterKottas.DotNetCore.WindowsService.Example.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.0 5 | Exe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService.Example/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.PlatformAbstractions; 2 | using System; 3 | using System.IO; 4 | 5 | namespace PeterKottas.DotNetCore.WindowsService.Example 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | var fileName = Path.Combine(System.AppContext.BaseDirectory, "log.txt"); 12 | ServiceRunner.Run(config => 13 | { 14 | var name = config.GetDefaultName(); 15 | config.Service(serviceConfig => 16 | { 17 | serviceConfig.ServiceFactory((extraArguments, controller) => 18 | { 19 | return new ExampleService(controller); 20 | }); 21 | 22 | serviceConfig.OnStart((service, extraParams) => 23 | { 24 | Console.WriteLine("Service {0} started", name); 25 | service.Start(); 26 | }); 27 | 28 | serviceConfig.OnStop(service => 29 | { 30 | Console.WriteLine("Service {0} stopped", name); 31 | service.Stop(); 32 | }); 33 | 34 | serviceConfig.OnShutdown(service => 35 | { 36 | Console.WriteLine("Service {0} shutdown", name); 37 | File.AppendAllText(fileName, $"Service {name} shutdown\n"); 38 | }); 39 | 40 | serviceConfig.OnError(e => 41 | { 42 | File.AppendAllText(fileName, $"Exception: {e.ToString()}\n"); 43 | Console.WriteLine("Service {0} errored with exception : {1}", name, e.Message); 44 | }); 45 | }); 46 | }); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService.Example/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTrademark("")] 9 | 10 | // Setting ComVisible to false makes the types in this assembly not visible 11 | // to COM components. If you need to access a type in this assembly from 12 | // COM, set the ComVisible attribute to true on that type. 13 | [assembly: ComVisible(false)] 14 | 15 | // The following GUID is for the ID of the typelib if this project is exposed to COM 16 | [assembly: Guid("3f56fc81-7ee5-45e8-864d-5865e3b05d4a")] 17 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Base/MicroService.cs: -------------------------------------------------------------------------------- 1 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | 7 | namespace PeterKottas.DotNetCore.WindowsService.Base 8 | { 9 | public class MicroService : IDisposable 10 | { 11 | protected Timers Timers { get; private set; } 12 | private bool disposed = false; 13 | 14 | public void StartBase() 15 | { 16 | Timers = new Timers(); 17 | } 18 | 19 | public void StopBase() 20 | { 21 | Timers.Stop(); 22 | } 23 | 24 | /// 25 | /// Dispose 26 | /// 27 | public void Dispose() 28 | { 29 | Dispose(true); 30 | GC.SuppressFinalize(this); 31 | } 32 | 33 | protected virtual void Dispose(bool disposing) 34 | { 35 | if (disposed) 36 | return; 37 | 38 | if (disposing) 39 | { 40 | StopBase(); 41 | } 42 | 43 | disposed = true; 44 | } 45 | 46 | ~MicroService() 47 | { 48 | Dispose(false); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Base/Timer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | 7 | namespace PeterKottas.DotNetCore.WindowsService.Base 8 | { 9 | /// 10 | /// Betfred timer 11 | /// 12 | public class Timer 13 | { 14 | private Thread thread; 15 | private AutoResetEvent stopRequest; 16 | private bool running = true; 17 | private bool paused = false; 18 | 19 | public Action OnTimer { get; set; } 20 | 21 | public Action OnException { get; set; } 22 | 23 | public string Name { get; private set; } 24 | 25 | public int Interval { get; set; } 26 | 27 | public Timer(string name, int interval, Action onTimer, Action onException = null) 28 | { 29 | this.OnTimer = onTimer == null ? () => { } : onTimer; ; 30 | this.Name = name; 31 | this.Interval = interval; 32 | this.OnException = onException == null ? (e) => { } : onException; 33 | } 34 | 35 | private void InternalWork(object arg) 36 | { 37 | while (running) 38 | { 39 | try 40 | { 41 | if (!paused) 42 | { 43 | this.OnTimer(); 44 | } 45 | } 46 | catch (Exception exception) 47 | { 48 | this.OnException(exception); 49 | } 50 | 51 | try 52 | { 53 | if (stopRequest.WaitOne(Interval)) 54 | { 55 | return; 56 | } 57 | } 58 | catch (Exception exception) 59 | { 60 | this.OnException(exception); 61 | } 62 | 63 | } 64 | } 65 | 66 | public void Start() 67 | { 68 | stopRequest = new AutoResetEvent(false); 69 | running = true; 70 | thread = new Thread(InternalWork); 71 | thread.Start(); 72 | } 73 | 74 | public void Pause() 75 | { 76 | paused = true; 77 | } 78 | 79 | public void Resume() 80 | { 81 | paused = false; 82 | } 83 | 84 | public void Stop() 85 | { 86 | if (running) 87 | { 88 | running = false; 89 | stopRequest.Set(); 90 | thread.Join(); 91 | 92 | thread = null; 93 | stopRequest.Dispose(); 94 | stopRequest = null; 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Base/Timers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace PeterKottas.DotNetCore.WindowsService.Base 7 | { 8 | public class Timers 9 | { 10 | List timers = new List(); 11 | 12 | public void Start(string timerName, int interval, Action onTimer, Action onException = null) 13 | { 14 | var tmpTimer = timers.Where(x => x.Name == timerName).FirstOrDefault(); 15 | if (tmpTimer == null) 16 | { 17 | tmpTimer = new Timer(timerName, interval, onTimer, onException); 18 | timers.Add(tmpTimer); 19 | 20 | tmpTimer.Start(); 21 | } 22 | else 23 | { 24 | tmpTimer.Stop(); 25 | Update(timerName, interval, onTimer, onException); 26 | tmpTimer.Start(); 27 | } 28 | } 29 | 30 | public void Update(string timerName, int interval = 0, Action onTimer = null, Action onException = null) 31 | { 32 | var tmpTimer = timers.Where(x => x.Name == timerName).FirstOrDefault(); 33 | if (tmpTimer != null) 34 | { 35 | if (onTimer != null) 36 | { 37 | tmpTimer.OnTimer = onTimer; 38 | } 39 | if (onException != null) 40 | { 41 | tmpTimer.OnException = onException; 42 | } 43 | if (interval > 0 && interval != tmpTimer.Interval) 44 | { 45 | tmpTimer.Interval = interval; 46 | } 47 | } 48 | } 49 | 50 | public void Resume() 51 | { 52 | foreach (var timer in timers) 53 | { 54 | timer.Resume(); 55 | } 56 | } 57 | 58 | public void Resume(string timerName) 59 | { 60 | var tmpTimer = timers.Where(x => x.Name == timerName).FirstOrDefault(); 61 | if (tmpTimer != null) 62 | { 63 | tmpTimer.Resume(); 64 | } 65 | } 66 | 67 | public void Pause() 68 | { 69 | foreach (var timer in timers) 70 | { 71 | timer.Pause(); 72 | } 73 | } 74 | 75 | public void Pause(string timerName) 76 | { 77 | var tmpTimer = timers.Where(x => x.Name == timerName).FirstOrDefault(); 78 | if (tmpTimer != null) 79 | { 80 | tmpTimer.Pause(); 81 | } 82 | } 83 | 84 | public void Stop() 85 | { 86 | foreach (var timer in timers) 87 | { 88 | timer.Stop(); 89 | } 90 | } 91 | 92 | public void Stop(string timerName) 93 | { 94 | var tmpTimer = timers.Where(x => x.Name == timerName).FirstOrDefault(); 95 | if (tmpTimer != null) 96 | { 97 | tmpTimer.Stop(); 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Configurators/Service/ServiceConfigurator.cs: -------------------------------------------------------------------------------- 1 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace PeterKottas.DotNetCore.WindowsService.Configurators.Service 6 | { 7 | public class ServiceConfigurator 8 | { 9 | private HostConfiguration config; 10 | public ServiceConfigurator(HostConfiguration config) 11 | { 12 | this.config = config; 13 | } 14 | 15 | public void ServiceFactory(Func, IMicroServiceController, TService> serviceFactory) 16 | { 17 | config.ServiceFactory = serviceFactory; 18 | } 19 | 20 | public void OnStart(Action> onStart) 21 | { 22 | config.OnServiceStart = onStart; 23 | } 24 | 25 | public void OnStop(Action onStop) 26 | { 27 | config.OnServiceStop = onStop; 28 | } 29 | 30 | public void OnError(Action onError) 31 | { 32 | config.OnServiceError = onError; 33 | } 34 | 35 | public void OnPause(Action onPause) 36 | { 37 | config.OnServicePause = onPause; 38 | } 39 | 40 | public void OnInstall(Action onInstall) 41 | { 42 | config.OnServiceInstall = onInstall; 43 | } 44 | 45 | public void OnUnInstall(Action onUnInstall) 46 | { 47 | config.OnServiceUnInstall = onUnInstall; 48 | } 49 | 50 | public void OnContinue(Action onContinue) 51 | { 52 | config.OnServiceContinue = onContinue; 53 | } 54 | 55 | public void OnShutdown(Action onShutdown) 56 | { 57 | config.OnServiceShutdown = onShutdown; 58 | } 59 | 60 | public void OnCustomCommand(Action onCustomCommand) { 61 | config.OnServiceCustomCommand = onCustomCommand; 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/ConsoleServiceHost.cs: -------------------------------------------------------------------------------- 1 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace PeterKottas.DotNetCore.WindowsService 7 | { 8 | /// 9 | /// Copy of Topshelf ConsoleRunHost 10 | /// https://github.com/Topshelf/Topshelf/blob/develop/src/Topshelf/Hosts/ConsoleRunHost.cs 11 | /// 12 | class ConsoleServiceHost 13 | { 14 | private InnerService _consoleService = null; 15 | private HostConfiguration _innerConfig = null; 16 | private ExitCode _exitCode = 0; 17 | private ManualResetEvent _exit = null; 18 | private volatile bool _hasCancelled = false; 19 | 20 | public ConsoleServiceHost(InnerService consoleService, HostConfiguration innerConfig) 21 | { 22 | _consoleService = consoleService 23 | ?? throw new ArgumentNullException(nameof(consoleService)); 24 | 25 | _innerConfig = innerConfig 26 | ?? throw new ArgumentNullException(nameof(innerConfig)); 27 | } 28 | 29 | internal ExitCode Run() 30 | { 31 | AppDomain.CurrentDomain.UnhandledException += CatchUnhandledException; 32 | 33 | bool started = false; 34 | try 35 | { 36 | Console.WriteLine("Starting up as a console service host"); 37 | 38 | _exit = new ManualResetEvent(false); 39 | _exitCode = ExitCode.Ok; 40 | 41 | Console.Title = _consoleService.ServiceName; 42 | Console.CancelKeyPress += HandleCancelKeyPress; 43 | 44 | _consoleService.Start(_innerConfig.ExtraArguments.ToArray(), () => Console.WriteLine("Stopping console service host")); 45 | started = true; 46 | 47 | Console.WriteLine("The {0} service is now running, press Control+C to exit.", _consoleService.ServiceName); 48 | 49 | _exit.WaitOne(); 50 | } 51 | catch (Exception ex) 52 | { 53 | Console.WriteLine("An exception occurred", ex); 54 | 55 | return ExitCode.AbnormalExit; 56 | } 57 | finally 58 | { 59 | if (started) 60 | StopService(); 61 | 62 | _exit.Close(); 63 | (_exit as IDisposable).Dispose(); 64 | } 65 | 66 | return _exitCode; 67 | } 68 | 69 | internal void StopService() 70 | { 71 | try 72 | { 73 | if (_hasCancelled) 74 | return; 75 | 76 | Console.WriteLine("Stopping the {0} service", _consoleService.ServiceName); 77 | 78 | Task stopTask = Task.Run(() => _consoleService.Stop()); 79 | if (!stopTask.Wait(TimeSpan.FromMilliseconds(_innerConfig.ServiceTimeout))) 80 | throw new Exception("The service failed to stop (returned false)."); 81 | 82 | _exitCode = ExitCode.Ok; 83 | } 84 | catch (Exception ex) 85 | { 86 | Console.WriteLine("The service did not shut down gracefully: {0}", ex.ToString()); 87 | _exitCode = ExitCode.AbnormalExit; 88 | } 89 | finally 90 | { 91 | Console.WriteLine("The {0} service has stopped.", _consoleService.ServiceName); 92 | _exitCode = ExitCode.Ok; 93 | } 94 | } 95 | 96 | private void HandleCancelKeyPress(object sender, ConsoleCancelEventArgs e) 97 | { 98 | if (e.SpecialKey == ConsoleSpecialKey.ControlBreak) 99 | { 100 | Console.WriteLine("Control+Break detected, terminating service (not cleanly, use Control+C to exit cleanly)"); 101 | return; 102 | } 103 | 104 | e.Cancel = true; 105 | 106 | if (_hasCancelled) 107 | return; 108 | 109 | Console.WriteLine("Control+C detected, attempting to stop service."); 110 | Task stopTask = Task.Run(() => _consoleService.Stop()); 111 | if (stopTask.Wait(TimeSpan.FromMilliseconds(_innerConfig.ConsoleTimeout))) 112 | { 113 | _hasCancelled = true; 114 | _exit.Set(); 115 | } 116 | else 117 | { 118 | _hasCancelled = false; 119 | Console.WriteLine("The service is not in a state where it can be stopped."); 120 | } 121 | } 122 | 123 | private void CatchUnhandledException(object sender, UnhandledExceptionEventArgs e) 124 | { 125 | Console.WriteLine("The service threw an unhandled exception: {0}", e.ToString()); 126 | 127 | if (!e.IsTerminating) 128 | return; 129 | 130 | _exitCode = ExitCode.UnhandledServiceException; 131 | _exit.Set(); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Enums/ActionEnum.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace PeterKottas.DotNetCore.WindowsService.Enums 7 | { 8 | public enum ActionEnum 9 | { 10 | Install, 11 | Uninstall, 12 | Run, 13 | RunInteractive, 14 | Stop, 15 | Start, 16 | Pause, 17 | Continue, 18 | CustomCommand 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/ExitCode.cs: -------------------------------------------------------------------------------- 1 | namespace PeterKottas.DotNetCore.WindowsService 2 | { 3 | /// 4 | /// Copy of: https://github.com/Topshelf/Topshelf/blob/develop/src/Topshelf/TopshelfExitCode.cs 5 | /// 6 | enum ExitCode 7 | { 8 | Ok = 0, 9 | AbnormalExit = 1, 10 | SudoRequired = 2, 11 | ServiceAlreadyInstalled = 3, 12 | ServiceNotInstalled = 4, 13 | StartServiceFailed = 5, 14 | StopServiceFailed = 6, 15 | ServiceAlreadyRunning = 7, 16 | UnhandledServiceException = 8, 17 | ServiceNotRunning = 9, 18 | SendCommandFailed = 10, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/HostConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using PeterKottas.DotNetCore.WindowsService.Enums; 4 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 5 | using DasMulli.Win32.ServiceUtils; 6 | 7 | namespace PeterKottas.DotNetCore.WindowsService 8 | { 9 | public class HostConfiguration 10 | { 11 | public HostConfiguration() 12 | { 13 | OnServiceStop = service => { }; 14 | OnServiceShutdown = service => { }; 15 | OnServiceError = e => 16 | { 17 | Console.WriteLine(e.ToString()); 18 | }; 19 | } 20 | 21 | public ActionEnum Action { get; set; } 22 | 23 | public string Username { get; set; } 24 | 25 | public string Password { get; set; } 26 | 27 | public string Name { get; set; } 28 | 29 | public string Description { get; set; } 30 | 31 | public string DisplayName { get; set; } 32 | 33 | public bool StartImmediately { get; set; } = true; 34 | 35 | public int ConsoleTimeout { get; set; } = 150; 36 | 37 | public int ServiceTimeout { get; set; } = 1500; 38 | 39 | public Win32ServiceCredentials DefaultCred { get; set; } = Win32ServiceCredentials.LocalSystem; 40 | 41 | public TService Service { get; set; } 42 | 43 | public Func, IMicroServiceController, TService> ServiceFactory { get; set; } 44 | 45 | public Action> OnServiceStart { get; set; } 46 | 47 | public Action OnServiceStop { get; set; } 48 | 49 | public Action OnServiceInstall { get; set; } 50 | 51 | public Action OnServiceUnInstall { get; set; } 52 | 53 | public Action OnServicePause { get; set; } 54 | 55 | public Action OnServiceContinue { get; set; } 56 | 57 | public Action OnServiceShutdown { get; set; } 58 | 59 | public Action OnServiceError { get; set; } 60 | 61 | public Action OnServiceCustomCommand { get; set; } 62 | 63 | public List ExtraArguments { get; set; } 64 | 65 | public int CustomCommand { get; set; } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/HostConfigurator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using PeterKottas.DotNetCore.WindowsService.Configurators.Service; 3 | 4 | namespace PeterKottas.DotNetCore.WindowsService 5 | { 6 | public class HostConfigurator 7 | { 8 | HostConfiguration innerConfig; 9 | public HostConfigurator(HostConfiguration innerConfig) 10 | { 11 | this.innerConfig = innerConfig; 12 | } 13 | 14 | public void SetName(string serviceName, bool force = false) 15 | { 16 | if (!string.IsNullOrEmpty(innerConfig.Name) || force) 17 | { 18 | innerConfig.Name = serviceName; 19 | } 20 | } 21 | 22 | public void SetDisplayName(string displayName, bool force = false) 23 | { 24 | if (!string.IsNullOrEmpty(innerConfig.DisplayName) || force) 25 | { 26 | innerConfig.DisplayName = displayName; 27 | } 28 | } 29 | 30 | public void SetDescription(string description, bool force = false) 31 | { 32 | if (!string.IsNullOrEmpty(innerConfig.Description) || force) 33 | { 34 | innerConfig.Description = description; 35 | } 36 | } 37 | 38 | public void SetConsoleTimeout(int milliseconds) 39 | { 40 | innerConfig.ConsoleTimeout = milliseconds; 41 | } 42 | 43 | public void SetServiceTimeout(int milliseconds) 44 | { 45 | innerConfig.ServiceTimeout = milliseconds; 46 | } 47 | 48 | public string GetDefaultName() 49 | { 50 | return innerConfig.Name; 51 | } 52 | 53 | public bool IsNameNullOrEmpty 54 | { 55 | get 56 | { 57 | return string.IsNullOrEmpty(innerConfig.Name); 58 | } 59 | } 60 | 61 | public bool IsDescriptionNullOrEmpty 62 | { 63 | get 64 | { 65 | return string.IsNullOrEmpty(innerConfig.Description); 66 | } 67 | } 68 | 69 | public bool IsDisplayNameNullOrEmpty 70 | { 71 | get 72 | { 73 | return string.IsNullOrEmpty(innerConfig.DisplayName); 74 | } 75 | } 76 | 77 | public void Service(Action> serviceConfigAction) 78 | { 79 | try 80 | { 81 | var serviceConfig = new ServiceConfigurator(innerConfig); 82 | serviceConfigAction(serviceConfig); 83 | if (innerConfig.ServiceFactory == null) 84 | { 85 | throw new ArgumentException("It's necesarry to configure action that creates the service (ServiceFactory)"); 86 | } 87 | 88 | if (innerConfig.OnServiceStart == null) 89 | { 90 | throw new ArgumentException("It's necesarry to configure action that is called when the service starts"); 91 | } 92 | } 93 | catch (Exception e) 94 | { 95 | throw new ArgumentException("Configuring the service thrown an exception", e); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/InnerService.cs: -------------------------------------------------------------------------------- 1 | using DasMulli.Win32.ServiceUtils; 2 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 3 | using System; 4 | 5 | namespace PeterKottas.DotNetCore.WindowsService 6 | { 7 | public class InnerService : IShutdownableWin32Service 8 | { 9 | string serviceName; 10 | Action onStart; 11 | Action onStopped; 12 | Action onShutdown; 13 | 14 | public InnerService(string serviceName, Action onStart, Action onStopped, Action onShutdown) 15 | { 16 | this.serviceName = serviceName; 17 | this.onStart = onStart; 18 | this.onStopped = onStopped; 19 | this.onShutdown = onShutdown; 20 | } 21 | 22 | public string ServiceName 23 | { 24 | get 25 | { 26 | return serviceName; 27 | } 28 | } 29 | 30 | public void Shutdown() 31 | { 32 | onShutdown(); 33 | } 34 | 35 | public void Start(string[] startupArguments, ServiceStoppedCallback serviceStoppedCallback) 36 | { 37 | try 38 | { 39 | onStart(); 40 | } 41 | catch (Exception) 42 | { 43 | onStopped(); 44 | serviceStoppedCallback(); 45 | } 46 | } 47 | 48 | public void Stop() 49 | { 50 | onStopped(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Interfaces/IMicroService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace PeterKottas.DotNetCore.WindowsService.Interfaces 7 | { 8 | public interface IMicroService 9 | { 10 | void Start(); 11 | void Stop(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Interfaces/IMicroServiceController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace PeterKottas.DotNetCore.WindowsService.Interfaces 6 | { 7 | public interface IMicroServiceController 8 | { 9 | void Stop(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Interfaces/IShutdownableWin32Service.cs: -------------------------------------------------------------------------------- 1 | using DasMulli.Win32.ServiceUtils; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace PeterKottas.DotNetCore.WindowsService.Interfaces 7 | { 8 | public interface IShutdownableWin32Service : IWin32Service 9 | { 10 | void Shutdown(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/MicroServiceController.cs: -------------------------------------------------------------------------------- 1 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 2 | using System; 3 | 4 | namespace PeterKottas.DotNetCore.WindowsService 5 | { 6 | public class MicroServiceController : IMicroServiceController 7 | { 8 | private Action stop; 9 | 10 | public MicroServiceController(Action stop) 11 | { 12 | this.stop = stop; 13 | } 14 | 15 | public void Stop() 16 | { 17 | stop(); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/PeterKottas.DotNetCore.WindowsService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.0;net461 5 | 2.0.12 6 | 2.0.12.0 7 | 2.0.12.0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/PeterKottas.DotNetCore.WindowsService.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PeterKottas.DotNetCore.WindowsService 5 | PeterKottas.DotNetCore.WindowsService 6 | 2.0.12 7 | Peter Kottas 8 | Easiest to use library that allows one to host .net core as windows services there is. 9 | .NET Core Windows service 10 | en-US 11 | dotnetcore,.netcore,'windows service','micro service' 12 | https://github.com/PeterKottas/DotNetCore.WindowsService 13 | https://github.com/PeterKottas/DotNetCore.WindowsService/blob/master/LICENSE 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTrademark("")] 9 | 10 | // Setting ComVisible to false makes the types in this assembly not visible 11 | // to COM components. If you need to access a type in this assembly from 12 | // COM, set the ComVisible attribute to true on that type. 13 | [assembly: ComVisible(false)] 14 | 15 | // The following GUID is for the ID of the typelib if this project is exposed to COM 16 | [assembly: Guid("19f85232-0fed-439e-90bf-bdcd6567f2b3")] 17 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/ServiceRunner.cs: -------------------------------------------------------------------------------- 1 | using DasMulli.Win32.ServiceUtils; 2 | using System; 3 | using PeterKottas.DotNetCore.WindowsService.Enums; 4 | using System.Diagnostics; 5 | using System.ServiceProcess; 6 | using PeterKottas.DotNetCore.CmdArgParser; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 10 | using Microsoft.Extensions.PlatformAbstractions; 11 | using System.IO; 12 | using System.Threading.Tasks; 13 | using PeterKottas.DotNetCore.WindowsService.StateMachines; 14 | 15 | namespace PeterKottas.DotNetCore.WindowsService 16 | { 17 | public static class ServiceRunner 18 | { 19 | public static int Run(Action> runAction) 20 | { 21 | Directory.SetCurrentDirectory(PlatformServices.Default.Application.ApplicationBasePath); 22 | 23 | var innerConfig = new HostConfiguration(); 24 | innerConfig.Action = ActionEnum.RunInteractive; 25 | innerConfig.Name = typeof(SERVICE).FullName; 26 | 27 | innerConfig.ExtraArguments = Parser.Parse(config => 28 | { 29 | config.AddParameter(new CmdArgParam() 30 | { 31 | Key = "username", 32 | Description = "Username for the service account", 33 | Value = val => 34 | { 35 | innerConfig.Username = val; 36 | } 37 | }); 38 | config.AddParameter(new CmdArgParam() 39 | { 40 | Key = "password", 41 | Description = "Password for the service account", 42 | Value = val => 43 | { 44 | innerConfig.Password = val; 45 | } 46 | }); 47 | config.AddParameter(new CmdArgParam() 48 | { 49 | Key = "built-in-account", 50 | Description = "Password for the service account", 51 | Value = val => 52 | { 53 | switch (val.ToLower()) 54 | { 55 | case "localsystem": 56 | innerConfig.DefaultCred = Win32ServiceCredentials.LocalSystem; 57 | break; 58 | case "localservice": 59 | innerConfig.DefaultCred = Win32ServiceCredentials.LocalService; 60 | break; 61 | case "networkservice": 62 | innerConfig.DefaultCred = Win32ServiceCredentials.NetworkService; 63 | break; 64 | default: 65 | innerConfig.DefaultCred = Win32ServiceCredentials.LocalSystem; 66 | break; 67 | } 68 | 69 | } 70 | }); 71 | config.AddParameter(new CmdArgParam() 72 | { 73 | Key = "start-immediately", 74 | Description = "Start the service immediately when installing.", 75 | Value = val => 76 | { 77 | if (bool.TryParse(val, out var startImmediately)) 78 | { 79 | innerConfig.StartImmediately = startImmediately; 80 | } 81 | } 82 | }); 83 | config.AddParameter(new CmdArgParam() 84 | { 85 | Key = "name", 86 | Description = "Service name", 87 | Value = val => 88 | { 89 | innerConfig.Name = val; 90 | } 91 | }); 92 | config.AddParameter(new CmdArgParam() 93 | { 94 | Key = "description", 95 | Description = "Service description", 96 | Value = val => 97 | { 98 | innerConfig.Description = val; 99 | } 100 | }); 101 | config.AddParameter(new CmdArgParam() 102 | { 103 | Key = "display-name", 104 | Description = "Service display name", 105 | Value = val => 106 | { 107 | innerConfig.DisplayName = val; 108 | } 109 | }); 110 | config.AddParameter(new CmdArgParam() { 111 | Key = "command", 112 | Description = "Send a custom command to the service.", 113 | Value = val => { 114 | if (int.TryParse(val, out var command)) { 115 | innerConfig.CustomCommand = command; 116 | } 117 | } 118 | }); 119 | config.AddParameter(new CmdArgParam() 120 | { 121 | Key = "action", 122 | Description = "Installs the service. It's run like console application otherwise", 123 | Value = val => 124 | { 125 | switch (val) 126 | { 127 | case "install": 128 | innerConfig.Action = ActionEnum.Install; 129 | break; 130 | case "pause": 131 | innerConfig.Action = ActionEnum.Pause; 132 | break; 133 | case "continue": 134 | innerConfig.Action = ActionEnum.Continue; 135 | break; 136 | case "start": 137 | innerConfig.Action = ActionEnum.Start; 138 | break; 139 | case "stop": 140 | innerConfig.Action = ActionEnum.Stop; 141 | break; 142 | case "uninstall": 143 | innerConfig.Action = ActionEnum.Uninstall; 144 | break; 145 | case "run": 146 | innerConfig.Action = ActionEnum.Run; 147 | break; 148 | case "run-interactive": 149 | innerConfig.Action = ActionEnum.RunInteractive; 150 | break; 151 | case "custom-command": 152 | innerConfig.Action = ActionEnum.CustomCommand; 153 | break; 154 | default: 155 | Console.WriteLine("{0} is unrecognized, will run the app as console application instead"); 156 | innerConfig.Action = ActionEnum.RunInteractive; 157 | break; 158 | } 159 | } 160 | }); 161 | 162 | config.UseDefaultHelp(); 163 | config.UseAppDescription("Sample microservice application"); 164 | }); 165 | 166 | if (string.IsNullOrEmpty(innerConfig.Name)) 167 | { 168 | innerConfig.Name = typeof(SERVICE).FullName; 169 | } 170 | 171 | if (string.IsNullOrEmpty(innerConfig.DisplayName)) 172 | { 173 | innerConfig.DisplayName = innerConfig.Name; 174 | } 175 | 176 | if (string.IsNullOrEmpty(innerConfig.Description)) 177 | { 178 | innerConfig.Description = "No description"; 179 | } 180 | 181 | var hostConfiguration = new HostConfigurator(innerConfig); 182 | 183 | try 184 | { 185 | runAction(hostConfiguration); 186 | if (innerConfig.Action == ActionEnum.Run) 187 | innerConfig.Service = innerConfig.ServiceFactory(innerConfig.ExtraArguments, 188 | new MicroServiceController(() => 189 | { 190 | var task = Task.Factory.StartNew(() => 191 | { 192 | UsingServiceController(innerConfig, (sc, cfg) => StopService(cfg, sc)); 193 | }); 194 | } 195 | )); 196 | else if (innerConfig.Action == ActionEnum.RunInteractive) 197 | { 198 | var consoleService = new InnerService(innerConfig.Name, () => Start(innerConfig), () => Stop(innerConfig), () => Shutdown(innerConfig)); 199 | var consoleHost = new ConsoleServiceHost(consoleService, innerConfig); 200 | 201 | innerConfig.Service = innerConfig.ServiceFactory(innerConfig.ExtraArguments, 202 | new MicroServiceController(() => 203 | { 204 | var task = Task.Factory.StartNew(() => 205 | { 206 | consoleHost.StopService(); 207 | }); 208 | } 209 | )); 210 | 211 | // Return the console host run result, so we get some idea what failed if result is not OK 212 | return (int)consoleHost.Run(); 213 | } 214 | else 215 | { 216 | innerConfig.Service = innerConfig.ServiceFactory(innerConfig.ExtraArguments, null); 217 | } 218 | 219 | ConfigureService(innerConfig); 220 | 221 | return 0; 222 | } 223 | catch (Exception e) 224 | { 225 | Error(innerConfig, e); 226 | return -1; 227 | } 228 | } 229 | 230 | private static string GetServiceCommand(List extraArguments) 231 | { 232 | var host = Process.GetCurrentProcess().MainModule.FileName; 233 | if (host.EndsWith("dotnet.exe", StringComparison.OrdinalIgnoreCase)) 234 | { 235 | var appPath = Path.Combine(System.AppContext.BaseDirectory, 236 | System.Reflection.Assembly.GetEntryAssembly().GetName().Name + ".dll"); 237 | host = string.Format("{0} \"{1}\"", SanitiseArgument(host), appPath); 238 | } 239 | else 240 | { 241 | //For self-contained apps, skip the dll path 242 | extraArguments = extraArguments.Skip(1).ToList(); 243 | } 244 | var fullServiceCommand = string.Format("{0} {1} {2}", host, string.Join(" ", extraArguments.Select(arg => SanitiseArgument(arg))), "action:run"); 245 | return fullServiceCommand; 246 | } 247 | 248 | private static string SanitiseArgument(string arg) 249 | { 250 | if (string.IsNullOrEmpty(arg)) 251 | { 252 | return arg; 253 | } 254 | return arg.Contains(" ") ? "\"" + arg + "\"" : arg; 255 | } 256 | 257 | private static void Install(HostConfiguration config, ServiceController sc, int counter = 0) 258 | { 259 | Win32ServiceCredentials cred = config.DefaultCred; 260 | if (!string.IsNullOrEmpty(config.Username)) 261 | { 262 | cred = new Win32ServiceCredentials(config.Username, config.Password); 263 | } 264 | try 265 | { 266 | new Win32ServiceManager().CreateService(new ServiceDefinition(config.Name, GetServiceCommand(config.ExtraArguments)) 267 | { 268 | DisplayName = config.DisplayName, 269 | Description = config.Description, 270 | Credentials = cred, 271 | AutoStart = true, 272 | DelayedAutoStart = !config.StartImmediately, 273 | ErrorSeverity = ErrorSeverity.Normal 274 | }); 275 | Console.WriteLine($@"Successfully registered and started service ""{config.Name}"" (""{config.Description}"")"); 276 | config.OnServiceInstall?.Invoke(config.Service); 277 | } 278 | catch (Exception e) 279 | { 280 | if (e.Message.Contains("already exists")) 281 | { 282 | Console.WriteLine($@"Service ""{config.Name}"" (""{config.Description}"") was already installed. Reinstalling..."); 283 | Reinstall(config, sc); 284 | } 285 | else if (e.Message.Contains("The specified service has been marked for deletion")) 286 | { 287 | if (counter < 10) 288 | { 289 | System.Threading.Thread.Sleep(500); 290 | counter++; 291 | string suffix = "th"; 292 | if (counter == 1) 293 | { 294 | suffix = "st"; 295 | } 296 | else if (counter == 2) 297 | { 298 | suffix = "nd"; 299 | } 300 | else if (counter == 3) 301 | { 302 | suffix = "rd"; 303 | } 304 | Console.WriteLine("The specified service has been marked for deletion. Retrying {0}{1} time", counter, suffix); 305 | Install(config, sc, counter); 306 | } 307 | else 308 | { 309 | throw; 310 | } 311 | } 312 | else 313 | { 314 | throw; 315 | } 316 | } 317 | } 318 | 319 | private static void Uninstall(HostConfiguration config, ServiceController sc) 320 | { 321 | try 322 | { 323 | if (!(sc.Status == ServiceControllerStatus.Stopped || sc.Status == ServiceControllerStatus.StopPending)) 324 | { 325 | StopService(config, sc); 326 | } 327 | new Win32ServiceManager().DeleteService(config.Name); 328 | Console.WriteLine($@"Successfully unregistered service ""{config.Name}"" (""{config.Description}"")"); 329 | config.OnServiceUnInstall?.Invoke(config.Service); 330 | } 331 | catch (Exception e) 332 | { 333 | if (!e.Message.Contains("does not exist")) 334 | { 335 | throw; 336 | } 337 | Console.WriteLine($@"Service ""{config.Name}"" (""{config.Description}"") does not exist. No action taken."); 338 | } 339 | } 340 | 341 | private static void StopService(HostConfiguration config, ServiceController sc) 342 | { 343 | if (!(sc.Status == ServiceControllerStatus.Stopped | sc.Status == ServiceControllerStatus.StopPending)) 344 | { 345 | sc.Stop(); 346 | sc.WaitForStatus(ServiceControllerStatus.Stopped, TimeSpan.FromMilliseconds(1000)); 347 | Console.WriteLine($@"Successfully stopped service ""{config.Name}"" (""{config.Description}"")"); 348 | config.OnServiceStop?.Invoke(config.Service); 349 | } 350 | else 351 | { 352 | Console.WriteLine($@"Service ""{config.Name}"" (""{config.Description}"") is already stopped or stop is pending."); 353 | } 354 | } 355 | 356 | private static void PauseService(HostConfiguration config, ServiceController sc) 357 | { 358 | if (!(sc.Status == ServiceControllerStatus.Paused | sc.Status == ServiceControllerStatus.PausePending)) 359 | { 360 | sc.Pause(); 361 | sc.WaitForStatus(ServiceControllerStatus.Paused, TimeSpan.FromMilliseconds(1000)); 362 | Console.WriteLine($@"Successfully paused service ""{config.Name}"" (""{config.Description}"")"); 363 | config.OnServicePause?.Invoke(config.Service); 364 | } 365 | else 366 | { 367 | Console.WriteLine($@"Service ""{config.Name}"" (""{config.Description}"") is already paused or stop is pending."); 368 | } 369 | } 370 | 371 | private static void ContinueService(HostConfiguration config, ServiceController sc) 372 | { 373 | if (!(sc.Status == ServiceControllerStatus.Running | sc.Status == ServiceControllerStatus.ContinuePending)) 374 | { 375 | sc.Continue(); 376 | sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(1000)); 377 | Console.WriteLine($@"Successfully stopped service ""{config.Name}"" (""{config.Description}"")"); 378 | config.OnServiceContinue?.Invoke(config.Service); 379 | } 380 | else 381 | { 382 | Console.WriteLine($@"Service ""{config.Name}"" (""{config.Description}"") is already stopped or stop is pending."); 383 | } 384 | } 385 | 386 | private static void StartService(HostConfiguration config, ServiceController sc) 387 | { 388 | if (!(sc.Status == ServiceControllerStatus.StartPending | sc.Status == ServiceControllerStatus.Running)) 389 | { 390 | sc.Start(); 391 | sc.WaitForStatus(ServiceControllerStatus.Running, TimeSpan.FromMilliseconds(1000)); 392 | Console.WriteLine($@"Successfully started service ""{config.Name}"" (""{config.Description}"")"); 393 | } 394 | else 395 | { 396 | Console.WriteLine($@"Service ""{config.Name}"" (""{config.Description}"") is already running or start is pending."); 397 | } 398 | } 399 | 400 | private static void Reinstall(HostConfiguration config, ServiceController sc) 401 | { 402 | StopService(config, sc); 403 | Uninstall(config, sc); 404 | Install(config, sc); 405 | } 406 | 407 | private static void SendCustomCommandToService(HostConfiguration config, ServiceController sc) { 408 | if (sc.Status == ServiceControllerStatus.Running) { 409 | sc.ExecuteCommand(config.CustomCommand); 410 | Console.WriteLine($@"Successfully sent custom command ({config.CustomCommand}) to service ""{config.Name}"" (""{config.Description}"")"); 411 | config.OnServiceCustomCommand(config.Service, config.CustomCommand); 412 | } else { 413 | Console.WriteLine($@"Service ""{config.Name}"" (""{config.Description}"") isn't running so can't send custom command ({config.CustomCommand})"); 414 | } 415 | } 416 | 417 | private static void ConfigureService(HostConfiguration config) 418 | { 419 | switch (config.Action) 420 | { 421 | case ActionEnum.Install: 422 | UsingServiceController(config, (sc, cfg) => Install(cfg, sc)); 423 | break; 424 | case ActionEnum.Pause: 425 | UsingServiceController(config, (sc, cfg) => PauseService(cfg, sc)); 426 | break; 427 | case ActionEnum.Continue: 428 | UsingServiceController(config, (sc, cfg) => ContinueService(cfg, sc)); 429 | break; 430 | case ActionEnum.Uninstall: 431 | UsingServiceController(config, (sc, cfg) => Uninstall(cfg, sc)); 432 | break; 433 | case ActionEnum.Run: 434 | var testService = new InnerService(config.Name, () => Start(config), () => Stop(config), () => Shutdown(config)); 435 | var serviceHost = new Win32ServiceHost(config.Name, new ShutdownableServiceStateMachine(testService)); 436 | serviceHost.Run(); 437 | break; 438 | case ActionEnum.RunInteractive: 439 | break; 440 | case ActionEnum.Stop: 441 | UsingServiceController(config, (sc, cfg) => StopService(cfg, sc)); 442 | break; 443 | case ActionEnum.Start: 444 | UsingServiceController(config, (sc, cfg) => StartService(cfg, sc)); 445 | break; 446 | case ActionEnum.CustomCommand: 447 | UsingServiceController(config, (sc, cfg) => SendCustomCommandToService(cfg, sc)); 448 | break; 449 | } 450 | } 451 | 452 | private static void UsingServiceController(HostConfiguration config, Action> action) 453 | { 454 | using (var sc = new ServiceController(config.Name)) 455 | { 456 | action(sc, config); 457 | } 458 | } 459 | 460 | private static void Start(HostConfiguration config) 461 | { 462 | try 463 | { 464 | config.OnServiceStart(config.Service, config.ExtraArguments); 465 | } 466 | catch (Exception e) 467 | { 468 | Error(config, e); 469 | } 470 | } 471 | 472 | private static void Stop(HostConfiguration config) 473 | { 474 | try 475 | { 476 | config.OnServiceStop(config.Service); 477 | } 478 | catch (Exception e) 479 | { 480 | Error(config, e); 481 | } 482 | } 483 | 484 | private static void Shutdown(HostConfiguration config) 485 | { 486 | try 487 | { 488 | config.OnServiceShutdown(config.Service); 489 | } 490 | catch (Exception e) 491 | { 492 | Error(config, e); 493 | } 494 | } 495 | 496 | private static void Error(HostConfiguration config, Exception e = null) 497 | { 498 | config.OnServiceError(e); 499 | } 500 | } 501 | } 502 | -------------------------------------------------------------------------------- /Source/PeterKottas.DotNetCore.WindowsService/StateMachines/ShutdownableServiceStateMachine.cs: -------------------------------------------------------------------------------- 1 | using DasMulli.Win32.ServiceUtils; 2 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Text; 7 | 8 | namespace PeterKottas.DotNetCore.WindowsService.StateMachines 9 | { 10 | public sealed class ShutdownableServiceStateMachine : IWin32ServiceStateMachine 11 | { 12 | private readonly IShutdownableWin32Service serviceImplementation; 13 | private ServiceStatusReportCallback statusReportCallback; 14 | 15 | /// 16 | /// Initializes a new to run the specified service. 17 | /// 18 | /// The service implementation. 19 | public ShutdownableServiceStateMachine(IShutdownableWin32Service serviceImplementation) 20 | { 21 | this.serviceImplementation = serviceImplementation; 22 | } 23 | 24 | /// 25 | /// Called when the service is started. 26 | /// Use the provided to notify the service manager about 27 | /// state changes such as started, paused etc. 28 | /// 29 | /// The startup arguments passed via windows' service configuration. 30 | /// The status report callback. 31 | [SuppressMessage("ReSharper", "ParameterHidesMember")] 32 | public void OnStart(string[] startupArguments, ServiceStatusReportCallback statusReportCallback) 33 | { 34 | this.statusReportCallback = statusReportCallback; 35 | 36 | try 37 | { 38 | serviceImplementation.Start(startupArguments, HandleServiceImplementationStoppedOnItsOwn); 39 | 40 | statusReportCallback(ServiceState.Running, ServiceAcceptedControlCommandsFlags.Stop | ServiceAcceptedControlCommandsFlags.Shutdown, win32ExitCode: 0, waitHint: 0); 41 | } 42 | catch 43 | { 44 | statusReportCallback(ServiceState.Stopped, ServiceAcceptedControlCommandsFlags.None, win32ExitCode: -1, waitHint: 0); 45 | } 46 | } 47 | 48 | /// 49 | /// Called when a command was received from windows' service system. 50 | /// 51 | /// The received command. 52 | /// Type of the command specific event. See description of dwEventType at https://msdn.microsoft.com/en-us/library/windows/desktop/ms683241(v=vs.85).aspx 53 | public void OnCommand(ServiceControlCommand command, uint commandSpecificEventType) 54 | { 55 | if (command == ServiceControlCommand.Stop) 56 | { 57 | statusReportCallback(ServiceState.StopPending, ServiceAcceptedControlCommandsFlags.None, win32ExitCode: 0, waitHint: 3000); 58 | 59 | var win32ExitCode = 0; 60 | 61 | try 62 | { 63 | serviceImplementation.Stop(); 64 | } 65 | catch 66 | { 67 | win32ExitCode = -1; 68 | } 69 | 70 | statusReportCallback(ServiceState.Stopped, ServiceAcceptedControlCommandsFlags.None, win32ExitCode, waitHint: 0); 71 | } 72 | else if (command == ServiceControlCommand.Shutdown) 73 | { 74 | try 75 | { 76 | statusReportCallback(ServiceState.StopPending, ServiceAcceptedControlCommandsFlags.None, win32ExitCode: 0, waitHint: 3000); //this is probably too much, see note down below 77 | serviceImplementation.Shutdown(); 78 | } 79 | catch { } 80 | finally 81 | { 82 | statusReportCallback(ServiceState.Stopped, ServiceAcceptedControlCommandsFlags.None, win32ExitCode: 0, waitHint: 0); 83 | } 84 | } 85 | } 86 | 87 | private void HandleServiceImplementationStoppedOnItsOwn() 88 | { 89 | statusReportCallback(ServiceState.Stopped, ServiceAcceptedControlCommandsFlags.None, win32ExitCode: 0, waitHint: 0); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.MinimalTemplate/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Peter Kottas", 3 | "classifications": [ "Microservices" ], 4 | "name": "Microservice - minimal", 5 | "identity": "PeterKottas.DotNetCore.WindowsService.MinimalTemplate", 6 | "tags": { 7 | "language": "C#" 8 | }, 9 | "shortName": "mcrsvc-min", 10 | "guids": [ "dc46e9be-12b0-43c5-ac94-5c7019d59196" ], 11 | "sourceName": "PeterKottas.DotNetCore.WindowsService.MinimalTemplate" 12 | } -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.MinimalTemplate/ExampleService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.PlatformAbstractions; 2 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Text; 7 | 8 | namespace PeterKottas.DotNetCore.WindowsService.MinimalTemplate 9 | { 10 | public class ExampleService : IMicroService 11 | { 12 | private IMicroServiceController controller; 13 | 14 | public ExampleService() 15 | { 16 | controller = null; 17 | } 18 | 19 | public ExampleService(IMicroServiceController controller) 20 | { 21 | this.controller = controller; 22 | } 23 | 24 | private string fileName = Path.Combine(System.AppContext.BaseDirectory, "log.txt"); 25 | public void Start() 26 | { 27 | Console.WriteLine("I started"); 28 | Console.WriteLine(fileName); 29 | File.AppendAllText(fileName, "Started\n"); 30 | if (controller != null) 31 | { 32 | controller.Stop(); 33 | } 34 | } 35 | 36 | public void Stop() 37 | { 38 | File.AppendAllText(fileName, "Stopped\n"); 39 | Console.WriteLine("I stopped"); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.MinimalTemplate/PeterKottas.DotNetCore.WindowsService.MinimalTemplate.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.MinimalTemplate/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.PlatformAbstractions; 2 | using PeterKottas.DotNetCore.WindowsService; 3 | using System; 4 | using System.IO; 5 | 6 | namespace PeterKottas.DotNetCore.WindowsService.MinimalTemplate 7 | { 8 | class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | var fileName = Path.Combine(System.AppContext.BaseDirectory, "log.txt"); 13 | ServiceRunner.Run(config => 14 | { 15 | var name = config.GetDefaultName(); 16 | config.Service(serviceConfig => 17 | { 18 | serviceConfig.ServiceFactory((extraArguments, controller) => 19 | { 20 | return new ExampleService(controller); 21 | }); 22 | 23 | serviceConfig.OnStart((service, extraParams) => 24 | { 25 | Console.WriteLine("Service {0} started", name); 26 | service.Start(); 27 | }); 28 | 29 | serviceConfig.OnStop(service => 30 | { 31 | Console.WriteLine("Service {0} stopped", name); 32 | service.Stop(); 33 | }); 34 | 35 | serviceConfig.OnError(e => 36 | { 37 | File.AppendAllText(fileName, $"Exception: {e.ToString()}\n"); 38 | Console.WriteLine("Service {0} errored with exception : {1}", name, e.Message); 39 | }); 40 | }); 41 | }); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.StandardTemplate/.template.config/template.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Peter Kottas", 3 | "classifications": [ "Microservices", "Console", "Boilerplate", "Dependency Injection", "Logging", "Configuration" ], 4 | "name": "Microservice - Standard w/ DI, Configuration and Logging Extensions", 5 | "identity": "PeterKottas.DotNetCore.WindowsService.StandardTemplate", 6 | "tags": { 7 | "language": "C#" 8 | }, 9 | "shortName": "mcrsvc-std", 10 | "guids": [ "dc46e9be-12b0-43c5-ac94-5c7019d59196" ], 11 | "sourceName": "PeterKottas.DotNetCore.WindowsService.StandardTemplate" 12 | } -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.StandardTemplate/ExampleService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using Microsoft.Extensions.PlatformAbstractions; 3 | using PeterKottas.DotNetCore.WindowsService.Base; 4 | using PeterKottas.DotNetCore.WindowsService.Interfaces; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Text; 9 | 10 | namespace PeterKottas.DotNetCore.WindowsService.StandardTemplate 11 | { 12 | public class ExampleService : MicroService, IMicroService 13 | { 14 | private IMicroServiceController _controller; 15 | private ILogger _logger; 16 | 17 | public ExampleService() 18 | { 19 | _controller = null; 20 | } 21 | 22 | public ExampleService(IMicroServiceController controller, ILogger logger) 23 | { 24 | _controller = controller; 25 | _logger = logger; 26 | } 27 | 28 | 29 | public void Start() 30 | { 31 | StartBase(); 32 | Timers.Start("Poller", 1000, () => 33 | { 34 | _logger.LogInformation(string.Format("Polling at {0}\n", DateTime.Now.ToString("o"))); 35 | }); 36 | _logger.LogTrace("Started\n"); 37 | } 38 | 39 | public void Stop() 40 | { 41 | StopBase(); 42 | _logger.LogTrace("Stopped\n"); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.StandardTemplate/LogFileProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace PeterKottas.DotNetCore.WindowsService.StandardTemplate 7 | { 8 | // Thanks to Andrew Lock for basic details needed here: https://andrewlock.net/creating-a-rolling-file-logging-provider-for-asp-net-core-2-0/ 9 | public class LogFileProvider : ILoggerProvider 10 | { 11 | public ILogger CreateLogger(string categoryName) 12 | { 13 | return new FileLogger(this, categoryName); 14 | } 15 | 16 | public void Dispose() 17 | { 18 | // TODO: Handle File IO mechanics 19 | } 20 | 21 | public void AddMessage(DateTimeOffset timestamp, string message) 22 | { 23 | // TODO: Handle File IO mechanics 24 | } 25 | } 26 | 27 | public class FileLogger : ILogger 28 | { 29 | LogFileProvider _provider; 30 | string _category; 31 | public FileLogger(LogFileProvider provider, string categoryName) 32 | { 33 | _provider = provider; 34 | _category = categoryName; 35 | } 36 | public IDisposable BeginScope(TState state) 37 | { 38 | return null; 39 | } 40 | 41 | public bool IsEnabled(LogLevel logLevel) 42 | { 43 | if (logLevel == LogLevel.None) 44 | { 45 | return false; 46 | } 47 | return true; 48 | } 49 | 50 | public void Log(DateTimeOffset timestamp, LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 51 | { 52 | if (!IsEnabled(logLevel)) 53 | { 54 | return; 55 | } 56 | 57 | var builder = new StringBuilder(); 58 | builder.Append(timestamp.ToString("yyyy-MM-dd HH:mm:ss.fff zzz")); 59 | builder.Append(" ["); 60 | builder.Append(logLevel.ToString()); 61 | builder.Append("] "); 62 | builder.Append(_category); 63 | builder.Append(": "); 64 | builder.AppendLine(formatter(state, exception)); 65 | 66 | if (exception != null) 67 | { 68 | builder.AppendLine(exception.ToString()); 69 | } 70 | 71 | _provider.AddMessage(timestamp, builder.ToString()); 72 | } 73 | 74 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 75 | { 76 | Log(DateTimeOffset.Now, logLevel, eventId, state, exception, formatter); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.StandardTemplate/PeterKottas.DotNetCore.WindowsService.StandardTemplate.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.StandardTemplate/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.PlatformAbstractions; 4 | using Microsoft.Extensions.Logging; 5 | using Microsoft.Extensions.FileProviders; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.DependencyInjection.Extensions; 8 | 9 | namespace PeterKottas.DotNetCore.WindowsService.StandardTemplate 10 | { 11 | class Program 12 | { 13 | public static void Main(string[] args) 14 | { 15 | #if !DEBUG 16 | var configuration = new ConfigurationBuilder() 17 | .SetBasePath(System.AppContext.BaseDirectory) 18 | .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true) 19 | .Build(); 20 | #else 21 | var configuration = new ConfigurationBuilder() 22 | .SetBasePath(System.AppContext.BaseDirectory) 23 | .AddJsonFile("appsettings.Development.json", optional: false, reloadOnChange: true) 24 | .Build(); 25 | #endif 26 | 27 | var svcProvider = new ServiceCollection() 28 | .AddLogging(builder => 29 | { 30 | builder 31 | .SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace) 32 | .AddProvider(new LogFileProvider()); // Implemented vanilla LogFile provider but is easily swapped for Nlog or SeriLog (et al.) providers 33 | 34 | }) 35 | .AddOptions() 36 | .AddLogging(o=>o.AddConsole()) 37 | 38 | .BuildServiceProvider(); 39 | 40 | var _logger = svcProvider.GetRequiredService().CreateLogger(); 41 | 42 | 43 | ServiceRunner.Run(config => 44 | { 45 | var name = config.GetDefaultName(); 46 | config.Service(serviceConfig => 47 | { 48 | serviceConfig.ServiceFactory((extraArguments, controller) => 49 | { 50 | return new ExampleService(controller, svcProvider.GetRequiredService().CreateLogger()); 51 | }); 52 | 53 | serviceConfig.OnStart((service, extraParams) => 54 | { 55 | _logger.LogTrace("Service {0} started", name); 56 | service.Start(); 57 | }); 58 | 59 | serviceConfig.OnStop(service => 60 | { 61 | _logger.LogTrace("Service {0} stopped", name); 62 | service.Stop(); 63 | }); 64 | 65 | serviceConfig.OnError(e => 66 | { 67 | _logger.LogError(e, string.Format("Service {0} errored with exception", name)); 68 | }); 69 | }); 70 | }); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.StandardTemplate/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionString": { 3 | }, 4 | "Logging": { 5 | "IncludeScopes": false, 6 | "LogLevel": { 7 | "Default": "Debug", 8 | "System": "Information", 9 | "Microsoft": "Information" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.StandardTemplate/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionString": { 3 | 4 | }, 5 | "Logging": { 6 | "IncludeScopes": false, 7 | "Debug": { 8 | "LogLevel": { 9 | "Default": "Warning" 10 | } 11 | }, 12 | "Console": { 13 | "LogLevel": { 14 | "Default": "Warning" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Source/Templates/PeterKottas.DotNetCore.WindowsService.Templates.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | PeterKottas.DotNetCore.WindowsService.Templates 5 | 1.0.1 6 | Templates for dot net core microservice. 7 | Peter Kottas 8 | .NET Core Windows service templates 9 | en-US 10 | dotnetcore templates,.netcore templates,windows service templates,micro service templates 11 | https://github.com/PeterKottas/DotNetCore.WindowsService 12 | https://github.com/PeterKottas/DotNetCore.WindowsService/blob/master/LICENSE 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------