SEBWIN-313: Started implementing application blacklist mechanism.
This commit is contained in:
parent
51570ecc91
commit
b72c37273e
23 changed files with 620 additions and 128 deletions
|
@ -8,9 +8,11 @@
|
|||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using SafeExamBrowser.Browser.Contracts;
|
||||
using SafeExamBrowser.Browser.Contracts.Events;
|
||||
using SafeExamBrowser.Client.Contracts;
|
||||
using SafeExamBrowser.Client.Operations.Events;
|
||||
using SafeExamBrowser.Communication.Contracts.Data;
|
||||
using SafeExamBrowser.Communication.Contracts.Events;
|
||||
using SafeExamBrowser.Communication.Contracts.Hosts;
|
||||
|
@ -343,7 +345,15 @@ namespace SafeExamBrowser.Client
|
|||
|
||||
private void Operations_ActionRequired(ActionRequiredEventArgs args)
|
||||
{
|
||||
// TODO
|
||||
switch (args)
|
||||
{
|
||||
case ApplicationTerminationEventArgs a:
|
||||
AskForAutomaticApplicationTermination(a);
|
||||
break;
|
||||
case ApplicationTerminationFailedEventArgs a:
|
||||
InformAboutFailedApplicationTermination(a);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Operations_ProgressChanged(ProgressChangedEventArgs args)
|
||||
|
@ -401,6 +411,27 @@ namespace SafeExamBrowser.Client
|
|||
terminationActivator.Resume();
|
||||
}
|
||||
|
||||
private void AskForAutomaticApplicationTermination(ApplicationTerminationEventArgs args)
|
||||
{
|
||||
var nl = Environment.NewLine;
|
||||
var applicationList = string.Join(Environment.NewLine, args.RunningApplications.Select(a => a.Name));
|
||||
var warning = text.Get(TextKey.MessageBox_ApplicationAutoTerminationDataLossWarning);
|
||||
var message = $"{text.Get(TextKey.MessageBox_ApplicationAutoTerminationQuestion)}{nl}{nl}{warning}{nl}{nl}{applicationList}";
|
||||
var title = text.Get(TextKey.MessageBox_ApplicationAutoTerminationQuestionTitle);
|
||||
var result = messageBox.Show(message, title, MessageBoxAction.YesNo, MessageBoxIcon.Question, parent: splashScreen);
|
||||
|
||||
args.TerminateProcesses = result == MessageBoxResult.Yes;
|
||||
}
|
||||
|
||||
private void InformAboutFailedApplicationTermination(ApplicationTerminationFailedEventArgs args)
|
||||
{
|
||||
var applicationList = string.Join(Environment.NewLine, args.Applications.Select(a => a.Name));
|
||||
var message = $"{text.Get(TextKey.MessageBox_ApplicationTerminationFailure)}{Environment.NewLine}{Environment.NewLine}{applicationList}";
|
||||
var title = text.Get(TextKey.MessageBox_ApplicationTerminationFailureTitle);
|
||||
|
||||
messageBox.Show(message, title, icon: MessageBoxIcon.Error, parent: splashScreen);
|
||||
}
|
||||
|
||||
private bool TryInitiateShutdown()
|
||||
{
|
||||
var hasQuitPassword = !String.IsNullOrEmpty(Settings.QuitPasswordHash);
|
||||
|
|
|
@ -29,7 +29,6 @@ using SafeExamBrowser.I18n.Contracts;
|
|||
using SafeExamBrowser.Logging;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Applications;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Monitoring.Display;
|
||||
using SafeExamBrowser.Monitoring.Keyboard;
|
||||
using SafeExamBrowser.Monitoring.Mouse;
|
||||
|
@ -63,7 +62,6 @@ namespace SafeExamBrowser.Client
|
|||
private UserInterfaceMode uiMode;
|
||||
|
||||
private IActionCenter actionCenter;
|
||||
private IApplicationMonitor applicationMonitor;
|
||||
private ILogger logger;
|
||||
private IMessageBox messageBox;
|
||||
private INativeMethods nativeMethods;
|
||||
|
@ -89,16 +87,16 @@ namespace SafeExamBrowser.Client
|
|||
InitializeText();
|
||||
|
||||
actionCenter = BuildActionCenter();
|
||||
applicationMonitor = new ApplicationMonitor(new ModuleLogger(logger, nameof(ApplicationMonitor)), nativeMethods);
|
||||
context = new ClientContext();
|
||||
messageBox = BuildMessageBox();
|
||||
uiFactory = BuildUserInterfaceFactory();
|
||||
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), new ModuleLogger(logger, nameof(RuntimeProxy)), Interlocutor.Client);
|
||||
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
|
||||
taskbar = BuildTaskbar();
|
||||
terminationActivator = new TerminationActivator(new ModuleLogger(logger, nameof(TerminationActivator)));
|
||||
terminationActivator = new TerminationActivator(ModuleLogger(nameof(TerminationActivator)));
|
||||
|
||||
var displayMonitor = new DisplayMonitor(new ModuleLogger(logger, nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
||||
var explorerShell = new ExplorerShell(new ModuleLogger(logger, nameof(ExplorerShell)), nativeMethods);
|
||||
var applicationMonitor = new ApplicationMonitor(ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory))));
|
||||
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
|
||||
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
|
||||
var hashAlgorithm = new HashAlgorithm();
|
||||
|
||||
var operations = new Queue<IOperation>();
|
||||
|
@ -195,7 +193,7 @@ namespace SafeExamBrowser.Client
|
|||
|
||||
private IOperation BuildBrowserOperation()
|
||||
{
|
||||
var moduleLogger = new ModuleLogger(logger, nameof(BrowserApplication));
|
||||
var moduleLogger = ModuleLogger(nameof(BrowserApplication));
|
||||
var browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, messageBox, moduleLogger, text, uiFactory);
|
||||
var browserInfo = new BrowserApplicationInfo();
|
||||
var operation = new BrowserOperation(actionCenter, context, logger, taskbar, uiFactory);
|
||||
|
@ -209,7 +207,7 @@ namespace SafeExamBrowser.Client
|
|||
{
|
||||
var processId = Process.GetCurrentProcess().Id;
|
||||
var factory = new HostObjectFactory();
|
||||
var clientHost = new ClientHost(context.AppConfig.ClientAddress, factory, new ModuleLogger(logger, nameof(ClientHost)), processId, FIVE_SECONDS);
|
||||
var clientHost = new ClientHost(context.AppConfig.ClientAddress, factory, ModuleLogger(nameof(ClientHost)), processId, FIVE_SECONDS);
|
||||
var operation = new CommunicationHostOperation(clientHost, logger);
|
||||
|
||||
context.ClientHost = clientHost;
|
||||
|
@ -220,7 +218,7 @@ namespace SafeExamBrowser.Client
|
|||
|
||||
private IOperation BuildKeyboardInterceptorOperation()
|
||||
{
|
||||
var keyboardInterceptor = new KeyboardInterceptor(new ModuleLogger(logger, nameof(KeyboardInterceptor)), nativeMethods, context.Settings.Keyboard);
|
||||
var keyboardInterceptor = new KeyboardInterceptor(ModuleLogger(nameof(KeyboardInterceptor)), nativeMethods, context.Settings.Keyboard);
|
||||
var operation = new KeyboardInterceptorOperation(context, keyboardInterceptor, logger);
|
||||
|
||||
return operation;
|
||||
|
@ -228,7 +226,7 @@ namespace SafeExamBrowser.Client
|
|||
|
||||
private IOperation BuildMouseInterceptorOperation()
|
||||
{
|
||||
var mouseInterceptor = new MouseInterceptor(new ModuleLogger(logger, nameof(MouseInterceptor)), nativeMethods, context.Settings.Mouse);
|
||||
var mouseInterceptor = new MouseInterceptor(ModuleLogger(nameof(MouseInterceptor)), nativeMethods, context.Settings.Mouse);
|
||||
var operation = new MouseInterceptorOperation(context, logger, mouseInterceptor);
|
||||
|
||||
return operation;
|
||||
|
@ -238,16 +236,16 @@ namespace SafeExamBrowser.Client
|
|||
{
|
||||
var aboutInfo = new AboutNotificationInfo(text);
|
||||
var aboutController = new AboutNotificationController(context.AppConfig, uiFactory);
|
||||
var audio = new Audio(context.Settings.Audio, new ModuleLogger(logger, nameof(Audio)));
|
||||
var keyboard = new Keyboard(new ModuleLogger(logger, nameof(Keyboard)));
|
||||
var audio = new Audio(context.Settings.Audio, ModuleLogger(nameof(Audio)));
|
||||
var keyboard = new Keyboard(ModuleLogger(nameof(Keyboard)));
|
||||
var logInfo = new LogNotificationInfo(text);
|
||||
var logController = new LogNotificationController(logger, uiFactory);
|
||||
var powerSupply = new PowerSupply(new ModuleLogger(logger, nameof(PowerSupply)));
|
||||
var wirelessAdapter = new WirelessAdapter(new ModuleLogger(logger, nameof(WirelessAdapter)));
|
||||
var powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply)));
|
||||
var wirelessAdapter = new WirelessAdapter(ModuleLogger(nameof(WirelessAdapter)));
|
||||
var activators = new IActionCenterActivator[]
|
||||
{
|
||||
new KeyboardActivator(new ModuleLogger(logger, nameof(KeyboardActivator))),
|
||||
new TouchActivator(new ModuleLogger(logger, nameof(TouchActivator)))
|
||||
new KeyboardActivator(ModuleLogger(nameof(KeyboardActivator))),
|
||||
new TouchActivator(ModuleLogger(nameof(TouchActivator)))
|
||||
};
|
||||
var operation = new ShellOperation(
|
||||
actionCenter,
|
||||
|
@ -298,9 +296,9 @@ namespace SafeExamBrowser.Client
|
|||
switch (uiMode)
|
||||
{
|
||||
case UserInterfaceMode.Mobile:
|
||||
return new Mobile.Taskbar(new ModuleLogger(logger, nameof(Mobile.Taskbar)));
|
||||
return new Mobile.Taskbar(ModuleLogger(nameof(Mobile.Taskbar)));
|
||||
default:
|
||||
return new Desktop.Taskbar(new ModuleLogger(logger, nameof(Desktop.Taskbar)));
|
||||
return new Desktop.Taskbar(ModuleLogger(nameof(Desktop.Taskbar)));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -319,5 +317,10 @@ namespace SafeExamBrowser.Client
|
|||
{
|
||||
ClientController.UpdateAppConfig();
|
||||
}
|
||||
|
||||
private IModuleLogger ModuleLogger(string moduleInfo)
|
||||
{
|
||||
return new ModuleLogger(logger, moduleInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace SafeExamBrowser.Client.Operations
|
|||
public override OperationResult Perform()
|
||||
{
|
||||
logger.Info("Initializing applications...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeProcessMonitoring);
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeApplications);
|
||||
|
||||
var result = InitializeApplications();
|
||||
|
||||
|
@ -51,9 +51,9 @@ namespace SafeExamBrowser.Client.Operations
|
|||
public override OperationResult Revert()
|
||||
{
|
||||
logger.Info("Finalizing applications...");
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_StopProcessMonitoring);
|
||||
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeApplications);
|
||||
|
||||
TerminateApplications();
|
||||
FinalizeApplications();
|
||||
StopMonitor();
|
||||
|
||||
return OperationResult.Success;
|
||||
|
@ -64,25 +64,48 @@ namespace SafeExamBrowser.Client.Operations
|
|||
var initialization = applicationMonitor.Initialize(Context.Settings.Applications);
|
||||
var result = OperationResult.Success;
|
||||
|
||||
if (initialization.RunningApplications.Any())
|
||||
if (initialization.FailedAutoTerminations.Any())
|
||||
{
|
||||
result = HandleAutoTerminationFailure(initialization.FailedAutoTerminations);
|
||||
}
|
||||
else if (initialization.RunningApplications.Any())
|
||||
{
|
||||
result = TryTerminate(initialization.RunningApplications);
|
||||
}
|
||||
|
||||
if (result == OperationResult.Success)
|
||||
{
|
||||
foreach (var application in Context.Settings.Applications.Whitelist)
|
||||
{
|
||||
Create(application);
|
||||
}
|
||||
CreateApplications();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void CreateApplications()
|
||||
{
|
||||
foreach (var application in Context.Settings.Applications.Whitelist)
|
||||
{
|
||||
Create(application);
|
||||
}
|
||||
}
|
||||
|
||||
private void Create(WhitelistApplication application)
|
||||
{
|
||||
// TODO: Use IApplicationFactory to create new application according to configuration, load into Context.Applications
|
||||
// StatusChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void FinalizeApplications()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private OperationResult HandleAutoTerminationFailure(IList<RunningApplication> applications)
|
||||
{
|
||||
logger.Error($"{applications.Count} application(s) could not be automatically terminated: {string.Join(", ", applications.Select(a => a.Name))}");
|
||||
ActionRequired?.Invoke(new ApplicationTerminationFailedEventArgs(applications));
|
||||
|
||||
return OperationResult.Failed;
|
||||
}
|
||||
|
||||
private void StartMonitor()
|
||||
|
@ -101,35 +124,42 @@ namespace SafeExamBrowser.Client.Operations
|
|||
}
|
||||
}
|
||||
|
||||
private void TerminateApplications()
|
||||
private OperationResult TryTerminate(IEnumerable<RunningApplication> runningApplications)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private OperationResult TryTerminate(IEnumerable<RunningApplicationInfo> runningApplications)
|
||||
{
|
||||
var args = new ProcessTerminationEventArgs();
|
||||
var args = new ApplicationTerminationEventArgs(runningApplications);
|
||||
var failed = new List<RunningApplication>();
|
||||
var result = OperationResult.Success;
|
||||
|
||||
ActionRequired?.Invoke(args);
|
||||
|
||||
if (args.TerminateProcesses)
|
||||
{
|
||||
// TODO: Terminate all processes of all running applications
|
||||
foreach (var application in runningApplications)
|
||||
{
|
||||
var success = applicationMonitor.TryTerminate(application);
|
||||
|
||||
//foreach (var application in runningApplications)
|
||||
//{
|
||||
// foreach (var process in application.Processes)
|
||||
// {
|
||||
// process.Kill();
|
||||
// }
|
||||
//}
|
||||
if (success)
|
||||
{
|
||||
logger.Info($"Successfully terminated application '{application.Name}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Failed;
|
||||
failed.Add(application);
|
||||
logger.Error($"Failed to automatically terminate application '{application.Name}'!");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result = OperationResult.Aborted;
|
||||
}
|
||||
|
||||
if (failed.Any())
|
||||
{
|
||||
ActionRequired?.Invoke(new ApplicationTerminationFailedEventArgs(failed));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
|
||||
namespace SafeExamBrowser.Client.Operations.Events
|
||||
{
|
||||
internal class ApplicationTerminationEventArgs : ActionRequiredEventArgs
|
||||
{
|
||||
internal IEnumerable<RunningApplication> RunningApplications { get; }
|
||||
internal bool TerminateProcesses { get; set; }
|
||||
|
||||
internal ApplicationTerminationEventArgs(IEnumerable<RunningApplication> runningApplications)
|
||||
{
|
||||
RunningApplications = runningApplications;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,12 +6,19 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
|
||||
namespace SafeExamBrowser.Client.Operations.Events
|
||||
{
|
||||
internal class ProcessTerminationEventArgs : ActionRequiredEventArgs
|
||||
internal class ApplicationTerminationFailedEventArgs : ActionRequiredEventArgs
|
||||
{
|
||||
public bool TerminateProcesses { get; set; }
|
||||
internal IEnumerable<RunningApplication> Applications { get; }
|
||||
|
||||
internal ApplicationTerminationFailedEventArgs(IEnumerable<RunningApplication> applications)
|
||||
{
|
||||
Applications = applications;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,7 +76,8 @@
|
|||
<Compile Include="Operations\ClientHostDisconnectionOperation.cs" />
|
||||
<Compile Include="Operations\ClientOperation.cs" />
|
||||
<Compile Include="Operations\ConfigurationOperation.cs" />
|
||||
<Compile Include="Operations\Events\ProcessTerminationEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\ApplicationTerminationEventArgs.cs" />
|
||||
<Compile Include="Operations\Events\ApplicationTerminationFailedEventArgs.cs" />
|
||||
<Compile Include="Operations\RuntimeConnectionOperation.cs" />
|
||||
<Compile Include="Communication\ClientHost.cs" />
|
||||
<Compile Include="CompositionRoot.cs" />
|
||||
|
|
|
@ -22,8 +22,13 @@ namespace SafeExamBrowser.I18n.Contracts
|
|||
BrowserWindow_ZoomMenuItem,
|
||||
Build,
|
||||
LogWindow_Title,
|
||||
MessageBox_ApplicationAutoTerminationDataLossWarning,
|
||||
MessageBox_ApplicationAutoTerminationQuestion,
|
||||
MessageBox_ApplicationAutoTerminationQuestionTitle,
|
||||
MessageBox_ApplicationError,
|
||||
MessageBox_ApplicationErrorTitle,
|
||||
MessageBox_ApplicationTerminationFailure,
|
||||
MessageBox_ApplicationTerminationFailureTitle,
|
||||
MessageBox_BrowserNavigationBlocked,
|
||||
MessageBox_BrowserNavigationBlockedTitle,
|
||||
MessageBox_CancelButton,
|
||||
|
@ -70,15 +75,15 @@ namespace SafeExamBrowser.I18n.Contracts
|
|||
Notification_LogTooltip,
|
||||
OperationStatus_CloseRuntimeConnection,
|
||||
OperationStatus_EmptyClipboard,
|
||||
OperationStatus_FinalizeApplications,
|
||||
OperationStatus_FinalizeServiceSession,
|
||||
OperationStatus_InitializeApplications,
|
||||
OperationStatus_InitializeBrowser,
|
||||
OperationStatus_InitializeConfiguration,
|
||||
OperationStatus_InitializeKioskMode,
|
||||
OperationStatus_InitializeProcessMonitoring,
|
||||
OperationStatus_InitializeRuntimeConnection,
|
||||
OperationStatus_InitializeServiceSession,
|
||||
OperationStatus_InitializeShell,
|
||||
OperationStatus_InitializeWindowMonitoring,
|
||||
OperationStatus_InitializeWorkingArea,
|
||||
OperationStatus_RestartCommunicationHost,
|
||||
OperationStatus_RestoreWorkingArea,
|
||||
|
@ -92,8 +97,6 @@ namespace SafeExamBrowser.I18n.Contracts
|
|||
OperationStatus_StopCommunicationHost,
|
||||
OperationStatus_StopKeyboardInterception,
|
||||
OperationStatus_StopMouseInterception,
|
||||
OperationStatus_StopProcessMonitoring,
|
||||
OperationStatus_StopWindowMonitoring,
|
||||
OperationStatus_TerminateBrowser,
|
||||
OperationStatus_TerminateShell,
|
||||
OperationStatus_WaitExplorerStartup,
|
||||
|
|
|
@ -24,12 +24,27 @@
|
|||
<Entry key="LogWindow_Title">
|
||||
Application Log
|
||||
</Entry>
|
||||
<Entry key="MessageBox_ApplicationAutoTerminationQuestion">
|
||||
The applications listed below need to be terminated before a new session can be started. Would you like to automatically terminate them now?
|
||||
</Entry>
|
||||
<Entry key="MessageBox_ApplicationAutoTerminationQuestionTitle">
|
||||
Running Applications Detected
|
||||
</Entry>
|
||||
<Entry key="MessageBox_ApplicationAutoTerminationDataLossWarning">
|
||||
IMPORTANT: Any unsaved application data might be lost!
|
||||
</Entry>
|
||||
<Entry key="MessageBox_ApplicationError">
|
||||
An unrecoverable error has occurred! Please consult the application log for more information. The application will now shut down...
|
||||
</Entry>
|
||||
<Entry key="MessageBox_ApplicationErrorTitle">
|
||||
Application Error
|
||||
</Entry>
|
||||
<Entry key="MessageBox_ApplicationTerminationFailure">
|
||||
The applications listed below could not be terminated! Please terminate them manually and try again...
|
||||
</Entry>
|
||||
<Entry key="MessageBox_ApplicationTerminationFailureTitle">
|
||||
Automatic Termination Failed
|
||||
</Entry>
|
||||
<Entry key="MessageBox_BrowserNavigationBlocked">
|
||||
Access to "%%URL%%" is not allowed according to the application configuration.
|
||||
</Entry>
|
||||
|
@ -168,9 +183,15 @@
|
|||
<Entry key="OperationStatus_EmptyClipboard">
|
||||
Emptying clipboard
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_FinalizeApplications">
|
||||
Finalizing applications
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_FinalizeServiceSession">
|
||||
Finalizing service session
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_InitializeApplications">
|
||||
Initializing applications
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_InitializeBrowser">
|
||||
Initializing browser
|
||||
</Entry>
|
||||
|
@ -180,9 +201,6 @@
|
|||
<Entry key="OperationStatus_InitializeKioskMode">
|
||||
Initializing kiosk mode
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_InitializeProcessMonitoring">
|
||||
Initializing process monitoring
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_InitializeRuntimeConnection">
|
||||
Initializing runtime connection
|
||||
</Entry>
|
||||
|
@ -195,9 +213,6 @@
|
|||
<Entry key="OperationStatus_InitializeShell">
|
||||
Initializing user interface
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_InitializeWindowMonitoring">
|
||||
Initializing window monitoring
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_InitializeWorkingArea">
|
||||
Initializing working area
|
||||
</Entry>
|
||||
|
@ -234,12 +249,6 @@
|
|||
<Entry key="OperationStatus_StopMouseInterception">
|
||||
Stopping mouse interception
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_StopProcessMonitoring">
|
||||
Stopping process monitoring
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_StopWindowMonitoring">
|
||||
Stopping window monitoring
|
||||
</Entry>
|
||||
<Entry key="OperationStatus_TerminateBrowser">
|
||||
Terminating browser
|
||||
</Entry>
|
||||
|
|
|
@ -35,5 +35,10 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
|
|||
/// Stops the application monitoring.
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to terminate all processes of the specified application. Returns <c>true</c> if successful, otherwise <c>false</c>.
|
||||
/// </summary>
|
||||
bool TryTerminate(RunningApplication application);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,14 +15,20 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
|
|||
/// </summary>
|
||||
public class InitializationResult
|
||||
{
|
||||
/// <summary>
|
||||
/// A list of currently running applications which could not be automatically terminated.
|
||||
/// </summary>
|
||||
public IList<RunningApplication> FailedAutoTerminations { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of currently running applications which need to be terminated.
|
||||
/// </summary>
|
||||
public IEnumerable<RunningApplicationInfo> RunningApplications { get; }
|
||||
public IList<RunningApplication> RunningApplications { get; }
|
||||
|
||||
public InitializationResult()
|
||||
{
|
||||
RunningApplications = new List<RunningApplicationInfo>();
|
||||
FailedAutoTerminations = new List<RunningApplication>();
|
||||
RunningApplications = new List<RunningApplication>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,16 +14,22 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
|
|||
/// <summary>
|
||||
/// Provides information about a running application.
|
||||
/// </summary>
|
||||
public class RunningApplicationInfo
|
||||
public class RunningApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the application.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A list of processes which belong to the application.
|
||||
/// </summary>
|
||||
public IEnumerable<IProcess> Processes { get; set; }
|
||||
public IList<IProcess> Processes { get; }
|
||||
|
||||
public RunningApplication(string name)
|
||||
{
|
||||
Name = name;
|
||||
Processes = new List<IProcess>();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -53,7 +53,7 @@
|
|||
<Reference Include="Microsoft.CSharp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Applications\RunningApplicationInfo.cs" />
|
||||
<Compile Include="Applications\RunningApplication.cs" />
|
||||
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" />
|
||||
<Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" />
|
||||
<Compile Include="Display\IDisplayMonitor.cs" />
|
||||
|
|
|
@ -7,8 +7,10 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Threading;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications;
|
||||
using SafeExamBrowser.Monitoring.Contracts.Applications.Events;
|
||||
|
@ -20,34 +22,73 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
public class ApplicationMonitor : IApplicationMonitor
|
||||
{
|
||||
private IntPtr activeWindow;
|
||||
private IList<BlacklistApplication> blacklist;
|
||||
private Guid? captureHookId;
|
||||
private ManagementEventWatcher explorerWatcher;
|
||||
private Guid? foregroundHookId;
|
||||
private ILogger logger;
|
||||
private INativeMethods nativeMethods;
|
||||
private ManagementEventWatcher explorerWatcher;
|
||||
private IProcessFactory processFactory;
|
||||
private IList<WhitelistApplication> whitelist;
|
||||
|
||||
public event ExplorerStartedEventHandler ExplorerStarted;
|
||||
|
||||
public ApplicationMonitor(ILogger logger, INativeMethods nativeMethods)
|
||||
public ApplicationMonitor(ILogger logger, INativeMethods nativeMethods, IProcessFactory processFactory)
|
||||
{
|
||||
this.blacklist = new List<BlacklistApplication>();
|
||||
this.logger = logger;
|
||||
this.nativeMethods = nativeMethods;
|
||||
this.processFactory = processFactory;
|
||||
this.whitelist = new List<WhitelistApplication>();
|
||||
}
|
||||
|
||||
public InitializationResult Initialize(ApplicationSettings settings)
|
||||
{
|
||||
// TODO
|
||||
// Initialize blacklist
|
||||
// Initialize whitelist
|
||||
// Check for running processes
|
||||
var result = new InitializationResult();
|
||||
|
||||
return new InitializationResult();
|
||||
foreach (var application in settings.Blacklist)
|
||||
{
|
||||
blacklist.Add(application);
|
||||
}
|
||||
|
||||
foreach (var application in settings.Whitelist)
|
||||
{
|
||||
whitelist.Add(application);
|
||||
}
|
||||
|
||||
logger.Debug($"Initialized blacklist with {blacklist.Count} applications: {string.Join(", ", blacklist.Select(a => a.ExecutableName))}");
|
||||
logger.Debug($"Initialized whitelist with {whitelist.Count} applications: {string.Join(", ", whitelist.Select(a => a.ExecutableName))}");
|
||||
|
||||
foreach (var process in processFactory.GetAllRunning())
|
||||
{
|
||||
foreach (var application in blacklist)
|
||||
{
|
||||
var isMatch = BelongsToApplication(process, application);
|
||||
|
||||
if (isMatch && !application.AutoTerminate)
|
||||
{
|
||||
AddForTermination(application.ExecutableName, process, result);
|
||||
}
|
||||
else if (isMatch && application.AutoTerminate && !TryTerminate(process))
|
||||
{
|
||||
AddFailed(application.ExecutableName, process, result);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var application in whitelist)
|
||||
{
|
||||
// TODO: Check if application is running, auto-terminate or add to result.
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
// TODO: Start monitoring blacklist...
|
||||
|
||||
// TODO: Remove WMI event and use timer mechanism!
|
||||
explorerWatcher = new ManagementEventWatcher(@"\\.\root\CIMV2", GetQueryFor("explorer.exe"));
|
||||
explorerWatcher.EventArrived += new EventArrivedEventHandler(ExplorerWatcher_EventArrived);
|
||||
explorerWatcher.Start();
|
||||
|
@ -78,9 +119,52 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
}
|
||||
}
|
||||
|
||||
public bool Terminate(int processId)
|
||||
public bool TryTerminate(RunningApplication application)
|
||||
{
|
||||
return false;
|
||||
var success = true;
|
||||
|
||||
foreach (var process in application.Processes)
|
||||
{
|
||||
success &= TryTerminate(process);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private void AddFailed(string name, IProcess process, InitializationResult result)
|
||||
{
|
||||
var application = result.FailedAutoTerminations.FirstOrDefault(a => a.Name == name);
|
||||
|
||||
if (application == default(RunningApplication))
|
||||
{
|
||||
application = new RunningApplication(name);
|
||||
result.FailedAutoTerminations.Add(application);
|
||||
}
|
||||
|
||||
application.Processes.Add(process);
|
||||
logger.Error($"Process '{process.Name}' belongs to application '{application.Name}' and could not be terminated automatically!");
|
||||
}
|
||||
|
||||
private void AddForTermination(string name, IProcess process, InitializationResult result)
|
||||
{
|
||||
var application = result.RunningApplications.FirstOrDefault(a => a.Name == name);
|
||||
|
||||
if (application == default(RunningApplication))
|
||||
{
|
||||
application = new RunningApplication(name);
|
||||
result.RunningApplications.Add(application);
|
||||
}
|
||||
|
||||
application.Processes.Add(process);
|
||||
logger.Debug($"Process '{process.Name}' belongs to application '{application.Name}' and needs to be terminated.");
|
||||
}
|
||||
|
||||
private bool BelongsToApplication(IProcess process, BlacklistApplication application)
|
||||
{
|
||||
var sameName = process.Name.Equals(application.ExecutableName, StringComparison.OrdinalIgnoreCase);
|
||||
var sameOriginalName = process.OriginalName?.Equals(application.ExecutableOriginalName, StringComparison.OrdinalIgnoreCase) == true;
|
||||
|
||||
return sameName || sameOriginalName;
|
||||
}
|
||||
|
||||
private void Check(IntPtr window)
|
||||
|
@ -109,19 +193,20 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
private bool IsAllowed(IntPtr window)
|
||||
{
|
||||
var processId = nativeMethods.GetProcessIdFor(window);
|
||||
var process = Process.GetProcessById(Convert.ToInt32(processId));
|
||||
// TODO: Allow only if in whitelist!
|
||||
//var process = processFactory.GetById(Convert.ToInt32(processId));
|
||||
|
||||
if (process != null)
|
||||
{
|
||||
var allowed = process.ProcessName == "SafeExamBrowser" || process.ProcessName == "SafeExamBrowser.Client";
|
||||
//if (process != null)
|
||||
//{
|
||||
// var allowed = process.Name == "SafeExamBrowser" || process.Name == "SafeExamBrowser.Client";
|
||||
|
||||
if (!allowed)
|
||||
{
|
||||
logger.Warn($"Window with handle = {window} belongs to not allowed process '{process.ProcessName}'!");
|
||||
}
|
||||
// if (!allowed)
|
||||
// {
|
||||
// logger.Warn($"Window with handle = {window} belongs to not allowed process '{process.Name}'!");
|
||||
// }
|
||||
|
||||
return allowed;
|
||||
}
|
||||
// return allowed;
|
||||
//}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -143,6 +228,57 @@ namespace SafeExamBrowser.Monitoring.Applications
|
|||
return success;
|
||||
}
|
||||
|
||||
private bool TryTerminate(IProcess process)
|
||||
{
|
||||
const int MAX_ATTEMPTS = 5;
|
||||
const int TIMEOUT = 100;
|
||||
|
||||
try
|
||||
{
|
||||
for (var attempt = 0; attempt < MAX_ATTEMPTS; attempt++)
|
||||
{
|
||||
if (process.TryClose())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(TIMEOUT);
|
||||
}
|
||||
}
|
||||
|
||||
if (!process.HasTerminated)
|
||||
{
|
||||
for (var attempt = 0; attempt < MAX_ATTEMPTS; attempt++)
|
||||
{
|
||||
if (process.TryKill())
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
Thread.Sleep(TIMEOUT);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (process.HasTerminated)
|
||||
{
|
||||
logger.Info($"Successfully terminated process '{process.Name}'.");
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn($"Failed to terminate process '{process.Name}'!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error($"An error occurred while attempting to terminate process '{process.Name}'!", e);
|
||||
}
|
||||
|
||||
return process.HasTerminated;
|
||||
}
|
||||
|
||||
private void ExplorerWatcher_EventArrived(object sender, EventArrivedEventArgs e)
|
||||
{
|
||||
var eventName = e.NewEvent.ClassPath.ClassName;
|
||||
|
|
|
@ -186,7 +186,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
proxy.Verify(p => p.InitiateShutdown(), Times.Once);
|
||||
proxy.Verify(p => p.Disconnect(), Times.Once);
|
||||
process.Verify(p => p.Kill(), Times.Never);
|
||||
process.Verify(p => p.TryKill(), Times.Never);
|
||||
|
||||
Assert.IsNull(sessionContext.ClientProcess);
|
||||
Assert.IsNull(sessionContext.ClientProxy);
|
||||
|
@ -195,12 +195,12 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
[TestMethod]
|
||||
public void Revert_MustKillClientIfStoppingFailed()
|
||||
{
|
||||
process.Setup(p => p.Kill()).Callback(() => process.SetupGet(p => p.HasTerminated).Returns(true));
|
||||
process.Setup(p => p.TryKill()).Callback(() => process.SetupGet(p => p.HasTerminated).Returns(true));
|
||||
|
||||
PerformNormally();
|
||||
sut.Revert();
|
||||
|
||||
process.Verify(p => p.Kill(), Times.AtLeastOnce);
|
||||
process.Verify(p => p.TryKill(), Times.AtLeastOnce);
|
||||
|
||||
Assert.IsNull(sessionContext.ClientProcess);
|
||||
Assert.IsNull(sessionContext.ClientProxy);
|
||||
|
@ -212,7 +212,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
PerformNormally();
|
||||
sut.Revert();
|
||||
|
||||
process.Verify(p => p.Kill(), Times.Exactly(5));
|
||||
process.Verify(p => p.TryKill(), Times.Exactly(5));
|
||||
|
||||
Assert.IsNotNull(sessionContext.ClientProcess);
|
||||
Assert.IsNotNull(sessionContext.ClientProxy);
|
||||
|
@ -227,7 +227,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
proxy.Verify(p => p.InitiateShutdown(), Times.Never);
|
||||
proxy.Verify(p => p.Disconnect(), Times.Never);
|
||||
process.Verify(p => p.Kill(), Times.Never);
|
||||
process.Verify(p => p.TryKill(), Times.Never);
|
||||
|
||||
Assert.IsNull(sessionContext.ClientProcess);
|
||||
Assert.IsNull(sessionContext.ClientProxy);
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
|
|||
|
||||
proxy.Verify(p => p.InitiateShutdown(), Times.Once);
|
||||
proxy.Verify(p => p.Disconnect(), Times.Once);
|
||||
process.Verify(p => p.Kill(), Times.Never);
|
||||
process.Verify(p => p.TryKill(), Times.Never);
|
||||
|
||||
Assert.IsNull(sessionContext.ClientProcess);
|
||||
Assert.IsNull(sessionContext.ClientProxy);
|
||||
|
|
|
@ -240,32 +240,30 @@ namespace SafeExamBrowser.Runtime.Operations
|
|||
return success;
|
||||
}
|
||||
|
||||
private bool TryKillClient(int attempt = 0)
|
||||
private bool TryKillClient()
|
||||
{
|
||||
const int MAX_ATTEMPTS = 5;
|
||||
|
||||
if (attempt == MAX_ATTEMPTS)
|
||||
for (var attempt = 1; attempt <= MAX_ATTEMPTS; attempt++)
|
||||
{
|
||||
logger.Error($"Failed to kill client process within {MAX_ATTEMPTS} attempts!");
|
||||
logger.Info($"Attempt {attempt}/{MAX_ATTEMPTS} to kill client process with ID = {ClientProcess.Id}.");
|
||||
|
||||
return false;
|
||||
if (ClientProcess.TryKill())
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
logger.Info($"Killing client process with ID = {ClientProcess.Id}.");
|
||||
ClientProcess.Kill();
|
||||
|
||||
if (ClientProcess.HasTerminated)
|
||||
{
|
||||
logger.Info("Client process has terminated.");
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Failed to kill client process. Trying again...");
|
||||
|
||||
return TryKillClient(++attempt);
|
||||
logger.Error($"Failed to kill client process within {MAX_ATTEMPTS} attempts!");
|
||||
}
|
||||
|
||||
return ClientProcess.HasTerminated;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,18 +12,18 @@ using System.Collections.Generic;
|
|||
namespace SafeExamBrowser.Settings.Applications
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// Defines all settings for third-party applications and application monitoring.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class ApplicationSettings
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// All applications which are not allowed to run during a session.
|
||||
/// </summary>
|
||||
public IList<BlacklistApplication> Blacklist { get; set; }
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// All applications which are allowed to run during a session.
|
||||
/// </summary>
|
||||
public IList<WhitelistApplication> Whitelist { get; set; }
|
||||
|
||||
|
|
|
@ -11,10 +11,14 @@ using System;
|
|||
namespace SafeExamBrowser.Settings.Applications
|
||||
{
|
||||
/// <summary>
|
||||
/// TODO
|
||||
/// Defines an application which is whitelisted, i.e. allowed to run during a session.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class WhitelistApplication
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the main executable of the application.
|
||||
/// </summary>
|
||||
public string ExecutableName { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,14 +25,29 @@ namespace SafeExamBrowser.WindowsApi.Contracts
|
|||
/// </summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The file name of the process executable.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The original file name of the process executable, if available.
|
||||
/// </summary>
|
||||
string OriginalName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Event fired when the process has terminated.
|
||||
/// </summary>
|
||||
event ProcessTerminatedEventHandler Terminated;
|
||||
|
||||
/// <summary>
|
||||
/// Immediately terminates the process.
|
||||
/// Attempts to gracefully terminate the process by closing its main window. This will only work for interactive processes which have a main window.
|
||||
/// </summary>
|
||||
void Kill();
|
||||
bool TryClose();
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to immediately kill the process.
|
||||
/// </summary>
|
||||
bool TryKill();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace SafeExamBrowser.WindowsApi.Contracts
|
||||
{
|
||||
/// <summary>
|
||||
|
@ -19,10 +21,20 @@ namespace SafeExamBrowser.WindowsApi.Contracts
|
|||
/// </summary>
|
||||
IDesktop StartupDesktop { set; }
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all currently running processes.
|
||||
/// </summary>
|
||||
IEnumerable<IProcess> GetAllRunning();
|
||||
|
||||
/// <summary>
|
||||
/// Starts a new process with the given command-line arguments.
|
||||
/// </summary>
|
||||
/// <exception cref="System.ComponentModel.Win32Exception">If the process could not be started.</exception>
|
||||
IProcess StartNew(string path, params string[] args);
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to retrieve a process by its identifier. Returns <c>true</c> if a process was found, otherwise <c>false</c>.
|
||||
/// </summary>
|
||||
bool TryGetById(int id, out IProcess process);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts.Events;
|
||||
|
||||
|
@ -13,9 +19,12 @@ namespace SafeExamBrowser.WindowsApi
|
|||
{
|
||||
internal class Process : IProcess
|
||||
{
|
||||
private bool eventInitialized, originalNameInitialized;
|
||||
private ILogger logger;
|
||||
private string originalName;
|
||||
private System.Diagnostics.Process process;
|
||||
|
||||
public event ProcessTerminatedEventHandler Terminated;
|
||||
private event ProcessTerminatedEventHandler TerminatedEvent;
|
||||
|
||||
public int Id
|
||||
{
|
||||
|
@ -24,29 +33,141 @@ namespace SafeExamBrowser.WindowsApi
|
|||
|
||||
public bool HasTerminated
|
||||
{
|
||||
get { process.Refresh(); return process.HasExited; }
|
||||
get { return IsTerminated(); }
|
||||
}
|
||||
|
||||
public Process(int id)
|
||||
public string Name { get; }
|
||||
|
||||
public string OriginalName
|
||||
{
|
||||
process = System.Diagnostics.Process.GetProcessById(id);
|
||||
process.Exited += Process_Exited;
|
||||
process.EnableRaisingEvents = true;
|
||||
get { return originalNameInitialized ? originalName : InitializeOriginalName(); }
|
||||
}
|
||||
|
||||
public void Kill()
|
||||
public event ProcessTerminatedEventHandler Terminated
|
||||
{
|
||||
process.Refresh();
|
||||
add { TerminatedEvent += value; InitializeEvent(); }
|
||||
remove { TerminatedEvent -= value; }
|
||||
}
|
||||
|
||||
if (!process.HasExited)
|
||||
internal Process(System.Diagnostics.Process process, ILogger logger)
|
||||
{
|
||||
this.Name = process.ProcessName;
|
||||
this.process = process;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
internal Process(System.Diagnostics.Process process, string originalName, ILogger logger) : this(process, logger)
|
||||
{
|
||||
this.originalName = originalName;
|
||||
this.originalNameInitialized = true;
|
||||
}
|
||||
|
||||
public bool TryClose()
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Kill();
|
||||
process.Refresh();
|
||||
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.CloseMainWindow();
|
||||
}
|
||||
|
||||
return process.HasExited;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to close main window!", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryKill()
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Refresh();
|
||||
|
||||
if (!process.HasExited)
|
||||
{
|
||||
process.Kill();
|
||||
}
|
||||
|
||||
return process.HasExited;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to kill process!", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IsTerminated()
|
||||
{
|
||||
try
|
||||
{
|
||||
process.Refresh();
|
||||
|
||||
return process.HasExited;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to check whether process is terminated!", e);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void InitializeEvent()
|
||||
{
|
||||
if (!eventInitialized)
|
||||
{
|
||||
eventInitialized = true;
|
||||
process.Exited += Process_Exited;
|
||||
process.EnableRaisingEvents = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void Process_Exited(object sender, System.EventArgs e)
|
||||
private string InitializeOriginalName()
|
||||
{
|
||||
Terminated?.Invoke(process.ExitCode);
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher($"SELECT ExecutablePath FROM Win32_Process WHERE ProcessId = {process.Id}"))
|
||||
using (var results = searcher.Get())
|
||||
using (var processData = results.Cast<ManagementObject>().First())
|
||||
{
|
||||
var executablePath = Convert.ToString(processData["ExecutablePath"]);
|
||||
|
||||
if (File.Exists(executablePath))
|
||||
{
|
||||
var executableInfo = FileVersionInfo.GetVersionInfo(executablePath);
|
||||
var originalName = Path.GetFileNameWithoutExtension(executableInfo.OriginalFilename);
|
||||
|
||||
this.originalName = originalName;
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.Warn("Could not find original name!");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to initialize original name!", e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
originalNameInitialized = true;
|
||||
}
|
||||
|
||||
return originalName;
|
||||
}
|
||||
|
||||
private void Process_Exited(object sender, EventArgs e)
|
||||
{
|
||||
TerminatedEvent?.Invoke(process.ExitCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,27 +7,44 @@
|
|||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Management;
|
||||
using System.Runtime.InteropServices;
|
||||
using SafeExamBrowser.Logging.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Constants;
|
||||
using SafeExamBrowser.WindowsApi.Contracts;
|
||||
using SafeExamBrowser.WindowsApi.Types;
|
||||
|
||||
namespace SafeExamBrowser.WindowsApi
|
||||
{
|
||||
public class ProcessFactory : IProcessFactory
|
||||
{
|
||||
private ILogger logger;
|
||||
private IModuleLogger logger;
|
||||
|
||||
public IDesktop StartupDesktop { private get; set; }
|
||||
|
||||
public ProcessFactory(ILogger logger)
|
||||
public ProcessFactory(IModuleLogger logger)
|
||||
{
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public IEnumerable<IProcess> GetAllRunning()
|
||||
{
|
||||
var processes = System.Diagnostics.Process.GetProcesses();
|
||||
var originalNames = LoadOriginalNames();
|
||||
|
||||
foreach (var process in processes)
|
||||
{
|
||||
var originalName = originalNames.FirstOrDefault(n => n.processId == process.Id).originalName;
|
||||
|
||||
yield return new Process(process, originalName, LoggerFor(process));
|
||||
}
|
||||
}
|
||||
|
||||
public IProcess StartNew(string path, params string[] args)
|
||||
{
|
||||
var commandLine = $"{'"' + path + '"'} {String.Join(" ", args)}";
|
||||
|
@ -48,11 +65,73 @@ namespace SafeExamBrowser.WindowsApi
|
|||
throw new Win32Exception(Marshal.GetLastWin32Error());
|
||||
}
|
||||
|
||||
var process = new Process(processInfo.dwProcessId);
|
||||
var raw = System.Diagnostics.Process.GetProcessById(processInfo.dwProcessId);
|
||||
var process = new Process(raw, LoggerFor(raw));
|
||||
|
||||
logger.Info($"Successfully started process '{Path.GetFileName(path)}' with ID = {process.Id}.");
|
||||
|
||||
return process;
|
||||
}
|
||||
|
||||
public bool TryGetById(int id, out IProcess process)
|
||||
{
|
||||
var raw = System.Diagnostics.Process.GetProcesses().FirstOrDefault(p => p.Id == id);
|
||||
|
||||
process = default(IProcess);
|
||||
|
||||
if (raw != default(System.Diagnostics.Process))
|
||||
{
|
||||
process = new Process(raw, LoggerFor(raw));
|
||||
}
|
||||
|
||||
return process != default(IProcess);
|
||||
}
|
||||
|
||||
private IEnumerable<(int processId, string originalName)> LoadOriginalNames()
|
||||
{
|
||||
var names = new List<(int, string)>();
|
||||
|
||||
try
|
||||
{
|
||||
using (var searcher = new ManagementObjectSearcher($"SELECT Name, ProcessId, ExecutablePath FROM Win32_Process"))
|
||||
using (var results = searcher.Get())
|
||||
{
|
||||
var processData = results.Cast<ManagementObject>().ToList();
|
||||
|
||||
foreach (var process in processData)
|
||||
{
|
||||
using (process)
|
||||
{
|
||||
var processId = Convert.ToInt32(process["ProcessId"]);
|
||||
var processName = Convert.ToString(process["Name"]);
|
||||
var executablePath = Convert.ToString(process["ExecutablePath"]);
|
||||
|
||||
if (File.Exists(executablePath))
|
||||
{
|
||||
var executableInfo = FileVersionInfo.GetVersionInfo(executablePath);
|
||||
var originalName = Path.GetFileNameWithoutExtension(executableInfo.OriginalFilename);
|
||||
|
||||
names.Add((processId, originalName));
|
||||
}
|
||||
else
|
||||
{
|
||||
names.Add((processId, default(string)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.Error("Failed to retrieve original names for processes!", e);
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
private ILogger LoggerFor(System.Diagnostics.Process process)
|
||||
{
|
||||
return logger.CloneFor($"{nameof(Process)} '{process.ProcessName}' ({process.Id})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Management" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Constants\Constant.cs" />
|
||||
|
|
Loading…
Reference in a new issue