SharePoint Claims-Authentication, Cookies, C#, and Excel-DLL/tld - c#

The basic concept of the project is it accesses SharePoint, verifies a certain file exists, verifies the file is NOT locked by another user, and then, if those two conditions are met, excel opens the file up and does some stuff with it, then saves the file and closes it again.
The issue is at the point where the program accesses SharePoint and runs through the verification steps. Initially, my first crack at this was directly from VBA using SOAP, and, until Microsoft decided to downgrade SharePoint to 2013 from 2010, it worked quite well, meaning now, I'm into using Visual Studio and C# to accomplish the same thing that SOAP was doing in SP2010.
I've attempted implementing the code found here: http://www.vrdmn.com/2013/01/authenticating-net-client-object-model.html, however I can't get it to work. What I did manage to get working was the 2010 authentication model which pops up a browser window, checks for valid cookies, and, if it finds them, reads the cookies, otherwise prompts the user to log into SharePoint and then closes the browser and continues on.
public CookieCollection Show()
{
if (string.IsNullOrEmpty(this.LoginPageUrl)) throw new ApplicationException(Constants.MSG_NOT_CLAIM_SITE);
// navigate to the login page url.
this.webBrowser.Navigate(this.LoginPageUrl);
DisplayLoginForm = new Form();
DisplayLoginForm.SuspendLayout();
// size the login form
int dialogWidth = Constants.DEFAULT_POP_UP_WIDTH;
int dialogHeight = Constants.DEFAULT_POP_UP_HEIGHT;
if (PopUpHeight != 0 && PopUpWidth != 0)
{
dialogWidth = Convert.ToInt32(PopUpWidth);
dialogHeight = Convert.ToInt32(PopUpHeight);
}
DisplayLoginForm.Width = dialogWidth;
DisplayLoginForm.Height = dialogHeight;
DisplayLoginForm.Text = this.fldTargetSiteUrl;
DisplayLoginForm.Controls.Add(this.webBrowser);
DisplayLoginForm.ResumeLayout(false);
//DisplayLoginForm.Show();
Application.Run(DisplayLoginForm);
// see ClaimsWebBrowser_Navigated event
//DisplayLoginForm.Dispose();
return this.fldCookies;
}
The problem comes about with the Application.Run(DisplayLoginForm). If I step through the code line-by-line, everything works fine, and I get the results I need within my VBA code. However, if I run the program with F5 (either by building it in Debug mode or in Release mode), Application.Run(DisplayLoginForm) kills the application (and Excel along with it) once the cookie jar (my term, and most likely not a computer term) has been examined for valid cookies.
You can see in the code, I attempted to use DisplayLoginForm.Show(); rather than Application.Run, however I kept getting null references to the file I'm trying to find, so that approach does not work either.
So here's the question:
How do I go about popping up a web browser (which is obviously set up as a windows form), look for the cookies, prompt the user if needed, close the web browser and remain alive long enough to return the appropriate values (file.exists and file.islockedbyuser.email, both of which are in functions called by the VBA and both of which work just fine) to excel, and then finish up the program without shutting Excel down in the process?

The answer to this question is to set up a new and separate thread on which to operate the browser and login sequence, returning to the main thread (and the thread Excel is running on) the context with the credentials, with which the file can be queried.

Related

What is the proper way to stop/kill a .net core web app process after launching a process to display the browser

