I'm trying to open a PDF from within my application. I've gone through several iterations for this and have resolved that my only remaining option is to just open Adobe Reader passing the location of the PDF as an argument. The libraries I've looked at don't support what I need (rendering form fields) nor does the Telerik PDF control. So, I've absolutely explored the buy option over build.
Anyway, this works fine from the Run prompt like so:
"C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe" "C:\Users\Foo\AppData\Local\Temp\Bar.pdf"
However, from code, doing the following does NOT work:
ProcessStartInfo info = new ProcessStartInfo(#"C:\Program Files (x86)\Adobe\Reader 11.0\Reader\AcroRd32.exe", location);
Process.Start(info);
Using Process.Start, I can see the Adobe process in my Task Manager list as a background process but there is no UI while it works flawlessly from Run where it shows up under App and gives the UI as I'd expect.
I don't see a way to force a UI to appear. The MainWindowHandle is 0, the WindowStyle is set to Normal, and CreateNoWindow is false.
Other things I've tried is setting the EnvironmentVariables collection manually and turning off shell execution. I've also tried loading the user profile into the process to no effect.
What do I need to do do here?
I ended up getting around this situation entirely. I used iTextSharp to flatten the PDF and from there my normal tooling for rendering PDFs worked fine. Thankfully, this has other benefits for my needs as well.
I still couldn't solve the different in behavior I described. I was about to go down the path of Win32 via ShellExecute to leave a "where I left off" note.
*.Pdf files are always opened using Adobe reader, unless you have installed some other pdf reader. So don't call the adobe application, just call the file using Process.start(#"file location and name.pdf"). Then the file will be opened in the default pdf reader.
You'll need a system reference using System.Diagnostics;
And here's another thing, above you have used Process.Start() to give the file location but remember this won't help if the application mentioned doesn't take any arguments/instructions.
Process.Start(#"application.exe", "instructions to application.exe")
Related
I want to have my C# (Xamarin) program run an EXE or batch (BAT) file. The user will be running my program, and will click on one of several buttons, some of which open Web pages and others of which run external programs. These files will be on the same computer as the one running the main program and don't need greater permissions. The overall program will be in Windows, UWP.
I already have code to pull info from the database saying "the button the user clicked references a program and it's (eg) C:\Tools\MyTool.exe". (Real path more like (C:\Users\Me\source\repos\ProductNameV2\ProductName\ProductName.UWP\Assets\EXE\whatever.exe".) I used a "demo.bat" file containing nothing but echo and pause statements, or references to a built-in Windows program like Notepad or Calc that an ordinary command prompt can recognize without an explicit path (ie. that's part of the recognized system Path). Yes, the real path to the dummy file does exist; I checked. I've also explicitly added files demo.bat and dummy.txt to my C# project.
Here's roughly what I've tried so far to actually run a batch file, or an EXE, or just to try opening a text file. Nothing works.
1)
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\demo.bat"); // Returns false.
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\dummy.txt"); // Returns true.
await Launcher.OpenAsync(#"file:///C:\Tools\demo.bat") // Seems to do nothing; silently fails.
await Launcher.OpenAsync(#"file:///C:\Tools\dummy.txt") // Same.
2)
Process batchProcess = new Process();
batchProcess.StartInfo.FileName = #"file:///C:\Tools\demo.bat"; // Same result with notepad.exe
batchProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
batchProcess.Start();
batchProcess.WaitForExit();
// Result: "Access is denied" error during Start().
3)
var otherProcessInfo = new ProcessStartInfo(#"file:///C:\Tools\demo.bat")
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
// Result: "The system cannot find the file specified" despite it being the same path as in previous examples.
// Also tried literally using the path C:\Tools\demo.bat, without adding that to the C# project.
// One thing that slightly works is to use:
var otherProcessInfo = new ProcessStartInfo("cmd.exe", "/c echo Hello world!");
// This version opens a window and instantly closes it again. With "/c pause" instead, it opens, saying "press any key to continue".
// Chaining multiple commands with newline or semicolon characters doesn't work as a form of batch file.
So: the only tiny success I've had here is to run cmd.exe, to run a one-line command. I suppose that depending on what the batch file must do, there's some possibility of receiving a string, breaking it into lines, then running cmd.exe using method 3 to call them one at a time. Which is ugly at best.
Is there some better way to do this -- to run a batch file or an EXE from within my program?
EDIT: Yes, I did in fact look at documentation before asking. Why did I use URIs? Because of multiple errors telling me that the simple path strings ("C:\this\that") I was using were in an "Invalid URI format". Using Process.Start("notepad.exe") silently fails, doing nothing. Using a method involving System.Diagnostics.Process (found at How to run external program via a C# program? and yes I saw that before) fails with an error of "Access denied" when using my batch file reference, or silently failing (no window opens) using plain old notepad.exe. I avoided setting Process options that say hide the window.
So to rephrase: Is there a way to make my program run some EXE somewhere on the computer, or to run a batch file that has more than one command in it? What is that way?
Using the data you collected, I was able to run a batch file by doing the following:
var strPathToExeOrBat = System.IO.Path.Combine("C:\\Tools", "demo.bat");
var otherProcessInfo = new ProcessStartInfo("cmd.exe", $"/c call \"{strPathToExeOrBat\"");
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
I also think it would be helpful to review the capabilities of the cmd.exe application.
I found this post to be helpful:
https://stackoverflow.com/questions/515309/what-does-cmd-c-mean#:~:text=%2FC%20Carries%20out%20the%20command%20specified%20by%20the%20string%20and,switches%20by%20typing%20cmd%20%2F%3F%20.
In particular the /k option will leave the window open, if you don't want it to close after running a script.
Thank you very much for your question! It really helped me find the answer to this! (at least for my situation of a .NET MAUI windows app, but MAUI is built off of Xamarin.Forms, so you shouldn't have a problem doing the same thing)
EDIT: Updated to use file path from question and string interpolation with System.IO.Path.Combine for slightly greater cross platform capability
Searched a lot, but without luck - so here goes
My C# winforms application creates temp files which are opened using the default registered application (let's call them viewer apps). Once the user is done viewing those files, I want to delete them.
Currently, I register for an Application.ApplicationExit event, to delete the file. This approach covers most of the situations but not all. Sometimes the user still has the viewing application open while exiting my app, so the success of my File.Delete depends on whether the viewer has opened the file with FileShare.Delete or not - which is out of my control.
This is what I have found so far, but fall short of what I want
FileOptions.DeleteOnClose does not help, since my app will already be closed in some cases and the temp file will still be needed. Also, when I create the file like this: new FileStream(fn, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete, 4096, FileOptions.DeleteOnClose), the viewer apps like say adobe reader & notepad, still complain about file in use by my application The process cannot access the file because it is being used by another process
MoveFileEx with MOVEFILE_DELAY_UNTIL_REBOOT dwFlags works, but it would wait till a reboot to delete it - I would rather have it deleted once the use is done, since reboots can be few and far between and forcing reboots IMO is not the most user friendly approach. On a side note, does windows automatically clear the %temp% folder on restart? Or is there any temp folder that windows automatically clears on restart?
I can write another background process which constantly tries to delete the temp files till it succeeds, but I would like to avoid deploying one more piece of software to accomplish this. It can be done using a windows service or scheduled task or adding command line switches to my existing app and making it run in "delete mode" in background or through task scheduler. All of it decrease my ease of deployment and use along with increasing my footprint on client's computer
In a nutshell, I am wondering if there is any Win32 API or .NET Framework API that will delete a file as soon as there are no processes with open handle to that file?
EDIT:
The information in the temp files are reasonably private (think your downloaded bank account statements) and hence the need for immediate deletion after viewing as opposed to waiting for a reboot or app restart
Summary of all Answers and Comments
After doing some more experiments with inputs from Scott Chamberlain's answer, and other comments on this question, the best path seems to be to force the end users to close the viewer app before closing my application, if the viewer app disallows deletion (FileShare.Delete) of the temp file. The below factors played a role in the decision
The best option is FileOptions.DeleteOnClose, but this only works if all files open before or after this call use FileShare.Delete option to open the file.
Viewer apps can frequently open files without FileShare.Delete option.
Some viewers close the handle immediately after reading/displaying the file contents (like notepad), whereas some other apps (like Adobe Reader) retain such handle till the file is closed in the viewer
Keeping sensitive files on disk for any longer than required is definitely not a good way to proceed. So waiting till reboot should only be used as a fail-safe and not as the main strategy.
The costs of maintaining another process to do the temp file cleanup, far exceeds the slight user inconvenience when they are forced to "close" the viewer before proceeding further.
This answer is based on my comments in the question.
Try write the file without the delete, close the file, let the editor open the file, then open a new filestream as a read with DeleteOnClose with an empty body in the using section.
If that 2nd opening does not fail it will behave exactly like you wanted, it will delete the file as soon as there are no processes with open handle to that file. If the 2nd opening for the delete does fail you can use MoveFileEx as a fallback failsafe.
Process.Start("d:/test.txt"); //simple .txt file works perfectly fine on Windows 8 onward but on Windows 7 (x64) it starts the process and immediately closes it.
I've already tried the following:
Calling through ProcessStartInfo and setting CreateNoWindow=true, UseShellExecute=true and Verb="runas" (though not sure why I had to set this one).
Tried attaching Exit event and it confirms that the process does start but it exits right away and I don't even see the Notepad window open for a blink of a second.
Edit: I've tried it with an image files and few other extensions and they open just perfect. Something wrong with just the .txt files (and/or probably other formats).
I was able to solve this bug just by changing build platform from AnyCPU to specifically x64 (my target machine is x64). This is strange but it solved the problem! Thanks Simon Mourier for this tip.
Its definitely an issue with file association. I have tried it windows 7 and it works fine. Try double clicking on the file and check if it opens in notepad, if it doesn't then configure it to open via notepad.Also you should check the exception that it throws,
If File association is missing then it will launch the Openwith dialog.
If it is associated with wrong program then you can change it manually.
If you want to find association type pragmatically then, I would suggest looking at this answer.
How to I get file type information....
You're saying your code is working fine in other OS and other file formats even in Win 7.
Let's try following checks to verify if things are correct
Verify if notepad.exe is in path
Start -> Run -> notepad.exe should launch Notepad
Double click on a .txt file and see if it automatically opens in Notepad
Verify if Process.Start("notepad.exe") starts an instance of Notepad
var process = Process.Start(file used in step 2); and verify the returned process info in the debug mode and see if says the newly created process is still running or not.
I've had this happen on Windows 7 before. It's likely that your Path environment variable has become corrupted. The maximum number of characters that can be used in the Path variable is 2047. Installing many executables on your machine can overflow the Path variable. Here is a SO discussion that shows some ideas to get around it:
How do you avoid over-populating the PATH Environment Variable in Windows?
If you just need to get notepad running again quickly, you can modify the Path environment variable and just put the system location to Notepad at the beginning of the variable. (ex. "c:\windows\system32\notepad.exe").
And if you're not sure how to modify your Path variable, here is a good how-to:
http://geekswithblogs.net/renso/archive/2009/10/21/how-to-set-the-windows-path-in-windows-7.aspx
What about just using
Process.start("start", "d:\\test.txt")
or
Process.start("explorer", "d:\\test.txt")
or
Process.start("cmd", "/c notepad.exe d:\\test.txt")
If it still doesn't work, try using the straight shellexecute, as described here
Executing another program from C#, do I need to parse the "command line" from registry myself?
https://www.gamedev.net/topic/310631-shellexecuteex-api-call-in-c/
I have a bit of a strange problem, in my program, when I open a PDF file using the following command:
Process.Start("test.pdf");
the first time, the file is opened just fine, but the second time I use this command on ANY pdf file, at ANY position in the code, vshost.exe crashes.
My next step was to disable vshost, with vshost disabled, the application its self crashes.
When I say 'crashes' I mean it says that it has stopped responding.
If I create a new project, it works just fine, so it must be something wrong with my project?
I am using the iTextSharp library at other points in the code to create the pdfs, could this be a cause?
I realise this problem is very general but I have no idea what could be causing it so I dont know what information to provide.
More info:
When I look at the output of the debugger after the program crashes it says "The program '[4320] SmartShelf.exe: Managed (v4.0.30319)' has exited with code -1073741819 (0xc0000005) 'Access violation'."
Edit: Does anybody know any other way of viewing a pdf using c#?
I would use using context or the dispose command to get rid of any open file connections before calling the start("asdf.pdf") and see if that takes care of it.
Also make sure your process is running as admin in win7.
And another thing you can try is use process.start(cmd, "aspdf.pdf") this way your starting a command window and it calls the pdf starter.
Although not perfect, my solution in the end was to display the pdf in a webBrowser control, by setting the URL to the path of the PDF using
webBrowser1.Navigate("asdf.pdf");
this displayed it in adobe reader but within a web browser.
This solution suited my needs absolutely fine (if not better) and doesn't cause the application to crash. Thanks for everybody's suggestions.
I am getting a pretty common, "The process cannot access the file because it is being used by another process."
Now I am nearly certain that the only process accessing this file is from code that I have written and I've been careful to use a using statement around accessing it.
But to be 100% sure, is there anyway to check this programatically when this error occurs?
There is also a small tool handle.exe in Sysinternals Suite that does exactly what you need. Use it from the command line:
handle.exe -a <filename>
Of course under Vista and Windows 7 this tool must be run elevated.
e.g. oh.exe from the Resource Kit and Process Explorer from sysInternals both show who is using what
Both of them use techniques not available in C#.
Windows Vista adds a new function, GetRunningObjectTable which you can use to detect which program has a file open. This only works in Vista+, and it only works when the application that has the file open actually implements supports for IFileIsInUse (e.g. Office supports it, but most 3rd-party apps probably don't).
Other than that, the usual way this is implemented is by opening each process, enumerating the file handles they have open and searching for the filename in question. This is pretty low-level, and would require quite a bit of P/Invoke to implement in C#, but it's not impossible.
It's usually easy enough just to open up Process Explorer and let it do the search.