Index: /trunk/src/VBox/Main/include/GuestCtrlImplPrivate.h
===================================================================
--- /trunk/src/VBox/Main/include/GuestCtrlImplPrivate.h	(revision 42213)
+++ /trunk/src/VBox/Main/include/GuestCtrlImplPrivate.h	(revision 42214)
@@ -37,6 +37,13 @@
 
 
-/* Builds a context ID out of the session ID, process ID and an
- * increasing count. */
+/** Maximum number of guest sessions a VM can have. */
+#define VBOX_GUESTCTRL_MAX_SESSIONS     255
+/** Maximum of guest processes a guest session can have. */
+#define VBOX_GUESTCTRL_MAX_PROCESSES    255
+/** Maximum of callback contexts a guest process can have. */
+#define VBOX_GUESTCTRL_MAX_CONTEXTS     _64K - 1
+
+/** Builds a context ID out of the session ID, process ID and an
+ *  increasing count. */
 #define VBOX_GUESTCTRL_CONTEXTID_MAKE(uSession, uProcess, uCount) \
     (  (uint32_t)((uSession) &   0xff) << 24 \
@@ -44,4 +51,13 @@
      | (uint32_t)((uCount)   & 0xffff)       \
     )
+/** Gets the session ID out of a context ID. */
+#define VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uContextID) \
+    ((uContextID) >> 24)
+/** Gets the process ID out of a context ID. */
+#define VBOX_GUESTCTRL_CONTEXTID_GET_PROCESS(uContextID) \
+    (((uContextID) >> 16) & 0xff)
+/** Gets the conext count of a process out of a context ID. */
+#define VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID) \
+    ((uContextID) & 0xffff)
 
 
@@ -67,4 +83,8 @@
 public:
 
+    int Cancel(void);
+
+    bool Canceled(void);
+
     int Init(eVBoxGuestCtrlCallbackType enmType);
 
@@ -73,5 +93,5 @@
     eVBoxGuestCtrlCallbackType Type(void);
 
-    int Wait(RTMSINTERVAL timeoutMS);
+    int Wait(ULONG uTimeoutMS);
 
 protected:
@@ -80,5 +100,7 @@
     eVBoxGuestCtrlCallbackType  mType;
     /** Callback flags. */
-    uint32_t                    mFlags;
+    uint32_t                    uFlags;
+    /** Was the callback canceled? */
+    bool                        fCanceled;
     /** Pointer to user-supplied data. */
     void                       *pvData;
@@ -86,9 +108,9 @@
     size_t                      cbData;
     /** The event semaphore triggering the*/
-    RTSEMEVENT                  mEventSem;
+    RTSEMEVENT                  hEventSem;
     /** Extended error information, if any. */
     ErrorInfo                   mErrorInfo;
 };
-typedef std::map <uint32_t, GuestCtrlCallback> GuestCtrlCallbacks;
+typedef std::map < uint32_t, GuestCtrlCallback* > GuestCtrlCallbacks;
 
 /**
@@ -112,5 +134,5 @@
 public:
 
-    int BuildEnvironmentBlock(void **ppvEnv, uint32_t *pcbEnv, uint32_t *pcEnvVars);
+    int BuildEnvironmentBlock(void **ppvEnv, size_t *pcbEnv, uint32_t *pcEnvVars);
 
     void Clear(void);
@@ -144,5 +166,5 @@
 protected:
 
-    int appendToEnvBlock(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnvVars);
+    int appendToEnvBlock(const char *pszEnv, void **ppvList, size_t *pcbList, uint32_t *pcEnvVars);
 
 protected:
Index: /trunk/src/VBox/Main/include/GuestImpl.h
===================================================================
--- /trunk/src/VBox/Main/include/GuestImpl.h	(revision 42213)
+++ /trunk/src/VBox/Main/include/GuestImpl.h	(revision 42214)
@@ -205,4 +205,5 @@
     /** @name Public internal methods.
      * @{ */
+    int         dispatchToSession(uint32_t uContextID, uint32_t uFunction, void *pvData, size_t cbData);
     Console    *getConsole(void) { return mParent; }
     int         sessionClose(ComObjPtr<GuestSession> pSession);
Index: /trunk/src/VBox/Main/include/GuestProcessImpl.h
===================================================================
--- /trunk/src/VBox/Main/include/GuestProcessImpl.h	(revision 42213)
+++ /trunk/src/VBox/Main/include/GuestProcessImpl.h	(revision 42214)
@@ -45,5 +45,5 @@
     DECLARE_EMPTY_CTOR_DTOR(GuestProcess)
 
-    int     init(Console *aConsole, GuestSession *aSession, uint32_t aProcessID, const GuestProcessInfo &aProcInfo);
+    int     init(Console *aConsole, GuestSession *aSession, ULONG aProcessID, const GuestProcessInfo &aProcInfo);
     void    uninit(void);
     HRESULT FinalConstruct(void);
@@ -69,14 +69,20 @@
     /** @name Public internal methods.
      * @{ */
-    int callbackAdd(const GuestCtrlCallback& theCallback, uint32_t *puContextID);
-    bool callbackExists(uint32_t uContextID);
+    int callbackAdd(GuestCtrlCallback *pCallback, ULONG *puContextID);
+    int callbackDispatcher(uint32_t uContextID, uint32_t uFunction, void *pvData, size_t cbData);
+    bool callbackExists(ULONG uContextID);
     bool isReady(void);
-    int prepareExecuteEnv(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnvVars);
-    int readData(ULONG aHandle, ULONG aSize, ULONG aTimeoutMS, ComSafeArrayOut(BYTE, aData));
+    ULONG getPID(void) { return mData.mPID; }
+    int onGuestDisconnected(void);
+    int onProcessInputStatus(uint32_t uStatus, uint32_t uFlags, uint32_t cbDataProcessed);
+    int onProcessStatusChange(uint32_t uStatus, uint32_t uFlags, uint32_t uPID);
+    int onProcessOutput(uint32_t uHandle, uint32_t uFlags, void *pvData, uint32_t cbData);
+    int prepareExecuteEnv(const char *pszEnv, void **ppvList, ULONG *pcbList, ULONG *pcEnvVars);
+    int readData(ULONG uHandle, ULONG uSize, ULONG uTimeoutMS, BYTE *pbData, size_t cbData);
     int startProcess(void);
     static DECLCALLBACK(int) startProcessThread(RTTHREAD Thread, void *pvUser);
     int terminateProcess(void);
-    int waitFor(ComSafeArrayIn(ProcessWaitForFlag_T, aFlags), ULONG aTimeoutMS, ProcessWaitReason_T *aReason);
-    int writeData(ULONG aHandle, ComSafeArrayIn(BYTE, aData), ULONG aTimeoutMS, ULONG *aWritten);
+    int waitFor(uint32_t fFlags, ULONG uTimeoutMS, ProcessWaitReason_T *penmReason);
+    int writeData(ULONG uHandle, BYTE const *pbData, size_t cbData, ULONG uTimeoutMS, ULONG *puWritten);
     /** @}  */
 
