mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
[platform] changing the way the Windows Defender checker works (IDEA-298058)
GitOrigin-RevId: 64a290b7e8d7d56854bc7ae9f6d7f665be057285
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ce42d3729c
commit
5c2eec522d
48
bin/win/defender-exclusions.ps1
Normal file
48
bin/win/defender-exclusions.ps1
Normal file
@@ -0,0 +1,48 @@
|
||||
<#
|
||||
The script adds paths, given as parameters, to the Windows Defender folder exclusion list,
|
||||
unless they are already excluded.
|
||||
#>
|
||||
|
||||
#Requires -RunAsAdministrator
|
||||
|
||||
if ($args.Count -eq 0) {
|
||||
Write-Host "usage: $PSCommandPath path [path ...]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
Import-Module Defender
|
||||
|
||||
# returns `$true` when a path is already covered by the exclusion list
|
||||
function Test-Excluded ([string] $path, [string[]] $exclusions) {
|
||||
foreach ($exclusion in $exclusions) {
|
||||
$expanded = [System.Environment]::ExpandEnvironmentVariables($exclusion)
|
||||
$resolvedPaths = Resolve-Path -Path $expanded
|
||||
foreach ($resolved in $resolvedPaths) {
|
||||
$resolvedStr = $resolved.ProviderPath.ToString()
|
||||
if ([cultureinfo]::InvariantCulture.CompareInfo.IsPrefix($path, $resolvedStr, @("IgnoreCase"))) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
$exclusions = (Get-MpPreference).ExclusionPath
|
||||
|
||||
foreach ($path in $args) {
|
||||
if (-not (Test-Excluded $path $exclusions)) {
|
||||
$exclusions += $path
|
||||
Write-Host "added: $path"
|
||||
} else {
|
||||
Write-Host "skipped: $path"
|
||||
}
|
||||
}
|
||||
|
||||
Set-MpPreference -ExclusionPath $exclusions
|
||||
} catch {
|
||||
Write-Host $_.Exception.Message
|
||||
Write-Host $_.ScriptStackTrace
|
||||
exit 1
|
||||
}
|
||||
@@ -159,18 +159,24 @@ change.memory.apply=Save and Restart
|
||||
change.memory.exit=Save and Exit
|
||||
change.memory.low=The value should be greater than {0}
|
||||
|
||||
virus.scanning.warn.title=Windows Defender might impact performance
|
||||
virus.scanning.warn.message=Exclude IDE and project directories from antivirus scans:<br>{0}<br>Alternatively, add the IDE process as an exclusion.
|
||||
virus.scanning.fix.action=Exclude directories...
|
||||
virus.scanning.fix.explanation=<html>To improve performance, {0} can update your Windows Defender configuration to exclude your IDE and project directories from real-time scanning.<br>\
|
||||
This will require running a command with administrator privileges.<br>\
|
||||
Alternatively, you can configure Windows Defender manually, according to the <a href="{1}">instructions</a>.
|
||||
virus.scanning.fix.title=Configure Windows Defender
|
||||
virus.scanning.fix.automatically=Configure Automatically
|
||||
virus.scanning.fix.manually=Configure Manually
|
||||
virus.scanning.fix.success.notification=Windows Defender configuration updated
|
||||
virus.scanning.fix.failed=Failed to update Windows Defender configuration: {0}
|
||||
virus.scanning.dont.show.again=Don't show again
|
||||
notification.group.defender.config=Windows Defender configuration
|
||||
defender.config.prompt=<html>The IDE has detected Windows Defender with Real-Time Protection enabled. \
|
||||
It might severely degrade IDE performance. \
|
||||
It is recommended to add following paths to the Defender folder exclusion list:<br>{0}<br><br> \
|
||||
Choose "{1}" to run a script that excludes these paths (<b>note:</b> Windows will ask for administrative privileges). \
|
||||
Choose "{2}" to see Defender configuration instructions.</html>
|
||||
defender.config.prompt.no.script=<html>The IDE has detected Windows Defender with Real-Time Protection enabled. \
|
||||
It might severely degrade IDE performance. \
|
||||
It is recommended to add following paths to the Defender folder exclusion list:<br>{0}<br><br></html>
|
||||
defender.config.auto=Automatically
|
||||
defender.config.manual=Manually
|
||||
defender.config.instructions=See instructions
|
||||
defender.config.suppress1=Ignore for this project
|
||||
defender.config.suppress2=Never ask again
|
||||
defender.config.progress=Updating Windows Defender configuration
|
||||
defender.config.success=Project paths were successfully added to the Windows Defender exclusion list
|
||||
defender.config.failed=Windows Defender configuration script failed. Please look for "WindowsDefenderChecker" records in the log.
|
||||
defender.config.restore=OK. If you change your mind, please use "{0}" action.
|
||||
|
||||
label.issue.type=Issue Type:
|
||||
label.the.information.may.contain.sensitive.data=The information may contain sensitive data
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.diagnostic;
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
@@ -10,16 +9,10 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class ResetWindowsDefenderNotification extends AnAction {
|
||||
final class ResetWindowsDefenderNotification extends AnAction {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
PropertiesComponent.getInstance().setValue(WindowsDefenderChecker.IGNORE_VIRUS_CHECK, false);
|
||||
Project project = e.getProject();
|
||||
if (project != null) {
|
||||
PropertiesComponent.getInstance(project).setValue(WindowsDefenderChecker.IGNORE_VIRUS_CHECK, false);
|
||||
ApplicationManager.getApplication().executeOnPooledThread(
|
||||
() -> new WindowsDefenderCheckerActivity().runActivity(project));
|
||||
}
|
||||
public @NotNull ActionUpdateThread getActionUpdateThread() {
|
||||
return ActionUpdateThread.BGT;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -28,7 +21,14 @@ public class ResetWindowsDefenderNotification extends AnAction {
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ActionUpdateThread getActionUpdateThread() {
|
||||
return ActionUpdateThread.BGT;
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
WindowsDefenderChecker checker = WindowsDefenderChecker.getInstance();
|
||||
checker.ignoreStatusCheck(null, false);
|
||||
Project project = e.getProject();
|
||||
if (project != null) {
|
||||
checker.ignoreStatusCheck(project, false);
|
||||
ApplicationManager.getApplication().executeOnPooledThread(
|
||||
() -> new WindowsDefenderCheckerActivity().runActivity(project));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,339 +3,203 @@ package com.intellij.diagnostic;
|
||||
|
||||
import com.intellij.execution.ExecutionException;
|
||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
||||
import com.intellij.execution.configurations.PathEnvironmentVariableUtil;
|
||||
import com.intellij.execution.process.ProcessOutput;
|
||||
import com.intellij.execution.util.ExecUtil;
|
||||
import com.intellij.ide.util.PropertiesComponent;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.PathManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.NlsContexts;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.impl.local.NativeFileWatcherImpl;
|
||||
import com.intellij.util.Restarter;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.openapi.project.ProjectUtil;
|
||||
import com.intellij.openapi.util.NullableLazyValue;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.sun.jna.platform.win32.COM.WbemcliUtil;
|
||||
import com.sun.jna.platform.win32.Ole32;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.openapi.util.NullableLazyValue.volatileLazyNullable;
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
/**
|
||||
* Sources:
|
||||
* <a href="https://learn.microsoft.com/en-us/microsoft-365/security/defender-endpoint/configure-extension-file-exclusions-microsoft-defender-antivirus">Defender Settings</a>,
|
||||
* <a href="https://learn.microsoft.com/en-us/powershell/module/defender/">Defender PowerShell Module</a>.
|
||||
*/
|
||||
@SuppressWarnings("MethodMayBeStatic")
|
||||
public class WindowsDefenderChecker {
|
||||
private static final Logger LOG = Logger.getInstance(WindowsDefenderChecker.class);
|
||||
|
||||
private static final Pattern WINDOWS_ENV_VAR_PATTERN = Pattern.compile("%([^%]+?)%");
|
||||
private static final Pattern WINDOWS_DEFENDER_WILDCARD_PATTERN = Pattern.compile("[?*]");
|
||||
private static final int WMIC_COMMAND_TIMEOUT_MS = 10000;
|
||||
private static final int POWERSHELL_COMMAND_TIMEOUT_MS = 10000;
|
||||
private static final int MAX_POWERSHELL_STDERR_LENGTH = 500;
|
||||
static final String IGNORE_VIRUS_CHECK = "ignore.virus.scanning.warn.message";
|
||||
private static final String IGNORE_STATUS_CHECK = "ignore.virus.scanning.warn.message";
|
||||
private static final String HELPER_SCRIPT_NAME = "defender-exclusions.ps1";
|
||||
private static final String SIG_MARKER = "# SIG # Begin signature block";
|
||||
private static final int WMIC_COMMAND_TIMEOUT_MS = 10_000, POWERSHELL_COMMAND_TIMEOUT_MS = 30_000;
|
||||
private static final ExtensionPointName<Extension> EP_NAME = ExtensionPointName.create("com.intellij.defender.config");
|
||||
|
||||
public enum RealtimeScanningStatus {
|
||||
SCANNING_DISABLED,
|
||||
SCANNING_ENABLED,
|
||||
ERROR
|
||||
public interface Extension {
|
||||
@NotNull Collection<Path> getPaths(@NotNull Project project);
|
||||
}
|
||||
|
||||
public static WindowsDefenderChecker getInstance() {
|
||||
return ApplicationManager.getApplication().getService(WindowsDefenderChecker.class);
|
||||
}
|
||||
|
||||
public static class CheckResult {
|
||||
public final RealtimeScanningStatus status;
|
||||
|
||||
// Value in the map is true if the path is excluded, false otherwise
|
||||
public final Map<Path, Boolean> pathStatus;
|
||||
|
||||
public CheckResult(RealtimeScanningStatus status, Map<Path, Boolean> pathStatus) {
|
||||
this.status = status;
|
||||
this.pathStatus = pathStatus;
|
||||
private final NullableLazyValue<Path> myHelper = volatileLazyNullable(() -> {
|
||||
var candidate = PathManager.findBinFile(HELPER_SCRIPT_NAME);
|
||||
if (candidate != null) {
|
||||
try {
|
||||
if (Files.readString(candidate).contains(SIG_MARKER)) {
|
||||
return candidate;
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.warn(e);
|
||||
}
|
||||
}
|
||||
LOG.info("'" + HELPER_SCRIPT_NAME + (candidate == null ? "' is missing" : "' is unsigned"));
|
||||
return null;
|
||||
});
|
||||
|
||||
public boolean isStatusCheckIgnored(@NotNull Project project) {
|
||||
return PropertiesComponent.getInstance().isTrueValue(IGNORE_STATUS_CHECK) ||
|
||||
PropertiesComponent.getInstance(project).isTrueValue(IGNORE_STATUS_CHECK);
|
||||
}
|
||||
|
||||
public boolean isVirusCheckIgnored(Project project) {
|
||||
return PropertiesComponent.getInstance().isTrueValue(IGNORE_VIRUS_CHECK) ||
|
||||
PropertiesComponent.getInstance(project).isTrueValue(IGNORE_VIRUS_CHECK);
|
||||
}
|
||||
|
||||
public CheckResult checkWindowsDefender(@NotNull Project project) {
|
||||
final Boolean windowsDefenderActive = isWindowsDefenderActive();
|
||||
if (windowsDefenderActive == null || !windowsDefenderActive) {
|
||||
LOG.info("Windows Defender status: not used");
|
||||
return new CheckResult(RealtimeScanningStatus.SCANNING_DISABLED, Collections.emptyMap());
|
||||
}
|
||||
|
||||
RealtimeScanningStatus scanningStatus = getRealtimeScanningEnabled();
|
||||
if (scanningStatus == RealtimeScanningStatus.SCANNING_ENABLED) {
|
||||
Collection<String> excludedProcesses = getExcludedProcesses();
|
||||
List<Path> processesToCheck = getProcessesToCheck();
|
||||
if (excludedProcesses != null &&
|
||||
ContainerUtil.all(processesToCheck, exe -> excludedProcesses.contains(exe.getFileName().toString().toLowerCase(Locale.ENGLISH))) &&
|
||||
excludedProcesses.contains("java.exe")) {
|
||||
LOG.info("Windows Defender status: all relevant processes excluded from real-time scanning");
|
||||
return new CheckResult(RealtimeScanningStatus.SCANNING_DISABLED, Collections.emptyMap());
|
||||
}
|
||||
|
||||
List<Pattern> excludedPatterns = getExcludedPatterns();
|
||||
if (excludedPatterns != null) {
|
||||
Map<Path, Boolean> pathStatuses = checkPathsExcluded(getImportantPaths(project), excludedPatterns);
|
||||
boolean anyPathNotExcluded = !ContainerUtil.all(pathStatuses.values(), Boolean::booleanValue);
|
||||
if (anyPathNotExcluded) {
|
||||
LOG.info("Windows Defender status: some relevant paths not excluded from real-time scanning, notifying user");
|
||||
}
|
||||
else {
|
||||
LOG.info("Windows Defender status: all relevant paths excluded from real-time scanning");
|
||||
}
|
||||
return new CheckResult(scanningStatus, pathStatuses);
|
||||
}
|
||||
else {
|
||||
LOG.info("Windows Defender status: Failed to get excluded patterns");
|
||||
return new CheckResult(RealtimeScanningStatus.ERROR, Collections.emptyMap());
|
||||
}
|
||||
}
|
||||
if (scanningStatus == RealtimeScanningStatus.ERROR) {
|
||||
LOG.info("Windows Defender status: failed to detect");
|
||||
final void ignoreStatusCheck(@Nullable Project project, boolean ignore) {
|
||||
var component = project == null ? PropertiesComponent.getInstance() : PropertiesComponent.getInstance(project);
|
||||
if (ignore) {
|
||||
component.setValue(IGNORE_STATUS_CHECK, true);
|
||||
}
|
||||
else {
|
||||
LOG.info("Windows Defender status: real-time scanning disabled");
|
||||
component.unsetValue(IGNORE_STATUS_CHECK);
|
||||
}
|
||||
return new CheckResult(scanningStatus, Collections.emptyMap());
|
||||
}
|
||||
|
||||
protected @NotNull List<Path> getProcessesToCheck() {
|
||||
List<Path> result = new ArrayList<>();
|
||||
Path ideStarter = Restarter.getIdeStarter();
|
||||
if (ideStarter != null) {
|
||||
result.add(ideStarter);
|
||||
}
|
||||
Path fsNotifier = NativeFileWatcherImpl.getFSNotifierExecutable();
|
||||
if (fsNotifier != null) {
|
||||
result.add(fsNotifier);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Boolean isWindowsDefenderActive() {
|
||||
/**
|
||||
* {@link Boolean#TRUE} means Defender is present, active, and real-time protection check is enabled.
|
||||
* {@link Boolean#FALSE} means something from the above list is not true.
|
||||
* {@code null} means the IDE cannot detect the status.
|
||||
*/
|
||||
public @Nullable Boolean isRealTimeProtectionEnabled() {
|
||||
try {
|
||||
ProcessOutput output = ExecUtil.execAndGetOutput(new GeneralCommandLine(
|
||||
"wmic", "/Namespace:\\\\root\\SecurityCenter2", "Path", "AntivirusProduct", "Get", "displayName,productState"
|
||||
), WMIC_COMMAND_TIMEOUT_MS);
|
||||
if (output.getExitCode() == 0) {
|
||||
return parseWindowsDefenderProductState(output);
|
||||
var comInit = Ole32.INSTANCE.CoInitializeEx(null, Ole32.COINIT_APARTMENTTHREADED);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("CoInitializeEx: " + comInit);
|
||||
|
||||
var avQuery = new WbemcliUtil.WmiQuery<>("Root\\SecurityCenter2", "AntivirusProduct", AntivirusProduct.class);
|
||||
var avResult = avQuery.execute(WMIC_COMMAND_TIMEOUT_MS);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("results: " + avResult.getResultCount());
|
||||
for (var i = 0; i < avResult.getResultCount(); i++) {
|
||||
var name = avResult.getValue(AntivirusProduct.DisplayName, i);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("DisplayName[" + i + "]: " + name + " (" + name.getClass().getName() + ')');
|
||||
if (name instanceof String s && s.contains("Windows Defender")) {
|
||||
var state = avResult.getValue(AntivirusProduct.ProductState, i);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("ProductState: " + state + " (" + state.getClass().getName() + ')');
|
||||
var enabled = state instanceof Integer intState && (intState.intValue() & 0x1000) != 0;
|
||||
if (!enabled) return false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var statusQuery = new WbemcliUtil.WmiQuery<>("Root\\Microsoft\\Windows\\Defender", "MSFT_MpComputerStatus", MpComputerStatus.class);
|
||||
var statusResult = statusQuery.execute(WMIC_COMMAND_TIMEOUT_MS);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("results: " + statusResult.getResultCount());
|
||||
if (statusResult.getResultCount() != 1) return false;
|
||||
var rtProtection = statusResult.getValue(MpComputerStatus.RealTimeProtectionEnabled, 0);
|
||||
if (LOG.isDebugEnabled()) LOG.debug("RealTimeProtectionEnabled: " + rtProtection + " (" + rtProtection.getClass().getName() + ')');
|
||||
return Boolean.TRUE.equals(rtProtection);
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.warn("WMI Windows Defender check failed", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean canRunScript() {
|
||||
return myHelper.getValue() != null;
|
||||
}
|
||||
|
||||
private enum AntivirusProduct {DisplayName, ProductState}
|
||||
private enum MpComputerStatus {RealTimeProtectionEnabled}
|
||||
|
||||
final @NotNull List<Path> getImportantPaths(@NotNull Project project) {
|
||||
var paths = new TreeSet<Path>();
|
||||
|
||||
var projectDir = ProjectUtil.guessProjectDir(project);
|
||||
if (projectDir != null && projectDir.getFileSystem() instanceof LocalFileSystem) {
|
||||
paths.add(projectDir.toNioPath());
|
||||
}
|
||||
|
||||
paths.add(PathManager.getSystemDir());
|
||||
|
||||
EP_NAME.forEachExtensionSafe(ext -> {
|
||||
paths.addAll(ext.getPaths(project));
|
||||
});
|
||||
|
||||
return new ArrayList<>(paths);
|
||||
}
|
||||
|
||||
final boolean excludeProjectPaths(@NotNull List<Path> paths) {
|
||||
try {
|
||||
var script = requireNonNull(myHelper.getValue(), "missing/dysfunctional helper");
|
||||
|
||||
var psh = PathEnvironmentVariableUtil.findInPath("powershell.exe");
|
||||
if (psh == null) {
|
||||
LOG.info("no 'powershell.exe' on " + PathEnvironmentVariableUtil.getPathVariableValue());
|
||||
return false;
|
||||
}
|
||||
var sane = Stream.of("SystemRoot", "ProgramFiles").map(System::getenv).anyMatch(val -> val != null && psh.toPath().startsWith(val));
|
||||
if (!sane) {
|
||||
LOG.info("suspicious 'powershell.exe' location: " + psh);
|
||||
return false;
|
||||
}
|
||||
|
||||
var command = new GeneralCommandLine(psh.getPath(), "-NonInteractive", "-Command", "(Get-AuthenticodeSignature '" + script + "').Status");
|
||||
var output = run(command);
|
||||
if (output.getExitCode() != 0 || !"Valid".equals(output.getStdout().trim())) {
|
||||
LOG.info("validation failed:\n[" + output.getExitCode() + "] " + command + "\noutput: " + output.getStdout().trim());
|
||||
return false;
|
||||
}
|
||||
|
||||
command = ExecUtil.sudoCommand(
|
||||
new GeneralCommandLine(Stream.concat(
|
||||
Stream.of(psh.getPath(), "-ExecutionPolicy", "Bypass", "-NonInteractive", "-File", script.toString()),
|
||||
paths.stream().map(Path::toString)
|
||||
).toList()),
|
||||
"");
|
||||
output = run(command);
|
||||
if (output.getExitCode() != 0) {
|
||||
LOG.info("script failed:\n[" + output.getExitCode() + "] " + command + "\noutput: " + output.getStdout().trim());
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
LOG.warn("wmic Windows Defender check exited with status " + output.getExitCode() + ": " +
|
||||
StringUtil.first(output.getStderr(), MAX_POWERSHELL_STDERR_LENGTH, false));
|
||||
LOG.info("OK; script output:\n" + output.getStdout().trim());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (ExecutionException e) {
|
||||
LOG.warn("wmic Windows Defender check failed", e);
|
||||
catch (Exception e) {
|
||||
LOG.warn(e);
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Boolean parseWindowsDefenderProductState(ProcessOutput output) {
|
||||
final String[] lines = StringUtil.splitByLines(output.getStdout());
|
||||
for (String line : lines) {
|
||||
if (line.startsWith("Windows Defender")) {
|
||||
final String productStateString = StringUtil.substringAfterLast(line, " ");
|
||||
int productState;
|
||||
try {
|
||||
productState = Integer.parseInt(productStateString);
|
||||
return (productState & 0x1000) != 0;
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
LOG.info("Unexpected wmic output format: " + line);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
private static ProcessOutput run(GeneralCommandLine command) throws ExecutionException {
|
||||
return ExecUtil.execAndGetOutput(
|
||||
command.withRedirectErrorStream(true).withWorkDirectory(PathManager.getTempPath()),
|
||||
POWERSHELL_COMMAND_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
/** Runs a powershell command to list the paths that are excluded from realtime scanning by Windows Defender. These
|
||||
*
|
||||
* paths can contain environment variable references, as well as wildcards ('?', which matches a single character, and
|
||||
* '*', which matches any sequence of characters (but cannot match multiple nested directories; i.e., "foo\*\bar" would
|
||||
* match foo\baz\bar but not foo\baz\quux\bar)). The behavior of wildcards with respect to case-sensitivity is undocumented.
|
||||
* Returns a list of patterns, one for each exclusion path, that emulate how Windows Defender would interpret that path.
|
||||
*/
|
||||
private static @Nullable List<Pattern> getExcludedPatterns() {
|
||||
final Collection<String> paths = getWindowsDefenderProperty("ExclusionPath");
|
||||
if (paths == null) return null;
|
||||
if (paths.size() > 0) {
|
||||
String path = paths.iterator().next();
|
||||
if (path.length() > 0 && path.indexOf('\\') < 0) {
|
||||
// "N/A: Must be admin to view exclusions"
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return ContainerUtil.map(paths, path -> wildcardsToRegex(expandEnvVars(path)));
|
||||
}
|
||||
|
||||
private static @Nullable Collection<String> getExcludedProcesses() {
|
||||
final Collection<String> processes = getWindowsDefenderProperty("ExclusionProcess");
|
||||
if (processes == null) return null;
|
||||
return ContainerUtil.map(processes, process -> process.toLowerCase());
|
||||
}
|
||||
|
||||
/** Runs a powershell command to determine whether realtime scanning is enabled or not. */
|
||||
private static @NotNull RealtimeScanningStatus getRealtimeScanningEnabled() {
|
||||
final Collection<String> output = getWindowsDefenderProperty("DisableRealtimeMonitoring");
|
||||
if (output == null) return RealtimeScanningStatus.ERROR;
|
||||
if (output.size() > 0 && output.iterator().next().startsWith("False")) return RealtimeScanningStatus.SCANNING_ENABLED;
|
||||
return RealtimeScanningStatus.SCANNING_DISABLED;
|
||||
}
|
||||
|
||||
private static @Nullable Collection<String> getWindowsDefenderProperty(final String propertyName) {
|
||||
try {
|
||||
ProcessOutput output = ExecUtil.execAndGetOutput(new GeneralCommandLine(
|
||||
"powershell", "-inputformat", "none", "-outputformat", "text", "-NonInteractive", "-Command",
|
||||
"Get-MpPreference | select -ExpandProperty \"" + propertyName + "\""), POWERSHELL_COMMAND_TIMEOUT_MS);
|
||||
if (output.getExitCode() == 0) {
|
||||
return output.getStdoutLines();
|
||||
} else {
|
||||
LOG.warn("Windows Defender " + propertyName + " check exited with status " + output.getExitCode() + ": " +
|
||||
StringUtil.first(output.getStderr(), MAX_POWERSHELL_STDERR_LENGTH, false));
|
||||
}
|
||||
} catch (ExecutionException e) {
|
||||
LOG.warn("Windows Defender " + propertyName + " check failed", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Returns a list of paths that might impact build performance if Windows Defender were configured to scan them. */
|
||||
protected @NotNull List<Path> getImportantPaths(@NotNull Project project) {
|
||||
String homeDir = System.getProperty("user.home");
|
||||
String gradleUserHome = System.getenv("GRADLE_USER_HOME");
|
||||
String projectDir = project.getBasePath();
|
||||
|
||||
List<Path> paths = new ArrayList<>();
|
||||
if (projectDir != null) {
|
||||
paths.add(Paths.get(projectDir));
|
||||
}
|
||||
paths.add(Paths.get(PathManager.getSystemPath()));
|
||||
if (gradleUserHome != null) {
|
||||
paths.add(Paths.get(gradleUserHome));
|
||||
} else {
|
||||
paths.add(Paths.get(homeDir, ".gradle"));
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
|
||||
/** Expands references to environment variables (strings delimited by '%') in 'path' */
|
||||
private static @NotNull String expandEnvVars(@NotNull String path) {
|
||||
Matcher m = WINDOWS_ENV_VAR_PATTERN.matcher(path);
|
||||
StringBuilder result = new StringBuilder();
|
||||
while (m.find()) {
|
||||
String value = System.getenv(m.group(1));
|
||||
if (value != null) {
|
||||
m.appendReplacement(result, Matcher.quoteReplacement(value));
|
||||
}
|
||||
}
|
||||
m.appendTail(result);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Produces a {@link Pattern} that approximates how Windows Defender interprets the exclusion path {@code path}.
|
||||
* The path is split around wildcards; the non-wildcard portions are quoted, and regex equivalents of
|
||||
* the wildcards are inserted between them. See
|
||||
* https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-antivirus/configure-extension-file-exclusions-windows-defender-antivirus
|
||||
* for more details.
|
||||
*/
|
||||
private static @NotNull Pattern wildcardsToRegex(@NotNull String path) {
|
||||
Matcher m = WINDOWS_DEFENDER_WILDCARD_PATTERN.matcher(path);
|
||||
StringBuilder sb = new StringBuilder();
|
||||
int previousWildcardEnd = 0;
|
||||
while (m.find()) {
|
||||
sb.append(Pattern.quote(path.substring(previousWildcardEnd, m.start())));
|
||||
if (m.group().equals("?")) {
|
||||
sb.append("[^\\\\]");
|
||||
} else {
|
||||
sb.append("[^\\\\]*");
|
||||
}
|
||||
previousWildcardEnd = m.end();
|
||||
}
|
||||
sb.append(Pattern.quote(path.substring(previousWildcardEnd)));
|
||||
sb.append(".*"); // technically this should only be appended if the path refers to a directory, not a file. This is difficult to determine.
|
||||
return Pattern.compile(sb.toString(), Pattern.CASE_INSENSITIVE); // CASE_INSENSITIVE is overly permissive. Being precise with this is more work than it's worth.
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether each of the given paths in {@code paths} is matched by some pattern in {@code excludedPatterns},
|
||||
* returning a map of the results.
|
||||
*/
|
||||
private static @NotNull Map<Path, Boolean> checkPathsExcluded(@NotNull List<? extends Path> paths, @NotNull List<Pattern> excludedPatterns) {
|
||||
Map<Path, Boolean> result = new HashMap<>();
|
||||
for (Path path : paths) {
|
||||
if (!path.toFile().exists()) continue;
|
||||
|
||||
try {
|
||||
String canonical = path.toRealPath().toString();
|
||||
boolean found = false;
|
||||
for (Pattern pattern : excludedPatterns) {
|
||||
if (pattern.matcher(canonical).matches() || pattern.matcher(path.toString()).matches()) {
|
||||
found = true;
|
||||
result.put(path, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
result.put(path, false);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.warn("Windows Defender exclusion check couldn't get real path for " + path, e);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void configureActions(Project project, WindowsDefenderNotification notification) {
|
||||
notification.addAction(new WindowsDefenderFixAction(notification.getPaths()));
|
||||
|
||||
notification.addAction(new NotificationAction(DiagnosticBundle.message("virus.scanning.dont.show.again")) {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e, @NotNull Notification notification) {
|
||||
notification.expire();
|
||||
PropertiesComponent.getInstance().setValue(IGNORE_VIRUS_CHECK, "true");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public @NlsContexts.NotificationContent String getNotificationText(Set<? extends Path> nonExcludedPaths) {
|
||||
return DiagnosticBundle.message("virus.scanning.warn.message", StringUtil.join(nonExcludedPaths, "<br/>"));
|
||||
}
|
||||
|
||||
public String getConfigurationInstructionsUrl() {
|
||||
return "https://intellij-support.jetbrains.com/hc/en-us/articles/360006298560";
|
||||
}
|
||||
|
||||
public boolean runExcludePathsCommand(Project project, Collection<Path> paths) {
|
||||
try {
|
||||
final ProcessOutput output =
|
||||
ExecUtil.sudoAndGetOutput(new GeneralCommandLine("powershell", "-Command", "Add-MpPreference", "-ExclusionPath",
|
||||
StringUtil
|
||||
.join(paths, (path) -> StringUtil.wrapWithDoubleQuote(path.toString()), ",")),
|
||||
"");
|
||||
return output.getExitCode() == 0;
|
||||
}
|
||||
catch (IOException | ExecutionException e) {
|
||||
UIUtil.invokeLaterIfNeeded(() ->
|
||||
Messages.showErrorDialog(project, DiagnosticBundle.message("virus.scanning.fix.failed", e.getMessage()),
|
||||
DiagnosticBundle.message("virus.scanning.fix.title")));
|
||||
}
|
||||
return false;
|
||||
public @NotNull String getConfigurationInstructionsUrl() {
|
||||
return "https://intellij.com/antivirus-impact-on-build-speed";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,86 +1,93 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.diagnostic
|
||||
|
||||
import com.intellij.CommonBundle
|
||||
import com.intellij.ide.BrowserUtil
|
||||
import com.intellij.ide.IdeBundle
|
||||
import com.intellij.notification.*
|
||||
import com.intellij.notification.impl.NotificationFullContent
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.ide.actions.ShowLogAction
|
||||
import com.intellij.idea.ActionsBundle
|
||||
import com.intellij.notification.Notification
|
||||
import com.intellij.notification.NotificationAction.createSimple
|
||||
import com.intellij.notification.NotificationAction.createSimpleExpiring
|
||||
import com.intellij.notification.NotificationType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ApplicationNamesInfo
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.progress.runBackgroundableTask
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.startup.ProjectPostStartupActivity
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.launch
|
||||
import java.nio.file.Path
|
||||
|
||||
private val LOG = logger<WindowsDefenderCheckerActivity>()
|
||||
|
||||
internal class WindowsDefenderCheckerActivity : ProjectPostStartupActivity {
|
||||
// is called directly from `ResetWindowsDefenderNotification`
|
||||
override fun runActivity(project: Project) {
|
||||
@Suppress("DEPRECATION")
|
||||
project.coroutineScope.launch { execute(project) }
|
||||
}
|
||||
|
||||
override suspend fun execute(project: Project) {
|
||||
val app = ApplicationManager.getApplication()
|
||||
if (app.isUnitTestMode) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) return
|
||||
|
||||
val checker = WindowsDefenderChecker.getInstance()
|
||||
if (checker.isStatusCheckIgnored(project)) {
|
||||
LOG.info("status check is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
val windowsDefenderChecker = WindowsDefenderChecker.getInstance()
|
||||
if (windowsDefenderChecker.isVirusCheckIgnored(project)) return
|
||||
val protection = checker.isRealTimeProtectionEnabled
|
||||
if (protection != true) {
|
||||
LOG.info("real-time protection: ${protection}")
|
||||
return
|
||||
}
|
||||
|
||||
val checkResult = windowsDefenderChecker.checkWindowsDefender(project)
|
||||
if (checkResult.status == WindowsDefenderChecker.RealtimeScanningStatus.SCANNING_ENABLED &&
|
||||
checkResult.pathStatus.any { !it.value }) {
|
||||
val paths = checker.getImportantPaths(project)
|
||||
val pathList = paths.joinToString(separator = "<br> ", prefix = "<br> ") { it.toString() }
|
||||
val notification = if (checker.canRunScript()) {
|
||||
val auto = DiagnosticBundle.message("defender.config.auto")
|
||||
val manual = DiagnosticBundle.message("defender.config.manual")
|
||||
notification(DiagnosticBundle.message("defender.config.prompt", pathList, auto, manual), NotificationType.INFORMATION)
|
||||
.addAction(createSimpleExpiring(auto) { updateDefenderConfig(checker, project, paths) })
|
||||
.addAction(createSimple(manual) { BrowserUtil.browse(checker.configurationInstructionsUrl) })
|
||||
}
|
||||
else {
|
||||
notification(DiagnosticBundle.message("defender.config.prompt.no.script", pathList), NotificationType.INFORMATION)
|
||||
.addAction(createSimple(DiagnosticBundle.message("defender.config.instructions")) { BrowserUtil.browse(checker.configurationInstructionsUrl) })
|
||||
}
|
||||
notification
|
||||
.also {
|
||||
it.isImportant = true
|
||||
it.collapseDirection = Notification.CollapseActionsDirection.KEEP_LEFTMOST
|
||||
}
|
||||
.addAction(createSimpleExpiring(DiagnosticBundle.message("defender.config.suppress1")) { suppressCheck(checker, project) })
|
||||
.addAction(createSimpleExpiring(DiagnosticBundle.message("defender.config.suppress2")) { suppressCheck(checker, null) })
|
||||
.notify(project)
|
||||
}
|
||||
|
||||
val nonExcludedPaths = checkResult.pathStatus.filter { !it.value }.keys
|
||||
val notification = WindowsDefenderNotification(
|
||||
DiagnosticBundle.message("virus.scanning.warn.title"),
|
||||
windowsDefenderChecker.getNotificationText(nonExcludedPaths),
|
||||
nonExcludedPaths
|
||||
)
|
||||
notification.isImportant = true
|
||||
notification.collapseDirection = Notification.CollapseActionsDirection.KEEP_LEFTMOST
|
||||
windowsDefenderChecker.configureActions(project, notification)
|
||||
|
||||
withContext(Dispatchers.EDT) {
|
||||
notification.notify(project)
|
||||
private fun updateDefenderConfig(checker: WindowsDefenderChecker, project: Project, paths: List<Path>) {
|
||||
@Suppress("DialogTitleCapitalization")
|
||||
runBackgroundableTask(DiagnosticBundle.message("defender.config.progress"), project, false) {
|
||||
val success = checker.excludeProjectPaths(paths)
|
||||
if (success) {
|
||||
checker.ignoreStatusCheck(project, true)
|
||||
notification(DiagnosticBundle.message("defender.config.success"), NotificationType.INFORMATION)
|
||||
.notify(project)
|
||||
}
|
||||
else {
|
||||
notification(DiagnosticBundle.message("defender.config.failed"), NotificationType.WARNING)
|
||||
.addAction(ShowLogAction.notificationAction())
|
||||
.notify(project)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class WindowsDefenderNotification(@NlsContexts.NotificationTitle title: String, @NlsContexts.NotificationContent text: String, val paths: Collection<Path>) :
|
||||
Notification(NotificationGroup.createIdWithTitle("System Health", IdeBundle.message("notification.group.system.health")), title, text, NotificationType.WARNING), NotificationFullContent
|
||||
|
||||
internal class WindowsDefenderFixAction(val paths: Collection<Path>) : NotificationAction(DiagnosticBundle.message("virus.scanning.fix.action")) {
|
||||
override fun actionPerformed(e: AnActionEvent, notification: Notification) {
|
||||
val rc = Messages.showDialog(
|
||||
e.project,
|
||||
DiagnosticBundle.message("virus.scanning.fix.explanation", ApplicationNamesInfo.getInstance().fullProductName,
|
||||
WindowsDefenderChecker.getInstance().configurationInstructionsUrl),
|
||||
DiagnosticBundle.message("virus.scanning.fix.title"),
|
||||
arrayOf(
|
||||
DiagnosticBundle.message("virus.scanning.fix.automatically"),
|
||||
DiagnosticBundle.message("virus.scanning.fix.manually"),
|
||||
CommonBundle.getCancelButtonText()
|
||||
),
|
||||
0,
|
||||
null)
|
||||
when (rc) {
|
||||
0 -> {
|
||||
notification.expire()
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
if (WindowsDefenderChecker.getInstance().runExcludePathsCommand(e.project, paths)) {
|
||||
UIUtil.invokeLaterIfNeeded {
|
||||
Notifications.Bus.notifyAndHide(
|
||||
Notification(NotificationGroup.createIdWithTitle("System Health", IdeBundle.message("notification.group.system.health")),
|
||||
"", DiagnosticBundle.message("virus.scanning.fix.success.notification"), NotificationType.INFORMATION), e.project)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1 -> BrowserUtil.browse(WindowsDefenderChecker.getInstance().configurationInstructionsUrl)
|
||||
}
|
||||
private fun suppressCheck(checker: WindowsDefenderChecker, project: Project?) {
|
||||
checker.ignoreStatusCheck(project, true)
|
||||
val action = ActionsBundle.message("action.ResetWindowsDefenderNotification.text")
|
||||
notification(DiagnosticBundle.message("defender.config.restore", action), NotificationType.INFORMATION)
|
||||
.notify(project)
|
||||
}
|
||||
|
||||
private fun notification(@NlsContexts.NotificationContent content: String, type: NotificationType): Notification =
|
||||
Notification("WindowsDefender", DiagnosticBundle.message("notification.group.defender.config"), content, type)
|
||||
}
|
||||
|
||||
@@ -499,5 +499,7 @@
|
||||
<extensionPoint name="internal.ml.featureProvider" beanClass="com.intellij.lang.LanguageExtensionPoint" dynamic="true">
|
||||
<with attribute="implementationClass" implements="com.intellij.internal.ml.MLFeatureProvider"/>
|
||||
</extensionPoint>
|
||||
|
||||
<extensionPoint name="defender.config" interface="com.intellij.diagnostic.WindowsDefenderChecker$Extension" dynamic="true" />
|
||||
</extensionPoints>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -1316,6 +1316,7 @@
|
||||
<notificationGroup id="Test Results" displayType="TOOL_WINDOW" toolWindowId="Run" isLogByDefault="false" hideFromSettings="true"/>
|
||||
<notificationGroup id="feedback.form" displayType="BALLOON" bundle="messages.ApplicationBundle" key="feedback.form.notification.group"/>
|
||||
<notificationGroup id="PerformanceWatcher" displayType="STICKY_BALLOON" bundle="messages.DiagnosticBundle" key="notification.group.performance.watcher"/>
|
||||
<notificationGroup id="WindowsDefender" displayType="BALLOON" bundle="messages.DiagnosticBundle" key="notification.group.defender.config"/>
|
||||
|
||||
<defaultHighlightingSettingProvider implementation="com.intellij.codeInsight.actions.ReaderModeHighlightingSettingsProvider"/>
|
||||
<registryKey key="html.editor.timeout" defaultValue="15000" description="HTML editor content loading timeout, ms"/>
|
||||
@@ -1425,13 +1426,16 @@
|
||||
</extensions>
|
||||
|
||||
<applicationListeners>
|
||||
<listener class="com.intellij.ide.plugins.DynamicPluginsFrameStateListener" topic="com.intellij.openapi.application.ApplicationActivationListener"/>
|
||||
<listener class="com.intellij.openapi.updateSettings.impl.UpdateCheckerService$MyAppLifecycleListener" topic="com.intellij.ide.AppLifecycleListener"/>
|
||||
<listener class="com.intellij.openapi.updateSettings.impl.UpdateSettingsEntryPointActionProvider$LifecycleListener" topic="com.intellij.ide.AppLifecycleListener"/>
|
||||
<listener class="com.intellij.ui.mac.MergeAllWindowsAction$RecentProjectsFullScreenTabSupport" topic="com.intellij.ide.AppLifecycleListener"/>
|
||||
|
||||
<listener class="com.intellij.openapi.vcs.FileStatusFactoryImpl$PluginListener" topic="com.intellij.ide.plugins.DynamicPluginListener"/>
|
||||
|
||||
<listener class="com.intellij.ide.plugins.DynamicPluginsFrameStateListener"
|
||||
topic="com.intellij.openapi.application.ApplicationActivationListener"/>
|
||||
<listener class="com.intellij.openapi.updateSettings.impl.UpdateCheckerService$MyAppLifecycleListener"
|
||||
topic="com.intellij.ide.AppLifecycleListener"/>
|
||||
<listener class="com.intellij.openapi.updateSettings.impl.UpdateSettingsEntryPointActionProvider$LifecycleListener"
|
||||
topic="com.intellij.ide.AppLifecycleListener"/>
|
||||
<listener class="com.intellij.ui.mac.MergeAllWindowsAction$RecentProjectsFullScreenTabSupport"
|
||||
topic="com.intellij.ide.AppLifecycleListener"/>
|
||||
<listener class="com.intellij.openapi.vcs.FileStatusFactoryImpl$PluginListener"
|
||||
topic="com.intellij.ide.plugins.DynamicPluginListener"/>
|
||||
<listener class="com.intellij.notification.impl.widget.NotificationWidgetListener" activeInHeadlessMode="false" activeInTestMode="false"
|
||||
topic="com.intellij.ide.ui.UISettingsListener"/>
|
||||
<listener class="com.intellij.notification.impl.widget.NotificationWidgetListener" activeInHeadlessMode="false" activeInTestMode="false"
|
||||
@@ -1443,47 +1447,37 @@
|
||||
<listener class="com.intellij.openapi.fileTypes.StdFileTypes$StdFileTypesUpdater" activeInHeadlessMode="true" activeInTestMode="false"
|
||||
topic="com.intellij.openapi.fileTypes.FileTypeListener"/>
|
||||
<listener class="com.intellij.internal.statistic.collectors.fus.TypingEventsLogger$TypingEventsListener" activeInHeadlessMode="true"
|
||||
activeInTestMode="false"
|
||||
topic="com.intellij.openapi.actionSystem.ex.AnActionListener"/>
|
||||
topic="com.intellij.openapi.actionSystem.ex.AnActionListener" activeInTestMode="false"/>
|
||||
<listener class="com.intellij.internal.statistic.collectors.fus.TypingEventsLogger$TypingLatencyReporter"
|
||||
topic="com.intellij.openapi.fileEditor.FileEditorManagerListener"
|
||||
activeInTestMode="false"/>
|
||||
topic="com.intellij.openapi.fileEditor.FileEditorManagerListener" activeInTestMode="false"/>
|
||||
<listener class="com.intellij.featureStatistics.StatisticsStateCollectorsTrigger" activeInTestMode="false" activeInHeadlessMode="false"
|
||||
topic="com.intellij.ide.AppLifecycleListener"/>
|
||||
|
||||
|
||||
<listener class="com.intellij.ide.plugins.CreateAllServicesAndExtensionsActivity" topic="com.intellij.ide.AppLifecycleListener"
|
||||
activeInHeadlessMode="false" activeInTestMode="false"/>
|
||||
<listener class="com.intellij.ide.plugins.CreateAllServicesAndExtensionsActivity"
|
||||
topic="com.intellij.ide.AppLifecycleListener" activeInHeadlessMode="false" activeInTestMode="false"/>
|
||||
<listener class="com.intellij.ide.actionsOnSave.impl.ActionsOnSaveFileDocumentManagerListener"
|
||||
topic="com.intellij.openapi.fileEditor.FileDocumentManagerListener"/>
|
||||
<listener class="com.intellij.ide.actionsOnSave.impl.ActionsOnSaveFileDocumentManagerListener$CurrentActionListener"
|
||||
topic="com.intellij.openapi.actionSystem.ex.AnActionListener"/>
|
||||
<listener class="com.intellij.ide.impl.UntrustedProjectNotificationProvider$TrustedListener"
|
||||
topic="com.intellij.ide.impl.trustedProjects.TrustedProjectsListener"
|
||||
activeInHeadlessMode="false" activeInTestMode="false"/>
|
||||
topic="com.intellij.ide.impl.trustedProjects.TrustedProjectsListener" activeInHeadlessMode="false" activeInTestMode="false"/>
|
||||
<listener class="com.intellij.ide.impl.TrustStateListener$Bridge"
|
||||
topic="com.intellij.ide.impl.trustedProjects.TrustedProjectsListener"
|
||||
activeInHeadlessMode="false" activeInTestMode="false"/>
|
||||
|
||||
topic="com.intellij.ide.impl.trustedProjects.TrustedProjectsListener" activeInHeadlessMode="false" activeInTestMode="false"/>
|
||||
<listener class="com.intellij.openapi.util.registry.EarlyAccessRegistryManager$MyListener"
|
||||
topic="com.intellij.openapi.util.registry.RegistryValueListener"/>
|
||||
<listener class="com.intellij.ide.ui.experimental.toolbar.ExperimentalToolbarSettings$ToolbarRegistryListener"
|
||||
topic="com.intellij.openapi.util.registry.RegistryValueListener"/>
|
||||
<listener class="com.intellij.ui.ExperimentalUI$NewUiRegistryListener"
|
||||
topic="com.intellij.openapi.util.registry.RegistryValueListener"/>
|
||||
|
||||
<listener class="com.intellij.ide.FrameStateManagerAppListener" topic="com.intellij.openapi.application.ApplicationActivationListener"/>
|
||||
|
||||
<listener class="com.intellij.diagnostic.FusFreezeReporter" topic="com.intellij.diagnostic.IdePerformanceListener"/>
|
||||
|
||||
<listener class="com.intellij.ide.FrameStateManagerAppListener"
|
||||
topic="com.intellij.openapi.application.ApplicationActivationListener"/>
|
||||
<listener class="com.intellij.diagnostic.FusFreezeReporter"
|
||||
topic="com.intellij.diagnostic.IdePerformanceListener"/>
|
||||
<listener class="com.intellij.openapi.keymap.impl.KeymapFlagsStorageListener"
|
||||
topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
|
||||
|
||||
<listener class="com.intellij.ide.plugins.advertiser.OnDemandDependencyFeatureCollector"
|
||||
topic="com.intellij.ide.AppLifecycleListener"
|
||||
activeInHeadlessMode="false"
|
||||
activeInTestMode="false"/>
|
||||
topic="com.intellij.ide.AppLifecycleListener" activeInHeadlessMode="false" activeInTestMode="false"/>
|
||||
</applicationListeners>
|
||||
|
||||
<projectListeners>
|
||||
<listener class="com.intellij.notification.impl.widget.NotificationWidgetListener"
|
||||
activeInHeadlessMode="false"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.diagnostic;
|
||||
|
||||
import com.intellij.testFramework.fixtures.BareTestFixtureTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.intellij.openapi.util.io.IoTestUtil.assumeWindows;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
public class WindowsDefenderCheckerTest extends BareTestFixtureTestCase {
|
||||
@Test
|
||||
public void defenderStatusDetection() {
|
||||
assumeWindows();
|
||||
assertNotNull(WindowsDefenderChecker.getInstance().isRealTimeProtectionEnabled());
|
||||
}
|
||||
}
|
||||
@@ -75,6 +75,8 @@
|
||||
<moduleBuilder builderClass="org.jetbrains.plugins.gradle.service.project.wizard.InternalGradleModuleBuilder"/>
|
||||
<projectImportProvider implementation="org.jetbrains.plugins.gradle.service.project.wizard.JavaGradleProjectImportProvider"/>
|
||||
|
||||
<defender.config implementation="org.jetbrains.plugins.gradle.util.GradleWindowsDefenderCheckerExt"/>
|
||||
|
||||
<!--Gradle Test Runner-->
|
||||
<testActionProvider implementation="org.jetbrains.plugins.gradle.execution.test.runner.OpenGradleTestResultActionProvider"/>
|
||||
|
||||
@@ -113,7 +115,6 @@
|
||||
enabledByDefault="true" level="WARNING"
|
||||
implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleConfigurationAvoidanceInspection"/>
|
||||
|
||||
|
||||
<localInspection language="" groupPath="Gradle" shortName="DependencyNotationArgument"
|
||||
bundle="messages.GradleInspectionBundle"
|
||||
key="inspection.display.name.unrecognized.dependency.notation" groupKey="group.names.probable.bugs" groupBundle="messages.InspectionsBundle"
|
||||
@@ -137,9 +138,5 @@
|
||||
key="inspection.display.name.deprecated.configurations" groupKey="inspection.validity" groupBundle="messages.GradleInspectionBundle"
|
||||
enabledByDefault="true" level="WARNING"
|
||||
implementationClass="org.jetbrains.plugins.gradle.codeInspection.GradleDeprecatedConfigurationInspection"/>
|
||||
|
||||
|
||||
|
||||
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.plugins.gradle.util;
|
||||
|
||||
import com.intellij.diagnostic.WindowsDefenderChecker;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.plugins.gradle.settings.GradleSettings;
|
||||
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class GradleWindowsDefenderCheckerExt implements WindowsDefenderChecker.Extension {
|
||||
@Override
|
||||
public @NotNull Collection<Path> getPaths(@NotNull Project project) {
|
||||
if (!GradleSettings.getInstance(project).getLinkedProjectsSettings().isEmpty()) {
|
||||
var envVar = System.getenv("GRADLE_USER_HOME");
|
||||
var gradleDir = envVar != null ? Path.of(envVar) : Path.of(System.getProperty("user.home"), ".gradle");
|
||||
if (Files.isDirectory(gradleDir)) {
|
||||
return List.of(gradleDir);
|
||||
}
|
||||
}
|
||||
|
||||
return List.of();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user