CefSharp crashing in SharePoint Timer Job - c#

I am attempting to schedule a CefSharp rendering of pages stored in SharePoint via a SharePoint Timer Job. However, the OWSTIMER.exe service crashes almost immediately after calling Cef.Initialize(), throwing next to no errors.
The Subscription Class is:
internal class Subscriptions: IDisposable
{
internal Subscriptions()
{
db = // CreateConnection();
if (Cef.IsInitialized == false)
{
var settings = new CefSettings
{
CachePath = "cache",
BrowserSubprocessPath = "ProjectBin\\CefSharp.BrowserSubprocess.exe",
LogSeverity = LogSeverity.Default,
};
Cef.Initialize(settings);
}
}
internal void RunTodaysSubscriptions()
{
var subs = db.tt_ScheduledJobs.Where(sj => sj.Date <= DateTime.Today).Select(sj => sj.tt_Subscription).ToList();
foreach (var sub in subs)
{
ExecuteSubscription(sub);
SetNextSchedule(sub);
}
}
private void ExecuteSubscription(tt_Subscription subscription)
{
tt_JobHistory historyEntry = new tt_JobHistory();
historyEntry.SubscriptionKey = subscription.SubscriptionKey;
historyEntry.StartDate = DateTime.Now;
historyEntry.Status = "In Progress";
dfe.tt_JobHistory.Add(historyEntry);
dfe.SaveChanges();
string url = //path to file
using (SubscriptionExecution se = new SubscriptionExecution(
url,
subscription.SubscriptionKey,
subscription.Format))
{
//Completed represents either a response from the dashboard that the export either succeded
//or failed. If there is no response (i.e. a javascript error preventing the dashboard from executing
//an export, then we would hit the timeout of 5 minutes and shutdown this process.
if (SpinWait.SpinUntil(() => se.Completed == true, 120000) == true)
{
//Had a response from dashboard, could be a success or failure
if (se.ServerModel.Success == true) SuccessHistoryEntry(historyEntry);
else ErrorHistoryEntry(se.ServerModel.Message, historyEntry);
}
else
{
ErrorHistoryEntry("Process timed out generating subscription", historyEntry);
}
}
db.SaveChanges();
}
// ...
}
SubscriptionExecution is:
internal class SubscriptionExecution : IDisposable
{
// ...
internal SubscriptionExecution(string url, int subscriptionId, string fileType, string logLocation)
{
LogLocation = logLocation;
SubscriptionId = subscriptionId;
FileType = fileType;
Completed = false;
ServerModel = new ServerModel();
ServerModel.PropertyChanged += new PropertyChangedEventHandler(ServerModel_PropertyChanged);
browser = new ChromiumWebBrowser(url);
browser.RegisterJsObject("serverModel", ServerModel);
browser.LoadingStateChanged += LoadingStateChanged;
if (LogLocation != string.Empty) browser.ConsoleMessage += BrowserConsoleMessage;
}
private void ServerModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Success")
{
Completed = true;
}
}
// ...
}
There's a loadingStateChange function that injects some javascript that modifies the ServerModel.Success field when an event happens.
However, I can't seem to get this far. Except in one specific circumstance, the timerjob reaches the Cef.Initialize(settings) section, and then immediately restarts. Unfortunately, outside the event log, there is no error message to be found.
In the Windows Event Log, there is
which is indicating an error in libcef.dll
I've installed the libcef.dll.pdb symbols for this version of cefsharp (v57).
The CefSharp troubleshooting guide suggests that in instances like this, there should be logfiles or error dumps. There should be a file 'debug.log' in the folder with the executable, and perhaps a mini crashdump file in AppData\Local\CrashDumps.
However, as this is a SharePoint Timer job, these files don't seem to be appearing. I did find a debug.log file inside the 15 hive bin (C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\BIN); however this file is empty.
I can't find the CrashDumps folder anywhere; checked under all user accounts on the machine, especially the running account and the SharePoint Timer Job managed account.
The one specific circumstance that I can get the code to run without restarting the timer job is immediately after the server is restarted. Restarting the server has (temporarily) fixed a few other problems around this issue, and so I suspect it might be related. Namely, I have had trouble retracting and redeploying the farm solution. This throws a couple of different errors:
on retracting: <SERVER>: The process cannot access the file 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\BIN...\icudtl.dat' because it is being used by another process.
Error: The removal of this file failed: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\BIN...\icudtl.dat.
on deploying: The requested operation cannot be performed on a file with a user-mapped section open.
I've attempted to find what other process is using icudtl.dat using Process Explorer, but all I could find was chrome, which was accessing a different copy. Killing all chrome processes and trying again also did not solve the problem.

