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}