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.

781 lines
21 KiB

  1. #include "stdafx.h"
  2. #include <windows.h>
  3. #include <winuserp.h>
  4. #include <winperf.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. #include "kill.h"
  9. PUCHAR g_CommonLargeBuffer = NULL;
  10. ULONG g_CommonLargeBufferSize = 64*1024;
  11. DWORD
  12. GetTaskListEx(
  13. PTASK_LIST pTask,
  14. DWORD dwNumTasks,
  15. BOOL fThreadInfo,
  16. DWORD dwNumServices,
  17. const _ENUM_SERVICE_STATUS_PROCESSA* pServiceInfo
  18. )
  19. /*++
  20. Routine Description:
  21. Provides an API for getting a list of tasks running at the time of the
  22. API call. This function uses internal NT apis and data structures. This
  23. api is MUCH faster that the non-internal version that uses the registry.
  24. Arguments:
  25. pTask - Array of TASK_LIST structures to fill.
  26. dwNumTasks - Maximum number of tasks that the pTask array can hold.
  27. fThreadInfo - TRUE if thread information is desired.
  28. dwNumServices - Maximum number of entries in pServiceInfo.
  29. pServiceInfo - Array of service status structures to reference
  30. for supporting services in processes.
  31. Return Value:
  32. Number of tasks placed into the pTask array.
  33. --*/
  34. {
  35. #ifndef _CHICAGO_
  36. PSYSTEM_PROCESS_INFORMATION ProcessInfo = NULL;
  37. NTSTATUS status;
  38. ANSI_STRING pname;
  39. PCHAR p = NULL;
  40. ULONG TotalOffset;
  41. ULONG totalTasks = 0;
  42. retry:
  43. if (g_CommonLargeBuffer == NULL)
  44. {
  45. g_CommonLargeBuffer = (PUCHAR) VirtualAlloc(NULL,g_CommonLargeBufferSize,MEM_COMMIT,PAGE_READWRITE);
  46. if (g_CommonLargeBuffer == NULL)
  47. {
  48. return 0;
  49. }
  50. }
  51. status = NtQuerySystemInformation(SystemProcessInformation,g_CommonLargeBuffer,g_CommonLargeBufferSize,NULL);
  52. if (status == STATUS_INFO_LENGTH_MISMATCH)
  53. {
  54. g_CommonLargeBufferSize += 8192;
  55. VirtualFree (g_CommonLargeBuffer, 0, MEM_RELEASE);
  56. g_CommonLargeBuffer = NULL;
  57. goto retry;
  58. }
  59. ProcessInfo = (PSYSTEM_PROCESS_INFORMATION) g_CommonLargeBuffer;
  60. TotalOffset = 0;
  61. while (TRUE)
  62. {
  63. pname.Buffer = NULL;
  64. if ( ProcessInfo->ImageName.Buffer )
  65. {
  66. RtlUnicodeStringToAnsiString(&pname,(PUNICODE_STRING)&ProcessInfo->ImageName,TRUE);
  67. if (pname.Buffer)
  68. {
  69. p = strrchr(pname.Buffer,'\\');
  70. if ( p )
  71. {
  72. p++;
  73. }
  74. else
  75. {
  76. p = pname.Buffer;
  77. }
  78. }
  79. else
  80. {
  81. p = "";
  82. }
  83. }
  84. else
  85. {
  86. p = "System Process";
  87. }
  88. strcpy( pTask->ProcessName, p );
  89. pTask->flags = 0;
  90. pTask->dwProcessId = (DWORD)(DWORD_PTR)ProcessInfo->UniqueProcessId;
  91. pTask->dwInheritedFromProcessId = (DWORD)(DWORD_PTR)ProcessInfo->InheritedFromUniqueProcessId;
  92. pTask->CreateTime.QuadPart = (ULONGLONG)ProcessInfo->CreateTime.QuadPart;
  93. pTask->PeakVirtualSize = ProcessInfo->PeakVirtualSize;
  94. pTask->VirtualSize = ProcessInfo->VirtualSize;
  95. pTask->PageFaultCount = ProcessInfo->PageFaultCount;
  96. pTask->PeakWorkingSetSize = ProcessInfo->PeakWorkingSetSize;
  97. pTask->WorkingSetSize = ProcessInfo->WorkingSetSize;
  98. pTask->NumberOfThreads = ProcessInfo->NumberOfThreads;
  99. if (fThreadInfo)
  100. {
  101. if (pTask->pThreadInfo = (PTHREAD_INFO) malloc(pTask->NumberOfThreads * sizeof(THREAD_INFO))) {
  102. UINT nThread = pTask->NumberOfThreads;
  103. PTHREAD_INFO pThreadInfo = pTask->pThreadInfo;
  104. PSYSTEM_THREAD_INFORMATION pSysThreadInfo =
  105. (PSYSTEM_THREAD_INFORMATION)(ProcessInfo + 1);
  106. while (nThread--) {
  107. pThreadInfo->ThreadState = pSysThreadInfo->ThreadState;
  108. pThreadInfo->UniqueThread = pSysThreadInfo->ClientId.UniqueThread;
  109. pThreadInfo++;
  110. pSysThreadInfo++;
  111. }
  112. }
  113. }
  114. else
  115. {
  116. pTask->pThreadInfo = NULL;
  117. }
  118. // Initialize the ServiceNames if this task hosts any.
  119. //
  120. *pTask->ServiceNames = 0;
  121. if (dwNumServices)
  122. {
  123. // For each service with this process id, append it's service
  124. // name to the buffer. Separate each with a comma.
  125. //
  126. BOOL fFirstTime = TRUE;
  127. DWORD iSvc;
  128. size_t cchRemain = SERVICENAMES_SIZE - 1;
  129. size_t cch;
  130. for (iSvc = 0; iSvc < dwNumServices; iSvc++) {
  131. if (pTask->dwProcessId == pServiceInfo[iSvc].ServiceStatusProcess.dwProcessId) {
  132. cch = strlen(pServiceInfo[iSvc].lpServiceName);
  133. if (fFirstTime) {
  134. fFirstTime = FALSE;
  135. strncpy(
  136. pTask->ServiceNames,
  137. pServiceInfo[iSvc].lpServiceName,
  138. cchRemain);
  139. // strncpy may not terminate the string if
  140. // cchRemain <= cch so we do it regardless.
  141. //
  142. pTask->ServiceNames[cchRemain] = 0;
  143. } else if (cchRemain > 1) { // ensure room for the comma
  144. strncat(
  145. pTask->ServiceNames,
  146. ",",
  147. cchRemain--);
  148. strncat(
  149. pTask->ServiceNames,
  150. pServiceInfo[iSvc].lpServiceName,
  151. cchRemain);
  152. }
  153. // Counts are unsigned so we have to check before
  154. // subtracting.
  155. //
  156. if (cchRemain < cch) {
  157. // No more room for any more.
  158. break;
  159. } else {
  160. cchRemain -= cch;
  161. }
  162. }
  163. }
  164. }
  165. pTask++;
  166. totalTasks++;
  167. if (totalTasks >= dwNumTasks)
  168. {
  169. break;
  170. }
  171. if (ProcessInfo->NextEntryOffset == 0)
  172. {
  173. break;
  174. }
  175. TotalOffset += ProcessInfo->NextEntryOffset;
  176. ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)&g_CommonLargeBuffer[TotalOffset];
  177. }
  178. return totalTasks;
  179. #else
  180. return 0;
  181. #endif
  182. }
  183. DWORD
  184. GetTaskList(
  185. PTASK_LIST pTask,
  186. DWORD dwNumTasks
  187. )
  188. {
  189. return GetTaskListEx(pTask, dwNumTasks, FALSE, 0, NULL);
  190. }
  191. void FreeTaskListMem(void)
  192. {
  193. if (g_CommonLargeBuffer)
  194. {
  195. VirtualFree (g_CommonLargeBuffer, 0, MEM_RELEASE);
  196. g_CommonLargeBuffer = NULL;
  197. }
  198. return;
  199. }
  200. BOOL DetectOrphans(PTASK_LIST pTask,DWORD dwNumTasks)
  201. {
  202. DWORD i, j;
  203. BOOL Result = FALSE;
  204. for (i=0; i<dwNumTasks; i++) {
  205. if (pTask[i].dwInheritedFromProcessId != 0) {
  206. for (j=0; j<dwNumTasks; j++) {
  207. if (i != j && pTask[i].dwInheritedFromProcessId == pTask[j].dwProcessId) {
  208. if (pTask[i].CreateTime.QuadPart <= pTask[j].CreateTime.QuadPart) {
  209. pTask[i].dwInheritedFromProcessId = 0;
  210. Result = TRUE;
  211. }
  212. break;
  213. }
  214. }
  215. }
  216. }
  217. return Result;
  218. }
  219. /*++
  220. Routine Description:
  221. Changes the tlist process's privilige so that kill works properly.
  222. Return Value:
  223. TRUE - success
  224. FALSE - failure
  225. --*/
  226. BOOL EnableDebugPriv(VOID)
  227. {
  228. HANDLE hToken;
  229. LUID DebugValue;
  230. TOKEN_PRIVILEGES tkp;
  231. //
  232. // Retrieve a handle of the access token
  233. //
  234. if (!OpenProcessToken(GetCurrentProcess(),
  235. TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY,
  236. &hToken)) {
  237. //printf("OpenProcessToken failed with %d\n", GetLastError());
  238. return FALSE;
  239. }
  240. //
  241. // Enable the SE_DEBUG_NAME privilege or disable
  242. // all privileges, depending on the fEnable flag.
  243. //
  244. //if (!LookupPrivilegeValue((LPSTR) NULL,SE_DEBUG_NAME,&DebugValue))
  245. if (!LookupPrivilegeValueA((LPSTR) NULL,"SeDebugPrivilege",&DebugValue))
  246. {
  247. //printf("LookupPrivilegeValue failed with %d\n", GetLastError());
  248. return FALSE;
  249. }
  250. tkp.PrivilegeCount = 1;
  251. tkp.Privileges[0].Luid = DebugValue;
  252. tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  253. if (!AdjustTokenPrivileges(
  254. hToken,
  255. FALSE,
  256. &tkp,
  257. sizeof(TOKEN_PRIVILEGES),
  258. (PTOKEN_PRIVILEGES) NULL,
  259. (PDWORD) NULL)) {
  260. //
  261. // The return value of AdjustTokenPrivileges be texted
  262. //
  263. //printf("AdjustTokenPrivileges failed with %d\n", GetLastError());
  264. return FALSE;
  265. }
  266. return TRUE;
  267. }
  268. BOOL KillProcess(PTASK_LIST tlist,BOOL fForce)
  269. {
  270. HANDLE hProcess1 = NULL;
  271. HANDLE hProcess2 = NULL;
  272. HDESK hdeskSave = NULL;
  273. HDESK hdesk = NULL;
  274. HWINSTA hwinsta = NULL;
  275. HWINSTA hwinstaSave = NULL;
  276. if (fForce || !tlist->hwnd) {
  277. hProcess1 = OpenProcess( PROCESS_ALL_ACCESS, FALSE, (DWORD) (DWORD_PTR) tlist->dwProcessId );
  278. if (hProcess1)
  279. {
  280. hProcess2 = OpenProcess( PROCESS_ALL_ACCESS, FALSE, (DWORD) (DWORD_PTR) tlist->dwProcessId );
  281. if (hProcess2 == NULL)
  282. {
  283. // clean up memory already allocated
  284. CloseHandle( hProcess1 );
  285. return FALSE;
  286. }
  287. if (!TerminateProcess( hProcess2, 1 ))
  288. {
  289. CloseHandle( hProcess1 );
  290. CloseHandle( hProcess2 );
  291. return FALSE;
  292. }
  293. CloseHandle( hProcess1 );
  294. CloseHandle( hProcess2 );
  295. return TRUE;
  296. }
  297. }
  298. //
  299. // save the current windowstation
  300. //
  301. hwinstaSave = GetProcessWindowStation();
  302. //
  303. // save the current desktop
  304. //
  305. hdeskSave = GetThreadDesktop( GetCurrentThreadId() );
  306. //
  307. // open the windowstation
  308. //
  309. hwinsta = OpenWindowStationA( tlist->lpWinsta, FALSE, MAXIMUM_ALLOWED );
  310. if (!hwinsta) {
  311. return FALSE;
  312. }
  313. //
  314. // change the context to the new windowstation
  315. //
  316. SetProcessWindowStation( hwinsta );
  317. //
  318. // open the desktop
  319. //
  320. hdesk = OpenDesktopA( tlist->lpDesk, 0, FALSE, MAXIMUM_ALLOWED );
  321. if (!hdesk) {
  322. return FALSE;
  323. }
  324. //
  325. // change the context to the new desktop
  326. //
  327. SetThreadDesktop( hdesk );
  328. //
  329. // kill the process
  330. //
  331. PostMessage( (HWND) tlist->hwnd, WM_CLOSE, 0, 0 );
  332. //
  333. // restore the previous desktop
  334. //
  335. if (hdesk != hdeskSave) {
  336. SetThreadDesktop( hdeskSave );
  337. CloseDesktop( hdesk );
  338. }
  339. //
  340. // restore the context to the previous windowstation
  341. //
  342. if (hwinsta != hwinstaSave) {
  343. SetProcessWindowStation( hwinstaSave );
  344. CloseWindowStation( hwinsta );
  345. }
  346. return TRUE;
  347. }
  348. VOID GetWindowTitles(PTASK_LIST_ENUM te)
  349. {
  350. //
  351. // enumerate all windows and try to get the window
  352. // titles for each task
  353. //
  354. EnumWindowStations( (WINSTAENUMPROC) EnumWindowStationsFunc, (LPARAM)te );
  355. }
  356. /*++
  357. Routine Description:
  358. Callback function for windowstation enumeration.
  359. Arguments:
  360. lpstr - windowstation name
  361. lParam - ** not used **
  362. Return Value:
  363. TRUE - continues the enumeration
  364. --*/
  365. BOOL CALLBACK EnumWindowStationsFunc(LPSTR lpstr,LPARAM lParam)
  366. {
  367. PTASK_LIST_ENUM te = (PTASK_LIST_ENUM)lParam;
  368. HWINSTA hwinsta;
  369. HWINSTA hwinstaSave;
  370. //
  371. // open the windowstation
  372. //
  373. hwinsta = OpenWindowStationA( lpstr, FALSE, MAXIMUM_ALLOWED );
  374. if (!hwinsta) {
  375. return FALSE;
  376. }
  377. //
  378. // save the current windowstation
  379. //
  380. hwinstaSave = GetProcessWindowStation();
  381. //
  382. // change the context to the new windowstation
  383. //
  384. SetProcessWindowStation( hwinsta );
  385. te->lpWinsta = _strdup( lpstr );
  386. //
  387. // enumerate all the desktops for this windowstation
  388. //
  389. EnumDesktops( hwinsta, (DESKTOPENUMPROC) EnumDesktopsFunc, lParam );
  390. //
  391. // restore the context to the previous windowstation
  392. //
  393. if (hwinsta != hwinstaSave) {
  394. SetProcessWindowStation( hwinstaSave );
  395. CloseWindowStation( hwinsta );
  396. }
  397. //
  398. // continue the enumeration
  399. //
  400. return TRUE;
  401. }
  402. /*++
  403. Routine Description:
  404. Callback function for desktop enumeration.
  405. Arguments:
  406. lpstr - desktop name
  407. lParam - ** not used **
  408. Return Value:
  409. TRUE - continues the enumeration
  410. --*/
  411. BOOL CALLBACK EnumDesktopsFunc(LPSTR lpstr,LPARAM lParam)
  412. {
  413. PTASK_LIST_ENUM te = (PTASK_LIST_ENUM)lParam;
  414. HDESK hdeskSave;
  415. HDESK hdesk;
  416. //
  417. // open the desktop
  418. //
  419. hdesk = OpenDesktopA( lpstr, 0, FALSE, MAXIMUM_ALLOWED );
  420. if (!hdesk) {
  421. return FALSE;
  422. }
  423. //
  424. // save the current desktop
  425. //
  426. hdeskSave = GetThreadDesktop( GetCurrentThreadId() );
  427. //
  428. // change the context to the new desktop
  429. //
  430. SetThreadDesktop( hdesk );
  431. te->lpDesk = _strdup( lpstr );
  432. //
  433. // enumerate all windows in the new desktop
  434. //
  435. ((PTASK_LIST_ENUM)lParam)->bFirstLoop = TRUE;
  436. EnumWindows( (WNDENUMPROC)EnumWindowsProc, lParam );
  437. ((PTASK_LIST_ENUM)lParam)->bFirstLoop = FALSE;
  438. EnumWindows( (WNDENUMPROC)EnumWindowsProc, lParam );
  439. //
  440. // restore the previous desktop
  441. //
  442. if (hdesk != hdeskSave) {
  443. SetThreadDesktop( hdeskSave );
  444. CloseDesktop( hdesk );
  445. }
  446. return TRUE;
  447. }
  448. /*++
  449. Routine Description:
  450. Callback function for window enumeration.
  451. Arguments:
  452. hwnd - window handle
  453. lParam - pte
  454. Return Value:
  455. TRUE - continues the enumeration
  456. --*/
  457. BOOL CALLBACK EnumWindowsProc(HWND hwnd,LPARAM lParam)
  458. {
  459. DWORD pid = 0;
  460. DWORD i;
  461. CHAR buf[TITLE_SIZE];
  462. PTASK_LIST_ENUM te = (PTASK_LIST_ENUM)lParam;
  463. PTASK_LIST tlist = te->tlist;
  464. DWORD numTasks = te->numtasks;
  465. //
  466. // Use try/except block when enumerating windows,
  467. // as a window may be destroyed by another thread
  468. // when being enumerated.
  469. //
  470. //try {
  471. //
  472. // get the processid for this window
  473. //
  474. if (!GetWindowThreadProcessId( hwnd, &pid )) {
  475. return TRUE;
  476. }
  477. if ((GetWindow( hwnd, GW_OWNER )) ||
  478. (!(GetWindowLong(hwnd, GWL_STYLE) & WS_VISIBLE)) && te->bFirstLoop) {
  479. //
  480. // not a top level window
  481. //
  482. return TRUE;
  483. }
  484. //
  485. // look for the task in the task list for this window
  486. // If this is the second time let invisible windows through if we don't
  487. // have a window already
  488. //
  489. for (i=0; i<numTasks; i++) {
  490. if (((DWORD) (DWORD_PTR)tlist[i].dwProcessId == pid) && (te->bFirstLoop || (tlist[i].hwnd == 0))) {
  491. tlist[i].hwnd = hwnd;
  492. tlist[i].lpWinsta = te->lpWinsta;
  493. tlist[i].lpDesk = te->lpDesk;
  494. //
  495. // we found the task no lets try to get the
  496. // window text
  497. //
  498. if (GetWindowTextA( (HWND) tlist[i].hwnd, buf, sizeof(buf) )) {
  499. //
  500. // go it, so lets save it
  501. //
  502. lstrcpyA( tlist[i].WindowTitle, buf );
  503. }
  504. break;
  505. }
  506. }
  507. //} except(EXCEPTION_EXECUTE_HANDLER) {
  508. //}
  509. //
  510. // continue the enumeration
  511. //
  512. return TRUE;
  513. }
  514. BOOL MatchPattern(PUCHAR String,PUCHAR Pattern)
  515. {
  516. UCHAR c, p, l;
  517. for (; ;) {
  518. switch (p = *Pattern++) {
  519. case 0: // end of pattern
  520. return *String ? FALSE : TRUE; // if end of string TRUE
  521. case '*':
  522. while (*String) { // match zero or more char
  523. if (MatchPattern (String++, Pattern))
  524. return TRUE;
  525. }
  526. return MatchPattern (String, Pattern);
  527. case '?':
  528. if (*String++ == 0) // match any one char
  529. return FALSE; // not end of string
  530. break;
  531. case '[':
  532. if ( (c = *String++) == 0) // match char set
  533. return FALSE; // syntax
  534. c = (UCHAR)toupper(c);
  535. l = 0;
  536. while (p = *Pattern++) {
  537. if (p == ']') // if end of char set, then
  538. return FALSE; // no match found
  539. if (p == '-') { // check a range of chars?
  540. p = *Pattern; // get high limit of range
  541. if (p == 0 || p == ']')
  542. return FALSE; // syntax
  543. if (c >= l && c <= p)
  544. break; // if in range, move on
  545. }
  546. l = p;
  547. if (c == p) // if char matches this element
  548. break; // move on
  549. }
  550. while (p && p != ']') // got a match in char set
  551. p = *Pattern++; // skip to end of set
  552. break;
  553. default:
  554. c = *String++;
  555. if (toupper(c) != p) // check for exact char
  556. return FALSE; // not a match
  557. break;
  558. }
  559. }
  560. }
  561. struct _ProcessIDStruct
  562. {
  563. DWORD pid;
  564. CHAR pname[MAX_PATH];
  565. } g_Arguments[ 64 ];
  566. DWORD g_dwNumberOfArguments;
  567. int _cdecl KillProcessNameReturn0(CHAR *ProcessNameToKill)
  568. {
  569. DWORD i, j;
  570. DWORD numTasks;
  571. TASK_LIST_ENUM te;
  572. int rval = 0;
  573. CHAR tname[PROCESS_SIZE];
  574. LPSTR p;
  575. DWORD ThisPid;
  576. BOOL iForceKill = TRUE;
  577. TASK_LIST The_TList[MAX_TASKS];
  578. g_dwNumberOfArguments = 0;
  579. //
  580. // Get the process name into the array
  581. //
  582. g_Arguments[g_dwNumberOfArguments].pid = 0;
  583. // make sure there is no path specified.
  584. char pfilename_only[_MAX_FNAME];
  585. char pextention_only[_MAX_EXT];
  586. _splitpath( ProcessNameToKill, NULL, NULL, pfilename_only, pextention_only);
  587. if (pextention_only) {strcat(pfilename_only,pextention_only);}
  588. // make it uppercase
  589. char *copy1 = _strupr(_strdup(pfilename_only));
  590. lstrcpyA(g_Arguments[g_dwNumberOfArguments].pname, copy1);
  591. free( copy1 );
  592. g_dwNumberOfArguments += 1;
  593. //
  594. // lets be god
  595. //
  596. EnableDebugPriv();
  597. //
  598. // get the task list for the system
  599. //
  600. numTasks = GetTaskList( The_TList, MAX_TASKS );
  601. //
  602. // enumerate all windows and try to get the window
  603. // titles for each task
  604. //
  605. te.tlist = The_TList;
  606. te.numtasks = numTasks;
  607. GetWindowTitles( &te );
  608. ThisPid = GetCurrentProcessId();
  609. for (i=0; i<numTasks; i++) {
  610. //
  611. // this prevents the user from killing KILL.EXE and
  612. // it's parent cmd window too
  613. //
  614. if (ThisPid == (DWORD) (DWORD_PTR) The_TList[i].dwProcessId) {
  615. continue;
  616. }
  617. if (MatchPattern( (PUCHAR) The_TList[i].WindowTitle, (PUCHAR) "*KILL*" )) {
  618. continue;
  619. }
  620. tname[0] = 0;
  621. lstrcpyA( tname, The_TList[i].ProcessName );
  622. p = strchr( tname, '.' );
  623. if (p) {
  624. p[0] = '\0';
  625. }
  626. for (j=0; j<g_dwNumberOfArguments; j++) {
  627. if (g_Arguments[j].pname) {
  628. if (MatchPattern( (PUCHAR) tname, (PUCHAR) g_Arguments[j].pname )) {
  629. The_TList[i].flags = TRUE;
  630. } else if (MatchPattern( (PUCHAR) The_TList[i].ProcessName, (PUCHAR) g_Arguments[j].pname )) {
  631. The_TList[i].flags = TRUE;
  632. } else if (MatchPattern( (PUCHAR) The_TList[i].WindowTitle, (PUCHAR) g_Arguments[j].pname )) {
  633. The_TList[i].flags = TRUE;
  634. }
  635. } else if (g_Arguments[j].pid) {
  636. if ((DWORD) (DWORD_PTR) The_TList[i].dwProcessId == g_Arguments[j].pid) {
  637. The_TList[i].flags = TRUE;
  638. }
  639. }
  640. }
  641. }
  642. for (i=0; i<numTasks; i++)
  643. {
  644. if (The_TList[i].flags)
  645. {
  646. if (KillProcess( &The_TList[i], iForceKill ))
  647. {
  648. //printf( "process %s (%d) - '%s' killed\n", The_TList[i].ProcessName,The_TList[i].dwProcessId,The_TList[i].hwnd ? The_TList[i].WindowTitle : "");
  649. }
  650. else
  651. {
  652. //printf( "process %s (%d) - '%s' could not be killed\n",The_TList[i].ProcessName,The_TList[i].dwProcessId,The_TList[i].hwnd ? The_TList[i].WindowTitle : "");
  653. rval = 1;
  654. }
  655. }
  656. }
  657. FreeTaskListMem();
  658. return rval;
  659. }