]> granicus.if.org Git - esp-idf/commitdiff
Installer: support for WD exclusions (IDF Tools)
authorMartin Vychodil <martin@espressif.com>
Tue, 20 Aug 2019 06:37:45 +0000 (08:37 +0200)
committerMartin Vychodil <martin@espressif.com>
Mon, 23 Sep 2019 12:42:32 +0000 (14:42 +0200)
JIRA IDF-306

tools/windows/tool_setup/idf_setup.iss.inc
tools/windows/tool_setup/idf_tool_setup.iss
tools/windows/tool_setup/main.iss.inc
tools/windows/tool_setup/tools_WD_clean.ps1 [new file with mode: 0644]
tools/windows/tool_setup/tools_WD_excl.ps1 [new file with mode: 0644]

index c1dd9e11392f4b547107ecbb44b7882a80783914..63716772f2254eef3bb535d10988665344e7df9d 100644 (file)
@@ -255,3 +255,15 @@ begin
     RaiseException('Failed to create the shortcut');
   end;
 end;
+
+
+{ ------------------------------ WD exclusion registration ------------------------------ }
+
+procedure RegisterIDFToolsExecutablesInWD();
+var
+  CmdLine: String;
+begin
+  CmdLine := ExpandConstant('powershell -ExecutionPolicy ByPass -File "{app}\dist\tools_WD_excl.ps1" -AddExclPath "{app}\*.exe"');
+  Log('Registering IDF Tools executables in Windows Defender: ' + CmdLine);
+  DoCmdlineInstall('Finishing ESP-IDF installation', 'Registering IDF Tools executables in Windows Defender', CmdLine);
+end;
index 13869feaecef935202012d0c37e88d0149e0e1b3..25a997069a0f0cb0b8206dc8fc76668fac2f39d7 100644 (file)
@@ -65,6 +65,8 @@ 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"
+Source: "tools_WD_excl.ps1"; DestDir: "{app}\dist"
+Source: "tools_WD_clean.ps1"; DestDir: "{app}\dist"
 
 [UninstallDelete]
 Type: filesandordirs; Name: "{app}\dist"
@@ -72,18 +74,26 @@ Type: filesandordirs; Name: "{app}\releases"
 Type: filesandordirs; Name: "{app}\tools"
 Type: filesandordirs; Name: "{app}\python_env"
 
+[Tasks]
+Name: createlnk; Description: "Create Desktop shortcut for ESP-IDF Tools Command Line";
+Name: wdexcl; Description: "Register ESP-IDF Tools executables as Windows Defender exclusions (improves compilation speed, requires elevation)";
+
 [Run]
 Filename: "{app}\dist\{#PythonInstallerName}"; Parameters: "/passive PrependPath=1 InstallLauncherAllUsers=0 Include_dev=0 Include_tcltk=0 Include_launcher=0 Include_test=0 Include_doc=0"; Description: "Installing Python"; Check: PythonInstallRequired
 Filename: "{app}\dist\{#GitInstallerName}"; Parameters: "/silent /tasks="""" /norestart"; Description: "Installing Git"; Check: GitInstallRequired
 Filename: "{group}\{#IDFCmdExeShortcutFile}"; Flags: postinstall shellexec; Description: "Run ESP-IDF Command Prompt (cmd.exe)"; Check: InstallationSuccessful
 
-[Registry]
+
+[UninstallRun]
+Filename: "powershell.exe"; \
+  Parameters: "-ExecutionPolicy Bypass -File ""{app}\dist\tools_WD_clean.ps1"" -RmExclPath ""{app}"""; \
+  WorkingDir: {app}; Flags: runhidden
+
+[Registry]                                                                                                        
 Root: HKCU; Subkey: "Environment"; ValueType: string; ValueName: "IDF_TOOLS_PATH"; \
     ValueData: "{app}"; Flags: preservestringtype createvalueifdoesntexist uninsdeletevalue deletevalue;
 
 [Code]
-
-
 #include "utils.iss.inc"
 #include "choice_page.iss.inc"
 #include "cmdline_page.iss.inc"
index a023f14d6d11cd096ab4d914814c260ad4cb60a5..288bf6bd83715e688301dc0459e47e0d62570d72 100644 (file)
@@ -119,7 +119,17 @@ begin
 
     IDFToolsSetup();
 
+
+  if WizardIsTaskSelected('createlnk') then
+  begin
     CreateIDFCommandPromptShortcut();
+  end;
+   
+  if WizardIsTaskSelected('wdexcl') then
+  begin
+    RegisterIDFToolsExecutablesInWD();
+  end; 
+
   except
     SetupAborted := True;
     if MsgBox('Installation log has been created, it may contain more information about the problem.' + #13#10
diff --git a/tools/windows/tool_setup/tools_WD_clean.ps1 b/tools/windows/tool_setup/tools_WD_clean.ps1
new file mode 100644 (file)
index 0000000..0c88686
--- /dev/null
@@ -0,0 +1,166 @@
+################################################################################
+#
+# Microsoft WindowsDefender exclusions cleaner
+# Espressif Systems, 2019
+#
+################################################################################
+#
+# - cleans all Windows Defender process exclusions containing given path (both Process and Path)
+# - run as Administrator, eg: PowerShell -ExecutionPolicy ByPass -File tools_WD_clean.ps1 -RmExclPath "C:\Program Files\Espressif\ESP-IDF Tools". If not running with admin privileges, the script tries to elevate itself (new process, output grabbed on exit)
+# minimum requirements: Windows XP SP3, PowerShell 2.0, Windows Defender with relevant PS cmdlets
+# - Returns 0 on success or -1 on failure
+#
+################################################################################
+
+
+Param
+(
+       [String]$RmExclPath,
+       [String]$logFile
+)
+
+Import-Module Defender
+
+function Check-Command($cmdname)
+{
+    return [bool](Get-Command -Name $cmdname -ErrorAction SilentlyContinue)
+}
+
+function Log-Msg($msg, $logF = $null)
+{
+       if( ![string]::IsNullOrEmpty($logF) ) { Write-Output $msg *>> $logF } 
+       else { Write-Output $msg }
+       [Console]::Out.Flush()
+}
+
+
+Try
+{      
+       #self-elevation support
+       $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
+       $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
+       $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
+
+       if( -not $myWindowsPrincipal.IsInRole($adminRole) ) { 
+          
+                $params = ""
+                foreach($key in $PSBoundParameters.keys) {
+                        $params = -join( $params, "-", $key, " `"", $PSBoundParameters[$key], "`"" )
+                }
+               
+               #running elevated and logFile not set
+               if( [string]::IsNullOrEmpty($logFile) ) {
+                       $tempFileName = Get-Date -UFormat "%Y%m%d%H%M%s"
+                       $lf = Join-Path -Path $env:TEMP -ChildPath "WDEspLog$tempFileName.log"
+                       #Write-Output "Logfile: $lf"
+               }
+               
+               $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell"
+               $newProcess.Arguments = "-ExecutionPolicy ByPass -File " + $script:MyInvocation.MyCommand.Definition + " " + $params + " -logFile $lf"
+               $newProcess.Verb = "RunAs"
+               $newProcess.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
+               
+               $proc = [System.Diagnostics.Process]::Start($newProcess)
+               $proc.WaitForExit()
+
+               if (Test-Path -Path $lf ) {
+                       foreach($line in Get-Content $lf) {
+                               Log-Msg -msg $line
+                       }
+                       Remove-Item $lf
+               }
+               
+               #Write-Output "Process finished with code " $proc.ExitCode      
+               exit $proc.ExitCode
+       }
+
+       Log-Msg -msg "Getting Windows Defender process exclusions..." -logF $logFile
+       
+       $Preferences = Get-MpPreference
+
+       #ExclusionProcess       
+       $cnt = $Preferences.ExclusionProcess.Count
+       $cntRemoved = 0
+       $cntRemovedTotal = 0
+       $cntMissed = 0
+       $cntMissedTotal = 0
+
+       $bRmPath = ![string]::IsNullOrEmpty($RmExclPath)
+       if( $bRmPath ) { Log-Msg -msg "Exclusion path: $RmExclPath" -logF $logFile }
+
+       Log-Msg -msg " Found total $cnt of ExclusionProcess items" -logF $logFile
+
+       foreach( $pref in $Preferences.ExclusionProcess ) {     
+
+               if( $bRmPath ) { $bGoAhead = $pref.Contains($RmExclPath) }
+               else { $bGoAhead = $true }
+               
+               if( $bGoAhead ) {
+                       Log-Msg -msg "  removing $pref" -logF $logFile
+                       Try 
+                       { 
+                               Remove-MpPreference -ExclusionProcess $pref
+                               $cntRemoved++
+                       } 
+                       Catch 
+                       {
+                               if( ![string]::IsNullOrEmpty($logFile) ) { Write-Error -Exception $_.Exception *>> $logFile }
+                               Write-Error -Exception $_.Exception
+                               $cntMissed++
+                       }
+               }
+       }
+       
+       if( $cntMissed -eq 0 ) { Log-Msg -msg " $cntRemoved relevant items removed from ExclusionProcess list" -logF $logFile }
+       else { Log-Msg -msg " WARNING: Only $cntRemoved out of $(cntRemoved+cntMissed) relevant items removed from ExclusionProcess list" -logF $logFile }
+
+       #ExclusionPath
+       $cnt = $Preferences.ExclusionPath.Count
+       $cntRemovedTotal = $cntRemoved
+       $cntRemoved = 0 
+       $cntMissedTotal = $cntMissed
+       $cntMissed = 0
+
+       Log-Msg -msg " Found total $cnt of ExclusionPath items" -logF $logFile
+
+       foreach( $pref in $Preferences.ExclusionPath ) {
+
+               if( $bRmPath ) { $bGoAhead = $pref.Contains($RmExclPath) }
+               else { $bGoAhead = $true }
+               
+               if( $bGoAhead ) {
+                       Log-Msg -msg "  removing $pref" -logF $logFile
+                       Try 
+                       { 
+                               Remove-MpPreference -ExclusionPath $pref
+                               $cntRemoved++
+                       } 
+                       Catch 
+                       {
+                               if( ![string]::IsNullOrEmpty($logFile) ) { Write-Error -Exception $_.Exception *>> $logFile }
+                               Write-Error -Exception $_.Exception
+                               $cntMissed++
+                       }                               
+               }
+       }
+
+       if( $cntMissed -eq 0 ) { Log-Msg -msg " $cntRemoved relevant items removed from ExclusionPath list" -logF $logFile }
+       else { Log-Msg -msg " WARNING: Only $cntRemoved out of $(cntRemoved+cntMissed) relevant items removed from ExclusionPath list" -logF $logFile }
+
+       #TOTAL
+       $cntRemovedTotal += $cntRemoved
+       $cntMissedTotal += $cntMissed
+       
+       Log-Msg -msg "============================" -logF $logFile
+       if( $cntMissedTotal -eq 0 ) { Log-Msg -msg "OK: Processed all $cntRemovedTotal items" -logF $logFile }
+       else { Log-Msg -msg "WARNING: Processed only $cntRemovedTotal out of $(cntRemovedTotal+cntMissedTotal) relevat items" -logF $logFile }
+
+       Log-Msg -msg  "`nDone" -logF $logFile
+}
+Catch
+{
+       if( ![string]::IsNullOrEmpty($logFile) ) { Write-Error -Exception $_.Exception *>> $logFile }
+       Write-Error -Exception $_.Exception
+       exit -1
+}
+
diff --git a/tools/windows/tool_setup/tools_WD_excl.ps1 b/tools/windows/tool_setup/tools_WD_excl.ps1
new file mode 100644 (file)
index 0000000..f099c81
--- /dev/null
@@ -0,0 +1,238 @@
+################################################################################
+#
+# Microsoft WindowsDefender exclusions handler
+# Espressif Systems, 2019
+#
+################################################################################
+#      
+#      PS utility to add/remove PROCESS exceptions to/from MS WD real-time 
+#      scanning. Files (referenced by 'path' or 'path\filemask') are expected
+#      to be Windows process executables, for obvious reasons.
+#
+#      The script requires Administrator privileges to succeed -> self-elevation procedure is involved
+#
+# Usage:
+#
+#      PowerShell -ExecutionPolicy ByPass -File tools_WD_excl.ps1 <ARGUMENTS>
+#
+#      ARGUMENTS:
+#              -AddExclPath <path | path\*.filemask>
+#                      add all matching files in the path (recursive) to the WD exception list
+#      
+#              -AddExclFile <filepath>
+#                      adds file to the WD exception list exactly as specified by 'filepath'
+#
+#              -RmExclPath <path | path\*.filemask>
+#                      remove all matching files in the path (recursive) from WD exclusions
+#
+#              -RmExclFile <filepath>
+#                      adds file to the WD exception list exactly as specified by 'filepath'
+#
+#              -logFile <filepath>
+#                      stdout/stderr redirection file. Used internally for elevated process (generated in tempdir, deleted after the script finishing) 
+#                      use manually at your own risk
+#
+#      Returns 0 on success or -1 on failure
+#
+#
+#      Example:
+#              PowerShell -ExecutionPolicy ByPass -File tools_WD_excl.ps1 -AddExclPath "C:\Program Files\Espressif\ESP-IDF Tools\*.exe"
+#
+#      Notes:
+#               - default scenario is set to the following 
+#                      -AddExclPath "$Env:ProgramFiles\Espressif\ESP-IDF Tools\*.exe"
+#                      (eg when called with no params)
+#               - only named parameters are supported, any other use-cases redirect to the default
+#               - multiple paths/files in 1 parameter are not supported by this version
+#               - minimum requirements: Windows XP SP3, PowerShell 2.0, Windows Defender with relevant PS cmdlets
+#
+################################################################################
+
+
+Param
+(
+       [String]$AddExclPath,
+       [String]$AddExclFile,
+       [String]$RmExclPath,
+       [String]$RmExclFile,
+       [String]$logFile
+)
+
+Import-Module Defender
+
+function Check-Command($cmdname)
+{
+    return [bool](Get-Command -Name $cmdname -ErrorAction SilentlyContinue)
+}
+
+function Log-Msg($msg, $logF = $null)
+{
+       if( ![string]::IsNullOrEmpty($logF) ) { Write-Output $msg *>> $logF } 
+       else { Write-Output $msg }
+       [Console]::Out.Flush()
+}
+
+
+Try
+{      
+       #self-elevation support
+       $myWindowsID=[System.Security.Principal.WindowsIdentity]::GetCurrent()
+       $myWindowsPrincipal=new-object System.Security.Principal.WindowsPrincipal($myWindowsID)
+       $adminRole=[System.Security.Principal.WindowsBuiltInRole]::Administrator
+
+       if( -not $myWindowsPrincipal.IsInRole($adminRole) ) { 
+          
+                $params = ""
+                foreach($key in $PSBoundParameters.keys) {
+                        $params = -join( $params, "-", $key, " `"", $PSBoundParameters[$key], "`"" )
+                }
+               
+               #running elevated and logFile not set
+               if( [string]::IsNullOrEmpty($logFile) ) {
+                       $tempFileName = Get-Date -UFormat "%Y%m%d%H%M%s"
+                       $lf = Join-Path -Path $env:TEMP -ChildPath "WDEspLog$tempFileName.log"
+                       #Write-Output "Logfile: $lf"
+               }
+               
+               $newProcess = new-object System.Diagnostics.ProcessStartInfo "PowerShell"
+               $newProcess.Arguments = "-ExecutionPolicy ByPass -File " + $script:MyInvocation.MyCommand.Definition + " " + $params + " -logFile $lf"
+               $newProcess.Verb = "RunAs"
+               $newProcess.WindowStyle = [System.Diagnostics.ProcessWindowStyle]::Hidden
+               
+               $proc = [System.Diagnostics.Process]::Start($newProcess)
+               $proc.WaitForExit()
+
+               if (Test-Path -Path $lf ) {
+                       foreach($line in Get-Content $lf) {
+                               Log-Msg -msg $line
+                       }
+                       Remove-Item $lf
+               }
+               
+               #Write-Output "Process finished with code " $proc.ExitCode      
+               exit $proc.ExitCode
+       }
+       
+       #parameter sanity check
+       if( $Args.Count -gt 0 ) {
+               $Exception = [ArgumentException]::new("Only named parameters are supported: $Args")
+               throw $Exception
+       }
+
+       #check WinDefender cmdlets are available
+       if (!(Check-Command -cmdname 'Add-MpPreference')) {
+               $Exception = [NotSupportedException ]::new("Windows Defender cmdlets not available")
+               throw $Exception
+       }
+
+       $pathsToExclude = New-Object 'System.Collections.Generic.List[String]'
+       $filesToExclude = New-Object 'System.Collections.Generic.List[String]'
+       $pathsToInclude = New-Object 'System.Collections.Generic.List[String]'
+       $filesToRemove = New-Object 'System.Collections.Generic.List[String]'
+
+       if( $PSBoundParameters.Count -gt 0 ) {
+
+               $bAddPath = ![string]::IsNullOrEmpty($AddExclPath)
+               $bAddFile = ![string]::IsNullOrEmpty($AddExclFile)
+               $bRmPath = ![string]::IsNullOrEmpty($RmExclPath)
+               $bRmFile = ![string]::IsNullOrEmpty($RmExclFile)
+               
+               if( !$bAddPath -And !$bAddFile -And !$bRmPath -And !$bRmFile ) {
+                       $Exception = [ArgumentException]::new("Invalid parameter(s): $Args")
+                       throw $Exception
+               }
+
+               #ADD exclusion paths
+               if( $bAddPath ) {
+                       #foreach ($wdPath in $AddExclPath) {
+                       #       $pathsToExclude.Add( $wdPath )
+                       #}
+                       $pathsToExclude.Add( $AddExclPath )
+               }
+
+               #ADD exclusion files
+               if( $bAddFile ) {
+                       #foreach ($wdFile in $AddExclFile) {
+                       #       $filesToExclude.Add( $wdFile )
+                       #}
+                       $filesToExclude.Add( $AddExclFile )
+               }
+
+               #REMOVE exclusion paths
+               if( $bRmPath ) {
+                       #foreach ($wdPath in $RmExclPath) {
+                       #       $pathsToInclude.Add( $wdPath )
+                       #}
+                       $pathsToInclude.Add( $RmExclPath )
+               }
+
+               #ADD exclusion file
+               if( $bAddFile ) {
+                       #foreach ($wdFile in $RmExclFile) {
+                       #       $filesToRemove.Add( $wdFile )
+                       #}
+                       $filesToRemove.Add( $RmExclFile )
+               }
+       }
+       #default: throw exception
+       else {
+               $Exception = [ArgumentException]::new("Mandatory parameter missing")
+               throw $Exception
+       }
+
+
+       #to exclude all files opened by a process including the process' binary, a record must be added to both Exclusions/Paths and Exclusions/Processes configurations, see
+       # https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-antivirus/configure-process-opened-file-exclusions-windows-defender-antivirus :
+       # "When you add a process to the process exclusion list, Windows Defender Antivirus won't scan files opened by that process, no matter where the files are located. The process itself, however, will be scanned unless it has also been added to the file exclusion list.
+       #The exclusions only apply to always-on real-time protection and monitoring. They don't apply to scheduled or on-demand scans."
+
+       Log-Msg -msg "Updating Windows Defender real-time scan exclusions:" -logF $logFile
+
+       $itemCount = 0
+
+       #exclusions
+       foreach( $exclPath in $pathsToExclude ) {
+               $exclFiles = Get-ChildItem -Recurse -File -Path $exclPath | % { $_.FullName }
+               foreach ($exfile in $exclFiles) {
+                       Log-Msg -msg " adding $exfile" -logF $logFile
+                       Add-MpPreference -ExclusionProcess $exfile
+                       Add-MpPreference -ExclusionPath $exfile
+                       $itemCount++
+               }
+       }
+
+       ### ! better run in separate, adding files to exclusion object array from above is very inefficient (forced reallocations)
+       foreach ($exfile1 in $filesToExclude) {
+               Log-Msg -msg " adding $exfile1" -logF $logFile
+               Add-MpPreference -ExclusionProcess $exfile1
+               Add-MpPreference -ExclusionPath $exfile1
+               $itemCount++
+       }
+
+       #inclusions
+       foreach( $inclPath in $pathsToInclude ) {
+               $inclFiles = Get-ChildItem -Recurse -File -Path $inclPath | % { $_.FullName }
+               foreach ($infile in $inclFiles) {
+                       Log-Msg -msg " removing $infile" -logF $logFile
+                       Remove-MpPreference -ExclusionProcess $infile
+                       Remove-MpPreference -ExclusionPath $infile
+                       $itemCount++
+               }
+       }
+
+       ### ! see exclusions
+       foreach ($infile1 in $filesToExclude) {
+               Log-Msg -msg " removing $infile1" -logF $logFile
+               Remove-MpPreference -ExclusionProcess $infile1
+               Remove-MpPreference -ExclusionPath $infile1
+               $itemCount++
+       }
+
+       Log-Msg -msg "Done (processed $itemCount items)" -logF $logFile
+}
+Catch
+{
+       if( ![string]::IsNullOrEmpty($logFile) ) { Write-Error -Exception $_.Exception *>> $logFile }
+       Write-Error -Exception $_.Exception
+       exit -1
+}