mirror of https://github.com/dahall/Vanara.git
309 lines
9.1 KiB
C#
309 lines
9.1 KiB
C#
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);
|
|
}
|
|
}
|
|
} |