@@ -99,5 +105,5 @@
         ULONG                    mPID;
         /** Internal, host-side process ID. */
-        uint32_t                 mProcessID;
+        ULONG                 mProcessID;
         /** The current process status. */
         ProcessStatus_T          mStatus;
@@ -105,6 +111,10 @@
         bool                     mStarted;
         /** The next upcoming context ID. */
-        uint32_t                 mNextContextID;
+        ULONG                    mNextContextID;
+        /** The waiting event. */
+        RTSEMEVENT               mEvent;
     } mData;
+
+    friend GuestSession; /* Let's be friends! */
 };
 
Index: /trunk/src/VBox/Main/include/GuestSessionImpl.h
===================================================================
--- /trunk/src/VBox/Main/include/GuestSessionImpl.h	(revision 42213)
+++ /trunk/src/VBox/Main/include/GuestSessionImpl.h	(revision 42214)
@@ -48,5 +48,5 @@
     DECLARE_EMPTY_CTOR_DTOR(GuestSession)
 
-    int     init(Guest *aGuest, uint32_t aSessionID, Utf8Str aUser, Utf8Str aPassword, Utf8Str aDomain, Utf8Str aName);
+    int     init(Guest *aGuest, ULONG aSessionID, Utf8Str aUser, Utf8Str aPassword, Utf8Str aDomain, Utf8Str aName);
     void    uninit(void);
     HRESULT FinalConstruct(void);
@@ -113,5 +113,7 @@
     typedef std::vector <ComObjPtr<GuestDirectory> > SessionDirectories;
     typedef std::vector <ComObjPtr<GuestFile> > SessionFiles;
-    typedef std::map <uint32_t, ComObjPtr<GuestProcess> > SessionProcesses;
+    /** Map of guest processes. The key specifies the internal process number.
+     *  To retrieve the process' guest PID use the Id() method of the IProgress interface. */
+    typedef std::map <ULONG, ComObjPtr<GuestProcess> > SessionProcesses;
 
 public:
