Note that there are some explanatory texts on larger screens.

plurals
  1. POHelp with a Windows Service/Scheduled Task that must use a web browser and file dialogs
    text
    copied!<p><strong>What I'm Trying To Do</strong></p> <p>I'm trying to create a solution of any kind that will run nightly on a Windows server, authenticate to a website, check a web page on the site for new links indicating a new version of a zip file, use new links (if present) to download a zip file, unzip the downloaded file to an existing folder on the server, use the unzipped contents (sql scripts, etc.) to build an instance of a database, and log everything that happens to a text file.</p> <p><strong>Forms App: The Part That Sorta Works</strong></p> <p>I created a Windows Forms app that uses a couple of WebBrowser controls, a couple of threads, and a few timers to do all that except the running nightly. It works great as a Form when I'm logged in and run it, but I need to get it (or something like it) to run on it's own like a Service or scheduled task.</p> <p><strong>My Service Attempt</strong></p> <p>So, I created a Windows Service that ticks every hour and, if the System.DateTime.Now.Hour >= 22, attempts to launch the Windows Forms app to do it's thing. When the Service attempts to launch the Form, this error occurs:</p> <p>ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.</p> <p>which I researched and tried to resolve by either placing the [STAThread] attribute on the Main method of the Service's Program class or using some code like this in a few places including the Form constructor:</p> <pre><code> webBrowseThread = new Thread(new ThreadStart(InitializeComponent)); webBrowseThread.SetApartmentState(ApartmentState.STA); webBrowseThread.Start(); </code></pre> <p>I couldn't get either approach to work. In the latter approach, the controls on the Form (which would get initialized inside IntializeComponent) don't get initialized and I get null reference exceptions.</p> <p><strong>My Scheduled Task Attempt</strong></p> <p>So, I tried creating a nightly scheduled task using my own credentials to run the Form locally on my dev machine (just testing). It gets farther than the Service did, but gets hung up at the File Download Dialog. </p> <p><em>Related Note:</em> To send the key sequences to get through the File Download and File Save As dialogs, my Form actually runs a couple of vbscript files that use WScript.Shell.SendKeys. Ok, that's embarassing to admit, but I tried a few different things including SendMessage in Win32 API and referencing IWshRuntimeLibrary to use SendKeys inside my C# code. When I was researching how to get through the dialogs, the Win32 API seemed to be the recommended way to go, but I couldn't figure it out. The vbscript files was the only thing I could get to work, but I'm worried now that this may be the reason why a scheduled task won't work.</p> <p><strong>Regarding My Choice of WebBrowser Control</strong></p> <p>I have read about the System.WebClient class as an alternative to the WebBrowser control, but at a glance, it doesn't look like it has what I need to get this done. For example, I needed (or I think I needed) the WebBrowser's DocumentCompleted and FileDownload events to handle the delays in pages loading, files downloading, etc. Is there more to WebClient that I'm not seeing? Is there another class besides WebBrowser that is more Service-friendly and would do the trick?</p> <p><strong>In Summary</strong></p> <p>Geez, this is long. Sorry! It would help to even have a high level recommendation for a better way to do what I'm trying to do, because nothing I've tried has worked.</p> <p><strong>Update 10/22/09</strong></p> <p>Well, I think I'm closer, but I'm stuck again. I should end up with a decent-sized zip file with several files in it, but the zip file resulting from my code is empty. Here's my code:</p> <pre><code>// build post request string targetHref = "http://wwwcf.nlm.nih.gov/umlslicense/kss/login.cfm"; HttpWebRequest request = (HttpWebRequest)WebRequest.Create(targetHref); request.Method = "POST"; request.ContentType = "application/x-www-form-urlencoded"; // encoding to use Encoding enc = Encoding.GetEncoding(1252); // build post string containing authentication information and add to post request string poststring = "returnUrl=" + fixCharacters(targetDownloadFileUrl); poststring += getUsernameAndPasswordString(); poststring += "&amp;login2.x=0&amp;login2.y=0"; // convert to required byte array byte[] postBytes = enc.GetBytes(poststring); request.ContentLength = postBytes.Length; // write post to request Stream postStream = request.GetRequestStream(); postStream.Write(postBytes, 0, postBytes.Length); postStream.Close(); // get response as stream HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream responseStream = response.GetResponseStream(); // writes stream to zip file FileStream writeStream = new FileStream(fullZipFileName, FileMode.Create, FileAccess.Write); ReadWriteStream(responseStream, writeStream); response.Close(); responseStream.Close(); </code></pre> <p>The code for ReadWriteStream looks like this.</p> <pre><code>private void ReadWriteStream(Stream readStream, Stream writeStream) { // taken verbatum from http://www.developerfusion.com/code/4669/save-a-stream-to-a-file/ int Length = 256; Byte[] buffer = new Byte[Length]; int bytesRead = readStream.Read(buffer, 0, Length); // write the required bytes while (bytesRead &gt; 0) { writeStream.Write(buffer, 0, bytesRead); bytesRead = readStream.Read(buffer, 0, Length); } readStream.Close(); writeStream.Close(); } </code></pre> <p>The building of the post string is taken from my previous forms app that works. I compared the resulting values in poststring for both sets of code (my working forms app and this one) and they're identical.</p> <p>I'm not even sure how to troubleshoot this further. Anyone see anything obvious as to why this isn't working?</p> <p><strong>Conclusion 10/23/09</strong></p> <p>I finally have this working. A couple of important hurdles I had to get over. I had some problems with the ReadWriteStream method code that I got online. I don't know why, but it wasn't working for me. A guy named JB in Claudio Lassala's <a href="http://claudiolassala.spaces.live.com/blog/cns!E2A4B22308B39CD2!2223.entry" rel="nofollow noreferrer">Virtual Brown Bag meeting</a> helped me to come up with this code which worked much better for my purposes:</p> <pre><code>private void WriteResponseStreamToFile(Stream responseStreamToRead, string zipFileFullName) { // responseStreamToRead will contain a zip file, write it to a file in // the target location at zipFileFullName FileStream fileStreamToWrite = new FileStream(zipFileFullName, FileMode.Create); int readByte = responseStreamToRead.ReadByte(); while (readByte != -1) { fileStreamToWrite.WriteByte((byte)readByte); readByte = responseStreamToRead.ReadByte(); } fileStreamToWrite.Flush(); fileStreamToWrite.Close(); } </code></pre> <p>As Will suggested below, I did have trouble with the authentication. The following code is what worked to get around that issue. A few comments inserted addressing key issues I ran into. </p> <pre><code>string targetHref = "http://wwwcf.nlm.nih.gov/umlslicense/kss/login.cfm"; HttpWebRequest firstRequest = (HttpWebRequest)WebRequest.Create(targetHref); firstRequest.AllowAutoRedirect = false; // this is critical, without this, NLM redirects and the whole thing breaks // firstRequest.Proxy = new WebProxy("127.0.0.1", 8888); // not needed for production, but this helped in order to debug the http traffic using Fiddler firstRequest.Method = "POST"; firstRequest.ContentType = "application/x-www-form-urlencoded"; // build post string containing authentication information and add to post request StringBuilder poststring = new StringBuilder("returnUrl=" + fixCharacters(targetDownloadFileUrl)); poststring.Append(getUsernameAndPasswordString()); poststring.Append("&amp;login2.x=0&amp;login2.y=0"); // convert to required byte array byte[] postBytes = Encoding.UTF8.GetBytes(poststring.ToString()); firstRequest.ContentLength = postBytes.Length; // write post to request Stream postStream = firstRequest.GetRequestStream(); postStream.Write(postBytes, 0, postBytes.Length); // Fiddler shows that post and response happen on this line postStream.Close(); // get response as stream HttpWebResponse firstResponse = (HttpWebResponse)firstRequest.GetResponse(); // create new request for new location and cookies HttpWebRequest secondRequest = (HttpWebRequest)WebRequest.Create(firstResponse.GetResponseHeader("location")); secondRequest.AllowAutoRedirect = false; secondRequest.Headers.Add(HttpRequestHeader.Cookie, firstResponse.GetResponseHeader("Set-Cookie")); // get response to second request HttpWebResponse secondResponse = (HttpWebResponse)secondRequest.GetResponse(); // write stream to zip file Stream responseStreamToRead = secondResponse.GetResponseStream(); WriteResponseStreamToFile(responseStreamToRead, fullZipFileName); responseStreamToRead.Close(); sl.logScriptActivity("Downloading update."); firstResponse.Close(); </code></pre> <p>I want to underscore that setting AllowAutoRedirect to false on the first HttpWebRequest instance was critical to the whole thing working. Fiddler showed two additional requests that occurred when this was not set, and it broke the rest of the script.</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload