1{ Copyright 2019-2021 Espressif Systems (Shanghai) CO LTD
2  SPDX-License-Identifier: Apache-2.0 }
3
4{ SystemCheck states }
5const
6  SYSTEM_CHECK_STATE_INIT = 0;      { No check was executed yet. }
7  SYSTEM_CHECK_STATE_RUNNING = 1;   { Check is in progress and can be cancelled. }
8  SYSTEM_CHECK_STATE_COMPLETE = 2;  { Check is complete. }
9  SYSTEM_CHECK_STATE_STOPPED = 3;   { User stopped the check. }
10
11var
12  { RTF View to display content of system check. }
13  SystemCheckViewer: TNewMemo;
14  { Indicate state of System Check. }
15  SystemCheckState:Integer;
16  { Text representation of log messages which are then converte to RTF. }
17  SystemLogText: TStringList;
18  { Message for user which gives a hint how to correct the problem. }
19  SystemCheckHint: String;
20  { Setup Page which displays progress/result of system check. }
21  SystemCheckPage: TOutputMsgWizardPage;
22  { TimeCounter for Spinner animation invoked during command execution. }
23  TimeCounter:Integer;
24  { Spinner is TStringList, because characters like backslash must be escaped and stored on two bytes. }
25  Spinner: TStringList;
26  { Button to request display of full log of system check/installation. }
27  FullLogButton: TNewButton;
28  { Button to request application of available fixtures. }
29  ApplyFixesButton: TNewButton;
30  { Commands which should be executed to fix problems discovered during system check. }
31  Fixes: TStringList;
32  { Button to request Stop of System Checks manually. }
33  StopSystemCheckButton: TNewButton;
34  { Count number of createde virtualenv to avoid collision with previous runs. }
35  VirtualEnvCounter: Integer;
36
37{ Indicates whether system check was able to find running Windows Defender. }
38var IsWindowsDefenderEnabled: Boolean;
39
40{ Const values for user32.dll which allows scrolling of the text view. }
41const
42  WM_VSCROLL = $0115;
43  SB_BOTTOM = 7;
44
45type
46  TMsg = record
47    hwnd: HWND;
48    message: UINT;
49    wParam: Longint;
50    lParam: Longint;
51    time: DWORD;
52    pt: TPoint;
53  end;
54
55const
56  PM_REMOVE = 1;
57
58{ Functions to communicate via Windows API. }
59function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): BOOL; external 'PeekMessageW@user32.dll stdcall';
60function TranslateMessage(const lpMsg: TMsg): BOOL; external 'TranslateMessage@user32.dll stdcall';
61function DispatchMessage(const lpMsg: TMsg): Longint; external 'DispatchMessageW@user32.dll stdcall';
62
63procedure AppProcessMessage;
64var
65  Msg: TMsg;
66begin
67  while PeekMessage(Msg, WizardForm.Handle, 0, 0, PM_REMOVE) do begin
68    TranslateMessage(Msg);
69    DispatchMessage(Msg);
70  end;
71end;
72
73{ Render text message for view, add spinner if necessary and scroll the window. }
74procedure SystemLogRefresh();
75begin
76  SystemCheckViewer.Lines := SystemLogText;
77
78  { Add Spinner to message. }
79  if ((TimeCounter > 0) and (TimeCounter < 6)) then begin
80    SystemCheckViewer.Lines[SystemCheckViewer.Lines.Count - 1] := SystemCheckViewer.Lines[SystemCheckViewer.Lines.Count - 1] + ' [' + Spinner[TimeCounter - 1] + ']';
81  end;
82
83  { Scroll window to the bottom of the log - https://stackoverflow.com/questions/64587596/is-it-possible-to-display-the-install-actions-in-a-list-in-inno-setup }
84  SendMessage(SystemCheckViewer.Handle, WM_VSCROLL, SB_BOTTOM, 0);
85end;
86
87{ Log message to file and display just a '.' to user so that user is not overloaded by details. }
88procedure SystemLogProgress(message:String);
89begin
90  Log(message);
91  if (SystemLogText.Count = 0) then begin
92    SystemLogText.Append('');
93  end;
94
95  SystemLogText[SystemLogText.Count - 1] := SystemLogText[SystemLogText.Count - 1] + '.';
96  SystemLogRefresh();
97end;
98
99{ Log message to file and display it to user as title message with asterisk prefix. }
100procedure SystemLogTitle(message:String);
101begin
102  message := '* ' + message;
103  Log(message);
104  SystemLogText.Append(message);
105  SystemLogRefresh();
106end;
107
108{ Log message to file and display it to user. }
109procedure SystemLog(message:String);
110begin
111  Log(message);
112    if (SystemLogText.Count = 0) then begin
113    SystemLogText.Append('');
114  end;
115
116  SystemLogText[SystemLogText.Count - 1] := SystemLogText[SystemLogText.Count - 1] + message;
117  SystemLogRefresh();
118end;
119
120{ Process timer tick during command execution so that the app keeps communicating with user. }
121procedure TimerTick();
122begin
123  { TimeCounter for animating Spinner. }
124  TimeCounter:=TimeCounter+1;
125  if (TimeCounter = 5) then begin
126    TimeCounter := 1;
127  end;
128
129  { Redraw Log with Spinner animation. }
130  SystemLogRefresh();
131
132  { Give control back to UI so that it can be updated. https://gist.github.com/jakoch/33ac13800c17eddb2dd4 }
133  AppProcessMessage;
134end;
135
136{ --- Command line nonblocking exec --- }
137function NonBlockingExec(command, workdir: String): Integer;
138var
139  Res: Integer;
140  Handle: Longword;
141  ExitCode: Integer;
142  LogTextAnsi: AnsiString;
143  LogText, LeftOver: String;
144
145begin
146  if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
147    ExitCode := -3;
148    Exit;
149  end;
150  try
151    ExitCode := -1;
152    { SystemLog('Workdir: ' + workdir); }
153    SystemLogProgress(' $ ' + command);
154    Handle := ProcStart(command, workdir)
155    if Handle = 0 then
156    begin
157      SystemLog('[' + CustomMessage('SystemCheckResultError') + ']');
158      Result := -2;
159      Exit;
160    end;
161    while (ExitCode = -1) and (SystemCheckState <> SYSTEM_CHECK_STATE_STOPPED) do
162    begin
163      ExitCode := ProcGetExitCode(Handle);
164      SetLength(LogTextAnsi, 4096);
165      Res := ProcGetOutput(Handle, LogTextAnsi, 4096)
166      if Res > 0 then
167      begin
168        SetLength(LogTextAnsi, Res);
169        LogText := LeftOver + String(LogTextAnsi);
170        SystemLogProgress(LogText);
171      end;
172      TimerTick();
173      Sleep(200);
174    end;
175    ProcEnd(Handle);
176  finally
177    if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then
178    begin
179      Result := -1;
180    end else begin
181      Result := ExitCode;
182    end;
183  end;
184end;
185
186{ Execute command for SystemCheck and reset timer so that Spinner will disappear after end of execution. }
187function SystemCheckExec(command, workdir: String): Integer;
188begin
189  TimeCounter := 0;
190  Result := NonBlockingExec(command, workdir);
191  TimeCounter := 0;
192end;
193
194{ Get formated line from SystemCheck for user. }
195function GetSystemCheckHint(Command: String; CustomCheckMessageKey:String):String;
196begin
197  Result := CustomMessage('SystemCheckUnableToExecute') + ' ' + Command + #13#10 + CustomMessage(CustomCheckMessageKey);
198end;
199
200{ Add command to list of fixes which can be executed by installer. }
201procedure AddFix(Command:String);
202begin
203  { Do not add possible fix command when check command was stopped by user. }
204  if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
205    Exit;
206  end;
207  Fixes.Append(Command);
208end;
209
210{ Execute checks to determine whether Python installation is valid so thet user can choose it to install IDF. }
211function IsPythonInstallationValid(displayName: String; pythonPath:String): Boolean;
212var
213  ResultCode: Integer;
214  ScriptFile: String;
215  TempDownloadFile: String;
216  Command: String;
217  VirtualEvnPath: String;
218  VirtualEnvPython: String;
219  RemedyCommand: String;
220begin
221  SystemLogTitle(CustomMessage('SystemCheckForComponent') + ' ' + displayName + ' ');
222  SystemCheckHint := '';
223
224  pythonPath := pythonPath + ' ';
225
226  Command := pythonPath + '-m pip --version';
227  ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
228  if (ResultCode <> 0) then begin
229    SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyMissingPip');
230    Result := False;
231    Exit;
232  end;
233
234  Command := pythonPath + '-m virtualenv --version';
235  ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
236  if (ResultCode <> 0) then begin
237    SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyMissingVirtualenv') + #13#10 + pythonPath + '-m pip install --upgrade pip' + #13#10 + pythonPath + '-m pip install virtualenv';
238    AddFix(pythonPath + '-m pip install --upgrade pip');
239    AddFix(pythonPath + '-m pip install virtualenv');
240    Result := False;
241    Exit;
242  end;
243
244  VirtualEnvCounter := VirtualEnvCounter + 1;
245  VirtualEvnPath := ExpandConstant('{tmp}\') + IntToStr(VirtualEnvCounter) + '-idf-test-venv\';
246  VirtualEnvPython := VirtualEvnPath + 'Scripts\python.exe ';
247  Command := pythonPath + '-m virtualenv ' + VirtualEvnPath;
248  ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
249  if (ResultCode <> 0) then begin
250    SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyCreateVirtualenv');
251    Result := False;
252    Exit;
253  end;
254
255  ScriptFile := ExpandConstant('{tmp}\system_check_virtualenv.py')
256  Command := VirtualEnvPython + ScriptFile + ' ' + VirtualEnvPython;
257  ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
258  if (ResultCode <> 0) then begin
259    SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyPythonInVirtualenv');
260    Result := False;
261    Exit;
262  end;
263
264  Command := VirtualEnvPython + '-m pip install --only-binary ":all:" "cryptography>=2.1.4" --no-binary future';
265  ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
266  if (ResultCode <> 0) then begin
267    SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyBinaryPythonWheel');
268    Result := False;
269    Exit;
270  end;
271
272  TempDownloadFile := IntToStr(VirtualEnvCounter) + '-idf-exe-v1.0.1.zip';
273  ScriptFile := ExpandConstant('{tmp}\system_check_download.py');
274  Command := VirtualEnvPython + ScriptFile + ExpandConstant(' https://dl.espressif.com/dl/idf-exe-v1.0.1.zip ' + TempDownloadFile);
275  ResultCode := SystemCheckExec(Command , ExpandConstant('{tmp}'));
276  if (ResultCode <> 0) then begin
277    SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyFailedHttpsDownload');
278    Result := False;
279    Exit;
280  end;
281
282  if (not FileExists(ExpandConstant('{tmp}\') + TempDownloadFile)) then begin
283    SystemLog(' [' + CustomMessage('SystemCheckResultFail') + '] - ' + CustomMessage('SystemCheckUnableToFindFile') + ' ' + ExpandConstant('{tmp}\') + TempDownloadFile);
284    Result := False;
285    Exit;
286  end;
287
288  ScriptFile := ExpandConstant('{tmp}\system_check_subprocess.py');
289  Command := pythonPath + ScriptFile;
290  ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
291  if (ResultCode <> 0) then begin
292    RemedyCommand := pythonPath + '-m pip uninstall subprocess.run';
293    SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyFailedSubmoduleRun') + #13#10 + RemedyCommand;
294    AddFix(RemedyCommand);
295    Result := False;
296    Exit;
297  end;
298
299  SystemLog(' [' + CustomMessage('SystemCheckResultOk') + ']');
300  Result := True;
301end;
302
303procedure FindPythonVersionsFromKey(RootKey: Integer; SubKeyName: String);
304var
305  CompanyNames: TArrayOfString;
306  CompanyName, CompanySubKey, TagName, TagSubKey: String;
307  ExecutablePath, DisplayName, Version: String;
308  TagNames: TArrayOfString;
309  CompanyId, TagId: Integer;
310  BaseDir: String;
311begin
312  if not RegGetSubkeyNames(RootKey, SubKeyName, CompanyNames) then
313  begin
314    Log('Nothing found in ' + IntToStr(RootKey) + '\' + SubKeyName);
315    Exit;
316  end;
317
318  for CompanyId := 0 to GetArrayLength(CompanyNames) - 1 do
319  begin
320    CompanyName := CompanyNames[CompanyId];
321
322    if CompanyName = 'PyLauncher' then
323      continue;
324
325    CompanySubKey := SubKeyName + '\' + CompanyName;
326    Log('In ' + IntToStr(RootKey) + '\' + CompanySubKey);
327
328    if not RegGetSubkeyNames(RootKey, CompanySubKey, TagNames) then
329      continue;
330
331    for TagId := 0 to GetArrayLength(TagNames) - 1 do
332    begin
333      TagName := TagNames[TagId];
334      TagSubKey := CompanySubKey + '\' + TagName;
335      Log('In ' + IntToStr(RootKey) + '\' + TagSubKey);
336
337      if not GetPythonVersionInfoFromKey(RootKey, SubKeyName, CompanyName, TagName, Version, DisplayName, ExecutablePath, BaseDir) then
338        continue;
339
340      if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
341        Exit;
342      end;
343
344      { Verify Python installation and display hint in case of invalid version or env. }
345      if not IsPythonInstallationValid(DisplayName, ExecutablePath) then begin
346        if ((Length(SystemCheckHint) > 0) and (SystemCheckState <> SYSTEM_CHECK_STATE_STOPPED)) then begin
347          SystemLogTitle(CustomMessage('SystemCheckHint') + ': ' + SystemCheckHint);
348        end;
349        continue;
350      end;
351
352      PythonVersionAdd(Version, DisplayName, ExecutablePath);
353    end;
354  end;
355end;
356
357procedure FindInstalledPythonVersions();
358begin
359  FindPythonVersionsFromKey(HKEY_CURRENT_USER, 'Software\Python');
360  FindPythonVersionsFromKey(HKEY_LOCAL_MACHINE, 'Software\Python');
361  FindPythonVersionsFromKey(HKEY_LOCAL_MACHINE, 'Software\Wow6432Node\Python');
362end;
363
364
365{ Get Boolean for UI to determine whether it make sense to register exceptions to Defender. }
366function GetWindowsDefenderStatus(): Boolean;
367var
368  bHasWD: Boolean;
369  szWDPath: String;
370  listPSModulePath: TStringList;
371  ResultCode: Integer;
372  x: Integer;
373begin
374  Log('Checking PSMODULEPATH for Windows Defender module');
375
376  listPSModulePath := TStringList.Create;
377  listPSModulePath.Delimiter := ';';
378  listPSModulePath.StrictDelimiter := True;
379  listPSModulePath.DelimitedText := GetEnv('PsModulePath');
380
381  for x:=0 to (listPSModulePath.Count-1) do
382  begin
383    szWDPath := listPSModulePath[x] + '\Defender'
384    bHasWD := DirExists(szWDPath);
385    if bHasWD then
386    begin
387      break;
388    end
389  end;
390
391  if not bHasWD then begin
392    Result := False;
393    Exit;
394  end;
395
396  Log('Checking Windows Services Defender is enabled: (Get-MpComputerStatus).AntivirusEnabled');
397  ResultCode := SystemCheckExec('powershell -ExecutionPolicy Bypass "if((Get-MpComputerStatus).AntivirusEnabled) { Exit 0 } else { Exit 1 }"', ExpandConstant('{tmp}'));
398  if (ResultCode <> 0)  then begin
399    Log('Result code: ' + IntToStr(ResultCode));
400    Result := False;
401    Exit;
402  end;
403
404  Result := True;
405end;
406
407{ Process user request to stop system checks. }
408function SystemCheckStopRequest():Boolean;
409begin
410  { In case of stopped check by user, procees to next/previous step. }
411  if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
412    Result := True;
413    Exit;
414  end;
415
416  if (SystemCheckState = SYSTEM_CHECK_STATE_RUNNING) then begin
417    if (MsgBox(CustomMessage('SystemCheckNotCompleteConsent'), mbConfirmation, MB_YESNO) = IDYES) then begin
418      SystemCheckState := SYSTEM_CHECK_STATE_STOPPED;
419      Result := True;
420      Exit;
421    end;
422  end;
423
424  if (SystemCheckState = SYSTEM_CHECK_STATE_COMPLETE) then begin
425    Result := True;
426  end else begin
427    Result := False;
428  end;
429end;
430
431{ Process request to proceed to next page. If the scan is running ask user for confirmation. }
432function OnSystemCheckValidate(Sender: TWizardPage): Boolean;
433begin
434  Result := SystemCheckStopRequest();
435end;
436
437{ Process request to go to previous screen (license). Prompt user for confirmation when system check is running. }
438function OnSystemCheckBackButton(Sender: TWizardPage): Boolean;
439begin
440  Result := SystemCheckStopRequest();
441end;
442
443{ Process request to stop System Check directly on the screen with System Check by Stop button. }
444procedure StopSystemCheckButtonClick(Sender: TObject);
445begin
446  SystemCheckStopRequest();
447end;
448
449{ Check whether site is reachable and that system trust the certificate. }
450procedure VerifyRootCertificates();
451var
452  ResultCode: Integer;
453  Command: String;
454  OutFile: String;
455begin
456  SystemLogTitle(CustomMessage('SystemCheckRootCertificates') + ' ');
457
458  { It's necessary to invoke PowerShell *BEFORE* Python. Invoke-Request will retrieve and add Root Certificate if necessary. }
459  { Without the certificate Python is failing to connect to https. }
460  { Windows command to list current certificates: certlm.msc }
461  OutFile := ExpandConstant('{tmp}\check');
462  Command := 'powershell -ExecutionPolicy Bypass ';
463  Command := Command + 'Invoke-WebRequest -Uri "https://dl.espressif.com/dl/?system_check=win' + GetWindowsVersionString + '" -OutFile "' + OutFile + '-1.txt";';
464  Command := Command + 'Invoke-WebRequest -Uri "https://github.com/espressif" -OutFile "' + OutFile + '-2.txt";';
465  {Command := Command + 'Invoke-WebRequest -Uri "https://www.s3.amazonaws.com/" -OutFile "' + OutFile + '-3.txt";';}
466  ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
467  if (ResultCode <> 0) then begin
468    SystemLog(' [' + CustomMessage('SystemCheckResultWarn') + ']');
469    SystemLog(CustomMessage('SystemCheckRootCertificateWarning'));
470  end else begin
471    SystemLog(' [' + CustomMessage('SystemCheckResultOk') + ']');
472  end;
473end;
474
475{ Execute system check }
476procedure ExecuteSystemCheck();
477var
478  UseEmbeddedPythonParam: String;
479begin
480  { Execute system check only once. Avoid execution in case of back button. }
481  if (SystemCheckState <> SYSTEM_CHECK_STATE_INIT) then begin
482    Exit;
483  end;
484
485  SystemCheckState := SYSTEM_CHECK_STATE_RUNNING;
486  SystemLogTitle(CustomMessage('SystemCheckStart'));
487  StopSystemCheckButton.Enabled := True;
488
489  VerifyRootCertificates();
490
491  { Search for the installed Python version only on explicit user request. }
492  UseEmbeddedPythonParam := ExpandConstant('{param:USEEMBEDDEDPYTHON|yes}');
493  if (UseEmbeddedPythonParam <> 'yes') then begin
494    FindInstalledPythonVersions();
495  end;
496
497  if (SystemCheckState <> SYSTEM_CHECK_STATE_STOPPED) then begin
498    SystemLogTitle(CustomMessage('SystemCheckForDefender') + ' ');
499    IsWindowsDefenderEnabled := GetWindowsDefenderStatus();
500    if (IsWindowsDefenderEnabled) then begin
501      SystemLog(' [' + CustomMessage('SystemCheckResultFound') + ']');
502    end else begin
503      SystemLog(' [' + CustomMessage('SystemCheckResultNotFound') + ']');
504    end;
505  end else begin
506    { User cancelled the check, let's enable Defender script so that use can decide to disable it. }
507    IsWindowsDefenderEnabled := True;
508  end;
509
510  if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
511    SystemLog('');
512    SystemLogTitle(CustomMessage('SystemCheckStopped'));
513  end else begin
514    SystemLogTitle(CustomMessage('SystemCheckComplete'));
515    SystemCheckState := SYSTEM_CHECK_STATE_COMPLETE;
516  end;
517
518  { Enable Apply Script button if some fixes are available. }
519  if (Fixes.Count > 0) then begin
520    ApplyFixesButton.Enabled := True;
521  end;
522
523  StopSystemCheckButton.Enabled := False;
524end;
525
526{ Invoke scan of system environment. }
527procedure OnSystemCheckActivate(Sender: TWizardPage);
528var SystemCheckParam:String;
529begin
530  { Display special controls. For some reason the first call of the page does not invoke SystemCheckOnCurPageChanged. }
531  FullLogButton.Visible := True;
532  ApplyFixesButton.Visible := True;
533  StopSystemCheckButton.Visible := True;
534  SystemCheckViewer.Visible := True;
535
536  SystemCheckParam := ExpandConstant('{param:SKIPSYSTEMCHECK|no}');
537  if (SystemCheckParam = 'yes') then begin
538    SystemCheckState := SYSTEM_CHECK_STATE_STOPPED;
539    SystemLog('System Check disabled by command line option /SKIPSYSTEMCHECK.');
540  end;
541
542  ExecuteSystemCheck();
543end;
544
545{ Handle request to display full log from the installation. Open the log in notepad. }
546procedure FullLogButtonClick(Sender: TObject);
547var
548  ResultCode: Integer;
549begin
550  Exec(ExpandConstant('{win}\notepad.exe'), ExpandConstant('{log}'), '', SW_SHOW, ewNoWait, ResultCode);
551end;
552
553{ Handle request to apply available fixes. }
554procedure ApplyFixesButtonClick(Sender: TObject);
555var
556  ResultCode: Integer;
557  FixIndex: Integer;
558  AreFixesApplied: Boolean;
559begin
560  if (MsgBox(CustomMessage('SystemCheckApplyFixesConsent'), mbConfirmation, MB_YESNO) = IDNO) then begin
561    Exit;
562  end;
563
564  ApplyFixesButton.Enabled := false;
565  SystemCheckState := SYSTEM_CHECK_STATE_INIT;
566  SystemLog('');
567  SystemLogTitle('Starting application of fixes');
568
569  AreFixesApplied := True;
570  for FixIndex := 0 to Fixes.Count - 1 do
571  begin
572    ResultCode := SystemCheckExec(Fixes[FixIndex], ExpandConstant('{tmp}'));
573    if (ResultCode <> 0) then begin
574      AreFixesApplied := False;
575      break;
576    end;
577  end;
578
579  SystemLog('');
580  if (AreFixesApplied) then begin
581    SystemLogTitle(CustomMessage('SystemCheckFixesSuccessful'));
582  end else begin
583    SystemLogTitle(CustomMessage('SystemCheckFixesFailed'));
584  end;
585
586  SystemLog('');
587  Fixes.Clear();
588
589  { Restart system check. }
590  ExecuteSystemCheck();
591end;
592
593{ Add Page for System Check so that user is informed about readiness of the system. }
594<event('InitializeWizard')>
595procedure CreateSystemCheckPage();
596begin
597  { Initialize data structure for Python }
598  InstalledPythonVersions := TStringList.Create();
599  InstalledPythonDisplayNames := TStringList.Create();
600  InstalledPythonExecutables := TStringList.Create();
601  PythonVersionAdd('{#PythonVersion}', 'Use Python {#PythonVersion} Embedded (Recommended)', 'tools\python\{#PythonVersion}\python.exe');
602
603  { Create Spinner animation. }
604  Spinner := TStringList.Create();
605  Spinner.Append('-');
606  Spinner.Append('\');
607  Spinner.Append('|');
608  Spinner.Append('/');
609
610  VirtualEnvCounter := 0;
611  Fixes := TStringList.Create();
612  SystemCheckState := SYSTEM_CHECK_STATE_INIT;
613  SystemCheckPage := CreateOutputMsgPage(wpLicense, CustomMessage('PreInstallationCheckTitle'), CustomMessage('PreInstallationCheckSubtitle'), '');
614
615  with SystemCheckPage do
616  begin
617    OnActivate := @OnSystemCheckActivate;
618    OnBackButtonClick := @OnSystemCheckBackButton;
619    OnNextButtonClick := @OnSystemCheckValidate;
620  end;
621
622  SystemCheckViewer := TNewMemo.Create(WizardForm);
623  with SystemCheckViewer do
624  begin
625    Parent := WizardForm;
626    Left := ScaleX(10);
627    Top := ScaleY(60);
628    ReadOnly := True;
629    Font.Name := 'Courier New';
630    Height := WizardForm.CancelButton.Top - ScaleY(40);
631    Width := WizardForm.ClientWidth + ScaleX(80);
632    WordWrap := True;
633    Visible := False;
634  end;
635
636  SystemLogText := TStringList.Create;
637
638  FullLogButton := TNewButton.Create(WizardForm);
639  with FullLogButton do
640  begin
641    Parent := WizardForm;
642    Left := WizardForm.ClientWidth;
643    Top := SystemCheckViewer.Top + SystemCheckViewer.Height + ScaleY(5);
644    Width := WizardForm.CancelButton.Width;
645    Height := WizardForm.CancelButton.Height;
646    Caption := CustomMessage('SystemCheckFullLogButtonCaption');
647    OnClick := @FullLogButtonClick;
648    Visible := False;
649  end;
650
651  ApplyFixesButton := TNewButton.Create(WizardForm);
652  with ApplyFixesButton do
653  begin
654    Parent := WizardForm;
655    Left := WizardForm.ClientWidth - FullLogButton.Width;
656    Top := FullLogButton.Top;
657    Width := WizardForm.CancelButton.Width;
658    Height := WizardForm.CancelButton.Height;
659    Caption := CustomMessage('SystemCheckApplyFixesButtonCaption');
660    OnClick := @ApplyFixesButtonClick;
661    Visible := False;
662    Enabled := False;
663  end;
664
665  StopSystemCheckButton := TNewButton.Create(WizardForm);
666  with StopSystemCheckButton do
667  begin
668    Parent := WizardForm;
669    Left := ApplyFixesButton.Left - ApplyFixesButton.Width;
670    Top := FullLogButton.Top;
671    Width := WizardForm.CancelButton.Width;
672    Height := WizardForm.CancelButton.Height;
673    Caption := CustomMessage('SystemCheckStopButtonCaption');
674    OnClick := @StopSystemCheckButtonClick;
675    Visible := False;
676    Enabled := False;
677  end;
678
679  { Extract helper files for sanity check of Python environment. }
680  ExtractTemporaryFile('system_check_download.py')
681  ExtractTemporaryFile('system_check_subprocess.py')
682  ExtractTemporaryFile('system_check_virtualenv.py')
683end;
684
685{ Process Cancel Button Click event. Prompt user to confirm Cancellation of System check. }
686{ Then continue with normal cancel window. }
687procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
688begin
689  if ((CurPageId = SystemCheckPage.ID) and (SystemCheckState = SYSTEM_CHECK_STATE_RUNNING)) then begin
690    SystemCheckStopRequest();
691  end;
692end;
693
694{ Display control specific for System Check page. }
695<event('CurPageChanged')>
696procedure SystemCheckOnCurPageChanged(CurPageID: Integer);
697begin
698  FullLogButton.Visible := CurPageID = SystemCheckPage.ID;
699  ApplyFixesButton.Visible := CurPageID = SystemCheckPage.ID;
700  StopSystemCheckButton.Visible := CurPageID = SystemCheckPage.ID;
701  SystemCheckViewer.Visible := CurPageID = SystemCheckPage.ID;
702end;
703