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…
	
	Add table
		
		Reference in a new issue
	
	 dbuechel
						dbuechel