Vanara/UnitTests/PInvoke/WsmSvc/WsmSvcTests.cs

305 lines
8.8 KiB
C#

using NUnit.Framework;
using NUnit.Framework.Internal;
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);
}
}