Added more functions and unit test for WsmSvc

pull/211/head
dahall 2021-01-11 18:46:28 -07:00
parent 4a63607d26
commit 0e22e0f9b4
4 changed files with 614 additions and 8 deletions

View File

@ -7,6 +7,37 @@ namespace Vanara.PInvoke
/// <summary>Items from the WsmSvc.dll</summary>
public static partial class WsmSvc
{
/// <summary>Code page option name to be used with WSManCreateShell API to remotely set the code page</summary>
public const string WSMAN_CMDSHELL_OPTION_CODEPAGE = "WINRS_CODEPAGE";
/// <summary>
/// Option name used with WSManRunShellCommand API to indicate that the client side mode of standard input is Console; default
/// implies Pipe.
/// </summary>
public const string WSMAN_CMDSHELL_OPTION_CONSOLEMODE_STDIN = "WINRS_CONSOLEMODE_STDIN";
/// <summary>To be used with WSManRunShellCommand API to not use cmd.exe /c prefix when launching the command</summary>
public const string WSMAN_CMDSHELL_OPTION_SKIP_CMD_SHELL = "WINRS_SKIP_CMD_SHELL";
/// <summary>pre-defined command states</summary>
public const string WSMAN_COMMAND_STATE_DONE = "/CommandState/Done";
/// <summary>pre-defined command states</summary>
public const string WSMAN_COMMAND_STATE_PENDING= "/CommandState/Pending";
/// <summary>pre-defined command states</summary>
public const string WSMAN_COMMAND_STATE_RUNNING= "/CommandState/Running";
/// <summary>Option name used with WSManCreateShell API to not load the user profile on the remote server</summary>
public const string WSMAN_SHELL_OPTION_NOPROFILE = "WINRS_NOPROFILE";
/// <summary/>
public const string WSMAN_STREAM_ID_STDERR = "stderr";
/// <summary/>
public const string WSMAN_STREAM_ID_STDIN = "stdin";
/// <summary/>
public const string WSMAN_STREAM_ID_STDOUT = "stdout";
private const string Lib_WsmSvc = "WsmSvc.dll";
/// <summary>The callback function that is called for shell operations, which result in a remote request.</summary>
@ -327,6 +358,36 @@ namespace Vanara.PInvoke
WSMAN_OPTION_USE_INTEARACTIVE_TOKEN,
}
/// <summary>Deletes a command and frees the resources that are associated with it.</summary>
/// <param name="commandHandle">Specifies the command handle to be closed. This handle is returned by a WSManRunShellCommand call.</param>
/// <param name="flags">Reserved for future use. Must be set to zero.</param>
/// <param name="async">
/// Defines an asynchronous structure. The asynchronous structure contains an optional user context and a mandatory callback
/// function. See the WSMAN_SHELL_ASYNC structure for more information. This parameter cannot be <c>NULL</c>.
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/nf-wsman-wsmanclosecommand void WSManCloseCommand( WSMAN_COMMAND_HANDLE
// commandHandle, DWORD flags, WSMAN_SHELL_ASYNC *async );
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManCloseCommand")]
public static extern void WSManCloseCommand(WSMAN_COMMAND_HANDLE commandHandle, [In, Optional] uint flags, in WSMAN_SHELL_ASYNC async);
/// <summary>Cancels or closes an asynchronous operation. All resources that are associated with the operation are freed.</summary>
/// <param name="operationHandle">Specifies the operation handle to be closed.</param>
/// <param name="flags">Reserved for future use. Set to zero.</param>
/// <returns>This method returns zero on success. Otherwise, this method returns an error code.</returns>
/// <remarks>
/// The method de-allocates local and remote resources associated with the operation. After the <c>WSManCloseOperation</c> method is
/// called, the operationHandle parameter cannot be passed to any other call. If the callback associated with the operation is
/// pending and has not completed before <c>WSManCloseOperation</c> is called, the operation is marked for deletion and the method
/// returns immediately.
/// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/nf-wsman-wsmancloseoperation DWORD WSManCloseOperation(
// WSMAN_OPERATION_HANDLE operationHandle, DWORD flags );
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManCloseOperation")]
public static extern Win32Error WSManCloseOperation(WSMAN_OPERATION_HANDLE operationHandle, uint flags = 0);
/// <summary>Closes a session object.</summary>
/// <param name="session">
/// Specifies the session handle to close. This handle is returned by a WSManCreateSession call. This parameter cannot be NULL.
@ -418,7 +479,7 @@ namespace Vanara.PInvoke
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManCreateSession")]
public static extern uint WSManCreateSession(WSMAN_API_HANDLE apiHandle, [Optional, MarshalAs(UnmanagedType.LPWStr)] string connection,
[Optional] uint flags, IntPtr serverAuthenticationCredentials, ref WSMAN_PROXY_INFO proxyInfo, out SafeWSMAN_SESSION_HANDLE session);
[Optional] uint flags, IntPtr serverAuthenticationCredentials, [In, Optional] IntPtr proxyInfo, out SafeWSMAN_SESSION_HANDLE session);
/// <summary>
/// Creates a shell object. The returned shell handle identifies an object that defines the context in which commands can be run.
@ -540,6 +601,198 @@ namespace Vanara.PInvoke
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManInitialize")]
public static extern Win32Error WSManInitialize(WSMAN_FLAG_REQUESTED_API_VERSION flags, out SafeWSMAN_API_HANDLE apiHandle);
/// <summary>Retrieves output from a running command or from the shell.</summary>
/// <param name="shell">Specifies the shell handle returned by a WSManCreateShell call. This parameter cannot be <c>NULL</c>.</param>
/// <param name="command">Specifies the command handle returned by a WSManRunShellCommand call.</param>
/// <param name="flags">Reserved for future use. Must be set to zero.</param>
/// <param name="desiredStreamSet">Specifies the requested output from a particular stream or a list of streams.</param>
/// <param name="async">
/// Defines an asynchronous structure. The asynchronous structure contains an optional user context and a mandatory callback
/// function. See the WSMAN_SHELL_ASYNC structure for more information. This parameter cannot be <c>NULL</c> and should be closed by
/// calling the WSManCloseOperation method.
/// </param>
/// <param name="receiveOperation">
/// Defines the operation handle for the receive operation. This handle is returned from a successful call of the function and can
/// be used to asynchronously cancel the receive operation. This handle should be closed by calling the WSManCloseOperation method.
/// This parameter cannot be <c>NULL</c>.
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/nf-wsman-wsmanreceiveshelloutput void WSManReceiveShellOutput(
// WSMAN_SHELL_HANDLE shell, WSMAN_COMMAND_HANDLE command, DWORD flags, WSMAN_STREAM_ID_SET *desiredStreamSet, WSMAN_SHELL_ASYNC
// *async, WSMAN_OPERATION_HANDLE *receiveOperation );
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManReceiveShellOutput")]
public static extern void WSManReceiveShellOutput(WSMAN_SHELL_HANDLE shell, [In, Optional] WSMAN_COMMAND_HANDLE command, [In, Optional] uint flags,
in WSMAN_STREAM_ID_SET desiredStreamSet, in WSMAN_SHELL_ASYNC async, out SafeWSMAN_OPERATION_HANDLE receiveOperation);
/// <summary>Retrieves output from a running command or from the shell.</summary>
/// <param name="shell">Specifies the shell handle returned by a WSManCreateShell call. This parameter cannot be <c>NULL</c>.</param>
/// <param name="command">Specifies the command handle returned by a WSManRunShellCommand call.</param>
/// <param name="flags">Reserved for future use. Must be set to zero.</param>
/// <param name="desiredStreamSet">Specifies the requested output from a particular stream or a list of streams.</param>
/// <param name="async">
/// Defines an asynchronous structure. The asynchronous structure contains an optional user context and a mandatory callback
/// function. See the WSMAN_SHELL_ASYNC structure for more information. This parameter cannot be <c>NULL</c> and should be closed by
/// calling the WSManCloseOperation method.
/// </param>
/// <param name="receiveOperation">
/// Defines the operation handle for the receive operation. This handle is returned from a successful call of the function and can
/// be used to asynchronously cancel the receive operation. This handle should be closed by calling the WSManCloseOperation method.
/// This parameter cannot be <c>NULL</c>.
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/nf-wsman-wsmanreceiveshelloutput void WSManReceiveShellOutput(
// WSMAN_SHELL_HANDLE shell, WSMAN_COMMAND_HANDLE command, DWORD flags, WSMAN_STREAM_ID_SET *desiredStreamSet, WSMAN_SHELL_ASYNC
// *async, WSMAN_OPERATION_HANDLE *receiveOperation );
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManReceiveShellOutput")]
public static extern void WSManReceiveShellOutput(WSMAN_SHELL_HANDLE shell, [In, Optional] WSMAN_COMMAND_HANDLE command, [In, Optional] uint flags,
[In, Optional] IntPtr desiredStreamSet, in WSMAN_SHELL_ASYNC async, out SafeWSMAN_OPERATION_HANDLE receiveOperation);
/// <summary>Starts the execution of a command within an existing shell and does not wait for the completion of the command.</summary>
/// <param name="shell">Specifies the shell handle returned by the WSManCreateShell call. This parameter cannot be <c>NULL</c>.</param>
/// <param name="flags">Reserved for future use. Must be zero.</param>
/// <param name="commandLine">
/// Defines a required <c>null</c>-terminated string that represents the command to be executed. Typically, the command is specified
/// without any arguments, which are specified separately. However, a user can specify the command line and all of the arguments by
/// using this parameter. If arguments are specified for the commandLine parameter, the args parameter should be <c>NULL</c>.
/// </param>
/// <param name="args">
/// A pointer to a WSMAN_COMMAND_ARG_SET structure that defines an array of argument values, which are passed to the command on
/// creation. If no arguments are required, this parameter should be <c>NULL</c>.
/// </param>
/// <param name="options">
/// Defines a set of options for the command. These options are passed to the service to modify or refine the command execution.
/// This parameter can be <c>NULL</c>. For more information about the options, see WSMAN_OPTION_SET.
/// </param>
/// <param name="async">
/// Defines an asynchronous structure. The asynchronous structure contains an optional user context and a mandatory callback
/// function. See the WSMAN_SHELL_ASYNC structure for more information. This parameter cannot be <c>NULL</c> and should be closed by
/// calling the WSManCloseCommand method.
/// </param>
/// <param name="command">
/// Defines the command object associated with a command within a shell. This handle is returned on a successful call and is used to
/// send and receive data and to signal the command. This handle should be closed by calling the WSManCloseCommand method. This
/// parameter cannot be <c>NULL</c>.
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/nf-wsman-wsmanrunshellcommand void WSManRunShellCommand(
// WSMAN_SHELL_HANDLE shell, DWORD flags, PCWSTR commandLine, WSMAN_COMMAND_ARG_SET *args, WSMAN_OPTION_SET *options,
// WSMAN_SHELL_ASYNC *async, WSMAN_COMMAND_HANDLE *command );
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManRunShellCommand")]
public static extern void WSManRunShellCommand(WSMAN_SHELL_HANDLE shell, [Optional] uint flags, [MarshalAs(UnmanagedType.LPWStr)] string commandLine,
in WSMAN_COMMAND_ARG_SET args, in WSMAN_OPTION_SET options, in WSMAN_SHELL_ASYNC async, out WSMAN_COMMAND_HANDLE command);
/// <summary>Starts the execution of a command within an existing shell and does not wait for the completion of the command.</summary>
/// <param name="shell">Specifies the shell handle returned by the WSManCreateShell call. This parameter cannot be <c>NULL</c>.</param>
/// <param name="flags">Reserved for future use. Must be zero.</param>
/// <param name="commandLine">
/// Defines a required <c>null</c>-terminated string that represents the command to be executed. Typically, the command is specified
/// without any arguments, which are specified separately. However, a user can specify the command line and all of the arguments by
/// using this parameter. If arguments are specified for the commandLine parameter, the args parameter should be <c>NULL</c>.
/// </param>
/// <param name="args">
/// A pointer to a WSMAN_COMMAND_ARG_SET structure that defines an array of argument values, which are passed to the command on
/// creation. If no arguments are required, this parameter should be <c>NULL</c>.
/// </param>
/// <param name="options">
/// Defines a set of options for the command. These options are passed to the service to modify or refine the command execution.
/// This parameter can be <c>NULL</c>. For more information about the options, see WSMAN_OPTION_SET.
/// </param>
/// <param name="async">
/// Defines an asynchronous structure. The asynchronous structure contains an optional user context and a mandatory callback
/// function. See the WSMAN_SHELL_ASYNC structure for more information. This parameter cannot be <c>NULL</c> and should be closed by
/// calling the WSManCloseCommand method.
/// </param>
/// <param name="command">
/// Defines the command object associated with a command within a shell. This handle is returned on a successful call and is used to
/// send and receive data and to signal the command. This handle should be closed by calling the WSManCloseCommand method. This
/// parameter cannot be <c>NULL</c>.
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/nf-wsman-wsmanrunshellcommand void WSManRunShellCommand(
// WSMAN_SHELL_HANDLE shell, DWORD flags, PCWSTR commandLine, WSMAN_COMMAND_ARG_SET *args, WSMAN_OPTION_SET *options,
// WSMAN_SHELL_ASYNC *async, WSMAN_COMMAND_HANDLE *command );
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManRunShellCommand")]
public static extern void WSManRunShellCommand(WSMAN_SHELL_HANDLE shell, [Optional] uint flags, [MarshalAs(UnmanagedType.LPWStr)] string commandLine,
[In, Optional] IntPtr args, [In, Optional] IntPtr options, in WSMAN_SHELL_ASYNC async, out WSMAN_COMMAND_HANDLE command);
/// <summary>Pipes the input stream to a running command or to the shell.</summary>
/// <param name="shell">Specifies the shell handle returned by a WSManCreateShell call. This parameter cannot be <c>NULL</c>.</param>
/// <param name="command">
/// Specifies the command handle returned by a WSManRunShellCommand call. This handle should be closed by calling the
/// WSManCloseCommand method.
/// </param>
/// <param name="flags">Reserved for future use. Must be set to zero.</param>
/// <param name="streamId">Specifies the input stream ID. This parameter cannot be <c>NULL</c>.</param>
/// <param name="streamData">
/// Uses the WSMAN_DATA structure to specify the stream data to be sent to the command or shell. This structure should be allocated
/// by the calling client and must remain allocated until <c>WSManSendShellInput</c> completes. If the end of the stream has been
/// reached, the endOfStream parameter should be set to <c>TRUE</c>.
/// </param>
/// <param name="endOfStream">
/// Set to <c>TRUE</c>, if the end of the stream has been reached. Otherwise, this parameter is set to <c>FALSE</c>.
/// </param>
/// <param name="async">
/// Defines an asynchronous structure. The asynchronous structure contains an optional user context and a mandatory callback
/// function. See the WSMAN_SHELL_ASYNC structure for more information. This parameter cannot be <c>NULL</c> and should be closed by
/// calling the WSManCloseCommand method.
/// </param>
/// <param name="sendOperation">
/// Defines the operation handle for the send operation. This handle is returned from a successful call of the function and can be
/// used to asynchronously cancel the send operation. This handle should be closed by calling the WSManCloseOperation method. This
/// parameter cannot be <c>NULL</c>.
/// </param>
/// <returns>None</returns>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/nf-wsman-wsmansendshellinput void WSManSendShellInput(
// WSMAN_SHELL_HANDLE shell, WSMAN_COMMAND_HANDLE command, DWORD flags, PCWSTR streamId, WSMAN_DATA *streamData, BOOL endOfStream,
// WSMAN_SHELL_ASYNC *async, WSMAN_OPERATION_HANDLE *sendOperation );
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManSendShellInput")]
public static extern void WSManSendShellInput(WSMAN_SHELL_HANDLE shell, [In, Optional] WSMAN_COMMAND_HANDLE command, [In, Optional] uint flags,
[MarshalAs(UnmanagedType.LPWStr)] string streamId, in WSMAN_DATA streamData, [MarshalAs(UnmanagedType.Bool)] bool endOfStream,
in WSMAN_SHELL_ASYNC async, out SafeWSMAN_OPERATION_HANDLE sendOperation);
/// <summary>Sets an extended set of options for the session.</summary>
/// <param name="session">Specifies the session handle returned by a WSManCreateSession call. This parameter cannot be <c>NULL</c>.</param>
/// <param name="option">
/// Specifies the option to be set. This parameter must be set to one of the values in the WSManSessionOption enumeration.
/// </param>
/// <param name="data">A pointer to a WSMAN_DATA structure that defines the option value.</param>
/// <returns>This method returns zero on success. Otherwise, this method returns an error code.</returns>
/// <remarks>
/// <para>
/// If the <c>WSManSetSessionOption</c> method is called with different values specified for the option parameter, the order of the
/// different options is important. The first time <c>WSManSetSessionOption</c> is called, the transport is set for the session. If
/// a second call requests a different type of transport, the call will fail.
/// </para>
/// <para>For example, the second method call will fail if the methods are called in the following order:</para>
/// <list type="bullet">
/// <item>
/// <term>
/// <code>WSManSetSessionOption(WSMAN_OPTION_UNENCRYPTED_MESSAGES)</code>
/// </term>
/// </item>
/// <item>
/// <term>
/// <code>WSManSetSessionOption(WSMAN_OPTION_ALLOW_NEGOTIATE_IMPLICIT_CREDENTIALS)</code>
/// </term>
/// </item>
/// </list>
/// <para>
/// The first method call sets the transport to HTTP because the option parameter is set to
/// <c>WSMAN_OPTION_UNENCRYPTED_MESSAGES</c>. The second call fails because the option that was passed is applicable for HTTPS and
/// the transport was set to HTTP by the first message.
/// </para>
/// </remarks>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/nf-wsman-wsmansetsessionoption DWORD WSManSetSessionOption(
// WSMAN_SESSION_HANDLE session, WSManSessionOption option, WSMAN_DATA *data );
[DllImport(Lib_WsmSvc, SetLastError = false, ExactSpelling = true)]
[PInvokeData("wsman.h", MSDNShortId = "NF:wsman.WSManSetSessionOption")]
public static extern Win32Error WSManSetSessionOption(WSMAN_SESSION_HANDLE session, WSManSessionOption option, in WSMAN_DATA data);
/// <summary>Provides a handle to a Windows Remote Client unique identifier.</summary>
[StructLayout(LayoutKind.Sequential)]
public struct WSMAN_API_HANDLE : IHandle
@ -610,6 +863,20 @@ namespace Vanara.PInvoke
public string certificateThumbprint { get => userAccount.username; set => userAccount.username = value; }
}
/// <summary>Represents the set of arguments that are passed in to the command line.</summary>
// https://docs.microsoft.com/en-us/windows/win32/api/wsman/ns-wsman-wsman_command_arg_set typedef struct _WSMAN_COMMAND_ARG_SET {
// DWORD argsCount; PCWSTR *args; } WSMAN_COMMAND_ARG_SET;
[PInvokeData("wsman.h", MSDNShortId = "NS:wsman._WSMAN_COMMAND_ARG_SET")]
[StructLayout(LayoutKind.Sequential)]
public struct WSMAN_COMMAND_ARG_SET
{
/// <summary>Specifies the number of arguments in the array.</summary>
public uint argsCount;
/// <summary>Defines an array of strings that specify the arguments.</summary>
public IntPtr args;
}
/// <summary>Provides a handle to a remote management command.</summary>
[StructLayout(LayoutKind.Sequential)]
public struct WSMAN_COMMAND_HANDLE : IHandle
@ -1257,6 +1524,28 @@ namespace Vanara.PInvoke
protected override bool InternalReleaseHandle() => WSManDeinitialize(handle).Succeeded;
}
/// <summary>Provides a <see cref="SafeHandle"/> for <see cref="WSMAN_OPERATION_HANDLE"/> that is disposed using <see cref="WSManCloseOperation"/>.</summary>
public class SafeWSMAN_OPERATION_HANDLE : SafeHANDLE
{
/// <summary>Initializes a new instance of the <see cref="SafeWSMAN_OPERATION_HANDLE"/> class and assigns an existing handle.</summary>
/// <param name="preexistingHandle">An <see cref="IntPtr"/> object that represents the pre-existing handle to use.</param>
/// <param name="ownsHandle">
/// <see langword="true"/> to reliably release the handle during the finalization phase; otherwise, <see langword="false"/> (not recommended).
/// </param>
public SafeWSMAN_OPERATION_HANDLE(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { }
/// <summary>Initializes a new instance of the <see cref="SafeWSMAN_OPERATION_HANDLE"/> class.</summary>
private SafeWSMAN_OPERATION_HANDLE() : base() { }
/// <summary>Performs an implicit conversion from <see cref="SafeWSMAN_OPERATION_HANDLE"/> to <see cref="WSMAN_OPERATION_HANDLE"/>.</summary>
/// <param name="h">The safe handle instance.</param>
/// <returns>The result of the conversion.</returns>
public static implicit operator WSMAN_OPERATION_HANDLE(SafeWSMAN_OPERATION_HANDLE h) => h.handle;
/// <inheritdoc/>
protected override bool InternalReleaseHandle() => WSManCloseOperation(handle).Succeeded;
}
/// <summary>
/// Provides a <see cref="SafeHandle"/> for <see cref="WSMAN_SESSION_HANDLE"/> that is disposed using <see
/// cref="WSManCloseSession(WSMAN_SESSION_HANDLE, uint)"/>.
@ -1286,7 +1575,6 @@ namespace Vanara.PInvoke
Structures
WSMAN_AUTHZ_QUOTA
WSMAN_CERTIFICATE_DETAILS
WSMAN_COMMAND_ARG_SET
WSMAN_FILTER
WSMAN_FRAGMENT
WSMAN_KEY
@ -1312,8 +1600,6 @@ namespace Vanara.PInvoke
WSMAN_PLUGIN_SIGNAL
Functions
WSManCloseCommand
WSManCloseOperation
WSManConnectShell
WSManConnectShellCommand
WSManCreateShellEx
@ -1329,13 +1615,9 @@ namespace Vanara.PInvoke
WSManPluginOperationComplete
WSManPluginReceiveResult
WSManPluginReportContext
WSManReceiveShellOutput
WSManReconnectShell
WSManReconnectShellCommand
WSManRunShellCommand
WSManRunShellCommandEx
WSManSendShellInput
WSManSetSessionOption
WSManSignalShell
WSMAN_PLUGIN_STARTUP
*/

View File

@ -0,0 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>UnitTest.PInvoke.WsmSvc</AssemblyName>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\PInvoke\WsmSvc\Vanara.PInvoke.WsmSvc.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,309 @@
using NUnit.Framework;
using NUnit.Framework.Internal;
using System;
using Vanara.Extensions;
using Vanara.InteropServices;
using static Vanara.PInvoke.Kernel32;
using static Vanara.PInvoke.WsmSvc;
namespace Vanara.PInvoke.Tests
{
public class ShellClient : IDisposable
{
private SafeWSMAN_API_HANDLE m_apiHandle;
private WSMAN_SHELL_ASYNC m_async;
private bool m_bExecute;
private bool m_bSetup;
private WSMAN_COMMAND_HANDLE m_command;
private Win32Error m_errorCode;
private SafeEventHandle m_event;
private WSMAN_SHELL_ASYNC m_ReceiveAsync;
private Win32Error m_ReceiveErrorCode;
private SafeEventHandle m_ReceiveEvent;
private SafeWSMAN_SESSION_HANDLE m_session;
private WSMAN_SHELL_HANDLE m_shell;
// Constructor.
public ShellClient()
{
}
// Clean up the used resources
public void Dispose()
{
if (!m_command.IsNull)
{
WSManCloseCommand(m_command, 0, m_async);
WaitForSingleObject(m_event, INFINITE);
if (Win32Error.NO_ERROR != m_errorCode)
{
wprintf("WSManCloseCommand failed: {0}\n", m_errorCode);
}
else
{
m_command = default;
}
}
if (!m_shell.IsNull)
{
WSManCloseShell(m_shell, 0, m_async);
WaitForSingleObject(m_event, INFINITE);
if (Win32Error.NO_ERROR != m_errorCode)
{
wprintf("WSManCloseShell failed: {0}\n", m_errorCode);
}
else
{
m_shell = default;
}
}
// Frees memory of session and closes all related operations before returning
m_session?.Dispose();
// deinitializes the Winrm client stack; all operations will finish before this API will return
m_apiHandle?.Dispose();
m_event?.Dispose();
m_ReceiveEvent?.Dispose();
m_bSetup = false;
m_bExecute = false;
}
// Execute shell-related operations
public bool Execute(string resourceUri, string commandLine, byte[] sendData, uint count)
{
if (!m_bSetup)
{
wprintf("Setup() needs to be called first");
return false;
}
if (m_bExecute)
{
wprintf("Execute() can only be called once");
return false;
}
m_bExecute = true;
// WSManCreateShell
WSManCreateShell(m_session, 0, resourceUri, default, default, default, m_async, out m_shell);
WaitForSingleObject(m_event, INFINITE);
if (Win32Error.NO_ERROR != m_errorCode)
{
wprintf("WSManCreateShell failed: {0}\n", m_errorCode);
return false;
}
// WSManRunShellCommand
WSManRunShellCommand(m_shell, 0, commandLine, default, default, m_async, out m_command);
WaitForSingleObject(m_event, INFINITE);
if (Win32Error.NO_ERROR != m_errorCode)
{
wprintf("WSManRunShellCommand failed: {0}\n", m_errorCode);
return false;
}
// WSManReceiveShellOutput
WSManReceiveShellOutput(m_shell, m_command, 0, default, m_ReceiveAsync, out SafeWSMAN_OPERATION_HANDLE receiveOperation);
// Send operation can be executed many times to send large data
if (count >= 1)
{
for (uint i = 1; i <= count; i++)
{
// last send operation should indicate end of stream
if (!Send(sendData, (i == count)))
{
wprintf("Send {0} failed.\n", i);
}
}
}
// Receive operation is finished
WaitForSingleObject(m_ReceiveEvent, INFINITE);
if (Win32Error.NO_ERROR != m_ReceiveErrorCode)
{
wprintf("WSManReceiveShellOutput failed: {0}\n", m_ReceiveErrorCode);
return false;
}
receiveOperation.Dispose();
return true;
}
// Initialize session for subsequent operations
public bool Setup(string connection, WSManAuthenticationFlags authenticationMechanism, string username, string password)
{
if (m_bSetup) return true;
// initialize the WinRM client
m_errorCode = WSManInitialize(0, out m_apiHandle);
if (Win32Error.NO_ERROR != m_errorCode)
{
wprintf("WSManInitialize failed: {0}\n", m_errorCode);
return false;
}
// Create a session which can be used to perform subsequent operations
var serverAuthenticationCredentials = new SafeCoTaskMemStruct<WSMAN_AUTHENTICATION_CREDENTIALS>(new WSMAN_AUTHENTICATION_CREDENTIALS
{
authenticationMechanism = authenticationMechanism,
userAccount = new WSMAN_USERNAME_PASSWORD_CREDS
{
username = username,
password = password
}
});
m_errorCode = WSManCreateSession(m_apiHandle, connection, 0, serverAuthenticationCredentials, default, out m_session);
if (Win32Error.NO_ERROR != m_errorCode)
{
wprintf("WSManCreateSession failed: {0}\n", m_errorCode);
return false;
}
// Repeat the call to set any desired session options
WSManSessionOption option = WSManSessionOption.WSMAN_OPTION_DEFAULT_OPERATION_TIMEOUTMS;
var data = new WSMAN_DATA
{
type = WSManDataType.WSMAN_DATA_TYPE_DWORD,
union = new WSMAN_DATA.WSMAN_DATA_UNION { number = 60000 }
};
m_errorCode = WSManSetSessionOption(m_session, option, data);
if (Win32Error.NO_ERROR != m_errorCode)
{
wprintf("WSManSetSessionOption failed: {0}\n", m_errorCode);
return false;
}
// Prepare async call
m_event = CreateEvent(default, false, false, default);
if (m_event.IsInvalid)
{
m_errorCode = Win32Error.GetLastError();
wprintf("CreateEvent failed: {0}\n", m_errorCode);
return false;
}
m_async = new WSMAN_SHELL_ASYNC
{
operationContext = default,
completionFunction = m_WSManShellCompletionFunction
};
m_ReceiveEvent = CreateEvent(default, false, false, default);
if (m_ReceiveEvent.IsInvalid)
{
m_errorCode = Win32Error.GetLastError();
wprintf("CreateEvent failed: {0}\n", m_errorCode);
return false;
}
m_ReceiveAsync = new WSMAN_SHELL_ASYNC
{
operationContext = default,
completionFunction = m_ReceiveCallback
};
m_bSetup = true;
return true;
}
// Receive async callback
private void m_ReceiveCallback(IntPtr operationContext, WSManCallbackFlags flags, in WSMAN_ERROR error,
WSMAN_SHELL_HANDLE shell, WSMAN_COMMAND_HANDLE command, WSMAN_OPERATION_HANDLE operationHandle, IntPtr pdata)
{
if (0 != error.code)
{
m_ReceiveErrorCode = error.code;
// NOTE: if the errorDetail needs to be used outside of the callback, then need to allocate memory, copy the content to that
// memory as error.errorDetail itself is owned by WSMan client stack and will be deallocated and invalid when the callback exits
wprintf(error.errorDetail);
}
// Output the received data to the console
WSMAN_RECEIVE_DATA_RESULT data = pdata.ToStructure<WSMAN_RECEIVE_DATA_RESULT>();
if (pdata != default)
{
if (data.streamData.type == WSManDataType.WSMAN_DATA_TYPE_BINARY && data.streamData.union.binaryData.dataLength != 0)
{
HFILE hFile = ((0 == string.Compare(data.streamId, WSMAN_STREAM_ID_STDERR)) ? GetStdHandle(StdHandleType.STD_ERROR_HANDLE) : GetStdHandle(StdHandleType.STD_OUTPUT_HANDLE));
WriteFile(hFile, data.streamData.union.binaryData.data, data.streamData.union.binaryData.dataLength, out _);
}
}
// for WSManReceiveShellOutput, needs to wait for state to be done before signalliing the end of the operation
if ((0 != error.code) || (pdata != default && !data.commandState.IsNull && string.Compare(data.commandState, WSMAN_COMMAND_STATE_DONE) == 0))
{
SetEvent(m_ReceiveEvent);
}
}
// async callback
private void m_WSManShellCompletionFunction(IntPtr operationContext, WSManCallbackFlags flags, in WSMAN_ERROR error,
WSMAN_SHELL_HANDLE shell, WSMAN_COMMAND_HANDLE command, WSMAN_OPERATION_HANDLE operationHandle, IntPtr data)
{
if (0 != error.code)
{
m_errorCode = error.code;
// NOTE: if the errorDetail needs to be used outside of the callback, then need to allocate memory, copy the content to that
// memory as error->errorDetail itself is owned by WSMan client stack and will be deallocated and invalid when the callback exits
wprintf(error.errorDetail);
}
// for non-receieve operation, the callback simply signals the async operation is finished
SetEvent(m_event);
}
private bool Send(byte[] sendData, bool endOfStream)
{
// WSManSendShellInput
var streamData = new WSMAN_DATA
{
type = WSManDataType.WSMAN_DATA_TYPE_BINARY
};
using var pSendData = new PinnedObject(sendData);
if (sendData != null && sendData.Length > 0)
{
streamData.union.binaryData.dataLength = (uint)sendData.Length;
streamData.union.binaryData.data = pSendData;
}
WSManSendShellInput(m_shell, m_command, 0, WSMAN_STREAM_ID_STDIN, streamData, endOfStream, m_async, out SafeWSMAN_OPERATION_HANDLE sendOperation);
WaitForSingleObject(m_event, INFINITE);
if (Win32Error.NO_ERROR != m_errorCode)
{
wprintf("WSManSendShellInput failed: {0}\n", m_errorCode);
return false;
}
sendOperation.Dispose();
return true;
}
private void wprintf(string fmt, params object[] p) => TestContext.Write(fmt, p);
}
[TestFixture]
public class WsmSvcTests
{
[OneTimeSetUp]
public void _Setup()
{
}
[OneTimeTearDown]
public void _TearDown()
{
}
[Test]
public void Test()
{
using var cli = new ShellClient();
cli.Setup("http://fqdn.domain.com", WSManAuthenticationFlags.WSMAN_FLAG_NO_AUTHENTICATION, null, null);
cli.Execute("http://schemas.microsoft.com/wbem/wsman/1/windows/shell/cmd", "dir", null, 1);
}
}
}

View File

@ -289,6 +289,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vanara.PInvoke.SHCore", "PI
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Vanara.PInvoke.WsmSvc", "PInvoke\WsmSvc\Vanara.PInvoke.WsmSvc.csproj", "{1D1A06C8-57E8-4754-8383-BBFB3C98F49F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WsmSvc", "UnitTests\PInvoke\WsmSvc\WsmSvc.csproj", "{28175EF4-C5CA-48EB-AD8D-1B0608AD4389}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -931,6 +933,10 @@ Global
{1D1A06C8-57E8-4754-8383-BBFB3C98F49F}.DebugNoTests|Any CPU.Build.0 = Debug|Any CPU
{1D1A06C8-57E8-4754-8383-BBFB3C98F49F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1D1A06C8-57E8-4754-8383-BBFB3C98F49F}.Release|Any CPU.Build.0 = Release|Any CPU
{28175EF4-C5CA-48EB-AD8D-1B0608AD4389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{28175EF4-C5CA-48EB-AD8D-1B0608AD4389}.Debug|Any CPU.Build.0 = Debug|Any CPU
{28175EF4-C5CA-48EB-AD8D-1B0608AD4389}.DebugNoTests|Any CPU.ActiveCfg = Debug|Any CPU
{28175EF4-C5CA-48EB-AD8D-1B0608AD4389}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1056,6 +1062,7 @@ Global
{E7242839-78DE-4AFE-8AF7-E9DDCE06E18C} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
{F1417ADE-A8D3-432F-8F43-EF626DF4A3C9} = {212ABBD0-B724-4CFA-9D6D-E3891547FA90}
{1D1A06C8-57E8-4754-8383-BBFB3C98F49F} = {212ABBD0-B724-4CFA-9D6D-E3891547FA90}
{28175EF4-C5CA-48EB-AD8D-1B0608AD4389} = {385CAD2D-0A5E-4F80-927B-D5499D126B90}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {543FAC75-2AF1-4EF1-9609-B242B63FEED4}