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();
}
Related
Environment: I have a windows console application and I am running the exe from command line.
Below is my code:
static void Main(string[] args)
{
CreatePSTUsingRedemption(args[0], args[1]);
}
private static void CreatePSTUsingRedemption(string messageFilePath, string pstPath)
{
RDOSession pstSession = new RDOSession();
RDOPstStore store = null;
store = pstSession.LogonPstStore(pstPath, 1, "combinedPST");
//actually there is a loop here to loop through each message files.
RDOMail rdo_Mail = pstSession.GetMessageFromMsgFile(messageFilePath);
rdo_Mail.CopyTo(store.IPMRootFolder);
rdo_Mail.Save();
store.Save();
completedCount++;
Console.WriteLine("FILES_PROCESSED:" + completedCount);
pstSession.Logoff();
}
Main purpose of this code is to create a single pst file combining email message(.msg) files.
Now when I run the exe, a pst file is created in the given location and its size keeps increasing as the code runs. After all the message files are processed application exists. There is no any error. Now, when I try to load this pst file in Outlook 2013. Pst is empty and its size is also reduced to 265KB every time. I don't think any process is using this pst because I can copy and move it anywhere I want. What might be the issue? Any suggestion, please?
UPDATE 1
private static void CreatePSTUsingRedemption(XmlNodeList nodelist, string pstPath)
{
System.Diagnostics.Debugger.Launch();
RDOSession pstSession = null;
RDOPstStore store = null;
RDOFolder folder = null;
RDOMail rdo_Mail = null;
try
{
pstSession = new RDOSession();
store = pstSession.LogonPstStore(pstPath, 1, Path.GetFileNameWithoutExtension(pstPath));
var enumerator = store.IPMRootFolder.Folders.GetEnumerator(); //DELETE DEFAULT FOLDERS
while (enumerator.MoveNext())
{
var defaultFolders = enumerator.Current as RDOFolder;
defaultFolders.Delete();
}
int completedCount = 0;
folder = store.IPMRootFolder;
foreach (XmlNode node in nodelist)
{
rdo_Mail = pstSession.GetMessageFromMsgFile(node["FullPath"].InnerText);
rdo_Mail.CopyTo(folder);
rdo_Mail.Save();
store.Save();
completedCount++;
Console.WriteLine("FILES_PROCESSED:" + completedCount);
}
}
finally
{
Marshal.ReleaseComObject(rdo_Mail);
Marshal.ReleaseComObject(folder);
Marshal.ReleaseComObject(store);
}
pstSession.Logoff();
Marshal.ReleaseComObject(pstSession);
GC.Collect();
}
Above is my code for the actual loop. I am loading all the email messages file path from an xml file. I still encounter same issue as above.
This is an indication that the PST store is not fully flushed to the disk and Outlook "fixes' the PST file by resetting it.
Try to explicitly release all Redemption objects first before logging off and call GC.Collect(). If you have a loop processing multiple files, release the message on each step of the loop.
private static void CreatePSTUsingRedemption(string messageFilePath, string pstPath)
{
RDOSession pstSession;
try
{
RDOPstStore store;
RDOFolder folder;
RDOMail rdo_Mail;
pstSession = new RDOSession();
store = pstSession.LogonPstStore(pstPath, 1, "combinedPST");
//actually there is a loop here to loop through each message files.
rdo_Mail = pstSession.GetMessageFromMsgFile(messageFilePath);
folder = store.IPMRootFolder;
rdo_Mail.CopyTo(folder);
rdo_Mail.Save();
store.Save();
completedCount++;
Console.WriteLine("FILES_PROCESSED:" + completedCount);
}
finally
{
Marshal.ReleaseComObject(rdo_Mail);
Marshal.ReleaseComObject(folder);
Marshal.ReleaseComObject(store);
}
pstSession.Logoff();
Marshal.ReleaseComObject(pstSession);
GC.Collect();
}
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 develop an outlook add-in using Visual studio 2013 and Add-in express v.7.7.4087.
I have to deal with multiple email accounts (stores). Please see following snapshot and code
private void timerSendFromDraftsFolder_Tick(object sender, EventArgs e)
{
Outlook.Stores stores = null; // CC and OL accounts,
Outlook.Store store = null;
Outlook.MAPIFolder rootFolder = null;
Outlook.Folders rootFolderFolders = null;
Outlook.MAPIFolder draftsFolder = null;
Outlook.Items items = null;
Outlook.MailItem mailItem = null;
bool itemSent = true;
bool allMailItemsSent = true;
try
{
if (Helper.IsOnline())
{
Debug.DebugMessage(3, "AddinModule : timerSendFromSaleswingsFolder_Tick : Fired");
string version = OutlookApp.Version;
if (String.Compare(version, "13") > 0)
{
stores = Globals.ObjNS.Stores;
for (int i = 1; i <= stores.Count; i++)
{
try
{
store = stores[i];
string storeName = store.DisplayName;
if (store.ExchangeStoreType != Outlook.OlExchangeStoreType.olExchangePublicFolder)
{
rootFolder = store.GetRootFolder();
rootFolderFolders = rootFolder.Folders;
if (rootFolderFolders != null)
{
try
{
draftsFolder = rootFolderFolders["drafts"]; // not working for "xxxxxxx#outlook.com" type email accounts
}
catch (Exception )
{
Debug.DebugMessage(3, "AddinModule : timerSendFromSaleswingsFolder_Tick : Excep");
draftsFolder = rootFolderFolders["Drafts (This computer only)"];
}
}
I need to access the drafts folder of each mail account, but the email account of “xxxxxxx#outlook.com“ shows drafts folder as "Drafts (This computer only)" instead of "drafts".
I works fine for me. But I don’t like to introduce this to the production version. Becaues I think this will not work for non-English environments.
Can you please suggest me a solution for that
In redemption (http://www.dimastr.com/redemption/home.htm), is there a solution for that?
P.S
I have used this code in some of my projects
oFolder = oNS.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderDrafts);
But it gives the drafts folder of primary mail account. In my code there is no such method for the “store” object here.
Use the GetDefaultFolder method of the Store class instead. It allows to get a Folder object that represents the default folder in the store and that is of the type specified by the FolderType argument.
This method is similar to the GetDefaultFolder method of the NameSpace object. The difference is that this method gets the default folder on the delivery store that is associated with the account, whereas NameSpace.GetDefaultFolder returns the default folder on the default store for the current profile.
The Redemption library provides the GetDefaultFolder method of the RDOStore class.
GetSharedDefaultFolder is the way to go - call Namespace.CreateRecipient / Recipient.Resolve / Namespace.GetSharedDefaultFolder.
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"
I need to get a list of installed program on local machine with application icons. Below is the code snippet that am using to get the list of installed program and installed directory path.
/// <summary>
/// Gets a list of installed software and, if known, the software's install path.
/// </summary>
/// <returns></returns>
private string Getinstalledsoftware()
{
//Declare the string to hold the list:
string Software = null;
//The registry key:
string SoftwareKey = #"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
using (RegistryKey rk = Registry.LocalMachine.OpenSubKey(SoftwareKey))
{
//Let's go through the registry keys and get the info we need:
foreach (string skName in rk.GetSubKeyNames())
{
using (RegistryKey sk = rk.OpenSubKey(skName))
{
try
{
//If the key has value, continue, if not, skip it:
if (!(sk.GetValue("DisplayName") == null))
{
//Is the install location known?
if (sk.GetValue("InstallLocation") == null)
Software += sk.GetValue("DisplayName") + " - Install path not known\n"; //Nope, not here.
else
Software += sk.GetValue("DisplayName") + " - " + sk.GetValue("InstallLocation") + "\n"; //Yes, here it is...
}
}
catch (Exception ex)
{
//No, that exception is not getting away... :P
}
}
}
}
return Software;
}
Now the issue is how i can get the installed application icon ?
Thanks in advance.
To determine if its an update, there will be a key called IsMinorUpgrade. This is present and set to a 1 for updates. If it's 0 or not present, then it's not an update.
To get an icon from an executable, use this code:
VB:
Public Function IconFromFilePath(filePath As String) As Icon
Dim result As Icon = Nothing
Try
result = Icon.ExtractAssociatedIcon(filePath)
Catch ''# swallow and return nothing. You could supply a default Icon here as well
End Try
Return result
End Function
C#:
public Icon IconFromFilePath(string filePath)
{
Icon result = null;
try {
result = Icon.ExtractAssociatedIcon(filePath);
} catch { }
return result;
}
To extract icon of installed windows application first we need to figure out the location of icon for the installed windows application. This information is stored in registry at following locations -
Key name - HEKY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall
Value - DisplayIcon
Key name - HKEY_CLASSES_ROOT\Installer\Products{productID}
Value - ProductIcon
For more detail and code to get application icons -
http://newapputil.blogspot.in/2015/06/extract-icons-of-installed-windows_17.html