Blog alskare

.Net y lo que surja

Gestionar cuentas de Outlook desde C#

Posted by alskare en 06/12/2009

Tras haberme peleado un tiempo con el tema de poder configurar la firma de Outlook desde una aplicación propia, tema que puede encontrarse en el post https://alskare.wordpress.com/2009/11/09/aadir-firma-en-outlook/, me quedé con las ganas de poder profundizar un poco más en el tema de cómo se gestiona el tema de las cuentas de correo en el registro del sistema, más que nada, porque estoy más que seguro que en breve necesitaré modificar no sólo la firma en la cuenta predeterminada.

Aunque la idea inicial es la de partir del artículo anterior, rápidamente me doy cuenta de alguna imprecisión cometida por mi parte. Imagino que las prisas y las ganas de acabar rápido logran que, en un primer intento, te dejes algún cabo suelto. En este caso, opto por dejar el artículo anterior tal cuál está y así me permitiré, desde aquí, realizar todas las correcciones o anotaciones que considere necesarias.

Una clave del registro controla las cuentas

La principal imprecisión a la que me refería es cuando indicaba que el primer byte de una determinada clave del registro “apuntaba” a la dirección de la cuenta por defecto. Después de haber intentado algún cambio en mi propio ordenador, casi me quedo sin poder entrar en Outlook al producir un error nada más entrar.

– ¿Qué has hecho para poder provocar este error?.
– Casi nada, tan sólo cambiar directamente, desde el registro, el primer byte que se muestra (el de la cuenta predeterminada) por el de otra cuenta válida
– Así, ¿sin más?. ¿Qué ha ocurrido?
– Poca cosa. No me di cuenta que el valor de dicha entrada no es un byte, son 4 bytes en formato Little Endian. Y además, esta misma clave contiene el orden en el que se encuentran las cuentas de correo. Fíjate en el ejemplo siguiente, mi cuenta por defecto es la que está en la clave 0000000b, la siguiente cuenta en la lista de Outlook es la 00000005 y así sucesivamente.

 registro

– ¿has conseguido arreglarlo?.
– Siempre que toco el registro, hago antes una copia de la rama (menos mal que no le digo que he tenido que descubrir lo del Litte Endian porque no conseguía entrar en Outlook)

Lectura de las cuentas configuradas

Una vez que conocemos tanto la cuenta predeterminada como el resto de cuentas de Outlook, tan sólo nos hace falta hacer una lectura de las mismas, así que empezamos creando una lectura, precisamente de los nombres de las cuentas.

/// <summary>
/// Obtiene una lista con las claves de las cuentas de Outlook
/// </summary>
/// <returns></returns>
public string[] getOutlookAccounts()
{
    RegistryKey rkRaiz = Registry.CurrentUser;

    string ramaCuentas =
          "Software\\Microsoft\\Windows NT"
           + "\\CurrentVersion\\Windows Messaging Subsystem"
           + "\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676";

    List<string> listaCuentas = new List<string>();
    RegistryKey rkCuentas = rkRaiz.OpenSubKey(ramaCuentas);

    // Lectura del valor que contiene las cuentas de Outlook
    object oCuentas = rkCuentas.GetValue("{ED475418-B0D6-11D2-8C3B-00104B2A6676}");
    if (oCuentas != null && oCuentas.GetType().Equals(typeof(byte[])))
    {
        byte[] cuentas = (byte[])oCuentas;
        for (int i = 0; i < cuentas.Length; i += 4)
        {
            // Traspaso de Little Endian a Big Endian
            StringBuilder sb = new StringBuilder();
            sb.Append(((byte)(cuentas.GetValue(i + 3))).ToString("X2").ToLower());
            sb.Append(((byte)(cuentas.GetValue(i + 2))).ToString("X2").ToLower());
            sb.Append(((byte)(cuentas.GetValue(i + 1))).ToString("X2").ToLower());
            sb.Append(((byte)(cuentas.GetValue(i))).ToString("X2").ToLower());

            listaCuentas.Add(sb.ToString());
        }
    }
    return listaCuentas.ToArray();
}

Leer información de una cuenta

Una vez tenemos una lista con las cuentas de correo existentes, podemos empezar a pensar en cómo extraer la información de cada una de las cuentas. Para ello, nos creamos una clase auxiliar para poder almacenar la información.

namespace jnSoftware.Outlook.Accounts
{

    /// <summary>
    /// Clase que representa una cuenta de correo de Outlook en el registro
    /// </summary>
    public class MailAccount
    {

        /// <summary>
        /// Obtiene el orden en el que se encuentra la cuenta de correo en la lista de
        /// Outlook
        /// </summary>
        public int OrderList { get; internal set; }


        /// <summary>
        /// Obtiene el nombre de la clave que contiene la información de la cuenta en 
        /// el registro
        /// </summary>
        public string AccountID { get; internal set; }


        /// <summary>
        /// Obtiene un valor que indica si la cuenta es la predeterminada
        /// </summary>
        public bool IsDefaultAccount
        { get { return OrderList == 0; } }


        /// <summary>
        /// Obtiene la cuenta de correo asociada a la cuenta
        /// </summary>
        public string Email { get; internal set; }


