Merge branch 'bugfix/win_inst_defender_check' into 'master'

Tools: Windows Installer add pre-installation screen with System checks for Python and Windows Defender

Closes IDF-1819

See merge request espressif/esp-idf!11152
pull/6365/head
Ivan Grokhotkov 2020-12-14 15:43:10 +08:00
commit 17b9fca02f
19 zmienionych plików z 890 dodań i 100 usunięć

Wyświetl plik

@ -0,0 +1,40 @@
; Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
; SPDX-License-Identifier: Apache-2.0
[LangOptions]
LanguageName=English
LanguageID=$0409
[CustomMessages]
PreInstallationCheckTitle=Pre-installation system check
PreInstallationCheckSubtitle=Verification of environment
SystemCheckStart=Starting system check ...
SystemCheckForDefender=Checking Windows Defender
SystemCheckHint=Hint
SystemCheckResultFound=FOUND
SystemCheckResultNotFound=NOT FOUND
SystemCheckResultOk=OK
SystemCheckResultFail=FAIL
SystemCheckResultError=ERR
SystemCheckResultWarn=WARN
SystemCheckStopped=Check stopped.
SystemCheckStopButtonCaption=Stop
SystemCheckComplete=Check complete.
SystemCheckForComponent=Checking installed
SystemCheckUnableToExecute=Unable to execute
SystemCheckUnableToFindFile=Unable to find file
SystemCheckRemedyMissingPip=Please use a supported version of Python available on the next screen.
SystemCheckRemedyMissingVirtualenv=Please install virtualenv and retry the installation. Suggested commands:
SystemCheckRemedyCreateVirtualenv=Please use the supported Python version that is available on the next screen.
SystemCheckRemedyPythonInVirtualenv=Please use the supported Python version that is available on the next screen.
SystemCheckRemedyBinaryPythonWheel=Please use the supported Python version that is available on the next screen.
SystemCheckRemedyFailedHttpsDownload=Please use the supported Python version that is available on the next screen.
SystemCheckRemedyFailedSubmoduleRun=Python contains a subprocess.run module intended for Python 2. Please uninstall the module. Suggested command:
SystemCheckApplyFixesButtonCaption=Apply Fixes
SystemCheckFullLogButtonCaption=Full log
SystemCheckApplyFixesConsent=Do you want to apply the commands with the suggested fixes to update your Windows environment and start a new System Check?
SystemCheckFixesSuccessful=Successful application of Fixes.
SystemCheckFixesFailed=Failed application of Fixes. Please refer to the Full log.
SystemCheckNotCompleteConsent=System check is not complete. Do you want to proceed by skipping checks?
SystemCheckRootCertificates=Checking certificates
SystemCheckRootCertificateWarning=Unable to load data from server dl.espressif.com.

Wyświetl plik

@ -14,6 +14,14 @@ Some functionality of the installer depends on additional programs:
* [cmdlinerunner](cmdlinerunner/cmdlinerunner.c) — a helper DLL used to run external command line programs from the installer, capture live console output, and get the exit code.
## Instalation of dependencies via Chocolatey
Run with Administrator privileges:
```
choco install inno-download-plugin
```
## Building the installer
### In Docker

Wyświetl plik

