SEBWIN-313: Started implementing application blacklist mechanism.

This commit is contained in:
dbuechel 2019-10-04 16:36:12 +02:00
parent 51570ecc91
commit b72c37273e
23 changed files with 620 additions and 128 deletions

View file

@ -8,9 +8,11 @@
using System; using System;
using System.IO; using System.IO;
using System.Linq;
using SafeExamBrowser.Browser.Contracts; using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Browser.Contracts.Events; using SafeExamBrowser.Browser.Contracts.Events;
using SafeExamBrowser.Client.Contracts; using SafeExamBrowser.Client.Contracts;
using SafeExamBrowser.Client.Operations.Events;
using SafeExamBrowser.Communication.Contracts.Data; using SafeExamBrowser.Communication.Contracts.Data;
using SafeExamBrowser.Communication.Contracts.Events; using SafeExamBrowser.Communication.Contracts.Events;
using SafeExamBrowser.Communication.Contracts.Hosts; using SafeExamBrowser.Communication.Contracts.Hosts;
@ -343,7 +345,15 @@ namespace SafeExamBrowser.Client
private void Operations_ActionRequired(ActionRequiredEventArgs args) 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) private void Operations_ProgressChanged(ProgressChangedEventArgs args)
@ -401,6 +411,27 @@ namespace SafeExamBrowser.Client
terminationActivator.Resume(); 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() private bool TryInitiateShutdown()
{ {
var hasQuitPassword = !String.IsNullOrEmpty(Settings.QuitPasswordHash); var hasQuitPassword = !String.IsNullOrEmpty(Settings.QuitPasswordHash);

View file

@ -29,7 +29,6 @@ using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging; using SafeExamBrowser.Logging;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Applications; using SafeExamBrowser.Monitoring.Applications;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Monitoring.Display; using SafeExamBrowser.Monitoring.Display;
using SafeExamBrowser.Monitoring.Keyboard; using SafeExamBrowser.Monitoring.Keyboard;
using SafeExamBrowser.Monitoring.Mouse; using SafeExamBrowser.Monitoring.Mouse;
@ -63,7 +62,6 @@ namespace SafeExamBrowser.Client
private UserInterfaceMode uiMode; private UserInterfaceMode uiMode;
private IActionCenter actionCenter; private IActionCenter actionCenter;
private IApplicationMonitor applicationMonitor;
private ILogger logger; private ILogger logger;
private IMessageBox messageBox; private IMessageBox messageBox;
private INativeMethods nativeMethods; private INativeMethods nativeMethods;
@ -89,16 +87,16 @@ namespace SafeExamBrowser.Client
InitializeText(); InitializeText();
actionCenter = BuildActionCenter(); actionCenter = BuildActionCenter();
applicationMonitor = new ApplicationMonitor(new ModuleLogger(logger, nameof(ApplicationMonitor)), nativeMethods);
context = new ClientContext(); context = new ClientContext();
messageBox = BuildMessageBox(); messageBox = BuildMessageBox();
uiFactory = BuildUserInterfaceFactory(); 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(); 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 applicationMonitor = new ApplicationMonitor(ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, new ProcessFactory(ModuleLogger(nameof(ProcessFactory))));
var explorerShell = new ExplorerShell(new ModuleLogger(logger, nameof(ExplorerShell)), nativeMethods); var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var hashAlgorithm = new HashAlgorithm(); var hashAlgorithm = new HashAlgorithm();
var operations = new Queue<IOperation>(); var operations = new Queue<IOperation>();
@ -195,7 +193,7 @@ namespace SafeExamBrowser.Client
private IOperation BuildBrowserOperation() 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 browser = new BrowserApplication(context.AppConfig, context.Settings.Browser, messageBox, moduleLogger, text, uiFactory);
var browserInfo = new BrowserApplicationInfo(); var browserInfo = new BrowserApplicationInfo();
var operation = new BrowserOperation(actionCenter, context, logger, taskbar, uiFactory); var operation = new BrowserOperation(actionCenter, context, logger, taskbar, uiFactory);
@ -209,7 +207,7 @@ namespace SafeExamBrowser.Client
{ {
var processId = Process.GetCurrentProcess().Id; var processId = Process.GetCurrentProcess().Id;
var factory = new HostObjectFactory(); 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); var operation = new CommunicationHostOperation(clientHost, logger);
context.ClientHost = clientHost; context.ClientHost = clientHost;
@ -220,7 +218,7 @@ namespace SafeExamBrowser.Client
private IOperation BuildKeyboardInterceptorOperation() 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); var operation = new KeyboardInterceptorOperation(context, keyboardInterceptor, logger);
return operation; return operation;
@ -228,7 +226,7 @@ namespace SafeExamBrowser.Client
private IOperation BuildMouseInterceptorOperation() 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); var operation = new MouseInterceptorOperation(context, logger, mouseInterceptor);
return operation; return operation;
@ -238,16 +236,16 @@ namespace SafeExamBrowser.Client
{ {
var aboutInfo = new AboutNotificationInfo(text); var aboutInfo = new AboutNotificationInfo(text);
var aboutController = new AboutNotificationController(context.AppConfig, uiFactory); var aboutController = new AboutNotificationController(context.AppConfig, uiFactory);
var audio = new Audio(context.Settings.Audio, new ModuleLogger(logger, nameof(Audio))); var audio = new Audio(context.Settings.Audio, ModuleLogger(nameof(Audio)));
var keyboard = new Keyboard(new ModuleLogger(logger, nameof(Keyboard))); var keyboard = new Keyboard(ModuleLogger(nameof(Keyboard)));
var logInfo = new LogNotificationInfo(text); var logInfo = new LogNotificationInfo(text);
var logController = new LogNotificationController(logger, uiFactory); var logController = new LogNotificationController(logger, uiFactory);
var powerSupply = new PowerSupply(new ModuleLogger(logger, nameof(PowerSupply))); var powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply)));
var wirelessAdapter = new WirelessAdapter(new ModuleLogger(logger, nameof(WirelessAdapter))); var wirelessAdapter = new WirelessAdapter(ModuleLogger(nameof(WirelessAdapter)));
var activators = new IActionCenterActivator[] var activators = new IActionCenterActivator[]
{ {
new KeyboardActivator(new ModuleLogger(logger, nameof(KeyboardActivator))), new KeyboardActivator(ModuleLogger(nameof(KeyboardActivator))),
new TouchActivator(new ModuleLogger(logger, nameof(TouchActivator))) new TouchActivator(ModuleLogger(nameof(TouchActivator)))
}; };
var operation = new ShellOperation( var operation = new ShellOperation(
actionCenter, actionCenter,
@ -298,9 +296,9 @@ namespace SafeExamBrowser.Client
switch (uiMode) switch (uiMode)
{ {
case UserInterfaceMode.Mobile: case UserInterfaceMode.Mobile:
return new Mobile.Taskbar(new ModuleLogger(logger, nameof(Mobile.Taskbar))); return new Mobile.Taskbar(ModuleLogger(nameof(Mobile.Taskbar)));
default: 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(); ClientController.UpdateAppConfig();
} }
private IModuleLogger ModuleLogger(string moduleInfo)
{
return new ModuleLogger(logger, moduleInfo);
}
} }
} }