When creating a .NET Core Web app C# and debugging you can opt to Launch the browser when Running the app. The console launches and then the url launches in your preferred browser. Once you close the browser tab opened by the debug session, the app process it stop and is no longer running on the machine.
When I published the app, the page does not launch automatically so I am starting a new Process to do so. I've added an event handler to the process before starting it but the since the app didn't launch the process it doesn't receive any notification when the tab has been closed.
What is the proper way to go about launching your .NET Core web app and/or handle communicating between the two?
I even tried adding a Close button but JS won't allow me to close the window as I haven't opened it.
I'm creating my Process as follows:
var p = Process.Start("cmd", "/C start http://localhost:5000");
I've tried
p.WaitForExit();
p.EventHandler += newHandler;
and I also tried using ProcessStartInfo but the only thing that is working right now is if I set a Timer and then kill the process when the timer is activated but this doesn't seem too clean.
Any advice is appreciated!
I wanted to follow-up with the process I feel is the most graceful way that I am happy with.
A few pieces of info that may be helpful in creating your own process. It was a goal of mine to use .net core to self host a client side web app which communicates with a server hosted web api.
I am using Razor views and .net core 3.1
I am using Startup and utilizing applicationLifeTime to handle OnStartup where I start the process as I described in my original post.
At this point the web app opens and if the user decides to close the browser the process doesn't know and continues to run in the background.
In order to prevent it from continuing to run and allow the user to initiate it closing, I made a few the following additions.
First, I added a timer that uses a configurable length of time via appsettings. When starting the process that opens the browser I call and set the timer that will perform an "AutoShutdown" if the process is still running after the configurable length of time that I defaulted to 1hr. I am using Enviroment.Exit(0) in the event that handles closing the running process.
My View is using a Model that contains a property who's value will be set from the page via a hidden field.
I added a Close button to the View that is displayed to the user at startup. When the user clicks close, I use jquery to set a hidden field in the view and then I use jquery to submit the form.
When the Model is received, the value set by the client's request to close is true and you can handle the close without display errors if you use a Timer and Start it almost immediately after redirecting the View to a view with a message to the client. Whether they leave it open or close it, the process will have closed. Example of how I did it below.
Finally, in Main.cs I put a check in to check if there is an existing process running and close it before starting a new one. I added this because the client may not always close the browser and/or they may attempt to start it before the "AutoShutdown" timer has kicked in.
This is more "hacky" then I would like but I couldn't find anything else that didn't throw and show Bad Request when trying to use Envirnoment.Exit(0) when the client is still has the view open and I was unsuccessful at being able to allow console input so that I could send close to the console.
This is how I am handling the close request from the client in my Controller
if (ui.HandleCloseApp)
{
var app = Process.GetProcessesByName("NameOfYourWebApp");
if (app.Length >= 1)
{
foreach (var p in app)
{
if (p.ProcessName == "NameOfYourWebApp")
{
p.Close();
p.Dispose();
}
}
}
var t = new System.Timers.Timer(10);
t.Elapsed += OnClose;
t.Start();
return View("Close");
}
I hope this helps someone else in creating their own "graceful" process that shouldn't be :)
I'm still open if anyone has input to share. Thx!

Trouble with Quit() on VisualFoxPro Application within C#