@@ -119,4 +121,5 @@
      * @{ */
     int                     directoryClose(ComObjPtr<GuestDirectory> pDirectory);
+    int                     dispatchToProcess(uint32_t uContextID, uint32_t uFunction, void *pvData, size_t cbData);
     int                     fileClose(ComObjPtr<GuestFile> pFile);
     const GuestCredentials &getCredentials(void);
@@ -124,5 +127,6 @@
     int                     processClose(ComObjPtr<GuestProcess> pProcess);
     int                     processCreateExInteral(GuestProcessInfo &aProcInfo, IGuestProcess **aProcess);
-    inline bool             processExists(uint32_t uProcessID);
+    inline bool             processExists(ULONG uProcessID, ComObjPtr<GuestProcess> *pProcess);
+    inline int              processGetByPID(ULONG uPID, ComObjPtr<GuestProcess> *pProcess);
     /** @}  */
 
Index: /trunk/src/VBox/Main/src-client/GuestCtrlImpl.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/GuestCtrlImpl.cpp	(revision 42213)
+++ /trunk/src/VBox/Main/src-client/GuestCtrlImpl.cpp	(revision 42214)
@@ -684,17 +684,31 @@
      * changes to the object state.
      */
-#ifdef DEBUG_andy
-    LogFlowFunc(("pvExtension=%p, u32Function=%d, pvParms=%p, cbParms=%d\n",
+    LogFlowFunc(("pvExtension=%p, u32Function=%RU32, pvParms=%p, cbParms=%RU32\n",
                  pvExtension, u32Function, pvParms, cbParms));
-#endif
     ComObjPtr<Guest> pGuest = reinterpret_cast<Guest *>(pvExtension);
-
-    int rc = VINF_SUCCESS;
+    Assert(!pGuest.isNull());
+
+    /*
+     * For guest control 2.0 using the legacy commands we need to do the following here:
+     * - Get the callback header to access the context ID
+     * - Get the context ID of the callback
+     * - Extract the session ID out of the context ID
+     * - Dispatch the whole stuff to the appropriate session (if still exists)
+     */
+
+    PCALLBACKHEADER pHeader = (PCALLBACKHEADER)pvParms;
+    AssertPtr(pHeader);
+
+    int rc = pGuest->dispatchToSession(pHeader->u32ContextID, u32Function, pvParms, cbParms);
+
+#ifdef VBOX_WITH_GUEST_CONTROL_LEGACY
+    if (RT_SUCCESS(rc))
+        return rc;
+
+    /* Legacy handling. */
     switch (u32Function)
     {
         case GUEST_DISCONNECTED:
         {
-            //LogFlowFunc(("GUEST_DISCONNECTED\n"));
-
             PCALLBACKDATACLIENTDISCONNECTED pCBData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvParms);
             AssertPtr(pCBData);
@@ -708,6 +722,4 @@
         case GUEST_EXEC_SEND_STATUS:
         {
-            //LogFlowFunc(("GUEST_EXEC_SEND_STATUS\n"));
-
             PCALLBACKDATAEXECSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECSTATUS>(pvParms);
             AssertPtr(pCBData);
@@ -721,6 +733,4 @@
         case GUEST_EXEC_SEND_OUTPUT:
         {
-            //LogFlowFunc(("GUEST_EXEC_SEND_OUTPUT\n"));
-
             PCALLBACKDATAEXECOUT pCBData = reinterpret_cast<PCALLBACKDATAEXECOUT>(pvParms);
             AssertPtr(pCBData);
@@ -734,6 +744,4 @@
         case GUEST_EXEC_SEND_INPUT_STATUS:
         {
-            //LogFlowFunc(("GUEST_EXEC_SEND_INPUT_STATUS\n"));
-
             PCALLBACKDATAEXECINSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECINSTATUS>(pvParms);
             AssertPtr(pCBData);
@@ -746,8 +754,9 @@
 
         default:
-            AssertMsgFailed(("Unknown guest control notification received, u32Function=%u\n", u32Function));
-            rc = VERR_INVALID_PARAMETER;
+            /* Silently ignore not implemented functions. */
+            rc = VERR_NOT_IMPLEMENTED;
             break;
     }
+#endif /* VBOX_WITH_GUEST_CONTROL_LEGACY */
     return rc;
 }
@@ -2664,4 +2673,24 @@
 /////////////////////////////////////////////////////////////////////////////
 
+int Guest::dispatchToSession(uint32_t uContextID, uint32_t uFunction, void *pvData, size_t cbData)
+{
+    AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+    AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+    GuestSessions::const_iterator itSession
+        = mData.mGuestSessions.find(VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uContextID));
+    if (itSession != mData.mGuestSessions.end())
+    {
+        ComObjPtr<GuestSession> pSession(itSession->second);
+        Assert(!pSession.isNull());
+
+        alock.release();
+        return pSession->dispatchToProcess(uContextID, uFunction, pvData, cbData);
+    }
+    return VERR_NOT_FOUND;
+}
+
 int Guest::sessionClose(ComObjPtr<GuestSession> pSession)
 {
@@ -2749,8 +2778,11 @@
 #else /* VBOX_WITH_GUEST_CONTROL */
 
-    /* Do not allow anonymous sessions (with system rights). */
+    LogFlowFuncEnter();
+
+    /* Do not allow anonymous sessions (with system rights) with official API. */
     if (RT_UNLIKELY((aUser) == NULL || *(aUser) == '\0'))
         return setError(E_INVALIDARG, tr("No user name specified"));
     CheckComArgOutPointerValid(aGuestSession);
+    /* Rest is optional. */
 
     AutoCaller autoCaller(this);
@@ -2758,5 +2790,10 @@
 
     int rc = sessionCreate(aUser, aPassword, aDomain, aSessionName, aGuestSession);
-    return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+
+    /** @todo Do setError() here. */
+    HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+    LogFlowFuncLeaveRC(hr);
+
+    return hr;
 #endif /* VBOX_WITH_GUEST_CONTROL */
 }
Index: /trunk/src/VBox/Main/src-client/GuestCtrlPrivate.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/GuestCtrlPrivate.cpp	(revision 42213)
+++ /trunk/src/VBox/Main/src-client/GuestCtrlPrivate.cpp	(revision 42214)
@@ -22,4 +22,5 @@
 #include "GuestCtrlImplPrivate.h"
 
+#include <iprt/asm.h>
 #include <iprt/ctype.h>
 #ifdef DEBUG
@@ -34,7 +35,9 @@
 GuestCtrlCallback::GuestCtrlCallback(void)
     : mType(VBOXGUESTCTRLCALLBACKTYPE_UNKNOWN),
+      uFlags(0),
+      fCanceled(false),
       pvData(NULL),
       cbData(0),
-      mEventSem(NIL_RTSEMEVENT)
+      hEventSem(NIL_RTSEMEVENT)
 {
 }
@@ -42,7 +45,9 @@
 GuestCtrlCallback::GuestCtrlCallback(eVBoxGuestCtrlCallbackType enmType)
     : mType(VBOXGUESTCTRLCALLBACKTYPE_UNKNOWN),
+      uFlags(0),
+      fCanceled(false),
       pvData(NULL),
       cbData(0),
-      mEventSem(NIL_RTSEMEVENT)
+      hEventSem(NIL_RTSEMEVENT)
 {
     int rc = Init(enmType);
@@ -53,4 +58,20 @@
 {
     Destroy();
+}
+
+int GuestCtrlCallback::Cancel(void)
+{
+    if (!ASMAtomicReadBool(&fCanceled))
+    {
+        int rc = RTSemEventSignal(hEventSem);
+        if (RT_SUCCESS(rc))
+            ASMAtomicXchgBool(&fCanceled, true);
+    }
+    return VINF_SUCCESS;
+}
+
+bool GuestCtrlCallback::Canceled(void)
+{
+    return ASMAtomicReadBool(&fCanceled);
 }
 
@@ -97,5 +118,5 @@
     if (RT_SUCCESS(rc))
     {
-        rc = RTSemEventCreate(&mEventSem);
+        rc = RTSemEventCreate(&hEventSem);
         if (RT_SUCCESS(rc))
             mType  = enmType;
@@ -114,6 +135,6 @@
     }
     cbData = 0;
-    if (mEventSem != NIL_RTSEMEVENT)
-        RTSemEventDestroy(mEventSem);
+    if (hEventSem != NIL_RTSEMEVENT)
+        RTSemEventDestroy(hEventSem);
 }
 
@@ -123,18 +144,22 @@
 }
 
-int GuestCtrlCallback::Wait(RTMSINTERVAL timeoutMS)
-{
-    Assert(mEventSem != NIL_RTSEMEVENT);
-    return RTSemEventWait(mEventSem, timeoutMS);
+int GuestCtrlCallback::Wait(ULONG uTimeoutMS)
+{
+    Assert(hEventSem != NIL_RTSEMEVENT);
+
+    RTMSINTERVAL msInterval = uTimeoutMS;
+    if (!uTimeoutMS)
+        msInterval = RT_INDEFINITE_WAIT;
+    return RTSemEventWait(hEventSem, msInterval);
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
-int GuestEnvironment::BuildEnvironmentBlock(void **ppvEnv, uint32_t *pcbEnv, uint32_t *pcEnvVars)
+int GuestEnvironment::BuildEnvironmentBlock(void **ppvEnv, size_t *pcbEnv, uint32_t *pcEnvVars)
 {
     AssertPtrReturn(ppvEnv, VERR_INVALID_POINTER);
     /* Rest is optional. */
 
-    uint32_t cbEnv = 0;
+    size_t cbEnv = 0;
     uint32_t cEnvVars = 0;
 
@@ -338,11 +363,11 @@
  *                          stored in the environment block.
  */
-int GuestEnvironment::appendToEnvBlock(const char *pszEnv, void **ppvList, uint32_t *pcbList, uint32_t *pcEnvVars)
+int GuestEnvironment::appendToEnvBlock(const char *pszEnv, void **ppvList, size_t *pcbList, uint32_t *pcEnvVars)
 {
     int rc = VINF_SUCCESS;
-    uint32_t cchEnv = strlen(pszEnv); Assert(cchEnv >= 2);
+    size_t cchEnv = strlen(pszEnv); Assert(cchEnv >= 2);
     if (*ppvList)
     {
-        uint32_t cbNewLen = *pcbList + cchEnv + 1; /* Include zero termination. */
+        size_t cbNewLen = *pcbList + cchEnv + 1; /* Include zero termination. */
         char *pvTmp = (char *)RTMemRealloc(*ppvList, cbNewLen);
         if (pvTmp == NULL)
Index: /trunk/src/VBox/Main/src-client/GuestProcessImpl.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/GuestProcessImpl.cpp	(revision 42213)
+++ /trunk/src/VBox/Main/src-client/GuestProcessImpl.cpp	(revision 42214)
@@ -91,5 +91,5 @@
 /////////////////////////////////////////////////////////////////////////////
 
-int GuestProcess::init(Console *aConsole, GuestSession *aSession, uint32_t aProcessID, const GuestProcessInfo &aProcInfo)
+int GuestProcess::init(Console *aConsole, GuestSession *aSession, ULONG aProcessID, const GuestProcessInfo &aProcInfo)
 {
     AssertPtrReturn(aSession, VERR_INVALID_POINTER);
@@ -103,4 +103,5 @@
     mData.mConsole = aConsole;
     mData.mParent = aSession;
+    mData.mPID = 0; /* Will be assigned by the guest OS later. */
     mData.mProcessID = aProcessID;
     mData.mStatus = ProcessStatus_Starting;
@@ -150,4 +151,6 @@
     ReturnComNotImplemented();
 #else
+    LogFlowThisFuncEnter();
+
     AutoCaller autoCaller(this);
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
@@ -169,4 +172,5 @@
     collection.detachTo(ComSafeArrayOutArg(aArguments));
 
+    LogFlowFuncLeaveRC(S_OK);
     return S_OK;
 #endif /* VBOX_WITH_GUEST_CONTROL */
@@ -178,4 +182,6 @@
     ReturnComNotImplemented();
 #else
+    LogFlowThisFuncEnter();
+
     AutoCaller autoCaller(this);
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
@@ -193,4 +199,5 @@
     arguments.detachTo(ComSafeArrayOutArg(aEnvironment));
 
+    LogFlowFuncLeaveRC(S_OK);
     return S_OK;
 #endif /* VBOX_WITH_GUEST_CONTROL */
@@ -202,4 +209,6 @@
     ReturnComNotImplemented();
 #else
+    LogFlowThisFuncEnter();
+
     AutoCaller autoCaller(this);
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
@@ -209,4 +218,5 @@
     mData.mProcess.mCommand.cloneTo(aExecutablePath);
 
+    LogFlowFuncLeaveRC(S_OK);
     return S_OK;
 #endif /* VBOX_WITH_GUEST_CONTROL */
@@ -218,4 +228,6 @@
     ReturnComNotImplemented();
 #else
+    LogFlowThisFuncEnter();
+
     AutoCaller autoCaller(this);
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
@@ -225,4 +237,5 @@
     *aExitCode = mData.mExitCode;
 
+    LogFlowFuncLeaveRC(S_OK);
     return S_OK;
 #endif /* VBOX_WITH_GUEST_CONTROL */
@@ -234,4 +247,6 @@
     ReturnComNotImplemented();
 #else
+    LogFlowThisFuncEnter();
+
     AutoCaller autoCaller(this);
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
@@ -241,4 +256,5 @@
     *aPID = mData.mPID;
 
+    LogFlowFuncLeaveRC(S_OK);
     return S_OK;
 #endif /* VBOX_WITH_GUEST_CONTROL */
@@ -250,4 +266,6 @@
     ReturnComNotImplemented();
 #else
+    LogFlowThisFuncEnter();
+
     AutoCaller autoCaller(this);
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
@@ -257,4 +275,5 @@
     *aStatus = mData.mStatus;
 
+    LogFlowFuncLeaveRC(S_OK);
     return S_OK;
 #endif /* VBOX_WITH_GUEST_CONTROL */
@@ -276,5 +295,5 @@
 */
 
-int GuestProcess::callbackAdd(const GuestCtrlCallback& theCallback, uint32_t *puContextID)
+int GuestProcess::callbackAdd(GuestCtrlCallback *pCallback, ULONG *puContextID)
 {
     const ComObjPtr<GuestSession> pSession(mData.mParent);
@@ -286,13 +305,13 @@
     /* Create a new context ID and assign it. */
     int rc = VERR_NOT_FOUND;
-    uint32_t uNewContextID = 0;
-    uint32_t uTries = 0;
+    ULONG uNewContextID = 0;
+    ULONG uTries = 0;
     for (;;)
     {
         /* Create a new context ID ... */
         uNewContextID = VBOX_GUESTCTRL_CONTEXTID_MAKE(uSessionID,
-                                                      mData.mProcessID, ASMAtomicIncU32(&mData.mNextContextID));
+                                                      mData.mProcessID, mData.mNextContextID++);
         if (uNewContextID == UINT32_MAX)
-            ASMAtomicUoWriteU32(&mData.mNextContextID, 0);
+            mData.mNextContextID = 0;
         /* Is the context ID already used?  Try next ID ... */
         if (!callbackExists(uNewContextID))
@@ -312,5 +331,5 @@
     {
         /* Add callback with new context ID to our callback map. */
-        mData.mCallbacks[uNewContextID] = theCallback;
+        mData.mCallbacks[uNewContextID] = pCallback;
         Assert(mData.mCallbacks.size());
 
@@ -323,5 +342,81 @@
 }
 
-bool GuestProcess::callbackExists(uint32_t uContextID)
+int GuestProcess::callbackDispatcher(uint32_t uContextID, uint32_t uFunction, void *pvData, size_t cbData)
+{
+    AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+    AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+    GuestCtrlCallbacks::const_iterator it
+        = mData.mCallbacks.find(VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID));
+    if (it != mData.mCallbacks.end())
+    {
+        std::auto_ptr<GuestCtrlCallback> pCallback = it->second;
+        AssertPtr(pCallback.get());
+
+        int rc;
+
+        switch (uFunction)
+        {
+            case GUEST_DISCONNECTED:
+            {
+                PCALLBACKDATACLIENTDISCONNECTED pCBData = reinterpret_cast<PCALLBACKDATACLIENTDISCONNECTED>(pvData);
+                AssertPtr(pCBData);
+                AssertReturn(sizeof(CALLBACKDATACLIENTDISCONNECTED) == cbData, VERR_INVALID_PARAMETER);
+                AssertReturn(CALLBACKDATAMAGIC_CLIENT_DISCONNECTED == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
+
+                rc = onGuestDisconnected(); /* Affects all callbacks. */
+                break;
+            }
+
+            case GUEST_EXEC_SEND_STATUS:
+            {
+                PCALLBACKDATAEXECSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECSTATUS>(pvData);
+                AssertPtr(pCBData);
+                AssertReturn(sizeof(CALLBACKDATAEXECSTATUS) == cbData, VERR_INVALID_PARAMETER);
+                AssertReturn(CALLBACKDATAMAGIC_EXEC_STATUS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
+
+                rc = onProcessStatusChange(pCBData->u32Status, pCBData->u32Flags, pCBData->u32PID);
+                break;
+            }
+
+            case GUEST_EXEC_SEND_OUTPUT:
+            {
+                PCALLBACKDATAEXECOUT pCBData = reinterpret_cast<PCALLBACKDATAEXECOUT>(pvData);
+                AssertPtr(pCBData);
+                AssertReturn(sizeof(CALLBACKDATAEXECOUT) == cbData, VERR_INVALID_PARAMETER);
+                AssertReturn(CALLBACKDATAMAGIC_EXEC_OUT == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
+
+                Assert(mData.mPID == pCBData->u32PID);
+                rc = onProcessOutput(pCBData->u32HandleId, pCBData->u32Flags, pCBData->pvData, pCBData->cbData);
+                break;
+            }
+
+            case GUEST_EXEC_SEND_INPUT_STATUS:
+            {
+                PCALLBACKDATAEXECINSTATUS pCBData = reinterpret_cast<PCALLBACKDATAEXECINSTATUS>(pvData);
+                AssertPtr(pCBData);
+                AssertReturn(sizeof(CALLBACKDATAEXECINSTATUS) == cbData, VERR_INVALID_PARAMETER);
+                AssertReturn(CALLBACKDATAMAGIC_EXEC_IN_STATUS == pCBData->hdr.u32Magic, VERR_INVALID_PARAMETER);
+
+                Assert(mData.mPID == pCBData->u32PID);
+                rc = onProcessInputStatus(pCBData->u32Status, pCBData->u32Flags, pCBData->cbProcessed);
+                break;
+            }
+
+            default:
+                /* Silently ignore not implemented functions. */
+                rc = VERR_NOT_IMPLEMENTED;
+                break;
+        }
+
+        return rc;
+    }
+
+    return VERR_NOT_FOUND;
+}
+
+bool GuestProcess::callbackExists(ULONG uContextID)
 {
     AssertReturn(uContextID, false);
@@ -344,7 +439,7 @@
 }
 
-int GuestProcess::readData(ULONG aHandle, ULONG aSize, ULONG aTimeoutMS, ComSafeArrayOut(BYTE, aData))
-{
-    LogFlowFuncEnter();
+int GuestProcess::onGuestDisconnected(void)
+{
+    LogFlowFunc(("uPID=%RU32", mData.mPID));
 
     LogFlowFuncLeave();
@@ -352,12 +447,55 @@
 }
 
+int GuestProcess::onProcessInputStatus(uint32_t uStatus, uint32_t uFlags, uint32_t cbDataProcessed)
+{
+    LogFlowFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32, cbDataProcessed=%RU32\n",
+                 mData.mPID, uStatus, uFlags, cbDataProcessed));
+
+    LogFlowFuncLeave();
+    return 0;
+}
+
+int GuestProcess::onProcessStatusChange(uint32_t uStatus, uint32_t uFlags, uint32_t uPID)
+{
+    LogFlowFunc(("uPID=%RU32, uStatus=%RU32, uFlags=%RU32\n",
+                 uPID, uStatus, uFlags));
+
+    LogFlowFuncLeave();
+    return 0;
+}
+
+int GuestProcess::onProcessOutput(uint32_t uHandle, uint32_t uFlags, void *pvData, uint32_t cbData)
+{
+    LogFlowFunc(("uPID=%RU32, uHandle=%RU32, uFlags=%RU32, pvData=%p, cbData=%RU32\n",
+                 mData.mPID, uHandle, uFlags, pvData, cbData));
+
+    LogFlowFuncLeave();
+    return 0;
+}
+
+int GuestProcess::readData(ULONG uHandle, ULONG uSize, ULONG uTimeoutMS, BYTE *pbData, size_t cbData)
+{
+    LogFlowFunc(("uPID=%RU32, uHandle=%RU32, uSize=%RU32, uTimeoutMS=%RU32, pbData=%p, cbData=%z\n",
+                 mData.mPID, uHandle, uSize, uTimeoutMS, pbData, cbData));
+    AssertReturn(uSize, VERR_INVALID_PARAMETER);
+    AssertPtrReturn(pbData, VERR_INVALID_POINTER);
+    AssertReturn(cbData >= uSize, VERR_INVALID_PARAMETER);
+
+    LogFlowFuncLeave();
+    return 0;
+}
+
 int GuestProcess::startProcess(void)
 {
-    LogFlowFuncEnter();
+    LogFlowFunc(("aCmd=%s, aTimeoutMS=%RU32, fFlags=%x\n",
+                 mData.mProcess.mCommand.c_str(), mData.mProcess.mTimeoutMS, mData.mProcess.mFlags));
 
     AssertReturn(!mData.mStarted, VERR_ALREADY_EXISTS);
 
     int rc;
-    uint32_t uContextID;
+    ULONG uContextID;
+    GuestCtrlCallback *pCallbackStart = (GuestCtrlCallback *)RTMemAlloc(sizeof(GuestCtrlCallback));
+    if (!pCallbackStart)
+        return VERR_NO_MEMORY;
 
     {
@@ -367,15 +505,13 @@
 
         /* Create callback and add it to the map. */
-        GuestCtrlCallback callbackStart;
-        rc = callbackStart.Init(VBOXGUESTCTRLCALLBACKTYPE_EXEC_START);
-        if (RT_FAILURE(rc))
-            return rc;
-
-        rc = callbackAdd(callbackStart, &uContextID);
+        rc = pCallbackStart->Init(VBOXGUESTCTRLCALLBACKTYPE_EXEC_START);
+        if (RT_SUCCESS(rc))
+            rc = callbackAdd(pCallbackStart, &uContextID);
+    }
+
+    if (RT_SUCCESS(rc))
+    {
         Assert(uContextID);
-    }
-
-    if (RT_SUCCESS(rc))
-    {
+
         ComObjPtr<GuestSession> pSession(mData.mParent);
         Assert(!pSession.isNull());
@@ -397,9 +533,9 @@
                 rc = RTGetOptArgvToString(&pszArgs, papszArgv, RTGETOPTARGV_CNV_QUOTE_MS_CRT);
         }
-        uint32_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
+        size_t cbArgs = pszArgs ? strlen(pszArgs) + 1 : 0; /* Include terminating zero. */
 
         /* Prepare environment. */
         void *pvEnv = NULL;
-        uint32_t cbEnv = 0;
+        size_t cbEnv = 0;
         rc = mData.mProcess.mEnvironment.BuildEnvironmentBlock(&pvEnv, &cbEnv, NULL /* cEnv */);
 
@@ -411,5 +547,5 @@
             paParms[i++].setUInt32(uContextID);
             paParms[i++].setPointer((void*)mData.mProcess.mCommand.c_str(),
-                                    (uint32_t)mData.mProcess.mCommand.length() + 1);
+                                    (ULONG)mData.mProcess.mCommand.length() + 1);
             paParms[i++].setUInt32(mData.mProcess.mFlags);
             paParms[i++].setUInt32(mData.mProcess.mArguments.size());
@@ -418,6 +554,6 @@
             paParms[i++].setUInt32(cbEnv);
             paParms[i++].setPointer((void*)pvEnv, cbEnv);
-            paParms[i++].setPointer((void*)sessionCreds.mUser.c_str(), (uint32_t)sessionCreds.mUser.length() + 1);
-            paParms[i++].setPointer((void*)sessionCreds.mPassword.c_str(), (uint32_t)sessionCreds.mPassword.length() + 1);
+            paParms[i++].setPointer((void*)sessionCreds.mUser.c_str(), (ULONG)sessionCreds.mUser.length() + 1);
+            paParms[i++].setPointer((void*)sessionCreds.mPassword.c_str(), (ULONG)sessionCreds.mPassword.length() + 1);
 
             /*
@@ -454,4 +590,13 @@
         if (pszArgs)
             RTStrFree(pszArgs);
+
+        if (RT_SUCCESS(rc))
+        {
+            /*
+             * Let's wait for the process being started.
+             * Note: Be sure not keeping a AutoRead/WriteLock here.
+             */
+            rc = pCallbackStart->Wait(mData.mProcess.mTimeoutMS);
+        }
     }
 
@@ -464,5 +609,8 @@
     LogFlowFuncEnter();
 
-    const ComObjPtr<GuestProcess> pProcess = static_cast<GuestProcess*>(pvUser);
+    std::auto_ptr<GuestProcessStartTask> pTask(static_cast<GuestProcessStartTask*>(pvUser));
+    AssertPtr(pTask.get());
+
+    const ComObjPtr<GuestProcess> pProcess(pTask->mProcess);
     Assert(!pProcess.isNull());
 
@@ -483,7 +631,8 @@
 }
 
-int GuestProcess::waitFor(ComSafeArrayIn(ProcessWaitForFlag_T, aFlags), ULONG aTimeoutMS, ProcessWaitReason_T *aReason)
-{
-    LogFlowFuncEnter();
+int GuestProcess::waitFor(uint32_t fFlags, ULONG uTimeoutMS, ProcessWaitReason_T *penmReason)
+{
+    LogFlowFunc(("fFlags=%x, uTimeoutMS=%RU32, penmReason=%p\n",
+                 fFlags, uTimeoutMS, penmReason));
 
     LogFlowFuncLeave();
@@ -491,7 +640,11 @@
 }
 
-int GuestProcess::writeData(ULONG aHandle, ComSafeArrayIn(BYTE, aData), ULONG aTimeoutMS, ULONG *aWritten)
-{
-    LogFlowFuncEnter();
+int GuestProcess::writeData(ULONG uHandle, const BYTE *pbData, size_t cbData, ULONG uTimeoutMS, ULONG *puWritten)
+{
+    LogFlowFunc(("uPID=%RU32, uHandle=%RU32, pbData=%p, cbData=%z, uTimeoutMS=%RU32, puWritten=%p\n",
+                 mData.mPID, uHandle, pbData, cbData, uTimeoutMS, puWritten));
+    AssertPtrReturn(pbData, VERR_INVALID_POINTER);
+    AssertReturn(pbData, VERR_INVALID_PARAMETER);
+    /* Rest is optional. */
 
     LogFlowFuncLeave();
@@ -507,11 +660,25 @@
     ReturnComNotImplemented();
 #else
-
-    AutoCaller autoCaller(this);
-    if (FAILED(autoCaller.rc())) return autoCaller.rc();
-
-    int rc = readData(aHandle, aSize, aTimeoutMS, ComSafeArrayOutArg(aData));
+    LogFlowThisFuncEnter();
+
+    if (aSize == 0)
+        return setError(E_INVALIDARG, tr("Invalid size to read specified"));
+    CheckComArgOutSafeArrayPointerValid(aData);
+
+    AutoCaller autoCaller(this);
+    if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+    com::SafeArray<BYTE> data(aSize);
+    int rc = readData(aHandle, aSize, aTimeoutMS, data.raw(), aSize);
+    if (RT_SUCCESS(rc))
+        data.detachTo(ComSafeArrayOutArg(aData));
+
     /** @todo Do setError() here. */
-    return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+    HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+    LogFlowFuncLeaveRC(hr);
+
+    return hr;
 #endif /* VBOX_WITH_GUEST_CONTROL */
 }
@@ -522,4 +689,6 @@
     ReturnComNotImplemented();
 #else
+    LogFlowThisFuncEnter();
+
     AutoCaller autoCaller(this);
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
@@ -527,5 +696,8 @@
     int rc = terminateProcess();
     /** @todo Do setError() here. */
-    return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+    HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+    LogFlowFuncLeaveRC(hr);
+
+    return hr;
 #endif /* VBOX_WITH_GUEST_CONTROL */
 }
@@ -536,10 +708,24 @@
     ReturnComNotImplemented();
 #else
-    AutoCaller autoCaller(this);
-    if (FAILED(autoCaller.rc())) return autoCaller.rc();
-
-    int rc = waitFor(ComSafeArrayInArg(aFlags), aTimeoutMS, aReason);
+    LogFlowThisFuncEnter();
+
+    CheckComArgOutPointerValid(aReason);
+
+    AutoCaller autoCaller(this);
+    if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+    uint32_t fFlags = ProcessWaitForFlag_None;
+    com::SafeArray<ProcessWaitForFlag_T> flags(ComSafeArrayInArg(aFlags));
+    for (size_t i = 0; i < flags.size(); i++)
+        fFlags |= flags[i];
+
+    int rc = waitFor(fFlags, aTimeoutMS, aReason);
     /** @todo Do setError() here. */
-    return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+    HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+    LogFlowFuncLeaveRC(hr);
+
+    return hr;
 #endif /* VBOX_WITH_GUEST_CONTROL */
 }
@@ -550,11 +736,21 @@
     ReturnComNotImplemented();
 #else
-    AutoCaller autoCaller(this);
-    if (FAILED(autoCaller.rc())) return autoCaller.rc();
-
-    int rc = writeData(aHandle, ComSafeArrayInArg(aData), aTimeoutMS, aWritten);
+    LogFlowThisFuncEnter();
+
+    CheckComArgOutPointerValid(aWritten);
+
+    AutoCaller autoCaller(this);
+    if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+    AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+    com::SafeArray<BYTE> data(aData);
+    int rc = writeData(aHandle, data.raw(), data.size(), aTimeoutMS, aWritten);
     /** @todo Do setError() here. */
-    return RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
-#endif /* VBOX_WITH_GUEST_CONTROL */
-}
-
+    HRESULT hr = RT_SUCCESS(rc) ? S_OK : VBOX_E_IPRT_ERROR;
+    LogFlowFuncLeaveRC(hr);
+
+    return hr;
+#endif /* VBOX_WITH_GUEST_CONTROL */
+}
+
Index: /trunk/src/VBox/Main/src-client/GuestSessionImpl.cpp
===================================================================
--- /trunk/src/VBox/Main/src-client/GuestSessionImpl.cpp	(revision 42213)
+++ /trunk/src/VBox/Main/src-client/GuestSessionImpl.cpp	(revision 42214)
@@ -55,5 +55,5 @@
 /////////////////////////////////////////////////////////////////////////////
 
-int GuestSession::init(Guest *aGuest, uint32_t aSessionID,
+int GuestSession::init(Guest *aGuest, ULONG aSessionID,
                        Utf8Str aUser, Utf8Str aPassword, Utf8Str aDomain, Utf8Str aName)
 {
@@ -347,4 +347,24 @@
     }
 
+    return VERR_NOT_FOUND;
+}
+
+int GuestSession::dispatchToProcess(uint32_t uContextID, uint32_t uFunction, void *pvData, size_t cbData)
+{
+    AssertPtrReturn(pvData, VERR_INVALID_POINTER);
+    AssertReturn(cbData, VERR_INVALID_PARAMETER);
+
+    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+    SessionProcesses::const_iterator itProc
+        = mData.mProcesses.find(VBOX_GUESTCTRL_CONTEXTID_GET_PROCESS(uContextID));
+    if (itProc != mData.mProcesses.end())
+    {
+        ComObjPtr<GuestProcess> pProcess(itProc->second);
+        Assert(!pProcess.isNull());
+
+        alock.release();
+        return pProcess->callbackDispatcher(uContextID, uFunction, pvData, cbData);
+    }
     return VERR_NOT_FOUND;
 }
@@ -422,11 +442,11 @@
 
     /* Create a new (host-based) process ID and assign it. */
-    uint32_t uNewProcessID = 0;
-    uint32_t uTries = 0;
+    ULONG uNewProcessID = 0;
+    ULONG uTries = 0;
 
     for (;;)
     {
         /* Is the context ID already used?  Try next ID ... */
-        if (!processExists(uNewProcessID++))
+        if (!processExists(uNewProcessID++, NULL /* pProgress */))
         {
             /* Callback with context ID was not found. This means
@@ -467,10 +487,40 @@
 }
 
-inline bool GuestSession::processExists(uint32_t uProcessID)
+inline bool GuestSession::processExists(ULONG uProcessID, ComObjPtr<GuestProcess> *pProcess)
 {
     AssertReturn(uProcessID, false);
 
-    SessionProcesses::const_iterator itProcesses = mData.mProcesses.find(uProcessID);
-    return (itProcesses == mData.mProcesses.end()) ? false : true;
+    SessionProcesses::const_iterator it = mData.mProcesses.find(uProcessID);
+    if (it != mData.mProcesses.end())
+    {
+        if (pProcess)
+            *pProcess = it->second;
+        return true;
+    }
+    return false;
+}
+
+inline int GuestSession::processGetByPID(ULONG uPID, ComObjPtr<GuestProcess> *pProcess)
+{
+    AssertReturn(uPID, false);
+    /* pProcess is optional. */
+
+    SessionProcesses::iterator it = mData.mProcesses.begin();
+    for (; it != mData.mProcesses.end(); it++)
+    {
+        ComObjPtr<GuestProcess> pCurProc = it->second;
+        AutoCaller procCaller(pCurProc);
+        if (procCaller.rc())
+            return VERR_COM_INVALID_OBJECT_STATE;
+
+        if (it->second->getPID() == uPID)
+        {
+            if (pProcess)
+                *pProcess = pCurProc;
+            return VINF_SUCCESS;
+        }
+    }
+
+    return VERR_NOT_FOUND;
 }
 
@@ -897,8 +947,10 @@
     LogFlowThisFuncEnter();
 
-    AutoCaller autoCaller(this);
-    if (FAILED(autoCaller.rc())) return autoCaller.rc();
-
+    if (RT_UNLIKELY((aCommand) == NULL || *(aCommand) == '\0'))
+        return setError(E_INVALIDARG, tr("No command to execute specified"));
     CheckComArgOutPointerValid(aProcess);
+
+    AutoCaller autoCaller(this);
+    if (FAILED(autoCaller.rc())) return autoCaller.rc();
 
     GuestProcessInfo procInfo;
@@ -925,5 +977,4 @@
     if (RT_SUCCESS(rc))
     {
-
         com::SafeArray<ProcessCreateFlag_T> flags(ComSafeArrayInArg(aFlags));
         for (size_t i = 0; i < flags.size(); i++)
@@ -953,10 +1004,29 @@
     ReturnComNotImplemented();
 #else
-    LogFlowThisFuncEnter();
-
-    AutoCaller autoCaller(this);
-    if (FAILED(autoCaller.rc())) return autoCaller.rc();
-
-    ReturnComNotImplemented();
+    LogFlowThisFunc(("aPID=%RU32\n", aPID));
+
+    CheckComArgOutPointerValid(aProcess);
+    if (aPID == 0)
+        return setError(E_INVALIDARG, tr("No valid process ID (PID) specified"));
+
+    AutoCaller autoCaller(this);
+    if (FAILED(autoCaller.rc())) return autoCaller.rc();
+
+    AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS);
+
+    HRESULT hr = S_OK;
+
+    ComObjPtr<GuestProcess> pProcess;
+    int rc = processGetByPID(aPID, &pProcess);
+    if (RT_FAILURE(rc))
+        hr = setError(E_INVALIDARG, tr("No process with PID %RU32 found"), aPID);
+
+    /* This will set (*aProcess) to NULL if pProgress is NULL. */
+    HRESULT hr2 = pProcess.queryInterfaceTo(aProcess);
+    if (SUCCEEDED(hr))
+        hr = hr2;
+
+    LogFlowThisFunc(("aProcess=%p, hr=%Rrc\n", *aProcess, hr));
+    return hr;
 #endif /* VBOX_WITH_GUEST_CONTROL */
 }
@@ -972,5 +1042,11 @@
     if (FAILED(autoCaller.rc())) return autoCaller.rc();
 
-    ReturnComNotImplemented();
+    if (aTimeoutMS < 1000)
+        return setError(E_INVALIDARG, tr("Invalid timeout value specified"));
+
+    mData.mTimeout = aTimeoutMS;
+
+    LogFlowThisFunc(("aTimeoutMS=%RU32\n", mData.mTimeout));
+    return S_OK;
 #endif /* VBOX_WITH_GUEST_CONTROL */
 }
Index: /trunk/src/VBox/Main/testcase/Makefile.kmk
===================================================================
--- /trunk/src/VBox/Main/testcase/Makefile.kmk	(revision 42213)
+++ /trunk/src/VBox/Main/testcase/Makefile.kmk	(revision 42214)
@@ -29,5 +29,6 @@
 	$(if $(VBOX_WITH_XPCOM),tstVBoxAPILinux,tstVBoxAPIWin) \
 	$(if $(VBOX_WITH_RESOURCE_USAGE_API),tstCollector,) \
-	$(if $(VBOX_WITH_GUEST_CONTROL),tstGuestCtrlParseBuffer,)
+	$(if $(VBOX_WITH_GUEST_CONTROL),tstGuestCtrlParseBuffer,) \
+	$(if $(VBOX_WITH_GUEST_CONTROL),tstGuestCtrlContextID,)
   PROGRAMS.linux += \
 	$(if $(VBOX_WITH_USB),tstUSBProxyLinux,)
@@ -160,4 +161,20 @@
 else
  tstGuestCtrlParseBuffer_DEPS    = $(VBOX_PATH_SDK)/bindings/xpcom/include/VirtualBox_XPCOM.h
+endif
+
+
+#
+# tstGuestCtrlContextID
+#
+tstGuestCtrlContextID_TEMPLATE = VBOXMAINCLIENTEXE
+tstGuestCtrlContextID_DEFS    += VBOX_WITH_HGCM VBOX_WITH_GUEST_CONTROL
+tstGuestCtrlContextID_SOURCES  = \
+	tstGuestCtrlContextID.cpp \
+	../src-client/GuestCtrlPrivate.cpp
+tstGuestCtrlContextID_INCS     = ../include
+ifeq ($(KBUILD_TARGET),win) ## @todo just add this to the template.
+ tstGuestCtrlContextID_DEPS    = $(VBOX_PATH_SDK)/bindings/mscom/include/VirtualBox.h
+else
+ tstGuestCtrlContextID_DEPS    = $(VBOX_PATH_SDK)/bindings/xpcom/include/VirtualBox_XPCOM.h
 endif
 
Index: /trunk/src/VBox/Main/testcase/tstGuestCtrlContextID.cpp
===================================================================
--- /trunk/src/VBox/Main/testcase/tstGuestCtrlContextID.cpp	(revision 42214)
+++ /trunk/src/VBox/Main/testcase/tstGuestCtrlContextID.cpp	(revision 42214)
@@ -0,0 +1,113 @@
+/* $Id$ */
+
+/** @file
+ *
+ * Context ID makeup/extraction test cases.
+ */
+
+/*
+ * Copyright (C) 2012 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#define LOG_ENABLED
+#define LOG_GROUP LOG_GROUP_MAIN
+#define LOG_INSTANCE NULL
+#include <VBox/log.h>
+
+#include "../include/GuestCtrlImplPrivate.h"
+
+using namespace com;
+
+#include <iprt/env.h>
+#include <iprt/rand.h>
+#include <iprt/stream.h>
+#include <iprt/test.h>
+
+int main()
+{
+    RTTEST hTest;
+    int rc = RTTestInitAndCreate("tstMakeup", &hTest);
+    if (rc)
+        return rc;
+    RTTestBanner(hTest);
+
+    RTTestIPrintf(RTTESTLVL_DEBUG, "Initializing COM...\n");
+    HRESULT hrc = com::Initialize();
+    if (FAILED(hrc))
+    {
+        RTTestFailed(hTest, "Failed to initialize COM (%Rhrc)!\n", hrc);
+        return RTEXITCODE_FAILURE;
+    }
+
+    /* Don't let the assertions trigger here
+     * -- we rely on the return values in the test(s) below. */
+    RTAssertSetQuiet(true);
+
+    uint32_t uContextMax = UINT32_MAX;
+    RTTestIPrintf(RTTESTLVL_DEBUG, "Max context is: %RU32\n", uContextMax);
+
+    /* Do 64 tests total. */
+    for (int t = 0; t < 64 && !RTTestErrorCount(hTest); t++)
+    {
+        uint32_t s = RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_SESSIONS);
+        uint32_t p = RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_PROCESSES);
+        uint32_t c = RTRandU32Ex(0, VBOX_GUESTCTRL_MAX_CONTEXTS);
+
+        uint64_t uContextID = VBOX_GUESTCTRL_CONTEXTID_MAKE(s, p, c);
+        RTTestIPrintf(RTTESTLVL_DEBUG, "ContextID (%d,%d,%d) = %RU32\n", s, p, c, uContextID);
+        if (s != VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uContextID))
+        {
+            RTTestFailed(hTest, "%d,%d,%d: Session is %d, expected %d -> %RU64\n",
+                         s, p, c, VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uContextID), s, uContextID);
+        }
+        else if (p != VBOX_GUESTCTRL_CONTEXTID_GET_PROCESS(uContextID))
+        {
+            RTTestFailed(hTest, "%d,%d,%d: Process is %d, expected %d -> %RU64\n",
+                         s, p, c, VBOX_GUESTCTRL_CONTEXTID_GET_PROCESS(uContextID), p, uContextID);
+        }
+        if (c != VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID))
+        {
+            RTTestFailed(hTest, "%d,%d,%d: Count is %d, expected %d -> %RU64\n",
+                         s, p, c, VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID), c, uContextID);
+        }
+        if (uContextID > UINT32_MAX)
+        {
+            RTTestFailed(hTest, "%d,%d,%d: Value overflow; does not fit anymore: %RU64\n",
+                         s, p, c, uContextID);
+        }
+    }
+
+#if 0
+    #define VBOX_GUESTCTRL_CONTEXTID_MAKE(uSession, uProcess, uCount) \
+    (  (uint32_t)((uSession) &   0xff) << 24 \
+     | (uint32_t)((uProcess) &   0xff) << 16 \
+     | (uint32_t)((uCount)   & 0xffff)       \
+    )
+/** Gets the session ID out of a context ID. */
+#define VBOX_GUESTCTRL_CONTEXTID_GET_SESSION(uContextID) \
+    (uContextID) >> 24)
+/** Gets the process ID out of a context ID. */
+#define VBOX_GUESTCTRL_CONTEXTID_GET_PROCESS(uContextID) \
+    (uContextID) >> 16)
+/** Gets the conext count of a process out of a context ID. */
+#define VBOX_GUESTCTRL_CONTEXTID_GET_COUNT(uContextID) \
+    ((uContextID) && 0xffff)
+#endif
+
+    RTTestIPrintf(RTTESTLVL_DEBUG, "Shutting down COM...\n");
+    com::Shutdown();
+
+    /*
+     * Summary.
+     */
+    return RTTestSummaryAndDestroy(hTest);
+}
+
Index: /trunk/src/VBox/Main/testcase/tstGuestCtrlParseBuffer.cpp
===================================================================
--- /trunk/src/VBox/Main/testcase/tstGuestCtrlParseBuffer.cpp	(revision 42213)
+++ /trunk/src/VBox/Main/testcase/tstGuestCtrlParseBuffer.cpp	(revision 42214)
@@ -205,10 +205,10 @@
             if (iResult != aTestBlock[iTest].iResult)
             {
-                RTTestFailed(hTest, "\tReturned %Rrc, expected %Rrc",
+                RTTestFailed(hTest, "\tReturned %Rrc, expected %Rrc\n",
                              iResult, aTestBlock[iTest].iResult);
             }
             else if (stream.GetOffset() != aTestBlock[iTest].uOffsetAfter)
             {
-                RTTestFailed(hTest, "\tOffset %u wrong, expected %u",
+                RTTestFailed(hTest, "\tOffset %u wrong, expected %u\n",
                              stream.GetOffset(), aTestBlock[iTest].uOffsetAfter);
             }
@@ -223,5 +223,5 @@
                 if (curBlock.GetCount() != aTestBlock[iTest].uMapElements)
                 {
-                    RTTestFailed(hTest, "\tMap has %u elements, expected %u",
+                    RTTestFailed(hTest, "\tMap has %u elements, expected %u\n",
                                  curBlock.GetCount(), aTestBlock[iTest].uMapElements);
                 }
@@ -275,5 +275,5 @@
             if (iResult != aTestStream[iTest].iResult)
             {
-                RTTestFailed(hTest, "\tReturned %Rrc, expected %Rrc",
+                RTTestFailed(hTest, "\tReturned %Rrc, expected %Rrc\n",
                              iResult, aTestStream[iTest].iResult);
             }