View file

@ -36,7 +36,7 @@ namespace SafeExamBrowser.Client.Operations
public override OperationResult Perform() public override OperationResult Perform()
{ {
logger.Info("Initializing applications..."); logger.Info("Initializing applications...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeProcessMonitoring); StatusChanged?.Invoke(TextKey.OperationStatus_InitializeApplications);
var result = InitializeApplications(); var result = InitializeApplications();
@ -51,9 +51,9 @@ namespace SafeExamBrowser.Client.Operations
public override OperationResult Revert() public override OperationResult Revert()
{ {
logger.Info("Finalizing applications..."); logger.Info("Finalizing applications...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopProcessMonitoring); StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeApplications);
TerminateApplications(); FinalizeApplications();
StopMonitor(); StopMonitor();
return OperationResult.Success; return OperationResult.Success;
@ -64,25 +64,48 @@ namespace SafeExamBrowser.Client.Operations
var initialization = applicationMonitor.Initialize(Context.Settings.Applications); var initialization = applicationMonitor.Initialize(Context.Settings.Applications);
var result = OperationResult.Success; 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); result = TryTerminate(initialization.RunningApplications);
} }
if (result == OperationResult.Success) if (result == OperationResult.Success)
{ {
foreach (var application in Context.Settings.Applications.Whitelist) CreateApplications();
{
Create(application);
}
} }
return result; return result;
} }
private void CreateApplications()
{
foreach (var application in Context.Settings.Applications.Whitelist)
{
Create(application);
}
}
private void Create(WhitelistApplication application) private void Create(WhitelistApplication application)
{ {
// TODO: Use IApplicationFactory to create new application according to configuration, load into Context.Applications // 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() private void StartMonitor()
@ -101,35 +124,42 @@ namespace SafeExamBrowser.Client.Operations
} }
} }
private void TerminateApplications() private OperationResult TryTerminate(IEnumerable<RunningApplication> runningApplications)
{ {
var args = new ApplicationTerminationEventArgs(runningApplications);
} var failed = new List<RunningApplication>();
private OperationResult TryTerminate(IEnumerable<RunningApplicationInfo> runningApplications)
{
var args = new ProcessTerminationEventArgs();
var result = OperationResult.Success; var result = OperationResult.Success;
ActionRequired?.Invoke(args); ActionRequired?.Invoke(args);
if (args.TerminateProcesses) 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) if (success)
//{ {
// foreach (var process in application.Processes) logger.Info($"Successfully terminated application '{application.Name}'.");
// { }
// process.Kill(); else
// } {
//} result = OperationResult.Failed;
failed.Add(application);
logger.Error($"Failed to automatically terminate application '{application.Name}'!");
}
}
} }
else else
{ {
result = OperationResult.Aborted; result = OperationResult.Aborted;
} }
if (failed.Any())
{
ActionRequired?.Invoke(new ApplicationTerminationFailedEventArgs(failed));
}
return result; return result;
} }
} }

View file

@ -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;
}
}
}

View file

@ -6,12 +6,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System.Collections.Generic;
using SafeExamBrowser.Core.Contracts.OperationModel.Events; using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.Monitoring.Contracts.Applications;
namespace SafeExamBrowser.Client.Operations.Events 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;
}
} }
} }

View file

@ -76,7 +76,8 @@
<Compile Include="Operations\ClientHostDisconnectionOperation.cs" /> <Compile Include="Operations\ClientHostDisconnectionOperation.cs" />
<Compile Include="Operations\ClientOperation.cs" /> <Compile Include="Operations\ClientOperation.cs" />
<Compile Include="Operations\ConfigurationOperation.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="Operations\RuntimeConnectionOperation.cs" />
<Compile Include="Communication\ClientHost.cs" /> <Compile Include="Communication\ClientHost.cs" />
<Compile Include="CompositionRoot.cs" /> <Compile Include="CompositionRoot.cs" />

View file

@ -22,8 +22,13 @@ namespace SafeExamBrowser.I18n.Contracts
BrowserWindow_ZoomMenuItem, BrowserWindow_ZoomMenuItem,
Build, Build,
LogWindow_Title, LogWindow_Title,
MessageBox_ApplicationAutoTerminationDataLossWarning,
MessageBox_ApplicationAutoTerminationQuestion,
MessageBox_ApplicationAutoTerminationQuestionTitle,
MessageBox_ApplicationError, MessageBox_ApplicationError,
MessageBox_ApplicationErrorTitle, MessageBox_ApplicationErrorTitle,
MessageBox_ApplicationTerminationFailure,
MessageBox_ApplicationTerminationFailureTitle,
MessageBox_BrowserNavigationBlocked, MessageBox_BrowserNavigationBlocked,
MessageBox_BrowserNavigationBlockedTitle, MessageBox_BrowserNavigationBlockedTitle,
MessageBox_CancelButton, MessageBox_CancelButton,
@ -70,15 +75,15 @@ namespace SafeExamBrowser.I18n.Contracts
Notification_LogTooltip, Notification_LogTooltip,
OperationStatus_CloseRuntimeConnection, OperationStatus_CloseRuntimeConnection,
OperationStatus_EmptyClipboard, OperationStatus_EmptyClipboard,
OperationStatus_FinalizeApplications,
OperationStatus_FinalizeServiceSession, OperationStatus_FinalizeServiceSession,
OperationStatus_InitializeApplications,
OperationStatus_InitializeBrowser, OperationStatus_InitializeBrowser,
OperationStatus_InitializeConfiguration, OperationStatus_InitializeConfiguration,
OperationStatus_InitializeKioskMode, OperationStatus_InitializeKioskMode,
OperationStatus_InitializeProcessMonitoring,
OperationStatus_InitializeRuntimeConnection, OperationStatus_InitializeRuntimeConnection,
OperationStatus_InitializeServiceSession, OperationStatus_InitializeServiceSession,
OperationStatus_InitializeShell, OperationStatus_InitializeShell,
OperationStatus_InitializeWindowMonitoring,
OperationStatus_InitializeWorkingArea, OperationStatus_InitializeWorkingArea,
OperationStatus_RestartCommunicationHost, OperationStatus_RestartCommunicationHost,
OperationStatus_RestoreWorkingArea, OperationStatus_RestoreWorkingArea,
@ -92,8 +97,6 @@ namespace SafeExamBrowser.I18n.Contracts
OperationStatus_StopCommunicationHost, OperationStatus_StopCommunicationHost,
OperationStatus_StopKeyboardInterception, OperationStatus_StopKeyboardInterception,
OperationStatus_StopMouseInterception, OperationStatus_StopMouseInterception,
OperationStatus_StopProcessMonitoring,
OperationStatus_StopWindowMonitoring,
OperationStatus_TerminateBrowser, OperationStatus_TerminateBrowser,
OperationStatus_TerminateShell, OperationStatus_TerminateShell,
OperationStatus_WaitExplorerStartup, OperationStatus_WaitExplorerStartup,

