From 261715ce347aa1c447cd6180da782a4dffc1cde1 Mon Sep 17 00:00:00 2001 From: adstep Date: Thu, 15 Dec 2022 11:18:11 -0800 Subject: [PATCH] adding documentation for the public interfaces --- .../Example.Protocol/Program.cs | 4 +- .../ProtocolTests.cs | 12 ++-- src/WinRMSharp.Tests/ProtocolTests.cs | 14 ++-- src/WinRMSharp.Tests/WinRMClientTests.cs | 3 +- .../Exceptions/OperationTimeoutException.cs | 2 +- .../Exceptions/TransportException.cs | 10 +++ .../Exceptions/WSManFaultException.cs | 3 + src/WinRMSharp/Exceptions/WinRMException.cs | 3 + src/WinRMSharp/IProtocol.cs | 58 +++++++++++++++- src/WinRMSharp/ITransport.cs | 11 ++++ src/WinRMSharp/Protocol.cs | 66 +++++++++++-------- src/WinRMSharp/Transport.cs | 19 +++++- src/WinRMSharp/WinRMClient.cs | 43 ++++++++---- 13 files changed, 184 insertions(+), 64 deletions(-) diff --git a/src/WinRMSharp.Examples/Example.Protocol/Program.cs b/src/WinRMSharp.Examples/Example.Protocol/Program.cs index a92da3f..b4646ce 100644 --- a/src/WinRMSharp.Examples/Example.Protocol/Program.cs +++ b/src/WinRMSharp.Examples/Example.Protocol/Program.cs @@ -31,10 +31,10 @@ static async Task Main(string[] args) Console.WriteLine($"Stdout: \r\n{commandState.Stdout}"); Console.WriteLine($"Stderr: \r\n{commandState.Stderr}"); - await protocol.CleanupCommand(shellId, commandId); + await protocol.CloseCommand(shellId, commandId); try { - await protocol.CleanupCommand("11111111-1111-1111-1111-111111111111", commandId); + await protocol.CloseCommand("11111111-1111-1111-1111-111111111111", commandId); } catch { } await protocol.CloseShell(shellId); diff --git a/src/WinRMSharp.IntegrationTests/ProtocolTests.cs b/src/WinRMSharp.IntegrationTests/ProtocolTests.cs index a0ac95d..90f20bc 100644 --- a/src/WinRMSharp.IntegrationTests/ProtocolTests.cs +++ b/src/WinRMSharp.IntegrationTests/ProtocolTests.cs @@ -40,7 +40,7 @@ public async Task RunCommandWithArgsAndCleanup() string shellId = await _protocol.OpenShell(); string commandId = await _protocol.RunCommand(shellId, "ipconfig", new string[] { "/all" }); - await _protocol.CleanupCommand(shellId, commandId); + await _protocol.CloseCommand(shellId, commandId); await _protocol.CloseShell(shellId); Assert.Matches(@"^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$", commandId); @@ -52,7 +52,7 @@ public async Task RunCommandWithoutArgsAndCleanup() string shellId = await _protocol.OpenShell(); string commandId = await _protocol.RunCommand(shellId, "hostname"); - await _protocol.CleanupCommand(shellId, commandId); + await _protocol.CloseCommand(shellId, commandId); await _protocol.CloseShell(shellId); Assert.Matches(@"^\w{8}-\w{4}-\w{4}-\w{4}-\w{12}$", commandId); @@ -72,7 +72,7 @@ public async Task RunCommandWithEnv() CommandState state = await _protocol.PollCommandState(shellId, commandId); - await _protocol.CleanupCommand(shellId, commandId); + await _protocol.CloseCommand(shellId, commandId); await _protocol.CloseShell(shellId); Assert.Matches(@"hi mom another var", state.Stdout); @@ -86,7 +86,7 @@ public async Task GetCommandState() CommandState state = await _protocol.PollCommandState(shellId, commandId); - await _protocol.CleanupCommand(shellId, commandId); + await _protocol.CloseCommand(shellId, commandId); await _protocol.CloseShell(shellId); Assert.Equal(0, state.StatusCode); @@ -102,11 +102,11 @@ public async Task RunCommandExceedingOperationTimeout() CommandState state = await _protocol.PollCommandState(shellId, commandId); - await _protocol.CleanupCommand(shellId, commandId); + await _protocol.CloseCommand(shellId, commandId); await _protocol.CloseShell(shellId); Assert.Equal(0, state.StatusCode); Assert.Equal(0, state.Stderr.Length); } } -} \ No newline at end of file +} diff --git a/src/WinRMSharp.Tests/ProtocolTests.cs b/src/WinRMSharp.Tests/ProtocolTests.cs index a54c157..b5459a1 100644 --- a/src/WinRMSharp.Tests/ProtocolTests.cs +++ b/src/WinRMSharp.Tests/ProtocolTests.cs @@ -27,7 +27,7 @@ public async Task RunCommandWithArgsAndCleanup() string shellId = await protocol.OpenShell(); string commandId = await protocol.RunCommand(shellId, "ipconfig", new[] { "/all" }); - await protocol.CleanupCommand(shellId, commandId); + await protocol.CloseCommand(shellId, commandId); await protocol.CloseShell(shellId); Assert.Equal("11111111-1111-1111-1111-111111111114", commandId); @@ -42,7 +42,7 @@ public async Task RunCommandWithoutArgsAndCleanup() string shellId = await protocol.OpenShell(); string commandId = await protocol.RunCommand(shellId, "hostname"); - await protocol.CleanupCommand(shellId, commandId); + await protocol.CloseCommand(shellId, commandId); await protocol.CloseShell(shellId); Assert.Equal("11111111-1111-1111-1111-111111111114", commandId); @@ -58,7 +58,7 @@ public async Task GetCommandState() string commandId = await protocol.RunCommand(shellId, "ipconfig", new[] { "/all" }); CommandState commandState = await protocol.GetCommandState(shellId, commandId); - await protocol.CleanupCommand(shellId, commandId); + await protocol.CloseCommand(shellId, commandId); await protocol.CloseShell(shellId); Assert.NotNull(commandState); @@ -77,7 +77,7 @@ public async Task PollCommandState() string commandId = await protocol.RunCommand(shellId, "ipconfig", new[] { "/all" }); CommandState commandState = await protocol.PollCommandState(shellId, commandId); - await protocol.CleanupCommand(shellId, commandId); + await protocol.CloseCommand(shellId, commandId); await protocol.CloseShell(shellId); Assert.NotNull(commandState); @@ -97,7 +97,7 @@ public async Task SendCommandInput() await protocol.SendCommandInput(shellId, commandId, "echo \"hello world\" && exit\\r\\n"); CommandState commandState = await protocol.GetCommandState(shellId, commandId); - await protocol.CleanupCommand(shellId, commandId); + await protocol.CloseCommand(shellId, commandId); await protocol.CloseShell(shellId); Assert.NotNull(commandState); @@ -126,7 +126,7 @@ public async Task RunCommandExceedingOperationTimeout() CommandState state = await protocol.PollCommandState(shellId, commandId); - await protocol.CleanupCommand(shellId, commandId); + await protocol.CloseCommand(shellId, commandId); await protocol.CloseShell(shellId); Assert.Equal(0, state.StatusCode); @@ -140,7 +140,7 @@ public async Task HandleCleanupCommandFault() MockClient mockClient = new MockClient(); Protocol protocol = new Protocol(mockClient.Transport.Object, mockClient.GuidProvider.Object); - await protocol.CleanupCommand("11111111-1111-1111-1111-111111111113", "11111111-1111-1111-1111-111111111117"); + await protocol.CloseCommand("11111111-1111-1111-1111-111111111113", "11111111-1111-1111-1111-111111111117"); // We've setup the transport to return a failure and we're just checcking an exception doesn't bubble up } diff --git a/src/WinRMSharp.Tests/WinRMClientTests.cs b/src/WinRMSharp.Tests/WinRMClientTests.cs index fe25916..4476394 100644 --- a/src/WinRMSharp.Tests/WinRMClientTests.cs +++ b/src/WinRMSharp.Tests/WinRMClientTests.cs @@ -7,7 +7,7 @@ namespace WinRMSharp.Tests public class WinRMClientTests { [Fact] - public void VerifyConstruciton() + public void VerifyConstruction() { string baseUrl = "https://localhost:5986"; ICredentials credentials = new NetworkCredential() @@ -18,7 +18,6 @@ public void VerifyConstruciton() }; WinRMClientOptions clientOptions = new WinRMClientOptions() { - Locale = "en-US", MaxEnvelopeSize = 1, OperationTimeout = TimeSpan.FromSeconds(20), ReadTimeout = TimeSpan.FromSeconds(30) diff --git a/src/WinRMSharp/Exceptions/OperationTimeoutException.cs b/src/WinRMSharp/Exceptions/OperationTimeoutException.cs index 5755edd..39ca629 100644 --- a/src/WinRMSharp/Exceptions/OperationTimeoutException.cs +++ b/src/WinRMSharp/Exceptions/OperationTimeoutException.cs @@ -1,6 +1,6 @@ namespace WinRMSharp.Exceptions { - internal class OperationTimeoutException : Exception + internal class OperationTimeoutException : WinRMException { public OperationTimeoutException() { diff --git a/src/WinRMSharp/Exceptions/TransportException.cs b/src/WinRMSharp/Exceptions/TransportException.cs index 62bf3ff..80e150c 100644 --- a/src/WinRMSharp/Exceptions/TransportException.cs +++ b/src/WinRMSharp/Exceptions/TransportException.cs @@ -1,8 +1,18 @@ namespace WinRMSharp.Exceptions { + /// + /// Wraps WinRM transport errors (unexpected HTTP error codes, etc). + /// public class TransportException : WinRMException { + /// + /// Http status code of response. + /// public int Code { get; set; } = 500; + + /// + /// Content of response message. + /// public string? Content { get; set; } public TransportException(int code, string content) diff --git a/src/WinRMSharp/Exceptions/WSManFaultException.cs b/src/WinRMSharp/Exceptions/WSManFaultException.cs index 0f605d3..eb89460 100644 --- a/src/WinRMSharp/Exceptions/WSManFaultException.cs +++ b/src/WinRMSharp/Exceptions/WSManFaultException.cs @@ -1,5 +1,8 @@ namespace WinRMSharp.Exceptions { + /// + /// Wraps error information returned as part of a response with a WSManFualt (see https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-wsmv/2b6ab0b1-4d5c-4c13-9f28-0f04716e5fa4). + /// internal class WSManFaultException : WinRMException { public string? FaultCode { get; set; } diff --git a/src/WinRMSharp/Exceptions/WinRMException.cs b/src/WinRMSharp/Exceptions/WinRMException.cs index c946368..cf5d205 100644 --- a/src/WinRMSharp/Exceptions/WinRMException.cs +++ b/src/WinRMSharp/Exceptions/WinRMException.cs @@ -1,5 +1,8 @@ namespace WinRMSharp.Exceptions { + /// + /// Base exeception for encapsulating all WinRM exceptions. + /// public class WinRMException : Exception { public WinRMException() diff --git a/src/WinRMSharp/IProtocol.cs b/src/WinRMSharp/IProtocol.cs index 080889b..54dd0b7 100644 --- a/src/WinRMSharp/IProtocol.cs +++ b/src/WinRMSharp/IProtocol.cs @@ -1,15 +1,71 @@ namespace WinRMSharp { + /// + /// Encapsulates WinRM network protocol for sending/receiving SOAP requests/responses necessary + /// for executing remote commands. + /// public interface IProtocol { + /// + /// Network transport used for sending/receiving SOAP requests/responses. + /// ITransport Transport { get; } + /// + /// Open a shell instance on the destination host. + /// + /// Input streams to open. + /// Output streams to open. + /// Working directory of shell. + /// Environment variables of shell. + /// Time length before shell is closed, if unused. + /// Identifier for opened shell. Task OpenShell(string inputStream = "stdin", string outputStream = "stdout stderr", string? workingDirectory = null, Dictionary? envVars = null, TimeSpan? idleTimeout = null); + + /// + /// Run a command in an opened shell on the destination host. + /// + /// The shell id on the remote machine. See . + /// The command id on the remote machine. See . + /// Array of arguments for the command. + /// Identifier for the executing command. Task RunCommand(string shellId, string command, string[]? args = null); + + /// + /// Send input to a command executing in a shell. + /// + /// The shell id on the remote machine. See . + /// The command id on the remote machine. See . + /// Input text to send. + /// Boolean flag to indicate to close the stdin stream. Task SendCommandInput(string shellId, string commandId, string input, bool end = false); + + /// + /// + /// + /// The shell id on the remote machine. See . + /// The command id on the remote machine. See . Task PollCommandState(string shellId, string commandId); + + /// + /// Gets the latest state of a command executing in a shell. + /// + /// The shell id on the remote machine. See . + /// The command id on the remote machine. See . + /// State of a executing command. Task GetCommandState(string shellId, string commandId); + + /// + /// Cleans up an executed command on the destination host. + /// + /// The shell id on the remote machine. See . + /// The command id on the remote machine. See . + Task CloseCommand(string shellId, string commandId); + + /// + /// Close a shell on the destination host. + /// + /// The shell id on the remote machine. See . Task CloseShell(string shellId); - Task CleanupCommand(string shellId, string commandId); } } diff --git a/src/WinRMSharp/ITransport.cs b/src/WinRMSharp/ITransport.cs index b5002bd..a75dfa2 100644 --- a/src/WinRMSharp/ITransport.cs +++ b/src/WinRMSharp/ITransport.cs @@ -1,7 +1,18 @@ namespace WinRMSharp { + /// + /// Encapsulates network transport for sending/receiving SOAP requests/responses + /// over WinRM. + /// public interface ITransport { + /// + /// Sends an XML message to the destination host. + /// + /// XML request message to send + /// XML response message + /// Thrown when provided credentials are invalid. + /// Thrown when response returns non success status code [200,299). Task Send(string message); } } diff --git a/src/WinRMSharp/Protocol.cs b/src/WinRMSharp/Protocol.cs index e70e3f6..4c0f487 100644 --- a/src/WinRMSharp/Protocol.cs +++ b/src/WinRMSharp/Protocol.cs @@ -9,11 +9,21 @@ namespace WinRMSharp { + /// + /// Options used to configure a instance. + /// public class ProtocolOptions { + /// + /// Maximum allowed time in seconds for any single wsman HTTP operation + /// public TimeSpan? OperationTimeout { get; set; } + + /// + /// Maximum response size in bytes. + /// public int? MaxEnvelopeSize { get; set; } - public string? Locale { get; set; } + } public class Protocol : IProtocol @@ -22,28 +32,41 @@ public class Protocol : IProtocol private static readonly int DefaultMaxEnvelopeSize = 153600; private static readonly string DefaultLocale = "en-US"; - public IGuidProvider GuidProvider { get; private set; } + private readonly IGuidProvider _guidProvider; + + /// public ITransport Transport { get; private set; } + /// + /// Maximum allowed time in seconds for any single wsman HTTP operation + /// public TimeSpan OperationTimeout { get; } + + /// + /// Maximum response size in bytes. + /// public int MaxEnvelopeSize { get; } - public string Locale { get; } internal Protocol(ITransport transport, IGuidProvider guidProvider, ProtocolOptions? options = null) { - GuidProvider = guidProvider; + _guidProvider = guidProvider; Transport = transport; OperationTimeout = options?.OperationTimeout ?? DefaultOperationTimeout; MaxEnvelopeSize = options?.MaxEnvelopeSize ?? DefaultMaxEnvelopeSize; - Locale = options?.Locale ?? DefaultLocale; } + /// + /// Initializes a new instance of the class. + /// + /// Network transport used for sending/receiving SOAP requests/responses. + /// Options to configure instance. public Protocol(ITransport transport, ProtocolOptions? options = null) : this(transport, new GuidProvider(), options) { } + /// public async Task OpenShell(string inputStream = "stdin", string outputStream = "stdout stderr", string? workingDirectory = null, Dictionary? envVars = null, TimeSpan? idleTimeout = null) { const string resourceUri = "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; @@ -94,13 +117,7 @@ public async Task OpenShell(string inputStream = "stdin", string outputS return shellId!; } - /// - /// Run a command on a machine with an open shell. See - /// - /// The shell id on the remote machine. - /// The command to run on the remote machine. - /// An array of arguments for the command. - /// The commandId needed to query the output. + /// public async Task RunCommand(string shellId, string command, string[]? args = null) { const string resourceUri = "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; @@ -145,6 +162,7 @@ public async Task RunCommand(string shellId, string command, string[]? a return commandId; } + /// public async Task SendCommandInput(string shellId, string commandId, string input, bool end = false) { const string resourceUri = "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; @@ -171,6 +189,7 @@ public async Task SendCommandInput(string shellId, string commandId, string inpu await Send(envelope); } + /// public async Task PollCommandState(string shellId, string commandId) { StringBuilder stdoutBuilder = new StringBuilder(); @@ -210,6 +229,7 @@ public async Task PollCommandState(string shellId, string commandI }; } + /// public async Task GetCommandState(string shellId, string commandId) { const string resourceUri = "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; @@ -268,11 +288,7 @@ public async Task GetCommandState(string shellId, string commandId }; } - /// - /// Close the shell. - /// - /// The shell id on the remote machine. - /// + /// public async Task CloseShell(string shellId) { const string resourceUri = "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; @@ -301,14 +317,8 @@ public async Task CloseShell(string shellId) } } - /// - /// Cleans up after a command. See - /// - /// The shell id on the remote machine. See - /// The command id on the remote machine. See - /// - /// - public async Task CleanupCommand(string shellId, string commandId) + /// + public async Task CloseCommand(string shellId, string commandId) { const string resourceUri = "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd"; const string action = "http://schemas.microsoft.com/wbem/wsman/1/windows/shell/Signal"; @@ -419,7 +429,7 @@ private async Task Send(Envelope envelope) private Header GetHeader(string resourceUri, string action, string? shellId = null) { - string messageId = GuidProvider.NewGuid().ToString(); + string messageId = _guidProvider.NewGuid().ToString(); var header = new Header() { @@ -441,12 +451,12 @@ private Header GetHeader(string resourceUri, string action, string? shellId = nu Locale = new Locale() { MustUnderstand = false, - Language = Locale + Language = DefaultLocale }, DataLocale = new Locale() { MustUnderstand = false, - Language = Locale + Language = DefaultLocale }, OperationTimeout = $"PT{OperationTimeout.TotalSeconds}S", ResourceURI = new ResourceURI() diff --git a/src/WinRMSharp/Transport.cs b/src/WinRMSharp/Transport.cs index 190f874..edc02a9 100644 --- a/src/WinRMSharp/Transport.cs +++ b/src/WinRMSharp/Transport.cs @@ -5,17 +5,29 @@ namespace WinRMSharp { + /// + /// Options used to configure a instance. + /// public class TransportOptions { + /// + /// Maximum timeout to wait before an HTTP connect/read times out. + /// public TimeSpan? ReadTimeout { get; set; } } public class Transport : ITransport { - private static TimeSpan DefaultReadTimeout = TimeSpan.FromSeconds(30); + private static readonly TimeSpan DefaultReadTimeout = TimeSpan.FromSeconds(30); - private HttpClient _httpClient; + private readonly HttpClient _httpClient; + /// + /// Initializes a new instance of the class. + /// + /// Base url of the destination host. + /// Credentials used to secure communication to the destination host. + /// Transport options. public Transport(string baseUrl, ICredentials credentials, TransportOptions? options = null) : this(baseUrl, GenerateSecureHandler(credentials), options) { @@ -30,6 +42,7 @@ internal Transport(string baseUrl, HttpMessageHandler messageHandler, TransportO }; } + /// public async Task Send(string message) { StringContent data = new StringContent(message, Encoding.UTF8, "application/soap+xml"); @@ -57,7 +70,7 @@ public async Task Send(string message) statusCode = (int)response.StatusCode; } - throw new TransportException(500, content, ex); + throw new TransportException(statusCode, content, ex); } } diff --git a/src/WinRMSharp/WinRMClient.cs b/src/WinRMSharp/WinRMClient.cs index eff11bc..769e8ee 100644 --- a/src/WinRMSharp/WinRMClient.cs +++ b/src/WinRMSharp/WinRMClient.cs @@ -2,21 +2,31 @@ namespace WinRMSharp { + /// + /// Options used to configure a instance. + /// public class WinRMClientOptions { + /// + /// Maximum allowed time in seconds for any single wsman HTTP operation + /// public TimeSpan? OperationTimeout { get; set; } + + /// + /// Maximum response size in bytes. + /// public int? MaxEnvelopeSize { get; set; } - public string? Locale { get; set; } + /// + /// Maximum timeout to wait before an HTTP connect/read times out. + /// public TimeSpan? ReadTimeout { get; set; } } public class WinRMClient { - private IProtocol _protocol; - - public ITransport Transport => _protocol.Transport; - public IProtocol Protocol => _protocol; + public ITransport Transport => Protocol.Transport; + public IProtocol Protocol { get; private set; } public WinRMClient(string baseUrl, ICredentials credentials, WinRMClientOptions? options = null) { @@ -28,28 +38,33 @@ public WinRMClient(string baseUrl, ICredentials credentials, WinRMClientOptions? ProtocolOptions protocolOptions = new ProtocolOptions { OperationTimeout = options?.OperationTimeout, - MaxEnvelopeSize = options?.MaxEnvelopeSize, - Locale = options?.Locale + MaxEnvelopeSize = options?.MaxEnvelopeSize }; Transport transport = new Transport(baseUrl, credentials, transportOptions); - _protocol = new Protocol(transport, protocolOptions); + Protocol = new Protocol(transport, protocolOptions); } public WinRMClient(IProtocol protocol) { - _protocol = protocol; + Protocol = protocol; } + /// + /// Executes a command on the destination host. + /// + /// Command to execute on the destination host. + /// Array of arguments for the command + /// public async Task RunCommand(string command, string[]? args = null) { - string shellId = await _protocol.OpenShell(); - string commandId = await _protocol.RunCommand(shellId, command, args); + string shellId = await Protocol.OpenShell(); + string commandId = await Protocol.RunCommand(shellId, command, args); - CommandState state = await _protocol.PollCommandState(shellId, commandId); + CommandState state = await Protocol.PollCommandState(shellId, commandId); - await _protocol.CleanupCommand(shellId, commandId); - await _protocol.CloseShell(shellId); + await Protocol.CloseCommand(shellId, commandId); + await Protocol.CloseShell(shellId); return state; }