Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1722 lines
56 KiB

  1. /*---------------------------------------------------------------------------
  2. File: DCTDispatcher.cpp
  3. Comments: Implementation of dispatcher COM object. Remotely installs and
  4. launches the DCT Agent on remote computers.
  5. The CDCTDispatcher class implements the COM interface for the dispatcher.
  6. It takes a varset, containing a list of machines, and dispatches agents to
  7. each specified machine.
  8. A job file (varset persisted to a file) is created for each agent, and
  9. necessary initialization configuration (such as building an account mapping file for
  10. security translation) is done. The DCDTDispatcher class instantiates a
  11. thread pool (CPooledDispatch), and uses the CDCTInstaller class to remotely
  12. install and start the agent service on each machine.
  13. (c) Copyright 1999, Mission Critical Software, Inc., All Rights Reserved
  14. Proprietary and confidential to Mission Critical Software, Inc.
  15. REVISION LOG ENTRY
  16. Revision By: Christy Boles
  17. Revised on 02/15/99 11:23:57
  18. ---------------------------------------------------------------------------
  19. */// DCTDispatcher.cpp : Implementation of CDCTDispatcher
  20. #include "stdafx.h"
  21. #include "resource.h"
  22. #include <locale.h>
  23. //#include "..\Common\Include\McsDispatcher.h"
  24. #include "Dispatch.h"
  25. #include "DDisp.h"
  26. #include "DInst.h"
  27. #include "Common.hpp"
  28. #include "ErrDct.hpp"
  29. #include "UString.hpp"
  30. #include "EaLen.hpp"
  31. #include "Cipher.hpp"
  32. #include "TNode.hpp"
  33. #include "TPool.h" // Thread pool for dispatching jobs
  34. #include "LSAUtils.h"
  35. #include "TxtSid.h"
  36. #include "sd.hpp"
  37. #include "SecObj.hpp"
  38. #include "BkupRstr.hpp"
  39. #include "TReg.hpp"
  40. #include "ResStr.h"
  41. #include "TaskChk.h"
  42. #include "CommaLog.hpp"
  43. #include "TInst.h"
  44. #include <lm.h>
  45. #include "AdmtAccount.h"
  46. /////////////////////////////////////////////////////////////////////////////
  47. // CDCTDispatcher
  48. //#import "\bin\McsEADCTAgent.tlb" named_guids
  49. //#include "..\AgtSvc\AgSvc.h"
  50. #import "Engine.tlb" named_guids
  51. #include "AgSvc.h"
  52. #include "AgSvc_c.c"
  53. #include "AgRpcUtl.h"
  54. //#import "\bin\McsDctWorkerObjects.tlb"
  55. //#include "..\Common\Include\McsPI.h"
  56. #import "WorkObj.tlb"
  57. #include "McsPI.h"
  58. #include "McsPI_i.c"
  59. #ifdef _DEBUG
  60. #define new DEBUG_NEW
  61. #undef THIS_FILE
  62. static char THIS_FILE[] = __FILE__;
  63. #endif
  64. TErrorDct errLog; // used to write dispatch log that is read by the agent monitor
  65. TErrorDct errTrace;
  66. TCriticalSection gCS;
  67. BOOL gbCacheFileBuilt = FALSE;
  68. // TServerNodes make up an internally used list of machines to install to
  69. class TServerNode : public TNode
  70. {
  71. WCHAR sourceName[LEN_Computer];
  72. WCHAR targetName[LEN_Computer];
  73. BOOL bTranslate;
  74. BOOL bChangeDomain;
  75. BOOL bReboot;
  76. DWORD dwRebootDelay;
  77. public:
  78. TServerNode() { sourceName[0] = 0; targetName[0] = 0; bTranslate = FALSE; bChangeDomain = FALSE; bReboot= FALSE; dwRebootDelay = 0; }
  79. WCHAR const * SourceName() { return sourceName; }
  80. WCHAR const * TargetName() { return targetName; }
  81. BOOL Translate() { return bTranslate; }
  82. BOOL Reboot() { return bReboot; }
  83. BOOL ChangeDomain() { return bChangeDomain; }
  84. DWORD RebootDelay() { return dwRebootDelay; }
  85. void SourceName(WCHAR const * src) { safecopy(sourceName,src); }
  86. void TargetName(WCHAR const * tgt) { safecopy(targetName,tgt); }
  87. void Translate(BOOL v) { bTranslate = v; }
  88. void ChangeDomain(BOOL v) { bChangeDomain = v; }
  89. void Reboot(BOOL v) { bReboot = v; }
  90. void RebootDelay(DWORD d) { dwRebootDelay = d; }
  91. };
  92. extern
  93. TErrorDct err;
  94. // defined in CkPlugIn.cpp
  95. BOOL IsValidPlugIn(IMcsDomPlugIn * pPlugIn);
  96. BOOL // ret - TRUE if need to dump debug information
  97. DumpDebugInfo(
  98. WCHAR * filename // out - where to dump debug information
  99. )
  100. {
  101. DWORD rc = 0;
  102. BOOL bFound = FALSE;
  103. TRegKey key;
  104. rc = key.OpenRead(GET_STRING(IDS_HKLM_DomainAdmin_Key),HKEY_LOCAL_MACHINE);
  105. if ( ! rc )
  106. {
  107. rc = key.ValueGetStr(L"DispatchVarSet",filename,MAX_PATH);
  108. if ( ! rc )
  109. {
  110. if ( *filename )
  111. bFound = TRUE;
  112. }
  113. }
  114. return bFound;
  115. }
  116. void
  117. BuildPlugInFileList(
  118. TNodeList * pList, // i/o- list that files needed by plug-ins wil be added to
  119. IVarSet * pVarSet // in - varset containing list of plug-ins to query
  120. )
  121. {
  122. // for now, build a list of all plug-ins, and add it to the varset
  123. MCSDCTWORKEROBJECTSLib::IPlugInInfoPtr pPtr;
  124. SAFEARRAY * pArray = NULL;
  125. HRESULT hr;
  126. LONG bound;
  127. LONG ndx[1];
  128. WCHAR key[LEN_Path];
  129. hr = pPtr.CreateInstance(__uuidof(MCSDCTWORKEROBJECTSLib::PlugInInfo));
  130. _bstr_t bStrGuid;
  131. swprintf(key,GET_STRING(IDS_DCTVS_Fmt_PlugIn_D),0);
  132. bStrGuid = pVarSet->get(key);
  133. if (! bStrGuid.length() )
  134. {
  135. // if no plug-ins are specified, use the ones in the plug-ins directory
  136. if ( SUCCEEDED(hr) )
  137. {
  138. hr = pPtr->raw_EnumeratePlugIns(&pArray);
  139. }
  140. if ( SUCCEEDED(hr) )
  141. {
  142. SafeArrayGetUBound(pArray,1,&bound);
  143. for ( ndx[0] = 0 ; ndx[0] <= bound ; ndx[0]++ )
  144. {
  145. BSTR val = NULL;
  146. SafeArrayGetElement(pArray,ndx,&val);
  147. swprintf(key,GET_STRING(IDS_DCTVS_Fmt_PlugIn_D),ndx[0]);
  148. pVarSet->put(key,val);
  149. SysFreeString(val);
  150. }
  151. SafeArrayDestroy(pArray);
  152. pArray = NULL;
  153. }
  154. }
  155. // enumerate the plug-ins specified in the varset, and make a list of their needed files
  156. int nRegFiles = 0;
  157. for ( int i = 0 ; ; i++ )
  158. {
  159. swprintf(key,GET_STRING(IDS_DCTVS_Fmt_PlugIn_D),i);
  160. bStrGuid = pVarSet->get(key);
  161. if ( bStrGuid.length() == 0 )
  162. break;
  163. IMcsDomPlugIn * pPlugIn = NULL;
  164. SAFEARRAY * pFileArray = NULL;
  165. TFileNode * pNode;
  166. CLSID clsid;
  167. hr = CLSIDFromString(bStrGuid,&clsid);
  168. if ( SUCCEEDED(hr) )
  169. {
  170. hr = CoCreateInstance(clsid,NULL,CLSCTX_ALL,IID_IMcsDomPlugIn,(void**)&pPlugIn);
  171. }
  172. if ( SUCCEEDED(hr) )
  173. {
  174. if ( IsValidPlugIn(pPlugIn) )
  175. {
  176. hr = pPlugIn->GetRequiredFiles(&pFileArray);
  177. if ( SUCCEEDED(hr) )
  178. {
  179. SafeArrayGetUBound(pFileArray,1,&bound);
  180. for ( ndx[0] = 0 ; ndx[0] <= bound ; ndx[0]++ )
  181. {
  182. BSTR val = NULL;
  183. SafeArrayGetElement(pFileArray,ndx,&val);
  184. pNode = new TFileNode(val);
  185. pList->InsertBottom(pNode);
  186. SysFreeString(val);
  187. }
  188. SafeArrayDestroy(pFileArray);
  189. pFileArray = NULL;
  190. }
  191. hr = pPlugIn->GetRegisterableFiles(&pFileArray);
  192. if ( SUCCEEDED(hr) )
  193. {
  194. SafeArrayGetUBound(pFileArray,1,&bound);
  195. for (ndx[0] = 0; ndx[0] <= bound ; ndx[0]++ )
  196. {
  197. BSTR val = NULL;
  198. SafeArrayGetElement(pFileArray,ndx,&val);
  199. swprintf(key,GET_STRING(IDS_DCTVSFmt_PlugIn_RegisterFiles_D),nRegFiles);
  200. pVarSet->put(key,val);
  201. SysFreeString(val);
  202. nRegFiles++;
  203. }
  204. SafeArrayDestroy(pFileArray);
  205. pFileArray = NULL;
  206. }
  207. }
  208. pPlugIn->Release();
  209. }
  210. }
  211. }
  212. // InstallJobInfo defines a Domain Migration 'job' to be installed and launched
  213. struct InstallJobInfo
  214. {
  215. IVarSetPtr pVarSetList; // varset defining the server list
  216. IVarSetPtr pVarSet; // VarSet defining the job to run
  217. _bstr_t serverName; // computer to install and run on
  218. long ndx; // index of this server in the server list
  219. TNodeList * pPlugInFileList; // list of files to install for plug-ins
  220. std::vector<CComBSTR>* pStartFailedVector;
  221. std::vector<CComBSTR>* pFailureDescVector;
  222. std::vector<CComBSTR>* pStartedVector;
  223. std::vector<CComBSTR>* pJobidVector;
  224. HANDLE hMutex;
  225. _bstr_t jobfile; // uses the specified job file instead of creating one
  226. int nErrCount;
  227. };
  228. // WaitInfo is used to pass information to a thread that waits and does cleanup
  229. // after all the Dispatcher's work is done
  230. struct WaitInfo
  231. {
  232. IUnknown * pUnknown; // IUnknown interface to the DCTDisptacher object
  233. TJobDispatcher **ppPool; // pointer to thread pool performing the tasks (installations)
  234. TNodeList * pPlugInFileList; // pointer to plug-in files list that will need to be freed
  235. };
  236. WCHAR gComputerName[LEN_Computer] = L""; // name of local computer
  237. // Calls DCTAgentService to start the Domain Migration Job on a remote computer
  238. DWORD // ret- OS return code
  239. StartJob(
  240. WCHAR const * serverName, // in - computer to start job on
  241. WCHAR const * password, // in - password for "Options.Credentials" account (used for writing results back to console machine)
  242. WCHAR const * fullname, // in - full path (including filename) to file containing the job's VarSet
  243. WCHAR const * filename, // in - filename of file containing the varset for the job
  244. _bstr_t& strJobid
  245. )
  246. {
  247. DWORD rc = 0;
  248. handle_t hBinding = NULL;
  249. WCHAR * sBinding = NULL;
  250. WCHAR jobGUID[LEN_Guid];
  251. WCHAR passwordW[LEN_Password];
  252. safecopy(passwordW,password);
  253. rc = EaxBindCreate(serverName,&hBinding,&sBinding,TRUE);
  254. if ( rc )
  255. {
  256. err.SysMsgWrite(ErrE,rc,DCT_MSG_AGENT_BIND_FAILED_SD, serverName,rc);
  257. }
  258. if( ! rc )
  259. {
  260. RpcTryExcept
  261. {
  262. // the job file has been copied to the remote computer
  263. // during the installation
  264. rc = EaxcSubmitJob(hBinding,filename,passwordW,jobGUID);
  265. if ( ! rc )
  266. {
  267. err.MsgWrite(0,DCT_MSG_AGENT_JOB_STARTED_SSS,serverName,filename,jobGUID);
  268. strJobid = jobGUID;
  269. }
  270. else
  271. {
  272. err.SysMsgWrite(ErrE,rc,DCT_MSG_AGENT_JOB_START_FAILED_SSD,serverName,filename,rc);
  273. }
  274. }
  275. RpcExcept(1)
  276. {
  277. rc = RpcExceptionCode();
  278. if ( rc != RPC_S_SERVER_UNAVAILABLE )
  279. {
  280. err.SysMsgWrite(ErrE,rc,DCT_MSG_AGENT_JOB_START_FAILED_SSD,serverName,filename,rc);
  281. }
  282. }
  283. RpcEndExcept
  284. if ( rc == RPC_S_SERVER_UNAVAILABLE )
  285. {
  286. // maybe the agent hasn't started up yet for some reason
  287. for ( int tries = 0 ; tries < 6 ; tries++ )
  288. {
  289. Sleep(5000); // wait a few seconds and try again
  290. RpcTryExcept
  291. {
  292. rc = EaxcSubmitJob(hBinding,filename,passwordW,jobGUID);
  293. if ( ! rc )
  294. {
  295. err.MsgWrite(0,DCT_MSG_AGENT_JOB_STARTED_SSS,serverName,filename,jobGUID);
  296. strJobid = jobGUID;
  297. break;
  298. }
  299. else
  300. {
  301. if ( tries == 5 )
  302. err.SysMsgWrite(ErrE,rc,DCT_MSG_AGENT_JOB_START_FAILED_SSD,serverName,filename,rc);
  303. }
  304. }
  305. RpcExcept(1)
  306. {
  307. rc = RpcExceptionCode();
  308. if ( tries == 5 )
  309. err.SysMsgWrite(ErrE,rc,DCT_MSG_AGENT_JOB_START_FAILED_SSD,serverName,filename,rc);
  310. }
  311. RpcEndExcept
  312. }
  313. }
  314. }
  315. if ( ! rc )
  316. {
  317. // if the job was started successfully, remove the job file
  318. if ( ! MoveFileEx(fullname,NULL, MOVEFILE_DELAY_UNTIL_REBOOT) )
  319. {
  320. // DWORD rc2 = GetLastError();
  321. }
  322. }
  323. // this indicates whether the server was started
  324. if ( ! rc )
  325. {
  326. errLog.DbgMsgWrite(0,L"%ls\t%ls\t%ld,%ls,%ls",serverName,L"Start",rc,filename,jobGUID);
  327. }
  328. else
  329. {
  330. errLog.DbgMsgWrite(0,L"%ls\t%ls\t%ld",serverName,L"Start",rc);
  331. }
  332. return rc;
  333. }
  334. // Gets the domain sid for the specified domain
  335. BOOL // ret- TRUE if successful
  336. GetSidForDomain(
  337. LPWSTR DomainName, // in - name of domain to get SID for
  338. PSID * pDomainSid // out- SID for domain, free with FreeSid
  339. )
  340. {
  341. PSID pSid = NULL;
  342. // DWORD lenSid = 200;
  343. DWORD rc = 0;
  344. WCHAR * domctrl = NULL;
  345. if ( DomainName[0] != L'\\' )
  346. {
  347. rc = NetGetDCName(NULL,DomainName,(LPBYTE*)&domctrl);
  348. }
  349. if ( ! rc )
  350. {
  351. rc = GetDomainSid(domctrl,&pSid);
  352. NetApiBufferFree(domctrl);
  353. }
  354. (*pDomainSid) = pSid;
  355. return ( pSid != NULL);
  356. }
  357. // Set parameters in the varset that are specific to this particular computer
  358. void
  359. SetupVarSetForJob(
  360. InstallJobInfo * pInfo, // structure defining job
  361. IVarSet * pVarSet, // varset describing job
  362. WCHAR const * uncname, // UNC path for results directory
  363. WCHAR const * filename // UNC path for results file for this job
  364. )
  365. {
  366. WCHAR uncresult[MAX_PATH];
  367. WCHAR serverName[MAX_PATH];
  368. WCHAR shareName[MAX_PATH];
  369. _bstr_t text;
  370. // Set server-specific parameters in the varset
  371. swprintf(uncresult,L"%s.result",filename);
  372. swprintf(serverName,L"\\\\%s",gComputerName);
  373. UStrCpy(shareName,uncname + UStrLen(serverName) );
  374. shareName[UStrLen(shareName)-1] = 0;
  375. pVarSet->put(GET_BSTR(DCTVS_Options_ResultFile),uncresult);
  376. pVarSet->put(GET_BSTR(DCTVS_Options_Credentials_Server),serverName);
  377. pVarSet->put(GET_BSTR(DCTVS_Options_Credentials_Share),shareName);
  378. pVarSet->put(GET_BSTR(DCTVS_Options_DeleteFileAfterLoad),GET_BSTR(IDS_YES));
  379. pVarSet->put(GET_BSTR(DCTVS_Options_RemoveAgentOnCompletion),GET_BSTR(IDS_YES));
  380. pVarSet->put(GET_BSTR(DCTVS_Options_LogToTemp),GET_BSTR(IDS_YES));
  381. pVarSet->put(GET_BSTR(DCTVS_Server_Index), CComVariant((long)pInfo->ndx));
  382. text = pVarSet->get(GET_BSTR(DCTVS_GatherInformation_UserRights));
  383. if ( !UStrICmp(text,GET_STRING(IDS_YES)) )
  384. {
  385. swprintf(uncresult,L"%s.userrights",filename);
  386. pVarSet->put(GET_BSTR(DCTVS_GatherInformation_UserRights),uncresult);
  387. }
  388. text = pVarSet->get(GET_BSTR(DCTVS_Security_ReportAccountReferences));
  389. if ( ! UStrICmp(text,GET_STRING(IDS_YES)) )
  390. {
  391. swprintf(uncresult,L"%s.secrefs",filename);
  392. pVarSet->put(GET_BSTR(DCTVS_Security_ReportAccountReferences),uncresult);
  393. }
  394. pVarSet->put(GET_BSTR(DCTVS_Options_LocalProcessingOnly),GET_BSTR(IDS_YES));
  395. }
  396. // Entry point for thread, waits until all agents are installed and started,
  397. // then cleans up and exits
  398. ULONG __stdcall // ret- returns 0
  399. Wait(
  400. void * arg // in - WaitInfo structure containing needed pointers
  401. )
  402. {
  403. WaitInfo * w = (WaitInfo*)arg;
  404. SetThreadLocale(LOCALE_SYSTEM_DEFAULT);
  405. // wait for all jobs to finish
  406. (*(w->ppPool))->WaitForCompletion();
  407. if ( w->pUnknown )
  408. w->pUnknown->Release();
  409. // delete the plug-in file list
  410. TNodeListEnum tEnum;
  411. TFileNode * fNode;
  412. TFileNode * fNext;
  413. for ( fNode = (TFileNode*)tEnum.OpenFirst(w->pPlugInFileList); fNode; fNode = fNext )
  414. {
  415. fNext = (TFileNode*)tEnum.Next();
  416. w->pPlugInFileList->Remove(fNode);
  417. delete fNode;
  418. }
  419. tEnum.Close();
  420. delete w->pPlugInFileList;
  421. delete *(w->ppPool);
  422. *(w->ppPool) = NULL;
  423. err.MsgWrite(0,DCT_MSG_DISPATCHER_DONE);
  424. errLog.DbgMsgWrite(0,L"%ls\t%ls\t%ld",L"All",L"Finished",0);
  425. err.LogClose();
  426. errLog.LogClose();
  427. return 0;
  428. }
  429. // Thread entry point, installs and starts agent on a single computer
  430. ULONG __stdcall // ret- HRESULT error code
  431. DoInstall(
  432. void * arg // in - InstallJobInfo structure
  433. )
  434. {
  435. SetThreadLocale(LOCALE_SYSTEM_DEFAULT);
  436. HRESULT hr = S_OK;
  437. InstallJobInfo * pInfo = (InstallJobInfo*)arg;
  438. _bstr_t strJobid;
  439. _bstr_t strFailureDesc(GET_STRING(IDS_START_FAILED));
  440. if(pInfo->nErrCount == 0)
  441. hr = CoInitializeEx(0,COINIT_MULTITHREADED );
  442. #ifdef OFA
  443. HKEY hkeyLocal;
  444. LONG lRes;
  445. bool bVersionSupported = true;
  446. // Check version info
  447. lRes = RegConnectRegistry(
  448. pInfo->serverName,
  449. // address of name of remote computer
  450. HKEY_LOCAL_MACHINE, // predefined registry handle
  451. &hkeyLocal // address of buffer for remote registry handle
  452. );
  453. if(lRes == ERROR_SUCCESS)
  454. {
  455. HKEY hkeyWin;
  456. lRes = ::RegOpenKey(hkeyLocal, GET_STRING(IDS_HKLM_WINDOWS_NT), &hkeyWin);
  457. if(lRes == ERROR_SUCCESS)
  458. {
  459. DWORD nMaxLen = 20;
  460. BSTR strVersion = SysAllocStringLen(0, nMaxLen), strSP = SysAllocStringLen(0, nMaxLen);
  461. strVersion[0] = L'\0'; strSP[0] = L'\0';
  462. DWORD type;
  463. DWORD nTemp = nMaxLen*2;
  464. lRes = RegQueryValueEx(hkeyWin, GET_STRING(IDS_CurrentVersion), 0, &type, (LPBYTE)strVersion, &nTemp);
  465. if(!lRes)
  466. {
  467. nTemp = nMaxLen*2;
  468. lRes = RegQueryValueEx(hkeyWin, GET_STRING(IDS_CSDVersion), 0, &type, (LPBYTE)strSP, &nTemp);
  469. lRes = 0;
  470. }
  471. if(!lRes)
  472. {
  473. // write out the version info
  474. err.MsgWrite(ErrI,DCT_MSG_AGENT_OSVERSION,(WCHAR*)pInfo->serverName,strVersion, strSP);
  475. TCHAR cSP = 0;
  476. if(strVersion[0] == L'4' && wcslen(strSP))
  477. cSP = strSP[wcslen(strSP) - 1];
  478. if(strVersion[0] < L'4' || (strVersion[0] == L'4' &&
  479. cSP < L'3') )
  480. bVersionSupported = false;
  481. }
  482. ::RegCloseKey(hkeyWin);
  483. ::SysFreeString(strVersion); ::SysFreeString(strSP);
  484. }
  485. else
  486. {
  487. lRes = ::RegOpenKey(hkeyLocal, GET_STRING(IDS_HKLM_MICROSOFT), &hkeyWin);
  488. if(!lRes)
  489. {
  490. bVersionSupported = false;
  491. err.MsgWrite(ErrI, lRes, DCT_MSG_AGENT_OSVERSION_NOT_WINNT,(WCHAR*)pInfo->serverName);
  492. }
  493. ::RegCloseKey(hkeyWin);
  494. }
  495. ::RegCloseKey(hkeyLocal);
  496. }
  497. if(lRes)
  498. err.SysMsgWrite(ErrI, lRes, DCT_MSG_AGENT_OSVERSION_NOT_FOUND,(WCHAR*)pInfo->serverName, lRes);
  499. else if(!bVersionSupported)
  500. {
  501. err.MsgWrite(ErrI,DCT_MSG_AGENT_OSVERSION_NOTSUPPORTED,(WCHAR*)pInfo->serverName);
  502. strFailureDesc = GET_STRING(IDS_UNSOUPPORTED_OS);
  503. hr = E_FAIL;
  504. }
  505. #endif
  506. if ( SUCCEEDED(hr) )
  507. {
  508. IWorkNode * pInstaller = NULL;
  509. IVarSetPtr pVarSet(CLSID_VarSet);
  510. WCHAR filename[MAX_PATH];
  511. WCHAR tempdir[MAX_PATH];
  512. WCHAR key[MAX_PATH];
  513. if ( pVarSet == NULL )
  514. {
  515. if(pInfo->nErrCount == 0)
  516. CoUninitialize();
  517. return E_FAIL;
  518. }
  519. DWORD uniqueNumber = (LONG)pInfo->pVarSet->get(GET_BSTR(DCTVS_Options_UniqueNumberForResultsFile));
  520. _bstr_t bstrResultPath = pInfo->pVarSet->get(GET_BSTR(DCTVS_Dispatcher_ResultPath));
  521. _bstr_t bstrPassword = pInfo->pVarSet->get(GET_BSTR(DCTVS_Options_Credentials_Password));
  522. // Copy the common information from the source varset
  523. gCS.Enter();
  524. // pInfo->pVarSet contains all the information except the server list
  525. // we don't want to copy the server list each time, so we create our new varset from pInfo->pVarSet
  526. pVarSet->ImportSubTree("",pInfo->pVarSet);
  527. gCS.Leave();
  528. // Set the server-specific data in the varset
  529. swprintf(key,GET_BSTR(IDS_DCTVSFmt_Servers_RenameTo_D),pInfo->ndx);
  530. // pInfo->pVarSetList contains the entire varset including the server list
  531. _bstr_t text = pInfo->pVarSetList->get(key);
  532. if ( text.length() )
  533. {
  534. pVarSet->put(GET_BSTR(DCTVS_LocalServer_RenameTo),text);
  535. }
  536. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_ChangeDomain_D),pInfo->ndx);
  537. text = pInfo->pVarSetList->get(key);
  538. if ( text.length() )
  539. {
  540. pVarSet->put(GET_BSTR(DCTVS_LocalServer_ChangeDomain),text);
  541. }
  542. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_MigrateOnly_D),pInfo->ndx);
  543. text = pInfo->pVarSetList->get(key);
  544. pVarSet->put(GET_BSTR(DCTVS_LocalServer_MigrateOnly),text);
  545. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_Reboot_D),pInfo->ndx);
  546. text = pInfo->pVarSetList->get(key);
  547. if ( text.length() )
  548. {
  549. pVarSet->put(GET_BSTR(DCTVS_LocalServer_Reboot),text);
  550. LONG delay;
  551. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RebootDelay_D),pInfo->ndx);
  552. delay = pInfo->pVarSetList->get(key);
  553. if ( delay )
  554. {
  555. pVarSet->put(GET_BSTR(DCTVS_LocalServer_RebootDelay),delay);
  556. }
  557. }
  558. // remove the password from the varset, so that we are not writing it
  559. // to a file in plain text. Instead, it will be passed to the agent service
  560. // when the job is submitted
  561. pVarSet->put(GET_BSTR(DCTVS_Options_Credentials_Password),"");
  562. pVarSet->put(GET_BSTR(DCTVS_AccountOptions_SidHistoryCredentials_Password),"");
  563. if ( ! uniqueNumber )
  564. {
  565. uniqueNumber = GetTickCount();
  566. }
  567. MCSASSERT(bstrResultPath.length());
  568. safecopy(tempdir,(WCHAR*)bstrResultPath);
  569. pInstaller = new CComObject<CDCTInstaller>;
  570. if (!pInstaller)
  571. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  572. pInstaller->AddRef();
  573. ((CDCTInstaller*)pInstaller)->SetFileList(pInfo->pPlugInFileList);
  574. if ( SUCCEEDED(hr) )
  575. {
  576. swprintf(filename,L"%s%s%ld",tempdir,(WCHAR*)pInfo->serverName + 2,uniqueNumber);
  577. if ( !pInfo->jobfile.length() )
  578. {
  579. SetupVarSetForJob(pInfo,pVarSet,tempdir,filename);
  580. // Save the input varset to a file
  581. IPersistStoragePtr ps = NULL;
  582. IStoragePtr store = NULL;
  583. hr = pVarSet->QueryInterface(IID_IPersistStorage,(void**)&ps);
  584. if ( SUCCEEDED(hr) )
  585. {
  586. hr = StgCreateDocfile(filename,STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE |STGM_FAILIFTHERE,0,&store);
  587. if ( SUCCEEDED(hr) )
  588. {
  589. hr = OleSave(ps,store,FALSE);
  590. }
  591. }
  592. }
  593. else
  594. {
  595. safecopy(filename,(WCHAR*)pInfo->jobfile);
  596. }
  597. IUnknown * pWorkItem = NULL;
  598. if ( SUCCEEDED(hr) )
  599. {
  600. pVarSet->put(GET_BSTR(DCTVS_ConfigurationFile),filename);
  601. pVarSet->put(GET_BSTR(DCTVS_InstallToServer),pInfo->serverName);
  602. hr = pVarSet->QueryInterface(IID_IUnknown,(void**)&pWorkItem);
  603. }
  604. if ( SUCCEEDED(hr) )
  605. {
  606. // Do the installation to the server
  607. hr = pInstaller->Process(pWorkItem);
  608. if(hr == 0x88070040)
  609. strFailureDesc = GET_STRING(IDS_AGENT_RUNNING);
  610. pWorkItem->Release();
  611. if ( SUCCEEDED(hr) )
  612. {
  613. err.MsgWrite(0,DCT_MSG_AGENT_INSTALLED_S,(WCHAR*)pInfo->serverName);
  614. // try to start the job
  615. DWORD rc = StartJob(pInfo->serverName,bstrPassword,filename,filename + UStrLen(tempdir), strJobid );
  616. if ( rc )
  617. {
  618. hr = HRESULT_FROM_WIN32(rc);
  619. // if we couldn't start the job, then try to stop the service
  620. TDCTInstall x( pInfo->serverName, NULL );
  621. x.SetServiceInformation(GET_STRING(IDS_DISPLAY_NAME),GET_STRING(IDS_SERVICE_NAME),L"EXE",NULL);
  622. DWORD rcOs = x.ScmOpen();
  623. if ( ! rcOs )
  624. {
  625. x.ServiceStop();
  626. }
  627. }
  628. }
  629. }
  630. pInstaller->Release();
  631. }
  632. }
  633. if(pInfo->nErrCount == 0)
  634. CoUninitialize();
  635. if ( hr )
  636. {
  637. if ( hr == HRESULT_FROM_WIN32(RPC_S_SERVER_UNAVAILABLE) )
  638. {
  639. err.MsgWrite(ErrE,DCT_MSG_AGENT_SERVICE_NOT_STARTED_SS,(WCHAR*)pInfo->serverName,(WCHAR*)pInfo->serverName);
  640. }
  641. else
  642. {
  643. err.SysMsgWrite(ErrE,hr,DCT_MSG_AGENT_LAUNCH_FAILED_SD,(WCHAR*)pInfo->serverName,hr);
  644. }
  645. if(hr == 0x80070040 && pInfo->nErrCount < 10)
  646. {
  647. Sleep(1000);
  648. pInfo->nErrCount++;
  649. err.DbgMsgWrite(0,L"Retrying install...");
  650. DoInstall((LPVOID)pInfo);
  651. }
  652. else if (hr == CO_E_NOT_SUPPORTED)
  653. {
  654. err.MsgWrite(ErrI,DCT_MSG_AGENT_ALPHA_NOTSUPPORTED,(WCHAR*)pInfo->serverName);
  655. strFailureDesc = GET_STRING(IDS_UNSOUPPORTED_OS);
  656. ::WaitForSingleObject(pInfo->hMutex, 30000);
  657. pInfo->pStartFailedVector->push_back((BSTR)pInfo->serverName);
  658. pInfo->pFailureDescVector->push_back((BSTR)strFailureDesc);
  659. ::ReleaseMutex(pInfo->hMutex);
  660. }
  661. else
  662. {
  663. ::WaitForSingleObject(pInfo->hMutex, 30000);
  664. pInfo->pStartFailedVector->push_back((BSTR)pInfo->serverName);
  665. pInfo->pFailureDescVector->push_back((BSTR)strFailureDesc);
  666. ::ReleaseMutex(pInfo->hMutex);
  667. }
  668. }
  669. else
  670. {
  671. // DWORD res = ::WaitForSingleObject(pInfo->hMutex, 30000);
  672. ::WaitForSingleObject(pInfo->hMutex, 30000);
  673. pInfo->pStartedVector->push_back((BSTR)pInfo->serverName);
  674. _ASSERTE(strJobid != _bstr_t(L""));
  675. pInfo->pJobidVector->push_back((BSTR)strJobid);
  676. ::ReleaseMutex(pInfo->hMutex);
  677. }
  678. if(pInfo->nErrCount == 0)
  679. delete pInfo;
  680. return hr;
  681. }
  682. // DispatchToServers
  683. // VarSet input:
  684. //
  685. STDMETHODIMP // ret- HRESULT
  686. CDCTDispatcher::DispatchToServers(
  687. IUnknown ** ppData // i/o- pointer to varset
  688. )
  689. {
  690. HRESULT hr;
  691. SetThreadLocale(LOCALE_SYSTEM_DEFAULT);
  692. //Sleep(60000); //delay for debugging
  693. (*ppData)->AddRef();
  694. hr = Process(*ppData,NULL,NULL);
  695. return hr;
  696. }
  697. // Sets share level permissions
  698. DWORD // OS return code
  699. CDCTDispatcher::SetSharePermissions(
  700. WCHAR const * domain, // in - domain for user account
  701. WCHAR const * user, // in - user account to grant permissions to
  702. WCHAR const * share, // in - name of share
  703. WCHAR const * directory // in - path of shared directory
  704. )
  705. {
  706. DWORD rc = 0;
  707. WCHAR domainname[LEN_Domain];
  708. WCHAR * server = NULL;
  709. WCHAR sharename[MAX_PATH];
  710. WCHAR dirname[MAX_PATH];
  711. BYTE * sid = (BYTE*)malloc(200);
  712. DWORD lenSid = 200;
  713. DWORD lenDomain = DIM(domainname);
  714. SID_NAME_USE snu;
  715. BOOL bGotSid = FALSE;
  716. if (!sid)
  717. return ERROR_NOT_ENOUGH_MEMORY;
  718. safecopy(sharename,share);
  719. safecopy(dirname,directory);
  720. // Set the share permissions
  721. TShareSD sd(sharename);
  722. // Get a domain controller for domain
  723. if ( UStrICmp(domain,gComputerName) )
  724. {
  725. rc = NetGetDCName(NULL,domain,(LPBYTE*)&server);
  726. }
  727. if ( ! rc )
  728. {
  729. // get the SID for the user account
  730. if ( ! LookupAccountName(server,user,sid,&lenSid,domainname,&lenDomain,&snu) )
  731. {
  732. rc = GetLastError();
  733. }
  734. else
  735. {
  736. bGotSid = TRUE;
  737. if (! UStrICmp(domainname,domain) )
  738. {
  739. // we found the correct account
  740. // grant permissions to this user
  741. if ( sd.GetSecurity() != NULL )
  742. {
  743. TACE ace(ACCESS_ALLOWED_ACE_TYPE,0,DACL_FULLCONTROL_MASK,sid);
  744. PACL acl = sd.GetSecurity()->GetDacl();
  745. sd.GetSecurity()->ACLAddAce(&acl,&ace,-1);
  746. sd.GetSecurity()->SetDacl(acl,TRUE);
  747. sd.WriteSD();
  748. }
  749. }
  750. }
  751. NetApiBufferFree(server);
  752. }
  753. if ( rc )
  754. {
  755. err.SysMsgWrite(ErrW,rc,DCT_MSG_CANNOT_FIND_ACCOUNT_SSD,domain,user,rc);
  756. }
  757. GetBkupRstrPriv();
  758. // Set NTFS permissions for the results directory
  759. TFileSD * fsd = new TFileSD(dirname);
  760. if (!fsd)
  761. return ERROR_NOT_ENOUGH_MEMORY;
  762. if ( bGotSid && fsd->GetSecurity() != NULL )
  763. {
  764. BYTE inherit = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE;
  765. TACE ace(ACCESS_ALLOWED_ACE_TYPE,0,DACL_FULLCONTROL_MASK,sid);
  766. TACE ace2(ACCESS_ALLOWED_ACE_TYPE,0,DACL_FULLCONTROL_MASK,sid);
  767. TACE ace3(ACCESS_ALLOWED_ACE_TYPE,0,DACL_FULLCONTROL_MASK,GetWellKnownSid(1/*ADMINISTRATORS*/));
  768. TACE ace4(ACCESS_ALLOWED_ACE_TYPE,0,DACL_FULLCONTROL_MASK,GetWellKnownSid(1/*ADMINISTRATORS*/));
  769. PACL acl = NULL;
  770. ace2.SetFlags(inherit);
  771. ace4.SetFlags(inherit);
  772. fsd->GetSecurity()->ACLAddAce(&acl,&ace,0);
  773. fsd->GetSecurity()->ACLAddAce(&acl,&ace2,1);
  774. fsd->GetSecurity()->ACLAddAce(&acl,&ace3,2);
  775. fsd->GetSecurity()->ACLAddAce(&acl,&ace4,3);
  776. fsd->GetSecurity()->SetDacl(acl,TRUE);
  777. fsd->WriteSD();
  778. }
  779. return rc;
  780. }
  781. // DoesAccountHaveAccessToShare Function
  782. //
  783. // Checks if the specified account's SID is in the specified folder's DACL. This function only
  784. // verifies the existence of the SID in the DACL.
  785. bool DoesAccountHaveAccessToShare(LPCTSTR pszDomainName, LPCTSTR pszAccountName, LPCTSTR pszResultFolder)
  786. {
  787. bool bAccess = false;
  788. try
  789. {
  790. // maximum SID size in bytes
  791. // Revision 1
  792. // SubAuthorityCount 1
  793. // IdentifierAuthority 6
  794. // SubAuthority[15] 60
  795. // Total 68
  796. BYTE bytSid[128];
  797. PSID pSid = (PSID)bytSid;
  798. DWORD cbSid = sizeof(bytSid);
  799. _TCHAR szDomainName[128];
  800. DWORD cchDomainName = sizeof(szDomainName) / sizeof(szDomainName[0]);
  801. SID_NAME_USE snu;
  802. LookupAccountName(NULL, pszAccountName, pSid, &cbSid, szDomainName, &cchDomainName, &snu);
  803. // verify that the domains match
  804. if (_tcsicmp(szDomainName, pszDomainName) == 0)
  805. {
  806. // add backup/restore privilege to current process/thread token
  807. GetBkupRstrPriv();
  808. // search for SID in DACL of result folder
  809. TFileSD sdFile((const LPTSTR)(pszResultFolder));
  810. TSD* psd = sdFile.GetSecurity();
  811. if (psd && psd->IsDaclPresent())
  812. {
  813. int c = psd->GetNumDaclAces();
  814. for (int i = 0; i < c; i++)
  815. {
  816. TACE ace(psd->GetDaclAce(i));
  817. if (EqualSid(ace.GetSid(), pSid))
  818. {
  819. bAccess = true;
  820. break;
  821. }
  822. }
  823. }
  824. }
  825. }
  826. catch (...)
  827. {
  828. ;
  829. }
  830. return bAccess;
  831. }
  832. // creates a share on the local machine for the results directory
  833. // the name of the share is determined by RESULT_SHARE_NAME, and a number
  834. //
  835. // VarSet input:
  836. // Dispatcher.ResultPath - name of directory to share
  837. // Options.Credentials.Domain - domain for user account
  838. // Options.Credentials.UserName - user account to grant permissions to
  839. //
  840. HRESULT // ret- HRESULT
  841. CDCTDispatcher::ShareResultDirectory(
  842. IVarSet * pVarSet // in - varset containing credentials
  843. )
  844. {
  845. DWORD rc = 0;
  846. _bstr_t bstrResultDir = pVarSet->get(GET_BSTR(DCTVS_Dispatcher_ResultPath));
  847. _bstr_t bstrDomain = pVarSet->get(GET_BSTR(DCTVS_Options_Credentials_Domain));
  848. _bstr_t bstrAccount = pVarSet->get(GET_BSTR(DCTVS_Options_Credentials_UserName));
  849. _bstr_t bstrShareName = pVarSet->get(_bstr_t((BSTR)L"Options.ShareName"));
  850. WCHAR resultPath[MAX_PATH];
  851. WCHAR uncResultPath[MAX_PATH];
  852. WCHAR shareName[MAX_PATH];
  853. if ( bstrResultDir.length() )
  854. {
  855. safecopy(resultPath,(WCHAR*)bstrResultDir);
  856. }
  857. else
  858. {
  859. GetTempPath(DIM(resultPath),resultPath);
  860. }
  861. // create a share for the results directory
  862. if ( bstrShareName.length() )
  863. {
  864. safecopy(shareName,(WCHAR*)bstrShareName);
  865. }
  866. else
  867. {
  868. swprintf(shareName,L"%s$",GET_STRING(IDS_RESULT_SHARE_NAME));
  869. }
  870. SHARE_INFO_2 shareInfo;
  871. memset(&shareInfo,0,(sizeof shareInfo));
  872. // remove the trailing backslash from the path
  873. if ( resultPath[UStrLen(resultPath)-1] == L'\\' )
  874. resultPath[UStrLen(resultPath)-1] = 0;
  875. shareInfo.shi2_netname = shareName;
  876. shareInfo.shi2_remark = GET_BSTR(IDS_RESULT_SHARE_REMARK);
  877. shareInfo.shi2_path = resultPath;
  878. shareInfo.shi2_max_uses = -1;
  879. shareInfo.shi2_type = STYPE_DISKTREE;
  880. swprintf(uncResultPath,L"\\\\%s\\%s\\",gComputerName,shareName);
  881. pVarSet->put(GET_BSTR(DCTVS_Dispatcher_ResultPath),uncResultPath);
  882. rc = NetShareAdd(NULL,2,(LPBYTE)&shareInfo,NULL);
  883. if ( ! rc )
  884. {
  885. // remove trailing backslash
  886. uncResultPath[UStrLen(uncResultPath)-1] = 0;
  887. // update the permissions for the share
  888. rc = SetSharePermissions((WCHAR*)bstrDomain,(WCHAR*)bstrAccount,uncResultPath,resultPath);
  889. }
  890. else if ( rc == NERR_DuplicateShare )
  891. {
  892. SHARE_INFO_2 * shInfo = NULL;
  893. DWORD parmErr;
  894. // make sure the share is pointing to the right directory
  895. rc = NetShareGetInfo(NULL,shareName,2,(LPBYTE*)&shInfo);
  896. if ( ! rc )
  897. {
  898. if ( UStrICmp(resultPath,shInfo->shi2_path) )
  899. {
  900. rc = NetShareDel(NULL, shareName, 0);
  901. if(!rc)
  902. {
  903. rc = NetShareAdd(NULL,2,(LPBYTE)&shareInfo,NULL);
  904. if ( ! rc )
  905. {
  906. // remove trailing backslash
  907. uncResultPath[UStrLen(uncResultPath)-1] = 0;
  908. // update the permissions for the share
  909. rc = SetSharePermissions((WCHAR*)bstrDomain,(WCHAR*)bstrAccount,uncResultPath,resultPath);
  910. }
  911. }
  912. else
  913. {
  914. // the share points to the wrong place
  915. shInfo->shi2_netname = shareName;
  916. rc = NetShareSetInfo(NULL,shareName,2,(LPBYTE)&shInfo,&parmErr);
  917. }
  918. }
  919. else
  920. {
  921. if (DoesAccountHaveAccessToShare(bstrDomain, bstrAccount, resultPath) == false)
  922. {
  923. uncResultPath[UStrLen(uncResultPath) - 1] = 0;
  924. SetSharePermissions(bstrDomain, bstrAccount, uncResultPath, resultPath);
  925. }
  926. }
  927. NetApiBufferFree(shInfo);
  928. }
  929. }
  930. if ( ! rc )
  931. {
  932. err.MsgWrite(0,DCT_MSG_CREATED_RESULT_SHARE_SS,resultPath,uncResultPath);
  933. errLog.DbgMsgWrite(0,L"%ls : %ls",resultPath,uncResultPath);
  934. }
  935. return HRESULT_FROM_WIN32(rc);
  936. }
  937. // BuildInputFile constructs a cache file to be used for security translation
  938. // VarSet input:
  939. // Options.UniqueNumberForResultsFile -unique number to append
  940. // Dispatcher.ResultPath -directory to write file to
  941. //
  942. HRESULT // ret- HRESULT
  943. CDCTDispatcher::BuildInputFile(
  944. IVarSet * pVarSet // in - varset containing data
  945. )
  946. {
  947. IVarSetPtr pVarSetST(CLSID_VarSet); // varset to use to run security translator
  948. IVarSetPtr pVarSetTemp;
  949. HRESULT hr = S_OK;
  950. _bstr_t key = GET_BSTR(DCTVS_Options);
  951. WCHAR tempdir[MAX_PATH];
  952. WCHAR resultPath[MAX_PATH];
  953. WCHAR logfile[MAX_PATH];
  954. DWORD uniqueNumber = (LONG)pVarSet->get(GET_BSTR(DCTVS_Options_UniqueNumberForResultsFile));
  955. _bstr_t bstrResultDir = pVarSet->get(GET_BSTR(DCTVS_Dispatcher_ResultPath));
  956. _bstr_t temp = pVarSet->get(GET_BSTR(DCTVS_Security_AlternateCacheFile));
  957. if ( pVarSetST == NULL )
  958. {
  959. return E_FAIL;
  960. }
  961. if ( ! NeedToUseST(pVarSet,TRUE) )
  962. {
  963. return S_OK;
  964. }
  965. // construct a filename for the cache
  966. if ( ! uniqueNumber )
  967. {
  968. uniqueNumber = GetTickCount();
  969. }
  970. if ( bstrResultDir.length() )
  971. {
  972. safecopy(tempdir,(WCHAR*)bstrResultDir);
  973. }
  974. else
  975. {
  976. // if no result path specified, use temp directory
  977. hr = GetTempPath(DIM(tempdir),tempdir);
  978. }
  979. swprintf(resultPath,L"%s%s",tempdir,GET_STRING(IDS_CACHE_FILE_NAME));
  980. // if a cache file is specified, use it instead of building a new one
  981. if ( temp.length() )
  982. {
  983. CopyFile(temp,resultPath,FALSE);
  984. pVarSet->put(GET_BSTR(DCTVS_Accounts_InputFile),GET_BSTR(IDS_CACHE_FILE_NAME));
  985. pVarSet->put(GET_BSTR(DCTVS_Accounts_WildcardSpec),"");
  986. gbCacheFileBuilt = TRUE;
  987. return S_OK;
  988. }
  989. // copy 'Options' settings to ST varset
  990. hr = pVarSet->raw_getReference(key,&pVarSetTemp);
  991. if ( SUCCEEDED(hr) )
  992. {
  993. pVarSetST->ImportSubTree(key,pVarSetTemp);
  994. }
  995. // copy 'Accounts' settings to ST varset
  996. key = GET_BSTR(DCTVS_Accounts);
  997. hr = pVarSet->raw_getReference(key,&pVarSetTemp);
  998. if ( SUCCEEDED(hr) )
  999. {
  1000. pVarSetST->ImportSubTree(key,pVarSetTemp);
  1001. }
  1002. pVarSetST->put(GET_BSTR(DCTVS_Security_TranslationMode),GET_BSTR(IDS_Replace));
  1003. pVarSetST->put(GET_BSTR(DCTVS_Options_NoChange),GET_BSTR(IDS_YES));
  1004. pVarSetST->put(GET_BSTR(DCTVS_Options_LogLevel),(LONG)0);
  1005. pVarSetST->put(GET_BSTR(DCTVS_Security_BuildCacheFile),resultPath);
  1006. // change the log file - the building of the cache file happens behind the scenes
  1007. // so we won't put it in the regular log file because it would cause confusion
  1008. swprintf(logfile,L"%s%s",tempdir,L"BuildCacheFileLog.txt");
  1009. pVarSetST->put(GET_BSTR(DCTVS_Options_Logfile),logfile);
  1010. //are we using a sID mapping file to perform security translation
  1011. pVarSetST->put(GET_BSTR(DCTVS_AccountOptions_SecurityInputMOT),
  1012. pVarSet->get(GET_BSTR(DCTVS_AccountOptions_SecurityInputMOT)));
  1013. pVarSetST->put(GET_BSTR(DCTVS_AccountOptions_SecurityMapFile),
  1014. pVarSet->get(GET_BSTR(DCTVS_AccountOptions_SecurityMapFile)));
  1015. MCSEADCTAGENTLib::IDCTAgentPtr pAgent(MCSEADCTAGENTLib::CLSID_DCTAgent);
  1016. try {
  1017. if ( pAgent == NULL )
  1018. return E_FAIL;
  1019. _bstr_t jobID;
  1020. BSTR b = NULL;
  1021. hr = pAgent->raw_SubmitJob(pVarSetST,&b);
  1022. if ( SUCCEEDED(hr) )
  1023. {
  1024. jobID = b;
  1025. IVarSetPtr pVarSetStatus; // used to retrieve status of running job
  1026. _bstr_t jobStatus;
  1027. IUnknown * pUnk;
  1028. // loop until the agent is finished
  1029. do {
  1030. Sleep(1000);
  1031. hr = pAgent->QueryJobStatus(jobID,&pUnk);
  1032. if ( SUCCEEDED(hr) )
  1033. {
  1034. pVarSetStatus = pUnk;
  1035. jobStatus = pVarSetStatus->get(GET_BSTR(DCTVS_JobStatus));
  1036. pUnk->Release();
  1037. }
  1038. else
  1039. {
  1040. break;
  1041. }
  1042. } while ( UStrICmp(jobStatus,GET_STRING(IDS_DCT_Status_Completed)) );
  1043. }
  1044. }
  1045. catch(...)
  1046. {
  1047. hr = E_FAIL;
  1048. }
  1049. if ( SUCCEEDED(hr) )
  1050. {
  1051. pVarSet->put(GET_BSTR(DCTVS_Accounts_InputFile),GET_BSTR(IDS_CACHE_FILE_NAME));
  1052. pVarSet->put(GET_BSTR(DCTVS_Accounts_WildcardSpec),"");
  1053. err.MsgWrite(0,DCT_MSG_CACHE_FILE_BUILT_S,(WCHAR*)GET_STRING(IDS_CACHE_FILE_NAME));
  1054. gbCacheFileBuilt = TRUE;
  1055. }
  1056. return hr;
  1057. }
  1058. // These are TNodeListSortable sorting functions
  1059. int ServerNodeCompare(TNode const * t1,TNode const * t2)
  1060. {
  1061. TServerNode * n1 = (TServerNode *)t1;
  1062. TServerNode * n2 = (TServerNode *)t2;
  1063. return UStrICmp(n1->SourceName(),n2->SourceName());
  1064. }
  1065. int ServerValueCompare(TNode const * t1, void const * val)
  1066. {
  1067. TServerNode * n1 = (TServerNode *)t1;
  1068. WCHAR const * name = (WCHAR const *) val;
  1069. return UStrICmp(n1->SourceName(),name);
  1070. }
  1071. // MergeServerList combines the security translation server list in Servers.* with the computer migration
  1072. // server list in MigrateServers.*
  1073. // The combined list is stored in the varset under Servers.* with subkeys specifying which actions to take for
  1074. // each computer
  1075. void
  1076. CDCTDispatcher::MergeServerList(
  1077. IVarSet * pVarSet // in - varset containing list of servers to migrate and translate on
  1078. )
  1079. {
  1080. int ndx = 0;
  1081. WCHAR key[1000];
  1082. _bstr_t text;
  1083. int lastndx = -1;
  1084. long totalsrvs;
  1085. //get the number of servers in the varset
  1086. totalsrvs = pVarSet->get(GET_BSTR(DCTVS_Servers_NumItems));
  1087. // if there are computers being migrated
  1088. if (totalsrvs > 0)
  1089. {
  1090. //add code to move varset server entries, with SkipDispatch set, to the bottom
  1091. //of the server list and decrease the number of server items by each server
  1092. //to be skipped
  1093. //check each server in the list moving all to be skipped to the end of the list and
  1094. //decreasing the server count for each to be skipped
  1095. for (ndx = 0; ndx < totalsrvs; ndx++)
  1096. {
  1097. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_SkipDispatch_D),ndx);
  1098. text = pVarSet->get(key);
  1099. //if the server is not to be skipped, we may have to move it above
  1100. //a server that is being skipped
  1101. if (!UStrICmp(text,GET_STRING(IDS_No)))
  1102. {
  1103. //if the last server looked at is not being skipped then we don't
  1104. //need to swap any servers in the list and we can increment the
  1105. //last server not being skipped
  1106. if (lastndx == (ndx - 1))
  1107. {
  1108. lastndx = ndx;
  1109. }
  1110. else //else swap servers in the varset so skipped server comes after
  1111. { //the one not being skipped
  1112. _bstr_t tempName, tempNewName, tempChngDom, tempReboot, tempMigOnly;
  1113. long tempRebootDelay;
  1114. _bstr_t skipName, skipNewName, skipChngDom, skipReboot, skipMigOnly;
  1115. long skipRebootDelay;
  1116. lastndx++; //move to the skipped server that we will swap with
  1117. //copy skipped server's values to temp
  1118. swprintf(key,GET_STRING(DCTVSFmt_Servers_D),lastndx);
  1119. skipName = pVarSet->get(key);
  1120. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RenameTo_D),lastndx);
  1121. skipNewName = pVarSet->get(key);
  1122. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_ChangeDomain_D),lastndx);
  1123. skipChngDom = pVarSet->get(key);
  1124. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_Reboot_D),lastndx);
  1125. skipReboot = pVarSet->get(key);
  1126. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_MigrateOnly_D),lastndx);
  1127. skipMigOnly = pVarSet->get(key);
  1128. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RebootDelay_D),lastndx);
  1129. skipRebootDelay = pVarSet->get(key);
  1130. //copy current, non-skipped, server valuesto second temp
  1131. swprintf(key,GET_STRING(DCTVSFmt_Servers_D),ndx);
  1132. tempName = pVarSet->get(key);
  1133. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RenameTo_D),ndx);
  1134. tempNewName = pVarSet->get(key);
  1135. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_ChangeDomain_D),ndx);
  1136. tempChngDom = pVarSet->get(key);
  1137. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_Reboot_D),ndx);
  1138. tempReboot = pVarSet->get(key);
  1139. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_MigrateOnly_D),ndx);
  1140. tempMigOnly = pVarSet->get(key);
  1141. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RebootDelay_D),ndx);
  1142. tempRebootDelay = pVarSet->get(key);
  1143. //place current server's values in place of values for the one
  1144. //being skipped
  1145. swprintf(key,GET_STRING(DCTVSFmt_Servers_D),lastndx);
  1146. pVarSet->put(key,tempName);
  1147. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RenameTo_D),lastndx);
  1148. pVarSet->put(key,tempNewName);
  1149. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_ChangeDomain_D),lastndx);
  1150. pVarSet->put(key,tempChngDom);
  1151. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_Reboot_D),lastndx);
  1152. pVarSet->put(key,tempReboot);
  1153. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_MigrateOnly_D),lastndx);
  1154. pVarSet->put(key,tempMigOnly);
  1155. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RebootDelay_D),lastndx);
  1156. pVarSet->put(key,tempRebootDelay);
  1157. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_SkipDispatch_D),lastndx);
  1158. pVarSet->put(key,GET_BSTR(IDS_No));
  1159. //place skipped server's values in place of values for current server
  1160. swprintf(key,GET_STRING(DCTVSFmt_Servers_D),ndx);
  1161. pVarSet->put(key,skipName);
  1162. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RenameTo_D),ndx);
  1163. pVarSet->put(key,skipNewName);
  1164. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_ChangeDomain_D),ndx);
  1165. pVarSet->put(key,skipChngDom);
  1166. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_Reboot_D),ndx);
  1167. pVarSet->put(key,skipReboot);
  1168. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_MigrateOnly_D),ndx);
  1169. pVarSet->put(key,skipMigOnly);
  1170. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_RebootDelay_D),ndx);
  1171. pVarSet->put(key,skipRebootDelay);
  1172. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_SkipDispatch_D),ndx);
  1173. pVarSet->put(key,GET_BSTR(IDS_YES));
  1174. }//end else need to swap with skipped server
  1175. }//end if not skipping dispatch for this server
  1176. }//end for each server in the server list
  1177. //exclude servers to be skipped for dispatch from being included in the server count
  1178. pVarSet->put(GET_BSTR(DCTVS_Servers_NumItems),(long)++lastndx);
  1179. }
  1180. }
  1181. STDMETHODIMP // ret- HRESULT
  1182. CDCTDispatcher::Process(
  1183. IUnknown * pWorkItem, // in - varset containing job information and list of servers
  1184. IUnknown ** ppResponse, // out- not used
  1185. UINT * pDisposition // out- not used
  1186. )
  1187. {
  1188. // initialize output parameters
  1189. if ( ppResponse )
  1190. {
  1191. (*ppResponse) = NULL;
  1192. }
  1193. HRESULT hr = S_OK;
  1194. IVarSetPtr pVarSetIn = pWorkItem;
  1195. LONG nThreads;
  1196. WCHAR key[100];
  1197. _bstr_t serverName;
  1198. LONG nServers = 0;
  1199. _bstr_t log;
  1200. _bstr_t useTempCredentials;
  1201. BOOL bFatalError = FALSE;
  1202. _bstr_t text;
  1203. WCHAR debugLog[MAX_PATH];
  1204. long bAppend = 0;
  1205. _bstr_t skip;
  1206. _bstr_t sWizard, text2;
  1207. BOOL bSkipSourceSid;
  1208. if ( DumpDebugInfo(debugLog) )
  1209. {
  1210. if ( pVarSetIn != NULL )
  1211. {
  1212. // temporarily remove the password fromthe varset, so that we don't write it to the file
  1213. _bstr_t password = pVarSetIn->get(GET_BSTR(DCTVS_Options_Credentials_Password));
  1214. pVarSetIn->put(GET_BSTR(DCTVS_Options_Credentials_Password),L"");
  1215. pVarSetIn->DumpToFile(debugLog);
  1216. pVarSetIn->put(GET_BSTR(DCTVS_Options_Credentials_Password),password);
  1217. }
  1218. }
  1219. //get the wizard being run
  1220. sWizard = pVarSetIn->get(GET_BSTR(DCTVS_Options_Wizard));
  1221. text2 = pVarSetIn->get(GET_BSTR(DCTVS_AccountOptions_SecurityInputMOT));
  1222. if ((!UStrICmp(sWizard, L"security")) && (!UStrICmp(text2,GET_STRING(IDS_YES))))
  1223. bSkipSourceSid = TRUE;
  1224. else
  1225. bSkipSourceSid = FALSE;
  1226. nThreads = pVarSetIn->get(GET_BSTR(DCTVS_Options_MaxThreads));
  1227. log = pVarSetIn->get(GET_BSTR(DCTVS_Options_DispatchCSV));
  1228. errLog.LogOpen((WCHAR*)log,0);
  1229. text = pVarSetIn->get(GET_BSTR(DCTVS_Options_AppendToLogs));
  1230. if (! UStrICmp(text,GET_STRING(IDS_YES)) )
  1231. {
  1232. bAppend = 1;
  1233. }
  1234. log = pVarSetIn->get(GET_BSTR(DCTVS_Options_DispatchLog));
  1235. err.LogOpen((WCHAR*)log,bAppend);
  1236. errLog.DbgMsgWrite(0,L"%ls",(WCHAR*)log);
  1237. // default to 20 threads if the client doesn't specify
  1238. if ( ! nThreads )
  1239. {
  1240. nThreads = 20;
  1241. }
  1242. // set credentials used to access share
  1243. _bstr_t strAdmtAccountDomain;
  1244. _bstr_t strAdmtAccountUserName;
  1245. _bstr_t strAdmtAccountPassword;
  1246. hr = GetOptionsCredentials(strAdmtAccountDomain, strAdmtAccountUserName, strAdmtAccountPassword);
  1247. pVarSetIn->put(GET_BSTR(DCTVS_Options_Credentials_Domain), _variant_t(strAdmtAccountDomain));
  1248. pVarSetIn->put(GET_BSTR(DCTVS_Options_Credentials_UserName), _variant_t(strAdmtAccountUserName));
  1249. pVarSetIn->put(GET_BSTR(DCTVS_Options_Credentials_Password), _variant_t(strAdmtAccountPassword));
  1250. if (FAILED(hr))
  1251. {
  1252. err.SysMsgWrite(ErrE, HRESULT_CODE(hr), DCT_MSG_COULDNT_GET_OPTIONS_CREDENTIALS);
  1253. }
  1254. // Get the name of the local computer
  1255. DWORD dim = DIM(gComputerName);
  1256. GetComputerName(gComputerName,&dim);
  1257. m_pThreadPool = new TJobDispatcher(nThreads);
  1258. if (!m_pThreadPool)
  1259. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  1260. hr = ShareResultDirectory(pVarSetIn);
  1261. if ( FAILED(hr) )
  1262. {
  1263. err.SysMsgWrite(ErrE,HRESULT_CODE(hr),DCT_MSG_RESULT_SHARE_CREATION_FAILED);
  1264. bFatalError = TRUE;
  1265. }
  1266. // Build an input file for the ST cache, to send to each server
  1267. hr = BuildInputFile(pVarSetIn);
  1268. if ( FAILED(hr) )
  1269. {
  1270. err.SysMsgWrite(ErrE,HRESULT_CODE(hr),DCT_MSG_CACHE_CONSTRUCTION_FAILED);
  1271. bFatalError = TRUE;
  1272. }
  1273. // Split out the remotable tasks for each server
  1274. // Get the sids for the source and target domains
  1275. PSID pSidSrc = NULL;
  1276. PSID pSidTgt = NULL;
  1277. _bstr_t source = pVarSetIn->get(GET_BSTR(DCTVS_Options_SourceDomain));
  1278. _bstr_t target = pVarSetIn->get(GET_BSTR(DCTVS_Options_TargetDomain));
  1279. //if security translation, retrieve source sid and convert so
  1280. //that it can be convert back below
  1281. if (bSkipSourceSid)
  1282. {
  1283. _bstr_t sSid = pVarSetIn->get(GET_BSTR(DCTVS_Options_SourceDomainSid));
  1284. pSidSrc = SidFromString((WCHAR*)sSid);
  1285. }
  1286. else //else get the sid now
  1287. GetSidForDomain((WCHAR*)source,&pSidSrc);
  1288. GetSidForDomain((WCHAR*)target,&pSidTgt);
  1289. if ( pSidSrc && pSidTgt )
  1290. {
  1291. WCHAR txtSid[200];
  1292. DWORD lenTxt = DIM(txtSid);
  1293. if ( GetTextualSid(pSidSrc,txtSid,&lenTxt) )
  1294. {
  1295. pVarSetIn->put(GET_BSTR(DCTVS_Options_SourceDomainSid),txtSid);
  1296. }
  1297. lenTxt = DIM(txtSid);
  1298. if ( GetTextualSid(pSidTgt,txtSid,&lenTxt) )
  1299. {
  1300. pVarSetIn->put(GET_BSTR(DCTVS_Options_TargetDomainSid),txtSid);
  1301. }
  1302. FreeSid(pSidSrc);
  1303. FreeSid(pSidTgt);
  1304. }
  1305. /* if ( pSidSrc )
  1306. {
  1307. WCHAR txtSid[200];
  1308. DWORD lenTxt = DIM(txtSid);
  1309. if ( GetTextualSid(pSidSrc,txtSid,&lenTxt) )
  1310. {
  1311. pVarSetIn->put(GET_BSTR(DCTVS_Options_SourceDomainSid),txtSid);
  1312. }
  1313. }
  1314. if ( pSidTgt )
  1315. {
  1316. WCHAR txtSid[200];
  1317. DWORD lenTxt = DIM(txtSid);
  1318. if ( GetTextualSid(pSidTgt,txtSid,&lenTxt) )
  1319. {
  1320. pVarSetIn->put(GET_BSTR(DCTVS_Options_TargetDomainSid),txtSid);
  1321. }
  1322. }
  1323. */ else
  1324. // if (!((pSidSrc) && (pSidTgt)))
  1325. {
  1326. // if ((source.length()) && (!pSidSrc) && (!bSkipSourceSid))
  1327. if ( source.length() && ! pSidSrc )
  1328. {
  1329. err.MsgWrite(ErrE,DCT_MSG_DOMAIN_SID_NOT_FOUND_S,(WCHAR*)source);
  1330. bFatalError = TRUE;
  1331. }
  1332. else if ( target.length() && ! pSidTgt )
  1333. {
  1334. err.MsgWrite(ErrE,DCT_MSG_DOMAIN_SID_NOT_FOUND_S,(WCHAR*)target);
  1335. bFatalError = TRUE;
  1336. }
  1337. }
  1338. MergeServerList(pVarSetIn);
  1339. LONG nServerCount = pVarSetIn->get(GET_BSTR(DCTVS_Servers_NumItems));
  1340. if ( nServerCount && ! bFatalError )
  1341. {
  1342. err.MsgWrite(0,DCT_MSG_DISPATCH_SERVER_COUNT_D,nServerCount);
  1343. }
  1344. errLog.DbgMsgWrite(0,L"%ld",nServerCount);
  1345. TNodeList * fileList = new TNodeList;
  1346. if (!fileList)
  1347. {
  1348. delete m_pThreadPool;
  1349. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  1350. }
  1351. // Build list of files to install for plug-ins (if any)
  1352. BuildPlugInFileList(fileList,pVarSetIn);
  1353. // Make a copy of the varset with the server lists removed,
  1354. // so we don't have to copy the entire server list for each agent
  1355. gCS.Enter();
  1356. IVarSet * pTemp = NULL;
  1357. IVarSetPtr pVarSetTemp(CLSID_VarSet);
  1358. hr = pVarSetTemp->ImportSubTree(_bstr_t(L""),pVarSetIn);
  1359. if ( SUCCEEDED(hr) )
  1360. {
  1361. hr = pVarSetTemp->raw_getReference(SysAllocString(L"MigrateServers"),&pTemp);
  1362. if ( SUCCEEDED(hr) )
  1363. {
  1364. pTemp->Clear();
  1365. pTemp->Release();
  1366. pTemp = NULL;
  1367. }
  1368. hr = pVarSetTemp->raw_getReference(SysAllocString(L"Servers"),&pTemp);
  1369. if ( SUCCEEDED(hr) )
  1370. {
  1371. pTemp->Clear();
  1372. pTemp->Release();
  1373. pTemp = NULL;
  1374. }
  1375. }
  1376. else
  1377. {
  1378. bFatalError = TRUE;
  1379. }
  1380. gCS.Leave();
  1381. m_startFailedVector.clear();
  1382. do
  1383. {
  1384. if ( bFatalError )
  1385. {
  1386. break;
  1387. }
  1388. swprintf(key,GET_STRING(IDS_DCTVSFmt_Servers_SkipDispatch_D),nServers);
  1389. skip = pVarSetIn->get(key);
  1390. swprintf(key,GET_STRING(DCTVSFmt_Servers_D),nServers);
  1391. serverName = pVarSetIn->get(key);
  1392. if ((serverName.length()) && (UStrICmp(skip,GET_STRING(IDS_YES))))
  1393. {
  1394. IVarSetPtr pVS(CLSID_VarSet);
  1395. InstallJobInfo * pInfo = new InstallJobInfo;
  1396. if (!pInfo)
  1397. {
  1398. delete fileList;
  1399. delete m_pThreadPool;
  1400. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  1401. }
  1402. if ( pVS == NULL )
  1403. {
  1404. return E_FAIL;
  1405. }
  1406. UStrCpy(key+UStrLen(key),L".JobFile");
  1407. _bstr_t file = pVarSetIn->get(key);
  1408. // Set up job structure
  1409. pInfo->pVarSetList = pVarSetIn;
  1410. pInfo->pVarSet = pVarSetTemp;
  1411. pInfo->serverName = serverName;
  1412. pInfo->ndx = nServers;
  1413. pInfo->pPlugInFileList = fileList;
  1414. pInfo->pStartFailedVector = &m_startFailedVector;
  1415. pInfo->pFailureDescVector = &m_failureDescVector;
  1416. pInfo->pStartedVector = &m_startedVector;
  1417. pInfo->pJobidVector = &m_jobidVector;
  1418. pInfo->hMutex = m_hMutex;
  1419. pInfo->nErrCount = 0;
  1420. if ( file.length() )
  1421. {
  1422. pInfo->jobfile = file;
  1423. }
  1424. err.MsgWrite(0,DCT_MSG_DISPATCHING_TO_SERVER_S,(WCHAR*)pInfo->serverName);
  1425. errLog.DbgMsgWrite(0,L"%ls\t%ls\t%ld",(WCHAR*)pInfo->serverName,L"WillInstall",0);
  1426. m_pThreadPool->SubmitJob(&DoInstall,(void *)pInfo);
  1427. }
  1428. nServers++;
  1429. if ( nServers == nServerCount )
  1430. break;
  1431. } while ( serverName.length() );
  1432. // launch a thread to wait for all jobs to finish, then clean up and exit
  1433. WaitInfo* wInfo = new WaitInfo;
  1434. if (!wInfo)
  1435. {
  1436. delete fileList;
  1437. delete m_pThreadPool;
  1438. return HRESULT_FROM_WIN32(ERROR_NOT_ENOUGH_MEMORY);
  1439. }
  1440. wInfo->ppPool = &m_pThreadPool;
  1441. wInfo->pUnknown = NULL;
  1442. wInfo->pPlugInFileList = fileList;
  1443. QueryInterface(IID_IUnknown,(LPVOID*)&(wInfo->pUnknown));
  1444. DWORD id = 0;
  1445. HANDLE waitHandle = CreateThread(NULL,0,&Wait,(void *)wInfo,0,&id);
  1446. CloseHandle(waitHandle);
  1447. return hr;
  1448. }
  1449. STDMETHODIMP CDCTDispatcher::AllAgentsStarted(long *bAllAgentsStarted)
  1450. {
  1451. *bAllAgentsStarted = m_pThreadPool == NULL;
  1452. return S_OK;
  1453. }
  1454. SAFEARRAY* MakeSafeArray(std::vector<CComBSTR>& stVector)
  1455. {
  1456. SAFEARRAYBOUND rgsabound[1];
  1457. rgsabound[0].lLbound = 0;
  1458. rgsabound[0].cElements = stVector.size();
  1459. SAFEARRAY FAR* psa = SafeArrayCreate(VT_BSTR, 1, rgsabound);
  1460. std::vector<CComBSTR>::iterator iter = stVector.begin();
  1461. for(long i=0; iter != stVector.end(); ++iter, ++i)
  1462. {
  1463. _ASSERTE(*iter && *iter != L"");
  1464. SafeArrayPutElement(psa, &i, (void*)(*iter).Copy());
  1465. }
  1466. stVector.clear();
  1467. return psa;
  1468. }
  1469. STDMETHODIMP CDCTDispatcher::GetStartedAgentsInfo(long* bAllAgentsStarted, SAFEARRAY** ppbstrStartedAgents, SAFEARRAY** ppbstrJobid, SAFEARRAY** ppbstrFailedAgents, SAFEARRAY** ppbstrFailureDesc)
  1470. {
  1471. *bAllAgentsStarted = m_pThreadPool == NULL;
  1472. // DWORD res = ::WaitForSingleObject(m_hMutex, 30000);
  1473. ::WaitForSingleObject(m_hMutex, 30000);
  1474. *ppbstrFailedAgents = MakeSafeArray(m_startFailedVector);
  1475. *ppbstrFailureDesc = MakeSafeArray(m_failureDescVector);
  1476. _ASSERTE(m_startedVector.size() == m_jobidVector.size());
  1477. *ppbstrStartedAgents = MakeSafeArray(m_startedVector);
  1478. *ppbstrJobid = MakeSafeArray(m_jobidVector);
  1479. ::ReleaseMutex(m_hMutex);
  1480. return S_OK;
  1481. }