├── .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 | 
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 | 
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 | 
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
--------------------------------------------------------------------------------