        /// <summary>
        /// Obtiene el nombre de la cuenta de correo.
        /// </summary>
        public string AccountName { get; internal set; }


        /// <summary>
        /// Obtiene el valor establecido como firma para nuevos mensajes
        /// </summary>
        public string NewSignature { get; internal set; }


        /// <summary>
        /// Obtiene el valor establecido como firma para los mensajes que 
        /// se responden
        /// </summary>
        public string ReplySignature { get; internal set; }
             
    }
}

A partir de ahora, la imaginación al poder. Además de los métodos que se nos pueden ocurrir en un primer momento, la verdad es que, pensando un poco en detalle, las posibilidades son amplias. Incluyamos algún ejemplo:

  • Obtener una lista con la información de todas las cuentas
  • Obtener información de una cuenta concreta (por Id o por cuenta de correo)
  • Obtener información de la cuenta de correo predeterminada
  • Cambiar la firma de una cuenta concreta (no sólo la cuenta predeterminada)

Partiendo de los puntos anteriores desarrollo una clase que permita realizar los trabajos mencionados, aunque me imagino que, en el momento que tenga un poco de tiempo acabaré creando alguna aplicación en WinForms que permita realizar estos cambios de un modo más amigable. De momento, con la librería actual se pueden realizar muchos cambios desde, por ejemplo, un inicio de sesión en una red.

using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Win32;


namespace jnSoftware.Outlook.Accounts
{

    /// <summary>
    /// Gestión de cuentas de correo de Outlook
    /// </summary>
    public class MailAccounts
    {


        private Dictionary<string, MailAccount> cuentasOutlook;

        // Las cuentas de correo dependen directamente de HKCU
        private RegistryKey rkRaiz
        { get { return Registry.CurrentUser; } }

        // De la rama siguiente dependen todas las cuentas de correo definidas
        // en Outlook
        private string ramaCuentas =
               "Software\\Microsoft\\Windows NT"
                + "\\CurrentVersion\\Windows Messaging Subsystem"
                + "\\Profiles\\Outlook\\9375CFF0413111d3B88A00104B2A6676";



        /// <summary>
        /// Obtiene las cuentas de correo de Outlook definidas en el registro
        /// </summary>
        /// <returns></returns>
        public List<MailAccount> GetAccounts()
        {
            readInfoAccounts();
            return new List<MailAccount>(cuentasOutlook.Values);
        }


        /// <summary>
        /// Obtiene información de una cuenta de correo determinada
        /// </summary>
        /// <param name="accountID">Identificador de la cuenta de correo. Se corresponde 
        /// con el nombre de la rama del registro</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce la excepción cuando no 
        /// se encuentra la cuenta especificada en <paramref name="accountID"/></exception>
        public MailAccount GetAccount(string accountID)
        {
            readInfoAccounts();
            try
            {
                return cuentasOutlook[accountID];
            }
            catch (KeyNotFoundException ex)
            {
                throw new ArgumentOutOfRangeException("No existe la cuenta solicitada", ex);
            }
            catch { throw; }
        }


        /// <summary>
        /// Obtiene información de la cuenta establecida por defecto
        /// </summary>
        public MailAccount GetDefaultAccount()
        {
            string[] cuentas = getOutlookIdAccounts();
            return GetAccount(cuentas[0]);
        }


        /// <summary>
        /// Obtiene información de una cuenta de correo en base a su dirección electrónica
        /// </summary>
        /// <param name="email">Cuenta de correo de la que se quiere recuperar la información</param>
        /// <exception cref="ArgumentOutOfRangeException">Se produce cuando  no se encuentra la 
        /// cuenta de correo especificada en <paramref name="email"/>.</exception>
        public MailAccount GetAcountByEmail(string email)
        {
            readInfoAccounts();

            MailAccount retVal = null;
            // Como en la Uni, desarrollamos un algoritmo de búsqueda
            foreach (MailAccount cuenta in cuentasOutlook.Values)
            {
                if (cuenta.Email == email)
                {
                    retVal = cuenta;
                    break;
                }
            }
            if (retVal != null)
            { return retVal; }
            else { throw new ArgumentOutOfRangeException("Cuenta de correo no encontrada"); }
        }


        /// <param name="email">Cuenta de correo de la que se quiere establecer la firma</param>
        /// <param name="newSignature">Nombre de la firma que quiere establecerse en la cuenta de 
        /// correo para mensajes nuevos</param>
        /// <param name="replySignature">Nombre de la firma que quiere establecerse en la cuenta 
        /// de correo para mensajes respondidos</param>
        public void SetSignature(string email, string newSignature, string replySignature)
        {
            MailAccount cuenta = GetAcountByEmail(email);

            // TODO : Queda pendiente una comprobación para que existan las firmas 
            // en la carpeta de firmas.

            RegistryKey rkCuenta = rkRaiz.OpenSubKey(ramaCuentas + "\\" + cuenta.AccountID, true);
            rkCuenta.SetValue("New Signature", GetFirmaCodificada(newSignature));
            rkCuenta.SetValue("Reply-Forward Signature", GetFirmaCodificada(replySignature));
        }


