├── .gitignore ├── MinimalApiPowershellService.sln ├── MinimalApiPowershellService ├── MinimalApiPowershellService.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── appsettings.Development.json └── appsettings.json ├── global.json ├── readme.md ├── riderPublish.png ├── serviceCreate.png └── serviceResults.png /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | obj/ 3 | /packages/ 4 | riderModule.iml 5 | /_ReSharper.Caches/ -------------------------------------------------------------------------------- /MinimalApiPowershellService.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MinimalApiPowershellService", "MinimalApiPowershellService\MinimalApiPowershellService.csproj", "{3DCF15D2-6003-4B08-8D85-21B346DD5614}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {3DCF15D2-6003-4B08-8D85-21B346DD5614}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {3DCF15D2-6003-4B08-8D85-21B346DD5614}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {3DCF15D2-6003-4B08-8D85-21B346DD5614}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {3DCF15D2-6003-4B08-8D85-21B346DD5614}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /MinimalApiPowershellService/MinimalApiPowershellService.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | win10-x64 8 | 11 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /MinimalApiPowershellService/Program.cs: -------------------------------------------------------------------------------- 1 | using System.Management.Automation; 2 | using System.Management.Automation.Runspaces; 3 | using Microsoft.Extensions.Hosting.WindowsServices; 4 | using Microsoft.PowerShell; 5 | 6 | var options = new WebApplicationOptions 7 | { 8 | Args = args, 9 | // Setting to allow the service to run both in IDE and as service 10 | ContentRootPath = WindowsServiceHelpers.IsWindowsService() 11 | ? AppContext.BaseDirectory 12 | : default 13 | }; 14 | var builder = WebApplication.CreateBuilder(options); 15 | builder.Host.UseWindowsService(); 16 | 17 | var app = builder.Build(); 18 | 19 | app.MapGet("/", () => 20 | { 21 | var scriptResponse = new List(); 22 | const string powerShellScript = @" 23 | param($RequestId = 'DefaultValue') 24 | $env:USERNAME + ""`n"" + $RequestId | Out-File (""c:\Temp\$([DateTime]::Now.ToString(""yyyyMMdd_HHmmss"")).txt"") 25 | Write-Output $RequestId 26 | Write-Output $ENV:UserName 27 | Write-Output $([DateTime]::Now) 28 | "; 29 | 30 | var initialSessionState = InitialSessionState.CreateDefault(); 31 | initialSessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted; 32 | 33 | using var powerShell = PowerShell.Create(initialSessionState); 34 | powerShell.AddScript(powerShellScript); 35 | powerShell.AddParameter("RequestId", Guid.NewGuid()); 36 | foreach (var line in powerShell.Invoke()) 37 | { 38 | scriptResponse.Add(line.BaseObject.ToString()); 39 | } 40 | 41 | return string.Join("\n", scriptResponse); 42 | }); 43 | 44 | // RunAsync for service. 45 | await app.RunAsync(); -------------------------------------------------------------------------------- /MinimalApiPowershellService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:6376", 7 | "sslPort": 44348 8 | } 9 | }, 10 | "profiles": { 11 | "MinimalApiPowershellService": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "applicationUrl": "https://localhost:7138;http://localhost:5004", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "IIS Express": { 21 | "commandName": "IISExpress", 22 | "launchBrowser": true, 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /MinimalApiPowershellService/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "Kestrel": { 9 | "Endpoints": { 10 | "Http": { 11 | "Url": "http://localhost:5004" 12 | }, 13 | "Https": { 14 | "Url": "https://localhost:7138" 15 | } 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /MinimalApiPowershellService/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | }, 7 | "EventLog": { 8 | "LogLevel": { 9 | "Default": "Information" 10 | } 11 | } 12 | }, 13 | "AllowedHosts": "localhost", 14 | "Kestrel": { 15 | "Endpoints": { 16 | "Http": { 17 | "Url": "http://localhost:5400" 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "7.0.0", 4 | "rollForward": "latestMinor", 5 | "allowPrerelease": false 6 | } 7 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Dotnet 7 PowerShell Service 2 | A sample project to run PowerShell in a Dotnet 7 windows service. 3 | 4 | This simple project shows running PowerShell commands from a windows 5 | service that are activated by a GET request on an API. The service passes 6 | a parameter to the PowerShell script and displays that with the current 7 | user and time in the browser, as well as writing this data to a file in 8 | c:\temp. Windows event logging has been enabled in ``appsettings.json`` 9 | 10 | It is import to target a specific OS in the configuration. Targeting 11 | generic win-64 will not allow powershell to work. At this time, single file publishing also 12 | does not work. 13 | 14 | ### Configure 15 | The port used by the service is specified in ``appsettings.json``. Note that different ports are configured in ``appsettings.Development.json``. 16 | #### Secure access to the API 17 | With the posted configuration, the service API is only accessible from localhost using http. If you need the service to 18 | be available from other machines on your network, I would recommend preventing eavesdropping by configuring use of an 19 | SSL certificate. 20 | 1. Install the certificate in the Windows Certificate Store using `certmgr.exe` into the Personal store 21 | 2. Get the location and validate the path with this powershell command. Note the location LocalMachine\\**My** and the private key is available: 22 | 23 | ```` 24 | PS C:\> Get-ChildItem -Path cert:\ -Recurse | Where-Object { $_.Subject -imatch "desk.domain.com" } | Select Subject, HasPrivateKey, PsParentPath 25 | 26 | Subject HasPrivateKey PSParentPath 27 | ------- ------------- ------------ 28 | CN=desk.domain.com True Microsoft.PowerShell.Security\Certificate::LocalMachine\My 29 | ```` 30 | 31 | 3. Add the FQDN from the certificate to AllowedHosts in `appsettings.json`: 32 | 33 | ``"AllowedHosts": "desk.domain.com;localhost"`` 34 | 35 | 4. Add an HTTPS entry: 36 | 37 | ``` 38 | "Kestrel": { 39 | "Endpoints": { 40 | "Http": { 41 | "Url": "http://localhost:5400" 42 | }, 43 | "Https": { 44 | "Url": "https://desk.domain.com:5401", 45 | "Certificate": { 46 | "Subject": "desk.domain.com", 47 | "Store": "My", 48 | "Location": "LocalMachine", 49 | "AllowInvalid": false 50 | } 51 | } 52 | } 53 | } 54 | 55 | ``` 56 | 57 | ### Publish 58 | To publish the project, mark the project as self contained and target 59 | the specific OS runtime. Generic win-x64 will fail. 60 | 61 | ``dotnet publish -o C:\Services\MinimalApiPowershellService\ --sc --runtime win10-x64`` 62 | 63 | This is the publishing profile in Rider: 64 | ![Rider Publish Configuration](riderPublish.png) 65 | 66 | ### Create a service 67 | Finally, create a service on your windows system using this command: 68 | 69 | ``sc create "MinimalApiPowershellService" binpath="c:\Services\MinimalApiPowershellService\MinimalApiPowershellService.exe"`` 70 | ![Create the service](serviceCreate.png) 71 | 72 | Once the service is created you can start it right away, or configure it 73 | to run under specific user context. When running under a specific user context, make sure to check the permissions 74 | for this user. 75 | 76 | ### Test 77 | With the service running you can test it by connecting to 78 | http://localhost:5400 from a web browser or a PowerShell 79 | command ``Invoke-WebRequest -Uri "http://localhost:5400"`` 80 | 81 | ![Results from the service running](serviceResults.png) 82 | 83 | ### Troubleshooting 84 | If the service will not run, check the event log for any errors. 85 | -------------------------------------------------------------------------------- /riderPublish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotmoo/MinimalApiPowershellService/b261a78997d19259b60b5babe27ecc114c2ffffc/riderPublish.png -------------------------------------------------------------------------------- /serviceCreate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotmoo/MinimalApiPowershellService/b261a78997d19259b60b5babe27ecc114c2ffffc/serviceCreate.png -------------------------------------------------------------------------------- /serviceResults.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gotmoo/MinimalApiPowershellService/b261a78997d19259b60b5babe27ecc114c2ffffc/serviceResults.png --------------------------------------------------------------------------------