@ -1,9 +1,12 @@
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
var
ChoicePagePrepare: array of TNotifyEvent;
ChoicePageSelectionChange: array of TNotifyEvent;
ChoicePageValidate: array of TWizardPageButtonEvent;
ChoicePageMaxTag: Integer;
ChoicePages: array of TInputOptionWizardPage;
ChoicePages: array of TWizardPage;
procedure ChoicePageOnClickCheck(Sender: TObject);
var

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Progress & log page for command line tools ------------------------------ }

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Find installed copies of Git ------------------------------ }

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Page to select Git ------------------------------ }

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Page to select the version of ESP-IDF to download ------------------------------ }

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Page to select whether to download ESP-IDF, or use an existing copy ------------------------------ }

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Downloading ESP-IDF ------------------------------ }
@ -90,6 +90,24 @@ begin
FindFileRecursive(Path + '\.git', 'alternates', @RemoveAlternatesFile);
end;
{
Run git config fileMode is repairing problem when git repo was zipped on Linux and extracted on Windows.
The repo and submodules are marked as dirty which confuses users that fresh installation already contains changes.
More information: https://mirrors.edge.kernel.org/pub/software/scm/git/docs/git-config.html
}
procedure GitRepoFixFileMode(Path: String);
var
CmdLine: String;
begin
CmdLine := GitExecutablePath + ' -C ' + Path + ' config --local core.fileMode false';
Log('Setting core.fileMode on repository: ' + CmdLine);
DoCmdlineInstall('Finishing ESP-IDF installation', 'Updating fileMode', CmdLine);
Log('Setting core.fileMode on repository for submodules: ' + CmdLine);
CmdLine := GitExecutablePath + ' -C ' + Path + ' submodule foreach --recursive git config --local core.fileMode false';
DoCmdlineInstall('Finishing ESP-IDF installation', 'Updating fileMode in submodules', CmdLine);
end;
{ Run git reset --hard in the repo and in the submodules, to fix the newlines. }
procedure GitRepoFixNewlines(Path: String);
var
@ -183,6 +201,7 @@ begin
}
CmdLine := ExpandConstant('cmd.exe /c ""xcopy" /s /e /i /h /q "' + IDFTempPath + '" "' + IDFPath + '""');
DoCmdlineInstall('Extracting ESP-IDF', 'Copying ESP-IDF into the destination directory', CmdLine);
GitRepoFixFileMode(IDFPath);
GitRepoFixNewlines(IDFPath);
DelTree(IDFTempPath, True, True, True);
end;
@ -248,20 +267,95 @@ begin
DoCmdlineInstall('Installing Python environment', '', CmdLine);
end;
{ Find Major and Minor version in esp_idf_version.h file. }
function GetIDFVersionFromHeaderFile():String;
var
HeaderFileName: String;
HeaderLines: TArrayOfString;
LineIndex: Integer;
LineCount: Longint;
Line: String;
MajorVersion: String;
MinorVersion: String;
begin
HeaderFileName := GetIDFPath('') + '\components\esp_common\include\esp_idf_version.h';
if (not FileExists(HeaderFileName)) then begin
Result := '';
Exit;
end;
LoadStringsFromFile(HeaderFileName, HeaderLines);
LineCount := GetArrayLength(HeaderLines);
for LineIndex := 0 to LineCount - 1 do begin
Line := HeaderLines[LineIndex];
if (pos('define ESP_IDF_VERSION_MAJOR', Line) > 0) then begin
Delete(Line, 1, 29);
MajorVersion := Trim(Line);
end else if (pos('define ESP_IDF_VERSION_MINOR', Line) > 0) then begin
Delete(Line, 1, 29);
MinorVersion := Trim(Line);
Result := MajorVersion + '.' + MinorVersion;
Exit;
end
end;
end;
{ ------------------------------ Start menu shortcut ------------------------------ }
procedure CreateIDFCommandPromptShortcut(LnkString: String);
var
Destination: String;
Description: String;
VersionIndex: Integer;
MajorString: String;
MinorString: String;
DotIndex: Integer;
IDFVersionString: String;
PythonVirtualEnvPath: String;
Command: String;
begin
ForceDirectories(ExpandConstant(LnkString));
Destination := ExpandConstant(LnkString + '\{#IDFCmdExeShortcutFile}');
Description := '{#IDFCmdExeShortcutDescription}';
IDFVersionString := IDFDownloadVersion;
{ Transform version vx.y or release/vx.y to x.y }
VersionIndex := pos('v', IDFVersionString);
if (VersionIndex > 0) then begin
Delete(IDFVersionString, 1, VersionIndex);
end;
{ Transform version x.y.z to x.y }
DotIndex := pos('.', IDFVersionString);
if (DotIndex > 0) then begin
MajorString := Copy(IDFVersionString, 1, DotIndex - 1);
Delete(IDFVersionString, 1, DotIndex);
{ Trim trailing version numbers. }
DotIndex := pos('.', IDFVersionString);
if (DotIndex > 0) then begin
MinorString := Copy(IDFVersionString, 1, DotIndex - 1);
IDFVersionString := MajorString + '.' + MinorString;
end else begin
IDFVersionString := MajorString + '.' + IDFVersionString;
end;
end;
{ Transform master to x.y }
if (IDFVersionString = 'master') then begin
IDFVersionString := GetIDFVersionFromHeaderFile();
end;
{ The links should contain reference to Python vitual env }
PythonVirtualEnvPath := ExpandConstant('{app}\python_env\idf') + IDFVersionString + '_py' + PythonVersion + '_env\Scripts';
{ Fallback in case of not existing environment. }
if (not FileExists(PythonVirtualEnvPath + '\python.exe')) then begin
PythonVirtualEnvPath := PythonPath;
end;
{ If cmd.exe command argument starts with a quote, the first and last quote chars in the command
will be removed by cmd.exe; each argument needs to be surrounded by quotes as well. }
Command := ExpandConstant('/k ""{app}\idf_cmd_init.bat" "') + PythonPath + '" "' + GitPath + '""';
Command := ExpandConstant('/k ""{app}\idf_cmd_init.bat" "') + PythonVirtualEnvPath + '" "' + GitPath + '""';
Log('CreateShellLink Destination=' + Destination + ' Description=' + Description + ' Command=' + Command)
try
CreateShellLink(
@ -290,47 +384,22 @@ begin
end;
<event('CurPageChanged')>
procedure CheckWinDefenderAvailable(CurPageID: Integer);
var
bHasWD: Boolean;
szHasWD: String;
szWDPath: String;
listPSModulePath: TStringList;
x: Integer;
begin
if CurPageID = wpSelectTasks then
begin
listPSModulePath := TStringList.Create;
listPSModulePath.Delimiter := ';';
listPSModulePath.StrictDelimiter := True;
listPSModulePath.DelimitedText := GetEnv('PsModulePath');
Log('Checking PSMODULEPATH for Windows Defender module...');
for x:=0 to (listPSModulePath.Count-1) do
begin
szWDPath := listPSModulePath[x] + '\Defender'
bHasWD := DirExists(szWDPath);
if bHasWD then
begin
szHasWD := 'YES (' + szWDPath + ')';
Break;
end
else
szHasWD := 'NO';
end;
Log('CheckWinDefenderAvailable: ' + szHasWD);
{ WD registration checkbox is identified by 'Windows Defender' substring anywhere in its caption.
Please, keep this in mind when making changes }
for x:=0 to (WizardForm.TasksList.Items.Count-1) do
begin
if Pos('Windows Defender', WizardForm.TasksList.ItemCaption[x]) > 0 then
begin
WizardForm.TasksList.ItemEnabled[x] := bHasWD;
WizardForm.TasksList.Checked[x] := bHasWD;
WizardForm.TasksList.ItemEnabled[x] := isWindowsDefenderEnabled;
WizardForm.TasksList.Checked[x] := isWindowsDefenderEnabled;
break;
end;
end;

Wyświetl plik

@ -1,4 +1,4 @@
; Copyright 2019 Espressif Systems (Shanghai) PTE LTD
; Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
; SPDX-License-Identifier: Apache-2.0
#pragma include __INCLUDE__ + ";" + ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir")
@ -51,7 +51,7 @@ ChangesEnvironment=yes
WizardStyle=modern
[Languages]
Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "english"; MessagesFile: "compiler:Default.isl,Languages/idf_tool_en-US.islu"
[Dirs]
Name: "{app}\dist"
@ -65,6 +65,11 @@ Source: "..\..\idf_tools.py"; DestDir: "{app}"; DestName: "idf_tools_fallback.py
Source: "tools_fallback.json"; DestDir: "{app}"; DestName: "tools_fallback.json"
Source: "idf_cmd_init.bat"; DestDir: "{app}"
Source: "dist\*"; DestDir: "{app}\dist"
; Helper Python files for sanity check of Python environment - used by system_check_page
Source: "system_check\system_check_download.py"; Flags: dontcopy
Source: "system_check\system_check_subprocess.py"; Flags: dontcopy
Source: "system_check\system_check_virtualenv.py"; Flags: dontcopy
; Helper PowerShell scripts for managing exceptions in Windows Defender
Source: "tools_WD_excl.ps1"; DestDir: "{app}\dist"
Source: "tools_WD_clean.ps1"; DestDir: "{app}\dist"
@ -104,6 +109,7 @@ Root: HKCU; Subkey: "Environment"; ValueType: string; ValueName: "IDF_TOOLS_PATH
#include "idf_page.iss.inc"
#include "git_page.iss.inc"
#include "python_page.iss.inc"
#include "system_check_page.iss.inc"
#include "idf_download_page.iss.inc"
#include "idf_setup.iss.inc"
#include "summary.iss.inc"

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Custom steps before the main installation flow ------------------------------ }

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Find installed Python interpreters in Windows Registry (see PEP 514) ------------------------------ }
@ -19,7 +19,8 @@ end;
function GetPythonVersionInfoFromKey(RootKey: Integer; SubKeyName, CompanyName, TagName: String;
var Version: String;
var DisplayName: String;
var ExecutablePath: String): Boolean;
var ExecutablePath: String;
var BaseDir: String): Boolean;
var
TagKey, InstallPathKey, DefaultPath: String;
begin
@ -39,6 +40,8 @@ begin
ExecutablePath := DefaultPath + '\python.exe';
end;
BaseDir := DefaultPath;
if not RegQueryStringValue(RootKey, TagKey, 'SysVersion', Version) then
begin
if CompanyName = 'PythonCore' then
@ -59,55 +62,3 @@ begin
Result := True;
end;
procedure FindPythonVersionsFromKey(RootKey: Integer; SubKeyName: String);
var
CompanyNames: TArrayOfString;
CompanyName, CompanySubKey, TagName, TagSubKey: String;
ExecutablePath, DisplayName, Version: String;
TagNames: TArrayOfString;
CompanyId, TagId: Integer;
begin
if not RegGetSubkeyNames(RootKey, SubKeyName, CompanyNames) then
begin
Log('Nothing found in ' + IntToStr(RootKey) + '\' + SubKeyName);
Exit;
end;
for CompanyId := 0 to GetArrayLength(CompanyNames) - 1 do
begin
CompanyName := CompanyNames[CompanyId];
if CompanyName = 'PyLauncher' then
continue;
CompanySubKey := SubKeyName + '\' + CompanyName;
Log('In ' + IntToStr(RootKey) + '\' + CompanySubKey);
if not RegGetSubkeyNames(RootKey, CompanySubKey, TagNames) then
continue;
for TagId := 0 to GetArrayLength(TagNames) - 1 do
begin
TagName := TagNames[TagId];
TagSubKey := CompanySubKey + '\' + TagName;
Log('In ' + IntToStr(RootKey) + '\' + TagSubKey);
if not GetPythonVersionInfoFromKey(RootKey, SubKeyName, CompanyName, TagName, Version, DisplayName, ExecutablePath) then
continue;
PythonVersionAdd(Version, DisplayName, ExecutablePath);
end;
end;
end;
procedure FindInstalledPythonVersions();
begin
InstalledPythonVersions := TStringList.Create();
InstalledPythonDisplayNames := TStringList.Create();
InstalledPythonExecutables := TStringList.Create();
FindPythonVersionsFromKey(HKEY_CURRENT_USER, 'Software\Python');
FindPythonVersionsFromKey(HKEY_LOCAL_MACHINE, 'Software\Python');
FindPythonVersionsFromKey(HKEY_LOCAL_MACHINE, 'Software\Wow6432Node\Python');
end;

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Page to select Python interpreter ------------------------------ }
@ -50,8 +50,6 @@ begin
if Page.CheckListBox.Items.Count > 0 then
exit;
FindInstalledPythonVersions();
VersionToInstall := '{#PythonVersion}';
OfferToInstall := True;
FirstEnabledIndex := -1;
@ -119,11 +117,11 @@ end;
procedure PythonExecutablePathUpdateAfterInstall();
var
Version, DisplayName, ExecutablePath: String;
Version, DisplayName, ExecutablePath, BaseDir: String;
begin
if not GetPythonVersionInfoFromKey(
HKEY_CURRENT_USER, 'Software\Python', 'PythonCore', '{#PythonVersion}',
Version, DisplayName, ExecutablePath) then
Version, DisplayName, ExecutablePath, BaseDir) then
begin
Log('Failed to find ExecutablePath for the installed copy of Python');
exit;

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Installation summary page ------------------------------ }