        /// <summary>
        /// Realiza la lectura de las cuentas definidas en el registro
        /// </summary>
        private void readInfoAccounts()
        {
            cuentasOutlook = new Dictionary<string, MailAccount>();
            string[] cuentas = getOutlookIdAccounts();
            for (int i = 0; i < cuentas.Length; i++)
            {
                MailAccount m = getInfoAccount(cuentas[i]);
                m.OrderList = i;
                cuentasOutlook.Add(m.AccountID, m);
            }
        }


        /// <summary>
        /// Recupera la información de una cuenta determinada.
        /// </summary>
        /// <param name="accountID"></param>
        /// <returns></returns>
        private MailAccount getInfoAccount(string accountID)
        {
            MailAccount m = new MailAccount();
            m.AccountID = accountID;

            RegistryKey rkCuentas = rkRaiz.OpenSubKey(ramaCuentas + "\\" + accountID, false);
            m.Email = binaryToString(rkCuentas.GetValue("Email"));
            m.NewSignature = binaryToString(rkCuentas.GetValue("New Signature"));
            m.ReplySignature = binaryToString(rkCuentas.GetValue("Reply-Forward Signature"));
            m.AccountName = binaryToString(rkCuentas.GetValue("Account Name"));

            return m;
        }


        /// <summary>
        /// Obtiene una lista con las claves de las cuentas de Outlook
        /// </summary>
        /// <returns></returns>
        private string[] getOutlookIdAccounts()
        {
            List<string> listaCuentas = new List<string>();
            RegistryKey rkCuentas = rkRaiz.OpenSubKey(ramaCuentas, false);

            // Lectura del valor que contiene las cuentas de Outlook
            object oCuentas = rkCuentas.GetValue("{ED475418-B0D6-11D2-8C3B-00104B2A6676}");
            if (oCuentas != null && oCuentas.GetType().Equals(typeof(byte[])))
            {
                byte[] cuentas = (byte[])oCuentas;
                for (int i = 0; i < cuentas.Length; i += 4)
                {
                    // Traspaso de Little Endian a Big Endian
                    StringBuilder sb = new StringBuilder();
                    sb.Append(((byte)(cuentas.GetValue(i + 3))).ToString("X2").ToLower());
                    sb.Append(((byte)(cuentas.GetValue(i + 2))).ToString("X2").ToLower());
                    sb.Append(((byte)(cuentas.GetValue(i + 1))).ToString("X2").ToLower());
                    sb.Append(((byte)(cuentas.GetValue(i))).ToString("X2").ToLower());

                    listaCuentas.Add(sb.ToString());
                }
            }
            return listaCuentas.ToArray();
        }


        /// <summary>
        /// Decodifica una cadena del registro en una cadena ASCII.
        /// </summary>
        /// <param name="cadena">Cadena del registro. Devuelve string.empty si la 
        /// cadena estuviese vacía.</param>
        /// <returns></returns>
        private string binaryToString(object cadena)
        {
            StringBuilder sb = new StringBuilder();
            if (cadena != null)
            {
                if (cadena.GetType().Equals(typeof(byte[])))
                {
                    byte[] entrada = (byte[])cadena;
                    ASCIIEncoding ascii = new ASCIIEncoding();

                    // Bucle para eliminar los bytes vacíos que se colocan en el registro
                    for (int i = 0; i < entrada.Length; i++)
                    {
                        if (entrada[i] != 0)
                        {
                            sb.Append(ascii.GetString(entrada, i, 1));
                        }
                        i++;
                    }
                }
            }
            return sb.ToString();
        }


        /// <summary>
        /// Codifica el nombre de la firma en un array de Bytes
        /// </summary>
        /// <returns>La codificación se hace tal cómo lo hace Outlook</returns>
        private static byte[] GetFirmaCodificada(string nombreFirma)
        {
            // Codificación del nombre de la firma
            ASCIIEncoding ascii = new ASCIIEncoding();
            byte[] firmaSinCeros = ascii.GetBytes(nombreFirma);

            // La firma de Outlook contiene un byte vacío por cada byte del nombre,
            // así que incluyo un List para no complicarme la vida y añadirle los 
            // bytes vacíos.
            List<byte> firmaConCeros = new List<byte>();

            // Se añaden los ceros a la firma
            foreach (byte b in firmaSinCeros)
            {
                firmaConCeros.Add(b);
                firmaConCeros.Add(new byte());
            }

            // No sé la razón, pero la firma le añade dos bytes más
            firmaConCeros.Add(new byte());
            firmaConCeros.Add(new byte());

            return firmaConCeros.ToArray();
        }


    }       // class
}           // namespace

 

En esta ocasión no dejo las librerías listas para la descarga puesto que así intento obligarme a crear una tercera entrega dedicada a Outlook en la que intentaré que se puedan establecer algunos cambios desde una aplicación WinForms.

Una respuesta to “Gestionar cuentas de Outlook desde C#”

  1. Juan Carlos said

    Viejo exclente tu reportaje! estoy al pendiente por tu siguiente entrega… gracias

Sorry, the comment form is closed at this time.

 
A %d blogueros les gusta esto: