/// <summary> /// Raised when the Chrome process exits /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void _chromeProcess_Exited(object sender, EventArgs e) { try { // ReSharper disable once AccessToModifiedClosure if (_chromeProcess == null) { return; } WriteToLog("Chrome exited unexpectedly, arguments used: " + string.Join(" ", DefaultArguments)); WriteToLog("Process id: " + _chromeProcess.Id); WriteToLog("Process exit time: " + _chromeProcess.ExitTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")); var exception = ExceptionHelpers.GetInnerException(Marshal.GetExceptionForHR(_chromeProcess.ExitCode)); WriteToLog("Exception: " + exception); throw new ChromeException("Chrome exited unexpectedly, " + exception); } catch (Exception exception) { _chromeEventException = exception; if (_chromeProcess != null) { _chromeProcess.Exited -= _chromeProcess_Exited; } _chromeWaitEvent.Set(); } }
/// <summary> /// Opens the <paramref name="inputFile" /> and returns it as an <see cref="WordInterop.Document" /> object /// </summary> /// <param name="inputFile">The file to open</param> /// <param name="repairMode">When true the <paramref name="inputFile" /> is opened in repair mode</param> /// <returns></returns> private WordInterop.Document OpenDocument(string inputFile, bool repairMode) { Logger.WriteToLog($"Opening document '{inputFile}'{(repairMode ? " with repair mode" : string.Empty)}"); try { WordInterop.Document document; var extension = Path.GetExtension(inputFile); if (extension != null && extension.ToUpperInvariant() == ".TXT") { document = _word.Documents.OpenNoRepairDialog(inputFile, false, true, false, "dummy password", Format: WordInterop.WdOpenFormat.wdOpenFormatUnicodeText, OpenAndRepair: repairMode, NoEncodingDialog: true); } else { document = _word.Documents.OpenNoRepairDialog(inputFile, false, true, false, "dummy password", OpenAndRepair: repairMode, NoEncodingDialog: true); } // This will lock or unlock all form fields in a Word document so that auto fill // and date/time field do or don't get updated automatic when converting if (document.Fields.Count > 0) { Logger.WriteToLog("Locking all form fields against modifications"); foreach (WordInterop.Field field in document.Fields) { field.Locked = true; } } Logger.WriteToLog("Document opened"); return(document); } catch (Exception exception) { Logger.WriteToLog( $"ERROR: Failed to open document, exception: '{ExceptionHelpers.GetInnerException(exception)}'"); if (repairMode) { throw new OCFileIsCorrupt("The file '" + Path.GetFileName(inputFile) + "' seems to be corrupt, error: " + ExceptionHelpers.GetInnerException(exception)); } return(OpenDocument(inputFile, true)); } }
/// <summary> /// Converts the <paramref name="inputFile"/> to PDF and saves it as the <paramref name="outputFile"/> /// </summary> /// <param name="inputFile">The Microsoft Office file</param> /// <param name="outputFile">The output file with full path</param> /// <returns>Returns true when the conversion is succesfull, false is retournerd when an exception occurred. /// The exception can be retrieved with the <see cref="GetErrorMessage"/> method</returns> public bool ConvertFromCom(string inputFile, string outputFile) { try { _errorMessage = string.Empty; Convert(inputFile, outputFile); return(true); } catch (Exception exception) { _errorMessage = ExceptionHelpers.GetInnerException(exception); return(false); } }
/// <summary> /// Opens the <paramref name="inputFile"/> and returns it as an <see cref="WordInterop.Document"/> object /// </summary> /// <param name="word">The <see cref="WordInterop.Application"/></param> /// <param name="inputFile">The file to open</param> /// <param name="repairMode">When true the <paramref name="inputFile"/> is opened in repair mode</param> /// <returns></returns> private static WordInterop.Document Open(WordInterop._Application word, string inputFile, bool repairMode) { try { WordInterop.Document document; var extension = Path.GetExtension(inputFile); if (extension != null && extension.ToUpperInvariant() == ".TXT") { document = word.Documents.OpenNoRepairDialog(inputFile, false, true, false, "dummypassword", Format: WordInterop.WdOpenFormat.wdOpenFormatUnicodeText, OpenAndRepair: repairMode, NoEncodingDialog: true); } else { document = word.Documents.OpenNoRepairDialog(inputFile, false, true, false, "dummypassword", OpenAndRepair: repairMode, NoEncodingDialog: true); } // This will lock or unlock all form fields in a Word document so that auto fill // and date/time field do or don't get updated automaticly when converting if (document.Fields.Count > 0) { foreach (WordInterop.Field field in document.Fields) { field.Locked = true; } } return(document); } catch (Exception exception) { if (repairMode) { throw new OCFileIsCorrupt("The file '" + Path.GetFileName(inputFile) + "' seems to be corrupt, error: " + ExceptionHelpers.GetInnerException(exception)); } return(Open(word, inputFile, true)); } }
/// <summary> /// This method will delete the automatic created Resiliency key. Word uses this registry key /// to make entries to corrupted documents. If there are to many entries under this key Word will /// get slower and slower to start. To prevent this we just delete this key when it exists /// </summary> private static void DeleteAutoRecoveryFiles() { try { // HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\Word\Resiliency\DocumentRecovery var version = string.Empty; switch (VersionNumber) { // Word 2003 case 11: version = "11.0"; break; // Word 2017 case 12: version = "12.0"; break; // Word 2010 case 14: version = "14.0"; break; // Word 2013 case 15: version = "15.0"; break; // Word 2016 case 16: version = "16.0"; break; } var key = @"Software\Microsoft\Office\" + version + @"\Word\Resiliency"; if (Registry.CurrentUser.OpenSubKey(key, false) != null) { Registry.CurrentUser.DeleteSubKeyTree(key); } } catch (Exception exception) { EventLog.WriteEntry("OfficeConverter", ExceptionHelpers.GetInnerException(exception), EventLogEntryType.Error); } }
/// <summary> /// Opens the <paramref name="inputFile" /> and returns it as an <see cref="PowerPointInterop.Presentation" /> object /// </summary> /// <param name="inputFile">The file to open</param> /// <param name="repairMode">When true the <paramref name="inputFile" /> is opened in repair mode</param> /// <returns></returns> /// <exception cref="OCFileIsCorrupt"> /// Raised when the <paramref name="inputFile" /> is corrupt and can't be opened in /// repair mode /// </exception> private PowerPointInterop.Presentation OpenPresentation(string inputFile, bool repairMode) { try { return(_powerPoint.Presentations.Open(inputFile, MsoTriState.msoTrue, MsoTriState.msoTrue, MsoTriState.msoFalse)); } catch (Exception exception) { if (repairMode) { throw new OCFileIsCorrupt("The file '" + Path.GetFileName(inputFile) + "' seems to be corrupt, error: " + ExceptionHelpers.GetInnerException(exception)); } return(OpenPresentation(inputFile, true)); } }
/// <summary> /// This method reads the <paramref name="inputFile" /> and when the file is supported it will do the following: <br /> /// - Extract the HTML, RTF (will be converted to html) or TEXT body (in these order) <br /> /// - Puts a header (with the sender, to, cc, etc... (depends on the message type) on top of the body so it looks /// like if the object is printed from Outlook <br /> /// - Reads all the attachents <br /> /// And in the end writes everything to the given <paramref name="outputFolder" /> /// </summary> /// <param name="inputFile">The vcf file</param> /// <param name="outputFolder">The folder where to save the extracted vcf file</param> /// <param name="hyperlinks">When true hyperlinks are clickable, otherwhise they are written as plain text</param> /// <param name="culture"></param> public string[] ExtractToFolderFromCom(string inputFile, string outputFolder, bool hyperlinks = false, string culture = "") { try { if (!string.IsNullOrEmpty(culture)) { SetCulture(culture); } return(ExtractToFolder(inputFile, outputFolder)); } catch (Exception exception) { _errorMessage = ExceptionHelpers.GetInnerException(exception); return(new string[0]); } }
/// <summary> /// If you want to run this code on a server then the following folders must exist, if they don't /// then you can't use Excel to convert files to PDF /// </summary> /// <exception cref="OCConfiguration">Raised when the needed directory could not be created</exception> private static void CheckIfSystemProfileDesktopDirectoryExists() { return; if (Environment.Is64BitOperatingSystem) { var x64DesktopPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"SysWOW64\config\systemprofile\desktop"); if (!Directory.Exists(x64DesktopPath)) { try { Directory.CreateDirectory(x64DesktopPath); } catch (Exception exception) { throw new OCConfiguration("Can't create folder '" + x64DesktopPath + "' Excel needs this folder to work on a server, error: " + ExceptionHelpers.GetInnerException(exception)); } } } var x86DesktopPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), @"System32\config\systemprofile\desktop"); if (!Directory.Exists(x86DesktopPath)) { try { Directory.CreateDirectory(x86DesktopPath); } catch (Exception exception) { throw new OCConfiguration("Can't create folder '" + x86DesktopPath + "' Excel needs this folder to work on a server, error: " + ExceptionHelpers.GetInnerException(exception)); } } }
/// <summary> /// This method will delete the automatic created Resiliency key. PowerPoint uses this registry key /// to make entries to corrupted presentations. If there are to many entries under this key PowerPoint will /// get slower and slower to start. To prevent this we just delete this key when it exists /// </summary> private void DeleteResiliencyKeys() { Logger.WriteToLog("Deleting PowerPoint resiliency keys from the registry"); try { // HKEY_CURRENT_USER\Software\Microsoft\Office\14.0\PowerPoint\Resiliency\DocumentRecovery var key = $@"Software\Microsoft\Office\{_versionNumber}.0\PowerPoint\Resiliency"; if (Registry.CurrentUser.OpenSubKey(key, false) != null) { Registry.CurrentUser.DeleteSubKeyTree(key); Logger.WriteToLog("Resiliency keys deleted"); } else { Logger.WriteToLog("There are no keys to delete"); } } catch (Exception exception) { Logger.WriteToLog($"Failed to delete resiliency keys, error: {ExceptionHelpers.GetInnerException(exception)}"); } }
/// <summary> /// Extract the given <paramref name="inputFile"/> to the given <paramref name="outputFolder"/> /// </summary> /// <param name="inputFile">The input file</param> /// <param name="outputFolder">The folder where to save the extracted web archive</param> /// <param name="options"><see cref="ExtractorOptions"/></param> /// <param name="logStream">When set then logging is written to this stream</param> /// <returns></returns> /// <exception cref="WAEInvalidFile">Raised when a required resource is not found in the web archive</exception> /// <exception cref="WAEResourceMissing">Raised when a required resource is not found in the web archive</exception> /// <exception cref="FileNotFoundException">Raised when the <paramref name="inputFile"/> is not found</exception> /// <exception cref="DirectoryNotFoundException">Raised when the <paramref name="outputFolder"/> does not exist</exception> public string Extract(string inputFile, string outputFolder, ExtractorOptions options = ExtractorOptions.None, Stream logStream = null) { if (logStream != null) { Logger.LogStream = logStream; } try { if (!Directory.Exists(outputFolder)) { throw new DirectoryNotFoundException($"The output folder '{outputFolder}' does not exist"); } var reader = new PList.BinaryPlistReader(); IDictionary archive; try { archive = reader.ReadObject(inputFile); } catch (Exception exception) { throw new WAEInvalidFile($"The file '{inputFile}' is not a valid Safari web archive", exception); } if (!archive.Contains(WebMainResource)) { var message = $"Can't find the resource '{WebMainResource}' in the webarchive"; Logger.WriteToLog(message); throw new WAEResourceMissing(message); } var mainResource = (IDictionary)archive[WebMainResource]; var webPageFileName = Path.Combine(outputFolder, "webpage.html"); Logger.WriteToLog($"Getting main web page from '{WebMainResource}'"); var webPage = ProcessMainResource(mainResource, out var mainUri); #if (DEBUG) File.WriteAllText(webPageFileName, webPage); #endif if (!archive.Contains(WebSubresources)) { Logger.WriteToLog("Web archive does not contain any sub resources"); } else { var subResources = (object[])archive[WebSubresources]; var count = subResources.Length; Logger.WriteToLog($"Web archive has {count} sub resource{(count > 1 ? "s" : string.Empty)}"); foreach (IDictionary subResource in subResources) { ProcessSubResources(subResource, outputFolder, mainUri, options, ref webPage); } } if (!archive.Contains(WebSubframeArchives)) { Logger.WriteToLog("Web archive does not contain any sub frame archives"); } else { var subFrameResources = (object[])archive[WebSubframeArchives]; var subFrameResourcesCount = subFrameResources.Length; Logger.WriteToLog($"Web archive has {subFrameResourcesCount} sub frame resource{(subFrameResourcesCount > 1 ? "s" : string.Empty)}"); var i = 1; foreach (IDictionary subFrameResource in subFrameResources) { var subFrameMainResource = (IDictionary)subFrameResource[WebMainResource]; Logger.WriteToLog($"Getting web page from sub frame resource '{WebMainResource}'"); var subFrameResourceWebPage = ProcessSubFrameMainResource(subFrameMainResource, out var frameName, out var subFrameMainUri); var subFrameOutputFolder = Path.Combine(outputFolder, $"subframe_{i}"); Logger.WriteToLog($"Creating folder '{subFrameOutputFolder}' for iframe '{frameName}' content"); Directory.CreateDirectory(subFrameOutputFolder); i += 1; var subFrameSubResources = (object[])subFrameResource[WebSubresources]; if (subFrameSubResources == null) { Logger.WriteToLog("Web archive sub frame does not contain any sub resources"); } else { var subFrameSubResourcesCount = subFrameSubResources.Length; Logger.WriteToLog($"Web archive sub frame has {subFrameSubResourcesCount} sub resource{(subFrameSubResourcesCount > 1 ? "s" : string.Empty)}"); foreach (IDictionary subFrameSubResource in subFrameSubResources) { ProcessSubResources(subFrameSubResource, subFrameOutputFolder, subFrameMainUri, options, ref subFrameResourceWebPage); } } var subFrameWebPageFileName = Path.Combine(subFrameOutputFolder, "webpage.html"); var subFrameWebPageRelativeUri = $"subframe_{i}/webpage.html"; var subFrameUri = subFrameMainUri.ToString(); var subFrameUriWithoutScheme = subFrameUri.Replace($"{subFrameMainUri.Scheme}:", string.Empty); var subFrameUriWithoutMainUri = subFrameUri.Replace($"{mainUri.Scheme}://{mainUri.Host}{mainUri.AbsolutePath}", string.Empty); if (webPage.Contains(subFrameUri)) { Logger.WriteToLog($"Replacing '{subFrameUri}' with '{subFrameWebPageRelativeUri}'"); webPage = webPage.Replace(subFrameUri, subFrameWebPageRelativeUri); } else if (webPage.Contains(subFrameUriWithoutScheme)) { Logger.WriteToLog($"Replacing '{subFrameUriWithoutScheme}' with '{subFrameWebPageRelativeUri}'"); webPage = webPage.Replace(subFrameUriWithoutScheme, $"{subFrameWebPageRelativeUri}"); } else if (webPage.Contains(subFrameUriWithoutMainUri)) { Logger.WriteToLog($"Replacing '{subFrameUriWithoutMainUri}' with '{subFrameWebPageRelativeUri}'"); webPage = webPage.Replace(subFrameUriWithoutMainUri, $"{subFrameWebPageRelativeUri}"); } else { Logger.WriteToLog($"Could not find any resources with url '{subFrameUri}' in the web page"); } File.WriteAllText(subFrameWebPageFileName, subFrameResourceWebPage); } } File.WriteAllText(webPageFileName, webPage); return(webPageFileName); } catch (Exception exception) { Logger.WriteToLog(ExceptionHelpers.GetInnerException(exception)); throw; } }
/// <summary> /// Opens the <paramref name="inputFile"/> and returns it as an <see cref="ExcelInterop.Workbook"/> object /// </summary> /// <param name="excel">The <see cref="ExcelInterop.Application"/></param> /// <param name="inputFile">The file to open</param> /// <param name="extension">The file extension</param> /// <param name="repairMode">When true the <paramref name="inputFile"/> is opened in repair mode</param> /// <returns></returns> /// <exception cref="OCCsvFileLimitExceeded">Raised when a CSV <paramref name="inputFile"/> has to many rows</exception> private static ExcelInterop.Workbook Open(ExcelInterop._Application excel, string inputFile, string extension, bool repairMode) { try { switch (extension.ToUpperInvariant()) { case ".CSV": var count = File.ReadLines(inputFile).Count(); var excelMaxRows = MaxRows; if (count > excelMaxRows) { throw new OCCsvFileLimitExceeded("The input CSV file has more then " + excelMaxRows + " rows, the installed Excel version supports only " + excelMaxRows + " rows"); } string separator; ExcelInterop.XlTextQualifier textQualifier; GetCsvSeperator(inputFile, out separator, out textQualifier); switch (separator) { case ";": excel.Workbooks.OpenText(inputFile, Type.Missing, Type.Missing, ExcelInterop.XlTextParsingType.xlDelimited, textQualifier, true, false, true); return(excel.ActiveWorkbook); case ",": excel.Workbooks.OpenText(inputFile, Type.Missing, Type.Missing, ExcelInterop.XlTextParsingType.xlDelimited, textQualifier, Type.Missing, false, false, true); return(excel.ActiveWorkbook); case "\t": excel.Workbooks.OpenText(inputFile, Type.Missing, Type.Missing, ExcelInterop.XlTextParsingType.xlDelimited, textQualifier, Type.Missing, true); return(excel.ActiveWorkbook); case " ": excel.Workbooks.OpenText(inputFile, Type.Missing, Type.Missing, ExcelInterop.XlTextParsingType.xlDelimited, textQualifier, Type.Missing, false, false, false, true); return(excel.ActiveWorkbook); default: excel.Workbooks.OpenText(inputFile, Type.Missing, Type.Missing, ExcelInterop.XlTextParsingType.xlDelimited, textQualifier, Type.Missing, false, true); return(excel.ActiveWorkbook); } default: if (repairMode) { return(excel.Workbooks.Open(inputFile, false, true, Password: "******", IgnoreReadOnlyRecommended: true, AddToMru: false, CorruptLoad: ExcelInterop.XlCorruptLoad.xlRepairFile)); } return(excel.Workbooks.Open(inputFile, false, true, Password: "******", IgnoreReadOnlyRecommended: true, AddToMru: false)); } } catch (COMException comException) { if (comException.ErrorCode == -2146827284) { throw new OCFileIsPasswordProtected("The file '" + Path.GetFileName(inputFile) + "' is password protected"); } throw new OCFileIsCorrupt("The file '" + Path.GetFileName(inputFile) + "' could not be opened, error: " + ExceptionHelpers.GetInnerException(comException)); } catch (Exception exception) { if (repairMode) { throw new OCFileIsCorrupt("The file '" + Path.GetFileName(inputFile) + "' could not be opened, error: " + ExceptionHelpers.GetInnerException(exception)); } return(Open(excel, inputFile, extension, true)); } }
/// <summary> /// Returns a dictionary with all the property mappings /// </summary> /// <param name="propertyIdents">List with all the named property idents, e.g 8005, 8006, 801C, etc...</param> /// <returns></returns> internal List <MapiTagMapping> GetMapping(IEnumerable <string> propertyIdents) { var result = new List <MapiTagMapping>(); var entryStreamBytes = GetStreamBytes(MapiTags.EntryStream); var stringStreamBytes = GetStreamBytes(MapiTags.StringStream); if (entryStreamBytes.Length == 0) { return(result); } foreach (var propertyIdent in propertyIdents) { try { // To read the correct mapped property we need to calculate the offset in the entry stream // The offset is calculated bij subtracting 32768 (8000 hex) from the named property and // multiply the outcome with 8 var identValue = ushort.Parse(propertyIdent, NumberStyles.HexNumber); var entryOffset = (identValue - 32768) * 8; if (entryOffset > entryStreamBytes.Length) { continue; } string entryIdentString; // We need the first 2 bytes for the mapping, but because the nameStreamBytes is in little // endian we need to swap the first 2 bytes if (entryStreamBytes[entryOffset + 1] == 0) { var entryIdent = new[] { entryStreamBytes[entryOffset] }; entryIdentString = BitConverter.ToString(entryIdent).Replace("-", string.Empty); } else { var entryIdent = new[] { entryStreamBytes[entryOffset + 1], entryStreamBytes[entryOffset] }; entryIdentString = BitConverter.ToString(entryIdent).Replace("-", string.Empty); } var stringOffset = ushort.Parse(entryIdentString, NumberStyles.HexNumber); if (stringOffset >= stringStreamBytes.Length) { //Debug.Print($"{propertyIdent} - {entryIdentString}"); result.Add(new MapiTagMapping(propertyIdent, entryIdentString)); continue; } // Read the first 4 bytes to determine the length of the string to read var stringLength = 0; var len = stringStreamBytes.Length - stringOffset; if (len == 1) { var bytes = new byte[1]; Buffer.BlockCopy(stringStreamBytes, stringOffset, bytes, 0, len); stringLength = bytes[0]; } if (len == 2) { stringLength = BitConverter.ToInt16(stringStreamBytes, stringOffset); } if (len == 3) { var bytes = new byte[3]; Buffer.BlockCopy(stringStreamBytes, stringOffset, bytes, 0, len); stringLength = Bytes2Int(bytes[2], bytes[1], bytes[0]); } else if (len >= 4) { stringLength = BitConverter.ToInt32(stringStreamBytes, stringOffset); } if (stringOffset + stringLength >= stringStreamBytes.Length) { result.Add(new MapiTagMapping(propertyIdent, entryIdentString)); continue; } var str = string.Empty; // Skip 4 bytes and start reading the string stringOffset += 4; for (var i = stringOffset; i < stringOffset + stringLength; i += 2) { var chr = BitConverter.ToChar(stringStreamBytes, i); str += chr; } // Remove any null character str = str.Replace("\0", string.Empty); result.Add(new MapiTagMapping(str, propertyIdent, true)); } catch (Exception exception) { Logger.WriteToLog(ExceptionHelpers.GetInnerException(exception)); throw; } } return(result); }
/// <summary> /// Start Chrome headless with the debugger set to the given port /// </summary> /// <remarks> /// If Chrome is already running then this step is skipped /// </remarks> /// <exception cref="ChromeException"></exception> private void StartChromeHeadless() { if (IsChromeRunning) { return; } var starting = true; var userName = string.Empty; if (_userName.Contains("\\")) { userName = _userName.Split('\\')[1]; } var domain = _userName.Split('\\')[0]; WriteToLog($"Starting Chrome from location {_chromeExeFileName}"); WriteToLog($"{_chromeExeFileName} {string.Join(" ", DefaultArguments)}"); _chromeProcess = new Process(); var processStartInfo = new ProcessStartInfo { FileName = _chromeExeFileName, Arguments = string.Join(" ", DefaultArguments), UseShellExecute = false, CreateNoWindow = true, ErrorDialog = false, WindowStyle = ProcessWindowStyle.Hidden, // ReSharper disable once AssignNullToNotNullAttribute WorkingDirectory = Path.GetDirectoryName(_chromeExeFileName), RedirectStandardOutput = true, RedirectStandardError = true }; if (!string.IsNullOrWhiteSpace(_userName)) { WriteToLog($"Starting Chrome with user '{userName}' on domain '{domain}'"); processStartInfo.Domain = domain; processStartInfo.UserName = userName; var secureString = new SecureString(); foreach (var t in _password) { secureString.AppendChar(t); } processStartInfo.Password = secureString; } _chromeProcess.StartInfo = processStartInfo; var waitEvent = new ManualResetEvent(false); _chromeProcess.OutputDataReceived += (sender, args) => { if (!string.IsNullOrWhiteSpace(args.Data)) { WriteToLog($"Error: {args.Data}"); } }; _chromeProcess.ErrorDataReceived += (sender, args) => { if (args.Data == null) { return; } if (args.Data.StartsWith("DevTools listening on")) { // DevTools listening on ws://127.0.0.1:50160/devtools/browser/53add595-f351-4622-ab0a-5a4a100b3eae _browser = new Browser(new Uri(args.Data.Replace("DevTools listening on ", string.Empty))); WriteToLog("Connected to dev protocol"); waitEvent.Set(); } else if (!string.IsNullOrWhiteSpace(args.Data)) { WriteToLog($"Error: {args.Data}"); } }; _chromeProcess.Exited += (sender, args) => { // ReSharper disable once AccessToModifiedClosure if (!starting) { return; } WriteToLog("Chrome exited unexpectedly, arguments used: " + string.Join(" ", DefaultArguments)); WriteToLog("Process id: " + _chromeProcess.Id); WriteToLog("Process exit time: " + _chromeProcess.ExitTime.ToString("yyyy-MM-ddTHH:mm:ss.fff")); var exception = ExceptionHelpers.GetInnerException(Marshal.GetExceptionForHR(_chromeProcess.ExitCode)); WriteToLog("Exception: " + exception); throw new ChromeException("Chrome exited unexpectedly, " + exception); }; _chromeProcess.EnableRaisingEvents = true; _chromeProcess.Start(); WriteToLog("Chrome process started"); _chromeProcess.BeginErrorReadLine(); _chromeProcess.BeginOutputReadLine(); if (_conversionTimeout.HasValue) { waitEvent.WaitOne(_conversionTimeout.Value); } else { waitEvent.WaitOne(); } starting = false; WriteToLog("Chrome started"); }
/// <summary> /// Start Chrome headless with the debugger set to the given port /// </summary> /// <remarks> /// If Chrome is already running then this step is skipped /// </remarks> /// <exception cref="ChromeException"></exception> private void StartChromeHeadless() { if (IsChromeRunning()) { return; } WriteToLog($"Starting Chrome from location {_chromeExeFileName}"); _chromeProcess = new Process(); var processStartInfo = new ProcessStartInfo { FileName = _chromeExeFileName, Arguments = string.Join(" ", _defaultArguments), UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, }; if (!string.IsNullOrWhiteSpace(_userName)) { var userName = string.Empty; if (_userName.Contains("\\")) { userName = _userName.Split('\\')[1]; } var domain = _userName.Split('\\')[0]; WriteToLog($"Starting Chrome with user '{userName}' on domain '{domain}'"); processStartInfo.Domain = domain; processStartInfo.UserName = userName; var secureString = new SecureString(); foreach (var t in _password) { secureString.AppendChar(t); } processStartInfo.Password = secureString; } _chromeProcess.StartInfo = processStartInfo; var waitEvent = new ManualResetEvent(false); _chromeProcess.ErrorDataReceived += (sender, args) => { if (args.Data == null) { return; } if (args.Data.StartsWith("DevTools listening on")) { // DevTools listening on ws://127.0.0.1:50160/devtools/browser/53add595-f351-4622-ab0a-5a4a100b3eae _communicator = new Browser(new Uri(args.Data.Replace("DevTools listening on ", string.Empty))); WriteToLog("Connected to dev protocol"); waitEvent.Set(); } else if (!string.IsNullOrWhiteSpace(args.Data)) { WriteToLog($"Error: {args.Data}"); } }; _chromeProcess.Exited += (sender, args) => { WriteToLog("Chrome process: " + _chromeExeFileName); WriteToLog("Arguments used: " + string.Join(" ", _defaultArguments)); var exception = ExceptionHelpers.GetInnerException(Marshal.GetExceptionForHR(_chromeProcess.ExitCode)); WriteToLog("Exception: " + exception); throw new ChromeException("Could not start Chrome, " + exception); }; _chromeProcess.Start(); _chromeProcess.BeginErrorReadLine(); waitEvent.WaitOne(); WriteToLog("Chrome started"); }
/// <summary> /// Start Chrome headless /// </summary> /// <remarks> /// If Chrome is already running then this step is skipped /// </remarks> /// <exception cref="ChromeException"></exception> public void EnsureRunning() { lock (mutex) { if (IsChromeRunning) { WriteToLog($"Chrome is already running on PID {_chromeProcess.Id}... skipped"); return; } _chromeEventException = null; var workingDirectory = Path.GetDirectoryName(_chromeExeFileName); WriteToLog( $"Starting Chrome from location '{_chromeExeFileName}' with working directory '{workingDirectory}'"); WriteToLog($"\"{_chromeExeFileName}\" {string.Join(" ", DefaultArguments)}"); _chromeProcess = new Process(); var processStartInfo = new ProcessStartInfo { FileName = _chromeExeFileName, Arguments = string.Join(" ", DefaultArguments), UseShellExecute = false, CreateNoWindow = true, ErrorDialog = false, WindowStyle = ProcessWindowStyle.Hidden, // ReSharper disable once AssignNullToNotNullAttribute WorkingDirectory = workingDirectory, RedirectStandardOutput = true, RedirectStandardError = true }; try { processStartInfo.LoadUserProfile = false; if (!string.IsNullOrWhiteSpace(_userName)) { var userName = string.Empty; var domain = string.Empty; if (_userName.Contains("\\")) { userName = _userName.Split('\\')[1]; domain = _userName.Split('\\')[0]; } WriteToLog($"Starting Chrome with username '{userName}' on domain '{domain}'"); processStartInfo.Domain = domain; processStartInfo.UserName = userName; var secureString = new SecureString(); foreach (var t in _password) { secureString.AppendChar(t); } processStartInfo.Password = secureString; processStartInfo.LoadUserProfile = true; } } catch (Exception ex) { _logger?.LogWarning($"Failed to set user info {ex.Message}"); } processStartInfo.Environment[UniqueEnviromentKey] = UniqueEnviromentKey; _chromeProcess.StartInfo = processStartInfo; _chromeWaitEvent = new ManualResetEvent(false); _chromeProcess.OutputDataReceived += _chromeProcess_OutputDataReceived; _chromeProcess.ErrorDataReceived += _chromeProcess_ErrorDataReceived; _chromeProcess.Exited += _chromeProcess_Exited; _chromeProcess.EnableRaisingEvents = true; try { _chromeProcess.Start(); } catch (Exception exception) { WriteToLog("Could not start the Chrome process due to the following reason: " + ExceptionHelpers.GetInnerException(exception)); throw; } WriteToLog("Chrome process started"); _chromeProcess.BeginErrorReadLine(); _chromeProcess.BeginOutputReadLine(); if (_conversionTimeout.HasValue) { _chromeWaitEvent.WaitOne(_conversionTimeout.Value); } else { _chromeWaitEvent.WaitOne(); } if (_chromeEventException != null) { WriteToLog("Exception: " + ExceptionHelpers.GetInnerException(_chromeEventException)); throw _chromeEventException; } WriteToLog("Chrome started"); } }
/// <summary> /// Stops Word /// </summary> private void StopWord() { if (IsWordRunning) { Logger.WriteToLog("Stopping Word"); try { _word.Quit(false); } catch (Exception exception) { Logger.WriteToLog($"Word did not shutdown gracefully, exception: {ExceptionHelpers.GetInnerException(exception)}"); } var counter = 0; // Give Word 2 seconds to close while (counter < 200) { if (!IsWordRunning) { break; } counter++; Thread.Sleep(10); } if (IsWordRunning) { Logger.WriteToLog($"Word did not shutdown gracefully in 2 seconds ... killing it on process id {_wordProcess.Id}"); _wordProcess.Kill(); Logger.WriteToLog("Word process killed"); } else { Logger.WriteToLog("Word stopped"); } } if (_word != null) { Marshal.ReleaseComObject(_word); _word = null; } _wordProcess = null; GC.Collect(); GC.WaitForPendingFinalizers(); }
/// <summary> /// Stops PowerPoint /// </summary> private void StopPowerPoint() { if (IsPowerPointRunning) { Logger.WriteToLog("Stopping PowerPoint"); try { _powerPoint.Quit(); } catch (Exception exception) { Logger.WriteToLog($"PowerPoint did not shutdown gracefully, exception: {ExceptionHelpers.GetInnerException(exception)}"); } var counter = 0; // Give PowerPoint 2 seconds to close while (counter < 200) { if (!IsPowerPointRunning) { break; } counter++; Thread.Sleep(10); } if (IsPowerPointRunning) { Logger.WriteToLog( $"PowerPoint did not shutdown gracefully in 2 seconds ... killing it on process id {_powerPointProcess.Id}"); _powerPointProcess.Kill(); Logger.WriteToLog("PowerPoint process killed"); } else { Logger.WriteToLog("PowerPoint stopped"); } } if (_powerPoint != null) { Marshal.ReleaseComObject(_powerPoint); _powerPoint = null; } _powerPointProcess = null; GC.Collect(); GC.WaitForPendingFinalizers(); }