I've created a WebAPI webservice that accepts requests from a legacy Visual FoxPro system. The service needs to parse the request and then launch the VFP runtime in order to execute some legacy FoxPro code to actually "process" the request.
I have some code like this:
try{
foxApp = new VisualFoxpro.FoxApplication();
foxApp.DoCmd(#"do hqinit with .T.");
...
foxApp.DoCmd("close all");
foxApp.DoCmd("release all");
foxApp.Quit();
}
catch{
...
}
The foxApp.Quit() constantly throws and error and the FoxPro Application is still left running (I can see it in the Task Manager).
Based on this SO post, I tried the following:
try{
foxApp = new VisualFoxpro.FoxApplication();
...
while(System.Runtime.InteropServices.Marshal.ReleaseComObject(foxApp) > 0) {}
However, while I no longer get the error I was getting when trying to foxApp.Quit(), VFP Application is still left running after each call to the web service.
How do I ensure that VFP gets closed down at the end of each web request?
Update
I just tested again and realized that the first code block does NOT throw an error. Rather, it simply hangs the server. However, if I add foxApp.DoCmd("clear all") before foxApp.Quit() I get an error stating that:
Cannot clear the object OPANEBROWSER because it is in use.
None of my code references an OPANEBROWSER object some I'm not sure where that is coming from.
Got it!
I had installed VFP9.0 SP2 on the server and technically, I'm launching the app itself. On my dev environments, I always disable the Task Manager pane that MS pops up by default. However, I had not done that on the server VFP environment.
Once I disabled the Task Manager in VFP environment so that it did not get displayed on Start of VFP, everything was golden.

Not picking up dynamically swapped config info for WCF service called from VSTO Word AddIn

I have a C# VSTO Word AddIn from which I am successfully making WCF service calls. I am now trying to dynamically replace the config file (MyAddin.dll.config in my AddIn's base directory) from within the AddIn so I can redirect to other endpoints. Once I make the file change, I am using this code to refresh:
System.Configuration.Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ConfigurationManager.RefreshSection("appSettings");
ConfigurationManager.RefreshSection("system.serviceModel/behaviors");
ConfigurationManager.RefreshSection("system.serviceModel/bindings");
ConfigurationManager.RefreshSection("system.serviceModel/client");
ConfigurationManager.RefreshSection("system.serviceModel/services");
I do not have any WCF clients open when I do this. After this refresh, when I immediately create a new client
var adminProxy = new CorrespondenceAdminClient();
mcRibbon.serviceHostUri = adminProxy.Endpoint.Address.Uri.ToString();
it appears to have picked up the new information, but when I call a service method, sometimes the call is directed to the correct endpoint and sometimes it is not. When I subsequently create a client and call a service from another part of my AddIn code, again it might or might not get to the expected endpoint. Sometimes the immediate call works but the subsequent call does not.
Is this information cached somewhere? Can I clear it?
I have tried running this from within and outside of the Visual Studio (Premium 2012) debugger and this does not make a difference.
It does get cached, you can try adding "|vstolocal" in registry https://msdn.microsoft.com/en-us/library/bb386106.aspx

"The handle is invalid" error when writing to eventlog using ASP.NET

I am working with an ASP.NET 2.0 application (created by my predecessor). Users log into it with AD credentials, and everything done within the app uses those credentials. I modified a page in the application that has nothing to do with event logging, and now my users get this error:
Here is the relevant code from the global.asax file:
public void LogException(Exception e)
{
string exceptionXml = RenderException(e, true);
_EventLog.WriteEntry("Exception of type " + e.GetType().FullName + " occurred.\n\n" + exceptionXml, EventLogEntryType.Error);
}
RenderException() just puts the exception XML into a flat string, removing white spaces.
I am at a loss on how to get rid of this error. I have tried re-publishing the website with an iisreset. I have tried restarting the web server (2k3 w/ iis 6.0), flushing the app pool. I have also tried modifying the permissions in the registry for the top-level event log key. Can anyone tell me how to get rid of this error? It does not happen on my computer, so it is very hard to replicate. Also, the browser used does not seem to matter. The previous version will work for the same persons getting this error.
By default the ASPNET user cannot access the existing event logs categories.
If you do want to write messages to the event log you must create your own category
Launch RegEdit
Navigate to HKEY_LOCAL_MACHINE\SYSTEM\
CurrentControlSet\Services\EventLog\
From the menu, select Edit->Permissions
Click the Add button and write ASPNET. (if ASP.NET is running under a different user id, use that id instead)
Click OK.
Select the newly added user from the list (ASP.NET Machine User by default).
Click on Full Control in the Allow column.
Click OK.
You can also check that the user, under which the applications is running, belongs to the correct group, for example IIS_WPG
http://www.microsoft.com/technet/prodtechnol/WindowsServer2003/Library/IIS/3648346f-e4f5-474b-86c7-5a86e85fa1ff.mspx?mfr=true
You appear to be calling an instance method of the EventLog class on the _EventLog instance:
_EvengLog.WriteEvent(message, ...);
But according to MSDN documentation for the EventLog class:
Any instance members are not guaranteed to be thread safe.
I suspect this is a likely source of your problem, and would recommend you use one of the static methods (which are guaranteed to be thread safe):
System.Diagnostics.EventLog.WriteEvent(source, message, ...);
Alternatively you could implement your own synchronization, but I wouldn't recommend this.

Change Browser download folder using C#

Is there a way I could change the download folder of the default web browser using c#.
Concurring with other's comments, you can only do it in a desktop app if you have the right permissions.
Here's some sample code to find out the default browser of the system (from this post):
private string getDefaultBrowser()
{
string browser = string.Empty;
RegistryKey key = null;
try
{
key = Registry.ClassesRoot.OpenSubKey(#"HTTP\shell\open\command", false);
//trim off quotes
browser = key.GetValue(null).ToString().ToLower().Replace("\"", "");
if (!browser.EndsWith("exe"))
{
//get rid of everything after the ".exe"
browser = browser.Substring(0, browser.LastIndexOf(".exe")+4);
}
}
finally
{
if (key != null) key.Close();
}
return browser;
}
However, things get tricky from here. Different browsers have different ways of saving the default location.
E.g.,
IE may store it in registry (usually under HKEY_CURRENT_USER\ Software\ Microsoft\ Internet Explorer)
FF stores it in prefs.js in Profile folder (checkout this post to get to it via code)
Not sure about Chrome and Safari
but you get the idea.
Not sure what your end goal is, but from a UX standpoint, I think the best thing to do would be to ask user to specify the Download directory (in other words, you expose a Setting in your App for the default download location).
To expand on Ash's comment - if you're within a web app, no. If you're a desktop app, and you have sufficient permissions (i.e. running as Administrator), probably. But you'd need to find the default browser (from the registry presumably) and know how to set the download folder for each popular browser, or every browser you want to work with.
Where are you trying to do this from? If you mean "someone hits our website and ...", the answer is no, as anything you run is in a security context. You can certainly suggest the user changes the folder, but you are stuck.
Assuming you are not a web application, you have options. The main user download directory is located at X under the key {374DE290-123F-4565-9164-39C4925E467B}. Yeah, that sounds like a lot of fun. You can learn how to hack the registry programatically here. But, the user can specify a specific folder in the browser, as well. This means you have to know what browser the user is using and hack it, or you can attempt to hack all.
The bad news is the app, running (most likely) in the user context, may not have administrator rights and be able to whack the registry keys to change the folder.

Categories