Leaked source code of windows server 2003
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.

649 lines
20 KiB

  1. //----------------------------------------------------------------------------
  2. // Copyright (C) Microsoft Corporation, 2001
  3. //
  4. // File: bitscnfg.cpp
  5. //
  6. // Contents: Configure BITS client service to default settings.
  7. //
  8. // EdwardR 07/27/2001 Initial version.
  9. // 08/03/2001 Add code to fixup ServiceDLL in registry.
  10. // Add code to regsvr qmgrprxy.dll
  11. // 08/13/2001 Add code to support XP as well as Win2k.
  12. //----------------------------------------------------------------------------
  13. #define UNICODE
  14. #include <windows.h>
  15. #include <stdio.h>
  16. #include <memory>
  17. using namespace std;
  18. #define VER_WINDOWS_2000 500
  19. #define VER_WINDOWS_XP 501
  20. //
  21. // Service configuration settings:
  22. //
  23. #define BITS_SERVICE_NAME TEXT("BITS")
  24. #define BITS_DISPLAY_NAME TEXT("Background Intelligent Transfer Service")
  25. #define BITS_BINARY_PATH TEXT("%SystemRoot%\\System32\\svchost.exe -k BITSgroup")
  26. #define BITS_LOAD_ORDER_GROUP TEXT("BITSgroup")
  27. #define BITS_SERVICE_TYPE SERVICE_WIN32_SHARE_PROCESS
  28. #define BITS_START_TYPE SERVICE_DEMAND_START
  29. #define BITS_ERROR_CONTROL SERVICE_ERROR_NORMAL
  30. //
  31. // This additional service registry setting is set incorrectly by the
  32. // Darwin install
  33. //
  34. #define REG_SERVICE_PATH TEXT("SYSTEM\\CurrentControlSet\\Services\\BITS")
  35. #define REG_PARAMETERS TEXT("Parameters")
  36. #define REG_SERVICEDLL TEXT("ServiceDll")
  37. #define REG_SERVICEDLL_PATH TEXT("%SystemRoot%\\System32\\qmgr.dll")
  38. //
  39. // For side-by-side install on Windows XP
  40. //
  41. #define BACKSLASH_STR TEXT("\\")
  42. #define BITS_SUBDIRECTORY TEXT("BITS")
  43. #define BITS_QMGR_DLL TEXT("qmgr.dll")
  44. #define REG_BITS TEXT("BITS")
  45. #define REG_BITS_SERVICEDLL TEXT("ServiceDLL")
  46. #define REG_SERVICEDLL_KEY TEXT("Software\\Microsoft\\Windows\\CurrentVersion")
  47. //
  48. // Constants to register qmgrprxy.dll
  49. //
  50. #define BITS_QMGRPRXY_DLL TEXT("qmgrprxy.dll")
  51. #define BITS_BITS15PRXY_DLL TEXT("bitsprx2.dll")
  52. #define BITS_DLL_REGISTER_FN "DllRegisterServer"
  53. typedef HRESULT (*RegisterFn)();
  54. //
  55. // Constants for parsing bitscnfg.exe runstring arguments
  56. //
  57. #define SZ_DELIMITERS " \t"
  58. #define SZ_INSTALL "/i"
  59. #define SZ_UNINSTALL "/u"
  60. #define SZ_DELETE_SERVICE "/d"
  61. #define ACTION_INSTALL 0
  62. #define ACTION_UNINSTALL 1
  63. #define ACTION_DELETE_SERVICE 2
  64. //
  65. // Log file for testing
  66. //
  67. FILE *f = NULL;
  68. //---------------------------------------------------------------------
  69. // RegSetKeyAndValue()
  70. //
  71. // Helper function to create a key, sets a value in the key,
  72. // then close the key.
  73. //
  74. // Parameters:
  75. // pwsKey WCHAR* The name of the key
  76. // pwsSubkey WCHAR* The name of a subkey
  77. // pwsValueName WCHAR* The value name.
  78. // pwsValue WCHAR* The data value to store
  79. // dwType The type for the new registry value.
  80. // dwDataSize The size for non-REG_SZ registry entry types.
  81. //
  82. // Return:
  83. // BOOL TRUE if successful, FALSE otherwise.
  84. //---------------------------------------------------------------------
  85. DWORD RegSetKeyAndValue( const WCHAR *pwsKey,
  86. const WCHAR *pwsSubKey,
  87. const WCHAR *pwsValueName,
  88. const WCHAR *pwsValue,
  89. const DWORD dwType = REG_SZ,
  90. DWORD dwDataSize = 0,
  91. BOOL fReCreate = TRUE )
  92. {
  93. DWORD dwStatus = ERROR_SUCCESS;
  94. HKEY hKey = NULL;
  95. DWORD dwSize = 0;
  96. WCHAR *pwsCompleteKey = NULL;
  97. if (pwsKey)
  98. dwSize = wcslen(pwsKey);
  99. if (pwsSubKey)
  100. dwSize += wcslen(pwsSubKey);
  101. pwsCompleteKey = new WCHAR[ 1+1+dwSize ]; // Extra +1 for the backslash...
  102. if (!pwsCompleteKey)
  103. {
  104. return ERROR_NOT_ENOUGH_MEMORY;
  105. }
  106. wcscpy(pwsCompleteKey, pwsKey);
  107. if (NULL!=pwsSubKey)
  108. {
  109. wcscat(pwsCompleteKey, BACKSLASH_STR);
  110. wcscat(pwsCompleteKey, pwsSubKey);
  111. }
  112. // If the key is already there then delete it, we will recreate it.
  113. if (fReCreate)
  114. {
  115. dwStatus = RegDeleteKey( HKEY_LOCAL_MACHINE,
  116. pwsCompleteKey );
  117. }
  118. dwStatus = RegCreateKeyEx( HKEY_LOCAL_MACHINE,
  119. pwsCompleteKey,
  120. 0,
  121. NULL,
  122. REG_OPTION_NON_VOLATILE,
  123. KEY_ALL_ACCESS,
  124. NULL,
  125. &hKey,
  126. NULL );
  127. if (dwStatus != ERROR_SUCCESS)
  128. {
  129. goto error;
  130. }
  131. if (pwsValue)
  132. {
  133. if ((dwType == REG_SZ)||(dwType == REG_EXPAND_SZ))
  134. dwDataSize = (1+wcslen(pwsValue))*sizeof(WCHAR);
  135. RegSetValueEx( hKey,
  136. pwsValueName, 0, dwType, (BYTE *)pwsValue, dwDataSize );
  137. }
  138. else
  139. {
  140. RegSetValueEx( hKey,
  141. pwsValueName, 0, dwType, (BYTE *)pwsValue, 0 );
  142. }
  143. error:
  144. if (hKey)
  145. {
  146. RegCloseKey(hKey);
  147. }
  148. if (pwsCompleteKey)
  149. {
  150. delete pwsCompleteKey;
  151. }
  152. return dwStatus;
  153. }
  154. //-------------------------------------------------------------------------
  155. // RegDeleteKeyOrValue()
  156. //
  157. // Delete either the specified sub-key or delete the specified value
  158. // within the sub-key. If pwszValueName is specified, the just delete
  159. // it and leave the key alone. If pwszValueName is NULL, then delete
  160. // the key.
  161. //-------------------------------------------------------------------------
  162. DWORD RegDeleteKeyOrValue( IN const WCHAR *pwszKey,
  163. IN const WCHAR *pwszSubKey,
  164. IN const WCHAR *pwszValueName )
  165. {
  166. LONG lStatus = 0;
  167. DWORD dwLen;
  168. HKEY hKey = 0;
  169. WCHAR *pwszCompleteKey = NULL;
  170. if (!pwszKey || !pwszSubKey)
  171. {
  172. return lStatus;
  173. }
  174. dwLen = wcslen(pwszKey) + wcslen(pwszSubKey) + 2;
  175. pwszCompleteKey = new WCHAR[dwLen];
  176. if (!pwszCompleteKey)
  177. {
  178. return ERROR_NOT_ENOUGH_MEMORY;
  179. }
  180. wcscpy(pwszCompleteKey,pwszKey);
  181. wcscat(pwszCompleteKey,BACKSLASH_STR);
  182. wcscat(pwszCompleteKey,pwszSubKey);
  183. if (pwszValueName)
  184. {
  185. // Delete a value in a key:
  186. if (f) fwprintf(f,TEXT("Delete Reg Value: %s : %s\n"),pwszCompleteKey,pwszValueName);
  187. lStatus = RegOpenKey(HKEY_LOCAL_MACHINE,pwszCompleteKey,&hKey);
  188. if (lStatus == ERROR_SUCCESS)
  189. {
  190. lStatus = RegDeleteValue(hKey,pwszValueName);
  191. RegCloseKey(hKey);
  192. }
  193. }
  194. else
  195. {
  196. // Delete the specified key:
  197. if (f) fwprintf(f,TEXT("Delete Reg Key: %s\n"),pwszCompleteKey);
  198. lStatus = RegDeleteKey(HKEY_LOCAL_MACHINE,pwszCompleteKey);
  199. }
  200. if (pwszCompleteKey)
  201. {
  202. delete pwszCompleteKey;
  203. }
  204. return lStatus;
  205. }
  206. //-------------------------------------------------------------------------
  207. // RegisterDLL()
  208. //
  209. //-------------------------------------------------------------------------
  210. DWORD RegisterDLL( IN WCHAR *pwszSubdirectory,
  211. IN WCHAR *pwszDllName )
  212. {
  213. DWORD dwStatus = 0;
  214. HMODULE hModule;
  215. RegisterFn pRegisterFn;
  216. UINT nChars;
  217. WCHAR wszDllPath[MAX_PATH+1];
  218. WCHAR wszSystemDirectory[MAX_PATH+1];
  219. if (pwszSubdirectory)
  220. {
  221. nChars = GetSystemDirectory(wszSystemDirectory,MAX_PATH);
  222. if ( (nChars > MAX_PATH)
  223. || (MAX_PATH < (3+wcslen(wszSystemDirectory)+wcslen(BITS_SUBDIRECTORY)+wcslen(pwszDllName))) )
  224. {
  225. return ERROR_BUFFER_OVERFLOW;
  226. }
  227. wcscpy(wszDllPath,wszSystemDirectory);
  228. wcscat(wszDllPath,BACKSLASH_STR);
  229. wcscat(wszDllPath,pwszSubdirectory);
  230. wcscat(wszDllPath,BACKSLASH_STR);
  231. wcscat(wszDllPath,pwszDllName);
  232. }
  233. else
  234. {
  235. if (MAX_PATH < wcslen(pwszDllName))
  236. {
  237. return ERROR_BUFFER_OVERFLOW;
  238. }
  239. wcscpy(wszDllPath,pwszDllName);
  240. }
  241. hModule = LoadLibrary(wszDllPath);
  242. if (!hModule)
  243. {
  244. dwStatus = GetLastError();
  245. return dwStatus;
  246. }
  247. pRegisterFn = (RegisterFn)GetProcAddress(hModule,BITS_DLL_REGISTER_FN);
  248. if (!pRegisterFn)
  249. {
  250. dwStatus = GetLastError();
  251. FreeLibrary(hModule);
  252. return dwStatus;
  253. }
  254. dwStatus = pRegisterFn();
  255. FreeLibrary(hModule);
  256. return dwStatus;
  257. }
  258. //-------------------------------------------------------------------------
  259. // ParseCmdLine()
  260. //
  261. //-------------------------------------------------------------------------
  262. void ParseCmdLine( LPSTR pszCmdLine,
  263. DWORD *pdwAction )
  264. {
  265. CHAR *pszTemp = pszCmdLine;
  266. CHAR *pszArg;
  267. BOOL fFirstTime = TRUE;
  268. *pdwAction = ACTION_INSTALL; // default is install.
  269. while (pszArg=strtok(pszTemp,SZ_DELIMITERS))
  270. {
  271. if (fFirstTime)
  272. {
  273. fFirstTime = FALSE;
  274. pszTemp = NULL;
  275. continue; // Skip over program name...
  276. }
  277. if (!_stricmp(pszArg,SZ_INSTALL))
  278. {
  279. *pdwAction = ACTION_INSTALL;
  280. if (f) fwprintf(f,TEXT("Install: %S\n"),SZ_INSTALL);
  281. }
  282. if (!_stricmp(pszArg,SZ_UNINSTALL))
  283. {
  284. *pdwAction = ACTION_UNINSTALL;
  285. if (f) fwprintf(f,TEXT("Uninstall: %S\n"),SZ_UNINSTALL);
  286. }
  287. if (!_stricmp(pszArg,SZ_DELETE_SERVICE))
  288. {
  289. *pdwAction = ACTION_DELETE_SERVICE;
  290. if (f) fwprintf(f,TEXT("DeleteService: %S\n"),SZ_UNINSTALL);
  291. }
  292. }
  293. }
  294. //-------------------------------------------------------------------------
  295. // DoInstall()
  296. //-------------------------------------------------------------------------
  297. DWORD DoInstall( DWORD dwOsVersion )
  298. {
  299. SC_HANDLE hSCM = NULL;
  300. SC_HANDLE hService = NULL;
  301. DWORD dwStatus = 0;
  302. UINT nChars;
  303. WCHAR wszQmgrPath[MAX_PATH+1];
  304. WCHAR wszSystemDirectory[MAX_PATH+1];
  305. //
  306. // Cleanup the service configuration:
  307. //
  308. if (dwOsVersion == VER_WINDOWS_2000)
  309. {
  310. hSCM = OpenSCManager(NULL,SERVICES_ACTIVE_DATABASE,SC_MANAGER_ALL_ACCESS);
  311. if (hSCM == NULL)
  312. {
  313. dwStatus = GetLastError();
  314. if (f) fwprintf(f,TEXT("OpenSCManager(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  315. goto error;
  316. }
  317. if (f) fwprintf(f,TEXT("Configuring BITS Service... \n"));
  318. hService = OpenService(hSCM,BITS_SERVICE_NAME,SERVICE_CHANGE_CONFIG);
  319. if (hService == NULL)
  320. {
  321. dwStatus = GetLastError();
  322. if (f) fwprintf(f,TEXT("OpenService(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  323. goto error;
  324. }
  325. if (!ChangeServiceConfig(hService,
  326. BITS_SERVICE_TYPE,
  327. BITS_START_TYPE,
  328. BITS_ERROR_CONTROL,
  329. BITS_BINARY_PATH,
  330. NULL, // Load order group (not changing this).
  331. NULL, // Tag ID for load order group (not needed).
  332. // Service dependencies (this is different for Win2k )
  333. // reply on XP installer to overwrite this.
  334. TEXT("LanmanWorkstation\0Rpcss\0SENS\0Wmi\0"),
  335. NULL, // Account Name (not changing this).
  336. NULL, // Account Password (not changing this).
  337. BITS_DISPLAY_NAME))
  338. {
  339. dwStatus = GetLastError();
  340. if (f) fwprintf(f,TEXT("ChangeServiceConfig(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  341. goto error;
  342. }
  343. //
  344. // Fix the ServiceDll registry value...
  345. //
  346. if (f) fwprintf(f,TEXT("bitscnfg.exe: Fix ServiceDll entry.\n"));
  347. dwStatus = RegSetKeyAndValue( REG_SERVICE_PATH,
  348. REG_PARAMETERS,
  349. REG_SERVICEDLL,
  350. REG_SERVICEDLL_PATH,
  351. REG_EXPAND_SZ);
  352. if (dwStatus)
  353. {
  354. if (f) fwprintf(f,TEXT("RegSetKeyAndValue(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  355. goto error;
  356. }
  357. }
  358. //
  359. // Register qmgrproxy.dll
  360. //
  361. if (dwOsVersion == VER_WINDOWS_2000)
  362. {
  363. if (f) fwprintf(f,TEXT("bitscnfg.exe: Register %s.\n"),BITS_QMGRPRXY_DLL);
  364. dwStatus = RegisterDLL(NULL,BITS_QMGRPRXY_DLL);
  365. if (dwStatus != 0)
  366. {
  367. if (f) fwprintf(f,TEXT("RegisterDLL(%s): Failed: 0x%x (%d)\n"),BITS_QMGRPRXY_DLL,dwStatus,dwStatus);
  368. goto error;
  369. }
  370. }
  371. //
  372. // Register bits15prxy.dll
  373. //
  374. if ((dwOsVersion == VER_WINDOWS_2000)||(dwOsVersion == VER_WINDOWS_XP))
  375. {
  376. if (f) fwprintf(f,TEXT("bitscnfg.exe: Register %s.\n"),BITS_BITS15PRXY_DLL);
  377. dwStatus = RegisterDLL(BITS_SUBDIRECTORY,BITS_BITS15PRXY_DLL);
  378. if (dwStatus != 0)
  379. {
  380. if (f) fwprintf(f,TEXT("RegisterDLL(%s): Failed: 0x%x (%d)\n"),BITS_BITS15PRXY_DLL,dwStatus,dwStatus);
  381. goto error;
  382. }
  383. }
  384. //
  385. // Configure WindowsXP BITS to run V1.5 qmgr.dll. This is also configured on Windows2000 systems to ready
  386. // it in case the system is upgraded to WindowsXP. This is done because there is no Migrate.dll to go from
  387. // Windows2000 to WindowsXP.
  388. //
  389. if ((dwOsVersion == VER_WINDOWS_2000)||(dwOsVersion == VER_WINDOWS_XP))
  390. {
  391. nChars = GetSystemDirectory(wszSystemDirectory,MAX_PATH);
  392. if ( (nChars > MAX_PATH)
  393. || (MAX_PATH < (3+wcslen(wszSystemDirectory)+wcslen(BITS_SUBDIRECTORY)+wcslen(BITS_QMGR_DLL))) )
  394. {
  395. if (f) fwprintf(f,TEXT("GetSystemDirectory(): System Path too long.\n"));
  396. goto error;
  397. }
  398. wcscpy(wszQmgrPath,wszSystemDirectory);
  399. wcscat(wszQmgrPath,BACKSLASH_STR);
  400. wcscat(wszQmgrPath,BITS_SUBDIRECTORY);
  401. wcscat(wszQmgrPath,BACKSLASH_STR);
  402. wcscat(wszQmgrPath,BITS_QMGR_DLL);
  403. if (f) fwprintf(f,TEXT("Set BITS V1.5 Override Path: %s\n"),wszQmgrPath);
  404. dwStatus = RegSetKeyAndValue( REG_SERVICEDLL_KEY,
  405. REG_BITS,
  406. REG_BITS_SERVICEDLL,
  407. wszQmgrPath,
  408. REG_SZ, 0, FALSE);
  409. if (dwStatus)
  410. {
  411. if (f) fwprintf(f,TEXT("RegSetKeyAndValue(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  412. goto error;
  413. }
  414. }
  415. error:
  416. if (hService)
  417. {
  418. CloseServiceHandle(hService);
  419. }
  420. if (hSCM)
  421. {
  422. CloseServiceHandle(hSCM);
  423. }
  424. return dwStatus;
  425. }
  426. //-------------------------------------------------------------------------
  427. // DoUninstall()
  428. //
  429. // If this is Windows2000 then delete the BITS service.
  430. //-------------------------------------------------------------------------
  431. DWORD DoUninstall( DWORD dwOsVersion )
  432. {
  433. DWORD dwStatus = 0;
  434. SC_HANDLE hSCM = NULL;
  435. SC_HANDLE hService = NULL;
  436. //
  437. // Delete the BITS thunk DLL registry entry:
  438. //
  439. if (dwOsVersion == VER_WINDOWS_2000)
  440. {
  441. // If Windows2000, then delete the BITS subkey and all its values.
  442. RegDeleteKeyOrValue( REG_SERVICEDLL_KEY,
  443. REG_BITS,
  444. NULL );
  445. }
  446. if (dwOsVersion == VER_WINDOWS_XP)
  447. {
  448. // If WindowsXP, then just delete the ServiceDLL value and leave the key and any other values.
  449. RegDeleteKeyOrValue( REG_SERVICEDLL_KEY,
  450. REG_BITS,
  451. REG_BITS_SERVICEDLL );
  452. }
  453. //
  454. // If this is Windows2000, then delete the service.
  455. //
  456. if (dwOsVersion == VER_WINDOWS_2000)
  457. {
  458. hSCM = OpenSCManager(NULL,SERVICES_ACTIVE_DATABASE,SC_MANAGER_ALL_ACCESS);
  459. if (hSCM == NULL)
  460. {
  461. dwStatus = GetLastError();
  462. if (f) fwprintf(f,TEXT("OpenSCManager(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  463. goto error;
  464. }
  465. if (f) fwprintf(f,TEXT("Configuring BITS Service... \n"));
  466. hService = OpenService(hSCM,BITS_SERVICE_NAME,SERVICE_CHANGE_CONFIG);
  467. if (hService == NULL)
  468. {
  469. dwStatus = GetLastError();
  470. if (dwStatus == ERROR_SERVICE_DOES_NOT_EXIST)
  471. {
  472. dwStatus = 0;
  473. if (f) fwprintf(f,TEXT("OpenService(): Failed: 0x%x (%d) Service doesn't exist.\n"),dwStatus,dwStatus);
  474. }
  475. else
  476. {
  477. if (f) fwprintf(f,TEXT("OpenService(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  478. }
  479. goto error;
  480. }
  481. if (!DeleteService(hService))
  482. {
  483. dwStatus = GetLastError();
  484. if (f) fwprintf(f,TEXT("DeleteService(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  485. }
  486. }
  487. error:
  488. if (hService)
  489. {
  490. CloseServiceHandle(hService);
  491. }
  492. if (hSCM)
  493. {
  494. CloseServiceHandle(hSCM);
  495. }
  496. return dwStatus;
  497. }
  498. //-------------------------------------------------------------------------
  499. // DeleteBitsService()
  500. //
  501. // Currently this is the same action as DoInstall().
  502. //-------------------------------------------------------------------------
  503. DWORD DeleteBitsService( IN DWORD dwOsVersion )
  504. {
  505. return DoUninstall( dwOsVersion );
  506. }
  507. //-------------------------------------------------------------------------
  508. // main()
  509. //
  510. //-------------------------------------------------------------------------
  511. int PASCAL WinMain( HINSTANCE hInstance,
  512. HINSTANCE hPrevInstance,
  513. LPSTR pszCmdLine,
  514. int nCmdShow )
  515. {
  516. SC_HANDLE hSCM = NULL;
  517. SC_HANDLE hService = NULL;
  518. DWORD dwAction;
  519. DWORD dwStatus;
  520. DWORD dwOsVersion;
  521. UINT nChars;
  522. WCHAR wszQmgrPath[MAX_PATH+1];
  523. WCHAR wszSystemDirectory[MAX_PATH+1];
  524. OSVERSIONINFO osVersionInfo;
  525. f = _wfopen(TEXT("c:\\temp\\bitscnfg.log"),TEXT("w"));
  526. if (f) fwprintf(f,TEXT("Runstring: %S\n"),pszCmdLine);
  527. ParseCmdLine(pszCmdLine,&dwAction);
  528. //
  529. // Get the operating system verison (Win2k == 500, XP == 501):
  530. //
  531. osVersionInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  532. if (!GetVersionEx(&osVersionInfo))
  533. {
  534. dwStatus = GetLastError();
  535. if (f) fwprintf(f,TEXT("GetVersionEx(): Failed: 0x%x (%d)\n"),dwStatus,dwStatus);
  536. goto error;
  537. }
  538. dwOsVersion = 100*osVersionInfo.dwMajorVersion + osVersionInfo.dwMinorVersion;
  539. if (f) fwprintf(f,TEXT("Windows Version: %d\n"),dwOsVersion);
  540. switch (dwAction)
  541. {
  542. case ACTION_INSTALL:
  543. dwStatus = DoInstall(dwOsVersion);
  544. break;
  545. case ACTION_UNINSTALL:
  546. dwStatus = DoUninstall(dwOsVersion);
  547. break;
  548. case ACTION_DELETE_SERVICE:
  549. dwStatus = DeleteBitsService(dwOsVersion);
  550. break;
  551. default:
  552. if (f) fwprintf(f,TEXT("Undefined Custom Action: %d\n"),dwAction);
  553. break;
  554. }
  555. error:
  556. if (f) fwprintf(f,TEXT("bitscnfg.exe: Complete.\n"));
  557. if (f) fclose(f);
  558. return 0;
  559. }