diff --git a/PInvoke/WsmSvc/WSMan.cs b/PInvoke/WsmSvc/WSMan.cs
index cd95e82c..8cf173b0 100644
--- a/PInvoke/WsmSvc/WSMan.cs
+++ b/PInvoke/WsmSvc/WSMan.cs
@@ -7,6 +7,37 @@ namespace Vanara.PInvoke
/// Items from the WsmSvc.dll
public static partial class WsmSvc
{
+ /// Code page option name to be used with WSManCreateShell API to remotely set the code page
+ public const string WSMAN_CMDSHELL_OPTION_CODEPAGE = "WINRS_CODEPAGE";
+
+ ///
+ /// Option name used with WSManRunShellCommand API to indicate that the client side mode of standard input is Console; default
+ /// implies Pipe.
+ ///
+ public const string WSMAN_CMDSHELL_OPTION_CONSOLEMODE_STDIN = "WINRS_CONSOLEMODE_STDIN";
+
+ /// To be used with WSManRunShellCommand API to not use cmd.exe /c prefix when launching the command
+ public const string WSMAN_CMDSHELL_OPTION_SKIP_CMD_SHELL = "WINRS_SKIP_CMD_SHELL";
+
+ /// pre-defined command states
+ public const string WSMAN_COMMAND_STATE_DONE = "/CommandState/Done";
+
+ /// pre-defined command states
+ public const string WSMAN_COMMAND_STATE_PENDING= "/CommandState/Pending";
+
+ /// pre-defined command states
+ public const string WSMAN_COMMAND_STATE_RUNNING= "/CommandState/Running";
+
+ /// Option name used with WSManCreateShell API to not load the user profile on the remote server
+ public const string WSMAN_SHELL_OPTION_NOPROFILE = "WINRS_NOPROFILE";
+
+ ///
+ public const string WSMAN_STREAM_ID_STDERR = "stderr";
+ ///
+ public const string WSMAN_STREAM_ID_STDIN = "stdin";
+ ///
+ public const string WSMAN_STREAM_ID_STDOUT = "stdout";
+
private const string Lib_WsmSvc = "WsmSvc.dll";
/// The callback function that is called for shell operations, which result in a remote request.
@@ -327,6 +358,36 @@ namespace Vanara.PInvoke
WSMAN_OPTION_USE_INTEARACTIVE_TOKEN,
}
+ /// Deletes a command and frees the resources that are associated with it.
+ /// Specifies the command handle to be closed. This handle is returned by a WSManRunShellCommand call.
+ /// Reserved for future use. Must be set to zero.
+ ///
+ /// 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 NULL.
+ ///
+ /// None
+ // 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);
+
+ /// Cancels or closes an asynchronous operation. All resources that are associated with the operation are freed.
+ /// Specifies the operation handle to be closed.
+ /// Reserved for future use. Set to zero.
+ /// This method returns zero on success. Otherwise, this method returns an error code.
+ ///
+ /// The method de-allocates local and remote resources associated with the operation. After the WSManCloseOperation 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 WSManCloseOperation is called, the operation is marked for deletion and the method
+ /// returns immediately.
+ ///
+ // 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);
+
/// Closes a session object.
///
/// 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);
///
/// 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);
+ /// Retrieves output from a running command or from the shell.
+ /// Specifies the shell handle returned by a WSManCreateShell call. This parameter cannot be NULL.
+ /// Specifies the command handle returned by a WSManRunShellCommand call.
+ /// Reserved for future use. Must be set to zero.
+ /// Specifies the requested output from a particular stream or a list of streams.
+ ///
+ /// 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 NULL and should be closed by
+ /// calling the WSManCloseOperation method.
+ ///
+ ///
+ /// 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 NULL.
+ ///
+ /// None
+ // 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);
+
+ /// Retrieves output from a running command or from the shell.
+ /// Specifies the shell handle returned by a WSManCreateShell call. This parameter cannot be NULL.
+ /// Specifies the command handle returned by a WSManRunShellCommand call.
+ /// Reserved for future use. Must be set to zero.
+ /// Specifies the requested output from a particular stream or a list of streams.
+ ///
+ /// 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 NULL and should be closed by
+ /// calling the WSManCloseOperation method.
+ ///
+ ///
+ /// 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 NULL.
+ ///
+ /// None
+ // 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);
+
+ /// Starts the execution of a command within an existing shell and does not wait for the completion of the command.
+ /// Specifies the shell handle returned by the WSManCreateShell call. This parameter cannot be NULL.
+ /// Reserved for future use. Must be zero.
+ ///
+ /// Defines a required null-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 NULL.
+ ///
+ ///
+ /// 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 NULL.
+ ///
+ ///
+ /// 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 NULL. For more information about the options, see WSMAN_OPTION_SET.
+ ///
+ ///
+ /// 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 NULL and should be closed by
+ /// calling the WSManCloseCommand method.
+ ///
+ ///
+ /// 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 NULL.
+ ///
+ /// None
+ // 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);
+
+ /// Starts the execution of a command within an existing shell and does not wait for the completion of the command.
+ /// Specifies the shell handle returned by the WSManCreateShell call. This parameter cannot be NULL.
+ /// Reserved for future use. Must be zero.
+ ///
+ /// Defines a required null-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 NULL.
+ ///
+ ///
+ /// 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 NULL.
+ ///
+ ///
+ /// 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 NULL. For more information about the options, see WSMAN_OPTION_SET.
+ ///
+ ///
+ /// 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 NULL and should be closed by
+ /// calling the WSManCloseCommand method.
+ ///
+ ///
+ /// 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 NULL.
+ ///
+ /// None
+ // 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);
+
+ /// Pipes the input stream to a running command or to the shell.
+ /// Specifies the shell handle returned by a WSManCreateShell call. This parameter cannot be NULL.
+ ///
+ /// Specifies the command handle returned by a WSManRunShellCommand call. This handle should be closed by calling the
+ /// WSManCloseCommand method.
+ ///
+ /// Reserved for future use. Must be set to zero.
+ /// Specifies the input stream ID. This parameter cannot be NULL.
+ ///
+ /// 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 WSManSendShellInput completes. If the end of the stream has been
+ /// reached, the endOfStream parameter should be set to TRUE.
+ ///
+ ///
+ /// Set to TRUE, if the end of the stream has been reached. Otherwise, this parameter is set to FALSE.
+ ///
+ ///
+ /// 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 NULL and should be closed by
+ /// calling the WSManCloseCommand method.
+ ///
+ ///
+ /// 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 NULL.
+ ///
+ /// None
+ // 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);
+
+ /// Sets an extended set of options for the session.
+ /// Specifies the session handle returned by a WSManCreateSession call. This parameter cannot be NULL.
+ ///
+ /// Specifies the option to be set. This parameter must be set to one of the values in the WSManSessionOption enumeration.
+ ///
+ /// A pointer to a WSMAN_DATA structure that defines the option value.
+ /// This method returns zero on success. Otherwise, this method returns an error code.
+ ///
+ ///
+ /// If the WSManSetSessionOption method is called with different values specified for the option parameter, the order of the
+ /// different options is important. The first time WSManSetSessionOption is called, the transport is set for the session. If
+ /// a second call requests a different type of transport, the call will fail.
+ ///
+ /// For example, the second method call will fail if the methods are called in the following order:
+ ///
+ /// -
+ ///
+ ///
WSManSetSessionOption(WSMAN_OPTION_UNENCRYPTED_MESSAGES)
+ ///
+ ///
+ /// -
+ ///
+ ///
WSManSetSessionOption(WSMAN_OPTION_ALLOW_NEGOTIATE_IMPLICIT_CREDENTIALS)
+ ///
+ ///
+ ///
+ ///
+ /// The first method call sets the transport to HTTP because the option parameter is set to
+ /// WSMAN_OPTION_UNENCRYPTED_MESSAGES. 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.
+ ///
+ ///
+ // 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);
+
/// Provides a handle to a Windows Remote Client unique identifier.
[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; }
}
+ /// Represents the set of arguments that are passed in to the command line.
+ // 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
+ {
+ /// Specifies the number of arguments in the array.
+ public uint argsCount;
+
+ /// Defines an array of strings that specify the arguments.
+ public IntPtr args;
+ }
+
/// Provides a handle to a remote management command.
[StructLayout(LayoutKind.Sequential)]
public struct WSMAN_COMMAND_HANDLE : IHandle
@@ -1257,6 +1524,28 @@ namespace Vanara.PInvoke
protected override bool InternalReleaseHandle() => WSManDeinitialize(handle).Succeeded;
}
+ /// Provides a for that is disposed using .
+ public class SafeWSMAN_OPERATION_HANDLE : SafeHANDLE
+ {
+ /// Initializes a new instance of the class and assigns an existing handle.
+ /// An object that represents the pre-existing handle to use.
+ ///
+ /// to reliably release the handle during the finalization phase; otherwise, (not recommended).
+ ///
+ public SafeWSMAN_OPERATION_HANDLE(IntPtr preexistingHandle, bool ownsHandle = true) : base(preexistingHandle, ownsHandle) { }
+
+ /// Initializes a new instance of the class.
+ private SafeWSMAN_OPERATION_HANDLE() : base() { }
+
+ /// Performs an implicit conversion from to .
+ /// The safe handle instance.
+ /// The result of the conversion.
+ public static implicit operator WSMAN_OPERATION_HANDLE(SafeWSMAN_OPERATION_HANDLE h) => h.handle;
+
+ ///
+ protected override bool InternalReleaseHandle() => WSManCloseOperation(handle).Succeeded;
+ }
+
///
/// Provides a for that is disposed using .
@@ -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
*/
diff --git a/UnitTests/PInvoke/WsmSvc/WsmSvc.csproj b/UnitTests/PInvoke/WsmSvc/WsmSvc.csproj
new file mode 100644
index 00000000..8291b623
--- /dev/null
+++ b/UnitTests/PInvoke/WsmSvc/WsmSvc.csproj
@@ -0,0 +1,8 @@
+
+
+ UnitTest.PInvoke.WsmSvc
+
+
+
+
+
\ No newline at end of file
diff --git a/UnitTests/PInvoke/WsmSvc/WsmSvcTests.cs b/UnitTests/PInvoke/WsmSvc/WsmSvcTests.cs
new file mode 100644
index 00000000..41dde032
--- /dev/null
+++ b/UnitTests/PInvoke/WsmSvc/WsmSvcTests.cs
@@ -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(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();
+ 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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Vanara.sln b/Vanara.sln
index c2fc460c..318724f2 100644
--- a/Vanara.sln
+++ b/Vanara.sln
@@ -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}