Related

ServerManager fails with 0x80040154 in c# Winforms app creating simple web application in IIS

I am writing a small app that installs IIS and configures a website before deploying the files to it.
On a freshly reset Windows 10, the first attempt always fails with the 0x80040154 COM+ component failure as documented in This question
I looked at the version I am using and it is the latest and correct one for .net standard (4.8) and not the one meant for .net core
When I press the button to rerun the function it always finishes correctly. I tried using a retry routine, and it fails on each retry, yet runs fine again when the button is pressed. The reason for this I assume is that the server manager object isn't disposed when it hits the catch block since its in a using statement.
I can work around that, but I really want to understand the issue and make a permanent fix.
My routine simply creates a website in IIS and creates an app pool to assign to it.
And it is running with elevated privileges
For reference:
Machine is Windows 10 latest from the downloadable media creator.
Microsoft.Web.Administrator version is 7.0.0.0
App is .net 4.8 standard windows forms
using (var serverManager = new ServerManager())
{
string iisrootdir = drive;
//Check for inetpub/wwwroot
if (!Directory.Exists(iisrootdir)) //Check for Drive D
{
iisrootdir = #"C:\";
}
string iiscmsdir = Path.Combine(iisrootdir, "webdir", "appdir");
if (!Directory.Exists(iiscmsdir))
Directory.CreateDirectory(iiscmsdir);
var settings = new ApplicationSettings();
settings.ReadFromFile();
settings.CMSPATH = iiscmsdir;
settings.SaveToFile();
try
{
string poolName = "DefaultAppPool";
if (serverManager.Sites.Count > 0)
{
Site myDefualtWebsite = serverManager.Sites[0];
if (myDefualtWebsite != null)
{
OnRaiseInstallEvent(new InstallEventArgs("CreateWebsite", ProcessState.Started,
"Remove Default Website"));
serverManager.Sites.Remove(myDefualtWebsite);
serverManager.CommitChanges();
}
}
if (!WebsiteExists("sitename"))
{
mySite.ServerAutoStart = true;
}
Site site = serverManager.Sites["sitename"];
if (!AppPoolExists(poolName))
{
serverManager.ApplicationPools.Add(poolName);
}
ApplicationPool apppool = serverManager.ApplicationPools[poolName];
apppool.ManagedPipelineMode = ManagedPipelineMode.Integrated;
apppool.ManagedRuntimeVersion = "";
serverManager.Sites["sitename"].ApplicationDefaults.ApplicationPoolName = poolName;
foreach (var item in serverManager.Sites["sitename"].Applications)
{
item.ApplicationPoolName = poolName;
}
serverManager.CommitChanges();
apppool.Recycle();
serverManager.CommitChanges();
}
catch (Exception ex)
{
if (ex.Message.Contains("80040154") && errorCount < 4)
{
if (serverManager != null)
serverManager.Dispose();
errorCount++;
OnRaiseInstallEvent(new InstallEventArgs("CreateWebsite", ProcessState.Started,
"Error encountered with COM+ object, trying again: " + errorCount));
CreateWebsite(#"D:\");
}
else
{
if (serverManager != null)
serverManager.Dispose();
errorCount = 0;
OnRaiseErrorEvent(new InstallErrorEventArgs("CreateWebsite", ProcessState.Error, ex));
return false;
}
}
finally
{
serverManager?.Dispose();
}
Thanks for the help Guys. I found the problem.
DISM was running in its own thread. The Process object exited the moment it launched. My function was then attempting to configure IIS before it had finished installing.

How can I open a .pdf file in the browser from a Xamarin UWP project?

I have a Xamarin Project where I generate a .pdf file from scratch and save it in my local storage. This works perfectly fine and can find it and open it in the disk where I saved it. However, I need to open the .pdf file immediately after creation programmatically.
I already tried different variations using Process and ProcessStartInfo but these just throw errors like "System.ComponentModel.Win32Exception: 'The system cannot find the file specified'" and "'System.PlatformNotSupportedException'".
This is basically the path I am trying to open using Process.
var p = Process.Start(#"cmd.exe", "/c start " + #"P:\\Receiving inspection\\Inspection Reports\\" + timestamp + ".pdf");
I also tried ProcessStartInfo using some variations but I'm getting the same errors all over and over.
var p = new Process();
p.StartInfo = new ProcessStartInfo(#"'P:\\Receiving inspection\\Inspection Reports\\'" + timestamp + ".pdf");
p.Start();
The better way is that use LaunchFileAsync method to open file with browser. You could create FileLauncher DependencyService to invoke uwp LaunchFileAsync method from xamarin share project.
Interface
public interface IFileLauncher
{
Task<bool> LaunchFileAsync(string uri);
}
Implementation
[assembly: Dependency(typeof(UWPFileLauncher))]
namespace App14.UWP
{
public class UWPFileLauncher : IFileLauncher
{
public async Task<bool> LaunchFileAsync(string uri)
{
var file = await Windows.Storage.StorageFile.GetFileFromPathAsync(uri);
bool success = false;
if (file != null)
{
// Set the option to show the picker
var options = new Windows.System.LauncherOptions();
options.DisplayApplicationPicker = true;
// Launch the retrieved file
success = await Windows.System.Launcher.LaunchFileAsync(file, options);
if (success)
{
// File launched
}
else
{
// File launch failed
}
}
else
{
// Could not
}
return success;
}
}
}
Usage
private async void Button_Clicked(object sender, EventArgs e)
{
await DependencyService.Get<IFileLauncher>().LaunchFileAsync("D:\\Key.pdf");
}
Please note if you want to access D or C disk in uwp, you need add broadFileSystemAccess capability. for more please refer this .
Update
If the UWP files are network based, not local zone based, you could use Xamarin.Essentials to open file with browser. And you must specify the privateNetworkClientServer capability in the manifest. For more please refer this link.

Monitor FTP directory in ASP.NET/C#

I have FileSystem watcher for a local directory. It's working fine. I want same to implement for FTP. Is there any way I can achieve it? I have checked many solutions but it's not clear.
Logic: Want to get files from FTP later than some timestamp.
Problem faced: Getting all files from FTP and then filtering the result is hitting the performance (used FtpWebRequest).
Is there any right way to do this? (WinSCP is on hold. Cant use it now.)
FileSystemWatcher oFsWatcher = new FileSystemWatcher();
OFSWatchers.Add(oFsWatcher);
oFsWatcher.Path = sFilePath;
oFsWatcher.Filter = string.IsNullOrWhiteSpace(sFileFilter) ? "*.*" : sFileFilter;
oFsWatcher.NotifyFilter = NotifyFilters.FileName;
oFsWatcher.EnableRaisingEvents = true;
oFsWatcher.IncludeSubdirectories = bIncludeSubdirectories;
oFsWatcher.Created += new FileSystemEventHandler(OFsWatcher_Created);
You cannot use the FileSystemWatcher or any other way, because the FTP protocol does not have any API to notify a client about changes in the remote directory.
All you can do is to periodically iterate the remote tree and find changes.
It's actually rather easy to implement, if you use an FTP client library that supports recursive listing of a remote tree. Unfortunately, the built-in .NET FTP client, the FtpWebRequest does not. But for example with WinSCP .NET assembly, you can use the Session.EnumerateRemoteFiles method.
See the article Watching for changes in SFTP/FTP server:
// Setup session options
SessionOptions sessionOptions = new SessionOptions
{
Protocol = Protocol.Ftp,
HostName = "example.com",
UserName = "user",
Password = "password",
};
using (Session session = new Session())
{
// Connect
session.Open(sessionOptions);
List<string> prevFiles = null;
while (true)
{
// Collect file list
List<string> files =
session.EnumerateRemoteFiles(
"/remote/path", "*.*", EnumerationOptions.AllDirectories)
.Select(fileInfo => fileInfo.FullName)
.ToList();
if (prevFiles == null)
{
// In the first round, just print number of files found
Console.WriteLine("Found {0} files", files.Count);
}
else
{
// Then look for differences against the previous list
IEnumerable<string> added = files.Except(prevFiles);
if (added.Any())
{
Console.WriteLine("Added files:");
foreach (string path in added)
{
Console.WriteLine(path);
}
}
IEnumerable<string> removed = prevFiles.Except(files);
if (removed.Any())
{
Console.WriteLine("Removed files:");
foreach (string path in removed)
{
Console.WriteLine(path);
}
}
}
prevFiles = files;
Console.WriteLine("Sleeping 10s...");
Thread.Sleep(10000);
}
}
(I'm the author of WinSCP)
Though, if you actually want to just download the changes, it's a way easier. Just use the Session.SynchronizeDirectories in the loop.
while (true)
{
SynchronizationResult result =
session.SynchronizeDirectories(
SynchronizationMode.Local, "/remote/path", #"C:\local\path", true);
result.Check();
// You can inspect result.Downloads for a list for updated files
Console.WriteLine("Sleeping 10s...");
Thread.Sleep(10000);
}
This will update even modified files, not only new files.
Though using WinSCP .NET assembly from a web application might be problematic. If you do not want to use a 3rd party library, you have to do with limitations of the FtpWebRequest. For an example how to recursively list a remote directory tree with the FtpWebRequest, see my answer to List names of files in FTP directory and its subdirectories.
You have edited your question to say that you have performance problems with the solutions I've suggested. Though you have already asked a new question that covers this:
Get FTP file details based on datetime in C#
Unless you have access to the OS which hosts the service; it will be a bit harder.
FileSystemWatcher places a hook on the filesystem, which will notify your application as soon as something happened.
FTP command specifications does not have such a hook. Besides that it's always initiated by the client.
Therefor, to implement such logic you should periodical perform a NLST to list the FTP-directory contents and track the changes (or hashes, perhaps (MDTM)) yourself.
More info:
FTP return codes
FTP
I have got an alternative solution to do my functionality.
Explanation:
I am downloading the files from FTP (Read permission reqd.) with same folder structure.
So everytime the job/service runs I can check into the physical path same file(Full Path) exists or not If not exists then it can be consider as a new file. And Ii can do some action for the same and download as well.
Its just an alternative solution.
Code Changes:
private static void GetFiles()
{
using (FtpClient conn = new FtpClient())
{
string ftpPath = "ftp://myftp/";
string downloadFileName = #"C:\temp\FTPTest\";
downloadFileName += "\\";
conn.Host = ftpPath;
//conn.Credentials = new NetworkCredential("ftptest", "ftptest");
conn.Connect();
//Get all directories
foreach (FtpListItem item in conn.GetListing(conn.GetWorkingDirectory(),
FtpListOption.Modify | FtpListOption.Recursive))
{
// if this is a file
if (item.Type == FtpFileSystemObjectType.File)
{
string localFilePath = downloadFileName + item.FullName;
//Only newly created files will be downloaded.
if (!File.Exists(localFilePath))
{
conn.DownloadFile(localFilePath, item.FullName);
//Do any action here.
Console.WriteLine(item.FullName);
}
}
}
}
}

Opening a document from Imanage in Word 2016

I am attempting to open an Imanage document, in MS Word, within a temporary test application (for debugging) to later copy over into an ActiveX control project. The error that is popping up is:
Exception thrown at 0x7618851A (msvcrt.dll) in w3wp.exe: 0xC0000005: Access >violation reading location 0x09801000.
If there is a handler for this exception, the program may be safely continued.
The error occurs when running the cmd.Execute line and I am unsure as to why I am getting the error.
using IManage;
using IMANEXTLib;
using System;
namespace WebApplication3
{
public partial class WebForm2 : System.Web.UI.Page
{
IManDatabase imanagedatabase;
IManDMS myDMS = new ManDMSClass();
protected void Page_Load(object sender, EventArgs e)
{
openImanageDoc("docNumber", "versionNumber", "server", "database", ReadOnly);
}
public void imanageLogin(string server, string database)
{
try
{
IManSession session = myDMS.Sessions.Add(server);
IManWorkArea oWorkArea = session.WorkArea;
session.TrustedLogin();
foreach (IManDatabase dbase in session.Databases)
{
if (dbase.Name == database)
{
imanagedatabase = dbase;
}
}
}
catch (Exception ex)
{
throw ex;
}
}
public void openImanageDoc(string docNo, string versionNo, string server, string database, bool isReadOnly = true)
{
IManDocument doc;
try
{
imanageLogin(server, database);
int iDocNo = int.Parse(docNo);
int iVersion = int.Parse(versionNo);
doc = imanagedatabase.GetDocument(iDocNo, iVersion);
openNRTDocument(ref doc, isReadOnly);
imanagedatabase.Session.Logout();
myDMS.Close();
}
catch (Exception Ex)
{
imanagedatabase.Session.Logout();
throw Ex;
}
finally
{
imanagedatabase = null;
myDMS = null;
}
}
public void openNRTDocument(ref IManDocument nrtDocument, Boolean isReadonly)
{
OpenCmd cmd = new OpenCmd();
ContextItems objContextItems = new ContextItems();
objContextItems.Add("NRTDMS", myDMS);
objContextItems.Add("SelectedNRTDocuments", new[] { (NRTDocument)nrtDocument.LatestVersion });
objContextItems.Add("IManExt.OpenCmd.Integration", false);
objContextItems.Add("IManExt.OpenCmd.NoCmdUI", true);
cmd.Initialize(objContextItems);
cmd.Update();
cmd.Execute();
}
}
}
Due to the nature of the error, I am presuming it is a configuration issue rather than a code error although I could be completely wrong as I am very new to programming.
I have found out that w3wp.exe is an IIS worker process created by the app pool but other than that I have no idea what the numeric code represents. Any help or advice is greatly appreciated.
The error is being raised by the OpenCmd instance because it is most likely trying to access resources such as local registry settings. It's not possible to do that in a web application, unless you host your code in a proprietary technology like ActiveX (which is specific to Internet Explorer)
Actually, it is not appropriate for you to use OpenCmd here. Those type of commands (iManage "ICommand" implementations) are intended to be used in regular Windows applications that have either the iManage FileSite or DeskSite client installed. These commands are all part of the so-called Extensibility COM libraries (iManExt.dll, iManExt2.dll, etc) and should not be used in web applications, or at least used with caution as they may inappropriately attempt to access the registry, as you've discovered, or perhaps even display input Win32 dialogs.
For a web app you should instead just limit yourself to the low-level iManage COM library (IManage.dll). This is in fact what iManage themselves do with their own WorkSite Web application
Probably what you should do is replace your openNRTDocument method with something like this:
// create a temporary file on your web server..
var filePath = Path.GetTempFileName();
// fetch a copy of the iManage document and save to the temporary file location
doc.GetCopy(filePath, imGetCopyOptions.imNativeFormat);
In an MVC web application you would then just return a FileContentResult, something like this:
// read entire document as a byte array
var docContent = File.ReadAllBytes(filePath);
// delete temporary copy of file
File.Delete(filePath);
// return byte stream to web client
return File(stream, MediaTypeNames.Application.Octet, fileName);
In a Web Forms application you could do something like this:
// set content disposition as appropriate - here example is for Word DOCX files
Response.ContentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
// write file to HTTP content output stream
Response.WriteFile(filePath);
Response.End();

Wix Bootstrapper - Best way to update/install Visual FoxPro database

As mentioned here wix-bootstrapper-update-ui-xaml-from-customaction I use a Bootstrapper to install two MSI-packages.
During the installation I want to install/update a Visual FoxPro database (consisting of free tables).
At the moment I achieve this by calling a Visual FoxPro-exe during the ApplyComplete-Event of the BootstrapperApplication. To establish a communication between the BootstrapperApplication and the Visual FoxPro-exe I use MSMQ :
private void OnApplyComplete(object sender, ApplyCompleteEventArgs e)
{
string updateFile = this.installationFolder + "\\updfile.exe";
if (!MessageQueue.Exists(NAMEDPVDATENBANKNACHRICHTENSCHLANGE))
{
this._msmq = MessageQueue.Create(UPDATEMSMQ);
}
else
{
this._msmq = new MessageQueue(UPDATEMSMQ);
}
this._msmq.SetPermissions("Everyone", MessageQueueAccessRights.FullControl);
this._msmq.Purge();
if (System.IO.File.Exists(updateFile))
{
this._dbUpdate = true;
ProcessStartInfo updateProcessInfo = new ProcessStartInfo();
updateProcessInfo.FileName = updateFile;
updateProcessInfo.Arguments = UPDATEMSMQ;
updateProcessInfo.UseShellExecute = true;
updateProcessInfo.WorkingDirectory = this.installationFolder;
updateProcessInfo.CreateNoWindow = true;
Process updateProcess = new Process();
updateProcess.StartInfo = updateProcessInfo;
updateProcess.EnableRaisingEvents = true;
updateProcess.Exited += new EventHandler(this.updateFinished);
updateProcess .Start();
while (this._dbUpdate)
{
Message msg = null;
try
{
nachricht = this._msmq.Receive(new TimeSpan(0, 0, 45));
}
catch (MessageQueueException msgEx)
{
if (nachrichtAusnahme.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
{
this.Engine.Log(LogLevel.Verbose, msgEx);
}
}
if (msg != null)
{
msg.Formatter = new ActiveXMessageFormatter();
this.Engine.Log(LogLevel.Verbose, "VfpUpdate - " + msg.Body.ToString());
}
}
}
this._msmq.Close();
MessageQueue.Delete(UPDATEMSMQ);
}
private void updateFinished(object sender, EventArgs e)
{
this._dbUpdate = false;
this.Engine.Log(LogLevel.Verbose, "Update finished");
}
This way it works like a charm unless there are errors during the the update of the Visual FoxPro database. It should be possible to roll-back the changes made during the installation. For me it would be no problem to create a backup of the Visual FoxPro-files and to restore the files if an error occurs. But how should I do this with the files changed by the actual Bootstrapper?
With a CustomAction I can use ActionResult.Failure or ActionResult.Success. But with a CustomAction I face the following issues:
no access to Application.Current (for reading values from a customized ResourceDictionary with localized strings that I use within the Bootstrapper)
MSMQ-queue is broken (closed?!) after the first message is delivered
display the currently performed task in the MainWindow.
Any advice on how to perform the update of the Visual FoxPro database inside a BootstrapperApplication is really welcome.
I don't know that in the context of an installation bootstrapper. OTOH, either a VFP database and tables or VFP tables (free) are just plain files that are copied/moved from a folder. You don't need to have an exe for that to create them programmatically.
Also having those files as part of the setup might not be a good idea as well. If some files are removed because of version changes then your installer might start to bark about that.
Looking at the bootstrapper code, I have a suspicion that you are creating those free tables in the installation folder or its subfolders which would be a real PITA. Because installation folder is generally under "Program Files (x86)" which is a read only location.
Your bootstrapper code is C#. You might use C# to create those tables as well. One way to do that is to use VFPOLEDB and ExecScript() calls.

Categories