Subversion Repositories userdetect2

Rev

Rev 87 | Rev 95 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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