Check if Adobe Acrobat Reader installed - pdf in WebBrowser control - c#

I have an application that displays a pdf in a webBrowser control, using the following code
webBrowser1.Navigate(filename + "#toolbar=0");
It works perfectly if Adobe Reader is installed
I would like to check if Adobe Acrobat Reader is installed before displaying the window, or at least when trying to display the pdf.
I have adapted the following code from here Check Adobe Reader is installed (C#)?
As mentioned in the comments, unfortunately, it flags uninstalled versions as well.
I have also tried the 64 bit code in the same article but find errors I can't easily resolve and suspect would give the same result anyway as it simmply looks at the registry in a similar way.
using System;
using Microsoft.Win32;
namespace MyApp
{
class Program
{
static void Main(string[] args)
{
RegistryKey adobe = Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Adobe");
if(null == adobe)
{
var policies = Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Policies");
if (null == policies)
return;
adobe = policies.OpenSubKey("Adobe");
}
if (adobe != null)
{
RegistryKey acroRead = adobe.OpenSubKey("Acrobat Reader");
if (acroRead != null)
{
string[] acroReadVersions = acroRead.GetSubKeyNames();
Console.WriteLine("The following version(s) of Acrobat Reader are installed: ");
foreach (string versionNumber in acroReadVersions)
{
Console.WriteLine(versionNumber);
}
}
}
}
}
}
If Adobe pdf Reader is not loaded, a prompt appears to open(in any other installed reader), save the file or cancel.
I would like to be able to intercept this so as to Indicate that Adobe's reader is not available.
I have tried
private void webBrowser1_Navigating(object sender, WebBrowserNavigatingEventArgs e)
{
string url = e.Url.ToString();
if (url.StartsWith("res://ieframe.dll/navcancl.htm") && url.EndsWith("pdf"))
{
e.Cancel = true;
MessageBox.Show("Cannot open PDF!");
}
}
at the following https://social.msdn.microsoft.com/Forums/en-US/46aaeecd-5317-462a-ac36-9ebb30ba90e7/load-pdf-file-using-webbrowser-control-in-windows-form-c?forum=csharpgeneral
but found the open, save cancel event precedes the webBrowser1_Navigating.
I would appreciate any help in a reliable solution that will not flag uninstalled versions, or a seperate solution that will stop the open, save, cancel prompt, and allow me to create a message to install the reader
Thanks

The Simple Way is to use try / catch
in the try give the code.
The web browser will give error if there is an error so in the catch then say the user to install adobe reader.
Here is the sample
try
{
//your code goes her
}
catch(Exception ex)
{
if(ex.Message != null)
{
Console.WriteLine("Adobe Reader not Installed");
//this is where you say adobe reader is not installed
}
}

Related

How to handle System.Runtime.InteropServices.COMException (0x800706BA): The RPC server is unavailable. (Exception from HRESULT: 0x800706BA)

I am developing a Windows application where I manipulate Word Application. More specific, I am opening a Word Document but when I quit it and try to open another Word Document this Error comes out.
How to handle
System.Runtime.InteropServices.COMException (0x800706BA): The RPC server is unavailable. (Exception from HRESULT: 0x800706BA) at Microsoft.Office,Word.ApplicationClass.set_Visible(Boolean Prop)**
If I don't quit the Word Application this error does not come out.
Below I show you the functions that I open and quit the Word Application.
//function to open word Document located in a specific path
public static void openWordDocument(string fileName)
{
try
{
wordApplication.Visible = true;
string filePath = myPath + fileName;
WordApi.Document docx = wordApplication.Documents.Open(filePath);
}
catch (Exception ex)
{
MyLogger.Error(ex.ToString());
}
}
//function to quit wordApplication
public static void CloseWordApp() {
try {
Object wordAppObject = Marshal.GetActiveObject("Word.Application");
WordApi.Application wordApp = (WordApi.Application)wordAppObject; //cast Object to its actual type
wordApp.Quit();
}
catch (Exception ex) {
MyLogger.Error(ex.ToString());
}
I finally figured it out what is the problem.
The main problem was that when I quit it and try to open another Word Document,which opening another Word Document means get/create an Object of Word Application. In my case wordApp != null, after finalizing the application, so I had to create another Word Application Object and return it for the case.
//open word Document located in a specific path
public static void openWordDocument(string fileName)
{
try
{
wordApplication = createWordApplicationObject(wordApplication);
wordApplication.Visible = true;
string filePath = myPath + fileName;
WordApi.Document docx = wordApplication.Documents.Open(filePath);
}
catch (Exception ex)
{
MyLogger.Error(ex.ToString());
}
}
private static WordApi.Application createWordApplicationObject(WordApi.Application wordApp)
{
WordApi.Application wordAppFirstTime;
WordApi.Application wordApp1;
if (wordApp == null)
{
wordAppFirstTime = new WordApi.Application();
return wordAppFirstTime;
}
else
{
wordApp1 = new WordApi.Application();
return wordApp1;
}
}
With CloseWordApp() remain the same.
Most probably the exception is fired by the following line of code:
wordApplication.Visible = true;
You need to make sure the COM server is alive. Because after quitting the object becomes unavailable. I'd suggest setting such object references to null, so later we could check whether the application object is still alive. For example:
try
{
if (wordApplication == null)
{
wordApplication = new Word.Application();
}
wordApplication.Visible = true;
string filePath = myPath + fileName;
WordApi.Document docx = wordApplication.Documents.Open(filePath);
}
catch (Exception ex)
{
MyLogger.Error(ex.ToString());
}
I wanted to add a solution that works for me. We had this issue in a .net web service, along with other errors, like "the remote procedure call failed" on Word.Documents.Open(). i'll list all the things we tried, and finish with the solution.
we tried:
Make sure RPC service is up. Word is not corrupted, opens properly,
including the file we were opening.
restart server and service hosting the web application.
Rollback a windows update that occured the same day it stopped working.
Uninstalled the antivirus software.
We isolated the code to a third party app to validate it was the open()
method that caused the problem, and using different files as well. We
created a win form app, and consol app. We ran that small app as win
admin, a regular account as well as the account that runs the web
app.
We ran procMon.
we did a repair on word.
we installed Office all over, we tried 32 and 64bits version
Finale solution:
we deleted the user profile that runs the web app.
4 days to find that out. I'd thought i'd share my paine with the world. lol
while posting these lines, we are not sure why the local profile created this issue.

PrintQueue.AddJob hangs when printing to non-xps based printers

I am trying to print an xps document to printers (network printer, some virtual local printers, xps and non xps based) with the following code.
C# Source:
static void Main(string[] args)
{
PrintServer printServer = new PrintServer(#"\\printserver.csez.zohocorpin.com");
foreach (PrintQueue queue in printServer.GetPrintQueues())
{
Console.WriteLine("Printer: {0}, Port: {1}, ShareName: {2}, status: {3}, PrintingIsCancelled: {4}",
queue.Name, queue.QueuePort.Name, queue.ShareName, queue.QueueStatus, queue.PrintingIsCancelled);
Program program = new Program();
Thread printingThread = new Thread(() => program.Print_XPXFile(queue, #"D:\Assist\RemotePrint\Spool\Donalduck.xps"));
// Set the thread that will use PrintQueue.AddJob to single threading.
printingThread.SetApartmentState(ApartmentState.STA);
printingThread.Start();
printingThread.Join();
}
}
public void Print_XPXFile(PrintQueue pQueue, String FilePath)
{
// Create print server and print queue.
bool fastCopy = pQueue.IsXpsDevice;
FileInfo file = new FileInfo(FilePath);
if (!file.Exists)
{
Console.WriteLine("There is no such file.");
}
else
{
Console.WriteLine("Adding {0} to {1} queue. share name : {2}", FilePath, pQueue.Name, pQueue.ShareName);
try
{
// Print the Xps file while providing XPS validation and progress notifications.
PrintSystemJobInfo xpsPrintJob = pQueue.AddJob(file.Name, FilePath, fastCopy);
Console.WriteLine("Done adding.");
}
catch (PrintJobException e)
{
Console.WriteLine("\n\t{0} could not be added to the print queue.", file.Name);
if (e.InnerException.Message == "File contains corrupted data.")
{
Console.WriteLine("\tIt is not a valid XPS file."); // Use the isXPS Conformance Tool to debug it.
}
else
{
Console.WriteLine("\tmessage : {0}", e.InnerException.Message);
}
}
}
}
When printing to Microsoft XPS Document Writer, Microsoft Print to PDF, etc it works fine.
I found that it is working fine with all XPS based printers. I even installed a XPS sample printer driver and added a virtual local printer to confirm this claim and as expected it worked.
For non-xps based printers, it actually gets stuck in the AddJob function. It neither throws any exception, nor it moves to the next statement.
I developed the code based on this msdn resource.
What is the cause and solution?
All thoughts are welcome.
I can't seem to find the problem. But here is a more promising way to print XPS files:
public static void PrintXPSToDefaultPrinter(string FilePath)
{
try
{
// Create the print dialog object and set options
PrintDialog pDialog = new PrintDialog();
pDialog.PageRangeSelection = PageRangeSelection.AllPages;
pDialog.UserPageRangeEnabled = true;
FileInfo file = new FileInfo(FilePath);
XpsDocument xpsDocument = new XpsDocument(FilePath, FileAccess.ReadWrite);
FixedDocumentSequence fixedDocSeq = xpsDocument.GetFixedDocumentSequence();
pDialog.PrintDocument(fixedDocSeq.DocumentPaginator, file.Name);
}
catch (System.IO.IOException ex)
{
Console.WriteLine("The file is being used by some other process.");
}
catch (Exception ex)
{
Console.WriteLine("Exception occured : {0}", ex.Message);
}
}
You should call this in STA mode just like you have used:
static void Main(string[] args)
{
Thread printingThread = new Thread(() => PrintXPSToDefaultPrinter(#"D:\Assist\RemotePrint\Spool\Donalduck.xps"));
printingThread.SetApartmentState(ApartmentState.STA);
printingThread.Start();
}
You can also mention any printqueue u want in the pDialog.PrintQueue property.
Hope this helps. Enjoy man !!!
I, too, got stuck in the AddJob() method. The problem seemed to be more prevalent as Windows 10 platforms became involved.
Breaking the execution in debug, the stack trace showed the STA thread was blocked on a call to
MS.Internal.PrintWin32Thunk.XpsCompatiblePrinter.JobIdentifier.get
and this was further blocking on a low-level WaitOne() on some unknown synchronization object.
Even though details on this problem are thin (this is the only post I have found on the topic), THANKFULLY the accepted solution works extremely well (and I have been trying all kinds of things to print in WPF for years).
AddJob() is fully replaced in all respects. One can even control the PrintTicket more easily through the PrintDialog (to set paper size, orientation, one/two sided printing, etc.)

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();

check Acrobat reader installed in pc and display installer

my Manager asked about autoplay cd which has PDF files and check on user pc if adobe acrobat installed on user pc or not if it dosent installed message apear to install this program from cd I had windows application to check if adob reader or acrobat installed in pc or not I did that well but I want if this program wasnot installed acrobat reader installer apear from cd and user install this program.
public Form1()
{
RegistryKey adobe = Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Adobe");
if (adobe != null)
{
RegistryKey acroRead = adobe.OpenSubKey("Acrobat Reader");
if (acroRead != null)
{
string[] acroReadVersions = acroRead.GetSubKeyNames();
MessageBox.Show("The following version(s) of Acrobat Reader are installed: ");
foreach (string versionNumber in acroReadVersions)
{
MessageBox.Show(versionNumber);
}
}
}
else
{
MessageBox.Show("The following version(s) of Acrobat Reader arenot installed: ");
}
you need to invoke installer process. something like this.
Process myProcess = new Process();
myProcess.StartInfo.FileName = "path to acrobat installer";
myProcess.Start();
Better approach would be to add a custom action in your application setup for this.
There are several ways to check for this.
1/Check installed applications (win installer)
Each Windows installer project (MSI) has an updgrade code and a product code.
Simply put, the product code defines the version of the installed application and it's dependencies. The updgrade code stays the same over diffirent versions.
You can search the product code for acrobat reader and use windows installer dll to check if it is installed.
There is some code on codeproject (search for MsiInterop) which will has all the needed dllimports.
2/ Keep it simple.
Why not just check if there is an application associated with files that have a PDF extension??
If there is an associated application (may be something other than Acrobat Reader, e.g. foxit) assume everything is all right.
Otherwise, launch a browser pointing to http://get.adobe.com/reader/
This way, your application does not assume responsability over the user's choice of PDF reader.
Accessing the windows installer in C#:
public enum InstallState
{
NotUsed = -7,
BadConfig = -6,
Incomplete = -5,
SourceAbsent = -4,
MoreData = -3,
InvalidArg = -2,
Unknown = -1,
Broken = 0,
Advertised = 1,
Removed = 1,
Absent = 2,
Local = 3,
Source = 4,
Default = 5
}
[System.Runtime.InteropServices.DllImport("msi.dll", CharSet = CharSet.Unicode)]
internal static extern InstallState MsiQueryProductState(string szProduct);
If you know the product codes for Adobe Acrobat you can query its installation status:
bool acrobatInstalled = allAcrobatReaderProductCodes.Any(guid =>
{
var productCode = "{" + guid.ToString().ToUpper() + "}";
var msiState = MsiQueryProductState(productCode);
return msiState == InstallState.Local || msiState == InstallState.Default);
});
Where allAcrobatReaderCodes is an IEnumerable of all the acrobat reader product codes.

Adobe acrobat reader registry key location keeps changing

My requirement is to read a particular registry key related to Adobe acrobat reader and take a decision based on the value of that key.
Though this seems straightforward like I need to query the key using Registry class (for .NET)
and then take a decision based on the value.
However, the issue i face now is that, the registry key location keeps changing in almost every new version of Adobe Acrobat Reader.
All I can think of now is to have a switch case to handle for all the different Adobe versions in my code.
RegistryKey adobe = Registry.LocalMachine.OpenSubKey("Software").OpenSubKey("Adobe");
if (adobe != null)
{
RegistryKey acroRead = adobe.OpenSubKey("Acrobat Reader");
if (acroRead != null)
{
string[] acroReadVersions = acroRead.GetSubKeyNames();
Console.WriteLine("The following version(s) of Acrobat Reader are installed: ");
foreach (string versionNumber in acroReadVersions)
{
switch(versionNumber)
{
case 1.x = //do something;
//break;
case 2.x = //do something;
//break;
case 6.x = //do something;
//break;
case 9.x = //do something;
//break;
}
}
}
}
But some im not satisfied with this approach. Every time Adobe releases a new version i have to make sure i have to handle it differently. Any suggestions for a better approach.
Thanks
you best hope is to open the registry key containing the version numbers, then enumerate each sub key, possibly validating it looks like a version number, then look in each of those subkeys for the thing that you want. You might want to only use the highest number version that you find.
Obviously this will only work if what you want is always contained in the same registry entry relative to the version key, or always in the same named entry (and you would then have to enumerate every element under the sub key looking for the thing you want).
if the thing you want changes name and location in every release then you will have a problem, unless you can somehow recognize it from the data, in which case enumerate every element and look at the4 data and try to decide if it is what you want, but this approach is likely to be fraught with danger or false positives.
Well, I have the exact same problem and since I know Adobe is not so brilliant in their decisions and makings, I think I will try this approach:
public static string AcrobatReaderPath
{
get
{
var paths = new List<string>()
{
Registry.GetValue(#"HKEY_CLASSES_ROOT\Software\Adobe\Acrobat\Exe", "", #"C:\Program Files (x86)\Adobe\Reader 10.0\Reader\AcroRd32.exe") as string
};
var files = Directory.GetFileSystemEntries(#"C:\Program Files (x86)\Adobe\", #"*Acr*R*d*32.exe", SearchOption.AllDirectories);
paths.AddRange(files);
foreach(var path in paths) if (File.Exists(path)) return path;
return "";
}
}
My registry has nothing related to Acrobat at :
HKEY_LOCAL_MACHINE\SOFTWARE\Adobe\
..so it seems Adobe is moving their registry keys all over the registry with time passing...
I just hope they will avoid moving Acrobat itself outside the Program Files folder in the future... (you never know with these people...)
I think you can apply following logic:
adobe file associations are kept in registry - you can read them under
HKEY_CLASSES_ROOT \ .pdf \ OpenWithList
Those subkeys are app names (if any):
Acrobat.exe
AcroRD32.exe
etc.
Use them to combine and read keys (either Open or Read should be present)
HKEY_CLASSES_ROOT\Applications\XXXX\shell\Open\command
HKEY_CLASSES_ROOT\Applications\XXXX\shell\Read\command
If present, they would be similar to
"C:\Program Files (x86)\Adobe\Acrobat 7.0\Acrobat\Acrobat.exe" "%1"
from where you can strip %1 and get adobe app path.
Here is C# code:
private void AddShellCommandDefault(List<string> lst, RegistryKey shell, string reg KeyOpenRead)
{
var shellOpen = shell.OpenSubKey(regKeyOpenRead);
if (shellOpen == null) return;
var shellOpenCommand = shellOpen.OpenSubKey("command");
if (shellOpenCommand == null) return;
var defaultVal = shellOpenCommand.GetValue(null);
if (defaultVal == null) return;
int kex = defaultVal.ToString().LastIndexOf(".exe", StringComparison.OrdinalIgnoreCase);
if (kex < 0) return;
lst.Add(defaultVal.ToString().Substring(0, kex).Replace("\"", "") + ".exe");
}
public List<string> GetAdobeApps()
{
var addobeList = new List<string>();
// HKEY_CLASSES_ROOT\.pdf\OpenWithList\Acrobat.exe
// HKEY_CLASSES_ROOT\Applications\Acrobat.exe\shell\Open\command
// default "C:\Program Files (x86)\Adobe\Acrobat 7.0\Acrobat\Acrobat.exe" "%1"
var adobe = Registry.ClassesRoot.OpenSubKey(".pdf");
if (adobe == null) return addobeList;
var openWith = adobe.OpenSubKey("OpenWithList");
if (openWith == null) return addobeList;
var apps = Registry.ClassesRoot.OpenSubKey("Applications");
if (apps == null) return addobeList;
foreach (string sLong in openWith.GetSubKeyNames())
{
string s = sLong.Split(#"\/".ToCharArray()).Last();
var adobeApp = apps.OpenSubKey(s);
if (adobeApp == null) continue;
var shell = adobeApp.OpenSubKey("shell");
if (shell == null) continue;
AddShellCommandDefault(addobeList, shell, "Read");
AddShellCommandDefault(addobeList, shell, "Open");
}
return addobeList;
}
When run GetAdobeApps, it returns collection similar to
Count = 2
[0]: "C:\\Program Files (x86)\\Adobe\\Acrobat 7.0\\Acrobat\\Acrobat.exe"
[1]: "C:\\Program Files (x86)\\Adobe\\Reader 9.0\\Reader\\AcroRd32.exe"

Categories