Wyświetl plik

@ -0,0 +1,12 @@
#!/usr/bin/env python
import sys
download_url = sys.argv[1]
output_filename = sys.argv[2]
if (sys.version_info > (3, 0)):
import urllib.request
urllib.request.urlretrieve(download_url, output_filename)
else:
import urllib2
response = urllib2.urlopen(download_url)
with open(output_filename, "w") as output_file:
output_file.write(response.read())

Wyświetl plik

@ -0,0 +1,5 @@
#!/usr/bin/env python
import sys
if (sys.version_info > (3, 0)):
import subprocess
subprocess.run("cmd /c echo hello")

Wyświetl plik

@ -0,0 +1,10 @@
#!/usr/bin/env python
import sys
expected_executable = sys.argv[1]
active_executable = sys.executable
if expected_executable != active_executable:
print("Failure. Expected executable does not match current executable.")
print("Expected:", expected_executable)
print("Active: ", active_executable)
sys.exit(1)

Wyświetl plik

@ -0,0 +1,688 @@
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ SystemCheck states }
const
SYSTEM_CHECK_STATE_INIT = 0; { No check was executed yet. }
SYSTEM_CHECK_STATE_RUNNING = 1; { Check is in progress and can be cancelled. }
SYSTEM_CHECK_STATE_COMPLETE = 2; { Check is complete. }
SYSTEM_CHECK_STATE_STOPPED = 3; { User stopped the check. }
var
{ RTF View to display content of system check. }
SystemCheckViewer: TNewMemo;
{ Indicate state of System Check. }
SystemCheckState:Integer;
{ Text representation of log messages which are then converte to RTF. }
SystemLogText: TStringList;
{ Message for user which gives a hint how to correct the problem. }
SystemCheckHint: String;
{ Setup Page which displays progress/result of system check. }
SystemCheckPage: TOutputMsgWizardPage;
{ TimeCounter for Spinner animation invoked during command execution. }
TimeCounter:Integer;
{ Spinner is TStringList, because characters like backslash must be escaped and stored on two bytes. }
Spinner: TStringList;
{ Button to request display of full log of system check/installation. }
FullLogButton: TNewButton;
{ Button to request application of available fixtures. }
ApplyFixesButton: TNewButton;
{ Commands which should be executed to fix problems discovered during system check. }
Fixes: TStringList;
{ Button to request Stop of System Checks manually. }
StopSystemCheckButton: TNewButton;
{ Count number of createde virtualenv to avoid collision with previous runs. }
VirtualEnvCounter: Integer;
{ Indicates whether system check was able to find running Windows Defender. }
var IsWindowsDefenderEnabled: Boolean;
{ Const values for user32.dll which allows scrolling of the text view. }
const
WM_VSCROLL = $0115;
SB_BOTTOM = 7;
type
TMsg = record
hwnd: HWND;
message: UINT;
wParam: Longint;
lParam: Longint;
time: DWORD;
pt: TPoint;
end;
const
PM_REMOVE = 1;
{ Functions to communicate via Windows API. }
function PeekMessage(var lpMsg: TMsg; hWnd: HWND; wMsgFilterMin, wMsgFilterMax, wRemoveMsg: UINT): BOOL; external 'PeekMessageW@user32.dll stdcall';
function TranslateMessage(const lpMsg: TMsg): BOOL; external 'TranslateMessage@user32.dll stdcall';
function DispatchMessage(const lpMsg: TMsg): Longint; external 'DispatchMessageW@user32.dll stdcall';
procedure AppProcessMessage;
var
Msg: TMsg;
begin
while PeekMessage(Msg, WizardForm.Handle, 0, 0, PM_REMOVE) do begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end;
{ Render text message for view, add spinner if necessary and scroll the window. }
procedure SystemLogRefresh();
begin
SystemCheckViewer.Lines := SystemLogText;
{ Add Spinner to message. }
if ((TimeCounter > 0) and (TimeCounter < 6)) then begin
SystemCheckViewer.Lines[SystemCheckViewer.Lines.Count - 1] := SystemCheckViewer.Lines[SystemCheckViewer.Lines.Count - 1] + ' [' + Spinner[TimeCounter - 1] + ']';
end;
{ 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 }
SendMessage(SystemCheckViewer.Handle, WM_VSCROLL, SB_BOTTOM, 0);
end;
{ Log message to file and display just a '.' to user so that user is not overloaded by details. }
procedure SystemLogProgress(message:String);
begin
Log(message);
if (SystemLogText.Count = 0) then begin
SystemLogText.Append('');
end;
SystemLogText[SystemLogText.Count - 1] := SystemLogText[SystemLogText.Count - 1] + '.';
SystemLogRefresh();
end;
{ Log message to file and display it to user as title message with asterisk prefix. }
procedure SystemLogTitle(message:String);
begin
message := '* ' + message;
Log(message);
SystemLogText.Append(message);
SystemLogRefresh();
end;
{ Log message to file and display it to user. }
procedure SystemLog(message:String);
begin
Log(message);
if (SystemLogText.Count = 0) then begin
SystemLogText.Append('');
end;
SystemLogText[SystemLogText.Count - 1] := SystemLogText[SystemLogText.Count - 1] + message;
SystemLogRefresh();
end;
{ Process timer tick during command execution so that the app keeps communicating with user. }
procedure TimerTick();
begin
{ TimeCounter for animating Spinner. }
TimeCounter:=TimeCounter+1;
if (TimeCounter = 5) then begin
TimeCounter := 1;
end;
{ Redraw Log with Spinner animation. }
SystemLogRefresh();
{ Give control back to UI so that it can be updated. https://gist.github.com/jakoch/33ac13800c17eddb2dd4 }
AppProcessMessage;
end;
{ --- Command line nonblocking exec --- }
function NonBlockingExec(command, workdir: String): Integer;
var
Res: Integer;
Handle: Longword;
ExitCode: Integer;
LogTextAnsi: AnsiString;
LogText, LeftOver: String;
begin
if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
ExitCode := -3;
Exit;
end;
try
ExitCode := -1;
{ SystemLog('Workdir: ' + workdir); }
SystemLogProgress(' $ ' + command);
Handle := ProcStart(command, workdir)
if Handle = 0 then
begin
SystemLog('[' + CustomMessage('SystemCheckResultError') + ']');
Result := -2;
Exit;
end;
while (ExitCode = -1) and (SystemCheckState <> SYSTEM_CHECK_STATE_STOPPED) do
begin
ExitCode := ProcGetExitCode(Handle);
SetLength(LogTextAnsi, 4096);
Res := ProcGetOutput(Handle, LogTextAnsi, 4096)
if Res > 0 then
begin
SetLength(LogTextAnsi, Res);
LogText := LeftOver + String(LogTextAnsi);
SystemLogProgress(LogText);
end;
TimerTick();
Sleep(200);
end;
ProcEnd(Handle);
finally
if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then
begin
Result := -1;
end else begin
Result := ExitCode;
end;
end;
end;
{ Execute command for SystemCheck and reset timer so that Spinner will disappear after end of execution. }
function SystemCheckExec(command, workdir: String): Integer;
begin
TimeCounter := 0;
Result := NonBlockingExec(command, workdir);
TimeCounter := 0;
end;
{ Get formated line from SystemCheck for user. }
function GetSystemCheckHint(Command: String; CustomCheckMessageKey:String):String;
begin
Result := CustomMessage('SystemCheckUnableToExecute') + ' ' + Command + #13#10 + CustomMessage(CustomCheckMessageKey);
end;
{ Add command to list of fixes which can be executed by installer. }
procedure AddFix(Command:String);
begin
{ Do not add possible fix command when check command was stopped by user. }
if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
Exit;
end;
Fixes.Append(Command);
end;
{ Execute checks to determine whether Python installation is valid so thet user can choose it to install IDF. }
function IsPythonInstallationValid(displayName: String; pythonPath:String): Boolean;
var
ResultCode: Integer;
ScriptFile: String;
TempDownloadFile: String;
Command: String;
VirtualEvnPath: String;
VirtualEnvPython: String;
RemedyCommand: String;
begin
SystemLogTitle(CustomMessage('SystemCheckForComponent') + ' ' + displayName + ' ');
SystemCheckHint := '';
pythonPath := pythonPath + ' ';
Command := pythonPath + '-m pip --version';
ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyMissingPip');
Result := False;
Exit;
end;
Command := pythonPath + '-m virtualenv --version';
ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyMissingVirtualenv') + #13#10 + pythonPath + '-m pip install --upgrade pip' + #13#10 + pythonPath + '-m pip install virtualenv';
AddFix(pythonPath + '-m pip install --upgrade pip');
AddFix(pythonPath + '-m pip install virtualenv');
Result := False;
Exit;
end;
VirtualEnvCounter := VirtualEnvCounter + 1;
VirtualEvnPath := ExpandConstant('{tmp}\') + IntToStr(VirtualEnvCounter) + '-idf-test-venv\';
VirtualEnvPython := VirtualEvnPath + 'Scripts\python.exe ';
Command := pythonPath + '-m virtualenv ' + VirtualEvnPath;
ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyCreateVirtualenv');
Result := False;
Exit;
end;
ScriptFile := ExpandConstant('{tmp}\system_check_virtualenv.py')
Command := VirtualEnvPython + ScriptFile + ' ' + VirtualEnvPython;
ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyPythonInVirtualenv');
Result := False;
Exit;
end;
Command := VirtualEnvPython + '-m pip install --only-binary ":all:" "cryptography>=2.1.4" --no-binary future';
ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyBinaryPythonWheel');
Result := False;
Exit;
end;
TempDownloadFile := IntToStr(VirtualEnvCounter) + '-idf-exe-v1.0.1.zip';
ScriptFile := ExpandConstant('{tmp}\system_check_download.py');
Command := VirtualEnvPython + ScriptFile + ExpandConstant(' https://dl.espressif.com/dl/idf-exe-v1.0.1.zip ' + TempDownloadFile);
ResultCode := SystemCheckExec(Command , ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyFailedHttpsDownload');
Result := False;
Exit;
end;
if (not FileExists(ExpandConstant('{tmp}\') + TempDownloadFile)) then begin
SystemLog(' [' + CustomMessage('SystemCheckResultFail') + '] - ' + CustomMessage('SystemCheckUnableToFindFile') + ' ' + ExpandConstant('{tmp}\') + TempDownloadFile);
Result := False;
Exit;
end;
ScriptFile := ExpandConstant('{tmp}\system_check_subprocess.py');
Command := pythonPath + ScriptFile;
ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
RemedyCommand := pythonPath + '-m pip uninstall subprocess.run';
SystemCheckHint := GetSystemCheckHint(Command, 'SystemCheckRemedyFailedSubmoduleRun') + #13#10 + RemedyCommand;
AddFix(RemedyCommand);
Result := False;
Exit;
end;
SystemLog(' [' + CustomMessage('SystemCheckResultOk') + ']');
Result := True;
end;
procedure FindPythonVersionsFromKey(RootKey: Integer; SubKeyName: String);
var
CompanyNames: TArrayOfString;
CompanyName, CompanySubKey, TagName, TagSubKey: String;
ExecutablePath, DisplayName, Version: String;
TagNames: TArrayOfString;
CompanyId, TagId: Integer;
BaseDir: String;
begin
if not RegGetSubkeyNames(RootKey, SubKeyName, CompanyNames) then
begin
Log('Nothing found in ' + IntToStr(RootKey) + '\' + SubKeyName);
Exit;
end;
for CompanyId := 0 to GetArrayLength(CompanyNames) - 1 do
begin
CompanyName := CompanyNames[CompanyId];
if CompanyName = 'PyLauncher' then
continue;
CompanySubKey := SubKeyName + '\' + CompanyName;
Log('In ' + IntToStr(RootKey) + '\' + CompanySubKey);
if not RegGetSubkeyNames(RootKey, CompanySubKey, TagNames) then
continue;
for TagId := 0 to GetArrayLength(TagNames) - 1 do
begin
TagName := TagNames[TagId];
TagSubKey := CompanySubKey + '\' + TagName;
Log('In ' + IntToStr(RootKey) + '\' + TagSubKey);
if not GetPythonVersionInfoFromKey(RootKey, SubKeyName, CompanyName, TagName, Version, DisplayName, ExecutablePath, BaseDir) then
continue;
if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
Exit;
end;
{ Verify Python installation and display hint in case of invalid version or env. }
if not IsPythonInstallationValid(DisplayName, ExecutablePath) then begin
if ((Length(SystemCheckHint) > 0) and (SystemCheckState <> SYSTEM_CHECK_STATE_STOPPED)) then begin
SystemLogTitle(CustomMessage('SystemCheckHint') + ': ' + SystemCheckHint);
end;
continue;
end;
PythonVersionAdd(Version, DisplayName, ExecutablePath);
end;
end;
end;
procedure FindInstalledPythonVersions();
begin
FindPythonVersionsFromKey(HKEY_CURRENT_USER, 'Software\Python');
FindPythonVersionsFromKey(HKEY_LOCAL_MACHINE, 'Software\Python');
FindPythonVersionsFromKey(HKEY_LOCAL_MACHINE, 'Software\Wow6432Node\Python');
end;
{ Get Boolean for UI to determine whether it make sense to register exceptions to Defender. }
function GetWindowsDefenderStatus(): Boolean;
var
bHasWD: Boolean;
szWDPath: String;
listPSModulePath: TStringList;
ResultCode: Integer;
x: Integer;
begin
Log('Checking PSMODULEPATH for Windows Defender module');
listPSModulePath := TStringList.Create;
listPSModulePath.Delimiter := ';';
listPSModulePath.StrictDelimiter := True;
listPSModulePath.DelimitedText := GetEnv('PsModulePath');
for x:=0 to (listPSModulePath.Count-1) do
begin
szWDPath := listPSModulePath[x] + '\Defender'
bHasWD := DirExists(szWDPath);
if bHasWD then
begin
break;
end
end;
if not bHasWD then begin
Result := False;
Exit;
end;
Log('Checking Windows Services Defender is enabled: (Get-MpComputerStatus).AntivirusEnabled');
ResultCode := SystemCheckExec('powershell -ExecutionPolicy Bypass "if((Get-MpComputerStatus).AntivirusEnabled) { Exit 0 } else { Exit 1 }"', ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
Log('Result code: ' + IntToStr(ResultCode));
Result := False;
Exit;
end;
Result := True;
end;
{ Process user request to stop system checks. }
function SystemCheckStopRequest():Boolean;
begin
{ In case of stopped check by user, procees to next/previous step. }
if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
Result := True;
Exit;
end;
if (SystemCheckState = SYSTEM_CHECK_STATE_RUNNING) then begin
if (MsgBox(CustomMessage('SystemCheckNotCompleteConsent'), mbConfirmation, MB_YESNO) = IDYES) then begin
SystemCheckState := SYSTEM_CHECK_STATE_STOPPED;
Result := True;
Exit;
end;
end;
if (SystemCheckState = SYSTEM_CHECK_STATE_COMPLETE) then begin
Result := True;
end else begin
Result := False;
end;
end;
{ Process request to proceed to next page. If the scan is running ask user for confirmation. }
function OnSystemCheckValidate(Sender: TWizardPage): Boolean;
begin
Result := SystemCheckStopRequest();
end;
{ Process request to go to previous screen (license). Prompt user for confirmation when system check is running. }
function OnSystemCheckBackButton(Sender: TWizardPage): Boolean;
begin
Result := SystemCheckStopRequest();
end;
{ Process request to stop System Check directly on the screen with System Check by Stop button. }
procedure StopSystemCheckButtonClick(Sender: TObject);
begin
SystemCheckStopRequest();
end;
{ Check whether site is reachable and that system trust the certificate. }
procedure VerifyRootCertificates();
var
ResultCode: Integer;
Command: String;
OutFile: String;
begin
SystemLogTitle(CustomMessage('SystemCheckRootCertificates') + ' ');
{ It's necessary to invoke PowerShell *BEFORE* Python. Invoke-Request will retrieve and add Root Certificate if necessary. }
{ Without the certificate Python is failing to connect to https. }
{ Windows command to list current certificates: certlm.msc }
OutFile := ExpandConstant('{tmp}\check');
Command := 'powershell -ExecutionPolicy Bypass ';
Command := Command + 'Invoke-WebRequest -Uri "https://dl.espressif.com/dl/?system_check=win' + GetWindowsVersionString + '" -OutFile "' + OutFile + '-1.txt";';
Command := Command + 'Invoke-WebRequest -Uri "https://github.com/espressif" -OutFile "' + OutFile + '-2.txt";';
{Command := Command + 'Invoke-WebRequest -Uri "https://www.s3.amazonaws.com/" -OutFile "' + OutFile + '-3.txt";';}
ResultCode := SystemCheckExec(Command, ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
SystemLog(' [' + CustomMessage('SystemCheckResultWarn') + ']');
SystemLog(CustomMessage('SystemCheckRootCertificateWarning'));
end else begin
SystemLog(' [' + CustomMessage('SystemCheckResultOk') + ']');
end;
end;
{ Execute system check }
procedure ExecuteSystemCheck();
begin
{ Execute system check only once. Avoid execution in case of back button. }
if (SystemCheckState <> SYSTEM_CHECK_STATE_INIT) then begin
Exit;
end;
SystemCheckState := SYSTEM_CHECK_STATE_RUNNING;
SystemLogTitle(CustomMessage('SystemCheckStart'));
StopSystemCheckButton.Enabled := True;
VerifyRootCertificates();
FindInstalledPythonVersions();
if (SystemCheckState <> SYSTEM_CHECK_STATE_STOPPED) then begin
SystemLogTitle(CustomMessage('SystemCheckForDefender') + ' ');
IsWindowsDefenderEnabled := GetWindowsDefenderStatus();
if (IsWindowsDefenderEnabled) then begin
SystemLog(' [' + CustomMessage('SystemCheckResultFound') + ']');
end else begin
SystemLog(' [' + CustomMessage('SystemCheckResultNotFound') + ']');
end;
end else begin
{ User cancelled the check, let's enable Defender script so that use can decide to disable it. }
IsWindowsDefenderEnabled := True;
end;
if (SystemCheckState = SYSTEM_CHECK_STATE_STOPPED) then begin
SystemLog('');
SystemLogTitle(CustomMessage('SystemCheckStopped'));
end else begin
SystemLogTitle(CustomMessage('SystemCheckComplete'));
SystemCheckState := SYSTEM_CHECK_STATE_COMPLETE;
end;
{ Enable Apply Script button if some fixes are available. }
if (Fixes.Count > 0) then begin
ApplyFixesButton.Enabled := True;
end;
StopSystemCheckButton.Enabled := False;
end;
{ Invoke scan of system environment. }
procedure OnSystemCheckActivate(Sender: TWizardPage);
begin
{ Display special controls. For some reason the first call of the page does not invoke SystemCheckOnCurPageChanged. }
FullLogButton.Visible := True;
ApplyFixesButton.Visible := True;
StopSystemCheckButton.Visible := True;
SystemCheckViewer.Visible := True;
ExecuteSystemCheck();
end;
{ Handle request to display full log from the installation. Open the log in notepad. }
procedure FullLogButtonClick(Sender: TObject);
var
ResultCode: Integer;
begin
Exec(ExpandConstant('{win}\notepad.exe'), ExpandConstant('{log}'), '', SW_SHOW, ewNoWait, ResultCode);
end;
{ Handle request to apply available fixes. }
procedure ApplyFixesButtonClick(Sender: TObject);
var
ResultCode: Integer;
FixIndex: Integer;
AreFixesApplied: Boolean;
begin
if (MsgBox(CustomMessage('SystemCheckApplyFixesConsent'), mbConfirmation, MB_YESNO) = IDNO) then begin
Exit;
end;
ApplyFixesButton.Enabled := false;
SystemCheckState := SYSTEM_CHECK_STATE_INIT;
SystemLog('');
SystemLogTitle('Starting application of fixes');
AreFixesApplied := True;
for FixIndex := 0 to Fixes.Count - 1 do
begin
ResultCode := SystemCheckExec(Fixes[FixIndex], ExpandConstant('{tmp}'));
if (ResultCode <> 0) then begin
AreFixesApplied := False;
break;
end;
end;
SystemLog('');
if (AreFixesApplied) then begin
SystemLogTitle(CustomMessage('SystemCheckFixesSuccessful'));
end else begin
SystemLogTitle(CustomMessage('SystemCheckFixesFailed'));
end;
SystemLog('');
Fixes.Clear();
{ Restart system check. }
ExecuteSystemCheck();
end;
{ Add Page for System Check so that user is informed about readiness of the system. }
<event('InitializeWizard')>
procedure CreateSystemCheckPage();
begin
{ Initialize data structure for Python }
InstalledPythonVersions := TStringList.Create();
InstalledPythonDisplayNames := TStringList.Create();
InstalledPythonExecutables := TStringList.Create();
{ Create Spinner animation. }
Spinner := TStringList.Create();
Spinner.Append('-');
Spinner.Append('\');
Spinner.Append('|');
Spinner.Append('/');
VirtualEnvCounter := 0;
Fixes := TStringList.Create();
SystemCheckState := SYSTEM_CHECK_STATE_INIT;
SystemCheckPage := CreateOutputMsgPage(wpLicense, CustomMessage('PreInstallationCheckTitle'), CustomMessage('PreInstallationCheckSubtitle'), '');
with SystemCheckPage do
begin
OnActivate := @OnSystemCheckActivate;
OnBackButtonClick := @OnSystemCheckBackButton;
OnNextButtonClick := @OnSystemCheckValidate;
end;
SystemCheckViewer := TNewMemo.Create(WizardForm);
with SystemCheckViewer do
begin
Parent := WizardForm;
Left := ScaleX(10);
Top := ScaleY(60);
ReadOnly := True;
Font.Name := 'Courier New';
Height := WizardForm.CancelButton.Top - ScaleY(40);
Width := WizardForm.ClientWidth + ScaleX(80);
WordWrap := True;
Visible := False;
end;
SystemLogText := TStringList.Create;
FullLogButton := TNewButton.Create(WizardForm);
with FullLogButton do
begin
Parent := WizardForm;
Left := WizardForm.ClientWidth;
Top := SystemCheckViewer.Top + SystemCheckViewer.Height + ScaleY(5);
Width := WizardForm.CancelButton.Width;
Height := WizardForm.CancelButton.Height;
Caption := CustomMessage('SystemCheckFullLogButtonCaption');
OnClick := @FullLogButtonClick;
Visible := False;
end;
ApplyFixesButton := TNewButton.Create(WizardForm);
with ApplyFixesButton do
begin
Parent := WizardForm;
Left := WizardForm.ClientWidth - FullLogButton.Width;
Top := FullLogButton.Top;
Width := WizardForm.CancelButton.Width;
Height := WizardForm.CancelButton.Height;
Caption := CustomMessage('SystemCheckApplyFixesButtonCaption');
OnClick := @ApplyFixesButtonClick;
Visible := False;
Enabled := False;
end;
StopSystemCheckButton := TNewButton.Create(WizardForm);
with StopSystemCheckButton do
begin
Parent := WizardForm;
Left := ApplyFixesButton.Left - ApplyFixesButton.Width;
Top := FullLogButton.Top;
Width := WizardForm.CancelButton.Width;
Height := WizardForm.CancelButton.Height;
Caption := CustomMessage('SystemCheckStopButtonCaption');
OnClick := @StopSystemCheckButtonClick;
Visible := False;
Enabled := False;
end;
{ Extract helper files for sanity check of Python environment. }
ExtractTemporaryFile('system_check_download.py')
ExtractTemporaryFile('system_check_subprocess.py')
ExtractTemporaryFile('system_check_virtualenv.py')
end;
{ Process Cancel Button Click event. Prompt user to confirm Cancellation of System check. }
{ Then continue with normal cancel window. }
procedure CancelButtonClick(CurPageID: Integer; var Cancel, Confirm: Boolean);
begin
if ((CurPageId = SystemCheckPage.ID) and (SystemCheckState = SYSTEM_CHECK_STATE_RUNNING)) then begin
SystemCheckStopRequest();
end;
end;
{ Display control specific for System Check page. }
<event('CurPageChanged')>
procedure SystemCheckOnCurPageChanged(CurPageID: Integer);
begin
FullLogButton.Visible := CurPageID = SystemCheckPage.ID;
ApplyFixesButton.Visible := CurPageID = SystemCheckPage.ID;
StopSystemCheckButton.Visible := CurPageID = SystemCheckPage.ID;
SystemCheckViewer.Visible := CurPageID = SystemCheckPage.ID;
end;

Wyświetl plik

@ -1,4 +1,4 @@
{ Copyright 2019 Espressif Systems (Shanghai) PTE LTD
{ Copyright 2019-2020 Espressif Systems (Shanghai) CO LTD
SPDX-License-Identifier: Apache-2.0 }
{ ------------------------------ Helper functions from libcmdlinerunner.dll ------------------------------ }