script_autoit.cpp 70 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993
  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. // AutoIt
  4. //
  5. // Copyright (C)1999-2006:
  6. // - Jonathan Bennett <jon@hiddensoft.com>
  7. // - Others listed at https://www.autoitscript.com/autoit3/docs/credits.htm
  8. // - Chris Mallett (support@autohotkey.com): various enhancements and
  9. // adaptation of this file's functions to interface with AutoHotkey.
  10. //
  11. // This program is free software; you can redistribute it and/or modify
  12. // it under the terms of the GNU General Public License as published by
  13. // the Free Software Foundation; either version 2 of the License, or
  14. // (at your option) any later version.
  15. //
  16. ///////////////////////////////////////////////////////////////////////////////
  17. #include "stdafx.h" // pre-compiled headers
  18. //#include <winsock.h> // for WSADATA. This also requires wsock32.lib to be linked in.
  19. #include <winsock2.h>
  20. #include <tlhelp32.h> // For the ProcessExist routines.
  21. #include <wininet.h> // For Download().
  22. #include "script.h"
  23. #include "globaldata.h"
  24. #include "window.h" // For ControlExist().
  25. #include "application.h" // For SLEEP_WITHOUT_INTERRUPTION and MsgSleep().
  26. #include "script_func_impl.h"
  27. #include "abi.h"
  28. #include <Psapi.h>
  29. ResultType Script::DoRunAs(LPTSTR aCommandLine, LPCTSTR aWorkingDir, bool aDisplayErrors, WORD aShowWindow
  30. , PROCESS_INFORMATION &aPI, bool &aSuccess // Output parameters we set for caller, but caller must have initialized aSuccess to false.
  31. , HANDLE &aNewProcess, DWORD &aLastError) // Same, but initialize to NULL.
  32. {
  33. typedef BOOL (WINAPI *MyCreateProcessWithLogonW)(
  34. LPCWSTR lpUsername, // user's name
  35. LPCWSTR lpDomain, // user's domain
  36. LPCWSTR lpPassword, // user's password
  37. DWORD dwLogonFlags, // logon option
  38. LPCWSTR lpApplicationName, // executable module name
  39. LPWSTR lpCommandLine, // command-line string
  40. DWORD dwCreationFlags, // creation flags
  41. LPVOID lpEnvironment, // new environment block
  42. LPCWSTR lpCurrentDirectory, // current directory name
  43. LPSTARTUPINFOW lpStartupInfo, // startup information
  44. LPPROCESS_INFORMATION lpProcessInfo // process information
  45. );
  46. // Set up wide char version that we need for CreateProcessWithLogon
  47. // init structure for running programs (wide char version)
  48. STARTUPINFOW wsi = {0};
  49. wsi.cb = sizeof(STARTUPINFOW);
  50. wsi.dwFlags = STARTF_USESHOWWINDOW;
  51. wsi.wShowWindow = aShowWindow;
  52. // The following are left initialized to 0/NULL (initialized earlier above):
  53. //wsi.lpReserved = NULL;
  54. //wsi.lpDesktop = NULL;
  55. //wsi.lpTitle = NULL;
  56. //wsi.cbReserved2 = 0;
  57. //wsi.lpReserved2 = NULL;
  58. #ifndef UNICODE
  59. // Convert to wide character format:
  60. WCHAR command_line_wide[LINE_SIZE], working_dir_wide[MAX_PATH];
  61. ToWideChar(aCommandLine, command_line_wide, LINE_SIZE); // Dest. size is in wchars, not bytes.
  62. if (aWorkingDir && *aWorkingDir)
  63. ToWideChar(aWorkingDir, working_dir_wide, MAX_PATH); // Dest. size is in wchars, not bytes.
  64. else
  65. *working_dir_wide = 0; // wide-char terminator.
  66. if (lpfnDLLProc(mRunAsUser, mRunAsDomain, mRunAsPass, LOGON_WITH_PROFILE, 0
  67. , command_line_wide, 0, 0, *working_dir_wide ? working_dir_wide : NULL, &wsi, &aPI))
  68. #else
  69. if (CreateProcessWithLogonW(mRunAsUser, mRunAsDomain, mRunAsPass, LOGON_WITH_PROFILE, 0
  70. , aCommandLine, 0, 0, aWorkingDir && *aWorkingDir ? aWorkingDir : NULL, &wsi, &aPI))
  71. #endif
  72. {
  73. aSuccess = true;
  74. if (aPI.hThread)
  75. CloseHandle(aPI.hThread); // Required to avoid memory leak.
  76. aNewProcess = aPI.hProcess;
  77. }
  78. else
  79. aLastError = GetLastError(); // Caller will use this to get an error message and set g->LastError if needed.
  80. return OK;
  81. }
  82. bif_impl void RunAs(optl<StrArg> aUser, optl<StrArg> aPass, optl<StrArg> aDomain)
  83. {
  84. StringTCharToWChar(aUser.value_or_empty(), g_script.mRunAsUser);
  85. StringTCharToWChar(aPass.value_or_empty(), g_script.mRunAsPass);
  86. StringTCharToWChar(aDomain.value_or_empty(), g_script.mRunAsDomain);
  87. }
  88. bif_impl FResult SysGetIPAddresses(IObject *&aRetVal)
  89. {
  90. // aaa.bbb.ccc.ddd = 15, but allow room for larger IP's in the future.
  91. #define IP_ADDRESS_SIZE 32 // The maximum size of any of the strings we return, including terminator.
  92. auto addresses = Array::Create();
  93. if (!addresses)
  94. return FR_E_OUTOFMEM;
  95. WSADATA wsadata;
  96. if (WSAStartup(MAKEWORD(1, 1), &wsadata)) // Failed (it returns 0 on success).
  97. {
  98. aRetVal = addresses;
  99. return OK;
  100. }
  101. char host_name[256];
  102. HOSTENT *lpHost = nullptr;
  103. // gethostname would probably only fail in exceptional circumstances, such as a damaged OS install
  104. // where networking is very broken. Running in safe mode without networking was confirmed to cause
  105. // gethostbyname to return NULL.
  106. if (gethostname(host_name, _countof(host_name)) == 0)
  107. lpHost = gethostbyname(host_name);
  108. if (lpHost)
  109. for (int i = 0; lpHost->h_addr_list[i]; ++i)
  110. {
  111. IN_ADDR inaddr;
  112. memcpy(&inaddr, lpHost->h_addr_list[i], 4);
  113. #ifdef UNICODE
  114. CStringTCharFromChar addr_buf(inet_ntoa(inaddr));
  115. LPTSTR addr_str = const_cast<LPTSTR>(addr_buf.GetString());
  116. #else
  117. LPTSTR addr_str = inet_ntoa(inaddr);
  118. #endif
  119. if (!addresses->Append(addr_str))
  120. {
  121. addresses->Release();
  122. WSACleanup();
  123. return FR_E_OUTOFMEM;
  124. }
  125. }
  126. WSACleanup();
  127. aRetVal = addresses;
  128. return OK;
  129. }
  130. BIV_DECL_R(BIV_IsAdmin)
  131. {
  132. bool result = false; // Default.
  133. SC_HANDLE h = OpenSCManager(NULL, NULL, SC_MANAGER_LOCK);
  134. if (h)
  135. {
  136. SC_LOCK lock = LockServiceDatabase(h);
  137. if (lock)
  138. {
  139. UnlockServiceDatabase(lock);
  140. result = true; // Current user is admin.
  141. }
  142. else
  143. {
  144. DWORD lastErr = GetLastError();
  145. if (lastErr == ERROR_SERVICE_DATABASE_LOCKED)
  146. result = true; // Current user is admin.
  147. }
  148. CloseServiceHandle(h);
  149. }
  150. _f_return_b(result);
  151. }
  152. bif_impl FResult PixelGetColor(int aX, int aY, optl<StrArg> aMode, StrRet &aRetVal)
  153. {
  154. LPTSTR buf = aRetVal.CallerBuf();
  155. aRetVal.SetTemp(buf, 8);
  156. auto aOptions = aMode.value_or_empty();
  157. if (tcscasestr(aOptions, _T("Slow"))) // New mode for v1.0.43.10. Takes precedence over Alt mode.
  158. {
  159. return PixelSearch(nullptr, nullptr, nullptr, aX, aY, aX, aY, 0, 0, buf); // It takes care of setting the return value.
  160. }
  161. CoordToScreen(aX, aY, COORD_MODE_PIXEL);
  162. bool use_alt_mode = tcscasestr(aOptions, _T("Alt")) != NULL; // New mode for v1.0.43.10: Two users reported that CreateDC works better in certain windows such as SciTE, at least one some systems.
  163. HDC hdc = use_alt_mode ? CreateDC(_T("DISPLAY"), NULL, NULL, NULL) : GetDC(NULL);
  164. if (!hdc)
  165. return FR_E_WIN32;
  166. // Assign the value as an 32-bit int to match Window Spy reports color values.
  167. // Update for v1.0.21: Assigning in hex format seems much better, since it's easy to
  168. // look at a hex BGR value to get some idea of the hue. In addition, the result
  169. // is zero padded to make it easier to convert to RGB and more consistent in
  170. // appearance:
  171. COLORREF color = GetPixel(hdc, aX, aY);
  172. if (use_alt_mode)
  173. DeleteDC(hdc);
  174. else
  175. ReleaseDC(NULL, hdc);
  176. _stprintf(buf, _T("0x%06X"), bgr_to_rgb(color));
  177. return OK;
  178. }
  179. bif_impl FResult MenuSelect(ExprTokenType *aWinTitle, optl<StrArg> aWinText, StrArg aMenu
  180. , optl<StrArg> a1, optl<StrArg> a2, optl<StrArg> a3, optl<StrArg> a4, optl<StrArg> a5, optl<StrArg> a6
  181. , optl<StrArg> aExcludeTitle, optl<StrArg> aExcludeText)
  182. {
  183. HWND target_window;
  184. DETERMINE_TARGET_WINDOW;
  185. LPCTSTR names[] { aMenu, a1.value_or_null(), a2.value_or_null(), a3.value_or_null(), a4.value_or_null(), a5.value_or_null(), a6.value_or_null() };
  186. int name_index = 0, name_count = 0;
  187. // Count up to the last present name parameter.
  188. for (int i = 0; i < _countof(names); ++i)
  189. if (names[i])
  190. name_count = i + 1;
  191. // Any omitted parameters preceding name_count will be detected by the main loop as invalid.
  192. UINT message = WM_COMMAND;
  193. HMENU hMenu;
  194. if (!_tcsicmp(names[0], _T("0&")))
  195. {
  196. hMenu = GetSystemMenu(target_window, FALSE);
  197. name_index += 1;
  198. message = WM_SYSCOMMAND;
  199. }
  200. else
  201. hMenu = GetMenu(target_window);
  202. if (!hMenu) // Window has no menu bar.
  203. return FError(ERR_WINDOW_HAS_NO_MENU, nullptr, ErrorPrototype::Target);
  204. int menu_item_count = GetMenuItemCount(hMenu);
  205. //if (menu_item_count < 1) // Menu bar has no menus.
  206. // goto error;
  207. #define MENU_ITEM_IS_SUBMENU 0xFFFFFFFF
  208. #define UPDATE_MENU_VARS(menu_pos) \
  209. menu_id = GetMenuItemID(hMenu, menu_pos);\
  210. if (menu_id == MENU_ITEM_IS_SUBMENU)\
  211. menu_item_count = GetMenuItemCount(hMenu = GetSubMenu(hMenu, menu_pos));\
  212. else\
  213. {\
  214. menu_item_count = 0;\
  215. hMenu = NULL;\
  216. }
  217. UINT menu_id = MENU_ITEM_IS_SUBMENU;
  218. TCHAR menu_text[1024];
  219. bool match_found;
  220. size_t this_menu_param_length, menu_text_length;
  221. int pos, target_menu_pos;
  222. LPCTSTR this_menu_param = nullptr;
  223. for ( ; name_index < name_count; ++name_index)
  224. {
  225. if (!hMenu) // The nesting of submenus ended prior to the end of the list of menu search terms.
  226. break;
  227. this_menu_param = names[name_index];
  228. if (!this_menu_param || !*this_menu_param)
  229. break;
  230. this_menu_param_length = _tcslen(this_menu_param);
  231. target_menu_pos = (this_menu_param[this_menu_param_length - 1] == '&') ? ATOI(this_menu_param) - 1 : -1;
  232. if (target_menu_pos > -1)
  233. {
  234. if (target_menu_pos >= menu_item_count) // Invalid menu position (doesn't exist).
  235. break;
  236. UPDATE_MENU_VARS(target_menu_pos)
  237. }
  238. else // Searching by text rather than numerical position.
  239. {
  240. for (match_found = false, pos = 0; pos < menu_item_count; ++pos)
  241. {
  242. menu_text_length = GetMenuString(hMenu, pos, menu_text, _countof(menu_text) - 1, MF_BYPOSITION);
  243. // v1.0.43.03: It's debatable, but it seems best to support locale's case insensitivity for
  244. // menu items, since menu names tend to adapt to the user's locale. By contrast, things
  245. // like process names (in the Process command) do not tend to change, so it seems best to
  246. // have them continue to use stricmp(): 1) avoids breaking existing scripts; 2) provides
  247. // consistent behavior across multiple locales; 3) performance.
  248. match_found = !lstrcmpni(menu_text // This call is basically a strnicmp() that obeys locale.
  249. , menu_text_length > this_menu_param_length ? this_menu_param_length : menu_text_length
  250. , this_menu_param, this_menu_param_length);
  251. //match_found = strcasestr(menu_text, this_menu_param);
  252. TCHAR *cpamp;
  253. if (!match_found && (cpamp = _tcschr(menu_text, '&')))
  254. {
  255. // Try again to find a match, this time without the ampersands used to indicate a menu item's
  256. // shortcut key. One might assume that only the first & needs to be removed, but the actual
  257. // behaviour confirmed on Windows 10.0.19041 was:
  258. // - Only every second & in a series of consecutive &&s was kept.
  259. // - Only the *first* &-letter was usable as a shortcut key.
  260. // - Only the *last* & caused an underline.
  261. for (TCHAR *cplit = cpamp; ; ++cpamp, ++cplit)
  262. {
  263. if (*cpamp == '&')
  264. ++cpamp; // Skip this '&' but copy the next character unconditionally, even if it is '&'.
  265. *cplit = *cpamp;
  266. if (!*cpamp)
  267. break; // Only after copying, so menu_text is null-terminated at the correct position (cpw).
  268. }
  269. menu_text_length = _tcslen(menu_text);
  270. match_found = !lstrcmpni(menu_text // This call is basically a strnicmp() that obeys locale.
  271. , menu_text_length > this_menu_param_length ? this_menu_param_length : menu_text_length
  272. , this_menu_param, this_menu_param_length);
  273. //match_found = strcasestr(menu_text, this_menu_param);
  274. }
  275. if (match_found)
  276. {
  277. UPDATE_MENU_VARS(pos)
  278. break;
  279. }
  280. } // inner for()
  281. if (!match_found) // The search hierarchy (nested menus) specified in the params could not be found.
  282. break;
  283. } // else
  284. } // outer for()
  285. if (name_index < name_count)
  286. return FR_E_ARG(2 + name_index);
  287. // This would happen if the outer loop above had zero iterations due to aMenu1 being NULL or blank,
  288. // or if the caller specified a submenu as the target (which doesn't seem valid since an app would
  289. // never expect to ever receive a message for a submenu?):
  290. if (menu_id == MENU_ITEM_IS_SUBMENU)
  291. return FR_E_ARG(2 + name_count - 1);
  292. // Since the above didn't return, the specified search hierarchy was completely found.
  293. PostMessage(target_window, message, (WPARAM)menu_id, 0);
  294. return OK;
  295. }
  296. bif_impl FResult ControlSetChecked(int aValue, CONTROL_PARAMETERS_DECL)
  297. {
  298. if (aValue < -1 || aValue > 1)
  299. return FR_E_ARG(0);
  300. DETERMINE_TARGET_CONTROL2;
  301. DWORD_PTR dwResult;
  302. if (aValue != -1)
  303. {
  304. if (!SendMessageTimeout(control_window, BM_GETCHECK, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  305. return FR_E_WIN32;
  306. if (dwResult == (aValue ? BST_CHECKED : BST_UNCHECKED)) // It's already in the right state, so don't press it.
  307. return OK;
  308. }
  309. // MSDN docs for BM_CLICK (and au3 author says it applies to this situation also):
  310. // "If the button is in a dialog box and the dialog box is not active, the BM_CLICK message
  311. // might fail. To ensure success in this situation, call the SetActiveWindow function to activate
  312. // the dialog box before sending the BM_CLICK message to the button."
  313. ATTACH_THREAD_INPUT
  314. SetActiveWindow(target_window == control_window ? GetNonChildParent(control_window) : target_window); // v1.0.44.13: Fixed to allow for the fact that target_window might be the control itself (e.g. via ahk_id %ControlHWND%).
  315. RECT rect;
  316. if (!GetWindowRect(control_window, &rect)) // au3: Code to primary click the centre of the control
  317. rect.bottom = rect.left = rect.right = rect.top = 0;
  318. auto lparam = MAKELPARAM((rect.right - rect.left) / 2, (rect.bottom - rect.top) / 2);
  319. FResult fr = (
  320. PostMessage(control_window, WM_LBUTTONDOWN, MK_LBUTTON, lparam) &&
  321. PostMessage(control_window, WM_LBUTTONUP, 0, lparam)
  322. ) ? OK : FR_E_WIN32(GetLastError());
  323. DETACH_THREAD_INPUT
  324. DoControlDelay;
  325. return fr;
  326. }
  327. bif_impl FResult ControlGetChecked(CONTROL_PARAMETERS_DECL, BOOL &aRetVal)
  328. {
  329. DETERMINE_TARGET_CONTROL2;
  330. DWORD_PTR dwResult;
  331. if (!SendMessageTimeout(control_window, BM_GETCHECK, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  332. return FR_E_WIN32;
  333. aRetVal = dwResult == BST_CHECKED;
  334. return OK;
  335. }
  336. FResult WinSetStyle(StrArg aValue, CONTROL_PARAMETERS_DECL_OPT, int style_index);
  337. bif_impl FResult ControlSetStyle(StrArg aValue, CONTROL_PARAMETERS_DECL)
  338. {
  339. return WinSetStyle(aValue, &CONTROL_PARAMETERS, GWL_STYLE);
  340. }
  341. bif_impl FResult ControlSetExStyle(StrArg aValue, CONTROL_PARAMETERS_DECL)
  342. {
  343. return WinSetStyle(aValue, &CONTROL_PARAMETERS, GWL_EXSTYLE);
  344. }
  345. static FResult ControlGetLong(CONTROL_PARAMETERS_DECL, UINT &aRetVal, int nIndex)
  346. {
  347. DETERMINE_TARGET_CONTROL2;
  348. aRetVal = GetWindowLong(control_window, nIndex);
  349. return OK;
  350. }
  351. bif_impl FResult ControlGetStyle(CONTROL_PARAMETERS_DECL, UINT &aRetVal)
  352. {
  353. return ControlGetLong(CONTROL_PARAMETERS, aRetVal, GWL_STYLE);
  354. }
  355. bif_impl FResult ControlGetExStyle(CONTROL_PARAMETERS_DECL, UINT &aRetVal)
  356. {
  357. return ControlGetLong(CONTROL_PARAMETERS, aRetVal, GWL_EXSTYLE);
  358. }
  359. static FResult ControlShowDropDown(CONTROL_PARAMETERS_DECL, BOOL aShow)
  360. {
  361. DETERMINE_TARGET_CONTROL2;
  362. DWORD_PTR dwResult;
  363. // CB_SHOWDROPDOWN: Although the return value (dwResult) is always TRUE, SendMessageTimeout()
  364. // will return failure if it times out:
  365. if (!SendMessageTimeout(control_window, CB_SHOWDROPDOWN
  366. , (WPARAM)aShow, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  367. return FR_E_WIN32;
  368. DoControlDelay;
  369. return OK;
  370. }
  371. bif_impl FResult ControlShowDropDown(CONTROL_PARAMETERS_DECL)
  372. {
  373. return ControlShowDropDown(CONTROL_PARAMETERS, TRUE);
  374. }
  375. bif_impl FResult ControlHideDropDown(CONTROL_PARAMETERS_DECL)
  376. {
  377. return ControlShowDropDown(CONTROL_PARAMETERS, FALSE);
  378. }
  379. enum IndexControlType {
  380. IC_INVALID = 0,
  381. IC_COMBO,
  382. IC_LIST,
  383. IC_TAB
  384. };
  385. static IndexControlType GetIndexControlType(HWND aControl, FResult &fr, bool aAllowTab = false)
  386. {
  387. TCHAR classname[256 + 1];
  388. GetClassName(aControl, classname, _countof(classname));
  389. if (tcscasestr(classname, _T("Combo")))
  390. return IC_COMBO;
  391. else if (tcscasestr(classname, _T("List")))
  392. return IC_LIST;
  393. else if (aAllowTab && tcscasestr(classname, _T("Tab")))
  394. return IC_TAB;
  395. fr = FError(ERR_GUI_NOT_FOR_THIS_TYPE, classname, ErrorPrototype::Target);
  396. return IC_INVALID;
  397. }
  398. bif_impl FResult ControlAddItem(StrArg aItem, CONTROL_PARAMETERS_DECL, INT_PTR &aRetVal)
  399. {
  400. DETERMINE_TARGET_CONTROL2;
  401. UINT msg;
  402. FResult fr = FAIL;
  403. switch (GetIndexControlType(control_window, fr))
  404. {
  405. case IC_COMBO: msg = CB_ADDSTRING; break;
  406. case IC_LIST: msg = LB_ADDSTRING; break;
  407. default: return fr;
  408. }
  409. DWORD_PTR dwResult;
  410. if (!SendMessageTimeout(control_window, msg, 0, (LPARAM)aItem, SMTO_ABORTIFHUNG, 2000, &dwResult))
  411. return FR_E_WIN32;
  412. if (dwResult == CB_ERR || dwResult == CB_ERRSPACE) // General error or insufficient space to store it.
  413. // CB_ERR == LB_ERR
  414. return FR_E_FAILED;
  415. DoControlDelay;
  416. aRetVal = dwResult + 1; // Return the one-based index of the new item.
  417. return OK;
  418. }
  419. bif_impl FResult ControlDeleteItem(INT_PTR aIndex, CONTROL_PARAMETERS_DECL)
  420. {
  421. if (--aIndex < 0)
  422. return FR_E_ARG(0);
  423. DETERMINE_TARGET_CONTROL2;
  424. UINT msg;
  425. FResult fr = FAIL;
  426. switch (GetIndexControlType(control_window, fr))
  427. {
  428. case IC_COMBO: msg = CB_DELETESTRING; break;
  429. case IC_LIST: msg = LB_DELETESTRING; break;
  430. default: return fr;
  431. }
  432. DWORD_PTR dwResult;
  433. if (!SendMessageTimeout(control_window, msg, (WPARAM)aIndex, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  434. return FR_E_WIN32;
  435. if (dwResult == CB_ERR) // CB_ERR == LB_ERR
  436. return FR_E_FAILED;
  437. DoControlDelay;
  438. return OK;
  439. }
  440. static FResult ControlNotifyParent(HWND control_window, UINT x_msg, UINT y_msg)
  441. {
  442. HWND immediate_parent;
  443. if ( !(immediate_parent = GetParent(control_window)) )
  444. return FR_E_WIN32;
  445. SetLastError(0); // Must be done to differentiate between success and failure when control has ID 0.
  446. auto control_id = GetDlgCtrlID(control_window);
  447. if (!control_id && GetLastError()) // Both conditions must be checked (see above).
  448. return FR_E_WIN32; // Avoid sending the notification in case some other control has ID 0.
  449. // Proceed even if control_id == 0, since some applications are known to
  450. // utilize the notification in that case (e.g. Notepad's Save As dialog).
  451. DWORD_PTR dwResult;
  452. if (!SendMessageTimeout(immediate_parent, WM_COMMAND, (WPARAM)MAKELONG(control_id, x_msg)
  453. , (LPARAM)control_window, SMTO_ABORTIFHUNG, 2000, &dwResult))
  454. return FR_E_WIN32;
  455. if (!SendMessageTimeout(immediate_parent, WM_COMMAND, (WPARAM)MAKELONG(control_id, y_msg)
  456. , (LPARAM)control_window, SMTO_ABORTIFHUNG, 2000, &dwResult))
  457. return FR_E_WIN32;
  458. DoControlDelay;
  459. return OK;
  460. }
  461. bif_impl FResult ControlChooseIndex(INT_PTR aIndex, CONTROL_PARAMETERS_DECL)
  462. {
  463. if (--aIndex < -1)
  464. return FR_E_ARG(0);
  465. DETERMINE_TARGET_CONTROL2;
  466. UINT msg, x_msg, y_msg;
  467. FResult fr = FAIL;
  468. switch (GetIndexControlType(control_window, fr, true))
  469. {
  470. case IC_COMBO:
  471. msg = CB_SETCURSEL;
  472. x_msg = CBN_SELCHANGE;
  473. y_msg = CBN_SELENDOK;
  474. break;
  475. case IC_LIST:
  476. if (GetWindowLong(control_window, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  477. msg = LB_SETSEL;
  478. else // single-select listbox
  479. msg = LB_SETCURSEL;
  480. x_msg = LBN_SELCHANGE;
  481. y_msg = LBN_DBLCLK;
  482. break;
  483. case IC_TAB:
  484. if (aIndex < 0)
  485. return FR_E_ARG(0);
  486. return ControlSetTab(control_window, (DWORD)aIndex);
  487. default:
  488. return fr;
  489. }
  490. DWORD_PTR dwResult;
  491. if (msg == LB_SETSEL) // Multi-select, so use the cumulative method.
  492. {
  493. if (!SendMessageTimeout(control_window, msg, aIndex != -1, aIndex, SMTO_ABORTIFHUNG, 2000, &dwResult))
  494. return FR_E_WIN32;
  495. }
  496. else // ComboBox or single-select ListBox.
  497. if (!SendMessageTimeout(control_window, msg, aIndex, 0, SMTO_ABORTIFHUNG, 2000, &dwResult))
  498. return FR_E_WIN32;
  499. if (dwResult == CB_ERR && aIndex != -1) // CB_ERR == LB_ERR
  500. return FR_E_FAILED;
  501. return ControlNotifyParent(control_window, x_msg, y_msg);
  502. }
  503. bif_impl FResult ControlChooseString(StrArg aValue, CONTROL_PARAMETERS_DECL, INT_PTR &aRetVal)
  504. {
  505. DETERMINE_TARGET_CONTROL2;
  506. UINT msg, x_msg, y_msg;
  507. FResult fr = FAIL;
  508. switch (GetIndexControlType(control_window, fr))
  509. {
  510. case IC_COMBO:
  511. msg = CB_SELECTSTRING;
  512. x_msg = CBN_SELCHANGE;
  513. y_msg = CBN_SELENDOK;
  514. break;
  515. case IC_LIST:
  516. if (GetWindowLong(control_window, GWL_STYLE) & (LBS_EXTENDEDSEL|LBS_MULTIPLESEL))
  517. msg = LB_FINDSTRING;
  518. else // single-select listbox
  519. msg = LB_SELECTSTRING;
  520. x_msg = LBN_SELCHANGE;
  521. y_msg = LBN_DBLCLK;
  522. break;
  523. default:
  524. return fr;
  525. }
  526. DWORD_PTR dwResult, item_index;
  527. if (msg == LB_FINDSTRING) // Multi-select ListBox (LB_SELECTSTRING is not supported by these).
  528. {
  529. if (!SendMessageTimeout(control_window, msg, -1, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &item_index))
  530. return FR_E_WIN32;
  531. if (item_index == LB_ERR)
  532. return FR_E_FAILED;
  533. if (!SendMessageTimeout(control_window, LB_SETSEL, TRUE, item_index, SMTO_ABORTIFHUNG, 2000, &dwResult))
  534. return FR_E_WIN32;
  535. if (dwResult == LB_ERR)
  536. return FR_E_FAILED;
  537. }
  538. else // ComboBox or single-select ListBox.
  539. {
  540. if (!SendMessageTimeout(control_window, msg, -1, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &item_index))
  541. return FR_E_WIN32;
  542. if (item_index == CB_ERR) // CB_ERR == LB_ERR
  543. return FR_E_FAILED;
  544. }
  545. aRetVal = item_index + 1; // Return the index chosen. Might have some use if the string was ambiguous.
  546. return ControlNotifyParent(control_window, x_msg, y_msg);
  547. }
  548. bif_impl FResult EditPaste(StrArg aValue, CONTROL_PARAMETERS_DECL)
  549. {
  550. DETERMINE_TARGET_CONTROL2;
  551. DWORD_PTR dwResult;
  552. if (!SendMessageTimeout(control_window, EM_REPLACESEL, TRUE, (LPARAM)aValue, SMTO_ABORTIFHUNG, 2000, &dwResult))
  553. return FR_E_WIN32;
  554. DoControlDelay;
  555. return OK;
  556. }
  557. bif_impl FResult ControlFindItem(StrArg aString, CONTROL_PARAMETERS_DECL, INT_PTR &aRetVal)
  558. {
  559. DETERMINE_TARGET_CONTROL2;
  560. UINT msg;
  561. FResult fr = FAIL;
  562. switch (GetIndexControlType(control_window, fr))
  563. {
  564. case IC_COMBO: msg = CB_FINDSTRINGEXACT; break;
  565. case IC_LIST: msg = LB_FINDSTRINGEXACT; break;
  566. default: return fr;
  567. }
  568. DWORD_PTR index;
  569. if (!SendMessageTimeout(control_window, msg, -1, (LPARAM)aString, SMTO_ABORTIFHUNG, 2000, &index))
  570. return FR_E_WIN32;
  571. if (index == CB_ERR) // CBERR == LB_ERR
  572. return FR_E_FAILED;
  573. aRetVal = index + 1;
  574. return OK;
  575. }
  576. bif_impl FResult ControlGetIndex(CONTROL_PARAMETERS_DECL, INT_PTR &aRetVal)
  577. {
  578. DETERMINE_TARGET_CONTROL2;
  579. UINT msg;
  580. FResult fr = FAIL;
  581. switch (GetIndexControlType(control_window, fr, true))
  582. {
  583. case IC_COMBO: msg = CB_GETCURSEL; break;
  584. case IC_LIST: msg = LB_GETCURSEL; break;
  585. case IC_TAB: msg = TCM_GETCURSEL; break;
  586. default: return fr;
  587. }
  588. DWORD_PTR index;
  589. if (!SendMessageTimeout(control_window, msg, 0, 0, SMTO_ABORTIFHUNG, 2000, &index))
  590. return FR_E_WIN32;
  591. aRetVal = index + 1;
  592. return OK;
  593. }
  594. bif_impl FResult ControlGetChoice(CONTROL_PARAMETERS_DECL, StrRet &aRetVal)
  595. {
  596. DETERMINE_TARGET_CONTROL2;
  597. UINT msg, x_msg, y_msg;
  598. FResult fr = FAIL;
  599. switch (GetIndexControlType(control_window, fr, true))
  600. {
  601. case IC_COMBO:
  602. msg = CB_GETCURSEL;
  603. x_msg = CB_GETLBTEXTLEN;
  604. y_msg = CB_GETLBTEXT;
  605. break;
  606. case IC_LIST:
  607. msg = LB_GETCURSEL;
  608. x_msg = LB_GETTEXTLEN;
  609. y_msg = LB_GETTEXT;
  610. break;
  611. default:
  612. return fr;
  613. }
  614. DWORD_PTR index, estimated_length, actual_length;
  615. if (!SendMessageTimeout(control_window, msg, 0, 0, SMTO_ABORTIFHUNG, 2000, &index))
  616. return FR_E_WIN32;
  617. if (index == CB_ERR) // CBERR == LB_ERR
  618. return FR_E_FAILED;
  619. if (!SendMessageTimeout(control_window, x_msg, (WPARAM)index, 0, SMTO_ABORTIFHUNG, 2000, &estimated_length))
  620. return FR_E_WIN32;
  621. if (estimated_length == CB_ERR)
  622. return FR_E_FAILED;
  623. // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  624. // being when the item's text is retrieved.
  625. auto buf = aRetVal.Alloc(estimated_length);
  626. if (!buf)
  627. return FR_E_OUTOFMEM;
  628. if (!SendMessageTimeout(control_window, y_msg, (WPARAM)index, (LPARAM)buf, SMTO_ABORTIFHUNG, 2000, &actual_length))
  629. return FR_E_WIN32;
  630. if (actual_length > estimated_length) // Guard against bogus return values. Also covers actual_length == CB_ERR due to unsigned type.
  631. return FR_E_FAILED;
  632. aRetVal.SetLength(actual_length); // Update to actual vs. estimated length.
  633. return OK;
  634. }
  635. static FResult ControlGetItems(Array *items, DWORD_PTR item_count, HWND control_window, UINT x_msg, UINT y_msg)
  636. {
  637. DWORD_PTR length, u, item_length;
  638. // Calculate the required buffer size for the largest string.
  639. for (length = 0, u = 0; u < item_count; ++u)
  640. {
  641. if (!SendMessageTimeout(control_window, x_msg, u, 0, SMTO_ABORTIFHUNG, 5000, &item_length)
  642. || item_length == LB_ERR) // Note that item_length is legitimately zero for a blank item in the list.
  643. return FR_E_FAILED;
  644. if (length < item_length)
  645. length = item_length;
  646. }
  647. // In unusual cases, MSDN says the indicated length might be longer than it actually winds up
  648. // being when the item's text is retrieved.
  649. ++length; // To include the null-terminator.
  650. // Could use alloca when length is within a safe limit, but it seems unlikely to help
  651. // performance much since the bottleneck is probably in message passing between processes.
  652. // Although length is only the size of the largest item, not the total of all items,
  653. // I don't know how large an item can be, so it's safest to just use malloc().
  654. LPTSTR dyn_buf;
  655. if ( !(dyn_buf = tmalloc(length)) )
  656. return FR_E_OUTOFMEM;
  657. for (u = 0; u < item_count; ++u)
  658. {
  659. if (!SendMessageTimeout(control_window, y_msg, (WPARAM)u, (LPARAM)dyn_buf, SMTO_ABORTIFHUNG, 5000, &item_length)
  660. || item_length > length) // Guard against bogus return values. Also covers item_length == CB_ERR due to unsigned type.
  661. break;
  662. dyn_buf[item_length] = '\0'; // Play it safe.
  663. if (!items->Append(dyn_buf))
  664. break;
  665. }
  666. free(dyn_buf);
  667. return u < item_count ? FR_E_FAILED : OK;
  668. }
  669. bif_impl FResult ControlGetItems(CONTROL_PARAMETERS_DECL, IObject *&aRetVal)
  670. {
  671. DETERMINE_TARGET_CONTROL2;
  672. UINT msg, x_msg, y_msg;
  673. FResult fr = FAIL;
  674. switch (GetIndexControlType(control_window, fr, true))
  675. {
  676. case IC_COMBO:
  677. msg = CB_GETCOUNT;
  678. x_msg = CB_GETLBTEXTLEN;
  679. y_msg = CB_GETLBTEXT;
  680. break;
  681. case IC_LIST:
  682. msg = LB_GETCOUNT;
  683. x_msg = LB_GETTEXTLEN;
  684. y_msg = LB_GETTEXT;
  685. break;
  686. default:
  687. return fr;
  688. }
  689. DWORD_PTR item_count;
  690. if (!SendMessageTimeout(control_window, msg, 0, 0, SMTO_ABORTIFHUNG, 5000, &item_count))
  691. return FR_E_WIN32;
  692. if (item_count == LB_ERR) // There was a problem getting the count.
  693. return FR_E_FAILED;
  694. Array *items;
  695. if ( !(items = Array::Create()) )
  696. return FR_E_OUTOFMEM;
  697. fr = ControlGetItems(items, item_count, control_window, x_msg, y_msg);
  698. if (fr != OK)
  699. {
  700. items->Release();
  701. return fr;
  702. }
  703. aRetVal = items;
  704. return OK;
  705. }
  706. bif_impl FResult EditGetLineCount(CONTROL_PARAMETERS_DECL, UINT_PTR &aRetVal)
  707. {
  708. DETERMINE_TARGET_CONTROL2;
  709. // MSDN: "If the control has no text, the return value is 1. The return value will never be less than 1."
  710. if (!SendMessageTimeout(control_window, EM_GETLINECOUNT, 0, 0, SMTO_ABORTIFHUNG, 2000, (PDWORD_PTR)&aRetVal))
  711. return FR_E_WIN32;
  712. return OK;
  713. }
  714. bif_impl FResult EditGetCurrentLine(CONTROL_PARAMETERS_DECL, UINT_PTR &aRetVal)
  715. {
  716. DETERMINE_TARGET_CONTROL2;
  717. DWORD_PTR line_number;
  718. if (!SendMessageTimeout(control_window, EM_LINEFROMCHAR, -1, 0, SMTO_ABORTIFHUNG, 2000, &line_number))
  719. return FR_E_WIN32;
  720. aRetVal = line_number + 1;
  721. return OK;
  722. }
  723. bif_impl FResult EditGetCurrentCol(CONTROL_PARAMETERS_DECL, UINT &aRetVal)
  724. {
  725. DETERMINE_TARGET_CONTROL2;
  726. DWORD_PTR dwResult, line_number;
  727. DWORD start = 0, end = 0;
  728. // The dwResult from the first msg below is not useful and is not checked.
  729. if ( !SendMessageTimeout(control_window, EM_GETSEL, (WPARAM)&start, (LPARAM)&end, SMTO_ABORTIFHUNG, 2000, &dwResult)
  730. || !SendMessageTimeout(control_window, EM_LINEFROMCHAR, (WPARAM)start, 0, SMTO_ABORTIFHUNG, 2000, &line_number) )
  731. return FR_E_WIN32;
  732. if (!line_number) // Since we're on line zero, the column number is simply start+1.
  733. {
  734. aRetVal = start + 1; // +1 to convert from zero based.
  735. return OK;
  736. }
  737. // The original Au3 function repeatedly decremented the character index and called EM_LINEFROMCHAR
  738. // until the row changed. Don't know why; the EM_LINEINDEX method is MUCH faster for long lines and
  739. // probably has been available since the dawn of time, though I've only tested it on Windows 10.
  740. DWORD_PTR line_start;
  741. if (!SendMessageTimeout(control_window, EM_LINEINDEX, (WPARAM)line_number, 0, SMTO_ABORTIFHUNG, 2000, &line_start))
  742. return FR_E_WIN32;
  743. aRetVal = start - (UINT)line_start + 1;
  744. return OK;
  745. }
  746. bif_impl FResult EditGetLine(INT_PTR aIndex, CONTROL_PARAMETERS_DECL, StrRet &aRetVal)
  747. {
  748. DETERMINE_TARGET_CONTROL2;
  749. auto control_index = aIndex - 1;
  750. if (control_index < 0)
  751. return FR_E_ARG(0);
  752. DWORD_PTR dwLineCount, dwResult;
  753. // Lexikos: Not sure if the following comment is relevant (does the OS multiply by sizeof(wchar_t)?).
  754. // jackieku: 32768 * sizeof(wchar_t) = 65536, which can not be stored in a unsigned 16bit integer.
  755. TCHAR line_buf[32767];
  756. *(LPWORD)line_buf = 32767; // EM_GETLINE requires first word of string to be set to its size.
  757. if (!SendMessageTimeout(control_window, EM_GETLINE, (WPARAM)control_index, (LPARAM)line_buf, SMTO_ABORTIFHUNG, 2000, &dwResult))
  758. return FR_E_WIN32;
  759. if (!dwResult) // Line is empty or line number is invalid.
  760. {
  761. if (!SendMessageTimeout(control_window, EM_GETLINECOUNT, 0, 0, SMTO_ABORTIFHUNG, 2000, &dwLineCount))
  762. return FR_E_WIN32;
  763. if ((DWORD_PTR)control_index >= dwLineCount)
  764. return FR_E_ARG(0);
  765. }
  766. return aRetVal.Copy(line_buf, dwResult) ? OK : FR_E_OUTOFMEM;
  767. }
  768. bif_impl FResult EditGetSelectedText(CONTROL_PARAMETERS_DECL, StrRet &aRetVal)
  769. {
  770. DETERMINE_TARGET_CONTROL2;
  771. DWORD_PTR length, dwResult;
  772. DWORD start = 0, end = 0;
  773. // Note: The RichEdit controls of certain apps such as Metapad don't return the right selection
  774. // with this technique. Au3 has the same problem with them, so for now it's just documented here
  775. // as a limitation.
  776. if (!SendMessageTimeout(control_window, EM_GETSEL, (WPARAM)&start, (LPARAM)&end, SMTO_ABORTIFHUNG, 2000, &dwResult))
  777. return FR_E_WIN32;
  778. // The above sets start to be the zero-based position of the start of the selection (similar for end).
  779. // If there is no selection, start and end will be equal, at least in the edit controls I tried it with.
  780. // The dwResult from the above is not useful and is not checked.
  781. if (start > end) // Later sections rely on this for safety with unsupported controls.
  782. return FR_E_FAILED; // The most likely cause is that this isn't an Edit control, but that isn't certain.
  783. if (start == end)
  784. {
  785. aRetVal.SetEmpty();
  786. return OK;
  787. }
  788. if ( !SendMessageTimeout(control_window, WM_GETTEXTLENGTH, 0, 0, SMTO_ABORTIFHUNG, 2000, &length)
  789. || !length ) // Since the above didn't return for start == end, this is an error because we have a selection of non-zero length, but no text to go with it!
  790. return FR_E_FAILED;
  791. // Dynamic memory is used because we must get all the control's text so that just the selected region
  792. // can be cropped out and returned:
  793. LPTSTR dyn_buf = tmalloc(length + 1);
  794. if (!dyn_buf)
  795. return FR_E_OUTOFMEM;
  796. if ( !SendMessageTimeout(control_window, WM_GETTEXT, (WPARAM)(length + 1), (LPARAM)dyn_buf, SMTO_ABORTIFHUNG, 2000, &length)
  797. || !length || end > length )
  798. {
  799. // The first check above implies failure since the length is unexpectedly zero
  800. // (above implied it shouldn't be). The second check is also a problem because the
  801. // end of the selection should not be beyond the length of text that was retrieved.
  802. free(dyn_buf);
  803. return FR_E_FAILED;
  804. }
  805. dyn_buf[end] = '\0'; // Terminate the string at the end of the selection.
  806. auto fr = aRetVal.Copy(dyn_buf + start, end - start) ? OK : FR_E_OUTOFMEM;
  807. free(dyn_buf);
  808. return fr;
  809. }
  810. // The terminology "HWND" was chosen rather than "ID" to avoid confusion with a control's
  811. // dialog ID (as retrieved by GetDlgCtrlID). This also reserves the word ID for possible
  812. // use with the control's Dialog ID in future versions.
  813. bif_impl FResult ControlGetHwnd(CONTROL_PARAMETERS_DECL, UINT &aRetVal)
  814. {
  815. DETERMINE_TARGET_CONTROL2;
  816. aRetVal = (UINT)(size_t)control_window;
  817. return OK;
  818. }
  819. bif_impl FResult Download(StrArg aURL, StrArg aFilespec)
  820. {
  821. // v1.0.44.07: Set default to INTERNET_FLAG_RELOAD vs. 0 because the vast majority of usages would want
  822. // the file to be retrieved directly rather than from the cache.
  823. // v1.0.46.04: Added more no-cache flags because otherwise, it definitely falls back to the cache if
  824. // the remote server doesn't respond (and perhaps other errors), which defeats the ability to use the
  825. // Download command for uptime/server monitoring. Also, in spite of what MSDN says, it seems nearly
  826. // certain based on other sources that more than one flag is supported. Someone also mentioned that
  827. // INTERNET_FLAG_CACHE_IF_NET_FAIL is related to this, but there's no way to specify it in these
  828. // particular calls, and it's the opposite of the desired behavior anyway; so it seems impossible to
  829. // turn it off explicitly.
  830. DWORD flags_for_open_url = INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE;
  831. aURL = omit_leading_whitespace(aURL);
  832. if (*aURL == '*') // v1.0.44.07: Provide an option to override flags_for_open_url.
  833. {
  834. flags_for_open_url = ATOU(++aURL);
  835. if (auto cp = StrChrAny(aURL, _T(" \t"))) // Find first space or tab.
  836. aURL = omit_leading_whitespace(cp);
  837. }
  838. // Open the internet session. v1.0.45.03: Provide a non-NULL user-agent because some servers reject
  839. // requests that lack a user-agent. Furthermore, it's more professional to have one, in which case it
  840. // should probably be kept as simple and unchanging as possible. Using something like the script's name
  841. // as the user agent (even if documented) seems like a bad idea because it might contain personal/sensitive info.
  842. HINTERNET hInet = InternetOpen(_T("AutoHotkey"), INTERNET_OPEN_TYPE_PRECONFIG_WITH_NO_AUTOPROXY, NULL, NULL, 0);
  843. if (!hInet)
  844. return FR_E_WIN32;
  845. // Open the required URL
  846. HINTERNET hFile = InternetOpenUrl(hInet, aURL, NULL, 0, flags_for_open_url, 0);
  847. if (!hFile)
  848. {
  849. DWORD last_error = GetLastError(); // Save this before calling InternetCloseHandle, otherwise it is set to 0.
  850. InternetCloseHandle(hInet);
  851. return FR_E_WIN32(last_error);
  852. }
  853. // Open our output file (overwrite if necessary)
  854. auto hOut = CreateFile(aFilespec, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, NULL);
  855. if (hOut == INVALID_HANDLE_VALUE)
  856. {
  857. DWORD last_error = GetLastError();
  858. InternetCloseHandle(hFile);
  859. InternetCloseHandle(hInet);
  860. return FR_E_WIN32(last_error);
  861. }
  862. BYTE bufData[1024 * 1]; // v1.0.44.11: Reduced from 8 KB to alleviate GUI window lag during Download. Testing shows this reduction doesn't affect performance on high-speed downloads (in fact, downloads are slightly faster; I tested two sites, one at 184 KB/s and the other at 380 KB/s). It might affect slow downloads, but that seems less likely so wasn't tested.
  863. INTERNET_BUFFERSA buffers = {0};
  864. buffers.dwStructSize = sizeof(INTERNET_BUFFERSA);
  865. buffers.lpvBuffer = bufData;
  866. buffers.dwBufferLength = sizeof(bufData);
  867. LONG_OPERATION_INIT
  868. // Read the file. I don't think synchronous transfers typically generate the pseudo-error
  869. // ERROR_IO_PENDING, so that is not checked here. That's probably just for async transfers.
  870. // IRF_NO_WAIT is used to avoid requiring the call to block until the buffer is full. By
  871. // having it return the moment there is any data in the buffer, the program is made more
  872. // responsive, especially when the download is very slow and/or one of the hooks is installed:
  873. BOOL result;
  874. DWORD bytes_written;
  875. if (*aURL == 'h' || *aURL == 'H')
  876. {
  877. while (result = InternetReadFileExA(hFile, &buffers, IRF_NO_WAIT, NULL)) // Assign
  878. {
  879. if (!buffers.dwBufferLength) // Transfer is complete.
  880. break;
  881. LONG_OPERATION_UPDATE // Done in between the net-read and the file-write to improve avg. responsiveness.
  882. result = WriteFile(hOut, bufData, buffers.dwBufferLength, &bytes_written, nullptr);
  883. if (!result)
  884. break;
  885. buffers.dwBufferLength = sizeof(bufData); // Reset buffer capacity for next iteration.
  886. }
  887. }
  888. else // v1.0.48.04: This section adds support for FTP and perhaps Gopher by using InternetReadFile() instead of InternetReadFileEx().
  889. {
  890. DWORD number_of_bytes_read;
  891. while (result = InternetReadFile(hFile, bufData, sizeof(bufData), &number_of_bytes_read))
  892. {
  893. if (!number_of_bytes_read)
  894. break;
  895. LONG_OPERATION_UPDATE
  896. result = WriteFile(hOut, bufData, number_of_bytes_read, &bytes_written, nullptr);
  897. if (!result)
  898. break;
  899. }
  900. }
  901. DWORD last_error = GetLastError();
  902. // Close internet session:
  903. InternetCloseHandle(hFile);
  904. InternetCloseHandle(hInet);
  905. // Close output file:
  906. CloseHandle(hOut);
  907. if (!result) // An error occurred during the transfer.
  908. {
  909. DeleteFile(aFilespec); // Delete damaged/incomplete file.
  910. return FR_E_WIN32(last_error);
  911. }
  912. return OK;
  913. }
  914. int CALLBACK FileSelectFolderCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
  915. {
  916. if (uMsg == BFFM_INITIALIZED) // Caller has ensured that lpData isn't NULL by having set a valid lParam value.
  917. SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
  918. // In spite of the quote below, the behavior does not seem to vary regardless of what value is returned
  919. // upon receipt of BFFM_VALIDATEFAILED, at least on XP. But in case it matters on other OSes, preserve
  920. // compatibility with versions older than 1.0.36.03 by keeping the dialog displayed even if the user enters
  921. // an invalid folder:
  922. // MSDN: "Returns zero except in the case of BFFM_VALIDATEFAILED. For that flag, returns zero to dismiss
  923. // the dialog or nonzero to keep the dialog displayed."
  924. return uMsg == BFFM_VALIDATEFAILED; // i.e. zero should be returned in almost every case.
  925. }
  926. bif_impl FResult DirSelect(optl<StrArg> aRootDir, optl<int> aOptions, optl<StrArg> aGreeting, StrRet &aRetVal)
  927. {
  928. if (g_nFolderDialogs >= MAX_FOLDERDIALOGS)
  929. {
  930. // Have a maximum to help prevent runaway hotkeys due to key-repeat feature, etc.
  931. return FError(_T("The maximum number of Folder Dialogs has been reached."));
  932. }
  933. LPMALLOC pMalloc;
  934. HRESULT hr = SHGetMalloc(&pMalloc);
  935. if (FAILED(hr)) // Initialize
  936. return hr;
  937. // v1.0.36.03: Support initial folder, which is different than the root folder because the root only
  938. // controls the origin point (above which the control cannot navigate).
  939. LPTSTR initial_folder;
  940. TCHAR root_dir[MAX_PATH*2 + 5]; // Up to two paths might be present inside, including an asterisk and spaces between them.
  941. if (aRootDir.has_value())
  942. tcslcpy(root_dir, aRootDir.value(), _countof(root_dir)); // Make a modifiable copy.
  943. else
  944. *root_dir = '\0';
  945. if (initial_folder = _tcschr(root_dir, '*'))
  946. {
  947. *initial_folder = '\0'; // Terminate so that root_dir becomes an isolated string.
  948. // Must eliminate the trailing whitespace or it won't work. However, only up to one space or tab
  949. // so that path names that really do end in literal spaces can be used:
  950. if (initial_folder > root_dir && IS_SPACE_OR_TAB(initial_folder[-1]))
  951. initial_folder[-1] = '\0';
  952. // In case absolute paths can ever have literal leading whitespace, preserve that whitespace
  953. // by incrementing by only one and not calling omit_leading_whitespace(). This has been documented.
  954. ++initial_folder;
  955. }
  956. else
  957. initial_folder = NULL;
  958. if (!*(omit_leading_whitespace(root_dir))) // Count all-whitespace as a blank string, but retain leading whitespace if there is also non-whitespace inside.
  959. *root_dir = '\0';
  960. BROWSEINFO bi;
  961. if (initial_folder)
  962. {
  963. bi.lpfn = FileSelectFolderCallback;
  964. bi.lParam = (LPARAM)initial_folder; // Used by the callback above.
  965. }
  966. else
  967. bi.lpfn = NULL; // It will ignore the value of bi.lParam when lpfn is NULL.
  968. if (*root_dir)
  969. {
  970. IShellFolder *pDF;
  971. if (SHGetDesktopFolder(&pDF) == NOERROR)
  972. {
  973. LPITEMIDLIST pIdl = NULL;
  974. ULONG chEaten;
  975. ULONG dwAttributes;
  976. #ifdef UNICODE
  977. pDF->ParseDisplayName(NULL, NULL, root_dir, &chEaten, &pIdl, &dwAttributes);
  978. #else
  979. OLECHAR olePath[MAX_PATH]; // wide-char version of path name
  980. ToWideChar(root_dir, olePath, MAX_PATH); // Dest. size is in wchars, not bytes.
  981. pDF->ParseDisplayName(NULL, NULL, olePath, &chEaten, &pIdl, &dwAttributes);
  982. #endif
  983. pDF->Release();
  984. bi.pidlRoot = pIdl;
  985. }
  986. }
  987. else // No root directory.
  988. bi.pidlRoot = NULL; // Make it use "My Computer" as the root dir.
  989. int iImage = 0;
  990. bi.iImage = iImage;
  991. bi.hwndOwner = THREAD_DIALOG_OWNER; // Can be NULL, which is used rather than main window since no need to have main window forced into the background by this.
  992. TCHAR greeting[1024];
  993. if (aGreeting.has_nonempty_value())
  994. tcslcpy(greeting, aGreeting.value(), _countof(greeting));
  995. else
  996. sntprintf(greeting, _countof(greeting), _T("Select Folder - %s"), g_script.DefaultDialogTitle());
  997. bi.lpszTitle = greeting;
  998. // Bitwise flags:
  999. #define FSF_ALLOW_CREATE 0x01
  1000. #define FSF_EDITBOX 0x02
  1001. #define FSF_NONEWDIALOG 0x04
  1002. auto options = (DWORD)aOptions.value_or(FSF_ALLOW_CREATE);
  1003. bi.ulFlags =
  1004. ((options & FSF_NONEWDIALOG) ? 0 : BIF_NEWDIALOGSTYLE) // v1.0.48: Added to support BartPE/WinPE.
  1005. | ((options & FSF_ALLOW_CREATE) ? 0 : BIF_NONEWFOLDERBUTTON)
  1006. | ((options & FSF_EDITBOX) ? BIF_EDITBOX : 0);
  1007. TCHAR Result[2048];
  1008. bi.pszDisplayName = Result; // This will hold the user's choice.
  1009. // At this point, we know a dialog will be displayed. See macro's comments for details:
  1010. DIALOG_PREP
  1011. POST_AHK_DIALOG(0) // Do this only after the above. Must pass 0 for timeout in this case.
  1012. ++g_nFolderDialogs;
  1013. LPITEMIDLIST lpItemIDList = SHBrowseForFolder(&bi); // Spawn Dialog
  1014. --g_nFolderDialogs;
  1015. DIALOG_END
  1016. if (!lpItemIDList)
  1017. {
  1018. // Due to rarity and because there doesn't seem to be any way to detect it,
  1019. // no exception is thrown when the function fails. Instead, we just assume
  1020. // that the user pressed CANCEL (which should not be treated as an error):
  1021. aRetVal.SetEmpty();
  1022. return OK;
  1023. }
  1024. *Result = '\0'; // Reuse this var, this time to hold the result of the below:
  1025. SHGetPathFromIDList(lpItemIDList, Result);
  1026. pMalloc->Free(lpItemIDList);
  1027. pMalloc->Release();
  1028. return aRetVal.Copy(Result) ? OK : FR_E_OUTOFMEM;
  1029. }
  1030. bif_impl FResult FileGetShortcut(StrArg aShortcutFile, StrRet *aTarget, StrRet *aWorkingDir
  1031. , StrRet *aArgs, StrRet *aDesc, StrRet *aIcon, ResultToken *aIconNum, int *aRunState)
  1032. // Credited to Holger <Holger.Kotsch at GMX de>.
  1033. {
  1034. CoInitialize(NULL);
  1035. IShellLink *psl;
  1036. HRESULT hr;
  1037. if (SUCCEEDED(hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl)))
  1038. {
  1039. IPersistFile *ppf;
  1040. if (SUCCEEDED(hr = psl->QueryInterface(IID_IPersistFile, (LPVOID *)&ppf)))
  1041. {
  1042. #ifdef UNICODE
  1043. if (SUCCEEDED(hr = ppf->Load(aShortcutFile, 0)))
  1044. #else
  1045. WCHAR wsz[MAX_PATH+1]; // +1 hasn't been explained, but is retained in case it's needed.
  1046. ToWideChar(aShortcutFile, wsz, MAX_PATH+1); // Dest. size is in wchars, not bytes.
  1047. if (SUCCEEDED(hr = ppf->Load((const WCHAR*)wsz, 0)))
  1048. #endif
  1049. {
  1050. TCHAR buf[MAX_PATH+1];
  1051. int icon_index, show_cmd;
  1052. if (aTarget)
  1053. {
  1054. psl->GetPath(buf, MAX_PATH, NULL, SLGP_UNCPRIORITY);
  1055. aTarget->Copy(buf);
  1056. }
  1057. if (aWorkingDir)
  1058. {
  1059. psl->GetWorkingDirectory(buf, MAX_PATH);
  1060. aWorkingDir->Copy(buf);
  1061. }
  1062. if (aArgs)
  1063. {
  1064. psl->GetArguments(buf, MAX_PATH);
  1065. aArgs->Copy(buf);
  1066. }
  1067. if (aDesc)
  1068. {
  1069. psl->GetDescription(buf, MAX_PATH); // Testing shows that the OS limits it to 260 characters.
  1070. aDesc->Copy(buf);
  1071. }
  1072. if (aIcon || aIconNum)
  1073. {
  1074. psl->GetIconLocation(buf, MAX_PATH, &icon_index);
  1075. if (aIcon)
  1076. aIcon->Copy(buf);
  1077. if (aIconNum)
  1078. if (*buf)
  1079. aIconNum->SetValue(icon_index + (icon_index >= 0 ? 1 : 0)); // Convert from 0-based to 1-based for consistency with the Menu command, etc. but leave negative resource IDs as-is.
  1080. //else: Leave it blank to indicate that there is none.
  1081. }
  1082. if (aRunState)
  1083. {
  1084. psl->GetShowCmd(&show_cmd);
  1085. *aRunState = show_cmd;
  1086. // For the above, decided not to translate them to Max/Min/Normal since other
  1087. // show-state numbers might be supported in the future (or are already). In other
  1088. // words, this allows the flexibility to specify some number other than 1/3/7 when
  1089. // creating the shortcut in case it happens to work. Of course, that applies only
  1090. // to FileCreateShortcut, not here. But it's done here so that this command is
  1091. // compatible with that one.
  1092. }
  1093. }
  1094. ppf->Release();
  1095. }
  1096. psl->Release();
  1097. }
  1098. CoUninitialize();
  1099. return hr;
  1100. }
  1101. bif_impl FResult FileCreateShortcut(StrArg aTargetFile, StrArg aShortcutFile, optl<StrArg> aWorkingDir
  1102. , optl<StrArg> aArgs, optl<StrArg> aDescription, optl<StrArg> aIconFile, optl<StrArg> aHotkey
  1103. , optl<int> aIconNumber, optl<int> aRunState)
  1104. {
  1105. CoInitialize(NULL);
  1106. IShellLink *psl;
  1107. HRESULT hr;
  1108. if (SUCCEEDED(hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID *)&psl)))
  1109. {
  1110. psl->SetPath(aTargetFile);
  1111. if (aWorkingDir.has_value())
  1112. psl->SetWorkingDirectory(aWorkingDir.value());
  1113. if (aArgs.has_value())
  1114. psl->SetArguments(aArgs.value());
  1115. if (aDescription.has_value())
  1116. psl->SetDescription(aDescription.value());
  1117. int icon_index = aIconNumber.value_or(0);
  1118. if (aIconFile.has_value())
  1119. psl->SetIconLocation(aIconFile.value(), icon_index - (icon_index > 0 ? 1 : 0)); // Convert 1-based index to 0-based, but leave negative resource IDs as-is.
  1120. if (aHotkey.has_value())
  1121. {
  1122. WORD mods = 0;
  1123. LPCTSTR cp = aHotkey.value();
  1124. for (;; ++cp)
  1125. {
  1126. if (!*cp || !cp[1]) // The last char is never a modifier.
  1127. break;
  1128. else if (*cp == '^')
  1129. mods |= HOTKEYF_CONTROL;
  1130. else if (*cp == '!')
  1131. mods |= HOTKEYF_ALT;
  1132. else if (*cp == '+')
  1133. mods |= HOTKEYF_SHIFT;
  1134. else
  1135. break;
  1136. }
  1137. // For backwards compatibility: if modifiers omitted, assume CTRL+ALT.
  1138. if (!mods)
  1139. mods = HOTKEYF_CONTROL | HOTKEYF_ALT;
  1140. // If badly formatted, it's not a critical error, just continue.
  1141. vk_type vk = TextToVK(cp);
  1142. if (vk)
  1143. // Vk in low 8 bits, mods in high 8:
  1144. psl->SetHotkey( (WORD)vk | (mods << 8) );
  1145. }
  1146. if (aRunState.has_value())
  1147. psl->SetShowCmd(*aRunState); // No validation is done since there's a chance other numbers might be valid now or in the future.
  1148. IPersistFile *ppf;
  1149. if (SUCCEEDED(hr = psl->QueryInterface(IID_IPersistFile,(LPVOID *)&ppf)))
  1150. {
  1151. #ifndef UNICODE
  1152. WCHAR wsz[MAX_PATH];
  1153. ToWideChar(aShortcutFile, wsz, MAX_PATH); // Dest. size is in wchars, not bytes.
  1154. #else
  1155. LPCWSTR wsz = aShortcutFile;
  1156. #endif
  1157. // MSDN says to pass "The absolute path of the file". Windows 10 requires it.
  1158. WCHAR full_path[MAX_PATH];
  1159. GetFullPathNameW(wsz, _countof(full_path), full_path, NULL);
  1160. hr = ppf->Save(full_path, TRUE);
  1161. ppf->Release();
  1162. }
  1163. psl->Release();
  1164. }
  1165. CoUninitialize();
  1166. return hr;
  1167. }
  1168. bif_impl FResult FileRecycle(StrArg aFilePattern)
  1169. {
  1170. if (!aFilePattern || !*aFilePattern)
  1171. return FR_E_ARG(0); // Since this is probably not what the user intended.
  1172. SHFILEOPSTRUCT FileOp;
  1173. TCHAR szFileTemp[_MAX_PATH+2];
  1174. // au3: Get the fullpathname - required for UNDO to work
  1175. Line::Util_GetFullPathName(aFilePattern, szFileTemp);
  1176. // au3: We must also make it a double nulled string *sigh*
  1177. szFileTemp[_tcslen(szFileTemp)+1] = '\0';
  1178. // au3: set to known values - Corrects crash
  1179. FileOp.hNameMappings = NULL;
  1180. FileOp.lpszProgressTitle = NULL;
  1181. FileOp.fAnyOperationsAborted = FALSE;
  1182. FileOp.hwnd = NULL;
  1183. FileOp.pTo = NULL;
  1184. FileOp.pFrom = szFileTemp;
  1185. FileOp.wFunc = FO_DELETE;
  1186. FileOp.fFlags = FOF_SILENT | FOF_ALLOWUNDO | FOF_NOCONFIRMATION | FOF_WANTNUKEWARNING;
  1187. // SHFileOperation() returns 0 on success:
  1188. return SHFileOperation(&FileOp) ? FR_E_FAILED : OK;
  1189. }
  1190. bif_impl FResult FileRecycleEmpty(optl<StrArg> szPath)
  1191. {
  1192. HRESULT hr = SHEmptyRecycleBin(NULL, szPath.value_or_null(), SHERB_NOCONFIRMATION | SHERB_NOPROGRESSUI | SHERB_NOSOUND);
  1193. // Throw no error for E_UNEXPECTED, since that is returned in the common case where the
  1194. // recycle bin is already empty. Seems more useful and user-friendly to ignore it.
  1195. return hr != E_UNEXPECTED ? hr : OK;
  1196. }
  1197. bif_impl FResult FileGetVersion(optl<StrArg> aPath, StrRet &aRetVal)
  1198. {
  1199. g->LastError = 0; // Set default for successful return or non-Win32 errors.
  1200. auto path = aPath.has_value() ? aPath.value() : g->mLoopFile ? g->mLoopFile->file_path : _T("");
  1201. if (!*path)
  1202. return FR_E_ARG(0);
  1203. DWORD dwUnused, dwSize;
  1204. if ( !(dwSize = GetFileVersionInfoSize(path, &dwUnused)) ) // No documented limit on how large it can be, so don't use _alloca().
  1205. return FR_E_WIN32;
  1206. BYTE *pInfo = (BYTE*)malloc(dwSize); // Allocate the size retrieved by the above.
  1207. VS_FIXEDFILEINFO *pFFI;
  1208. UINT uSize;
  1209. // Read the version resource
  1210. if (!GetFileVersionInfo(path, 0, dwSize, (LPVOID)pInfo)
  1211. // Locate the fixed information
  1212. || !VerQueryValue(pInfo, _T("\\"), (LPVOID *)&pFFI, &uSize))
  1213. {
  1214. free(pInfo);
  1215. return FR_E_WIN32;
  1216. }
  1217. // extract the fields you want from pFFI
  1218. UINT iFileMS = (UINT)pFFI->dwFileVersionMS;
  1219. UINT iFileLS = (UINT)pFFI->dwFileVersionLS;
  1220. sntprintf(aRetVal.CallerBuf(), aRetVal.CallerBufSize, _T("%u.%u.%u.%u")
  1221. , (iFileMS >> 16), (iFileMS & 0xFFFF), (iFileLS >> 16), (iFileLS & 0xFFFF));
  1222. aRetVal.SetTemp(aRetVal.CallerBuf());
  1223. free(pInfo);
  1224. return OK;
  1225. }
  1226. bool Line::Util_CopyDir(LPCTSTR szInputSource, LPCTSTR szInputDest, int OverwriteMode, bool bMove)
  1227. {
  1228. if (!*szInputSource || !*szInputDest)
  1229. return false;
  1230. bool bOverwrite = OverwriteMode == 1 || OverwriteMode == 2; // Strict validation for safety.
  1231. // Get the fullpathnames and strip trailing \s
  1232. TCHAR szSource[_MAX_PATH+2];
  1233. TCHAR szDest[_MAX_PATH+2];
  1234. Util_GetFullPathName(szInputSource, szSource);
  1235. Util_GetFullPathName(szInputDest, szDest);
  1236. // Permit !IsDir when copying to support extracting zip files. Other checks prevent
  1237. // going in the other direction, which isn't supported by SHFileOperation anyway.
  1238. // The "\*.*" suffix added below prevents any single file from being copied.
  1239. if (bMove && Util_IsDir(szSource) == false)
  1240. return false; // Nope
  1241. // Jon on the AutoIt forums says "Well SHFileOp is way too unpredictable under 9x. Grrr."
  1242. // So the comments below about some OSes/old versions are probably just referring to 9x,
  1243. // which we don't support anymore. Testing on Windows 2000 and Windows 10 showed that
  1244. // SHFileOperation can move a directory between two volumes. However, performing copy
  1245. // then remove ensures nothing is removed if the copy partially fails, so it's kept this
  1246. // way for backward-compatibility, even though it may be inconsistent with local moves.
  1247. // Obsolete comment from Util_MoveDir:
  1248. // If the source and dest are on different volumes then we must copy rather than move
  1249. // as move in this case only works on some OSes. Copy and delete (poor man's move).
  1250. if (bMove && (ctolower(szSource[0]) != ctolower(szDest[0]) || szSource[1] != ':'))
  1251. {
  1252. if (!Util_CopyDir(szSource, szDest, bOverwrite, false))
  1253. return false;
  1254. return Util_RemoveDir(szSource, true);
  1255. }
  1256. // Does the destination dir exist?
  1257. DWORD attr = GetFileAttributes(szDest);
  1258. if (attr != 0xFFFFFFFF) // Destination already exists as a file or directory.
  1259. {
  1260. if (attr & FILE_ATTRIBUTE_DIRECTORY) // Dest already exists as a directory.
  1261. {
  1262. if (!bOverwrite) // Overwrite Mode is "Never".
  1263. return false;
  1264. }
  1265. else // Dest already exists as a file.
  1266. return false; // Don't even attempt to overwrite a file with a dir, regardless of mode (I think SHFileOperation refuses to do it anyway).
  1267. }
  1268. else // Dest doesn't exist.
  1269. {
  1270. // We must create the top level directory
  1271. // FOF_SILENT (which is included in FOF_NO_UI and means "Do not display a progress dialog box")
  1272. // seems to be bugged on some older OSes (such as 2k and XP). Specifically, it answers "No" to
  1273. // the "confirmmkdir" dialog, which it isn't supposed to suppress, and ignores FOF_NOCONFIRMMKDIR.
  1274. // Creating the directory first works around this. Win 7 is okay without this; Vista wasn't tested.
  1275. if (!bMove && !FileCreateDir(szDest))
  1276. return false;
  1277. }
  1278. // The wildcard below is kept for backward-compatibility, although as indicated above, the
  1279. // issues alluded to below are probably only on 9x, which is no longer supported. Adding the
  1280. // wildcard appears to permit copying a directory into itself (perhaps because the directory
  1281. // itself isn't being copied), although we still document the result as "undefined".
  1282. // This suffix also allows SHFileOperation to extract the contents of a zip file, and prevents
  1283. // it from copying the source itself into the destination.
  1284. if (!bMove)
  1285. _tcscat(szSource, _T("\\*.*"));
  1286. // We must also make source\dest double nulled strings for the SHFileOp API
  1287. szSource[_tcslen(szSource)+1] = '\0';
  1288. szDest[_tcslen(szDest)+1] = '\0';
  1289. // Setup the struct
  1290. SHFILEOPSTRUCT FileOp = {0};
  1291. FileOp.pFrom = szSource;
  1292. FileOp.pTo = szDest;
  1293. FileOp.wFunc = bMove ? FO_MOVE : FO_COPY;
  1294. FileOp.fFlags = FOF_NO_UI; // Set default.
  1295. if (OverwriteMode == 2)
  1296. FileOp.fFlags |= FOF_MULTIDESTFILES; // v1.0.46.07: Using the FOF_MULTIDESTFILES flag (as hinted by MSDN) overwrites/merges any existing target directory. This logic supersedes and fixes old logic that didn't work properly when the source dir was being both renamed and moved to overwrite an existing directory.
  1297. // All of the below left set to NULL/FALSE by the struct initializer higher above:
  1298. //FileOp.hNameMappings = NULL;
  1299. //FileOp.lpszProgressTitle = NULL;
  1300. //FileOp.fAnyOperationsAborted = FALSE;
  1301. //FileOp.hwnd = NULL;
  1302. // If the source directory contains any saved webpages consisting of a SiteName.htm file and a
  1303. // corresponding directory named SiteName_files, the following may indicate an error even when the
  1304. // copy is successful. Under Windows XP at least, the return value is 7 under these conditions,
  1305. // which according to WinError.h is "ERROR_ARENA_TRASHED: The storage control blocks were destroyed."
  1306. // However, since this error might occur under a variety of circumstances, it probably wouldn't be
  1307. // proper to consider it a non-error.
  1308. // I also checked GetLastError() after calling SHFileOperation(), but it does not appear to be
  1309. // valid/useful in this case (MSDN mentions this fact but isn't clear about it).
  1310. // The issue appears to affect only FileCopyDir, not FileMoveDir or FileRemoveDir. It also seems
  1311. // unlikely to affect FileCopy/FileMove because they never copy directories.
  1312. return !SHFileOperation(&FileOp);
  1313. }
  1314. bool Line::Util_RemoveDir(LPCTSTR szInputSource, bool bRecurse)
  1315. {
  1316. if (!*szInputSource)
  1317. return false;
  1318. SHFILEOPSTRUCT FileOp;
  1319. TCHAR szSource[_MAX_PATH+2];
  1320. // If recursion not on just try a standard delete on the directory (the SHFile function WILL
  1321. // delete a directory even if not empty no matter what flags you give it...)
  1322. if (bRecurse == false)
  1323. {
  1324. // v1.1.31.00: Use the original source path in case its length exceeds _MAX_PATH.
  1325. // Relative paths and trailing slashes are okay in this case, and Util_IsDir() is
  1326. // not needed since the function only removes empty directories, not files.
  1327. if (!RemoveDirectory(szInputSource))
  1328. return false;
  1329. else
  1330. return true;
  1331. }
  1332. // Get the fullpathnames and strip trailing \s
  1333. Util_GetFullPathName(szInputSource, szSource);
  1334. // Ensure source is a directory
  1335. if (Util_IsDir(szSource) == false)
  1336. return false; // Nope
  1337. // We must also make double nulled strings for the SHFileOp API
  1338. szSource[_tcslen(szSource)+1] = '\0';
  1339. // Setup the struct
  1340. FileOp.pFrom = szSource;
  1341. FileOp.pTo = NULL;
  1342. FileOp.hNameMappings = NULL;
  1343. FileOp.lpszProgressTitle = NULL;
  1344. FileOp.fAnyOperationsAborted = FALSE;
  1345. FileOp.hwnd = NULL;
  1346. FileOp.wFunc = FO_DELETE;
  1347. FileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMMKDIR | FOF_NOCONFIRMATION | FOF_NOERRORUI;
  1348. return !SHFileOperation(&FileOp);
  1349. }
  1350. ///////////////////////////////////////////////////////////////////////////////
  1351. // Util_CopyFile()
  1352. // (moves files too)
  1353. // Returns the number of files that could not be copied or moved due to error.
  1354. ///////////////////////////////////////////////////////////////////////////////
  1355. int Line::Util_CopyFile(LPCTSTR szInputSource, LPCTSTR szInputDest, bool bOverwrite, bool bMove, DWORD &aLastError)
  1356. {
  1357. TCHAR szSource[T_MAX_PATH];
  1358. TCHAR szDest[T_MAX_PATH];
  1359. TCHAR szDestPattern[MAX_PATH];
  1360. // Get local version of our source/dest with full path names, strip trailing \s
  1361. // and normalize the path separator (replace / with \).
  1362. Util_GetFullPathName(szInputSource, szSource, _countof(szSource));
  1363. Util_GetFullPathName(szInputDest, szDest, _countof(szDest));
  1364. // If the source or dest is a directory then add *.* to the end
  1365. if (Util_IsDir(szSource))
  1366. _tcscat(szSource, _T("\\*.*"));
  1367. if (Util_IsDir(szDest))
  1368. _tcscat(szDest, _T("\\*.*"));
  1369. WIN32_FIND_DATA findData;
  1370. HANDLE hSearch = FindFirstFile(szSource, &findData);
  1371. if (hSearch == INVALID_HANDLE_VALUE)
  1372. {
  1373. aLastError = GetLastError(); // Set even in this case since FindFirstFile can fail due to actual errors, such as an invalid path.
  1374. return StrChrAny(const_cast<LPTSTR>(szInputSource), _T("?*")) ? 0 : 1; // Indicate failure only if there were no wildcards.
  1375. }
  1376. aLastError = 0; // Set default. Overridden only when a failure occurs.
  1377. // Otherwise, loop through all the matching files.
  1378. // Locate the filename/pattern, which will be overwritten on each iteration.
  1379. LPTSTR source_append_pos = _tcsrchr(szSource, '\\') + 1;
  1380. LPTSTR dest_append_pos = _tcsrchr(szDest, '\\') + 1;
  1381. size_t space_remaining = _countof(szSource) - (source_append_pos - szSource) - 1;
  1382. // Copy destination filename or pattern, since it will be overwritten.
  1383. tcslcpy(szDestPattern, dest_append_pos, _countof(szDestPattern));
  1384. int failure_count = 0;
  1385. LONG_OPERATION_INIT
  1386. do
  1387. {
  1388. // Since other script threads can interrupt during LONG_OPERATION_UPDATE, it's important that
  1389. // this function and those that call it not refer to sArgDeref[] and sArgVar[] anytime after an
  1390. // interruption becomes possible. This is because an interrupting thread usually changes the
  1391. // values to something inappropriate for this thread.
  1392. LONG_OPERATION_UPDATE
  1393. // Make sure the returned handle is a file and not a directory before we
  1394. // try and do copy type things on it!
  1395. if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) // dwFileAttributes should never be invalid (0xFFFFFFFF) in this case.
  1396. continue;
  1397. if (_tcslen(findData.cFileName) > space_remaining) // v1.0.45.03: Basic check in case of files whose full spec is over 260 characters long.
  1398. {
  1399. aLastError = ERROR_BUFFER_OVERFLOW; // MSDN: "The file name is too long."
  1400. ++failure_count;
  1401. continue;
  1402. }
  1403. _tcscpy(source_append_pos, findData.cFileName); // Indirectly populate szSource. Above has ensured this won't overflow.
  1404. // Expand the destination based on this found file
  1405. Util_ExpandFilenameWildcard(findData.cFileName, szDestPattern, dest_append_pos);
  1406. // Fixed for v1.0.36.01: This section has been revised to avoid unnecessary calls; but more
  1407. // importantly, it now avoids the deletion and complete loss of a file when it is copied or
  1408. // moved onto itself. That used to happen because any existing destination file used to be
  1409. // deleted prior to attempting the move/copy.
  1410. if (bMove) // Move vs. copy mode.
  1411. {
  1412. // Note that MoveFile() is capable of moving a file to a different volume, regardless of
  1413. // operating system version. That's enough for what we need because this function never
  1414. // moves directories, only files.
  1415. // The following call will report success if source and dest are the same file, even if
  1416. // source is something like "..\Folder\Filename.txt" and dest is something like
  1417. // "C:\Folder\Filename.txt" (or if source is an 8.3 filename and dest is the long name
  1418. // of the same file). This is good because it avoids the need to devise code
  1419. // to determine whether two different path names refer to the same physical file
  1420. // (note that GetFullPathName() has shown itself to be inadequate for this purpose due
  1421. // to problems with short vs. long names, UNC vs. mapped drive, and possibly NTFS hard
  1422. // links (aliases) that might all cause two different filenames to point to the same
  1423. // physical file on disk (hopefully MoveFile handles all of these correctly by indicating
  1424. // success [below] when a file is moved onto itself, though it has only been tested for
  1425. // basic cases of relative vs. absolute path).
  1426. if (!MoveFile(szSource, szDest))
  1427. {
  1428. // If overwrite mode was not specified by the caller, or it was but the existing
  1429. // destination file cannot be deleted (perhaps because it is a folder rather than
  1430. // a file), or it can be deleted but the source cannot be moved, indicate a failure.
  1431. // But by design, continue the operation. The following relies heavily on
  1432. // short-circuit boolean evaluation order:
  1433. if ( !(bOverwrite && DeleteFile(szDest) && MoveFile(szSource, szDest)) )
  1434. {
  1435. aLastError = GetLastError();
  1436. ++failure_count; // At this stage, any of the above 3 being false is cause for failure.
  1437. }
  1438. //else everything succeeded, so nothing extra needs to be done. In either case,
  1439. // continue on to the next file.
  1440. }
  1441. }
  1442. else // The mode is "Copy" vs. "Move"
  1443. if (!CopyFile(szSource, szDest, !bOverwrite)) // Force it to fail if bOverwrite==false.
  1444. {
  1445. aLastError = GetLastError();
  1446. ++failure_count;
  1447. }
  1448. } while (FindNextFile(hSearch, &findData));
  1449. FindClose(hSearch);
  1450. return failure_count;
  1451. }
  1452. void Line::Util_ExpandFilenameWildcard(LPCTSTR szSource, LPCTSTR szDest, LPTSTR szExpandedDest)
  1453. {
  1454. // copy one.two.three *.txt = one.two .txt
  1455. // copy one.two.three *.*.txt = one.two. .txt (extra asterisks are removed)
  1456. // copy one.two test = test
  1457. TCHAR szSrcFile[_MAX_PATH+1];
  1458. TCHAR szSrcExt[_MAX_PATH+1];
  1459. TCHAR szDestFile[_MAX_PATH+1];
  1460. TCHAR szDestExt[_MAX_PATH+1];
  1461. // If the destination doesn't include a wildcard, send it back verbatim
  1462. if (_tcschr(szDest, '*') == NULL)
  1463. {
  1464. _tcscpy(szExpandedDest, szDest);
  1465. return;
  1466. }
  1467. // Split source and dest into file and extension
  1468. _tsplitpath( szSource, NULL, NULL, szSrcFile, szSrcExt );
  1469. _tsplitpath( szDest, NULL, NULL, szDestFile, szDestExt );
  1470. // Source and Dest ext will either be ".nnnn" or "" or ".*", remove the period
  1471. if (szSrcExt[0] == '.')
  1472. _tcscpy(szSrcExt, &szSrcExt[1]);
  1473. if (szDestExt[0] == '.')
  1474. _tcscpy(szDestExt, &szDestExt[1]);
  1475. // Replace first * in the destfile with the srcfile, remove any other *
  1476. Util_ExpandFilenameWildcardPart(szSrcFile, szDestFile, szExpandedDest);
  1477. if (*szSrcExt || *szDestExt)
  1478. {
  1479. LPTSTR ext = _tcschr(szExpandedDest, '\0');
  1480. if (!szDestExt[0])
  1481. {
  1482. // Always include the source extension if destination extension was blank
  1483. // (for backward-compatibility, this is done even if a '.' was present)
  1484. szDestExt[0] = '*';
  1485. szDestExt[1] = '\0';
  1486. }
  1487. // Replace first * in the destext with the srcext, remove any other *
  1488. Util_ExpandFilenameWildcardPart(szSrcExt, szDestExt, ext + 1);
  1489. // If there's a non-blank extension, replace the filename's null terminator with .
  1490. if (ext[1])
  1491. *ext = '.';
  1492. }
  1493. }
  1494. void Line::Util_ExpandFilenameWildcardPart(LPCTSTR szSource, LPCTSTR szDest, LPTSTR szExpandedDest)
  1495. {
  1496. LPTSTR lpTemp;
  1497. int i, j, k;
  1498. // Replace first * in the dest with the src, remove any other *
  1499. i = 0; j = 0; k = 0;
  1500. lpTemp = (LPTSTR)_tcschr(szDest, '*');
  1501. if (lpTemp != NULL)
  1502. {
  1503. // Contains at least one *, copy up to this point
  1504. while(szDest[i] != '*')
  1505. szExpandedDest[j++] = szDest[i++];
  1506. // Skip the * and replace in the dest with the srcext
  1507. while(szSource[k] != '\0')
  1508. szExpandedDest[j++] = szSource[k++];
  1509. // Skip any other *
  1510. i++;
  1511. while(szDest[i] != '\0')
  1512. {
  1513. if (szDest[i] == '*')
  1514. i++;
  1515. else
  1516. szExpandedDest[j++] = szDest[i++];
  1517. }
  1518. szExpandedDest[j] = '\0';
  1519. }
  1520. else
  1521. {
  1522. // No wildcard, straight copy of destext
  1523. _tcscpy(szExpandedDest, szDest);
  1524. }
  1525. }
  1526. bool Line::Util_IsDir(LPCTSTR szPath) // Returns true if the path is a directory
  1527. {
  1528. DWORD dwTemp = GetFileAttributes(szPath);
  1529. return dwTemp != 0xffffffff && (dwTemp & FILE_ATTRIBUTE_DIRECTORY);
  1530. }
  1531. void Line::Util_GetFullPathName(LPCTSTR szIn, LPTSTR szOut)
  1532. // Returns the full pathname and strips any trailing \s. Assumes output is _MAX_PATH in size.
  1533. {
  1534. ASSERT(*szIn && szOut);
  1535. LPTSTR szFilePart;
  1536. GetFullPathName(szIn, _MAX_PATH, szOut, &szFilePart);
  1537. strip_trailing_backslash(szOut);
  1538. }
  1539. void Line::Util_GetFullPathName(LPCTSTR szIn, LPTSTR szOut, DWORD aBufSize)
  1540. {
  1541. ASSERT(*szIn && szOut);
  1542. GetFullPathName(szIn, aBufSize, szOut, NULL);
  1543. strip_trailing_backslash(szOut);
  1544. }
  1545. FResult Shutdown(int nFlag)
  1546. // Shutdown or logoff the system.
  1547. // Returns OK if successful.
  1548. {
  1549. /*
  1550. flags can be a combination of:
  1551. #define EWX_LOGOFF 0
  1552. #define EWX_SHUTDOWN 0x00000001
  1553. #define EWX_REBOOT 0x00000002
  1554. #define EWX_FORCE 0x00000004
  1555. #define EWX_POWEROFF 0x00000008 */
  1556. HANDLE hToken;
  1557. TOKEN_PRIVILEGES tkp;
  1558. // Get a token for this process.
  1559. if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
  1560. return FR_E_WIN32;
  1561. // Get the LUID for the shutdown privilege.
  1562. LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
  1563. tkp.PrivilegeCount = 1; /* one privilege to set */
  1564. tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
  1565. // Get the shutdown privilege for this process.
  1566. AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0);
  1567. // Cannot test the return value of AdjustTokenPrivileges.
  1568. if (GetLastError() != ERROR_SUCCESS)
  1569. return FR_E_WIN32;
  1570. // ExitWindows
  1571. if (ExitWindowsEx(nFlag, 0))
  1572. return OK;
  1573. else
  1574. return FR_E_WIN32;
  1575. }
  1576. void Util_WinKill(HWND hWnd)
  1577. {
  1578. DWORD_PTR dwResult;
  1579. // Use WM_CLOSE vs. SC_CLOSE in this case, since the target window is slightly more likely to
  1580. // respond to that:
  1581. if (!SendMessageTimeout(hWnd, WM_CLOSE, 0, 0, SMTO_ABORTIFHUNG, 500, &dwResult)) // Wait up to 500ms.
  1582. {
  1583. // Use more force - Mwuahaha
  1584. DWORD pid = 0;
  1585. GetWindowThreadProcessId(hWnd, &pid);
  1586. HANDLE hProcess = pid ? OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid) : NULL;
  1587. if (hProcess)
  1588. {
  1589. TerminateProcess(hProcess, 0);
  1590. CloseHandle(hProcess);
  1591. }
  1592. }
  1593. }
  1594. void DoIncrementalMouseMove(int aX1, int aY1, int aX2, int aY2, int aSpeed)
  1595. // aX1 and aY1 are the starting coordinates, and "2" are the destination coordinates.
  1596. // Caller has ensured that aSpeed is in the range 0 to 100, inclusive.
  1597. {
  1598. // AutoIt3: So, it's a more gradual speed that is needed :)
  1599. int delta;
  1600. #define INCR_MOUSE_MIN_SPEED 32
  1601. while (aX1 != aX2 || aY1 != aY2)
  1602. {
  1603. if (aX1 < aX2)
  1604. {
  1605. delta = (aX2 - aX1) / aSpeed;
  1606. if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
  1607. delta = INCR_MOUSE_MIN_SPEED;
  1608. if ((aX1 + delta) > aX2)
  1609. aX1 = aX2;
  1610. else
  1611. aX1 += delta;
  1612. }
  1613. else
  1614. if (aX1 > aX2)
  1615. {
  1616. delta = (aX1 - aX2) / aSpeed;
  1617. if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
  1618. delta = INCR_MOUSE_MIN_SPEED;
  1619. if ((aX1 - delta) < aX2)
  1620. aX1 = aX2;
  1621. else
  1622. aX1 -= delta;
  1623. }
  1624. if (aY1 < aY2)
  1625. {
  1626. delta = (aY2 - aY1) / aSpeed;
  1627. if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
  1628. delta = INCR_MOUSE_MIN_SPEED;
  1629. if ((aY1 + delta) > aY2)
  1630. aY1 = aY2;
  1631. else
  1632. aY1 += delta;
  1633. }
  1634. else
  1635. if (aY1 > aY2)
  1636. {
  1637. delta = (aY1 - aY2) / aSpeed;
  1638. if (delta == 0 || delta < INCR_MOUSE_MIN_SPEED)
  1639. delta = INCR_MOUSE_MIN_SPEED;
  1640. if ((aY1 - delta) < aY2)
  1641. aY1 = aY2;
  1642. else
  1643. aY1 -= delta;
  1644. }
  1645. MouseEvent(MOUSEEVENTF_MOVE | MOUSEEVENTF_ABSOLUTE, 0, aX1, aY1);
  1646. DoMouseDelay();
  1647. // Above: A delay is required for backward compatibility and because it's just how the incremental-move
  1648. // feature was originally designed in AutoIt v3. It may in fact improve reliability in some cases,
  1649. // especially with the mouse_event() method vs. SendInput/Play.
  1650. } // while()
  1651. }
  1652. ////////////////////
  1653. // PROCESS ROUTINES
  1654. ////////////////////
  1655. DWORD ProcessExist(LPCTSTR aProcess, bool aGetParent, bool aVerifyPID)
  1656. {
  1657. // Determine the PID if aProcess is a pure, non-negative integer (any negative number
  1658. // is more likely to be the name of a process [with a leading dash], rather than the PID).
  1659. DWORD specified_pid = IsNumeric(aProcess) ? ATOU(aProcess) : 0;
  1660. if (specified_pid && !aGetParent)
  1661. {
  1662. // Most of the time while a PID is being used, the process still exists. So try opening
  1663. // the process directly, since doing so is much faster than enumerating all processes:
  1664. if (HANDLE hproc = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, specified_pid))
  1665. {
  1666. DWORD wait_result = WAIT_FAILED;
  1667. // OpenProcess can succeed for erroneous PID values; e.g. values of 1501 to 1503 can
  1668. // open the process with ID 1500. This is likely due to the undocumented fact that
  1669. // PIDs are a multiple of four: https://devblogs.microsoft.com/oldnewthing/20080228-00/?p=23283
  1670. DWORD actual_pid = GetProcessId(hproc); // Requires PROCESS_QUERY_LIMITED_INFORMATION access.
  1671. if (actual_pid == specified_pid)
  1672. {
  1673. // OpenProcess can succeed for a process which has already exited if another process
  1674. // still has an open handle to it. So check whether it's still running:
  1675. wait_result = WaitForSingleObject(hproc, 0); // Requires SYNCHRONIZE access.
  1676. }
  1677. CloseHandle(hproc);
  1678. if (wait_result == WAIT_OBJECT_0)
  1679. return 0; // Process has exited.
  1680. if (wait_result == WAIT_TIMEOUT)
  1681. return specified_pid; // Process still running.
  1682. // Otherwise, fall back to the slow but more reliable method to get a more conclusive result.
  1683. }
  1684. // If OpenProcess failed, some likely causes are ERROR_ACCESS_DENIED and ERROR_INVALID_PARAMETER.
  1685. // The latter probably indicates the PID is invalid, but we continue anyway, for the unlikely
  1686. // case of a process with a name composed of digits and no extension (verified possible).
  1687. // If ERROR_ACCESS_DENIED was returned, we can't rule out the false-positive cases described
  1688. // above without doing a thorough enumeration of processes, so continue in that case as well.
  1689. if (!aVerifyPID && GetLastError() != ERROR_INVALID_PARAMETER && !(specified_pid & 3))
  1690. // Caller doesn't strictly need a result of "no such process" if the process apparently
  1691. // exists but can't be opened, so we return early under these specific conditions.
  1692. // This speeds up ProcessGetPath(PID) when PID identifies a protected system process,
  1693. // without affecting error detection significantly.
  1694. return specified_pid;
  1695. // (specified_pid & 3) almost certainly means an invalid PID, but since that's not documented,
  1696. // do a thorough check before returning 0 in case there ever is a valid PID like that.
  1697. }
  1698. PROCESSENTRY32 proc;
  1699. proc.dwSize = sizeof(proc);
  1700. HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
  1701. Process32First(snapshot, &proc);
  1702. while (Process32Next(snapshot, &proc))
  1703. {
  1704. if (specified_pid && specified_pid == proc.th32ProcessID)
  1705. {
  1706. CloseHandle(snapshot);
  1707. return aGetParent ? proc.th32ParentProcessID : specified_pid;
  1708. }
  1709. // Otherwise, check for matching name even if aProcess is purely numeric (i.e. a number might
  1710. // also be a valid name?):
  1711. // It seems that proc.szExeFile never contains a path, just the executable name.
  1712. // But in case it ever does, ensure consistency by removing the path:
  1713. LPCTSTR proc_name = _tcsrchr(proc.szExeFile, '\\');
  1714. proc_name = proc_name ? proc_name + 1 : proc.szExeFile;
  1715. if (!_tcsicmp(proc_name, aProcess)) // lstrcmpi() is not used: 1) avoids breaking existing scripts; 2) provides consistent behavior across multiple locales; 3) performance.
  1716. {
  1717. CloseHandle(snapshot);
  1718. return aGetParent ? proc.th32ParentProcessID : proc.th32ProcessID;
  1719. }
  1720. }
  1721. CloseHandle(snapshot);
  1722. return 0; // Not found.
  1723. }