Restore SEBPatch

This commit is contained in:
2025-06-01 11:44:20 +02:00
commit 8c656e3137
1297 changed files with 142172 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8" />
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Principal.Windows" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
</dependentAssembly>
<dependentAssembly>
<assemblyIdentity name="Microsoft.Win32.Registry" publicKeyToken="b03f5f7f11d50a3a" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-5.0.0.0" newVersion="5.0.0.0" />
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -0,0 +1,101 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows;
using SafeExamBrowser.Configuration.Contracts;
namespace SafeExamBrowser.Client
{
public class App : Application
{
private const int ILMCM_CHECKLAYOUTANDTIPENABLED = 0x00001;
private const int ILMCM_LANGUAGEBAROFF = 0x00002;
private static readonly Mutex Mutex = new Mutex(true, AppConfig.CLIENT_MUTEX_NAME);
private readonly CompositionRoot instances = new CompositionRoot();
[STAThread]
public static void Main()
{
try
{
StartApplication();
}
catch (Exception e)
{
MessageBox.Show(e.Message + "\n\n" + e.StackTrace, "Fatal Error", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
Mutex.Close();
}
}
private static void StartApplication()
{
if (NoInstanceRunning())
{
new App().Run();
}
else
{
MessageBox.Show("You can only run one instance of SEB at a time.", "Startup Not Allowed", MessageBoxButton.OK, MessageBoxImage.Information);
}
}
private static bool NoInstanceRunning()
{
return Mutex.WaitOne(TimeSpan.Zero, true);
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ShutdownMode = ShutdownMode.OnExplicitShutdown;
// We need to manually initialize a monitor in order to prevent Windows from automatically doing so and thus rendering an input lanuage
// switch in the bottom right corner of the desktop. This must be done before any UI element is initialized or rendered on the screen.
InitLocalMsCtfMonitor(ILMCM_CHECKLAYOUTANDTIPENABLED | ILMCM_LANGUAGEBAROFF);
instances.BuildObjectGraph(Shutdown);
instances.LogStartupInformation();
var success = instances.ClientController.TryStart();
if (!success)
{
Shutdown();
}
}
public new void Shutdown()
{
void shutdown()
{
instances.ClientController.Terminate();
instances.LogShutdownInformation();
UninitLocalMsCtfMonitor();
base.Shutdown();
}
Dispatcher.InvokeAsync(shutdown);
}
[DllImport("MsCtfMonitor.dll", SetLastError = true)]
private static extern IntPtr InitLocalMsCtfMonitor(int dwFlags);
[DllImport("MsCtfMonitor.dll", SetLastError = true)]
private static extern IntPtr UninitLocalMsCtfMonitor();
}
}

View File

@@ -0,0 +1,84 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using System.Collections.Generic;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Browser.Contracts;
using SafeExamBrowser.Communication.Contracts.Hosts;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Integrity;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.Client
{
/// <summary>
/// Holds all configuration and session data for the client.
/// </summary>
internal class ClientContext
{
/// <summary>
/// All activators for shell components.
/// </summary>
internal IList<IActivator> Activators { get; }
/// <summary>
/// All applications allowed for the current session.
/// </summary>
internal IList<IApplication<IApplicationWindow>> Applications { get; }
/// <summary>
/// The global application configuration.
/// </summary>
internal AppConfig AppConfig { get; set; }
/// <summary>
/// The browser application.
/// </summary>
internal IBrowserApplication Browser { get; set; }
/// <summary>
/// The client communication host.
/// </summary>
internal IClientHost ClientHost { get; set; }
/// <summary>
/// The integrity module.
/// </summary>
internal IIntegrityModule IntegrityModule { get; set; }
/// <summary>
/// The proctoring controller to be used if the current session has proctoring enabled.
/// </summary>
internal IProctoringController Proctoring { get; set; }
/// <summary>
/// The server proxy to be used if the current session mode is <see cref="SessionMode.Server"/>.
/// </summary>
internal IServerProxy Server { get; set; }
/// <summary>
/// The identifier of the current session.
/// </summary>
internal Guid SessionId { get; set; }
/// <summary>
/// The settings for the current session.
/// </summary>
internal AppSettings Settings { get; set; }
internal ClientContext()
{
Activators = new List<IActivator>();
Applications = new List<IApplication<IApplicationWindow>>();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,111 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using SafeExamBrowser.Communication.Contracts;
using SafeExamBrowser.Communication.Contracts.Data;
using SafeExamBrowser.Communication.Contracts.Events;
using SafeExamBrowser.Communication.Contracts.Hosts;
using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Client.Communication
{
internal class ClientHost : BaseHost, IClientHost
{
private bool allowConnection;
private int processId;
public Guid AuthenticationToken { private get; set; }
public bool IsConnected { get; private set; }
public event CommunicationEventHandler<ExamSelectionRequestEventArgs> ExamSelectionRequested;
public event CommunicationEventHandler<MessageBoxRequestEventArgs> MessageBoxRequested;
public event CommunicationEventHandler<PasswordRequestEventArgs> PasswordRequested;
public event CommunicationEventHandler ReconfigurationAborted;
public event CommunicationEventHandler<ReconfigurationEventArgs> ReconfigurationDenied;
public event CommunicationEventHandler RuntimeDisconnected;
public event CommunicationEventHandler<ServerFailureActionRequestEventArgs> ServerFailureActionRequested;
public event CommunicationEventHandler Shutdown;
public ClientHost(
string address,
IHostObjectFactory factory,
ILogger logger,
int processId,
int timeout_ms) : base(address, factory, logger, timeout_ms)
{
this.allowConnection = true;
this.processId = processId;
}
protected override bool OnConnect(Guid? token)
{
var authenticated = AuthenticationToken == token;
var accepted = allowConnection && authenticated;
if (accepted)
{
allowConnection = false;
IsConnected = true;
}
return accepted;
}
protected override void OnDisconnect(Interlocutor interlocutor)
{
if (interlocutor == Interlocutor.Runtime)
{
RuntimeDisconnected?.Invoke();
IsConnected = false;
}
}
protected override Response OnReceive(Message message)
{
switch (message)
{
case ExamSelectionRequestMessage m:
ExamSelectionRequested?.InvokeAsync(new ExamSelectionRequestEventArgs { Exams = m.Exams, RequestId = m.RequestId });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case MessageBoxRequestMessage m:
MessageBoxRequested?.InvokeAsync(new MessageBoxRequestEventArgs { Action = m.Action, Icon = m.Icon, Message = m.Message, RequestId = m.RequestId, Title = m.Title });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case PasswordRequestMessage m:
PasswordRequested?.InvokeAsync(new PasswordRequestEventArgs { Purpose = m.Purpose, RequestId = m.RequestId });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case ReconfigurationDeniedMessage m:
ReconfigurationDenied?.InvokeAsync(new ReconfigurationEventArgs { ConfigurationPath = m.FilePath });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case ServerFailureActionRequestMessage m:
ServerFailureActionRequested?.InvokeAsync(new ServerFailureActionRequestEventArgs { Message = m.Message, RequestId = m.RequestId, ShowFallback = m.ShowFallback });
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
}
protected override Response OnReceive(SimpleMessagePurport message)
{
switch (message)
{
case SimpleMessagePurport.Authenticate:
return new AuthenticationResponse { ProcessId = processId };
case SimpleMessagePurport.ReconfigurationAborted:
ReconfigurationAborted?.InvokeAsync();
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
case SimpleMessagePurport.Shutdown:
Shutdown?.Invoke();
return new SimpleResponse(SimpleResponsePurport.Acknowledged);
}
return new SimpleResponse(SimpleResponsePurport.UnknownMessage);
}
}
}

View File

@@ -0,0 +1,396 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using SafeExamBrowser.Applications;
using SafeExamBrowser.Browser;
using SafeExamBrowser.Client.Communication;
using SafeExamBrowser.Client.Notifications;
using SafeExamBrowser.Client.Operations;
using SafeExamBrowser.Communication.Contracts;
using SafeExamBrowser.Communication.Contracts.Proxies;
using SafeExamBrowser.Communication.Hosts;
using SafeExamBrowser.Communication.Proxies;
using SafeExamBrowser.Configuration.Cryptography;
using SafeExamBrowser.Configuration.Integrity;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.OperationModel;
using SafeExamBrowser.Core.Operations;
using SafeExamBrowser.I18n;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring;
using SafeExamBrowser.Monitoring.Applications;
using SafeExamBrowser.Monitoring.Display;
using SafeExamBrowser.Monitoring.Keyboard;
using SafeExamBrowser.Monitoring.Mouse;
using SafeExamBrowser.Monitoring.System;
using SafeExamBrowser.Proctoring;
using SafeExamBrowser.Server;
using SafeExamBrowser.Settings.Logging;
using SafeExamBrowser.Settings.UserInterface;
using SafeExamBrowser.SystemComponents;
using SafeExamBrowser.SystemComponents.Audio;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
using SafeExamBrowser.SystemComponents.Keyboard;
using SafeExamBrowser.SystemComponents.Network;
using SafeExamBrowser.SystemComponents.PowerSupply;
using SafeExamBrowser.SystemComponents.Registry;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.FileSystemDialog;
using SafeExamBrowser.UserInterface.Contracts.MessageBox;
using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.UserInterface.Shared.Activators;
using SafeExamBrowser.WindowsApi;
using SafeExamBrowser.WindowsApi.Contracts;
using SafeExamBrowser.WindowsApi.Processes;
using Desktop = SafeExamBrowser.UserInterface.Desktop;
using Mobile = SafeExamBrowser.UserInterface.Mobile;
namespace SafeExamBrowser.Client
{
internal class CompositionRoot
{
private const int TWO_SECONDS = 2000;
private const int FIVE_SECONDS = 5000;
private Guid authenticationToken;
private ClientContext context;
private string logFilePath;
private LogLevel logLevel;
private string runtimeHostUri;
private UserInterfaceMode uiMode;
private IActionCenter actionCenter;
private ApplicationMonitor applicationMonitor;
private ILogger logger;
private IMessageBox messageBox;
private INativeMethods nativeMethods;
private INetworkAdapter networkAdapter;
private IPowerSupply powerSupply;
private IRuntimeProxy runtimeProxy;
private ISystemInfo systemInfo;
private ITaskbar taskbar;
private ITaskview taskview;
private IUserInfo userInfo;
private IText text;
private IUserInterfaceFactory uiFactory;
internal ClientController ClientController { get; private set; }
internal void BuildObjectGraph(Action shutdown)
{
ValidateCommandLineArguments();
InitializeLogging();
InitializeText();
var processFactory = new ProcessFactory(ModuleLogger(nameof(ProcessFactory)));
var registry = new Registry(ModuleLogger(nameof(Registry)));
uiFactory = BuildUserInterfaceFactory();
actionCenter = uiFactory.CreateActionCenter();
context = new ClientContext();
messageBox = BuildMessageBox();
nativeMethods = new NativeMethods();
applicationMonitor = new ApplicationMonitor(TWO_SECONDS, ModuleLogger(nameof(ApplicationMonitor)), nativeMethods, processFactory);
networkAdapter = new NetworkAdapter(ModuleLogger(nameof(NetworkAdapter)), nativeMethods);
runtimeProxy = new RuntimeProxy(runtimeHostUri, new ProxyObjectFactory(), ModuleLogger(nameof(RuntimeProxy)), Interlocutor.Client);
systemInfo = new SystemInfo(registry);
taskbar = uiFactory.CreateTaskbar(ModuleLogger("Taskbar"));
taskview = uiFactory.CreateTaskview();
userInfo = new UserInfo(ModuleLogger(nameof(UserInfo)));
var applicationFactory = new ApplicationFactory(applicationMonitor, ModuleLogger(nameof(ApplicationFactory)), nativeMethods, processFactory, new Registry(ModuleLogger(nameof(Registry))));
var clipboard = new Clipboard(ModuleLogger(nameof(Clipboard)), nativeMethods);
var coordinator = new Coordinator();
var displayMonitor = new DisplayMonitor(ModuleLogger(nameof(DisplayMonitor)), nativeMethods, systemInfo);
var explorerShell = new ExplorerShell(ModuleLogger(nameof(ExplorerShell)), nativeMethods);
var fileSystemDialog = BuildFileSystemDialog();
var hashAlgorithm = new HashAlgorithm();
var sentinel = new SystemSentinel(ModuleLogger(nameof(SystemSentinel)), nativeMethods, registry);
var splashScreen = uiFactory.CreateSplashScreen();
var operations = new Queue<IOperation>();
operations.Enqueue(new I18nOperation(logger, text));
operations.Enqueue(new RuntimeConnectionOperation(context, logger, runtimeProxy, authenticationToken));
operations.Enqueue(new ConfigurationOperation(context, logger, runtimeProxy));
operations.Enqueue(new DelegateOperation(UpdateAppConfig));
operations.Enqueue(new DelegateOperation(BuildIntegrityModule));
operations.Enqueue(new DelegateOperation(BuildPowerSupply));
operations.Enqueue(new LazyInitializationOperation(BuildClientHostOperation));
operations.Enqueue(new ClientHostDisconnectionOperation(context, logger, FIVE_SECONDS));
operations.Enqueue(new LazyInitializationOperation(BuildKeyboardInterceptorOperation));
operations.Enqueue(new LazyInitializationOperation(BuildMouseInterceptorOperation));
operations.Enqueue(new ApplicationOperation(context, applicationFactory, applicationMonitor, logger, text));
operations.Enqueue(new DisplayMonitorOperation(context, displayMonitor, logger, taskbar));
operations.Enqueue(new LazyInitializationOperation(BuildShellOperation));
operations.Enqueue(new LazyInitializationOperation(BuildBrowserOperation));
operations.Enqueue(new LazyInitializationOperation(BuildServerOperation));
operations.Enqueue(new LazyInitializationOperation(BuildProctoringOperation));
operations.Enqueue(new ClipboardOperation(context, clipboard, logger));
var sequence = new OperationSequence(logger, operations);
ClientController = new ClientController(
actionCenter,
applicationMonitor,
context,
coordinator,
displayMonitor,
explorerShell,
fileSystemDialog,
hashAlgorithm,
logger,
messageBox,
networkAdapter,
sequence,
runtimeProxy,
shutdown,
splashScreen,
sentinel,
taskbar,
text,
uiFactory);
}
internal void LogStartupInformation()
{
logger.Log($"# New client instance started at {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}");
logger.Log(string.Empty);
}
internal void LogShutdownInformation()
{
logger?.Log($"# Client instance terminated at {DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}");
}
private void ValidateCommandLineArguments()
{
var args = Environment.GetCommandLineArgs();
var hasFive = args?.Length >= 5;
var valid = false;
if (hasFive)
{
var logFilePath = Encoding.UTF8.GetString(Convert.FromBase64String(args[1]));
var hasLogFilePath = Uri.TryCreate(logFilePath, UriKind.Absolute, out var filePath) && filePath.IsFile;
var hasLogLevel = Enum.TryParse(args[2], out LogLevel logLevel);
var hasHostUri = Uri.TryCreate(args[3], UriKind.Absolute, out var runtimeHostUri) && runtimeHostUri.IsWellFormedOriginalString();
var hasAuthenticationToken = Guid.TryParse(args[4], out var authenticationToken);
if (hasLogFilePath && hasLogLevel && hasHostUri && hasAuthenticationToken)
{
this.authenticationToken = authenticationToken;
this.logFilePath = logFilePath;
this.logLevel = logLevel;
this.runtimeHostUri = args[3];
this.uiMode = args.Length == 6 && Enum.TryParse(args[5], out uiMode) ? uiMode : UserInterfaceMode.Desktop;
valid = true;
}
}
if (!valid)
{
throw new ArgumentException("Invalid arguments! Required: SafeExamBrowser.Client.exe <logfile path> <log level> <host URI> <token>");
}
}
private void InitializeLogging()
{
var logFileWriter = new LogFileWriter(new DefaultLogFormatter(), logFilePath);
logFileWriter.Initialize();
logger = new Logger();
logger.LogLevel = logLevel;
logger.Subscribe(logFileWriter);
}
private void InitializeText()
{
text = new Text(ModuleLogger(nameof(Text)));
}
private IOperation BuildBrowserOperation()
{
var fileSystemDialog = BuildFileSystemDialog();
var keyGenerator = new KeyGenerator(context.AppConfig, context.IntegrityModule, ModuleLogger(nameof(KeyGenerator)));
var moduleLogger = ModuleLogger(nameof(BrowserApplication));
var browser = new BrowserApplication(
context.AppConfig,
context.Settings.Browser,
fileSystemDialog,
new HashAlgorithm(),
keyGenerator,
messageBox,
moduleLogger,
nativeMethods,
context.Settings.SessionMode,
text,
uiFactory);
var operation = new BrowserOperation(actionCenter, context, logger, taskbar, taskview, uiFactory);
context.Browser = browser;
return operation;
}
private IOperation BuildClientHostOperation()
{
var processId = Process.GetCurrentProcess().Id;
var factory = new HostObjectFactory();
var clientHost = new ClientHost(context.AppConfig.ClientAddress, factory, ModuleLogger(nameof(ClientHost)), processId, FIVE_SECONDS);
var operation = new CommunicationHostOperation(clientHost, logger);
context.ClientHost = clientHost;
context.ClientHost.AuthenticationToken = authenticationToken;
return operation;
}
private void BuildIntegrityModule()
{
context.IntegrityModule = new IntegrityModule(context.AppConfig, ModuleLogger(nameof(IntegrityModule)));
}
private void BuildPowerSupply()
{
powerSupply = new PowerSupply(ModuleLogger(nameof(PowerSupply)), context.Settings.PowerSupply);
}
private IOperation BuildKeyboardInterceptorOperation()
{
var keyboardInterceptor = new KeyboardInterceptor(ModuleLogger(nameof(KeyboardInterceptor)), nativeMethods, context.Settings.Keyboard);
var operation = new KeyboardInterceptorOperation(context, keyboardInterceptor, logger);
return operation;
}
private IOperation BuildMouseInterceptorOperation()
{
var mouseInterceptor = new MouseInterceptor(ModuleLogger(nameof(MouseInterceptor)), nativeMethods, context.Settings.Mouse);
var operation = new MouseInterceptorOperation(context, logger, mouseInterceptor);
return operation;
}
private IOperation BuildProctoringOperation()
{
var controller = new ProctoringController(
context.AppConfig,
applicationMonitor,
context.Browser,
new FileSystem(),
ModuleLogger(nameof(ProctoringController)),
nativeMethods,
context.Server,
text,
uiFactory);
var operation = new ProctoringOperation(actionCenter, context, controller, logger, taskbar, uiFactory);
context.Proctoring = controller;
return operation;
}
private IOperation BuildServerOperation()
{
var keyGenerator = new KeyGenerator(context.AppConfig, context.IntegrityModule, ModuleLogger(nameof(KeyGenerator)));
var server = new ServerProxy(context.AppConfig, keyGenerator, ModuleLogger(nameof(ServerProxy)), systemInfo, userInfo, powerSupply, networkAdapter);
var operation = new ServerOperation(context, logger, server);
context.Server = server;
return operation;
}
private IOperation BuildShellOperation()
{
var aboutNotification = new AboutNotification(context.AppConfig, text, uiFactory);
var audio = new Audio(context.Settings.Audio, ModuleLogger(nameof(Audio)));
var keyboard = new Keyboard(ModuleLogger(nameof(Keyboard)));
var logNotification = new LogNotification(logger, text, uiFactory);
var operation = new ShellOperation(
actionCenter,
audio,
aboutNotification,
context,
keyboard,
logger,
logNotification,
nativeMethods,
networkAdapter,
powerSupply,
systemInfo,
taskbar,
taskview,
text,
uiFactory);
context.Activators.Add(new ActionCenterKeyboardActivator(ModuleLogger(nameof(ActionCenterKeyboardActivator)), nativeMethods));
context.Activators.Add(new ActionCenterTouchActivator(ModuleLogger(nameof(ActionCenterTouchActivator)), nativeMethods));
context.Activators.Add(new TaskbarKeyboardActivator(ModuleLogger(nameof(TaskbarKeyboardActivator)), nativeMethods));
context.Activators.Add(new TaskviewKeyboardActivator(ModuleLogger(nameof(TaskviewKeyboardActivator)), nativeMethods));
context.Activators.Add(new TerminationActivator(ModuleLogger(nameof(TerminationActivator)), nativeMethods));
return operation;
}
private IFileSystemDialog BuildFileSystemDialog()
{
switch (uiMode)
{
case UserInterfaceMode.Mobile:
return new Mobile.FileSystemDialogFactory(systemInfo, text);
default:
return new Desktop.FileSystemDialogFactory(systemInfo, text);
}
}
private IMessageBox BuildMessageBox()
{
switch (uiMode)
{
case UserInterfaceMode.Mobile:
return new Mobile.MessageBoxFactory(text);
default:
return new Desktop.MessageBoxFactory(text);
}
}
private IUserInterfaceFactory BuildUserInterfaceFactory()
{
switch (uiMode)
{
case UserInterfaceMode.Mobile:
return new Mobile.UserInterfaceFactory(text);
default:
return new Desktop.UserInterfaceFactory(text);
}
}
private void UpdateAppConfig()
{
ClientController.UpdateAppConfig();
}
private IModuleLogger ModuleLogger(string moduleInfo)
{
return new ModuleLogger(logger, moduleInfo);
}
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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/.
*/
namespace SafeExamBrowser.Client.Contracts
{
/// <summary>
/// Coordinates concurrent operations of the client application.
/// </summary>
internal interface ICoordinator
{
/// <summary>
/// Indicates whether the reconfiguration lock is currently occupied.
/// </summary>
bool IsReconfigurationLocked();
/// <summary>
/// Indicates whether the session lock is currently occupied.
/// </summary>
bool IsSessionLocked();
/// <summary>
/// Releases the reconfiguration lock.
/// </summary>
void ReleaseReconfigurationLock();
/// <summary>
/// Releases the session lock.
/// </summary>
void ReleaseSessionLock();
/// <summary>
/// Attempts to acquire the unique reconfiguration lock. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool RequestReconfigurationLock();
/// <summary>
/// Attempts to acquire the unique session lock. Returns <c>true</c> if successful, otherwise <c>false</c>.
/// </summary>
bool RequestSessionLock();
}
}

View File

@@ -0,0 +1,78 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using System.Collections.Concurrent;
using SafeExamBrowser.Client.Contracts;
namespace SafeExamBrowser.Client
{
internal class Coordinator : ICoordinator
{
private readonly ConcurrentBag<Guid> reconfiguration;
private readonly ConcurrentBag<Guid> session;
internal Coordinator()
{
reconfiguration = new ConcurrentBag<Guid>();
session = new ConcurrentBag<Guid>();
}
public bool IsReconfigurationLocked()
{
return !reconfiguration.IsEmpty;
}
public bool IsSessionLocked()
{
return !session.IsEmpty;
}
public void ReleaseReconfigurationLock()
{
reconfiguration.TryTake(out _);
}
public void ReleaseSessionLock()
{
session.TryTake(out _);
}
public bool RequestReconfigurationLock()
{
var acquired = false;
lock (reconfiguration)
{
if (reconfiguration.IsEmpty)
{
reconfiguration.Add(Guid.NewGuid());
acquired = true;
}
}
return acquired;
}
public bool RequestSessionLock()
{
var acquired = false;
lock (session)
{
if (session.IsEmpty)
{
session.Add(Guid.NewGuid());
acquired = true;
}
}
return acquired;
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Core.Contracts.Notifications;
using SafeExamBrowser.Core.Contracts.Notifications.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
namespace SafeExamBrowser.Client.Notifications
{
internal class AboutNotification : INotification
{
private readonly AppConfig appConfig;
private readonly IUserInterfaceFactory uiFactory;
private IWindow window;
public bool CanActivate { get; }
public string Tooltip { get; }
public IconResource IconResource { get; }
public event NotificationChangedEventHandler NotificationChanged { add { } remove { } }
public AboutNotification(AppConfig appConfig, IText text, IUserInterfaceFactory uiFactory)
{
this.appConfig = appConfig;
this.uiFactory = uiFactory;
CanActivate = true;
IconResource = new XamlIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/AboutNotification.xaml") };
Tooltip = text.Get(TextKey.Notification_AboutTooltip);
}
public void Activate()
{
if (window == default)
{
window = uiFactory.CreateAboutWindow(appConfig);
window.Closed += () => window = default;
window.Show();
}
else
{
window.BringToForeground();
}
}
public void Terminate()
{
window?.Close();
}
}
}

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using SafeExamBrowser.Core.Contracts.Notifications;
using SafeExamBrowser.Core.Contracts.Notifications.Events;
using SafeExamBrowser.Core.Contracts.Resources.Icons;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Windows;
namespace SafeExamBrowser.Client.Notifications
{
internal class LogNotification : INotification
{
private readonly ILogger logger;
private readonly IUserInterfaceFactory uiFactory;
private IWindow window;
public bool CanActivate { get; }
public string Tooltip { get; }
public IconResource IconResource { get; }
public event NotificationChangedEventHandler NotificationChanged { add { } remove { } }
public LogNotification(ILogger logger, IText text, IUserInterfaceFactory uiFactory)
{
this.logger = logger;
this.uiFactory = uiFactory;
CanActivate = true;
IconResource = new BitmapIconResource { Uri = new Uri("pack://application:,,,/SafeExamBrowser.UserInterface.Desktop;component/Images/LogNotification.ico") };
Tooltip = text.Get(TextKey.Notification_LogTooltip);
}
public void Activate()
{
if (window == default)
{
window = uiFactory.CreateLogWindow(logger);
window.Closed += () => window = default;
window.Show();
}
else
{
window.BringToForeground();
}
}
public void Terminate()
{
window?.Close();
}
}
}

View File

@@ -0,0 +1,204 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 System.Linq;
using SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Client.Operations.Events;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Applications;
using SafeExamBrowser.Settings.Applications;
using SafeExamBrowser.Settings.Security;
namespace SafeExamBrowser.Client.Operations
{
internal class ApplicationOperation : ClientOperation
{
private IApplicationFactory factory;
private ILogger logger;
private IApplicationMonitor monitor;
private IText text;
public override event ActionRequiredEventHandler ActionRequired;
public override event StatusChangedEventHandler StatusChanged;
public ApplicationOperation(
ClientContext context,
IApplicationFactory factory,
IApplicationMonitor monitor,
ILogger logger,
IText text) : base(context)
{
this.factory = factory;
this.monitor = monitor;
this.logger = logger;
this.text = text;
}
public override OperationResult Perform()
{
logger.Info("Initializing applications...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeApplications);
var result = InitializeApplications();
if (result == OperationResult.Success)
{
StartMonitor();
}
return result;
}
public override OperationResult Revert()
{
logger.Info("Finalizing applications...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeApplications);
FinalizeApplications();
StopMonitor();
return OperationResult.Success;
}
private OperationResult InitializeApplications()
{
var initialization = monitor.Initialize(Context.Settings.Applications);
var result = OperationResult.Success;
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)
{
Initialize(application);
}
}
return result;
}
private void Initialize(WhitelistApplication settings)
{
var result = factory.TryCreate(settings, out var application);
while (result == FactoryResult.NotFound && settings.AllowCustomPath)
{
var args = new ApplicationNotFoundEventArgs(settings.DisplayName, settings.ExecutableName);
ActionRequired?.Invoke(args);
if (args.Success)
{
settings.ExecutablePath = args.CustomPath;
result = factory.TryCreate(settings, out application);
}
else
{
break;
}
}
if (result == FactoryResult.Success)
{
Context.Applications.Add(application);
}
else
{
logger.Error($"Failed to initialize application '{settings.DisplayName}' ({settings.ExecutableName}). Reason: {result}.");
ActionRequired?.Invoke(new ApplicationInitializationFailedEventArgs(settings.DisplayName, settings.ExecutableName, result));
}
}
private void FinalizeApplications()
{
foreach (var application in Context.Applications)
{
application.Terminate();
}
}
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()
{
if (Context.Settings.Security.KioskMode != KioskMode.None)
{
monitor.Start();
}
}
private void StopMonitor()
{
if (Context.Settings.Security.KioskMode != KioskMode.None)
{
monitor.Stop();
}
}
private OperationResult TryTerminate(IEnumerable<RunningApplication> runningApplications)
{
var args = new ApplicationTerminationEventArgs(runningApplications);
var failed = new List<RunningApplication>();
var result = OperationResult.Success;
logger.Info($"The following applications need to be terminated: {string.Join(", ", runningApplications.Select(a => a.Name))}.");
ActionRequired?.Invoke(args);
if (args.TerminateProcesses)
{
logger.Info($"The user chose to automatically terminate all running applications.");
foreach (var application in runningApplications)
{
var success = monitor.TryTerminate(application);
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
{
logger.Info("The user chose not to automatically terminate all running applications. Aborting...");
result = OperationResult.Aborted;
}
if (failed.Any())
{
ActionRequired?.Invoke(new ApplicationTerminationFailedEventArgs(failed));
}
return result;
}
}
}

View File

@@ -0,0 +1,90 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.Client.Operations
{
internal class BrowserOperation : ClientOperation
{
private readonly IActionCenter actionCenter;
private readonly ILogger logger;
private readonly ITaskbar taskbar;
private readonly ITaskview taskview;
private readonly IUserInterfaceFactory uiFactory;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public BrowserOperation(
IActionCenter actionCenter,
ClientContext context,
ILogger logger,
ITaskbar taskbar,
ITaskview taskview,
IUserInterfaceFactory uiFactory) : base(context)
{
this.actionCenter = actionCenter;
this.logger = logger;
this.taskbar = taskbar;
this.taskview = taskview;
this.uiFactory = uiFactory;
}
public override OperationResult Perform()
{
logger.Info("Initializing browser...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeBrowser);
if (Context.Settings.Browser.EnableBrowser)
{
Context.Browser.Initialize();
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter)
{
actionCenter.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.ActionCenter), true);
}
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar)
{
taskbar.AddApplicationControl(uiFactory.CreateApplicationControl(Context.Browser, Location.Taskbar), true);
}
taskview.Add(Context.Browser);
}
else
{
logger.Info("Browser application is disabled for this session.");
}
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Terminating browser...");
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateBrowser);
if (Context.Settings.Browser.EnableBrowser)
{
Context.Browser.Terminate();
}
else
{
logger.Info("Browser application was disabled for this session.");
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Threading;
using SafeExamBrowser.Communication.Contracts.Events;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Client.Operations
{
/// <summary>
/// During application shutdown, it could happen that the client stops its communication host before the runtime had the chance to
/// disconnect from it. This operation prevents the described race condition by waiting on the runtime to disconnect from the client.
/// </summary>
internal class ClientHostDisconnectionOperation : ClientOperation
{
private ILogger logger;
private int timeout_ms;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ClientHostDisconnectionOperation(ClientContext context, ILogger logger, int timeout_ms) : base(context)
{
this.logger = logger;
this.timeout_ms = timeout_ms;
}
public override OperationResult Perform()
{
return OperationResult.Success;
}
public override OperationResult Revert()
{
StatusChanged?.Invoke(TextKey.OperationStatus_WaitRuntimeDisconnection);
if (Context.ClientHost.IsConnected)
{
var disconnected = false;
var disconnectedEvent = new AutoResetEvent(false);
var disconnectedEventHandler = new CommunicationEventHandler(() => disconnectedEvent.Set());
Context.ClientHost.RuntimeDisconnected += disconnectedEventHandler;
logger.Info("Waiting for runtime to disconnect from client communication host...");
disconnected = disconnectedEvent.WaitOne(timeout_ms);
Context.ClientHost.RuntimeDisconnected -= disconnectedEventHandler;
if (disconnected)
{
logger.Info("The runtime has successfully disconnected from the client communication host.");
}
else
{
logger.Error($"The runtime failed to disconnect within {timeout_ms / 1000} seconds!");
return OperationResult.Failed;
}
}
else
{
logger.Info("The runtime has already disconnected from the client communication host.");
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
namespace SafeExamBrowser.Client.Operations
{
/// <summary>
/// The base implementation to be used for all operations in the client operation sequence.
/// </summary>
internal abstract class ClientOperation : IOperation
{
protected ClientContext Context { get; private set; }
public abstract event ActionRequiredEventHandler ActionRequired;
public abstract event StatusChangedEventHandler StatusChanged;
public ClientOperation(ClientContext context)
{
Context = context;
}
public abstract OperationResult Perform();
public abstract OperationResult Revert();
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class ClipboardOperation : ClientOperation
{
private readonly IClipboard clipboard;
private readonly ILogger logger;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ClipboardOperation(ClientContext context, IClipboard clipboard, ILogger logger) : base(context)
{
this.clipboard = clipboard;
this.logger = logger;
}
public override OperationResult Perform()
{
InitializeClipboard();
return OperationResult.Success;
}
public override OperationResult Revert()
{
FinalizeClipboard();
return OperationResult.Success;
}
private void InitializeClipboard()
{
logger.Info("Initializing clipboard...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeClipboard);
clipboard.Initialize(Context.Settings.Security.ClipboardPolicy);
}
private void FinalizeClipboard()
{
logger.Info("Finalizing clipboard...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeClipboard);
clipboard.Terminate();
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Communication.Contracts.Proxies;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class ConfigurationOperation : ClientOperation
{
private readonly ILogger logger;
private readonly IRuntimeProxy runtime;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ConfigurationOperation(ClientContext context, ILogger logger, IRuntimeProxy runtime) : base(context)
{
this.logger = logger;
this.runtime = runtime;
}
public override OperationResult Perform()
{
logger.Info("Initializing application configuration...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeConfiguration);
var communication = runtime.GetConfiguration();
var configuration = communication.Value.Configuration;
Context.AppConfig = configuration.AppConfig;
Context.SessionId = configuration.SessionId;
Context.Settings = configuration.Settings;
logger.Info("Successfully retrieved the application configuration from the runtime.");
logger.Info($" -> Client-ID: {Context.AppConfig.ClientId}");
logger.Info($" -> Runtime-ID: {Context.AppConfig.RuntimeId}");
logger.Info($" -> Session-ID: {Context.SessionId}");
return OperationResult.Success;
}
public override OperationResult Revert()
{
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Display;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.Client.Operations
{
internal class DisplayMonitorOperation : ClientOperation
{
private readonly IDisplayMonitor displayMonitor;
private readonly ILogger logger;
private readonly ITaskbar taskbar;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public DisplayMonitorOperation(ClientContext context, IDisplayMonitor displayMonitor, ILogger logger, ITaskbar taskbar) : base(context)
{
this.displayMonitor = displayMonitor;
this.logger = logger;
this.taskbar = taskbar;
}
public override OperationResult Perform()
{
logger.Info("Initializing working area...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeWorkingArea);
displayMonitor.InitializePrimaryDisplay(Context.Settings.UserInterface.Taskbar.EnableTaskbar ? taskbar.GetAbsoluteHeight() : 0);
displayMonitor.StartMonitoringDisplayChanges();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Restoring working area...");
StatusChanged?.Invoke(TextKey.OperationStatus_RestoreWorkingArea);
displayMonitor.StopMonitoringDisplayChanges();
displayMonitor.ResetPrimaryDisplay();
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Applications.Contracts;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
namespace SafeExamBrowser.Client.Operations.Events
{
internal class ApplicationInitializationFailedEventArgs : ActionRequiredEventArgs
{
internal string DisplayName { get; }
internal string ExecutableName { get; }
internal FactoryResult Result { get; }
internal ApplicationInitializationFailedEventArgs(string displayName, string executableName, FactoryResult result)
{
DisplayName = displayName;
ExecutableName = executableName;
Result = result;
}
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel.Events;
namespace SafeExamBrowser.Client.Operations.Events
{
internal class ApplicationNotFoundEventArgs : ActionRequiredEventArgs
{
internal string CustomPath { get; set; }
internal string DisplayName { get; }
internal string ExecutableName { get; }
internal bool Success { get; set; }
internal ApplicationNotFoundEventArgs(string displayName, string executableName)
{
DisplayName = displayName;
ExecutableName = executableName;
}
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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

@@ -0,0 +1,24 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 ApplicationTerminationFailedEventArgs : ActionRequiredEventArgs
{
internal IEnumerable<RunningApplication> Applications { get; }
internal ApplicationTerminationFailedEventArgs(IEnumerable<RunningApplication> applications)
{
Applications = applications;
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Keyboard;
namespace SafeExamBrowser.Client.Operations
{
internal class KeyboardInterceptorOperation : ClientOperation
{
private IKeyboardInterceptor keyboardInterceptor;
private ILogger logger;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public KeyboardInterceptorOperation(ClientContext context, IKeyboardInterceptor keyboardInterceptor, ILogger logger) : base(context)
{
this.keyboardInterceptor = keyboardInterceptor;
this.logger = logger;
}
public override OperationResult Perform()
{
logger.Info("Starting keyboard interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StartKeyboardInterception);
keyboardInterceptor.Start();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Stopping keyboard interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopKeyboardInterception);
keyboardInterceptor.Stop();
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Monitoring.Contracts.Mouse;
namespace SafeExamBrowser.Client.Operations
{
internal class MouseInterceptorOperation : ClientOperation
{
private ILogger logger;
private IMouseInterceptor mouseInterceptor;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public MouseInterceptorOperation(ClientContext context, ILogger logger, IMouseInterceptor mouseInterceptor) : base(context)
{
this.logger = logger;
this.mouseInterceptor = mouseInterceptor;
}
public override OperationResult Perform()
{
logger.Info("Starting mouse interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StartMouseInterception);
mouseInterceptor.Start();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Stopping mouse interception...");
StatusChanged?.Invoke(TextKey.OperationStatus_StopMouseInterception);
mouseInterceptor.Stop();
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Proctoring.Contracts;
using SafeExamBrowser.Settings;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
namespace SafeExamBrowser.Client.Operations
{
internal class ProctoringOperation : ClientOperation
{
private readonly IActionCenter actionCenter;
private readonly IProctoringController controller;
private readonly ILogger logger;
private readonly ITaskbar taskbar;
private readonly IUserInterfaceFactory uiFactory;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ProctoringOperation(
IActionCenter actionCenter,
ClientContext context,
IProctoringController controller,
ILogger logger,
ITaskbar taskbar,
IUserInterfaceFactory uiFactory) : base(context)
{
this.actionCenter = actionCenter;
this.controller = controller;
this.logger = logger;
this.taskbar = taskbar;
this.uiFactory = uiFactory;
}
public override OperationResult Perform()
{
if (Context.Settings.Proctoring.Enabled)
{
logger.Info("Initializing proctoring...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeProctoring);
controller.Initialize(Context.Settings.Proctoring);
if (Context.Settings.SessionMode == SessionMode.Server && Context.Settings.Proctoring.ShowRaiseHandNotification)
{
actionCenter.AddNotificationControl(uiFactory.CreateRaiseHandControl(controller, Location.ActionCenter, Context.Settings.Proctoring));
taskbar.AddNotificationControl(uiFactory.CreateRaiseHandControl(controller, Location.Taskbar, Context.Settings.Proctoring));
}
foreach (var notification in controller.Notifications)
{
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.ActionCenter));
if (Context.Settings.Proctoring.ShowTaskbarNotification)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(notification, Location.Taskbar));
}
}
}
return OperationResult.Success;
}
public override OperationResult Revert()
{
if (Context.Settings.Proctoring.Enabled)
{
logger.Info("Terminating proctoring...");
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateProctoring);
controller.Terminate();
foreach (var notification in controller.Notifications)
{
notification.Terminate();
}
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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;
using SafeExamBrowser.Communication.Contracts.Proxies;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class RuntimeConnectionOperation : ClientOperation
{
private ILogger logger;
private IRuntimeProxy runtime;
private Guid token;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public RuntimeConnectionOperation(ClientContext context, ILogger logger, IRuntimeProxy runtime, Guid token) : base(context)
{
this.logger = logger;
this.runtime = runtime;
this.token = token;
}
public override OperationResult Perform()
{
logger.Info("Initializing runtime connection...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeRuntimeConnection);
var connected = runtime.Connect(token);
if (connected)
{
logger.Info("Successfully connected to the runtime.");
}
else
{
logger.Error("Failed to connect to the runtime. Aborting startup...");
}
return connected ? OperationResult.Success : OperationResult.Failed;
}
public override OperationResult Revert()
{
logger.Info("Closing runtime connection...");
StatusChanged?.Invoke(TextKey.OperationStatus_CloseRuntimeConnection);
if (runtime.IsConnected)
{
var success = runtime.Disconnect();
if (success)
{
logger.Info("Successfully disconnected from the runtime.");
}
else
{
logger.Error("Failed to disconnect from the runtime!");
}
return success ? OperationResult.Success : OperationResult.Failed;
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,64 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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 SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.Server.Contracts;
using SafeExamBrowser.Settings;
namespace SafeExamBrowser.Client.Operations
{
internal class ServerOperation : ClientOperation
{
private readonly ILogger logger;
private readonly IServerProxy server;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ServerOperation(ClientContext context, ILogger logger, IServerProxy server) : base(context)
{
this.logger = logger;
this.server = server;
}
public override OperationResult Perform()
{
if (Context.Settings.SessionMode == SessionMode.Server)
{
logger.Info("Initializing server...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeServer);
server.Initialize(
Context.AppConfig.ServerApi,
Context.AppConfig.ServerConnectionToken,
Context.AppConfig.ServerExamId,
Context.AppConfig.ServerOauth2Token,
Context.Settings.Server);
server.StartConnectivity();
}
return OperationResult.Success;
}
public override OperationResult Revert()
{
if (Context.Settings?.SessionMode == SessionMode.Server)
{
logger.Info("Finalizing server...");
StatusChanged?.Invoke(TextKey.OperationStatus_FinalizeServer);
server.StopConnectivity();
}
return OperationResult.Success;
}
}
}

View File

@@ -0,0 +1,371 @@
/*
* Copyright (c) 2024 ETH Zürich, IT Services
*
* 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.Linq;
using SafeExamBrowser.Core.Contracts.Notifications;
using SafeExamBrowser.Core.Contracts.OperationModel;
using SafeExamBrowser.Core.Contracts.OperationModel.Events;
using SafeExamBrowser.I18n.Contracts;
using SafeExamBrowser.Logging.Contracts;
using SafeExamBrowser.SystemComponents.Contracts;
using SafeExamBrowser.SystemComponents.Contracts.Audio;
using SafeExamBrowser.SystemComponents.Contracts.Keyboard;
using SafeExamBrowser.SystemComponents.Contracts.Network;
using SafeExamBrowser.SystemComponents.Contracts.PowerSupply;
using SafeExamBrowser.UserInterface.Contracts;
using SafeExamBrowser.UserInterface.Contracts.Shell;
using SafeExamBrowser.WindowsApi.Contracts;
namespace SafeExamBrowser.Client.Operations
{
internal class ShellOperation : ClientOperation
{
private readonly IActionCenter actionCenter;
private readonly IAudio audio;
private readonly INotification aboutNotification;
private readonly IKeyboard keyboard;
private readonly ILogger logger;
private readonly INotification logNotification;
private readonly INativeMethods nativeMethods;
private readonly INetworkAdapter networkAdapter;
private readonly IPowerSupply powerSupply;
private readonly ISystemInfo systemInfo;
private readonly ITaskbar taskbar;
private readonly ITaskview taskview;
private readonly IText text;
private readonly IUserInterfaceFactory uiFactory;
public override event ActionRequiredEventHandler ActionRequired { add { } remove { } }
public override event StatusChangedEventHandler StatusChanged;
public ShellOperation(
IActionCenter actionCenter,
IAudio audio,
INotification aboutNotification,
ClientContext context,
IKeyboard keyboard,
ILogger logger,
INotification logNotification,
INativeMethods nativeMethods,
INetworkAdapter networkAdapter,
IPowerSupply powerSupply,
ISystemInfo systemInfo,
ITaskbar taskbar,
ITaskview taskview,
IText text,
IUserInterfaceFactory uiFactory) : base(context)
{
this.aboutNotification = aboutNotification;
this.actionCenter = actionCenter;
this.audio = audio;
this.keyboard = keyboard;
this.logger = logger;
this.logNotification = logNotification;
this.nativeMethods = nativeMethods;
this.networkAdapter = networkAdapter;
this.powerSupply = powerSupply;
this.systemInfo = systemInfo;
this.text = text;
this.taskbar = taskbar;
this.taskview = taskview;
this.uiFactory = uiFactory;
}
public override OperationResult Perform()
{
logger.Info("Initializing shell...");
StatusChanged?.Invoke(TextKey.OperationStatus_InitializeShell);
InitializeSystemComponents();
InitializeActionCenter();
InitializeTaskbar();
InitializeTaskview();
InitializeActivators();
InitializeAlwaysOnState();
return OperationResult.Success;
}
public override OperationResult Revert()
{
logger.Info("Terminating shell...");
StatusChanged?.Invoke(TextKey.OperationStatus_TerminateShell);
TerminateActivators();
TerminateNotifications();
TerminateSystemComponents();
return OperationResult.Success;
}
private void InitializeActivators()
{
foreach (var activator in Context.Activators)
{
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter && activator is IActionCenterActivator actionCenterActivator)
{
actionCenter.Register(actionCenterActivator);
actionCenterActivator.Start();
}
if (Context.Settings.Keyboard.AllowAltTab && activator is ITaskviewActivator taskViewActivator)
{
taskview.Register(taskViewActivator);
taskViewActivator.Start();
}
if (Context.Settings.Security.AllowTermination && activator is ITerminationActivator terminationActivator)
{
terminationActivator.Start();
}
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar && activator is ITaskbarActivator taskbarActivator)
{
taskbar.Register(taskbarActivator);
taskbarActivator.Start();
}
}
}
private void InitializeActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.EnableActionCenter)
{
logger.Info("Initializing action center...");
actionCenter.InitializeText(text);
InitializeApplicationsFor(Location.ActionCenter);
InitializeAboutNotificationForActionCenter();
InitializeAudioForActionCenter();
InitializeClockForActionCenter();
InitializeLogNotificationForActionCenter();
InitializeKeyboardLayoutForActionCenter();
InitializeNetworkForActionCenter();
InitializePowerSupplyForActionCenter();
InitializeQuitButtonForActionCenter();
}
else
{
logger.Info("Action center is disabled, skipping initialization.");
}
}
private void InitializeAlwaysOnState()
{
var display = Context.Settings.Display.AlwaysOn;
var system = Context.Settings.System.AlwaysOn;
nativeMethods.SetAlwaysOnState(display, system);
logger.Info($"Display(s) will {(display ? "be always on" : "use the operating system configuration and may turn off")}.");
logger.Info($"System will {(system ? "be always on" : "use the operating system configuration and may enter sleep mode or standby")}.");
}
private void InitializeTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.EnableTaskbar)
{
logger.Info("Initializing taskbar...");
taskbar.InitializeText(text);
InitializeApplicationsFor(Location.Taskbar);
InitializeAboutNotificationForTaskbar();
InitializeLogNotificationForTaskbar();
InitializePowerSupplyForTaskbar();
InitializeNetworkForTaskbar();
InitializeAudioForTaskbar();
InitializeKeyboardLayoutForTaskbar();
InitializeClockForTaskbar();
InitializeQuitButtonForTaskbar();
}
else
{
logger.Info("Taskbar is disabled, skipping initialization.");
}
}
private void InitializeTaskview()
{
logger.Info("Initializing task view...");
foreach (var application in Context.Applications)
{
taskview.Add(application);
}
}
private void InitializeApplicationsFor(Location location)
{
foreach (var application in Context.Applications)
{
var settings = Context.Settings.Applications.Whitelist.First(a => a.Id == application.Id);
if (settings.ShowInShell)
{
var control = uiFactory.CreateApplicationControl(application, location);
switch (location)
{
case Location.ActionCenter:
actionCenter.AddApplicationControl(control);
break;
case Location.Taskbar:
taskbar.AddApplicationControl(control);
break;
}
}
}
}
private void InitializeSystemComponents()
{
audio.Initialize();
keyboard.Initialize();
networkAdapter.Initialize();
powerSupply.Initialize();
}
private void InitializeAboutNotificationForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowApplicationInfo)
{
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(aboutNotification, Location.ActionCenter));
}
}
private void InitializeAboutNotificationForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowApplicationInfo)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(aboutNotification, Location.Taskbar));
}
}
private void InitializeAudioForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowAudio)
{
actionCenter.AddSystemControl(uiFactory.CreateAudioControl(audio, Location.ActionCenter));
}
}
private void InitializeAudioForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowAudio)
{
taskbar.AddSystemControl(uiFactory.CreateAudioControl(audio, Location.Taskbar));
}
}
private void InitializeClockForActionCenter()
{
actionCenter.ShowClock = Context.Settings.UserInterface.ActionCenter.ShowClock;
}
private void InitializeClockForTaskbar()
{
taskbar.ShowClock = Context.Settings.UserInterface.Taskbar.ShowClock;
}
private void InitializeLogNotificationForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowApplicationLog)
{
actionCenter.AddNotificationControl(uiFactory.CreateNotificationControl(logNotification, Location.ActionCenter));
}
}
private void InitializeLogNotificationForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowApplicationLog)
{
taskbar.AddNotificationControl(uiFactory.CreateNotificationControl(logNotification, Location.Taskbar));
}
}
private void InitializeKeyboardLayoutForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowKeyboardLayout)
{
actionCenter.AddSystemControl(uiFactory.CreateKeyboardLayoutControl(keyboard, Location.ActionCenter));
}
}
private void InitializeKeyboardLayoutForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowKeyboardLayout)
{
taskbar.AddSystemControl(uiFactory.CreateKeyboardLayoutControl(keyboard, Location.Taskbar));
}
}
private void InitializePowerSupplyForActionCenter()
{
if (systemInfo.HasBattery)
{
actionCenter.AddSystemControl(uiFactory.CreatePowerSupplyControl(powerSupply, Location.ActionCenter));
}
}
private void InitializePowerSupplyForTaskbar()
{
if (systemInfo.HasBattery)
{
taskbar.AddSystemControl(uiFactory.CreatePowerSupplyControl(powerSupply, Location.Taskbar));
}
}
private void InitializeQuitButtonForActionCenter()
{
actionCenter.ShowQuitButton = Context.Settings.Security.AllowTermination;
}
private void InitializeQuitButtonForTaskbar()
{
taskbar.ShowQuitButton = Context.Settings.Security.AllowTermination;
}
private void InitializeNetworkForActionCenter()
{
if (Context.Settings.UserInterface.ActionCenter.ShowNetwork)
{
actionCenter.AddSystemControl(uiFactory.CreateNetworkControl(networkAdapter, Location.ActionCenter));
}
}
private void InitializeNetworkForTaskbar()
{
if (Context.Settings.UserInterface.Taskbar.ShowNetwork)
{
taskbar.AddSystemControl(uiFactory.CreateNetworkControl(networkAdapter, Location.Taskbar));
}
}
private void TerminateActivators()
{
foreach (var activator in Context.Activators)
{
activator.Stop();
}
}
private void TerminateNotifications()
{
aboutNotification.Terminate();
logNotification.Terminate();
}
private void TerminateSystemComponents()
{
audio.Terminate();
keyboard.Terminate();
networkAdapter.Terminate();
powerSupply.Terminate();
}
}
}

View File

@@ -0,0 +1,56 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Windows;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SafeExamBrowser.Client")]
[assembly: AssemblyDescription("Safe Exam Browser")]
[assembly: AssemblyCompany("ETH Zürich")]
[assembly: AssemblyProduct("SafeExamBrowser.Client")]
[assembly: AssemblyCopyright("Copyright © 2024 ETH Zürich, IT Services")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// Required for mocking internal contracts with Moq
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("SafeExamBrowser.Client.UnitTests")]
//In order to begin building localizable applications, set
//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file
//inside a <PropertyGroup>. For example, if you are using US english
//in your source files, set the <UICulture> to en-US. Then uncomment
//the NeutralResourceLanguage attribute below. Update the "en-US" in
//the line below to match the UICulture setting in the project file.
//[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)]
[assembly: ThemeInfo(
ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located
//(used if a resource is not found in the page,
// or application resource dictionaries)
ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located
//(used if a resource is not found in the page,
// app, or any theme specific resource dictionaries)
)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.8.0.742")]
[assembly: AssemblyFileVersion("3.8.0.742")]
[assembly: AssemblyInformationalVersion("3.8.0.742")]

View File

@@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SafeExamBrowser.Client.Properties {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SafeExamBrowser.Client.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
}
}

View File

@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

View File

@@ -0,0 +1,26 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SafeExamBrowser.Client.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.6.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
public static Settings Default {
get {
return defaultInstance;
}
}
}
}

View File

@@ -0,0 +1,7 @@
<?xml version='1.0' encoding='utf-8'?>
<SettingsFile xmlns="uri:settings" CurrentProfile="(Default)">
<Profiles>
<Profile Name="(Default)" />
</Profiles>
<Settings />
</SettingsFile>

View File

@@ -0,0 +1,271 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{7CC5A895-E0D3-4E43-9B39-CCEC05A5A6A7}</ProjectGuid>
<OutputType>WinExe</OutputType>
<RootNamespace>SafeExamBrowser.Client</RootNamespace>
<AssemblyName>SafeExamBrowser.Client</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<WarningLevel>4</WarningLevel>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<TargetFrameworkProfile />
</PropertyGroup>
<PropertyGroup>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x86\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x86'">
<OutputPath>bin\x86\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x86</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<ApplicationIcon>SafeExamBrowser.ico</ApplicationIcon>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Debug|x64'">
<DebugSymbols>true</DebugSymbols>
<OutputPath>bin\x64\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<DebugType>full</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release|x64'">
<OutputPath>bin\x64\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<Optimize>true</Optimize>
<DebugType>pdbonly</DebugType>
<PlatformTarget>x64</PlatformTarget>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>true</Prefer32Bit>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Xaml">
<RequiredTargetFramework>4.0</RequiredTargetFramework>
</Reference>
<Reference Include="WindowsBase" />
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
</ItemGroup>
<ItemGroup>
<Compile Include="App.cs" />
<Compile Include="ClientContext.cs" />
<Compile Include="ClientController.cs" />
<Compile Include="Contracts\ICoordinator.cs" />
<Compile Include="Coordinator.cs" />
<Compile Include="Operations\ClientHostDisconnectionOperation.cs" />
<Compile Include="Operations\ClientOperation.cs" />
<Compile Include="Operations\ConfigurationOperation.cs" />
<Compile Include="Operations\Events\ApplicationNotFoundEventArgs.cs" />
<Compile Include="Operations\Events\ApplicationInitializationFailedEventArgs.cs" />
<Compile Include="Operations\Events\ApplicationTerminationEventArgs.cs" />
<Compile Include="Operations\Events\ApplicationTerminationFailedEventArgs.cs" />
<Compile Include="Operations\ProctoringOperation.cs" />
<Compile Include="Operations\RuntimeConnectionOperation.cs" />
<Compile Include="Communication\ClientHost.cs" />
<Compile Include="CompositionRoot.cs" />
<Compile Include="Notifications\AboutNotification.cs" />
<Compile Include="Notifications\LogNotification.cs" />
<Compile Include="Operations\BrowserOperation.cs" />
<Compile Include="Operations\ClipboardOperation.cs" />
<Compile Include="Operations\DisplayMonitorOperation.cs" />
<Compile Include="Operations\KeyboardInterceptorOperation.cs" />
<Compile Include="Operations\MouseInterceptorOperation.cs" />
<Compile Include="Operations\ApplicationOperation.cs" />
<Compile Include="Operations\ServerOperation.cs" />
<Compile Include="Operations\ShellOperation.cs" />
<Compile Include="Properties\AssemblyInfo.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<None Include="app.manifest" />
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
</ItemGroup>
<ItemGroup>
<None Include="App.config">
<SubType>Designer</SubType>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SafeExamBrowser.Applications.Contracts\SafeExamBrowser.Applications.Contracts.csproj">
<Project>{ac77745d-3b41-43e2-8e84-d40e5a4ee77f}</Project>
<Name>SafeExamBrowser.Applications.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Applications\SafeExamBrowser.Applications.csproj">
<Project>{a113e68f-1209-4689-981a-15c554b2df4e}</Project>
<Name>SafeExamBrowser.Applications</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Browser.Contracts\SafeExamBrowser.Browser.Contracts.csproj">
<Project>{5fb5273d-277c-41dd-8593-a25ce1aff2e9}</Project>
<Name>SafeExamBrowser.Browser.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Browser\SafeExamBrowser.Browser.csproj">
<Project>{04E653F1-98E6-4E34-9DD7-7F2BC1A8B767}</Project>
<Name>SafeExamBrowser.Browser</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Communication.Contracts\SafeExamBrowser.Communication.Contracts.csproj">
<Project>{0cd2c5fe-711a-4c32-afe0-bb804fe8b220}</Project>
<Name>SafeExamBrowser.Communication.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Communication\SafeExamBrowser.Communication.csproj">
<Project>{c9416a62-0623-4d38-96aa-92516b32f02f}</Project>
<Name>SafeExamBrowser.Communication</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Configuration.Contracts\SafeExamBrowser.Configuration.Contracts.csproj">
<Project>{7d74555e-63e1-4c46-bd0a-8580552368c8}</Project>
<Name>SafeExamBrowser.Configuration.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Configuration\SafeExamBrowser.Configuration.csproj">
<Project>{C388C4DD-A159-457D-AF92-89F7AD185109}</Project>
<Name>SafeExamBrowser.Configuration</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Core.Contracts\SafeExamBrowser.Core.Contracts.csproj">
<Project>{fe0e1224-b447-4b14-81e7-ed7d84822aa0}</Project>
<Name>SafeExamBrowser.Core.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Core\SafeExamBrowser.Core.csproj">
<Project>{3D6FDBB6-A4AF-4626-BB2B-BF329D44F9CC}</Project>
<Name>SafeExamBrowser.Core</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.I18n.Contracts\SafeExamBrowser.I18n.Contracts.csproj">
<Project>{1858ddf3-bc2a-4bff-b663-4ce2ffeb8b7d}</Project>
<Name>SafeExamBrowser.I18n.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.I18n\SafeExamBrowser.I18n.csproj">
<Project>{10c62628-8e6a-45aa-9d97-339b119ad21d}</Project>
<Name>SafeExamBrowser.I18n</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging.Contracts\SafeExamBrowser.Logging.Contracts.csproj">
<Project>{64ea30fb-11d4-436a-9c2b-88566285363e}</Project>
<Name>SafeExamBrowser.Logging.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Logging\SafeExamBrowser.Logging.csproj">
<Project>{e107026c-2011-4552-a7d8-3a0d37881df6}</Project>
<Name>SafeExamBrowser.Logging</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Monitoring.Contracts\SafeExamBrowser.Monitoring.Contracts.csproj">
<Project>{6d563a30-366d-4c35-815b-2c9e6872278b}</Project>
<Name>SafeExamBrowser.Monitoring.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Monitoring\SafeExamBrowser.Monitoring.csproj">
<Project>{EF563531-4EB5-44B9-A5EC-D6D6F204469B}</Project>
<Name>SafeExamBrowser.Monitoring</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Proctoring.Contracts\SafeExamBrowser.Proctoring.Contracts.csproj">
<Project>{8e52bd1c-0540-4f16-b181-6665d43f7a7b}</Project>
<Name>SafeExamBrowser.Proctoring.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Proctoring\SafeExamBrowser.Proctoring.csproj">
<Project>{3f1f262e-a07c-4513-83c6-d7ef2f203ebf}</Project>
<Name>SafeExamBrowser.Proctoring</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Server.Contracts\SafeExamBrowser.Server.Contracts.csproj">
<Project>{db701e6f-bddc-4cec-b662-335a9dc11809}</Project>
<Name>SafeExamBrowser.Server.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Server\SafeExamBrowser.Server.csproj">
<Project>{46edbde0-58b4-4725-9783-0c55c3d49c0c}</Project>
<Name>SafeExamBrowser.Server</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.Settings\SafeExamBrowser.Settings.csproj">
<Project>{30b2d907-5861-4f39-abad-c4abf1b3470e}</Project>
<Name>SafeExamBrowser.Settings</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.SystemComponents.Contracts\SafeExamBrowser.SystemComponents.Contracts.csproj">
<Project>{903129c6-e236-493b-9ad6-c6a57f647a3a}</Project>
<Name>SafeExamBrowser.SystemComponents.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.SystemComponents\SafeExamBrowser.SystemComponents.csproj">
<Project>{ACEE2EF1-14D2-4B52-8994-5C053055BB51}</Project>
<Name>SafeExamBrowser.SystemComponents</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Contracts\SafeExamBrowser.UserInterface.Contracts.csproj">
<Project>{c7889e97-6ff6-4a58-b7cb-521ed276b316}</Project>
<Name>SafeExamBrowser.UserInterface.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Desktop\SafeExamBrowser.UserInterface.Desktop.csproj">
<Project>{A502DF54-7169-4647-94BD-18B192924866}</Project>
<Name>SafeExamBrowser.UserInterface.Desktop</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Mobile\SafeExamBrowser.UserInterface.Mobile.csproj">
<Project>{89bc24dd-ff31-496e-9816-a160b686a3d4}</Project>
<Name>SafeExamBrowser.UserInterface.Mobile</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.UserInterface.Shared\SafeExamBrowser.UserInterface.Shared.csproj">
<Project>{38525928-87ba-4f8c-8010-4eb97bfaae13}</Project>
<Name>SafeExamBrowser.UserInterface.Shared</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi.Contracts\SafeExamBrowser.WindowsApi.Contracts.csproj">
<Project>{7016f080-9aa5-41b2-a225-385ad877c171}</Project>
<Name>SafeExamBrowser.WindowsApi.Contracts</Name>
</ProjectReference>
<ProjectReference Include="..\SafeExamBrowser.WindowsApi\SafeExamBrowser.WindowsApi.csproj">
<Project>{73724659-4150-4792-A94E-42F5F3C1B696}</Project>
<Name>SafeExamBrowser.WindowsApi</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Resource Include="SafeExamBrowser.ico" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<PropertyGroup>
<PostBuildEvent>robocopy "$(SolutionDir)SafeExamBrowser.Browser\bin\$(PlatformName)\$(ConfigurationName)" "$(TargetDir)\" /e /np
IF %25ERRORLEVEL%25 LSS 8 (
robocopy "$(TargetDir)\" "$(SolutionDir)SafeExamBrowser.Runtime\bin\$(PlatformName)\$(ConfigurationName)" /e /np
IF %25ERRORLEVEL%25 LSS 8 (
EXIT 0
)
)
EXIT 1</PostBuildEvent>
</PropertyGroup>
<PropertyGroup>
<PreBuildEvent>
</PreBuildEvent>
</PropertyGroup>
<Target Name="AfterClean" AfterTargets="Clean">
<RemoveDir Directories="$(TargetDir)" />
</Target>
</Project>

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
</security>
</trustInfo>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of the Windows versions that this application has been tested on and is
is designed to work with. Uncomment the appropriate elements and Windows will
automatically selected the most compatible environment. -->
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
</application>
</compatibility>
<dependency>
<dependentAssembly>
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<!-- IMPORTANT: This dependency is required by CEF (e.g. to render tooltips) -->
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*" />
</dependentAssembly>
</dependency>
</assembly>