View file

@ -24,12 +24,27 @@
<Entry key="LogWindow_Title"> <Entry key="LogWindow_Title">
Application Log Application Log
</Entry> </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"> <Entry key="MessageBox_ApplicationError">
An unrecoverable error has occurred! Please consult the application log for more information. The application will now shut down... An unrecoverable error has occurred! Please consult the application log for more information. The application will now shut down...
</Entry> </Entry>
<Entry key="MessageBox_ApplicationErrorTitle"> <Entry key="MessageBox_ApplicationErrorTitle">
Application Error Application Error
</Entry> </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"> <Entry key="MessageBox_BrowserNavigationBlocked">
Access to "%%URL%%" is not allowed according to the application configuration. Access to "%%URL%%" is not allowed according to the application configuration.
</Entry> </Entry>
@ -168,9 +183,15 @@
<Entry key="OperationStatus_EmptyClipboard"> <Entry key="OperationStatus_EmptyClipboard">
Emptying clipboard Emptying clipboard
</Entry> </Entry>
<Entry key="OperationStatus_FinalizeApplications">
Finalizing applications
</Entry>
<Entry key="OperationStatus_FinalizeServiceSession"> <Entry key="OperationStatus_FinalizeServiceSession">
Finalizing service session Finalizing service session
</Entry> </Entry>
<Entry key="OperationStatus_InitializeApplications">
Initializing applications
</Entry>
<Entry key="OperationStatus_InitializeBrowser"> <Entry key="OperationStatus_InitializeBrowser">
Initializing browser Initializing browser
</Entry> </Entry>
@ -180,9 +201,6 @@
<Entry key="OperationStatus_InitializeKioskMode"> <Entry key="OperationStatus_InitializeKioskMode">
Initializing kiosk mode Initializing kiosk mode
</Entry> </Entry>
<Entry key="OperationStatus_InitializeProcessMonitoring">
Initializing process monitoring
</Entry>
<Entry key="OperationStatus_InitializeRuntimeConnection"> <Entry key="OperationStatus_InitializeRuntimeConnection">
Initializing runtime connection Initializing runtime connection
</Entry> </Entry>
@ -195,9 +213,6 @@
<Entry key="OperationStatus_InitializeShell"> <Entry key="OperationStatus_InitializeShell">
Initializing user interface Initializing user interface
</Entry> </Entry>
<Entry key="OperationStatus_InitializeWindowMonitoring">
Initializing window monitoring
</Entry>
<Entry key="OperationStatus_InitializeWorkingArea"> <Entry key="OperationStatus_InitializeWorkingArea">
Initializing working area Initializing working area
</Entry> </Entry>
@ -234,12 +249,6 @@
<Entry key="OperationStatus_StopMouseInterception"> <Entry key="OperationStatus_StopMouseInterception">
Stopping mouse interception Stopping mouse interception
</Entry> </Entry>
<Entry key="OperationStatus_StopProcessMonitoring">
Stopping process monitoring
</Entry>
<Entry key="OperationStatus_StopWindowMonitoring">
Stopping window monitoring
</Entry>
<Entry key="OperationStatus_TerminateBrowser"> <Entry key="OperationStatus_TerminateBrowser">
Terminating browser Terminating browser
</Entry> </Entry>

