Subversion Repositories userdetect2

Rev

Rev 92 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. unit UD2_Utils;
  2.  
  3. interface
  4.  
  5. {$IF CompilerVersion >= 25.0}
  6. {$LEGACYIFEND ON}
  7. {$IFEND}
  8.  
  9. {$INCLUDE 'UserDetect2.inc'}
  10.  
  11. uses
  12.   Windows, SysUtils, Dialogs, ShellAPI, Classes, UD2_Parsing;
  13.  
  14. const
  15.   EXITCODE_OK = 0;
  16.   EXITCODE_TASK_NOTHING_MATCHES = 1;
  17.   EXITCODE_RUN_FAILURE = 2;
  18.   EXITCODE_TASK_NOT_EXISTS = 10;
  19.   EXITCODE_INI_NOT_FOUND = 11;
  20.   EXITCODE_RUNCMD_SYNTAX_ERROR = 12;
  21.   EXITCODE_SYNTAX_ERROR = 13;
  22.  
  23. type
  24.   TArrayOfString = array of string;
  25.  
  26.   TIconFileIdx = record
  27.     FileName: string;
  28.     IconIndex: integer;
  29.   end;
  30.  
  31. function SplitString(const aSeparator, aString: string; aMax: Integer = 0): TArrayOfString;
  32. function MergeString(ary: TArrayOfString; glue: string): string;
  33. function BetterInterpreteBool(str: string): boolean;
  34. function GetOwnCmdName: string;
  35. function ExpandEnvStr(const szInput: string): string;
  36. procedure UD2_RunCMD(cmd: TUD2Command);
  37. function SplitIconString(IconString: string): TIconFileIdx;
  38. // function GetHTML(AUrl: string): string;
  39. procedure VTS_CheckUpdates(VTSID, CurVer: string);
  40. function FormatOSError(ec: DWORD): string;
  41. function CheckBoolParam(idx: integer; name: string): boolean;
  42. function IndexOf_CS(aStrings: TStrings; aToken: string): Integer;
  43. function UD2_GetThreadErrorMode: DWORD;
  44. function UD2_SetThreadErrorMode(dwNewMode: DWORD; lpOldMode: LPDWORD): BOOL;
  45. function GetFileVersion(const FileName: string=''): string;
  46.  
  47. implementation
  48.  
  49. uses
  50.   idhttp, Forms;
  51.  
  52. function SplitString(const aSeparator, aString: string; aMax: Integer = 0): TArrayOfString;
  53. // http://stackoverflow.com/a/2626991/3544341
  54. var
  55.   i, strt, cnt: Integer;
  56.   sepLen: Integer;
  57.  
  58.   procedure AddString(aEnd: Integer = -1);
  59.   var
  60.     endPos: Integer;
  61.   begin
  62.     if (aEnd = -1) then
  63.       endPos := i
  64.     else
  65.       endPos := aEnd + 1;
  66.  
  67.     if (strt < endPos) then
  68.       result[cnt] := Copy(aString, strt, endPos - strt)
  69.     else
  70.       result[cnt] := '';
  71.  
  72.     Inc(cnt);
  73.   end;
  74.  
  75. begin
  76.   if (aString = '') or (aMax < 0) then
  77.   begin
  78.     SetLength(result, 0);
  79.     EXIT;
  80.   end;
  81.  
  82.   if (aSeparator = '') then
  83.   begin
  84.     SetLength(result, 1);
  85.     result[0] := aString;
  86.     EXIT;
  87.   end;
  88.  
  89.   sepLen := Length(aSeparator);
  90.   SetLength(result, (Length(aString) div sepLen) + 1);
  91.  
  92.   i     := 1;
  93.   strt  := i;
  94.   cnt   := 0;
  95.   while (i <= (Length(aString)- sepLen + 1)) do
  96.   begin
  97.     if (aString[i] = aSeparator[1]) then
  98.       if (Copy(aString, i, sepLen) = aSeparator) then
  99.       begin
  100.         AddString;
  101.  
  102.         if (cnt = aMax) then
  103.         begin
  104.           SetLength(result, cnt);
  105.           EXIT;
  106.         end;
  107.  
  108.         Inc(i, sepLen - 1);
  109.         strt := i + 1;
  110.       end;
  111.  
  112.     Inc(i);
  113.   end;
  114.  
  115.   AddString(Length(aString));
  116.  
  117.   SetLength(result, cnt);
  118. end;
  119.  
  120. function BetterInterpreteBool(str: string): boolean;
  121. resourcestring
  122.   LNG_CANNOT_INTERPRETE_BOOL = 'Cannot determinate the boolean value of "%s"';
  123. begin
  124.   str := LowerCase(str);
  125.   if (str = 'yes') or (str = 'true') or (str = '1') then
  126.     result := true
  127.   else if (str = 'no') or (str = 'false') or (str = '0') then
  128.     result := false
  129.   else
  130.     raise EConvertError.CreateFmt(LNG_CANNOT_INTERPRETE_BOOL, [str]);
  131. end;
  132.  
  133. function GetOwnCmdName: string;
  134. begin
  135.   result := ParamStr(0);
  136.   result := ExtractFileName(result);
  137.   result := ChangeFileExt(result, '');
  138.   result := UpperCase(result);
  139. end;
  140.  
  141. function ExpandEnvStr(const szInput: string): string;
  142. // http://stackoverflow.com/a/2833147/3544341
  143. const
  144.   MAXSIZE = 32768;
  145. begin
  146.   SetLength(Result, MAXSIZE);
  147.   SetLength(Result, ExpandEnvironmentStrings(pchar(szInput),
  148.     @Result[1],length(Result)));
  149. end;
  150.  
  151. function FormatOSError(ec: DWORD): string;
  152. resourcestring
  153.   LNG_UNKNOWN_ERROR = 'Operating system error %d';
  154. begin
  155.   result := SysErrorMessage(ec);
  156.  
  157.   // Some errors have no error message, e.g. error 193 (BAD_EXE_FORMAT) in the German version of Windows 10
  158.   if result = '' then result := Format(LNG_UNKNOWN_ERROR, [ec]);
  159. end;
  160.  
  161. function CheckLastOSCall(AThrowException: boolean): boolean;
  162. var
  163.   LastError: Cardinal;
  164. begin
  165.   LastError := GetLastError;
  166.   result := LastError = 0;
  167.   if not result then
  168.   begin
  169.     if AThrowException then
  170.     begin
  171.       RaiseLastOSError;
  172.     end
  173.     else
  174.     begin
  175.       MessageDlg(FormatOSError(LastError), mtError, [mbOK], 0);
  176.     end;
  177.   end;
  178. end;
  179.  
  180. function SplitIconString(IconString: string): TIconFileIdx;
  181. var
  182.   p: integer;
  183. begin
  184.   p := Pos(',', IconString);
  185.  
  186.   if p = 0 then
  187.   begin
  188.     result.FileName := IconString;
  189.     result.IconIndex := 0;
  190.   end
  191.   else
  192.   begin
  193.     result.FileName  := ExpandEnvStr(copy(IconString, 0, p-1));
  194.     result.IconIndex := StrToInt(Copy(IconString, p+1, Length(IconString)-p));
  195.   end;
  196. end;
  197.  
  198. procedure UD2_RunCMD(cmd: TUD2Command);
  199. // Discussion: http://stackoverflow.com/questions/32802679/acceptable-replacement-for-winexec/32804669#32804669
  200. // Version 1: http://pastebin.com/xQjDmyVe
  201. // --> CreateProcess + ShellExecuteEx
  202. // --> Problem: Run-In-Same-Directory functionality is not possible
  203. //              (requires manual command and argument separation)
  204. // Version 2: http://pastebin.com/YpUmF5rd
  205. // --> Splits command and arguments manually, and uses ShellExecute
  206. // --> Problem: error handling wrong
  207. // --> Problem: Run-In-Same-Directory functionality is not implemented
  208. // Current version:
  209. // --> Splits command and arguments manually, and uses ShellExecute
  210. // --> Run-In-Same-Directory functionality is implemented
  211. resourcestring
  212.   LNG_INVALID_SYNTAX = 'The command line has an invalid syntax';
  213. var
  214.   cmdFile, cmdArgs, cmdDir: string;
  215.   p: integer;
  216.   sei: TShellExecuteInfo;
  217.   cmdLine: string;
  218. begin
  219.   // We need a function which does following:
  220.   // 1. Replace the Environment strings, e.g. %SystemRoot%
  221.   // 2. Runs EXE files with parameters (e.g. "cmd.exe /?")
  222.   // 3. Runs EXE files without path (e.g. "calc.exe")
  223.   // 4. Runs EXE files without extension (e.g. "calc")
  224.   // 5. Runs non-EXE files (e.g. "Letter.doc")
  225.   // 6. Commands with white spaces (e.g. "C:\Program Files\xyz.exe") must be enclosed in quotes.
  226.  
  227.   cmdLine := ExpandEnvStr(cmd.executable);
  228.  
  229.   // Split command line from argument list
  230.   if Copy(cmdLine, 1, 1) = '"' then
  231.   begin
  232.     cmdLine := Copy(cmdLine, 2, Length(cmdLine)-1);
  233.     p := Pos('"', cmdLine);
  234.     if p = 0 then
  235.     begin
  236.       // No matching quotes
  237.       // CreateProcess() handles the whole command line as single file name  ("abc -> "abc")
  238.       // ShellExecuteEx() does not accept the command line
  239.       ExitCode := EXITCODE_RUNCMD_SYNTAX_ERROR;
  240.       MessageDlg(LNG_INVALID_SYNTAX, mtError, [mbOK], 0);
  241.       Exit;
  242.     end;
  243.     cmdFile := Copy(cmdLine, 1, p-1);
  244.     cmdArgs := Copy(cmdLine, p+2, Length(cmdLine)-p-1);
  245.   end
  246.   else
  247.   begin
  248.     p := Pos(' ', cmdLine);
  249.     if p = 0 then
  250.     begin
  251.       cmdFile := cmdLine;
  252.       cmdArgs := '';
  253.     end
  254.     else
  255.     begin
  256.       cmdFile := Copy(cmdLine, 1, p-1);
  257.       cmdArgs := Copy(cmdLine, p+1, Length(cmdLine)-p);
  258.     end;
  259.   end;
  260.  
  261.   ZeroMemory(@sei, SizeOf(sei));
  262.  
  263.   if cmd.runAsAdmin then
  264.   begin
  265.     sei.lpVerb := 'runas';
  266.   end;
  267.  
  268.   if cmd.runInOwnDirectory then
  269.   begin
  270.     cmdFile := ExtractFileName(cmdLine);
  271.     cmdDir  := ExtractFilePath(cmdLine);
  272.   end
  273.   else
  274.   begin
  275.     cmdFile := cmdLine;
  276.     cmdDir := '';
  277.   end;
  278.  
  279.   sei.cbSize       := SizeOf(sei);
  280.   sei.lpFile       := PChar(cmdFile);
  281.   {$IFNDEF PREFER_SHELLEXECUTEEX_MESSAGES}
  282.   sei.fMask        := SEE_MASK_FLAG_NO_UI;
  283.   {$ENDIF}
  284.   if cmdArgs <> '' then sei.lpParameters := PChar(cmdArgs);
  285.   if cmdDir  <> '' then sei.lpDirectory  := PChar(cmdDir);
  286.   sei.nShow        := cmd.windowMode;
  287.   if ShellExecuteEx(@sei) then Exit;
  288.   {$IFNDEF PREFER_SHELLEXECUTEEX_MESSAGES}
  289.   if not CheckLastOSCall(false) then ExitCode := EXITCODE_RUN_FAILURE;
  290.   {$ENDIF}
  291. end;
  292.  
  293. function GetHTML(const url: string): string;
  294. var
  295.   idhttp :Tidhttp;
  296. begin
  297.   idhttp := Tidhttp.Create(nil);
  298.   try
  299.     result := idhttp.Get(url);
  300.   finally
  301.     idhttp.Free;
  302.   end;
  303. end;
  304.  
  305. procedure VTS_CheckUpdates(VTSID, CurVer: string);
  306. resourcestring
  307.   (*
  308.   LNG_DOWNLOAD_ERR = 'Ein Fehler ist aufgetreten. Wahrscheinlich ist keine Internetverbindung aufgebaut, oder der der ViaThinkSoft-Server temporär offline.';
  309.   LNG_NEW_VERSION = 'Eine neue Programmversion ist vorhanden. Möchten Sie diese jetzt herunterladen?';
  310.   LNG_NO_UPDATE = 'Es ist keine neue Programmversion vorhanden.';
  311.   *)
  312.   LNG_DOWNLOAD_ERR = 'An error occurred while searching for updates. Please check your internet connection and firewall.';
  313.   LNG_NEW_VERSION = 'A new version is available. Do you want to download it now?';
  314.   LNG_NO_UPDATE = 'You already have the newest program version.';
  315. var
  316.   status: string;
  317. begin
  318.   status := GetHTML('http://www.viathinksoft.de/update/?id='+VTSID);
  319.   if Copy(status, 0, 7) = 'Status:' then
  320.   begin
  321.     MessageDlg(LNG_DOWNLOAD_ERR, mtError, [mbOK], 0);
  322.   end
  323.   else
  324.   begin
  325.     if status <> CurVer then
  326.     begin
  327.       if MessageDlg(LNG_NEW_VERSION, mtConfirmation, mbYesNoCancel, 0) = ID_YES then
  328.       begin
  329.         shellexecute(application.handle, 'open', pchar('http://www.viathinksoft.de/update/?id=@'+VTSID), '', '', SW_Normal);
  330.       end;
  331.     end
  332.     else
  333.     begin
  334.       MessageDlg(LNG_NO_UPDATE, mtInformation, [mbOk], 0);
  335.     end;
  336.   end;
  337. end;
  338.  
  339. function CheckBoolParam(idx: integer; name: string): boolean;
  340. begin
  341.   Result := ('/'+LowerCase(name) = LowerCase(ParamStr(idx))) or
  342.             ('-'+LowerCase(name) = LowerCase(ParamStr(idx)));
  343. end;
  344.  
  345. // function GetThreadErrorMode: DWORD; stdcall; external kernel32 name 'GetThreadErrorMode';
  346. function UD2_GetThreadErrorMode: DWORD;
  347. type
  348.   TFuncGetThreadErrorMode = function: DWORD; stdcall;
  349. var
  350.   dllHandle: Cardinal;
  351.   fGetThreadErrorMode: TFuncGetThreadErrorMode;
  352. begin
  353.   dllHandle := LoadLibrary(kernel32);
  354.   if dllHandle = 0 then
  355.   begin
  356.     result := 0;
  357.     Exit;
  358.   end;
  359.   try
  360.     @fGetThreadErrorMode := GetProcAddress(dllHandle, 'GetThreadErrorMode');
  361.     if not Assigned(fGetThreadErrorMode) then
  362.     begin
  363.       result := 0; // Windows Vista and prior
  364.       Exit;
  365.     end;
  366.     result := fGetThreadErrorMode();
  367.   finally
  368.     FreeLibrary(dllHandle);
  369.   end;
  370. end;
  371.  
  372. // function SetThreadErrorMode(dwNewMode: DWORD; lpOldMode: LPDWORD): BOOL; stdcall; external kernel32 name 'SetThreadErrorMode';
  373. function UD2_SetThreadErrorMode(dwNewMode: DWORD; lpOldMode: LPDWORD): BOOL;
  374. type
  375.   TFuncSetThreadErrorMode = function(dwNewMode: DWORD; lpOldMode: LPDWORD): BOOL; stdcall;
  376. var
  377.   dllHandle: Cardinal;
  378.   fSetThreadErrorMode: TFuncSetThreadErrorMode;
  379. begin
  380.   dllHandle := LoadLibrary(kernel32);
  381.   if dllHandle = 0 then
  382.   begin
  383.     result := FALSE;
  384.     if Assigned(lpOldMode) then lpOldMode^ := UD2_GetThreadErrorMode;
  385.     Exit;
  386.   end;
  387.   try
  388.     @fSetThreadErrorMode := GetProcAddress(dllHandle, 'SetThreadErrorMode');
  389.     if not Assigned(fSetThreadErrorMode) then
  390.     begin
  391.       result := FALSE; // Windows Vista and prior
  392.       if Assigned(lpOldMode) then lpOldMode^ := UD2_GetThreadErrorMode;
  393.       Exit;
  394.     end;
  395.     result := fSetThreadErrorMode(dwNewMode, lpOldMode);
  396.   finally
  397.     FreeLibrary(dllHandle);
  398.   end;
  399. end;
  400.  
  401. function IndexOf_CS(aStrings: TStrings; aToken: String): Integer;
  402. var
  403.   i: Integer;
  404. begin
  405.   Result := -1;
  406.   for i := 0 to aStrings.Count-1 do
  407.   begin
  408.     if aStrings[i] = aToken then
  409.     begin
  410.       Result := i;
  411.       Break;
  412.     end;
  413.   end;
  414. end;
  415.  
  416. function MergeString(ary: TArrayOfString; glue: string): string;
  417. var
  418.   i: integer;
  419. begin
  420.   result := '';
  421.   for i := Low(ary) to High(ary) do
  422.   begin
  423.     if result <> '' then result := result + glue;
  424.     result := result + ary[i];
  425.   end;
  426. end;
  427.  
  428. function GetFileVersion(const FileName: string=''): string;
  429. var
  430.   lpVerInfo: pointer;
  431.   rVerValue: PVSFixedFileInfo;
  432.   dwInfoSize: cardinal;
  433.   dwValueSize: cardinal;
  434.   dwDummy: cardinal;
  435.   lpstrPath: pchar;
  436.   a, b, c, d: word;
  437. resourcestring
  438.   LNG_NO_VERSION = 'No version specification';
  439. begin
  440.   if Trim(FileName) = EmptyStr then
  441.     lpstrPath := pchar(ParamStr(0))
  442.   else
  443.     lpstrPath := pchar(FileName);
  444.  
  445.   dwInfoSize := GetFileVersionInfoSize(lpstrPath, dwDummy);
  446.  
  447.   if dwInfoSize = 0 then
  448.   begin
  449.     Result := LNG_NO_VERSION;
  450.     Exit;
  451.   end;
  452.  
  453.   GetMem(lpVerInfo, dwInfoSize);
  454.   try
  455.     GetFileVersionInfo(lpstrPath, 0, dwInfoSize, lpVerInfo);
  456.     VerQueryValue(lpVerInfo, '', pointer(rVerValue), dwValueSize);
  457.  
  458.     with rVerValue^ do
  459.     begin
  460.       a := dwFileVersionMS shr 16;
  461.       b := dwFileVersionMS and $FFFF;
  462.       c := dwFileVersionLS shr 16;
  463.       d := dwFileVersionLS and $FFFF;
  464.  
  465.       Result := IntToStr(a);
  466.       if (b <> 0) or (c <> 0) or (d <> 0) then Result := Result + '.' + IntToStr(b);
  467.       if (c <> 0) or (d <> 0) then Result := Result + '.' + IntToStr(c);
  468.       if (d <> 0) then Result := Result + '.' + IntToStr(d);
  469.     end;
  470.   finally
  471.     FreeMem(lpVerInfo, dwInfoSize);
  472.   end;
  473.  
  474. end;
  475.  
  476. end.
  477.