/// <summary> /// Runs a child process, synchronously, with the specified input. /// This method should be called by the parent process. It starts the child process, providing it /// with specific command line arguments that will allow the child process to support cancellation /// and error handling. /// The child process should call <see cref="RunAndListenToParentAsync{TInput,TOutput}"/>, provide /// it with the command line arguments and the main method that receives the input object and returns /// an object of type <typeparamref name="TOutput"/>. /// </summary> /// <example> /// Parent process: /// <code> /// private async cTask<OutputData> RunInChildProcess(string childProcessName, InputData input, ITracer tracer, CancellationToken cancellationToken) /// { /// IChildProcessManager childProcessManager = new ChildProcessManager(); /// OutputData output = await childProcessManager.RunChildProcessAsync<OutputData>(childProcessName, input, tracer, cancellationToken); /// return output; /// } /// </code> /// Child process: /// <code> /// public static void Main(string[] args) /// { /// ITracer tracer; /// // Initialize tracer... /// /// IChildProcessManager childProcessManager = new ChildProcessManager(); /// childProcessManager.RunAndListenToParentAsync<InputData, OutputData>(args, MainFunction, tracer).Wait(); /// } /// /// private static OutputData MainFunction(InputData input, CancellationToken cancellationToken) /// { /// // ... /// /// return output; /// } /// </code> /// </example> /// <typeparam name="TOutput">The child process output type</typeparam> /// <param name="exePath">The child process' executable file path</param> /// <param name="input">The child process input</param> /// <param name="cancellationToken">The cancellation token</param> /// <exception cref="InvalidOperationException">The child process could not be started</exception> /// <exception cref="ChildProcessException">The child process failed - see InnerException for details</exception> /// <returns>A <see cref="Task{TResult}"/>, returning the child process output</returns> public async Task <TOutput> RunChildProcessAsync <TOutput>(string exePath, object input, CancellationToken cancellationToken) { this.CurrentStatus = RunChildProcessStatus.Initializing; this.tracer.TraceInformation($"Starting to run child process {exePath}"); // Create a temporary folder for the child process string tempFolder = FileSystemExtensions.CreateTempFolder(TempSubFolderName); this.tracer.TraceInformation($"Created temporary folder for child process: {tempFolder}"); try { // The pipe from the parent to the child is used to pass a cancellation instruction // The pipe from the child to the parent is used to pass the child process output using (AnonymousPipeServerStream pipeParentToChild = new AnonymousPipeServerStream(PipeDirection.Out, HandleInheritability.Inheritable)) { using (AnonymousPipeServerStream pipeChildToParent = new AnonymousPipeServerStream(PipeDirection.In, HandleInheritability.Inheritable)) { using (Process childProcess = new Process()) { // Write the output to the pipe await this.WriteToStream(input, pipeParentToChild, cancellationToken); // Get pipe handles string pipeParentToChildHandle = pipeParentToChild.GetClientHandleAsString(); string pipeChildToParentHandle = pipeChildToParent.GetClientHandleAsString(); // Prepare command line arguments ChildProcessArguments arguments = new ChildProcessArguments( pipeParentToChildHandle, pipeChildToParentHandle, this.tracer.SessionId, this.tracer.GetCustomProperties(), tempFolder); // Setup the child process childProcess.StartInfo = new ProcessStartInfo(exePath) { Arguments = ChildProcessArguments.ToCommandLineArguments(arguments), CreateNoWindow = true, UseShellExecute = false, RedirectStandardError = true }; // Start the child process Stopwatch sw = Stopwatch.StartNew(); childProcess.Start(); this.tracer.TraceInformation($"Started to run child process '{Path.GetFileName(exePath)}', process ID {childProcess.Id}"); this.CurrentStatus = RunChildProcessStatus.WaitingForProcessToExit; this.ChildProcessIds.Add(childProcess.Id); // Dispose the local copy of the client handle pipeParentToChild.DisposeLocalCopyOfClientHandle(); pipeChildToParent.DisposeLocalCopyOfClientHandle(); // Wait for the child process to finish bool wasChildTerminatedByParent = false; MemoryStream outputStream = new MemoryStream(); using (cancellationToken.Register(() => { this.CancelChildProcess(childProcess, pipeParentToChild, ref wasChildTerminatedByParent); })) { // Read the child's output // We do not use the cancellation token here - we want to wait for the child to gracefully cancel await pipeChildToParent.CopyToAsync(outputStream, 2048, default(CancellationToken)); // Ensure the child existed childProcess.WaitForExit(); } this.CurrentStatus = RunChildProcessStatus.Finalizing; sw.Stop(); this.tracer.TraceInformation($"Process {exePath} completed, duration {sw.ElapsedMilliseconds / 1000}s, exit code {childProcess.ExitCode}"); // If the child process was terminated by the parent, throw appropriate exception if (wasChildTerminatedByParent) { throw new ChildProcessTerminatedByParentException(); } // If the child process has exited with an error code, throw appropriate exception if (childProcess.ExitCode != 0) { // This read ignores the cancellation token - if there was a cancellation, the process output will contain the appropriate exception outputStream.Seek(0, SeekOrigin.Begin); string processOutput = await this.ReadFromStream <string>(outputStream, default(CancellationToken)); throw new ChildProcessFailedException(childProcess.ExitCode, processOutput); } // Read the process result from the stream outputStream.Seek(0, SeekOrigin.Begin); TOutput processResult = await this.ReadFromStream <TOutput>(outputStream, cancellationToken); // Return process result this.CurrentStatus = RunChildProcessStatus.Completed; return(processResult); } } } } catch (Exception) { this.CurrentStatus = cancellationToken.IsCancellationRequested ? RunChildProcessStatus.Canceled : RunChildProcessStatus.Failed; throw; } finally { FileSystemExtensions.TryDeleteFolder(tempFolder, this.tracer); FileSystemExtensions.CleanupTempFolders(TempSubFolderName, tracer: this.tracer); } }
/// <summary> /// Raises the <see cref="Application.Startup" /> event. /// </summary> /// <param name="e">A <see cref="StartupEventArgs" /> that contains the event data.</param> protected override void OnStartup(StartupEventArgs e) { // Cleanup previous temp folders (that are at least 2 days old), and create a new temp folder FileSystemExtensions.CleanupTempFolders(TempSubFolderName, 48); tempFolder = FileSystemExtensions.CreateTempFolder(TempSubFolderName); NotificationService notificationService = new NotificationService(); ITracer consoleTracer = new ConsoleTracer(string.Empty); var smartDetectorLoader = new SmartDetectorLoader(tempFolder, consoleTracer); // *Temporary*: if package file path wasn't accepted, raise file selection window to allow package file selection. // This option should be removed before launching version for customers (bug for tracking: 1177247) string smartDetectorPackagePath = e.Args.Length != 1 ? GetSmartDetectorPackagePath() : Diagnostics.EnsureStringNotNullOrWhiteSpace(() => e.Args[0]); SmartDetectorPackage smartDetectorPackage; using (var fileStream = new FileStream(smartDetectorPackagePath, FileMode.Open)) { smartDetectorPackage = SmartDetectorPackage.CreateFromStream(fileStream); } try { SmartDetectorManifest smartDetectorManifest = smartDetectorPackage.Manifest; ISmartDetector detector = smartDetectorLoader.LoadSmartDetector(smartDetectorPackage); // Authenticate the user to Active Directory IAuthenticationServices authenticationServices = new AuthenticationServices(); authenticationServices.AuthenticateUserAsync().Wait(); ICredentialsFactory credentialsFactory = new ActiveDirectoryCredentialsFactory(authenticationServices); IHttpClientWrapper httpClientWrapper = new HttpClientWrapper(); IExtendedAzureResourceManagerClient azureResourceManagerClient = new ExtendedAzureResourceManagerClient(httpClientWrapper, credentialsFactory, consoleTracer); // Create analysis service factory IInternalAnalysisServicesFactory analysisServicesFactory = new AnalysisServicesFactory(consoleTracer, httpClientWrapper, credentialsFactory, azureResourceManagerClient); // Create state repository factory IStateRepositoryFactory stateRepositoryFactory = new EmulationStateRepositoryFactory(); // Load user settings var userSettings = UserSettings.LoadUserSettings(); // Create the detector runner IPageableLogArchive logArchive = new PageableLogArchive(smartDetectorManifest.Name); IEmulationSmartDetectorRunner smartDetectorRunner = new SmartDetectorRunner( detector, analysisServicesFactory, smartDetectorManifest, stateRepositoryFactory, azureResourceManagerClient, logArchive); // Create a Unity container with all the required models and view models registrations Container = new UnityContainer(); Container .RegisterInstance(notificationService) .RegisterInstance <ITracer>(consoleTracer) .RegisterInstance(new AlertsRepository()) .RegisterInstance(authenticationServices) .RegisterInstance(azureResourceManagerClient) .RegisterInstance(detector) .RegisterInstance(smartDetectorManifest) .RegisterInstance(analysisServicesFactory) .RegisterInstance(logArchive) .RegisterInstance(smartDetectorRunner) .RegisterInstance(stateRepositoryFactory) .RegisterInstance(userSettings); } catch (Exception exception) { var message = $"{exception.Message}. {Environment.NewLine}{exception.InnerException?.Message}"; MessageBox.Show(message); System.Diagnostics.Trace.WriteLine(message); Environment.Exit(1); } }