View file

@ -35,5 +35,10 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
/// Stops the application monitoring. /// Stops the application monitoring.
/// </summary> /// </summary>
void Stop(); 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);
} }
} }

View file

@ -15,14 +15,20 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
/// </summary> /// </summary>
public class InitializationResult public class InitializationResult
{ {
/// <summary>
/// A list of currently running applications which could not be automatically terminated.
/// </summary>
public IList<RunningApplication> FailedAutoTerminations { get; }
/// <summary> /// <summary>
/// A list of currently running applications which need to be terminated. /// A list of currently running applications which need to be terminated.
/// </summary> /// </summary>
public IEnumerable<RunningApplicationInfo> RunningApplications { get; } public IList<RunningApplication> RunningApplications { get; }
public InitializationResult() public InitializationResult()
{ {
RunningApplications = new List<RunningApplicationInfo>(); FailedAutoTerminations = new List<RunningApplication>();
RunningApplications = new List<RunningApplication>();
} }
} }
} }

View file

@ -14,16 +14,22 @@ namespace SafeExamBrowser.Monitoring.Contracts.Applications
/// <summary> /// <summary>
/// Provides information about a running application. /// Provides information about a running application.
/// </summary> /// </summary>
public class RunningApplicationInfo public class RunningApplication
{ {
/// <summary> /// <summary>
/// The name of the application. /// The name of the application.
/// </summary> /// </summary>
public string Name { get; set; } public string Name { get; }
/// <summary> /// <summary>
/// A list of processes which belong to the application. /// A list of processes which belong to the application.
/// </summary> /// </summary>
public IEnumerable<IProcess> Processes { get; set; } public IList<IProcess> Processes { get; }
public RunningApplication(string name)
{
Name = name;
Processes = new List<IProcess>();
}
} }
} }

View file

@ -53,7 +53,7 @@
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Applications\RunningApplicationInfo.cs" /> <Compile Include="Applications\RunningApplication.cs" />
<Compile Include="Display\Events\DisplayChangedEventHandler.cs" /> <Compile Include="Display\Events\DisplayChangedEventHandler.cs" />
<Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" /> <Compile Include="Applications\Events\ExplorerStartedEventHandler.cs" />
<Compile Include="Display\IDisplayMonitor.cs" /> <Compile Include="Display\IDisplayMonitor.cs" />

