├── THIRD_PARTY_INCLUDES.MD
├── Test
└── Client.Test
│ ├── Client.Test.cs
│ └── Client.Test.csproj
├── .gitignore
├── .vscode
├── settings.json
├── tasks.json
└── launch.json
├── global.json
├── Source
├── Server
│ ├── StartPowerServeCmdlet.cs
│ ├── Server.csproj
│ ├── Server.cs
│ └── packages.lock.json
├── Client
│ ├── Client.csproj
│ ├── packages.lock.json
│ ├── Main.cs
│ └── Client.cs
└── Shared
│ ├── TracedStream.cs
│ ├── ScriptInvocationOptions.cs
│ └── StreamString.cs
├── .github
└── workflows
│ └── ci.yml
├── LICENSE
├── PowerServe.sln
├── README.MD
└── .editorconfig
/THIRD_PARTY_INCLUDES.MD:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Test/Client.Test/Client.Test.cs:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin/
2 | obj/
3 | out/
4 | Build/
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet.defaultSolution": "PowerServe.sln"
3 | }
--------------------------------------------------------------------------------
/global.json:
--------------------------------------------------------------------------------
1 | {
2 | "sdk": {
3 | "version": "9.0.100"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/Source/Server/StartPowerServeCmdlet.cs:
--------------------------------------------------------------------------------
1 | using System.Management.Automation;
2 |
3 | namespace PowerServe;
4 |
5 | [Cmdlet(VerbsLifecycle.Start, "PowerServe")]
6 | public class StartPowerServeCmdlet : PSCmdlet
7 | {
8 | [Parameter(Position = 0)]
9 | public string PipeName { get; set; } = "PowerServe-" + Environment.UserName;
10 |
11 | // Runs in the background for the life of the module
12 | protected override void ProcessRecord() => _ = new Server().StartAsync(PipeName!);
13 | }
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "Build Server",
6 | "icon": {
7 | "color": "terminal.ansiYellow",
8 | "id": "wrench"
9 | },
10 | "type": "shell",
11 | "command": "dotnet build Source/Server",
12 | "group": "build",
13 | },
14 | {
15 | "label": "Build Client",
16 | "icon": {
17 | "color": "terminal.ansiYellow",
18 | "id": "wrench"
19 | },
20 | "type": "shell",
21 | "command": "dotnet build Source/Server",
22 | "group": "build",
23 | }
24 | ]
25 | }
--------------------------------------------------------------------------------
/Test/Client.Test/Client.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0
5 | enable
6 | enable
7 | false
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: Build and Publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | - ci
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | defaults:
13 | run:
14 | shell: pwsh
15 |
16 | jobs:
17 | build:
18 | runs-on: windows-2022
19 | steps:
20 | - name: Checkout code
21 | uses: actions/checkout@v4
22 | with:
23 | fetch-depth: 0
24 |
25 | - name: Setup .NET Preview
26 | uses: actions/setup-dotnet@v4
27 | with:
28 | global-json-file: global.json
29 | cache-dependency-path: "**/packages.lock.json"
30 | cache: true
31 |
32 | - name: Build
33 | run: dotnet publish -c Release -r win-x64
34 |
35 | - name: Publish Artifact
36 | uses: actions/upload-artifact@v4
37 | with:
38 | name: PowerServeClient
39 | path: Build/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2023 Justin Grote @JustinWGrote
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "PowerServe: Server",
9 | "type": "PowerShell",
10 | "request": "launch",
11 | "script": "Import-Module ./Source/Server/bin/Debug/net8.0/PowerServe.dll;Start-PowerServe;sleep 300",
12 | "createTemporaryIntegratedConsole": true,
13 | "attachDotnetDebugger": true,
14 | "preLaunchTask": "Build Server",
15 | },
16 | {
17 | "name": "Client: Hello World",
18 | "type": "coreclr",
19 | "request": "launch",
20 | "program": "${workspaceFolder}/Source/Client/bin/Debug/net9.0/PowerServeClient.dll",
21 | "preLaunchTask": "Build Client",
22 | "args": [
23 | "-c",
24 | "'Hello World'"
25 | ],
26 | "cwd": "${workspaceFolder}",
27 | "stopAtEntry": false,
28 | "console": "integratedTerminal"
29 | }
30 | ]
31 | }
--------------------------------------------------------------------------------
/Source/Server/Server.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | PowerServe
5 | net8.0
6 | latest
7 | enable
8 | enable
9 | portable
10 | en
11 | false
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Source/Client/Client.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | PowerServeClient
6 | Justin Grote
7 | net8.0
8 | latest
9 | enable
10 | enable
11 | true
12 | true
13 | Speed
14 | true
15 | portable
16 | en
17 | false
18 | true
19 | true
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/Source/Shared/TracedStream.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | namespace PowerServe;
3 |
4 | public class TracedStreamReader(Stream stream) : StreamReader(stream, leaveOpen: true)
5 | {
6 | public override string? ReadLine()
7 | {
8 | var line = base.ReadLine();
9 | Trace.TraceInformation($"READ: {line}");
10 | return line;
11 | }
12 |
13 | public override async Task ReadLineAsync() => await ReadLine(CancellationToken.None).AsTask();
14 |
15 | public override async ValueTask ReadLine(CancellationToken cancellationToken)
16 | {
17 | string? line = await base.ReadLineAsync(cancellationToken);
18 | Trace.TraceInformation($"READASYNC: {line}");
19 | return line;
20 | }
21 | }
22 |
23 | public class TracedStreamWriter : StreamWriter
24 | {
25 | public TracedStreamWriter(Stream stream) : base(stream, leaveOpen: true)
26 | {
27 | AutoFlush = true;
28 | }
29 |
30 | public override void WriteLine(string? value)
31 | {
32 | Trace.TraceInformation($"WRITE: {value}");
33 | base.WriteLine(value);
34 | }
35 |
36 | public override async Task WriteLine(string? value)
37 | {
38 | Trace.TraceInformation($"WRITEASYNC: {value}");
39 | await base.WriteLineAsync(value);
40 | }
41 |
42 | public async Task WriteLine(string? value, CancellationToken cancellationToken)
43 | {
44 | Trace.TraceInformation($"WRITEASYNC: {value}");
45 | await base.WriteLineAsync(value.AsMemory(), cancellationToken);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/Source/Client/packages.lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "dependencies": {
4 | "net8.0": {
5 | "Microsoft.DotNet.ILCompiler": {
6 | "type": "Direct",
7 | "requested": "[8.0.13, )",
8 | "resolved": "8.0.13",
9 | "contentHash": "CCIhseY9KUJDIYKt7qD1IRLQA6Hr/8Dky31KS6UrM2sFyaFUb2JLagT0Uy2BiSf1i1Qy3nPjRb0zc1JFogOi9w=="
10 | },
11 | "Microsoft.NET.ILLink.Tasks": {
12 | "type": "Direct",
13 | "requested": "[8.0.13, )",
14 | "resolved": "8.0.13",
15 | "contentHash": "R19ZTaRiQAK+xo9ZwaHbF/1vb1wwR1Wn5+sqp9v8+CDjbdS8R6qftKdw0VSXWKm7VAMi7P+NCU4zxDzhEWcAwQ=="
16 | },
17 | "System.CommandLine": {
18 | "type": "Direct",
19 | "requested": "[2.0.0-beta1.20071.2, )",
20 | "resolved": "2.0.0-beta1.20071.2",
21 | "contentHash": "jlLkd6R6SUP9Dfjkg3N/0YNfkw3yWZWDHhpwuCJmnyNVLZKczeFM8VNxaIvob+Gxdl+FNL2CjeyrtJZqygIWXg==",
22 | "dependencies": {
23 | "Microsoft.CSharp": "4.4.1",
24 | "system.memory": "4.5.3"
25 | }
26 | },
27 | "Microsoft.CSharp": {
28 | "type": "Transitive",
29 | "resolved": "4.4.1",
30 | "contentHash": "A5hI3gk6WpcBI0QGZY6/d5CCaYUxJgi7iENn1uYEng+Olo8RfI5ReGVkjXjeu3VR3srLvVYREATXa2M0X7FYJA=="
31 | },
32 | "System.Memory": {
33 | "type": "Transitive",
34 | "resolved": "4.5.3",
35 | "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA=="
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/Source/Shared/ScriptInvocationOptions.cs:
--------------------------------------------------------------------------------
1 | namespace PowerServe.Shared;
2 |
3 | ///
4 | /// A dictionary that can be serialized to a string format.
5 | ///
6 | public class OptionsDictionary : Dictionary
7 | {
8 | public OptionsDictionary() : base() { }
9 |
10 | // Create from existing dictionary
11 | public OptionsDictionary(IDictionary dictionary) : base(dictionary) { }
12 |
13 | // Serialize to string format (key1=value1;key2=value2)
14 | public override string ToString()
15 | {
16 | return string.Join(";", this.Select(kvp => $"{Uri.EscapeDataString(kvp.Key)}={Uri.EscapeDataString(kvp.Value)}"));
17 | }
18 |
19 | // Parse from string format
20 | public static OptionsDictionary FromString(string input)
21 | {
22 | var result = new OptionsDictionary();
23 | if (string.IsNullOrEmpty(input)) return result;
24 |
25 | foreach (var pair in input.Split(';'))
26 | {
27 | var parts = pair.Split('=');
28 | if (parts.Length == 2)
29 | {
30 | result[Uri.UnescapeDataString(parts[0])] = Uri.UnescapeDataString(parts[1]);
31 | }
32 | }
33 | return result;
34 | }
35 | }
36 |
37 | ///
38 | /// Options for script invocation that can be serialized to a string format.
39 | ///
40 | public class ScriptInvocationOptions(int Depth = 2)
41 | {
42 | public OptionsDictionary ToOptionsDictionary()
43 | {
44 | return new OptionsDictionary
45 | {
46 | { nameof(Depth), Depth.ToString() }
47 | };
48 | }
49 |
50 | public static ScriptInvocationOptions FromOptionsDictionary(OptionsDictionary options)
51 | {
52 | var result = new ScriptInvocationOptions();
53 | if (options.TryGetValue(nameof(Depth), out string? depthString))
54 | {
55 | result.Depth = int.Parse(depthString);
56 | }
57 | return result;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Source/Client/Main.cs:
--------------------------------------------------------------------------------
1 | using System.CommandLine;
2 | using System.CommandLine.Invocation;
3 | using System.Diagnostics; // added for Trace and ConsoleTraceListener
4 |
5 | using static PowerServe.Client;
6 |
7 | // Write Trace to stderr
8 |
9 | using var cts = new CancellationTokenSource();
10 | Console.CancelKeyPress += (sender, eventArgs) =>
11 | {
12 | eventArgs.Cancel = true;
13 | cts.Cancel();
14 | };
15 |
16 | var rootCommand = new RootCommand
17 | {
18 | new Option(["--script", "-c"], "The PowerShell script to execute."),
19 | new Option(["--file", "-f"], "The path to the PowerShell script file to execute."),
20 | new Option(["--working-directory", "-w"], () => Directory.GetCurrentDirectory(), "Specify the working directory for the PowerShell process. Defaults to the current directory."),
21 | new Option(["--pipe-name", "-p"], () => $"PowerServe-{Environment.UserName}", "The named pipe to use. The server will start here if not already running. Defaults to PowerServe-{username}."),
22 | new Option(["--verbose", "-v"], "Log verbose messages about what PowerServeClient is doing to stderr. This may interfere with the JSON response so only use for troubleshooting."),
23 | new Option(["--depth", "-d"], () => 2, "The maximum depth for JSON serialization of PowerShell objects. Defaults to 2"),
24 | new Option(["--exeDir", "-e"], "Where to locate the PowerServe module.") {IsHidden = true}
25 | };
26 |
27 | rootCommand.Handler = CommandHandler.Create((script, file, workingDirectory, pipeName, verbose, depth, exeDir) =>
28 | {
29 | string resolvedScript = !string.IsNullOrEmpty(file) ? $"& (Resolve-Path {file})" : script;
30 | return InvokeScript(
31 | script: resolvedScript,
32 | pipeName,
33 | workingDirectory,
34 | verbose,
35 | cts.Token,
36 | exeDir,
37 | depth
38 | );
39 | });
40 |
41 | await rootCommand.InvokeAsync(args);
42 |
43 | Trace.Flush();
--------------------------------------------------------------------------------
/PowerServe.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{58094665-E0B8-4C8F-9E1C-5C56161BB6D2}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client", "Source\Client\Client.csproj", "{20D14D84-9010-4EEC-82B6-0AEC574D966E}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Server", "Source\Server\Server.csproj", "{24941B8F-97DA-4A20-A464-DC6DEA5DBC3C}"
11 | EndProject
12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Test", "Test", "{39CBF43A-7219-422E-BAC5-05ADFD1FBF00}"
13 | EndProject
14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Client.Test", "Test\Client.Test\Client.Test.csproj", "{DAF7BB9C-501A-4642-9471-A0186F4736A5}"
15 | EndProject
16 | Global
17 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
18 | Debug|Any CPU = Debug|Any CPU
19 | Release|Any CPU = Release|Any CPU
20 | EndGlobalSection
21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
22 | {20D14D84-9010-4EEC-82B6-0AEC574D966E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {20D14D84-9010-4EEC-82B6-0AEC574D966E}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {20D14D84-9010-4EEC-82B6-0AEC574D966E}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {20D14D84-9010-4EEC-82B6-0AEC574D966E}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {24941B8F-97DA-4A20-A464-DC6DEA5DBC3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {24941B8F-97DA-4A20-A464-DC6DEA5DBC3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {24941B8F-97DA-4A20-A464-DC6DEA5DBC3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {24941B8F-97DA-4A20-A464-DC6DEA5DBC3C}.Release|Any CPU.Build.0 = Release|Any CPU
30 | {DAF7BB9C-501A-4642-9471-A0186F4736A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
31 | {DAF7BB9C-501A-4642-9471-A0186F4736A5}.Debug|Any CPU.Build.0 = Debug|Any CPU
32 | {DAF7BB9C-501A-4642-9471-A0186F4736A5}.Release|Any CPU.ActiveCfg = Release|Any CPU
33 | {DAF7BB9C-501A-4642-9471-A0186F4736A5}.Release|Any CPU.Build.0 = Release|Any CPU
34 | EndGlobalSection
35 | GlobalSection(SolutionProperties) = preSolution
36 | HideSolutionNode = FALSE
37 | EndGlobalSection
38 | GlobalSection(NestedProjects) = preSolution
39 | {20D14D84-9010-4EEC-82B6-0AEC574D966E} = {58094665-E0B8-4C8F-9E1C-5C56161BB6D2}
40 | {24941B8F-97DA-4A20-A464-DC6DEA5DBC3C} = {58094665-E0B8-4C8F-9E1C-5C56161BB6D2}
41 | {DAF7BB9C-501A-4642-9471-A0186F4736A5} = {39CBF43A-7219-422E-BAC5-05ADFD1FBF00}
42 | EndGlobalSection
43 | EndGlobal
44 |
--------------------------------------------------------------------------------
/README.MD:
--------------------------------------------------------------------------------
1 | # PowerServe
2 |
3 | Run pwsh scripts repeatedly in a high performance manner and get JSONified results.
4 |
5 | ## Quick Start
6 |
7 | Download the release and extract into a folder.
8 |
9 | `PowerServeClient.exe "MyScript"`
10 |
11 | Note: This will spawn a background pwsh.exe process. To end that process when finished, use the following command:
12 | `get-process pwsh | where commandline -match 'Start-PowerServe' | stop-process`
13 |
14 | To see more options, run `PowerServeClient.exe --help`
15 |
16 | However it is purposefully meant to be persistent to "cache" the startup and runspace pools for better efficiency, so you would generally only do this for troubleshooting and testing.
17 |
18 | ## Purpose
19 |
20 | This is primarily targeted at monitoring programs such as PRTG that execute pwsh.exe dozens if not hundreds of times a minute, in an effort to reduce the memory and overhead of all those separate processes. It also has a good use for other programming languages being able to call into PWSH and get a JSON result quickly and simply.
21 |
22 | ## Architecture
23 |
24 | PowerClient.exe is a .NET 8 AOT-optimized native executable that takes a script argument and then:
25 |
26 | 1. Attempts to connect to the PowerServe server via a named pipe
27 | 1. If the server is not running, start a pwsh.exe process and inject the PowerServe server into it, loading it as a module and opening the named pipe for the server.
28 | 1. Encode the script in base64 and send it as a single line to the server via the named pipe
29 | 1. Server takes the script, decodes it, and executes it in a runspace pool
30 | 1. It will take the results, serialized them to a single-lined JSON, and then return it to the client via the named pipe which in turn outputs that via stdout.
31 | 1. If a single string is returned from the script, it is passed through (removing any newlines). This is done in the case the script has already done the JSON serialization or wants to return an alternate output.
32 |
33 | ## Debugging
34 |
35 | You can alternatively for debugging purposes start a pwsh.exe process and then run:
36 | `import-module path/to/powerserve.dll;Start-PowerServe -PipeName PowerServe-USERNAME`
37 |
38 | Replacing USERNAME with your current username. If you do this then you will be able to see the logs for the normally backgrounded process. The client will then attach to this server.
39 |
40 | ## Roadmap
41 |
42 | * Controlling number of threads
43 | * "Script Affinity" which will attempt to re-run the same script in the same runspace and minimize memory usage and startup time
44 |
45 | ## Limitations / Known Issues
46 |
47 | * Only error and output streams are considered, warning/progress/verbose/debug output is discarded
48 | * Sometimes gets frozen on serialization due to a large default depth size, be sure to be very narrow in the objects you return. `PSCustomObject and Select-Object are your friends`.
49 | * Environment variables are currently not passed through from the PowerServeClient.exe call to the backend pwsh process, it is recommended instead you provide the environment variables as parameters to your script.
--------------------------------------------------------------------------------
/Source/Shared/StreamString.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Text;
3 |
4 | namespace PowerServe.Shared;
5 |
6 | ///
7 | /// Provides methods for efficiently reading and writing strings to a stream with a length prefix.
8 | /// This has the advantage of being able to use any character in the string since no delimiter is required, whereas typical methods like StringWriter.WriteLine() use newline as a delimiter and so your content cannot contain a newline.
9 | /// Can also be used as an IEnumerable or IAsyncEnumerable to read all strings from the stream until the stream closes.
10 | /// Autoflushes by default and does not dispose the underlying stream.
11 | ///
12 | public class StreamString(Stream stream, bool autoFlush = true) : IEnumerable, IAsyncEnumerable
13 | {
14 | // We use UTF8 to optimize size, and we don't need to do any on-character splitting due to using length-based prefixes
15 | private readonly Encoding encoding = Encoding.UTF8;
16 |
17 | ///
18 | /// Read a string from the stream with a length prefix.
19 | ///
20 | ///
21 | /// If the stream is not readable
22 | /// If the stream ends before the result is found
23 | public string Read()
24 | {
25 | if (!stream.CanRead) throw new NotSupportedException("Stream is not readable");
26 | // Read string length prefix
27 | byte[] lengthBytes = new byte[sizeof(int)];
28 | stream.ReadExactly(lengthBytes);
29 |
30 | // Read string bytes
31 | byte[] valueBytes = new byte[BitConverter.ToInt32(lengthBytes)];
32 | stream.ReadExactly(valueBytes);
33 | return encoding.GetString(valueBytes);
34 | }
35 |
36 | ///
37 | /// Write a string to the stream with a length prefix. Returns the number of bytes written.
38 | ///
39 | /// If the stream is not writable
40 | /// If the stream ends before the result is found
41 | public int Write(string value)
42 | {
43 | if (!stream.CanWrite) throw new NotSupportedException("Stream is not writable");
44 | // Convert string to bytes with minimal allocation
45 | byte[] valueBytes = encoding.GetBytes(value);
46 |
47 | // Write string length prefix
48 | stream.Write(BitConverter.GetBytes(valueBytes.Length));
49 |
50 | stream.Write(valueBytes);
51 | if (autoFlush) stream.Flush();
52 | return valueBytes.Length;
53 | }
54 |
55 | public async Task ReadAsync(CancellationToken cancellationToken = default)
56 | {
57 | if (!stream.CanRead) throw new NotSupportedException("Stream is not readable");
58 | // Read string length prefix
59 | byte[] lengthBytes = new byte[sizeof(int)];
60 | await stream.ReadExactlyAsync(lengthBytes, cancellationToken);
61 |
62 | if (cancellationToken.IsCancellationRequested) throw new OperationCanceledException(cancellationToken);
63 |
64 | // Read string bytes
65 | byte[] valueBytes = new byte[BitConverter.ToInt32(lengthBytes)];
66 | await stream.ReadExactlyAsync(valueBytes, cancellationToken);
67 | return encoding.GetString(valueBytes);
68 | }
69 |
70 | ///
71 | /// Write a string to the stream with a length prefix. Returns the number of bytes written.
72 | ///
73 | public async Task WriteAsync(string value, CancellationToken cancellationToken = default)
74 | {
75 |
76 | if (!stream.CanWrite) throw new NotSupportedException("Stream is not writable");
77 | byte[] valueBytes = encoding.GetBytes(value);
78 |
79 | // Write string length prefix
80 | byte[] lengthBytes = BitConverter.GetBytes(valueBytes.Length);
81 | await stream.WriteAsync(lengthBytes, cancellationToken);
82 |
83 | // Write string value
84 | await stream.WriteAsync(valueBytes, cancellationToken);
85 | if (autoFlush) await FlushAsync(cancellationToken);
86 |
87 | return valueBytes.Length;
88 | }
89 |
90 | public void Flush() => stream.Flush();
91 | public Task FlushAsync(CancellationToken cancellationToken = default) => stream.FlushAsync(cancellationToken);
92 |
93 | #region IEnumerable/IAsyncEnumerable Implementation
94 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
95 | public IEnumerator GetEnumerator()
96 | {
97 | while (true)
98 | {
99 | string value;
100 | try
101 | {
102 | value = Read();
103 | }
104 | catch (EndOfStreamException)
105 | {
106 | yield break;
107 | }
108 | yield return value;
109 | }
110 | }
111 |
112 | public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
113 | {
114 | while (!cancellationToken.IsCancellationRequested)
115 | {
116 | string value;
117 | try
118 | {
119 | value = await ReadAsync(cancellationToken);
120 | }
121 | catch (EndOfStreamException)
122 | {
123 | yield break;
124 | }
125 | yield return value;
126 | }
127 | }
128 | #endregion
129 | }
130 |
--------------------------------------------------------------------------------
/Source/Client/Client.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.IO.Pipes;
3 | using System.Runtime.InteropServices;
4 |
5 | using PowerServe.Shared;
6 |
7 | namespace PowerServe;
8 | static class Client
9 | {
10 | ///
11 | /// An invocation of the client. We connect, send the script, and receive the response in JSONLines format.
12 | ///
13 | public static async Task InvokeScript(string script, string pipeName, string? workingDirectory, bool verbose, CancellationToken cancellationToken, string? exeDir, int depth)
14 | {
15 | if (verbose)
16 | {
17 | // Log all tracing to stderr.
18 | Trace.Listeners.Add(new ConsoleTraceListener(useErrorStream: true));
19 | }
20 |
21 | using var pipeClient = new NamedPipeClientStream(
22 | ".",
23 | pipeName,
24 | PipeDirection.InOut,
25 | PipeOptions.Asynchronous
26 | );
27 |
28 | try
29 | {
30 |
31 | // While we could use some pipe existence checks, they are platform-specific, and this should only incur a small "cold-start" penalty which is why we use Connect instead
32 | // FIXME: There is a risk the server is unresponsive and we try to create a second listener here.
33 | await pipeClient.ConnectAsync(500, cancellationToken);
34 | }
35 | catch (OperationCanceledException)
36 | {
37 | throw new OperationCanceledException("Connection to PowerServe was canceled");
38 | }
39 |
40 | if (!pipeClient.IsConnected)
41 | {
42 | Trace.TraceInformation($"PowerServe is not listening on pipe {pipeName}. Spawning new pwsh.exe PowerServe listener...");
43 |
44 | if (string.IsNullOrWhiteSpace(exeDir))
45 | {
46 | exeDir = Environment.GetEnvironmentVariable("POWERSERVE_EXE_DIR")
47 | ?? Path.GetDirectoryName(Process.GetCurrentProcess().MainModule?.FileName)
48 | ?? Environment.CurrentDirectory;
49 | }
50 |
51 | ProcessStartInfo startInfo = new()
52 | {
53 | FileName = "pwsh",
54 | CreateNoWindow = true,
55 | UseShellExecute = false,
56 | WindowStyle = ProcessWindowStyle.Hidden,
57 | WorkingDirectory = workingDirectory ?? exeDir,
58 | ArgumentList =
59 | {
60 | "-NoProfile",
61 | "-NoExit",
62 | "-NonInteractive",
63 | "-Command", $"Import-Module $(Join-Path (Resolve-Path '{exeDir}') 'PowerServe.dll');Start-PowerServe -PipeName {pipeName}"
64 | }
65 | };
66 | Process process = new()
67 | {
68 | StartInfo = startInfo
69 | };
70 |
71 | try
72 | {
73 | process.Start();
74 | // Shouldn't take more than 3 seconds to start up
75 | await pipeClient.ConnectAsync(3000, cancellationToken);
76 | }
77 | catch (OperationCanceledException)
78 | {
79 | throw new OperationCanceledException("PowerServe startup was cancelled.");
80 | }
81 | finally
82 | {
83 | if (!pipeClient.IsConnected)
84 | {
85 | // Cleanup the process if running
86 | Trace.TraceInformation($"PowerServe did not successfully start listening on {pipeName}. Attempting to kill the process.");
87 | // Let these exceptions bubble up
88 | process.Kill();
89 | }
90 | }
91 | }
92 |
93 | if (!pipeClient.IsConnected)
94 | {
95 | throw new InvalidOperationException($"Failed to connect to PowerServe on pipe {pipeName}.");
96 | }
97 |
98 | Trace.TraceInformation($"Connected to PowerServe on Pipe {pipeName}.");
99 |
100 | Trace.TraceInformation($"Script contents: {script}");
101 |
102 | StreamString reader = new(pipeClient);
103 | StreamString writer = new(pipeClient);
104 |
105 | // Register a callback to send <> when cancellation is requested
106 | cancellationToken.Register(() =>
107 | {
108 | Trace.TraceWarning("Cancellation requested. Sending <> to server.");
109 | writer.Write("<>");
110 | });
111 |
112 | // Send the script to the server
113 | string stringWithDepth = $"{depth} {script}";
114 | Trace.TraceInformation($"Writing to Server: {stringWithDepth}");
115 | int writtenBytes = await writer.WriteAsync(stringWithDepth, cancellationToken);
116 | Trace.TraceInformation($"Wrote {writtenBytes} bytes to server.");
117 |
118 | string? response;
119 | while ((response = reader.Read()) != null)
120 | {
121 | if (response == "<>")
122 | {
123 | Trace.TraceInformation("Received <>. Terminating normally.");
124 | break;
125 | }
126 |
127 | if (response == "<>")
128 | {
129 | Trace.TraceInformation("Script Cancelled Successfully.");
130 | continue;
131 | }
132 |
133 | string[] responseParts = response.Split(':', 2);
134 | if (responseParts.Length != 2 && responseParts[0].Length != 1)
135 | {
136 | throw new InvalidOperationException($"Invalid message format from server: {response}");
137 | }
138 |
139 | string type = responseParts[0];
140 | string message = responseParts[1];
141 |
142 | switch (type)
143 | {
144 | case "O":
145 | Console.WriteLine(message);
146 | continue;
147 | case "D":
148 | Console.Error.WriteLine($"DEBUG: {message}");
149 | continue;
150 | case "V":
151 | Console.Error.WriteLine($"VERBOSE: {message}");
152 | continue;
153 | case "I":
154 | Console.WriteLine($"INFO: {message}");
155 | continue;
156 | case "W":
157 | Console.Error.WriteLine($"WARNING: {message}");
158 | continue;
159 | case "E":
160 | Console.Error.WriteLine($"ERROR: {message}");
161 | continue;
162 | default:
163 | throw new InvalidOperationException($"Invalid message type from server: {type}");
164 | }
165 | }
166 |
167 | if (response != "<>")
168 | {
169 | throw new InvalidOperationException("Connection closed unexpectedly before receiving <>.");
170 | }
171 | }
172 |
173 | [DllImport("kernel32.dll", SetLastError = true)]
174 | public static extern bool GetNamedPipeServerProcessId(IntPtr hPipe, out int ClientProcessId);
175 | }
176 |
--------------------------------------------------------------------------------
/Source/Server/Server.cs:
--------------------------------------------------------------------------------
1 | using System.IO.Pipes;
2 | using System.Management.Automation;
3 | using System.Management.Automation.Runspaces;
4 |
5 | using Microsoft.PowerShell.Commands;
6 |
7 | using PowerServe.Shared;
8 |
9 | namespace PowerServe;
10 |
11 | public class PowerShellTarget
12 | {
13 | private readonly InitialSessionState initialSessionState = InitialSessionState.CreateDefault();
14 | private readonly RunspacePool runspacePool;
15 |
16 | public PowerShellTarget() : this(null) { }
17 | public PowerShellTarget(int? maxRunspaces)
18 | {
19 | runspacePool = RunspaceFactory.CreateRunspacePool(initialSessionState);
20 | runspacePool.SetMaxRunspaces(maxRunspaces ?? Environment.ProcessorCount * 2);
21 | runspacePool.Open();
22 | }
23 |
24 | public async Task RunScriptJsonAsync(string script, Func writeClient, CancellationToken cancellationToken, int depth = 5)
25 | {
26 | _ = Console.Error.WriteLineAsync($"Running script: {script}");
27 | using PowerShell ps = PowerShell.Create();
28 | ps.RunspacePool = runspacePool;
29 |
30 | using PSDataCollection outputCollection = new() { BlockingEnumerator = true };
31 |
32 | cancellationToken.Register(() =>
33 | {
34 | _ = Console.Error.WriteLineAsync($"Cancellation requested. Stopping script: {script}");
35 | // This generates a PipelineStoppedException that must be handled
36 | ps.Stop();
37 | _ = Console.Error.WriteLineAsync($"Script Stopped");
38 | });
39 |
40 | if (cancellationToken.IsCancellationRequested)
41 | {
42 | _ = Console.Error.WriteLineAsync($"Cancellation requested. Stopping script: {script}");
43 | return;
44 | }
45 | PSInvocationSettings settings = new()
46 | {
47 | RemoteStreamOptions = RemoteStreamOptions.AddInvocationInfoToErrorRecord
48 | };
49 |
50 | ps.AddScript(script);
51 | // Redirect all streams to output (e.g. Error, Warning) so it is captured by the collection
52 | ps.Commands.Commands[0].MergeMyResults(PipelineResultTypes.All, PipelineResultTypes.Output);
53 |
54 | Task> invokeTask = ps.InvokeAsync(
55 | input: null,
56 | outputCollection,
57 | settings,
58 | callback: null,
59 | state: null
60 | );
61 |
62 | JsonObject.ConvertToJsonContext context = new(depth, true, true);
63 |
64 | // Since the collection is blocking, this should stream appropriately and in order until completed
65 | Task outputTask = Task.Run(async () =>
66 | {
67 | foreach (var item in outputCollection)
68 | {
69 | // Stringify and format the resuilts so the client knows how to handle them
70 | string result = item.BaseObject switch
71 | {
72 | DebugRecord dr => $"D:{dr}",
73 | VerboseRecord vr => $"V:{vr}",
74 | InformationRecord ir => $"I:{ir}",
75 | WarningRecord wr => $"W:{wr}",
76 | ErrorRecord er => $"E:{er}",
77 | _ => "O:" + JsonObject.ConvertToJson(item, in context)
78 | };
79 |
80 | _ = Console.Error.WriteLineAsync($"Item Output: {result}");
81 | await writeClient(result);
82 | }
83 | });
84 |
85 | try
86 | {
87 | _ = await invokeTask;
88 | }
89 | catch (RuntimeException ex)
90 | {
91 | _ = Console.Error.WriteLineAsync($"Script Error: {ex}");
92 | await writeClient($"SCRIPT ERROR: {ex.InnerException} {ex.InnerException.StackTrace}");
93 | }
94 | catch (Exception ex)
95 | {
96 | _ = Console.Error.WriteLineAsync($"Server Error: {ex}");
97 | await writeClient($"SERVER ERROR: {ex}");
98 | }
99 | // PowerShell doesn't auto-close the collection, we must do it manually. This will unblock the outputTask after it finishes all the items in the collection.
100 | outputCollection.Complete();
101 | await outputTask;
102 | }
103 | }
104 |
105 | public class Server
106 | {
107 | private readonly PowerShellTarget Target = new();
108 | private readonly CancellationTokenSource CancellationTokenSource = new();
109 | private CancellationToken CancellationToken => CancellationTokenSource.Token;
110 | private int ClientSessionId = 1;
111 |
112 | public async Task StartAsync(string pipeName) => await StartAsync(pipeName, CancellationToken);
113 | public async Task StartAsync(string pipeName, CancellationToken cancellationToken)
114 | {
115 | if (string.IsNullOrWhiteSpace(pipeName))
116 | {
117 | pipeName = "PowerServe-" + Environment.UserName;
118 | }
119 | Dictionary retryExceptions = [];
120 | NamedPipeServerStream? pipeServer = CreatePipe(pipeName);
121 |
122 | try
123 | {
124 | while (!cancellationToken.IsCancellationRequested)
125 | {
126 | try
127 | {
128 | // This should be the first await in the method so that the pipe server is running before we return to our caller.
129 | _ = Console.Out.WriteLineAsync($"Listening for client {ClientSessionId} on {pipeName}");
130 | await pipeServer.WaitForConnectionAsync(cancellationToken).ConfigureAwait(false);
131 | }
132 | catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
133 | {
134 | break;
135 | }
136 | catch (IOException ex)
137 | {
138 | retryExceptions.TryGetValue(ex.GetType(), out int retryThisExceptionType);
139 | retryExceptions[ex.GetType()] = retryThisExceptionType + 1;
140 |
141 | // The client has disconnected prematurely before WaitForConnectionAsync could pick it up.
142 | // Ignore that and wait for the next connection unless cancellation is requested.
143 | if (cancellationToken.IsCancellationRequested)
144 | {
145 | break;
146 | }
147 |
148 | await pipeServer.DisposeAsync().ConfigureAwait(false);
149 | pipeServer = CreatePipe(pipeName);
150 | continue;
151 | }
152 |
153 | // A client has connected. Open a stream to it (and possibly start listening for the next client)
154 | // unless cancellation is requested.
155 | if (!cancellationToken.IsCancellationRequested)
156 | {
157 | _ = Console.Out.WriteLineAsync($"Client {ClientSessionId} connected to {pipeName}");
158 | ClientSessionId++;
159 |
160 |
161 | // We invoke the callback in a fire-and-forget fashion as documented. It handles its own exceptions.
162 | _ = HandleClientConnectionAsync(pipeServer);
163 | // Start listening for the next client.
164 | pipeServer = CreatePipe(pipeName);
165 | }
166 | }
167 | }
168 | finally
169 | {
170 | if (pipeServer is not null)
171 | {
172 | await pipeServer.DisposeAsync().ConfigureAwait(false);
173 | }
174 | }
175 |
176 | static NamedPipeServerStream CreatePipe(string pipeName)
177 | {
178 | return new(
179 | pipeName,
180 | PipeDirection.InOut,
181 | NamedPipeServerStream.MaxAllowedServerInstances,
182 | PipeTransmissionMode.Byte,
183 | PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly
184 | );
185 | }
186 | }
187 |
188 | async Task HandleClientConnectionAsync(NamedPipeServerStream serverStream)
189 | {
190 | StreamString reader = new(serverStream);
191 | StreamString writer = new(serverStream);
192 | string inFromClient = await reader.ReadAsync();
193 |
194 | _ = Console.Out.WriteLineAsync("IN: " + inFromClient);
195 | // We accept from client either a depth and script or just a script. ex. 0 AAAFFESERS
196 | var parts = inFromClient.Split(' ', 2);
197 | int depth = 5; // default depth value
198 | string script = parts.Length == 2 && int.TryParse(parts[0], out depth) ? parts[1] : inFromClient;
199 |
200 | using CancellationTokenSource runScriptCts = new();
201 | try
202 | {
203 |
204 | Task cancellationReadTask = Task.Run(() =>
205 | {
206 | Console.Error.WriteLine("Waiting for Cancellation.");
207 | // This should block on the client until a new line is received or the stream is closed.
208 | try
209 | {
210 | if (reader.Read() == "<>")
211 | {
212 | Console.Error.WriteLine("Cancel message received by client. Cancelling script.");
213 | runScriptCts.Cancel();
214 | }
215 | else
216 | {
217 | Console.Error.WriteLine("No cancel message received. Continuing script.");
218 | }
219 | }
220 | catch (EndOfStreamException)
221 | {
222 | Console.Error.WriteLine("Stream completed with no cancellation. This is normal.");
223 | }
224 | });
225 |
226 | await Target.RunScriptJsonAsync(
227 | script,
228 | async outLine => await writer.WriteAsync(outLine),
229 | runScriptCts.Token,
230 | depth
231 | );
232 | }
233 | catch (PipelineStoppedException)
234 | {
235 | if (!runScriptCts.IsCancellationRequested)
236 | {
237 | Console.Error.WriteLine($"SERVER ERROR: Script unexpectedly stopped");
238 | }
239 | Console.Error.WriteLine($"Script Succssfully Cancelled");
240 | await writer.WriteAsync("<>");
241 | }
242 | catch (Exception ex)
243 | {
244 | Console.Error.WriteLine($"Error running script: {ex}");
245 | if (serverStream.CanWrite)
246 | {
247 | await writer.WriteAsync($"SERVER ERROR: {ex}");
248 | }
249 | }
250 |
251 | // Signal the end of the response.
252 | _ = Console.Out.WriteLineAsync("End of script ");
253 | await writer.WriteAsync("<>");
254 | }
255 | }
--------------------------------------------------------------------------------
/Source/Server/packages.lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "dependencies": {
4 | "net8.0": {
5 | "Microsoft.PowerShell.Commands.Utility": {
6 | "type": "Direct",
7 | "requested": "[7.4.0, )",
8 | "resolved": "7.4.0",
9 | "contentHash": "wJIRRORIYhKw7JSfaFoUg1NaT8nsqLAA3ZLcnD6TJUPcOAixZbZyEIVGVMoTTP7IzInx4znyaAV8A4aXtVdfGQ==",
10 | "dependencies": {
11 | "JsonSchema.Net": "5.2.6",
12 | "Markdig.Signed": "0.33.0",
13 | "Microsoft.CodeAnalysis.CSharp": "4.8.0-2.final",
14 | "Microsoft.PowerShell.MarkdownRender": "7.2.1",
15 | "System.Drawing.Common": "8.0.0",
16 | "System.Management.Automation": "7.4.0",
17 | "System.Threading.AccessControl": "8.0.0"
18 | }
19 | },
20 | "System.Management.Automation": {
21 | "type": "Direct",
22 | "requested": "[7.4.0, )",
23 | "resolved": "7.4.0",
24 | "contentHash": "nWsPB750tBAA6+08kcRY9fiV2eiRK6JYmySL4/IllocnA+gCUP2+sHX1enzy4uQ5DHE4SgFNv9yW+7tKX7uqsw==",
25 | "dependencies": {
26 | "Microsoft.ApplicationInsights": "2.21.0",
27 | "Microsoft.Management.Infrastructure": "3.0.0",
28 | "Microsoft.PowerShell.CoreCLR.Eventing": "7.4.0",
29 | "Microsoft.PowerShell.Native": "7.4.0",
30 | "Microsoft.Security.Extensions": "1.2.0",
31 | "Microsoft.Win32.Registry.AccessControl": "8.0.0",
32 | "Newtonsoft.Json": "13.0.3",
33 | "System.Configuration.ConfigurationManager": "8.0.0",
34 | "System.Diagnostics.DiagnosticSource": "8.0.0",
35 | "System.DirectoryServices": "8.0.0",
36 | "System.Management": "8.0.0",
37 | "System.Security.AccessControl": "6.0.2-mauipre.1.22102.15",
38 | "System.Security.Cryptography.Pkcs": "8.0.0",
39 | "System.Security.Permissions": "8.0.0",
40 | "System.Text.Encoding.CodePages": "8.0.0"
41 | }
42 | },
43 | "JetBrains.Annotations": {
44 | "type": "Transitive",
45 | "resolved": "2021.2.0",
46 | "contentHash": "kKSyoVfndMriKHLfYGmr0uzQuI4jcc3TKGyww7buJFCYeHb/X0kodYBPL7n9454q7v6ASiRmDgpPGaDGerg/Hg=="
47 | },
48 | "Json.More.Net": {
49 | "type": "Transitive",
50 | "resolved": "1.9.0",
51 | "contentHash": "MMjd2dOh32hLbcZg9YyA+7aEH9gu2cMTEAWrQY17in4+aEsPg2NtYTcwgWHJS9Tt2WUx+4iN1mNegR2uiEwsVQ==",
52 | "dependencies": {
53 | "System.Text.Json": "6.0.2"
54 | }
55 | },
56 | "JsonPointer.Net": {
57 | "type": "Transitive",
58 | "resolved": "3.0.3",
59 | "contentHash": "mCGQc15lHLp1R2CVhWiipnZurHXm93+LbPPAT/vXQm5PdHt6WQuYLhaEF8VZ+aXL9P2I6bGND6pDTEfqFs6gig==",
60 | "dependencies": {
61 | "Json.More.Net": "1.8.0"
62 | }
63 | },
64 | "JsonSchema.Net": {
65 | "type": "Transitive",
66 | "resolved": "5.2.6",
67 | "contentHash": "Zu+Zh6v7GVcqUxA2Ur1SifMMUIvaJULYZijscqiofEg6H6XuGuItXLZanaLp6PU2wtUoLVu4mcSPvyZvCEp5Lg==",
68 | "dependencies": {
69 | "JetBrains.Annotations": "2021.2.0",
70 | "Json.More.Net": "1.9.0",
71 | "JsonPointer.Net": "3.0.3"
72 | }
73 | },
74 | "Markdig.Signed": {
75 | "type": "Transitive",
76 | "resolved": "0.33.0",
77 | "contentHash": "/BE/XANxmocgEqajbWB/ur4Jei+j1FkXppWH9JFmEuoq8T3xJndkQKZVCW/7lTdc9Ru6kfEAkwSXFOv30EkU2Q=="
78 | },
79 | "Microsoft.ApplicationInsights": {
80 | "type": "Transitive",
81 | "resolved": "2.21.0",
82 | "contentHash": "btZEDWAFNo9CoYliMCriSMTX3ruRGZTtYw4mo2XyyfLlowFicYVM2Xszi5evDG95QRYV7MbbH3D2RqVwfZlJHw==",
83 | "dependencies": {
84 | "System.Diagnostics.DiagnosticSource": "5.0.0"
85 | }
86 | },
87 | "Microsoft.CodeAnalysis.Analyzers": {
88 | "type": "Transitive",
89 | "resolved": "3.3.4",
90 | "contentHash": "AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g=="
91 | },
92 | "Microsoft.CodeAnalysis.Common": {
93 | "type": "Transitive",
94 | "resolved": "4.8.0-2.final",
95 | "contentHash": "sH+5d3H18D8W13Kgusib4usJRWnDcZoJ3nU7MiIlytg7uiLA8DlAQKWEk+x8h8SJOD7CSeqqL9/D6c6ShqidLg==",
96 | "dependencies": {
97 | "Microsoft.CodeAnalysis.Analyzers": "3.3.4",
98 | "System.Collections.Immutable": "7.0.0",
99 | "System.Reflection.Metadata": "7.0.0",
100 | "System.Runtime.CompilerServices.Unsafe": "6.0.0"
101 | }
102 | },
103 | "Microsoft.CodeAnalysis.CSharp": {
104 | "type": "Transitive",
105 | "resolved": "4.8.0-2.final",
106 | "contentHash": "2HS51hRSY7NbyiQAOW/0WQArfqkUhVWqmN+Z/KEeqnm+6fk46HmYesvN/BS5RKa1KswcjYYK92xdne+WdhebiQ==",
107 | "dependencies": {
108 | "Microsoft.CodeAnalysis.Common": "[4.8.0-2.final]"
109 | }
110 | },
111 | "Microsoft.Management.Infrastructure": {
112 | "type": "Transitive",
113 | "resolved": "3.0.0",
114 | "contentHash": "cGZi0q5IujCTVYKo9h22Pw+UwfZDV82HXO8HTxMG2HqntPlT3Ls8jY6punLp4YzCypJNpfCAu2kae3TIyuAiJw==",
115 | "dependencies": {
116 | "Microsoft.Management.Infrastructure.Runtime.Unix": "3.0.0",
117 | "Microsoft.Management.Infrastructure.Runtime.Win": "3.0.0"
118 | }
119 | },
120 | "Microsoft.Management.Infrastructure.Runtime.Unix": {
121 | "type": "Transitive",
122 | "resolved": "3.0.0",
123 | "contentHash": "QZE3uEDvZ0m7LabQvcmNOYHp7v1QPBVMpB/ild0WEE8zqUVAP5y9rRI5we37ImI1bQmW5pZ+3HNC70POPm0jBQ=="
124 | },
125 | "Microsoft.Management.Infrastructure.Runtime.Win": {
126 | "type": "Transitive",
127 | "resolved": "3.0.0",
128 | "contentHash": "uwMyWN33+iQ8Wm/n1yoPXgFoiYNd0HzJyoqSVhaQZyJfaQrJR3udgcIHjqa1qbc3lS6kvfuUMN4TrF4U4refCQ=="
129 | },
130 | "Microsoft.PowerShell.CoreCLR.Eventing": {
131 | "type": "Transitive",
132 | "resolved": "7.4.0",
133 | "contentHash": "WHcqfVoaP2dZuf93GS7dk117+/CuLNCqiJN8JUhMthtJuA/lvIzblIzUf3yiEppm1QnINvF1wjy4sB1nXUuGqQ==",
134 | "dependencies": {
135 | "System.Diagnostics.EventLog": "8.0.0"
136 | }
137 | },
138 | "Microsoft.PowerShell.MarkdownRender": {
139 | "type": "Transitive",
140 | "resolved": "7.2.1",
141 | "contentHash": "o5oUwL23R/KnjQPD2Oi49WAG5j4O4VLo1fPRSyM/aq0HuTrY2RnF4B3MCGk13BfcmK51p9kPlHZ1+8a/ZjO4Jg==",
142 | "dependencies": {
143 | "Markdig.Signed": "0.31.0"
144 | }
145 | },
146 | "Microsoft.PowerShell.Native": {
147 | "type": "Transitive",
148 | "resolved": "7.4.0",
149 | "contentHash": "FlaJ3JBWhqFToYT0ycMb/Xxzoof7oTQbNyI4UikgubC7AMWt5ptBNKjIAMPvOcvEHr+ohaO9GvRWp3tiyS3sKw=="
150 | },
151 | "Microsoft.Security.Extensions": {
152 | "type": "Transitive",
153 | "resolved": "1.2.0",
154 | "contentHash": "GjHZBE5PHKrxPRyGujWQKwbKNjPQYds6HcAWKeV49X3KPgBfF2B1vV5uJey5UluyGQlvAO/DezL7WzEx9HlPQA=="
155 | },
156 | "Microsoft.Win32.Registry.AccessControl": {
157 | "type": "Transitive",
158 | "resolved": "8.0.0",
159 | "contentHash": "u8PB9/v02C8mBXzl0vJ7bOyC020zOP+T1mRct+KA46DqZkB40XtsNn9pGD0QowTRsT6R4jPCghn+yAODn2UMMw=="
160 | },
161 | "Microsoft.Win32.SystemEvents": {
162 | "type": "Transitive",
163 | "resolved": "8.0.0",
164 | "contentHash": "9opKRyOKMCi2xJ7Bj7kxtZ1r9vbzosMvRrdEhVhDz8j8MoBGgB+WmC94yH839NPH+BclAjtQ/pyagvi/8gDLkw=="
165 | },
166 | "Newtonsoft.Json": {
167 | "type": "Transitive",
168 | "resolved": "13.0.3",
169 | "contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
170 | },
171 | "System.CodeDom": {
172 | "type": "Transitive",
173 | "resolved": "8.0.0",
174 | "contentHash": "WTlRjL6KWIMr/pAaq3rYqh0TJlzpouaQ/W1eelssHgtlwHAH25jXTkUphTYx9HaIIf7XA6qs/0+YhtLEQRkJ+Q=="
175 | },
176 | "System.Collections.Immutable": {
177 | "type": "Transitive",
178 | "resolved": "7.0.0",
179 | "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ=="
180 | },
181 | "System.Configuration.ConfigurationManager": {
182 | "type": "Transitive",
183 | "resolved": "8.0.0",
184 | "contentHash": "JlYi9XVvIREURRUlGMr1F6vOFLk7YSY4p1vHo4kX3tQ0AGrjqlRWHDi66ImHhy6qwXBG3BJ6Y1QlYQ+Qz6Xgww==",
185 | "dependencies": {
186 | "System.Diagnostics.EventLog": "8.0.0",
187 | "System.Security.Cryptography.ProtectedData": "8.0.0"
188 | }
189 | },
190 | "System.Diagnostics.DiagnosticSource": {
191 | "type": "Transitive",
192 | "resolved": "8.0.0",
193 | "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ=="
194 | },
195 | "System.Diagnostics.EventLog": {
196 | "type": "Transitive",
197 | "resolved": "8.0.0",
198 | "contentHash": "fdYxcRjQqTTacKId/2IECojlDSFvp7LP5N78+0z/xH7v/Tuw5ZAxu23Y6PTCRinqyu2ePx+Gn1098NC6jM6d+A=="
199 | },
200 | "System.DirectoryServices": {
201 | "type": "Transitive",
202 | "resolved": "8.0.0",
203 | "contentHash": "7nit//efUTy1OsAKco2f02PMrwsR2S234N0dVVp84udC77YcvpOQDz5znAWMtgMWBzY1aRJvUW61jo/7vQRfXg=="
204 | },
205 | "System.Drawing.Common": {
206 | "type": "Transitive",
207 | "resolved": "8.0.0",
208 | "contentHash": "JkbHJjtI/dWc5dfmEdJlbe3VwgZqCkZRtfuWFh5GOv0f+gGCfBtzMpIVkmdkj2AObO9y+oiOi81UGwH3aBYuqA==",
209 | "dependencies": {
210 | "Microsoft.Win32.SystemEvents": "8.0.0"
211 | }
212 | },
213 | "System.Formats.Asn1": {
214 | "type": "Transitive",
215 | "resolved": "8.0.0",
216 | "contentHash": "AJukBuLoe3QeAF+mfaRKQb2dgyrvt340iMBHYv+VdBzCUM06IxGlvl0o/uPOS7lHnXPN6u8fFRHSHudx5aTi8w=="
217 | },
218 | "System.Management": {
219 | "type": "Transitive",
220 | "resolved": "8.0.0",
221 | "contentHash": "jrK22i5LRzxZCfGb+tGmke2VH7oE0DvcDlJ1HAKYU8cPmD8XnpUT0bYn2Gy98GEhGjtfbR/sxKTVb+dE770pfA==",
222 | "dependencies": {
223 | "System.CodeDom": "8.0.0"
224 | }
225 | },
226 | "System.Reflection.Metadata": {
227 | "type": "Transitive",
228 | "resolved": "7.0.0",
229 | "contentHash": "MclTG61lsD9sYdpNz9xsKBzjsmsfCtcMZYXz/IUr2zlhaTaABonlr1ESeompTgM+Xk+IwtGYU7/voh3YWB/fWw==",
230 | "dependencies": {
231 | "System.Collections.Immutable": "7.0.0"
232 | }
233 | },
234 | "System.Runtime.CompilerServices.Unsafe": {
235 | "type": "Transitive",
236 | "resolved": "6.0.0",
237 | "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg=="
238 | },
239 | "System.Security.AccessControl": {
240 | "type": "Transitive",
241 | "resolved": "6.0.2-mauipre.1.22102.15",
242 | "contentHash": "ny0SrGGm/O1Q889Zzx1tLP8X0UjkOHjDPN0omy3onMwU1qPrPq90kWvMY8gmh6eHtRkRAGzlJlEer64ii7GMrg=="
243 | },
244 | "System.Security.Cryptography.Pkcs": {
245 | "type": "Transitive",
246 | "resolved": "8.0.0",
247 | "contentHash": "ULmp3xoOwNYjOYp4JZ2NK/6NdTgiN1GQXzVVN1njQ7LOZ0d0B9vyMnhyqbIi9Qw4JXj1JgCsitkTShboHRx7Eg==",
248 | "dependencies": {
249 | "System.Formats.Asn1": "8.0.0"
250 | }
251 | },
252 | "System.Security.Cryptography.ProtectedData": {
253 | "type": "Transitive",
254 | "resolved": "8.0.0",
255 | "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg=="
256 | },
257 | "System.Security.Permissions": {
258 | "type": "Transitive",
259 | "resolved": "8.0.0",
260 | "contentHash": "v/BBylw7XevuAsHXoX9dDUUfmBIcUf7Lkz8K3ZXIKz3YRKpw8YftpSir4n4e/jDTKFoaK37AsC3xnk+GNFI1Ow==",
261 | "dependencies": {
262 | "System.Windows.Extensions": "8.0.0"
263 | }
264 | },
265 | "System.Text.Encoding.CodePages": {
266 | "type": "Transitive",
267 | "resolved": "8.0.0",
268 | "contentHash": "OZIsVplFGaVY90G2SbpgU7EnCoOO5pw1t4ic21dBF3/1omrJFpAGoNAVpPyMVOC90/hvgkGG3VFqR13YgZMQfg=="
269 | },
270 | "System.Text.Encodings.Web": {
271 | "type": "Transitive",
272 | "resolved": "6.0.0",
273 | "contentHash": "Vg8eB5Tawm1IFqj4TVK1czJX89rhFxJo9ELqc/Eiq0eXy13RK00eubyU6TJE6y+GQXjyV5gSfiewDUZjQgSE0w==",
274 | "dependencies": {
275 | "System.Runtime.CompilerServices.Unsafe": "6.0.0"
276 | }
277 | },
278 | "System.Text.Json": {
279 | "type": "Transitive",
280 | "resolved": "6.0.2",
281 | "contentHash": "0nE2gwXLn3PTBOPwORLqwuYvWB+Beomt9ZBX+6LmogMNKUvfD1SoDb/ycB1vBntT94rGaB/SvxEyeLu14H6aEg==",
282 | "dependencies": {
283 | "System.Runtime.CompilerServices.Unsafe": "6.0.0",
284 | "System.Text.Encodings.Web": "6.0.0"
285 | }
286 | },
287 | "System.Threading.AccessControl": {
288 | "type": "Transitive",
289 | "resolved": "8.0.0",
290 | "contentHash": "cIed5+HuYz+eV9yu9TH95zPkqmm1J9Qps9wxjB335sU8tsqc2kGdlTEH9FZzZeCS8a7mNSEsN8ZkyhQp1gfdEw=="
291 | },
292 | "System.Windows.Extensions": {
293 | "type": "Transitive",
294 | "resolved": "8.0.0",
295 | "contentHash": "Obg3a90MkOw9mYKxrardLpY2u0axDMrSmy4JCdq2cYbelM2cUwmUir5Bomvd1yxmPL9h5LVHU1tuKBZpUjfASg=="
296 | }
297 | }
298 | }
299 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | # All files
4 | [*]
5 | indent_style = space
6 |
7 | # Xml files
8 | [*.xml]
9 | indent_size = 2
10 |
11 | # C# files
12 | [*.cs]
13 |
14 | #### Core EditorConfig Options ####
15 |
16 | # Indentation and spacing
17 | indent_size = 2
18 | tab_width = 2
19 |
20 | # New line preferences
21 | end_of_line = crlf
22 | insert_final_newline = false
23 |
24 | #### .NET Coding Conventions ####
25 | [*.{cs,vb}]
26 |
27 | # Organize usings
28 | dotnet_separate_import_directive_groups = true
29 | dotnet_sort_system_directives_first = true
30 | file_header_template = unset
31 |
32 | # this. and Me. preferences
33 | dotnet_style_qualification_for_event = false:silent
34 | dotnet_style_qualification_for_field = false:silent
35 | dotnet_style_qualification_for_method = false:silent
36 | dotnet_style_qualification_for_property = false:silent
37 |
38 | # Language keywords vs BCL types preferences
39 | dotnet_style_predefined_type_for_locals_parameters_members = true:silent
40 | dotnet_style_predefined_type_for_member_access = true:silent
41 |
42 | # Parentheses preferences
43 | dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
44 | dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
45 | dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
46 | dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
47 |
48 | # Modifier preferences
49 | dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
50 |
51 | # Expression-level preferences
52 | dotnet_style_coalesce_expression = true:suggestion
53 | dotnet_style_collection_initializer = true:suggestion
54 | dotnet_style_explicit_tuple_names = true:suggestion
55 | dotnet_style_null_propagation = true:suggestion
56 | dotnet_style_object_initializer = true:suggestion
57 | dotnet_style_operator_placement_when_wrapping = beginning_of_line
58 | dotnet_style_prefer_auto_properties = true:suggestion
59 | dotnet_style_prefer_compound_assignment = true:suggestion
60 | dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
61 | dotnet_style_prefer_conditional_expression_over_return = true:suggestion
62 | dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
63 | dotnet_style_prefer_inferred_tuple_names = true:suggestion
64 | dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
65 | dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
66 | dotnet_style_prefer_simplified_interpolation = true:suggestion
67 |
68 | # Field preferences
69 | dotnet_style_readonly_field = true:warning
70 |
71 | # Parameter preferences
72 | dotnet_code_quality_unused_parameters = all:suggestion
73 |
74 | # Suppression preferences
75 | dotnet_remove_unnecessary_suppression_exclusions = none
76 |
77 | #### C# Coding Conventions ####
78 | [*.cs]
79 |
80 | # var preferences
81 | csharp_style_var_elsewhere = false:silent
82 | csharp_style_var_for_built_in_types = false:silent
83 | csharp_style_var_when_type_is_apparent = false:silent
84 |
85 | # Expression-bodied members
86 | csharp_style_expression_bodied_accessors = true:silent
87 | csharp_style_expression_bodied_constructors = false:silent
88 | csharp_style_expression_bodied_indexers = true:silent
89 | csharp_style_expression_bodied_lambdas = true:suggestion
90 | csharp_style_expression_bodied_local_functions = false:silent
91 | csharp_style_expression_bodied_methods = false:silent
92 | csharp_style_expression_bodied_operators = false:silent
93 | csharp_style_expression_bodied_properties = true:silent
94 |
95 | # Pattern matching preferences
96 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
97 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
98 | csharp_style_prefer_not_pattern = true:suggestion
99 | csharp_style_prefer_pattern_matching = true:silent
100 | csharp_style_prefer_switch_expression = true:suggestion
101 |
102 | # Null-checking preferences
103 | csharp_style_conditional_delegate_call = true:suggestion
104 |
105 | # Modifier preferences
106 | csharp_prefer_static_local_function = true:warning
107 | csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
108 |
109 | # Code-block preferences
110 | csharp_prefer_braces = true:silent
111 | csharp_prefer_simple_using_statement = true:suggestion
112 |
113 | # Expression-level preferences
114 | csharp_prefer_simple_default_expression = true:suggestion
115 | csharp_style_deconstructed_variable_declaration = true:suggestion
116 | csharp_style_inlined_variable_declaration = true:suggestion
117 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
118 | csharp_style_prefer_index_operator = true:suggestion
119 | csharp_style_prefer_range_operator = true:suggestion
120 | csharp_style_throw_expression = true:suggestion
121 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
122 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent
123 |
124 | # 'using' directive preferences
125 | csharp_using_directive_placement = outside_namespace:silent
126 |
127 | #### C# Formatting Rules ####
128 |
129 | # New line preferences
130 | csharp_new_line_before_catch = true
131 | csharp_new_line_before_else = true
132 | csharp_new_line_before_finally = true
133 | csharp_new_line_before_members_in_anonymous_types = true
134 | csharp_new_line_before_members_in_object_initializers = true
135 | csharp_new_line_before_open_brace = all
136 | csharp_new_line_between_query_expression_clauses = true
137 |
138 | # Indentation preferences
139 | csharp_indent_block_contents = true
140 | csharp_indent_braces = false
141 | csharp_indent_case_contents = true
142 | csharp_indent_case_contents_when_block = true
143 | csharp_indent_labels = one_less_than_current
144 | csharp_indent_switch_labels = true
145 |
146 | # Space preferences
147 | csharp_space_after_cast = false
148 | csharp_space_after_colon_in_inheritance_clause = true
149 | csharp_space_after_comma = true
150 | csharp_space_after_dot = false
151 | csharp_space_after_keywords_in_control_flow_statements = true
152 | csharp_space_after_semicolon_in_for_statement = true
153 | csharp_space_around_binary_operators = before_and_after
154 | csharp_space_around_declaration_statements = false
155 | csharp_space_before_colon_in_inheritance_clause = true
156 | csharp_space_before_comma = false
157 | csharp_space_before_dot = false
158 | csharp_space_before_open_square_brackets = false
159 | csharp_space_before_semicolon_in_for_statement = false
160 | csharp_space_between_empty_square_brackets = false
161 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
162 | csharp_space_between_method_call_name_and_opening_parenthesis = false
163 | csharp_space_between_method_call_parameter_list_parentheses = false
164 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
165 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
166 | csharp_space_between_method_declaration_parameter_list_parentheses = false
167 | csharp_space_between_parentheses = false
168 | csharp_space_between_square_brackets = false
169 |
170 | # Wrapping preferences
171 | csharp_preserve_single_line_blocks = true
172 | csharp_preserve_single_line_statements = true
173 |
174 | #### Naming styles ####
175 | [*.{cs,vb}]
176 |
177 | # Naming rules
178 |
179 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
180 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
181 | dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
182 |
183 | dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = none
184 | dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
185 | dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
186 |
187 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
188 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
189 | dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
190 |
191 | dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
192 | dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
193 | dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
194 |
195 | dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
196 | dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
197 | dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
198 |
199 | dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
200 | dotnet_naming_rule.events_should_be_pascalcase.symbols = events
201 | dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
202 |
203 | dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
204 | dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
205 | dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
206 |
207 | dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
208 | dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
209 | dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
210 |
211 | dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
212 | dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
213 | dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
214 |
215 | dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
216 | dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
217 | dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
218 |
219 | dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
220 | dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
221 | dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
222 |
223 | dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
224 | dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
225 | dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
226 |
227 | dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
228 | dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
229 | dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
230 |
231 | dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
232 | dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
233 | dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
234 |
235 | dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
236 | dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
237 | dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
238 |
239 | dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
240 | dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
241 | dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
242 |
243 | dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
244 | dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
245 | dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
246 |
247 | dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
248 | dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
249 | dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
250 |
251 | dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = suggestion
252 | dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
253 | dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
254 |
255 | # Symbol specifications
256 |
257 | dotnet_naming_symbols.interfaces.applicable_kinds = interface
258 | dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
259 | dotnet_naming_symbols.interfaces.required_modifiers =
260 |
261 | dotnet_naming_symbols.enums.applicable_kinds = enum
262 | dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
263 | dotnet_naming_symbols.enums.required_modifiers =
264 |
265 | dotnet_naming_symbols.events.applicable_kinds = event
266 | dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
267 | dotnet_naming_symbols.events.required_modifiers =
268 |
269 | dotnet_naming_symbols.methods.applicable_kinds = method
270 | dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
271 | dotnet_naming_symbols.methods.required_modifiers =
272 |
273 | dotnet_naming_symbols.properties.applicable_kinds = property
274 | dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
275 | dotnet_naming_symbols.properties.required_modifiers =
276 |
277 | dotnet_naming_symbols.public_fields.applicable_kinds = field
278 | dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
279 | dotnet_naming_symbols.public_fields.required_modifiers =
280 |
281 | dotnet_naming_symbols.private_fields.applicable_kinds = field
282 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
283 | dotnet_naming_symbols.private_fields.required_modifiers =
284 |
285 | dotnet_naming_symbols.private_static_fields.applicable_kinds = field
286 | dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
287 | dotnet_naming_symbols.private_static_fields.required_modifiers = static
288 |
289 | dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
290 | dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
291 | dotnet_naming_symbols.types_and_namespaces.required_modifiers =
292 |
293 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
294 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
295 | dotnet_naming_symbols.non_field_members.required_modifiers =
296 |
297 | dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
298 | dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
299 | dotnet_naming_symbols.type_parameters.required_modifiers =
300 |
301 | dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
302 | dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
303 | dotnet_naming_symbols.private_constant_fields.required_modifiers = const
304 |
305 | dotnet_naming_symbols.local_variables.applicable_kinds = local
306 | dotnet_naming_symbols.local_variables.applicable_accessibilities = local
307 | dotnet_naming_symbols.local_variables.required_modifiers =
308 |
309 | dotnet_naming_symbols.local_constants.applicable_kinds = local
310 | dotnet_naming_symbols.local_constants.applicable_accessibilities = local
311 | dotnet_naming_symbols.local_constants.required_modifiers = const
312 |
313 | dotnet_naming_symbols.parameters.applicable_kinds = parameter
314 | dotnet_naming_symbols.parameters.applicable_accessibilities = *
315 | dotnet_naming_symbols.parameters.required_modifiers =
316 |
317 | dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
318 | dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
319 | dotnet_naming_symbols.public_constant_fields.required_modifiers = const
320 |
321 | dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
322 | dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
323 | dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
324 |
325 | dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
326 | dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
327 | dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
328 |
329 | dotnet_naming_symbols.local_functions.applicable_kinds = local_function
330 | dotnet_naming_symbols.local_functions.applicable_accessibilities = *
331 | dotnet_naming_symbols.local_functions.required_modifiers =
332 |
333 | # Naming styles
334 |
335 | dotnet_naming_style.pascalcase.required_prefix =
336 | dotnet_naming_style.pascalcase.required_suffix =
337 | dotnet_naming_style.pascalcase.word_separator =
338 | dotnet_naming_style.pascalcase.capitalization = pascal_case
339 |
340 | dotnet_naming_style.ipascalcase.required_prefix = I
341 | dotnet_naming_style.ipascalcase.required_suffix =
342 | dotnet_naming_style.ipascalcase.word_separator =
343 | dotnet_naming_style.ipascalcase.capitalization = pascal_case
344 |
345 | dotnet_naming_style.tpascalcase.required_prefix = T
346 | dotnet_naming_style.tpascalcase.required_suffix =
347 | dotnet_naming_style.tpascalcase.word_separator =
348 | dotnet_naming_style.tpascalcase.capitalization = pascal_case
349 |
350 | # dotnet_naming_style._camelcase.required_prefix = _
351 | # dotnet_naming_style._camelcase.required_suffix =
352 | # dotnet_naming_style._camelcase.word_separator =
353 | # dotnet_naming_style._camelcase.capitalization = camel_case
354 |
355 | # dotnet_naming_style.camelcase.required_prefix =
356 | # dotnet_naming_style.camelcase.required_suffix =
357 | # dotnet_naming_style.camelcase.word_separator =
358 | # dotnet_naming_style.camelcase.capitalization = camel_case
359 |
360 | # dotnet_naming_style.s_camelcase.required_prefix = s_
361 | # dotnet_naming_style.s_camelcase.required_suffix =
362 | # dotnet_naming_style.s_camelcase.word_separator =
363 | # dotnet_naming_style.s_camelcase.capitalization = camel_case
364 |
365 |
--------------------------------------------------------------------------------