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