View file

@ -7,8 +7,10 @@
*/ */
using System; using System;
using System.Diagnostics; using System.Collections.Generic;
using System.Linq;
using System.Management; using System.Management;
using System.Threading;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications; using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Monitoring.Contracts.Applications.Events; using SafeExamBrowser.Monitoring.Contracts.Applications.Events;
@ -20,34 +22,73 @@ namespace SafeExamBrowser.Monitoring.Applications
public class ApplicationMonitor : IApplicationMonitor public class ApplicationMonitor : IApplicationMonitor
{ {
private IntPtr activeWindow; private IntPtr activeWindow;
private IList<BlacklistApplication> blacklist;
private Guid? captureHookId; private Guid? captureHookId;
private ManagementEventWatcher explorerWatcher;
private Guid? foregroundHookId; private Guid? foregroundHookId;
private ILogger logger; private ILogger logger;
private INativeMethods nativeMethods; private INativeMethods nativeMethods;
private ManagementEventWatcher explorerWatcher; private IProcessFactory processFactory;
private IList<WhitelistApplication> whitelist;
public event ExplorerStartedEventHandler ExplorerStarted; 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.logger = logger;
this.nativeMethods = nativeMethods; this.nativeMethods = nativeMethods;
this.processFactory = processFactory;
this.whitelist = new List<WhitelistApplication>();
} }
public InitializationResult Initialize(ApplicationSettings settings) public InitializationResult Initialize(ApplicationSettings settings)
{ {
// TODO var result = new InitializationResult();
// Initialize blacklist
// Initialize whitelist
// Check for running processes
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() public void Start()
{ {
// TODO: Start monitoring blacklist... // TODO: Start monitoring blacklist...
// TODO: Remove WMI event and use timer mechanism!
explorerWatcher = new ManagementEventWatcher(@"\\.\root\CIMV2", GetQueryFor("explorer.exe")); explorerWatcher = new ManagementEventWatcher(@"\\.\root\CIMV2", GetQueryFor("explorer.exe"));
explorerWatcher.EventArrived += new EventArrivedEventHandler(ExplorerWatcher_EventArrived); explorerWatcher.EventArrived += new EventArrivedEventHandler(ExplorerWatcher_EventArrived);
explorerWatcher.Start(); 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) private void Check(IntPtr window)
@ -109,19 +193,20 @@ namespace SafeExamBrowser.Monitoring.Applications
private bool IsAllowed(IntPtr window) private bool IsAllowed(IntPtr window)
{ {
var processId = nativeMethods.GetProcessIdFor(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) //if (process != null)
{ //{
var allowed = process.ProcessName == "SafeExamBrowser" || process.ProcessName == "SafeExamBrowser.Client"; // var allowed = process.Name == "SafeExamBrowser" || process.Name == "SafeExamBrowser.Client";
if (!allowed) // if (!allowed)
{ // {
logger.Warn($"Window with handle = {window} belongs to not allowed process '{process.ProcessName}'!"); // logger.Warn($"Window with handle = {window} belongs to not allowed process '{process.Name}'!");
} // }
return allowed; // return allowed;
} //}
return true; return true;
} }
@ -143,6 +228,57 @@ namespace SafeExamBrowser.Monitoring.Applications
return success; 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) private void ExplorerWatcher_EventArrived(object sender, EventArrivedEventArgs e)
{ {
var eventName = e.NewEvent.ClassPath.ClassName; var eventName = e.NewEvent.ClassPath.ClassName;

View file

@ -186,7 +186,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
proxy.Verify(p => p.InitiateShutdown(), Times.Once); proxy.Verify(p => p.InitiateShutdown(), Times.Once);
proxy.Verify(p => p.Disconnect(), 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.ClientProcess);
Assert.IsNull(sessionContext.ClientProxy); Assert.IsNull(sessionContext.ClientProxy);
@ -195,12 +195,12 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
[TestMethod] [TestMethod]
public void Revert_MustKillClientIfStoppingFailed() 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(); PerformNormally();
sut.Revert(); sut.Revert();
process.Verify(p => p.Kill(), Times.AtLeastOnce); process.Verify(p => p.TryKill(), Times.AtLeastOnce);
Assert.IsNull(sessionContext.ClientProcess); Assert.IsNull(sessionContext.ClientProcess);
Assert.IsNull(sessionContext.ClientProxy); Assert.IsNull(sessionContext.ClientProxy);
@ -212,7 +212,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
PerformNormally(); PerformNormally();
sut.Revert(); 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.ClientProcess);
Assert.IsNotNull(sessionContext.ClientProxy); Assert.IsNotNull(sessionContext.ClientProxy);
@ -227,7 +227,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
proxy.Verify(p => p.InitiateShutdown(), Times.Never); proxy.Verify(p => p.InitiateShutdown(), Times.Never);
proxy.Verify(p => p.Disconnect(), 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.ClientProcess);
Assert.IsNull(sessionContext.ClientProxy); Assert.IsNull(sessionContext.ClientProxy);

View file

@ -81,7 +81,7 @@ namespace SafeExamBrowser.Runtime.UnitTests.Operations
proxy.Verify(p => p.InitiateShutdown(), Times.Once); proxy.Verify(p => p.InitiateShutdown(), Times.Once);
proxy.Verify(p => p.Disconnect(), 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.ClientProcess);
Assert.IsNull(sessionContext.ClientProxy); Assert.IsNull(sessionContext.ClientProxy);

View file

@ -240,32 +240,30 @@ namespace SafeExamBrowser.Runtime.Operations
return success; return success;
} }
private bool TryKillClient(int attempt = 0) private bool TryKillClient()
{ {
const int MAX_ATTEMPTS = 5; 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) if (ClientProcess.HasTerminated)
{ {
logger.Info("Client process has terminated."); logger.Info("Client process has terminated.");
return true;
} }
else else
{ {
logger.Warn("Failed to kill client process. Trying again..."); logger.Error($"Failed to kill client process within {MAX_ATTEMPTS} attempts!");
return TryKillClient(++attempt);
} }
return ClientProcess.HasTerminated;
} }
} }
} }

