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.

783 lines
20 KiB

  1. // Interfaces.cpp : Implementation of TSUserExInterfaces class.
  2. #include "stdafx.h"
  3. #if 1 //POST_BETA_3
  4. #include <sspi.h>
  5. #include <secext.h>
  6. #include <dsgetdc.h>
  7. #endif //POST_BETA_3
  8. #include "tsuserex.h"
  9. //#include "ConfigDlg.h" // for CTSUserProperties
  10. #include "tsusrsht.h"
  11. #include "Interfaces.h"
  12. //#include "logmsg.h"
  13. #include "limits.h" // USHRT_MAX
  14. #ifdef _RTM_
  15. #include <ntverp.h> // VER_PRODUCTVERSION_DW
  16. #endif
  17. #include <winsta.h>
  18. //#include "ntdsapi.h" // enbable for "having some fun macro"
  19. // clipboard format to retreive the machine name and account name associated
  20. // with a data object created by local user manager
  21. #define CCF_LOCAL_USER_MANAGER_MACHINE_NAME TEXT("Local User Manager Machine Focus Name")
  22. #define ByteOffset(base, offset) (((LPBYTE)base)+offset)
  23. BOOL g_bPagesHaveBeenInvoked = FALSE;
  24. /////////////////////////////////////////////////////////////////////////////
  25. // IExtendPropertySheet implementation
  26. HRESULT GetMachineAndUserName(IDataObject *pDataObject, LPWSTR pMachineName, LPWSTR pUserName , PBOOL pbDSAType , PSID *ppUserSid )
  27. {
  28. ASSERT_(pUserName);
  29. ASSERT_(pMachineName);
  30. ASSERT_(pDataObject != NULL );
  31. // register the display formats.
  32. // first 2 formats supported by local user manager snapin
  33. static UINT s_cfMachineName = RegisterClipboardFormat(CCF_LOCAL_USER_MANAGER_MACHINE_NAME);
  34. static UINT s_cfDisplayName = RegisterClipboardFormat(CCF_DISPLAY_NAME);;
  35. static UINT s_cfDsObjectNames = RegisterClipboardFormat(CFSTR_DSOBJECTNAMES); // this format is supported by dsadmin snapin.
  36. ASSERT_(s_cfMachineName);
  37. ASSERT_(s_cfDisplayName);
  38. ASSERT_(s_cfDsObjectNames);
  39. FORMATETC fmte = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL};
  40. STGMEDIUM medium = { TYMED_HGLOBAL, NULL, NULL };
  41. HRESULT hr = S_OK;
  42. ASSERT_(USHRT_MAX > s_cfDsObjectNames);
  43. // first we will try dsdataobject format. This means we are running in context of dsadmin
  44. fmte.cfFormat = ( USHORT )s_cfDsObjectNames;
  45. hr = pDataObject->GetData(&fmte, &medium);
  46. if( SUCCEEDED( hr ) )
  47. {
  48. // CFSTR_DSOBJECTNAMES is supported.
  49. // It means we are dealing with dsadmin
  50. // lets get username, and domain name from the dsadmin.
  51. LPDSOBJECTNAMES pDsObjectNames = (LPDSOBJECTNAMES)medium.hGlobal;
  52. *pbDSAType = TRUE;
  53. if( pDsObjectNames->cItems < 1 )
  54. {
  55. ODS( L"TSUSEREX : @GetMachineAndUserName DS Object names < 1\n" );
  56. return E_FAIL;
  57. }
  58. LPWSTR pwszObjName = ( LPWSTR )ByteOffset( pDsObjectNames , pDsObjectNames->aObjects[0].offsetName );
  59. KdPrint( ( "TSUSEREX : adspath is %ws\n" , pwszObjName ) );
  60. // first stage get the server name from the adspath
  61. // since IADsPathname does not live off a normal IADs Directory object
  62. // so me must cocreate the object set the path and then retrieve the server name
  63. // hey this saves us wire-tripping
  64. // IADsPathname *pPathname = NULL;
  65. IADsObjectOptions *pADsOptions = NULL;
  66. IADs *pADs = NULL;
  67. hr = ADsGetObject( pwszObjName, IID_IADs, (void**)&pADs );
  68. if( FAILED( hr ) )
  69. {
  70. KdPrint( ( "TSUSEREX : no means of binding to adspath -- hresult = 0x%x\n" , hr ) );
  71. return hr;
  72. }
  73. VARIANT varServerName;
  74. VariantInit(&varServerName);
  75. hr = pADs->QueryInterface( IID_IADsObjectOptions , ( void ** )&pADsOptions );
  76. KdPrint( ( "TSUSEREX : binded to adsobject queried for IID_IADsObjectOptions returned 0x%x\n" , hr ) );
  77. if( SUCCEEDED( hr ) )
  78. {
  79. hr = pADsOptions->GetOption( ADS_OPTION_SERVERNAME, &varServerName);
  80. pADsOptions->Release( );
  81. KdPrint( ( "TSUSEREX: GetOption returned 0x%x\n" , hr ) ) ;
  82. }
  83. if( SUCCEEDED( hr ) )
  84. {
  85. lstrcpy( pMachineName , V_BSTR( &varServerName ) );
  86. KdPrint( ( "TSUSEREX: Server name is %ws\n" , pMachineName ) ) ;
  87. }
  88. VariantClear( &varServerName );
  89. if( FAILED( hr ) )
  90. {
  91. // ADS_FORMAT_SERVER is not supported this could mean we're dealing with an WinNT format
  92. // or a DS Provider that is poorly implemented
  93. KdPrint( ( "IADsPathname could not obtain server name 0x%x\n" , hr ) );
  94. // let's go wire tapping to get the server name
  95. VARIANT v;
  96. LPTSTR szDName = NULL;
  97. ULONG ulDName = 0;
  98. VariantInit(&v);
  99. hr = pADs->Get(L"distinguishedName", &v);
  100. if( FAILED( hr ) )
  101. {
  102. KdPrint( ( "TSUSEREX : pADs->Get( DN ) returned 0x%x\n", hr ) );
  103. return hr;
  104. }
  105. ASSERT_( V_VT( &v ) == VT_BSTR );
  106. if( !TranslateNameW( V_BSTR(&v), NameFullyQualifiedDN, NameCanonical, szDName, &ulDName) )
  107. {
  108. KdPrint( ( "TSUSEREX : TranslateNameW failed with 0x%x\n", GetLastError( ) ) );
  109. return E_FAIL;
  110. }
  111. szDName = ( LPTSTR )new TCHAR[ ulDName + 1 ];
  112. if( szDName == NULL )
  113. {
  114. KdPrint( ( "TSUSEREX : could not allocate space for szDName\n" ) );
  115. return E_OUTOFMEMORY;
  116. }
  117. if( !TranslateNameW( V_BSTR(&v), NameFullyQualifiedDN, NameCanonical, szDName, &ulDName) )
  118. {
  119. KdPrint( ( "TSUSEREX : TranslateNameW failed 2nd pass with 0x%x\n", GetLastError( ) ) );
  120. delete[] szDName;
  121. return E_FAIL;
  122. }
  123. // perform LEFT$( szDName , up to '/' )
  124. KdPrint( ( "TSUSEREX : TranslateNameW cracked the name to %ws\n" , szDName ) );
  125. LPTSTR pszTemp = szDName;
  126. while( pszTemp != NULL )
  127. {
  128. if( *pszTemp == L'/' )
  129. {
  130. *pszTemp = 0;
  131. break;
  132. }
  133. pszTemp++;
  134. }
  135. KdPrint( ("TranslateName with my LEFT$ returned %ws\n",szDName ) );
  136. // get the domaincontroller name of the remote machine
  137. DOMAIN_CONTROLLER_INFO *pdinfo;
  138. DWORD dwStatus = DsGetDcName( NULL , szDName , NULL , NULL , 0 , &pdinfo );
  139. KdPrint( ( "TSUSEREX : DsGetDcName: %ws returned 0x%x\n", pdinfo->DomainControllerName , dwStatus ) );
  140. if( dwStatus == NO_ERROR )
  141. {
  142. lstrcpy( pMachineName , pdinfo->DomainControllerName );
  143. NetApiBufferFree( pdinfo );
  144. }
  145. if( szDName != NULL )
  146. {
  147. delete[] szDName;
  148. }
  149. VariantClear( &v );
  150. } // END else
  151. pADs->Release( );
  152. IADsUser *pADsUser = NULL;
  153. hr = ADsGetObject( pwszObjName, IID_IADsUser, (void**)&pADsUser);
  154. if( FAILED( hr ) )
  155. {
  156. KdPrint( ( "TSUSEREX: ADsGetObject failed to get the user object 0x%x\n",hr ) );
  157. return hr;
  158. }
  159. VARIANT var;
  160. VARIANT varSid;
  161. VariantInit(&var);
  162. VariantInit(&varSid);
  163. hr = pADsUser->Get(L"ObjectSid", &varSid);
  164. if( FAILED( hr ) )
  165. {
  166. ODS( L"TSUSEREX : IADsUser::Get( ObjectSid ) failed \n" );
  167. return hr;
  168. }
  169. if( !( varSid.vt & VT_ARRAY) )
  170. {
  171. ODS( L"TSUSEREX : Object SID is not a VT_ARRAY\n" );
  172. return E_FAIL;
  173. }
  174. PSID pSid = NULL;
  175. PSID pUserSid = NULL;
  176. SafeArrayAccessData( varSid.parray, &pSid );
  177. if( !IsValidSid( pSid ) )
  178. {
  179. ODS( L"TSUSEREX : pSid is invalid\n" );
  180. return E_FAIL;
  181. }
  182. DWORD dwSidSize = GetLengthSid( pSid );
  183. pUserSid = new BYTE[ dwSidSize ];
  184. if( pUserSid == NULL )
  185. {
  186. ODS( L"TSUSEREX : failed to allocate pUserSid\n" );
  187. return E_FAIL;
  188. }
  189. CopySid( dwSidSize , pUserSid , pSid );
  190. *ppUserSid = pUserSid;
  191. SafeArrayUnaccessData( varSid.parray );
  192. VariantClear( &varSid );
  193. hr = pADsUser->Get( L"samAccountName" , &var );
  194. pADsUser->Release();
  195. if( FAILED( hr ) )
  196. {
  197. KdPrint( ( "TSUSEREX : ADsUser::Get( name ) failed 0x%x\n", hr ) );
  198. return hr;
  199. }
  200. ASSERT_( V_VT( &var ) == VT_BSTR );
  201. lstrcpy( pUserName , V_BSTR( &var ) );
  202. KdPrint( ( "TSUSEREX : Server name %ws user name is %ws\n" , pMachineName , pUserName ) );
  203. VariantClear( &var );
  204. ReleaseStgMedium(&medium);
  205. }
  206. else
  207. {
  208. // CFSTR_DSOBJECTNAMES is NOT supported.
  209. // It means we are dealing with local user manager.
  210. // we must be able to get
  211. // Allocate medium for GetDataHere.
  212. medium.hGlobal = GlobalAlloc(GMEM_SHARE, MAX_PATH * sizeof(WCHAR));
  213. if( !medium.hGlobal )
  214. {
  215. ODS( L"TSUSEREX : GlobalAlloc failed in GetMachineAndUserName\n" );
  216. return E_OUTOFMEMORY;
  217. }
  218. *pbDSAType = FALSE;
  219. // since we are doing data conversion.
  220. // check for possible data loss.
  221. ASSERT_(USHRT_MAX > s_cfMachineName);
  222. // request the machine name from the dataobject.
  223. fmte.cfFormat = (USHORT)s_cfMachineName;
  224. hr = pDataObject->GetDataHere(&fmte, &medium);
  225. if( FAILED( hr ) )
  226. {
  227. ODS( L"TSUSEREX : @GetMachineAndUserName GetDataHere for s_cfMachineName failed\n" );
  228. return hr;
  229. }
  230. // copy the machine name into our buffer
  231. if( ( LPWSTR )medium.hGlobal != NULL && pMachineName != NULL )
  232. {
  233. wcscpy(pMachineName, (LPWSTR)medium.hGlobal );
  234. }
  235. // administer local accounts only for Terminal Servers
  236. SERVER_INFO_101 *psi101;
  237. HANDLE hTServer = NULL;
  238. DWORD dwNetStatus = NetServerGetInfo( pMachineName , 101 , ( LPBYTE * )&psi101 );
  239. if( dwNetStatus != NERR_Success )
  240. {
  241. KdPrint( ( "TSUSEREX:GetMachineAndUserName -- NetServerGetInfo failed with 0x%x\n", dwNetStatus ) );
  242. return E_FAIL;
  243. }
  244. if( psi101 == NULL )
  245. {
  246. KdPrint( ( "TSUSEREX:GetMachineAndUserName -- NetServerGetInfo failed getting sinfo101 0x%x\n",dwNetStatus ) );
  247. return E_FAIL;
  248. }
  249. KdPrint( ("TSUSEREX:NetServerGetInfo server bits returnd 0x%x and nttype is 0x%x\n", psi101->sv101_type , SV_TYPE_SERVER_NT ) );
  250. BOOL fServer = ( BOOL )( psi101->sv101_type & ( DWORD )SV_TYPE_SERVER_NT );
  251. NetApiBufferFree( ( LPVOID )psi101 );
  252. if( !fServer )
  253. {
  254. KdPrint( ( "TSUSEREX : viewing local account on non-TS ( exiting )\n" ) );
  255. return E_FAIL;
  256. }
  257. hTServer = WinStationOpenServer( pMachineName );
  258. if( hTServer == NULL )
  259. {
  260. KdPrint( ( "TSUSEREX: This OS does not support terminal services\n" ) ) ;
  261. return E_FAIL;
  262. }
  263. WinStationCloseServer( hTServer );
  264. // since we are doing data conversion.
  265. // check for possible data loss.
  266. ASSERT_(USHRT_MAX > s_cfDisplayName);
  267. // request data about user name.
  268. fmte.cfFormat = (USHORT)s_cfDisplayName;
  269. hr = pDataObject->GetDataHere( &fmte , &medium );
  270. if( FAILED( hr ) )
  271. {
  272. ODS( L"TSUSEREX : @GetMachineAndUserName GetDataHere for s_cfDisplayName failed\n" );
  273. return hr;
  274. }
  275. // copy the user name into our buffer and release the medium.
  276. if( ( LPWSTR )medium.hGlobal != NULL && pUserName != NULL )
  277. {
  278. wcscpy( pUserName , ( LPWSTR )medium.hGlobal );
  279. }
  280. ReleaseStgMedium( &medium );
  281. }
  282. return S_OK;
  283. }
  284. //-----------------------------------------------------------------------------------------------------
  285. TSUserExInterfaces::TSUserExInterfaces()
  286. {
  287. // LOGMESSAGE0(_T("TSUserExInterfaces::TSUserExInterfaces()..."));
  288. // m_pUserConfigPage = NULL;
  289. m_pTSUserSheet = NULL;
  290. m_pDsadataobj = NULL;
  291. }
  292. //-----------------------------------------------------------------------------------------------------
  293. TSUserExInterfaces::~TSUserExInterfaces()
  294. {
  295. ODS( L"Good bye\n" );
  296. }
  297. //-----------------------------------------------------------------------------------------------------
  298. HRESULT TSUserExInterfaces::CreatePropertyPages(
  299. LPPROPERTYSHEETCALLBACK lpProvider, // pointer to the callback interface
  300. LONG_PTR , // handle for routing notification
  301. LPDATAOBJECT lpIDataObject) // pointer to the data object);
  302. {
  303. //
  304. // Test for valid parameters
  305. //
  306. if( lpIDataObject == NULL || IsBadReadPtr( lpIDataObject , sizeof( LPDATAOBJECT ) ) )
  307. {
  308. ODS( L"TSUSEREX : @ CreatePropertyPages IDataObject is invalid\n " );
  309. return E_INVALIDARG;
  310. }
  311. if( lpProvider == NULL )
  312. {
  313. ODS( L"TSUSEREX @ CreatePropertyPages LPPROPERTYSHEETCALLBACK is invalid\n" );
  314. return E_INVALIDARG;
  315. }
  316. WCHAR wUserName[ MAX_PATH ];
  317. WCHAR wMachineName[ MAX_PATH ];
  318. BOOL bDSAType;
  319. if( g_bPagesHaveBeenInvoked )
  320. {
  321. ODS( L"TSUSEREX : TSUserExInterfaces::CreatePropertyPages pages have been invoked\n" );
  322. return E_FAIL;
  323. }
  324. PSID pUserSid = NULL;
  325. if( FAILED( GetMachineAndUserName( lpIDataObject , wMachineName , wUserName , &bDSAType , &pUserSid ) ) )
  326. {
  327. ODS( L"TSUSEREX : GetMachineAndUserName failed @CreatePropertyPages \n" );
  328. return E_FAIL;
  329. }
  330. //
  331. // Test to see if we are being called twice
  332. //
  333. if( m_pTSUserSheet != NULL )
  334. {
  335. return E_FAIL;
  336. }
  337. //
  338. // MMC likes to release IEXtendPropertySheet ( this object )
  339. // so we cannot free CTSUserSheet in TSUserExInterfaces::dtor
  340. // CTSUserSheet must release itself!!!
  341. //
  342. m_pTSUserSheet = new CTSUserSheet( );
  343. if( m_pTSUserSheet != NULL )
  344. {
  345. ODS( L"TSUSEREX : CreatePropertyPages mem allocation succeeded\n" );
  346. m_pTSUserSheet->SetDSAType( bDSAType );
  347. VERIFY_S( TRUE , m_pTSUserSheet->SetServerAndUser( &wMachineName[0] , &wUserName[0] ) );
  348. m_pTSUserSheet->CopyUserSid( pUserSid );
  349. VERIFY_S( S_OK , m_pTSUserSheet->AddPagesToPropSheet( lpProvider ) );
  350. }
  351. return S_OK;
  352. }
  353. //-----------------------------------------------------------------------------------------------------
  354. HRESULT TSUserExInterfaces::QueryPagesFor( LPDATAOBJECT /* lpDataObject */ )
  355. {
  356. return S_OK;
  357. }
  358. //-----------------------------------------------------------------------------------------------------
  359. // this has not been checked in yet!!!
  360. //-----------------------------------------------------------------------------------------------------
  361. STDMETHODIMP TSUserExInterfaces::GetHelpTopic( LPOLESTR *ppszHelp )
  362. {
  363. ODS( L"TSUSEREX : GetHelpTopic\n" );
  364. if( ppszHelp == NULL )
  365. {
  366. return E_INVALIDARG;
  367. }
  368. TCHAR tchHelpFile[ 80 ];
  369. VERIFY_E( 0 , LoadString( _Module.GetResourceInstance( ) , IDS_TSUSERHELP , tchHelpFile , sizeof( tchHelpFile ) / sizeof( TCHAR ) ) );
  370. // mmc will call CoTaskMemFree
  371. *ppszHelp = ( LPOLESTR )CoTaskMemAlloc( sizeof( TCHAR ) * MAX_PATH );
  372. if( *ppszHelp != NULL )
  373. {
  374. if( GetSystemWindowsDirectory( *ppszHelp , MAX_PATH ) != 0 )
  375. {
  376. lstrcat( *ppszHelp , tchHelpFile );
  377. }
  378. else
  379. {
  380. lstrcpy( *ppszHelp , tchHelpFile );
  381. }
  382. ODS( *ppszHelp );
  383. ODS( L"\n" );
  384. return S_OK;
  385. }
  386. return E_OUTOFMEMORY;
  387. }
  388. //-----------------------------------------------------------------------------------------------------
  389. // IShellExtInit
  390. STDMETHODIMP TSUserExInterfaces::Initialize(
  391. LPCITEMIDLIST ,
  392. LPDATAOBJECT lpdobj,
  393. HKEY
  394. )
  395. {
  396. m_pDsadataobj = lpdobj;
  397. return S_OK;
  398. }
  399. //-----------------------------------------------------------------------------------------------------
  400. // IShellPropSheetExt - this interface is used only for dsadmin based tools
  401. // for this reason the DSAType flag is set to true.
  402. STDMETHODIMP TSUserExInterfaces::AddPages(
  403. LPFNADDPROPSHEETPAGE lpfnAddPage,
  404. LPARAM lParam
  405. )
  406. {
  407. //
  408. // Test for valid parameters
  409. //
  410. if( m_pDsadataobj == NULL )
  411. {
  412. ODS( L"TSUSEREX : @ AddPages IDataObject is invalid\n " );
  413. return E_INVALIDARG;
  414. }
  415. if( lpfnAddPage == NULL )
  416. {
  417. ODS( L"TSUSEREX @ AddPages LPFNADDPROPSHEETPAGE is invalid\n" );
  418. return E_INVALIDARG;
  419. }
  420. WCHAR wUserName[ MAX_PATH ];
  421. WCHAR wMachineName[ MAX_PATH ];
  422. BOOL bDSAType;
  423. PSID pUserSid = NULL;
  424. if( FAILED( GetMachineAndUserName( m_pDsadataobj , wMachineName , wUserName , &bDSAType , &pUserSid ) ) )
  425. {
  426. ODS( L"TSUSEREX : GetMachineAndUserName @AddPages failed \n" );
  427. return E_FAIL;
  428. }
  429. ODS( L"TSUSEREX : DSATYPE in AddPages\n" );
  430. g_bPagesHaveBeenInvoked = TRUE;
  431. //
  432. // Test to see if we are being called twice
  433. //
  434. if( m_pTSUserSheet != NULL )
  435. {
  436. return E_FAIL;
  437. }
  438. //
  439. // MMC likes to release IEXtendPropertySheet ( this object )
  440. // so we cannot free CTSUserSheet in TSUserExInterfaces::dtor
  441. // CTSUserSheet must release itself!!!
  442. //
  443. m_pTSUserSheet = new CTSUserSheet( );
  444. if( m_pTSUserSheet != NULL )
  445. {
  446. ODS( L"TSUSEREX : AddPages mem allocation succeeded\n" );
  447. m_pTSUserSheet->SetDSAType( bDSAType );
  448. m_pTSUserSheet->CopyUserSid( pUserSid );
  449. VERIFY_S( TRUE , m_pTSUserSheet->SetServerAndUser( &wMachineName[0] , &wUserName[0] ) );
  450. VERIFY_S( S_OK , m_pTSUserSheet->AddPagesToDSAPropSheet( lpfnAddPage , lParam ) );
  451. }
  452. return S_OK;
  453. }
  454. //-----------------------------------------------------------------------------------------------------
  455. STDMETHODIMP TSUserExInterfaces::ReplacePage(
  456. UINT ,
  457. LPFNADDPROPSHEETPAGE ,
  458. LPARAM
  459. )
  460. {
  461. return E_FAIL;
  462. }
  463. #ifdef _RTM_ // add ISnapinAbout
  464. //-----------------------------------------------------------------------------------------------------
  465. STDMETHODIMP TSUserExInterfaces::GetSnapinDescription(
  466. LPOLESTR *ppOlestr )
  467. {
  468. TCHAR tchMessage[] = TEXT("This extension allows the administrator to configure Terminal Services user properties. This extension is only enabled on Terminal Servers.");
  469. ODS( L"TSUSEREX: GetSnapinDescription called\n" );
  470. *ppOlestr = ( LPOLESTR )CoTaskMemAlloc( ( lstrlen( tchMessage ) + 1 ) * sizeof( TCHAR ) );
  471. if( *ppOlestr != NULL )
  472. {
  473. lstrcpy( *ppOlestr , tchMessage );
  474. return S_OK;
  475. }
  476. return E_OUTOFMEMORY;
  477. }
  478. //-----------------------------------------------------------------------------------------------------
  479. STDMETHODIMP TSUserExInterfaces::GetProvider(
  480. LPOLESTR *ppOlestr )
  481. {
  482. TCHAR tchMessage[] = TEXT("Microsoft Corporation");
  483. ODS( L"TSUSEREX: GetProvider called\n" );
  484. *ppOlestr = ( LPOLESTR )CoTaskMemAlloc( ( lstrlen( tchMessage ) + 1 ) * sizeof( TCHAR ) );
  485. if( *ppOlestr != NULL )
  486. {
  487. lstrcpy( *ppOlestr , tchMessage );
  488. return S_OK;
  489. }
  490. return E_OUTOFMEMORY;
  491. }
  492. //-----------------------------------------------------------------------------------------------------
  493. STDMETHODIMP TSUserExInterfaces::GetSnapinVersion(
  494. LPOLESTR *ppOlestr )
  495. {
  496. char chMessage[ 32 ] = VER_PRODUCTVERSION_STR;
  497. TCHAR tchMessage[32];
  498. ODS( L"TSUSEREX: GetSnapinVersion called\n" );
  499. int iCharCount = MultiByteToWideChar( CP_ACP , 0 , chMessage , sizeof( chMessage ) , tchMessage , sizeof( tchMessage ) / sizeof( TCHAR ) );
  500. //wsprintf( tchMessage , TEXT( "%d" ) , VER_PRODUCTVERSION_DW );
  501. *ppOlestr = ( LPOLESTR )CoTaskMemAlloc( ( iCharCount + 1 ) * sizeof( TCHAR ) );
  502. if( *ppOlestr != NULL )
  503. {
  504. lstrcpy( *ppOlestr , tchMessage );
  505. return S_OK;
  506. }
  507. return E_OUTOFMEMORY;
  508. }
  509. //-----------------------------------------------------------------------------------------------------
  510. STDMETHODIMP TSUserExInterfaces::GetSnapinImage(
  511. HICON * )
  512. {
  513. return E_NOTIMPL;
  514. }
  515. //-----------------------------------------------------------------------------------------------------
  516. STDMETHODIMP TSUserExInterfaces::GetStaticFolderImage(
  517. /* [out] */ HBITMAP *,
  518. /* [out] */ HBITMAP *,
  519. /* [out] */ HBITMAP *,
  520. /* [out] */ COLORREF *)
  521. {
  522. return E_NOTIMPL;
  523. }
  524. #endif //_RTM_