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"
Related
using (var openFileDialog1 = new OpenFileDialog())
{
openFileDialog1.Reset();
if (!string.IsNullOrEmpty(ExcelFilePath))
{
string fileName = Path.GetFileName(ExcelFilePath);
string fileExt = Path.GetExtension(ExcelFilePath);
//Avoid "you can't open this location using this program file" dialog
//if there is a file name in the path strip it )
if (!string.IsNullOrEmpty(fileName))
initialDirectory = Path.GetDirectoryName(ExcelFilePath);
//if not let it be
else
initialDirectory = ExcelFilePath;
openFileDialog1.InitialDirectory = initialDirectory;
}
else
openFileDialog1.InitialDirectory = "c:\\";
openFileDialog1.Filter = "Excel files (*.xls or *.xlsx)|*.xls;*.xlsx";
//openFileDialog1.Filter = "xls files (*.xls)|*.xls|xlsx files(*.xlsx)|.xlsx";
openFileDialog1.FilterIndex = 2;
openFileDialog1.RestoreDirectory = false;
openFileDialog1.CheckFileExists = true;
openFileDialog1.CheckPathExists = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
var browseSelectionMade = BrowseSelectionMade;
if (browseSelectionMade!=null)
browseSelectionMade(this, new DataEventArgs<string>(openFileDialog1.FileName));
}
}
Regardless of whether or not I set RestoreDirectory to true, I will always browse to the LAST used directory if my initial directory is set to a path that doesn't exist. Where is the last used directory saved by OpenFileDialog? And is there a way to override this behavior? (e.g. I always want to set it to C:\ if the initial directory doesn't exist?)
It seems like all you need to do is the following:
string path; // this is the path that you are checking.
if(Directory.Exists(path)) {
openFileDialog1.InitialDirectory = path;
} else {
openFileDialog1.InitialDirectory = #"C:\";
}
That is unless I'm missing something.
Where is the last used directory saved?
It is stored in the registry. The exact location depends on the Windows version, for Win7 it is HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32. A quick look with regedit ought to convince you that you don't want to mess with that.
The simple workaround is to provide a valid path. If the one you calculate isn't valid, Directory.Exists returns false, then provide a valid one. Like the Documents folder returned by Environment.GetFolderPath(). Then again, nothing wrong with the last used one either, the user will easily recognize it with good odds that it happens to be close to the desired one.
I don't think there is anything built in for that. Just check before you open the dialog:
if (!Directory.Exists(initialDirectory))
{
openFileDialog1.InitialDirectory = #"C:\";
}
Check to see if the ExcelFilePath exists, you check to see if it's null or empty, however if before your block you check to see if the directory exists, and if it doesn't reset the value to an empty string you should be golden.
(yes you'll need to apply your file name logic etc earlier) however once you've parsed all of that out, it's trivial to determine if the directory exits
if (!Directory.Exists(excelPath))
{
ExcelFilePath = String.Empty;
}
Also, to set the default extension you should set FilterIndex property instead of DefaultExt. see: https://stackoverflow.com/a/6104319/381082
Here's a good article on the OpenFileDialog in C#: http://www.c-sharpcorner.com/uploadfile/mahesh/openfiledialog-in-C-Sharp/
For future me
remember to do:
try
{
result = dialog.ShowDialog(Window);
}
catch
{
dialog.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
result = dialog.ShowDialog(Window);
}
This helps in the situation when user opened file from location, that does not longer exists (ex. USB stick, mapped network drive) - ShowDialog throws exception if InitialDirectory is invalid.
Edit: After consulting with a more knowledgeable friend, perhaps the better solution is, in hindsight, obvious. Just store your own registry key in HKEY_CURRENT_USER\SOFTWARE\YourCompanyOrAppName\Whatevs or something similar (not sure on best practices, or which folders you have read/write access to, do your own research in that) and avoid this problem altogether. By simply letting the user navigate to where they want once, and then storing the path in the registry (as a normal string, and not a PIDL) and retrieving that path the next time. For reference, see the MSDN articles on the Registry and RegistryKey classes, and their example in the RegistryKey/Methods/SetValue article. Still, I'll leave this post as is, as a point of curiosity, or if someone has a very specific problem and needs this solution. As always, good luck!
For any poor soul wandering through here in the future, it seems I figured out how to find the last used directory. Just like stated previously, it's stored in the registry, more specifically in HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\ComDlg32\OpenSavePidlMRU\
Here is a set of folders for each file extension, including a "*" for unknown file extensions. I'll do this for txt files, change the path as needed. To access this path we make a RegistryKey and call OpenSubKey (BTW, full code below)
string RegistryPath = #"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\txt";
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath);
In here is a set of entries which all contain PIDLs (we'll get to that) of the last opened or saved items.
DO NOTE: the folder name OpenSavePidlMRU, i've seen called just OpenSaveMRU, and seems to be for versions older than win 10.
Also in here is an entry called "MRUListEx", MRU stands for "Most Recently Used". In this entry is an index of which item was... well, most recently used. So if I have 10 entries, named 0 to 9, and 9 was the last used, the first byte in MRUListEx will be 0x09. So for:
byte[] mrulistex = (byte[])rk.GetValue("MRUListEx");
byte Last = mrulistex[0];
Last will equal 0x09 (on my system)
Then we call GetValue again but for that entry
byte[] LastPathByteArray = (byte[])rk.GetValue(Last.ToString());
And here's where things get problematic, as this won't return a byte array where each byte is a character in our filepath, it returns what's known as a PIDL. While the byte array will seem to contain the path, in both char and wide char, it also contains a bunch of gibberish that can't be easily converted.
I won't pretend to understand it, but https://stackoverflow.com/a/4318663 provides a way to convert this to a string. (see code below)
string LastPath = GetPathFromPIDL(LastPathByteArray);
And we're done. PLEASE NOTE this doesn't necessarily represent a good solution, but I wasn't able to find much official documentation on this in my half hour of digging. And obviously this code doesn't check if the registry path is correct, if the registry keys exist, or do much error checking at all, but this does at least work.
using Microsoft.Win32; //for the registry class
using System.Runtime.InteropServices; //for converting the PIDL
//GetPathFromPIDL from matt.schechtman at https://stackoverflow.com/a/4318663
[DllImport("shell32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SHGetPathFromIDListW(IntPtr pidl, MarshalAs(UnmanagedType.LPTStr)] StringBuilder pszPath);
private string GetPathFromPIDL(byte[] byteCode)
{
//MAX_PATH = 260
StringBuilder builder = new StringBuilder(260);
IntPtr ptr = IntPtr.Zero;
GCHandle h0 = GCHandle.Alloc(byteCode, GCHandleType.Pinned);
try
{
ptr = h0.AddrOfPinnedObject();
}
finally
{
h0.Free();
}
SHGetPathFromIDListW(ptr, builder);
return builder.ToString();
}
public void OnClick_Button_OpenFile(object sender, RoutedEventArgs e)
{
string RegistryPath = #"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\OpenSavePidlMRU\\txt";
RegistryKey rk = Registry.CurrentUser.OpenSubKey(RegistryPath);
byte[] mrulistex = (byte[])rk.GetValue("MRUListEx");
byte Last = mrulistex[0];
byte[] LastPathByteArray = (byte[])rk.GetValue(Last.ToString());
string LastPath = GetPathFromPIDL(LastPathByteArray);
// Configure open file dialog box
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();`
dlg.InitialDirectory = LastPath;
result = dlg.ShowDialog();
if (result == true)
{
string filename = dlg.FileName;
}
//etc etc, rest of your code
}
Good luck.
In case you're using file name stored in some string, it's better to use Path to cut the file name (on my W10 the open dialog doesn't open in initial directory, if I supply just file name):
if (!System.IO.Directory.Exists(filename))
{
openDlg.InitialDirectory =
System.IO.Path.GetDirectoryName(filename);
}
i want to find out that mspaint shortcut exist in desktop or no? if its exist, user score is 7 else its 0. i use this code:
string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
if (Directory.Exist (path + #"\mspaint.exe"))
{
Controller.ExamController.AddExam(1, n, 7, time, Session.currentUserId);
}
else
{
Controller.ExamController.AddExam(1, n, 0, time, Session.currentUserId);
}
but anyway the result is "0".but this code works for directory and folders and return 7.
also i try "File.Exist" but it has same problem.
How can i check a shortcut of specific program exist in desktop or no?
if (questionNumber == 2)
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var list = Directory.EnumerateFiles(path);
foreach (var v in list)
{
var extension = Path.GetExtension(v);
if (extension.Equals(".lnk", StringComparison.InvariantCultureIgnoreCase))
{
WshShell shell = new WshShell();
IWshShortcut link = (IWshShortcut)shell.CreateShortcut(v);
if (Path.GetFileName(link.TargetPath).Equals("mspaint.exe", StringComparison.InvariantCultureIgnoreCase))
{
Controller.ExamController.AddExam(1, n, 7, time, Session.currentUserId);
}
else
{
Controller.ExamController.AddExam(1, n, 0, time, Session.currentUserId);
}
}
}
}
when i use this, its ok, but it returns 0 for not exist and return both of 0 and 7 for exist
Shortcut is just another type of files, as MSDN says:
When the user creates a shortcut to an object by choosing the Create
Shortcut command from the object's shortcut menu, Windows stores the
information it needs to access the object in a link file—a binary file
that has the .lnk file name extension.
It mean that you should refer exactly to shortcut: with exact name and .lnk extension.
You need to check shortcut for example like this:
File.Exist(Path.Combine(path, "Paint.lnk"))
But in my opinion right solution is to get all shortcuts from desktop and examine target path for each one for mspaint.exe path.
For reading shortcut information read this SO post: Get target of shortcut folder
This needs explicit coding and you cannot look for names of the file in deskTop since it can be changed to anything because its just a short cut,
Include the COM addin reference Windows Script Host Object Model - Interop.IWshRuntimeLibrary
using IWshRuntimeLibrary;
public string test()
{
string path = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
var list = Directory.EnumerateFiles(path);
foreach(var v in list)
{
var extension = Path.GetExtension(v);
if (extension.Equals(".lnk", StringComparison.InvariantCultureIgnoreCase))
{
WshShell shell = new WshShell();
IWshShortcut link = (IWshShortcut) shell.CreateShortcut(v);
if (Path.GetFileName(link.TargetPath).Equals("mspaint.exe", StringComparison.InvariantCultureIgnoreCase))
{
return link.TargetPath;
}
}
}
return null;
}
A Shortcut is a special kind of file
A shortcut is a special kind of file. It contains data which points to a location (for example mspaint.exe), but that doesn't mean it needs to be named the same as the exe it's pointing to. For example, it can have a name of "HappyPaint.lnk" and point to "mspaint.exe".
Reading Shortcut Destination
Therefore you need to look for all "*.lnk" files on the desktop and read their destination paths. Here's how you can go about it:
First, add a reference to Microsoft Shell Controls And Automation:
Second, add some code along the lines of:
string desktopDirectoryPath = Environment.GetFolderPath(
Environment.SpecialFolder.DesktopDirectory);
string msPaintPath = Environment.ExpandEnvironmentVariables(
#"%windir%\system32\mspaint.exe");
// add reference to COM --> Microsoft Shell controls and Automation
Shell shell = new Shell();
Folder folder = shell.NameSpace(desktopDirectoryPath);
var shortcutFilePaths = Directory.GetFiles(desktopDirectoryPath, "*.lnk");
bool msPaintShortcutExists = false;
foreach (string shortcutFilePath in shortcutFilePaths)
{
FolderItem folderItem = folder.ParseName(Path.GetFileName(shortcutFilePath));
Shell32.ShellLinkObject link = (Shell32.ShellLinkObject) folderItem.GetLink;
var shortcutDestination = Environment.ExpandEnvironmentVariables(link.Path);
if (string.Compare(
msPaintPath, shortcutDestination, StringComparison.OrdinalIgnoreCase) == 0)
{
msPaintShortcutExists = true;
break;
}
}
if (msPaintShortcutExists)
{
Controller.ExamController.AddExam(1, n, 7, time, Session.currentUserId);
}
else
{
Controller.ExamController.AddExam(1, n, 0, time, Session.currentUserId);
}
Needs to be run in an STAThread
Note: In case an InvalidCastException with a message
Unable to cast COM object of type 'System.__ComObject' to interface type 'Shell32.Shell'. This operation failed because the QueryInterface call on the COM component for the interface with IID '{286E6F1B-7113-4355-9562-96B7E9D64C54}' failed due to the following error: No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).
occurs on new Shell(); you're not running the code in an STAThread but it needs to be run in an STAThread. An easy work around is to add the following method:
private static void ExecuteInStaThread(Action a)
{
var thread = new Thread(() => a());
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
if (!thread.Join(TimeSpan.FromSeconds(30)))
{
thread.Abort();
}
}
and wrap the code in a call to it:
ExecuteInStaThread(() =>
{
string desktopDirectoryPath = ...
...
});
I would like to get the filename of a font. This can't be that hard... I am aware, there is a very similar question already, but the answer to that question just can't be it.
What I want to do is to send a Font file over TCP/IP to an other client, if he requests it. I select the desired font over a FontDialog, I can get the FontName from the framework. I can't find the font file in a way that I can say will work most of the time.
Where does .NET know which fonts are installed on the system? It can't be that the framework relies on a solution which does not work all the time, like the solution on CodeProject and suggested in Stackoverflow. There must be a secure way to retrieve the font file. The FontDialog can list them all in a box and the fonts installed must have a path to their file.
Anyone interested in helping me?
using System;
using System.Drawing;
using System.Text.RegularExpressions;
using Microsoft.Win32
public static string GetSystemFontFileName(Font font)
{
RegistryKey fonts = null;
try{
fonts = Registry.LocalMachine.OpenSubKey(#"Software\Microsoft\Windows NT\CurrentVersion\Fonts", false);
if(fonts == null)
{
fonts = Registry.LocalMachine.OpenSubKey(#"Software\Microsoft\Windows\CurrentVersion\Fonts", false);
if(fonts == null)
{
throw new Exception("Can't find font registry database.");
}
}
string suffix = "";
if(font.Bold)
suffix += "(?: Bold)?";
if(font.Italic)
suffix += "(?: Italic)?";
var regex = new Regex(#"^(?:.+ & )?"+Regex.Escape(font.Name)+#"(?: & .+)?(?<suffix>"+suffix+#") \(TrueType\)$");
string[] names = fonts.GetValueNames();
string name = names.Select(n => regex.Match(n)).Where(m => m.Success).OrderByDescending(m => m.Groups["suffix"].Length).Select(m => m.Value).FirstOrDefault();
if(name != null)
{
return fonts.GetValue(name).ToString();
}else{
return null;
}
}finally{
if(fonts != null)
{
fonts.Dispose();
}
}
}
For one, your problem describes issues with Windows OS. Hence your solution needs to be a Windows specific solution. In your comment you mentioned that the solution may not work on other OS.
It surely WILL NOT work.
Each OS will needs to be handled separately. Also, you can't assume that installation of fonts will happen in the same way on client's OS.
As for the problem with getting font file names. There is nothing wrong with the solutions provided on CP. In many instances the only way to get something in windows is to make API calls. .Net simply has no support for a number of things we may need to do. So relying on API is doesn't make it automatically wrong or undesirable.
EDIT:
In .NET 4.0 Fonts is a special folder that can be accessed like so
var fontsFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.Fonts);
Dictionary<string, List<string>> _fontNameToFiles;
/// <summary>
/// This is a brute force way of finding the files that represent a particular
/// font family.
/// The first call may be quite slow.
/// Only finds font files that are installed in the standard directory.
/// Will not discover font files installed after the first call.
/// </summary>
/// <returns>enumeration of file paths (possibly none) that contain data
/// for the specified font name</returns>
private IEnumerable<string> GetFilesForFont(string fontName)
{
if (_fontNameToFiles == null)
{
_fontNameToFiles = new Dictionary<string, List<string>>();
foreach (var fontFile in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.Fonts)))
{
var fc = new PrivateFontCollection();
try
{
fc.AddFontFile(fontFile);
}
catch (FileNotFoundException)
{
continue; // not sure how this can happen but I've seen it.
}
var name = fc.Families[0].Name;
// If you care about bold, italic, etc, you can filter here.
List<string> files;
if (!_fontNameToFiles.TryGetValue(name, out files))
{
files = new List<string>();
_fontNameToFiles[name] = files;
}
files.Add(fontFile);
}
}
List<string> result;
if (!_fontNameToFiles.TryGetValue(fontName, out result))
return new string[0];
return result;
}
We're currently developing a plug-in for a piece of software in C#. This plug-in extracts the contents of a PST file and stores all items in it as text files (with the exception of attachments, which are stored as their type in the same folder as the email).
It has been working without issue until we tested it on Windows 7 w/ Outlook 2K7. After running the same previous job on a machine with Outlook 2000 on it, we noticed that there were over 12,000 files missing. These files turned out to be attachments (mostly URLs)
We found that the issue is that Outlook 2K7 blocks attachments with specific extensions. If you open the email in Outlook itself, you see a blue bar at the top stating "Outlook blocked access to the following potentially unsafe attachments" and all the attachments in the emails.
Is there a way to programmatically get these attachments without Outlook blocking them?
The code we use to save the attachments is:
private void saveAttachment(ref object oEmail, StoreInfo currentStoreInfo, string sEmailID, string sExportPath)
{
int iAttachCount = 0;
object oAttach = null;
oAttach = getNextAttachment(oEmail, ref iAttachCount);
while (oAttach != null)
{
saveAttachment(sEmailID, sExportPath, oAttach);
oAttach = getNextAttachment(oEmail, ref iAttachCount);
}
}
private object getNextAttachment(object oEmail, ref int iAttachCount)
{
object oAttach = null;
try
{
iAttachCount++;
oAttach = GetProperty(oEmail, "Attachments", new object[] { iAttachCount });
}
catch //(Exception ex)
{
// There was no attachment to be gotten
oAttach = null;
}
return oAttach;
}
Just putting this on here in case anybody else runs into the same issue. Outlook 2K7 has a Level1 file type feature that blocks access to attachments with specific extensions.
However, you can change the registry to allow access to these files. Just remember to set it back to the way it was prior to you modifying it for security's sake.
private void SetLevel1RemoveValues()
{
// Get the base key for the current user HKEY_CURRENT_USER
Microsoft.Win32.RegistryKey regKey = Microsoft.Win32.RegistryKey.OpenRemoteBaseKey(Microsoft.Win32.RegistryHive.CurrentUser, "");
// Get the security key from the registry
Microsoft.Win32.RegistryKey subKey = regKey.OpenSubKey("Software\\Microsoft\\Office\\" + sCurrentOutlookVersion + ".0\\Outlook\\Security", true);
// If the Outlook\Security key doesn't exit, create one
if (subKey == null)
subKey = regKey.CreateSubKey("Software\\Microsoft\\Office\\" + sCurrentOutlookVersion + ".0\\Outlook\\Security");
// Loop through each Value in the registry to see if the Level1Remove key already exists.
string[] sValues = subKey.GetValueNames();
bool bHasLevel1RemoveKey = false;
foreach (string sValue in sValues)
if (sValue == "Level1Remove")
bHasLevel1RemoveKey = true;
// If the key already exists, store the data so we can reset it later
if (bHasLevel1RemoveKey)
sPrevLevel1RemoveValues = subKey.GetValue("Level1Remove").ToString();
else
sPrevLevel1RemoveValues = "";
// Create an array of all Level 1 Extensions
string[] level1Extensions = new string[] { ".ade", ".adp", ".app", ".asp", ".bas", ".bat",
".cer", ".chm", ".cmd", ".com", ".cpl", ".crt", ".csh", ".exe", ".fxp", ".gadget",
".hlp", ".hta", ".inf", ".ins", ".isp", ".its", ".js", ".jse", ".ksh", ".lnk",
".mad", ".maf", ".mag", ".mam", ".maq", ".mar", ".mas", ".mat", ".mau", ".mav", ".maw",
".mda", ".mdb", ".mde", ".mdt", ".mdw", ".mdz", ".msc", ".msi", ".msp", ".mst", ".ops",
".pcd", ".pif", ".pfr", ".prg", ".pst", ".reg", ".scf", ".scr", ".sct", ".shb", ".shs",
".tmp", ".url", ".vb", ".vbe", ".vbs", ".vsmacros", ".vss", ".vst", ".vsw",
".ws", ".wsc", ".wsf", ".wsh" };
// Set the value in the registry to the list of all Level 1 extensions so we can extract them
subKey.SetValue("Level1Remove", string.Join(";", level1Extensions));
// Close (and save) the values
subKey.Close();
regKey.Close();
}
How can I get the location of the tnsnames.ora file by code, in a machine with the Oracle client installed?
Is there a windows registry key indicating the location of this file?
Some years ago I had the same problem.
Back then I had to support Oracle 9 and 10 so the code only takes care of those versions, but maybe it saves you from some research.
The idea is to:
search the registry to determine the oracle client version
try to find the ORACLE_HOME
finally get the tnsnames from HOME
public enum OracleVersion
{
Oracle9,
Oracle10,
Oracle0
};
private OracleVersion GetOracleVersion()
{
RegistryKey rgkLM = Registry.LocalMachine;
RegistryKey rgkAllHome = rgkLM.OpenSubKey(#"SOFTWARE\ORACLE\ALL_HOMES");
/*
* 10g Installationen don't have an ALL_HOMES key
* Try to find HOME at SOFTWARE\ORACLE\
* 10g homes start with KEY_
*/
string[] okeys = rgkLM.OpenSubKey(#"SOFTWARE\ORACLE").GetSubKeyNames();
foreach (string okey in okeys)
{
if (okey.StartsWith("KEY_"))
return OracleVersion.Oracle10;
}
if (rgkAllHome != null)
{
string strLastHome = "";
object objLastHome = rgkAllHome.GetValue("LAST_HOME");
strLastHome = objLastHome.ToString();
RegistryKey rgkActualHome = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\ORACLE\HOME" + strLastHome);
string strOraHome = "";
object objOraHome = rgkActualHome.GetValue("ORACLE_HOME");
string strOracleHome = strOraHome = objOraHome.ToString();
return OracleVersion.Oracle9;
}
return OracleVersion.Oracle0;
}
private string GetOracleHome()
{
RegistryKey rgkLM = Registry.LocalMachine;
RegistryKey rgkAllHome = rgkLM.OpenSubKey(#"SOFTWARE\ORACLE\ALL_HOMES");
OracleVersion ov = this.GetOracleVersion();
switch(ov)
{
case OracleVersion.Oracle10:
{
string[] okeys = rgkLM.OpenSubKey(#"SOFTWARE\ORACLE").GetSubKeyNames();
foreach (string okey in okeys)
{
if (okey.StartsWith("KEY_"))
{
return rgkLM.OpenSubKey(#"SOFTWARE\ORACLE\" + okey).GetValue("ORACLE_HOME") as string;
}
}
throw new Exception("No Oracle Home found");
}
case OracleVersion.Oracle9:
{
string strLastHome = "";
object objLastHome = rgkAllHome.GetValue("LAST_HOME");
strLastHome = objLastHome.ToString();
RegistryKey rgkActualHome = Registry.LocalMachine.OpenSubKey(#"SOFTWARE\ORACLE\HOME" + strLastHome);
string strOraHome = "";
object objOraHome = rgkActualHome.GetValue("ORACLE_HOME");
string strOracleHome = strOraHome = objOraHome.ToString();
return strOraHome;
}
default:
{
throw new Exception("No supported Oracle Installation found");
}
}
}
public string GetTNSNAMESORAFilePath()
{
string strOracleHome = GetOracleHome();
if (strOracleHome != "")
{
string strTNSNAMESORAFilePath = strOracleHome + #"\NETWORK\ADMIN\TNSNAMES.ORA";
if (File.Exists(strTNSNAMESORAFilePath))
{
return strTNSNAMESORAFilePath;
}
else
{
strTNSNAMESORAFilePath = strOracleHome + #"\NET80\ADMIN\TNSNAMES.ORA";
if (File.Exists(strTNSNAMESORAFilePath))
{
return strTNSNAMESORAFilePath;
}
else
{
throw new SystemException("Could not find tnsnames.ora");
}
}
}
else
{
throw new SystemException("Could not determine ORAHOME");
}
}
On Windows, the most likely locations are either %ORACLE_HOME%/network/admin or %TNS_ADMIN% (or the TNS_ADMIN registry setting). These two cover almost every installation.
Of course it is possible to have a working Oracle client without this file. Oracle has bewildering array of networking options, and there are plenty of ways to achieve a working setup with using TNSNAMES. Depending on what you are trying to achieve here, your first port of call might be the sqlnet.ora file, which is also found in %ORACLE_HOME%/network/admin. This should contain a line that looks something like this:
NAMES.DIRECTORY_PATH= (LDAP, TNSNAMES, HOSTNAME)
TNSNAMES means it will use the TNSNAMES.ora file (second in this case). LDAP and HOSTNAME are alternate ways of resolving the database. If there is no TNSNAMES the TNSNAMES.ora file will be ignored if it exists in the right place.
In C# / .NET this should get you the environment variables:
Environment.GetEnvironmentVariable("ORACLE_HOME");
Environment.GetEnvironmentVariable("TNS_ADMIN");
List<string> logicalDrives = Directory.GetLogicalDrives().ToList();
List<string> result = new List<string>();
foreach (string drive in logicalDrives)
{
Console.WriteLine("Searching " + drive);
DriveInfo di = new DriveInfo(drive);
if(di.IsReady)
result = Directory.GetFiles(drive, "tnsnames.ora", SearchOption.AllDirectories).ToList();
if (0 < result.Count) return;
}
foreach (string file in result) { Console.WriteLine(result); }
According to the net that depends on the version of Oracle and the working directory of the SQL*Plus process. This first link tells you the environment variable that specifies the base path for some versions (7, 8, 9i) of Oracle. If you use a different one, I'm sure there's a similar way to get to the system directory.
If you spread versions of these files all over the place though and rely on the "look for a local tnsnames.ora first" behaviour of the client, then I guess you're out of luck.
I'm not a C# or a Windows guy for that matter so hopefully this helps. The tnsnames.ora file should be located in:
ORACLE_HOME\network\admin
If an alternate location has been specified, it should be available via the TNS_ADMIN registry key.
See this link for more information on how Oracle handles tns names on Windows.