using System; using System.Collections.Generic; using System.Runtime.InteropServices; using StarfaceOutlookSync.Models; namespace StarfaceOutlookSync.Services { /// /// Zugriff auf Outlook-Kontakte per dynamic COM. /// Funktioniert mit jeder Outlook Classic Version (2013-2024) /// ohne Abhaengigkeit von einer bestimmten Interop-DLL. /// public class OutlookContactsService : IDisposable { private dynamic _outlookApp; private bool _weStartedOutlook; // OlDefaultFolders.olFolderContacts = 10 private const int OlFolderContacts = 10; // OlItemType.olContactItem = 2 private const int OlContactItem = 2; [DllImport("oleaut32.dll", PreserveSig = false)] private static extern void GetActiveObject( [MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, IntPtr pvReserved, [MarshalAs(UnmanagedType.IUnknown)] out object ppunk); private static object GetActiveComObject(string progId) { var type = Type.GetTypeFromProgID(progId, false); if (type == null) return null; GetActiveObject(type.GUID, IntPtr.Zero, out var obj); return obj; } private dynamic GetOutlookApp() { if (_outlookApp != null) return _outlookApp; // Versuch 1: Laufende Outlook-Instanz finden try { var obj = GetActiveComObject("Outlook.Application"); if (obj != null) { _outlookApp = obj; _weStartedOutlook = false; return _outlookApp; } } catch { } // Versuch 2: Outlook per COM starten try { var outlookType = Type.GetTypeFromProgID("Outlook.Application", false); if (outlookType != null) { _outlookApp = Activator.CreateInstance(outlookType); _weStartedOutlook = true; return _outlookApp; } } catch { } throw new InvalidOperationException( "Outlook Classic konnte nicht gefunden werden.\n\n" + "Moegliche Ursachen:\n" + "- Outlook Classic ist nicht installiert\n" + "- Outlook Classic ist nicht gestartet\n" + "- Das neue Outlook wird verwendet (wird noch nicht unterstuetzt)\n\n" + "Bitte Outlook Classic starten und erneut versuchen."); } public List GetContactFolderPaths() { var folders = new List(); try { var app = GetOutlookApp(); var ns = app.GetNamespace("MAPI"); // Alle Stores durchgehen var stores = ns.Stores; for (int i = 1; i <= (int)stores.Count; i++) { try { var store = stores[i]; var rootFolder = store.GetRootFolder(); FindContactFoldersRecursive(rootFolder, folders); Marshal.ReleaseComObject(rootFolder); Marshal.ReleaseComObject(store); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error scanning store {i}: {ex.Message}"); } } // Falls nichts gefunden, Standard-Kontaktordner als Fallback if (folders.Count == 0) { try { var defaultFolder = ns.GetDefaultFolder(OlFolderContacts); folders.Add((string)defaultFolder.FolderPath); Marshal.ReleaseComObject(defaultFolder); } catch { } } Marshal.ReleaseComObject(stores); Marshal.ReleaseComObject(ns); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error getting folders: {ex.Message}"); throw; } return folders; } private void FindContactFoldersRecursive(dynamic folder, List paths) { try { if ((int)folder.DefaultItemType == OlContactItem) { string path = folder.FolderPath; if (!paths.Contains(path)) paths.Add(path); } var subFolders = folder.Folders; for (int i = 1; i <= (int)subFolders.Count; i++) { try { var sub = subFolders[i]; FindContactFoldersRecursive(sub, paths); Marshal.ReleaseComObject(sub); } catch { } } Marshal.ReleaseComObject(subFolders); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error scanning folder: {ex.Message}"); } } private dynamic FindFolderByPath(dynamic folder, string targetPath) { try { string currentPath = folder.FolderPath; if (currentPath == targetPath) return folder; var subs = folder.Folders; for (int i = 1; i <= (int)subs.Count; i++) { try { var sub = subs[i]; var match = FindFolderByPath(sub, targetPath); if (match != null) return match; Marshal.ReleaseComObject(sub); } catch { } } Marshal.ReleaseComObject(subs); } catch { } return null; } private dynamic GetFolderByPath(string folderPath) { var app = GetOutlookApp(); var ns = app.GetNamespace("MAPI"); if (string.IsNullOrEmpty(folderPath)) return ns.GetDefaultFolder(OlFolderContacts); try { // Versuch 1: Alle Kontaktordner durchsuchen und per FolderPath matchen var stores = ns.Stores; for (int i = 1; i <= (int)stores.Count; i++) { try { var store = stores[i]; var root = store.GetRootFolder(); var match = FindFolderByPath(root, folderPath); if (match != null) { Marshal.ReleaseComObject(stores); return match; } Marshal.ReleaseComObject(root); Marshal.ReleaseComObject(store); } catch { } } Marshal.ReleaseComObject(stores); // Versuch 2: Namespace.Folders direkt navigieren var parts = folderPath.Split(new[] { '\\' }, StringSplitOptions.RemoveEmptyEntries); if (parts.Length >= 1) { var topFolders = ns.Folders; for (int i = 1; i <= (int)topFolders.Count; i++) { var topFolder = topFolders[i]; if ((string)topFolder.Name == parts[0]) { dynamic current = topFolder; for (int p = 1; p < parts.Length; p++) { bool found = false; var subs = current.Folders; for (int j = 1; j <= (int)subs.Count; j++) { var sub = subs[j]; if ((string)sub.Name == parts[p]) { current = sub; found = true; break; } Marshal.ReleaseComObject(sub); } Marshal.ReleaseComObject(subs); if (!found) { current = null; break; } } if (current != null) { Marshal.ReleaseComObject(topFolders); return current; } } Marshal.ReleaseComObject(topFolder); } Marshal.ReleaseComObject(topFolders); } System.Diagnostics.Debug.WriteLine($"Folder not found by path: {folderPath}, using default"); return ns.GetDefaultFolder(OlFolderContacts); } catch { return ns.GetDefaultFolder(OlFolderContacts); } } public List GetContacts(string folderPath) { var contacts = new List(); try { var folder = GetFolderByPath(folderPath); System.Diagnostics.Debug.WriteLine($"Reading contacts from: {(string)folder.FolderPath}"); var items = folder.Items; int count = (int)items.Count; System.Diagnostics.Debug.WriteLine($"Items count: {count}"); for (int i = 1; i <= count; i++) { dynamic item = null; try { item = items[i]; int itemClass = -1; try { itemClass = (int)item.Class; } catch { } // olContact = 40, aber manche Kontakte melden Class anders // Versuch einfach die Kontakt-Felder zu lesen if (itemClass == 40) { contacts.Add(MapFromOutlook(item)); } else { // Fallback: Pruefen ob es trotzdem ein Kontakt ist try { string eid = item.EntryID; string fn = item.FirstName; string ln = item.LastName; // Hat Kontakt-Felder -> ist ein Kontakt contacts.Add(MapFromOutlook(item)); } catch { // Kein Kontakt, ueberspringen } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error reading item {i}: {ex.Message}"); } finally { if (item != null) try { Marshal.ReleaseComObject(item); } catch { } } } Marshal.ReleaseComObject(items); Marshal.ReleaseComObject(folder); } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error reading contacts: {ex.Message}"); } return contacts; } public UnifiedContact CreateContact(UnifiedContact contact, string folderPath) { try { var folder = GetFolderByPath(folderPath); var items = folder.Items; dynamic ci = items.Add(OlContactItem); MapToOutlook(contact, ci); ci.Save(); contact.OutlookEntryId = ci.EntryID; Marshal.ReleaseComObject(ci); Marshal.ReleaseComObject(items); Marshal.ReleaseComObject(folder); return contact; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error creating contact: {ex.Message}"); return null; } } public bool UpdateContact(string entryId, UnifiedContact contact) { try { var app = GetOutlookApp(); var ns = app.GetNamespace("MAPI"); dynamic ci = ns.GetItemFromID(entryId); MapToOutlook(contact, ci); ci.Save(); Marshal.ReleaseComObject(ci); Marshal.ReleaseComObject(ns); return true; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error updating contact: {ex.Message}"); return false; } } public bool DeleteContact(string entryId) { try { var app = GetOutlookApp(); var ns = app.GetNamespace("MAPI"); dynamic ci = ns.GetItemFromID(entryId); ci.Delete(); Marshal.ReleaseComObject(ci); Marshal.ReleaseComObject(ns); return true; } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"Error deleting contact: {ex.Message}"); return false; } } private UnifiedContact MapFromOutlook(dynamic ci) { return new UnifiedContact { OutlookEntryId = (string)(ci.EntryID ?? ""), FirstName = (string)(ci.FirstName ?? ""), LastName = (string)(ci.LastName ?? ""), Company = (string)(ci.CompanyName ?? ""), JobTitle = (string)(ci.JobTitle ?? ""), Email = (string)(ci.Email1Address ?? ""), EmailSecondary = (string)(ci.Email2Address ?? ""), PhoneWork = (string)(ci.BusinessTelephoneNumber ?? ""), PhoneMobile = (string)(ci.MobileTelephoneNumber ?? ""), PhoneHome = (string)(ci.HomeTelephoneNumber ?? ""), Fax = (string)(ci.BusinessFaxNumber ?? ""), Street = (string)(ci.BusinessAddressStreet ?? ""), City = (string)(ci.BusinessAddressCity ?? ""), PostalCode = (string)(ci.BusinessAddressPostalCode ?? ""), State = (string)(ci.BusinessAddressState ?? ""), Country = (string)(ci.BusinessAddressCountry ?? ""), Website = (string)(ci.WebPage ?? ""), Notes = (string)(ci.Body ?? ""), Salutation = (string)(ci.Title ?? ""), Birthday = GetBirthdayString(ci) }; } private string GetBirthdayString(dynamic ci) { try { DateTime bday = ci.Birthday; if (bday.Year > 1900 && bday != DateTime.MinValue) return bday.ToString("yyyy-MM-dd"); } catch { } return ""; } private void MapToOutlook(UnifiedContact contact, dynamic ci) { ci.FirstName = contact.FirstName; ci.LastName = contact.LastName; ci.CompanyName = contact.Company; ci.JobTitle = contact.JobTitle; ci.Email1Address = contact.Email; if (!string.IsNullOrEmpty(contact.EmailSecondary)) ci.Email2Address = contact.EmailSecondary; ci.BusinessTelephoneNumber = contact.PhoneWork; ci.MobileTelephoneNumber = contact.PhoneMobile; ci.HomeTelephoneNumber = contact.PhoneHome; ci.BusinessFaxNumber = contact.Fax; ci.BusinessAddressStreet = contact.Street; ci.BusinessAddressCity = contact.City; ci.BusinessAddressPostalCode = contact.PostalCode; ci.BusinessAddressState = contact.State; ci.BusinessAddressCountry = contact.Country; ci.WebPage = contact.Website; ci.Body = contact.Notes; ci.Title = contact.Salutation; if (!string.IsNullOrEmpty(contact.Birthday) && DateTime.TryParse(contact.Birthday, out var bday) && bday.Year > 1900) { ci.Birthday = bday; } } public void Dispose() { if (_outlookApp != null) { if (_weStartedOutlook) { try { _outlookApp.Quit(); } catch { } } try { Marshal.ReleaseComObject(_outlookApp); } catch { } _outlookApp = null; } } } }