View file

@ -12,18 +12,18 @@ using System.Collections.Generic;
namespace SafeExamBrowser.Settings.Applications namespace SafeExamBrowser.Settings.Applications
{ {
/// <summary> /// <summary>
/// TODO /// Defines all settings for third-party applications and application monitoring.
/// </summary> /// </summary>
[Serializable] [Serializable]
public class ApplicationSettings public class ApplicationSettings
{ {
/// <summary> /// <summary>
/// /// All applications which are not allowed to run during a session.
/// </summary> /// </summary>
public IList<BlacklistApplication> Blacklist { get; set; } public IList<BlacklistApplication> Blacklist { get; set; }
/// <summary> /// <summary>
/// /// All applications which are allowed to run during a session.
/// </summary> /// </summary>
public IList<WhitelistApplication> Whitelist { get; set; } public IList<WhitelistApplication> Whitelist { get; set; }

View file

@ -11,10 +11,14 @@ using System;
namespace SafeExamBrowser.Settings.Applications namespace SafeExamBrowser.Settings.Applications
{ {
/// <summary> /// <summary>
/// TODO /// Defines an application which is whitelisted, i.e. allowed to run during a session.
/// </summary> /// </summary>
[Serializable] [Serializable]
public class WhitelistApplication public class WhitelistApplication
{ {
/// <summary>
/// The name of the main executable of the application.
/// </summary>
public string ExecutableName { get; set; }
} }
} }

View file

@ -25,14 +25,29 @@ namespace SafeExamBrowser.WindowsApi.Contracts
/// </summary> /// </summary>
int Id { get; } 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> /// <summary>
/// Event fired when the process has terminated. /// Event fired when the process has terminated.
/// </summary> /// </summary>
event ProcessTerminatedEventHandler Terminated; event ProcessTerminatedEventHandler Terminated;
/// <summary> /// <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> /// </summary>
void Kill(); bool TryClose();
/// <summary>
/// Attempts to immediately kill the process.
/// </summary>
bool TryKill();
} }
} }

View file

@ -6,6 +6,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/ */
using System.Collections.Generic;
namespace SafeExamBrowser.WindowsApi.Contracts namespace SafeExamBrowser.WindowsApi.Contracts
{ {
/// <summary> /// <summary>
@ -19,10 +21,20 @@ namespace SafeExamBrowser.WindowsApi.Contracts
/// </summary> /// </summary>
IDesktop StartupDesktop { set; } IDesktop StartupDesktop { set; }
/// <summary>
/// Retrieves all currently running processes.
/// </summary>
IEnumerable<IProcess> GetAllRunning();
/// <summary> /// <summary>
/// Starts a new process with the given command-line arguments. /// Starts a new process with the given command-line arguments.
/// </summary> /// </summary>
/// <exception cref="System.ComponentModel.Win32Exception">If the process could not be started.</exception> /// <exception cref="System.ComponentModel.Win32Exception">If the process could not be started.</exception>
IProcess StartNew(string path, params string[] args); 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);
} }
} }

View file

@ -6,6 +6,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. * 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;
using SafeExamBrowser.WindowsApi.Contracts.Events; using SafeExamBrowser.WindowsApi.Contracts.Events;
@ -13,9 +19,12 @@ namespace SafeExamBrowser.WindowsApi
{ {
internal class Process : IProcess internal class Process : IProcess
{ {
private bool eventInitialized, originalNameInitialized;
private ILogger logger;
private string originalName;
private System.Diagnostics.Process process; private System.Diagnostics.Process process;
public event ProcessTerminatedEventHandler Terminated; private event ProcessTerminatedEventHandler TerminatedEvent;
public int Id public int Id
{ {
@ -24,29 +33,141 @@ namespace SafeExamBrowser.WindowsApi
public bool HasTerminated 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); get { return originalNameInitialized ? originalName : InitializeOriginalName(); }
process.Exited += Process_Exited;
process.EnableRaisingEvents = true;
} }
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);
} }
} }
} }

View file

@ -7,27 +7,44 @@
*/ */
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Management;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SafeExamBrowser.Logging.Contracts; using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Constants; using SafeExamBrowser.WindowsApi.Constants;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Types; using SafeExamBrowser.WindowsApi.Types;
namespace SafeExamBrowser.WindowsApi namespace SafeExamBrowser.WindowsApi
{ {
public class ProcessFactory : IProcessFactory public class ProcessFactory : IProcessFactory
{ {
private ILogger logger; private IModuleLogger logger;
public IDesktop StartupDesktop { private get; set; } public IDesktop StartupDesktop { private get; set; }
public ProcessFactory(ILogger logger) public ProcessFactory(IModuleLogger logger)
{ {
this.logger = 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) public IProcess StartNew(string path, params string[] args)
{ {
var commandLine = $"{'"' + path + '"'} {String.Join(" ", args)}"; var commandLine = $"{'"' + path + '"'} {String.Join(" ", args)}";
@ -48,11 +65,73 @@ namespace SafeExamBrowser.WindowsApi
throw new Win32Exception(Marshal.GetLastWin32Error()); 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}."); logger.Info($"Successfully started process '{Path.GetFileName(path)}' with ID = {process.Id}.");
return process; 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})");
}
} }
} }

View file

@ -50,6 +50,7 @@
<ItemGroup> <ItemGroup>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="Microsoft.CSharp" /> <Reference Include="Microsoft.CSharp" />
<Reference Include="System.Management" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Constants\Constant.cs" /> <Compile Include="Constants\Constant.cs" />