Error al restrear documentos en MOSS 2007 (acceso denegado)

SharePoint


Otra de MOSS 2007 en apenas unos días. Si en la anterior tuvo relación con la publicación de formularios de Infopath, en esta ocasión ha sido un poquito más grave. Resulta que de buenas a primeras algo que funcionaba perfectamente ha dejado de hacerlo y nadie ha tocado nada. Si, si, ya se que me direis que esto es lo que se dice siempre… pero esta vez os juro que es verdad :-)


Y cuál era el error? Resulta que el proceso de indexación de documentos del site principal ha empezado a ‘petar’ cada vez que se hace un rastreo completo o incremental, mostrando un mensaje de error de acceso denegado por cada documento escaneado:


Detalles:  Acceso denegado. Compruebe si la cuenta predeterminada de acceso al contenido tiene acceso a este contenido, o bien agregue una regla de rastreo para rastrear este contenido.   (0x80041205)”  (en inglés:  “Access is denied. Check that the Default Content Access Account has access to this content, or add a crawl rule to crawl this content. (0x80041205)”)


image


Lo primero que verificamos fue la cuenta del servicio de rastreo, pero la cuenta era correcta :-(


Después de ponerme en contacto con el departamento de soporte, me mandaron un link a un artículo reciente de la KB, en el que se describía que este problema era debido a una actualización de seguridad de Windows Server 2003 Service Pack 1 (SP1). Así que procedimos a plicar la solución, que simplemente consistía en establecer un valor del registro de Windows.


Después de aplicar la solución (y reiniciar el servidor) procedimos a lanzar manualmente un rastreo y todo volvió a la normalidad.


Os dejo el enlace al artículo de la KB por si alguien se encuentra en la misma situación:


http://support.microsoft.com/?id=971382


Nota: En el artículo dice que la solución se aplica a las direcciones que empiezan por ‘sts3://*’ pero no es correcto, ya que la solución aplicaba también en nuestro caso. Según me comentan actualizarán el artículo en breve…


** crossposting desde el blog de Lluís Franco en geeks.ms **

Mapa de actualizaciones de SharePoint 2007

Es de agradecer para vagos como yo que publiquen un mapa detallado de las actualizaciones del producto. Tanto Services Packs como Cumulative Update Packages. De este modo, podemos ver con una simple ojeada el estado de nuestros servidores y lo que nos falta por instalar:

updatetimelineMOSS2007

Link del artículo completo en el blog del equipo:

http://blogs.msdn.com/sharepoint/archive/2009/05/13/april-cumulative-update-packages-ready-for-download.aspx

Saludos,


** crossposting desde el blog de Lluís Franco en geeks.ms **

Anunciados (y confirmados) los requisitos para SharePoint 2010

SharePointLogo

El equipo de SharePoint acaba de hacer públicos los requisitos de hardware para SharePoint 2010. Como ya se venía comentando se ha confirmado que sólo se dará soporte a la arquitectura de 64 bits:

  1. SharePoint Server 2010 will be 64-bit only.
  2. SharePoint Server 2010 will require 64-bit Windows Server 2008 or 64-bit Windows Server 2008 R2.
  3. SharePoint Server 2010 will require 64-bit SQL Server 2008 or 64-bit SQL Server 2005.

Además, se hace incapié en las mejoras introducidas en el soporte a varios browsers (hay vida más allá de IE?) gracias al uso de estándares como XHTML 1.0. Para empezar se soportará IE7 y 8, además de Firefox 3 sobre Windows, y se está planeando ampliar el soporte a FireFox 3 y Safari 3 en sistemas operativos no Windows (para celebrar el año de Linux en el desktop?).

Como notícia destacable, Internet Explorer 6 dejará de estar soportado. Lo cual, a mí me parece excelente.

Darle un vistazo a lartículo original:

http://blogs.msdn.com/sharepoint/archive/2009/05/07/announcing-sharepoint-server-2010-preliminary-system-requirements.aspx


** crossposting desde el blog de Lluís Franco en geeks.ms **

Ya está aquí el Service Pack 2 de SharePoint 2007

SharePointLogo


Ya es oficial.


Después de esperarlo durante muchos meses, por fin leo en el blog del equipo de SharePoint que está libre para su descarga el Service Pack 2 de SharePoint 2007, algo que muchos de nosotros estábamos esperando con ansia.


Creo que muchos de nosotros ya tendremos algo que hacer esta mañana, empezando por rezar tres padres nuestros mientras se hacen los backups, hasta unos cuantos avemarías mientras se instala el SP, pasando por un par de rosarios por cada reinicio del servidor.


 


Beneficios:


Mejoras en el rendimiento y la disponibilidad – Incluye diversas mejoras diseñadas a mejorar el rendimiento, la estabilidad y la disponibilidad de las granjas.


Mejoras en la interoperabilidad – Por ejemplo se ha mejorado el soporte para Firefox y se ha añadido IE8 a la lista de nevegadores soportados :-)


Preparación para el próximo SharePoint 2010 – Mi favoritra. Se ha añadido un nuevo comando llamado preupgradecheck al STSADM, de modo que en una futura (y previsible) migración podremos realizar un escaneo para identificar posibles problemas en la migración. Algo que preocupa a más de uno (por ejemplo a mi) ya que en su momento se decidió montar MOSS sobre 32 bits, y como ya sagbemos la siguiente versión sólo soportará 64 bits :-/.


Guía de instalación:


El SP2 incluye todos los fixes liberados desde Febrero de este año, de modo que es posible instalarlo directamente sobre la RTM de estos productos:


  1. Service Pack 2 for Windows SharePoint Services 3.0
  2. Service Pack 2 for Windows SharePoint Services 3.0 Language Pack (if applicable)
  3. Service Pack 2 for Office SharePoint Server 2007
  4. Service Pack 2 for Office SharePoint Server 2007 Language Pack (if applicable)

Una vez instalado, es necesario ejecutar el asistente de configuración de SharePoint mediante:


psconfig –cmd upgrade –inplace b2b –wait


Desde la línea de comandos. Ojo, que esto es necesario para cada servidor en nuestra granja!


La versión de las bases de datos de contenido debería ser 12.0.0.6425 después de haberse aplicado con éxito las actualizaciones.


Para una guía más detallada en el proceso de instalación, se recomienda *encarecidamente* dar una vistazo a los siguientes documentos antes de realizar la instalación, y no nos olvidemos de los padresnuestros y los avemarías:


Deploy software updates for Windows SharePoint Services 3.0
http://technet.microsoft.com/en-us/library/cc288269.aspx
Deploy software updates for Office SharePoint Server 2007
http://technet.microsoft.com/en-us/library/cc263467.aspx


Create an installation source that includes software updates (Windows SharePoint Services 3.0)
http://technet.microsoft.com/en-us/library/cc287882.aspx
Create an installation source that includes software updates (Office SharePoint Server 2007)
http://technet.microsoft.com/en-us/library/cc261890.aspx


Si queréis, podeis encontrar información más detallada en el este enlace.


Download Links


Service Pack 2 for Windows SharePoint Services 3.0, x86 & x64
http://www.microsoft.com/downloads/details.aspx?FamilyId=79BADA82-C13F-44C1-BDC1-D0447337051B&displaylang=en


Service Pack 2 for Office SharePoint Server 2007, x86 & x64
http://www.microsoft.com/downloads/details.aspx?FamilyId=B7816D90-5FC6-4347-89B0-A80DEB27A082&displaylang=en


KB Article Links


Description of Windows SharePoint Services 3.0 SP2 and of Windows SharePoint Services 3.0 Language Pack SP2
http://support.microsoft.com/kb/953338
Description of 2007 Microsoft Office servers Service Pack 2 (SP2) and of 2007 Microsoft Office servers Language Pack Service Pack 2 (SP2)
http://support.microsoft.com/kb/953334


Eso es todo. Saludos desde Andorra,


** crossposting desde el blog de Lluís Franco en geeks.ms **

Unable to publish infopath forms in MOSS 2007

:-)
Hola de nuevo,


Hoy os voy a contar un pequeño problema que hemos tenido a la hora de publicar formularios de InfoPath. Como ya sabéis, MOSS Enterprise dispone de los Forms Services, que permiten la publicación de documentos de InfoPath para su visualización en el navegador. Esto es muy útil, ya que permite que los clientes no necesiten disponer del cliente InfoPath.


Forms1


Para poder publicar un formulario, es necesario que se habilite esta característica en las propiedades del formulario, y posteriormente publicarlo en una librería de documentos:


Forms2


Evidentemente hay que tener activadas estas características en el servidor MOSS, dentro de las características de la colección de sitios:


Forms3


Sin embargo aunque estaban activadas, en el momento de publicar el formulario nos aparecía un mensaje de error:


Forms4


Visto con más detalle, las posibles causas del error podían ser las siguientes:


Forms5


Uy, uy, uy……… mientras me estaban entrendo los sudores de la muerte, pensé “a mi me suena que esto está bien, pero vamos a revisarlo”. Efectivamente, la primera no era, ya que se estaban ejecutando los servicios de InfoPathServices correctamente. La segunda tampoco, como habéis podido ver en la tercera imágen, y la última tampoco (psé, mira que llamarme a mí ‘usuario’, ¡que uno es administrador oiga!).


Así que empezamos a pelearnos con el tema, y después de conalutar con Ioana (mi ángel particular, e ingeniera de soporte de Microsoft :-P). La solución estaba en ‘forzar’ la desinstalación y posterior instalación de esta feature mediante el ya famoso STSADM. O sea, ir a la consola y tirar unas líneas para hacer de forma manual lo que desde el entorno gráfico parecía estar bien.


Os dejo la solución por si alguien más se encuentra con esto alguna vez:


Resolution:


- Checked the “Office SharePoint Server Enterprise Site Collection features”


· Browse to the site collection features


· Check for the “Office SharePoint Server Enterprise Site Collection features” to be activated. -> it was activated



- Checked that there was a Form Library on the Site. There was not, so we created it, as sometimes the publishing wizard fails if there is not a Form Library created. -> the warning still appears.



- Deactivated and reactivated the “Office SharePoint Server Enterprise Site Collection features” from the UI – the warning is still appearing



- Forced the deactivation and reactivation with STSADM:


· Open the command line


· Browse to the 12\BIN folder: C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\BIN


· To force the deactivation of the features use:


stsadm -o deactivatefeature -filename IPFSSiteFeatures\feature.xml -url %sitecollection_URL% -force


stsadm -o deactivatefeature -filename IPFSWebFeatures\feature.xml -url %sitecollection_URL% -force


· To force the reactivation of the features use:


stsadm -o activatefeature -filename IPFSSiteFeatures\feature.xml -url %sitecollection_URL% -force


stsadm -o activatefeature -filename IPFSWebFeatures\feature.xml -url %sitecollection_URL% -force



This solved the issue and the warning no longer appears when publishing an InfoPath form to the site on a MOSS Server.


Venga! Un saludo desde Andorra,


** crossposting desde el blog de Lluís Franco en geeks.ms **

SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (VII)

Entradas anteriores de la serie:


SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (I)


SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (II)


SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (III)


SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (IV)


SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (V)


SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (VI)




bender3


Han pasado unos días desde que publiqué la última entrada, y es que voy absolutamente desbordado de curro.


Pero como lo prometido es deuda, vamos a proseguir con el tema, ya quedan un par de temas por ver antes de concluir la serie:


  • Cómo ejecutar distintos threads y mostrar el progreso (Callbacks).


  • Cómo distribuir nuestro complemento, creando un archivo MSI (Windows Installer packages).


  • Además, os dije que en el último post publicaría el código fuente del proyecto… y varios de vosotros ya lo habéis reclamado con insistencia :-P


    De modo que vamos a hacerlo al revés: Hoy publico el código fuente del proyecto, y mañana (espero) continuaré el resto de la serie. De este modo al menos ya tendreis el código para jugar con él, y poder machacarme a preguntas…


    ProjectExplorer


    El proyecto incluye el código de ejemplo del Add-In y el proyecto de instalación, y lo podéis descargar desde este enlace de Skydrive:


    http://cid-f3a970280830b5fe.skydrive.live.com/self.aspx/MSDN%20Samples/OutlookToMOSS/OutlookToMOSS.zip


    Saludos desde Andorra,


    ** crossposting desde el blog de Lluís Franco en geeks.ms **

    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (V)

    Entradas anteriores de la serie:


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (I)


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (II)


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (III)


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (IV)




    bender2


    Después de algunos días sin poder seguir con la serie porque han detenido a Bender hay que sacar adelante otros proyectos, volvemos a la carga con lo que promete ser la serie de artículos más larga que he publicado hasta ahora en este blog.


     


    Antes de nada vamos a ponernos al día:


    Que pretendemos?


    Crear un add-in de Outlook que permita guardar nuestros correos en una librería de documentos de SharePoint (tanto MOSS como WSS).


    Que necesitamos?


    • Cómo crear un complemento para Office (VSTO). visto
    • Cómo registrar acciones en los menús de la aplicación host (Outlook). visto
    • Cómo conectar con un servidor MOSS/WSS con diferentes credenciales de usuario (System.Net.NetworkCredential) visto
    • Cómo acceder a los sitios y listas de un site de MOSS/WSS (servicios Web de SharePoint). visto
    • Cómo interpretar la información XML devuelta por los servicios (LINQ to XML). visto
    • Cómo extender el modelo de objetos de SharePoint para acceder los valores devueltos por los servicios Web.
    • Cómo crear las columnas necesarias en una lista para guardar los metadatos del elementos de correo (From, To, Subject).
    • Cómo guardar un fichero en una biblioteca de documentos de SharePoint con control de versiones (DocLibHelper).
    • Cómo ejecutar distintos threads y mostrar el progreso (Callbacks).
    • Cómo distribuir nuestro complemento, creando un archivo MSI (Windows Installer packages).

    Que vamos a ver hoy?


    En este post vamos a centrarnos en cómo obtener datos de los servicios Web de SharePoint. En nuestro caso necesitamos obtener las colecciones de sitios, las librerías de cada uno de los sitios, y las carpetas de cada librería para mostrarlos en una ventana que permita al usuario seleccionar la ubicación en la que desea guardar los elementos de coreo:


    AddIn_Step2


    Del mismo modo, vamos a necesitar obtener las columnas de la librería seleccionada, para comprobar si ya existen las columnas para almacenar los metadatos de los elementos de correo, y en caso contrario proceder a crearlas (esto va a ir muy bien para posteriormente poder clasificar los elementos).


    Correos1


     


    Extendiendo el modelo de objetos de SharePoint


    En el post anterior ya vimos que habíamos declarado algunas clases de apoyo (como la clase ‘SPSiteInfo’) para que nuestros métodos (como el método ‘getSiteSubSites’ que también vimos en el anterior post) pudiesen devolver colecciones genéricas de éestos tipos de datos, lo cual es mucho más manejable que pelearnos con el XML que devuelven los servicios Web de SharePoint.


    Los métodos que vamos a crear son:


    • getSiteSubSites: Devuelve una lista genérica de objetos ‘SPSiteInfo’, a partir de un servicio Web websProxy.Webs
    • getSiteLists: Devuelve una lista genérica de objetos ‘SPListInfo’, a partir de un servicio Web listsProxy.Lists
    • getListFolders: Devuelve una lista genérica de objetos ‘SPFolderInfo’, a partir de un servicio Web listsProxy.Lists
    • getListColumns: Devuelve una lista genérica de objetos ‘SPFolderInfo’, a partir de un servicio Web listsProxy.Lists y de un listID (GUID)
    • createMetadataColumns: Crea las columnas de metadatos definidas en los settings del add-in en una lista, a partir de un servicio Web listsProxy.Lists y de un listID (GUID).

    Las clases de apoyo son:


    public class SPSiteInfo
    {
        public string Title { get; set; }
        public string URL { get; set; }
     
        public SPSiteInfo()
        {
            //
        }
     
        public SPSiteInfo(string title, string url)
        {
            Title = title;
            URL = url;
        }
    }
     
    public class SPListInfo
    {
        public Guid ID { get; set; }
        public string Title { get; set; }
        public string URL { get; set; }
     
        public SPListInfo()
        {
            //
        }
     
        public SPListInfo(Guid id, string title, string url)
        {
            ID = id;
            Title = title;
            URL = url;
        }
    }
     
    public class SPFolderInfo
    {
        public string Title { get; set; }
        public string URL { get; set; }
     
        public SPFolderInfo()
        {
            //
        }
     
        public SPFolderInfo(string title, string url)
        {
            Title = title;
            URL = url;
        }
    }
     
    public class SPColumnInfo
    {
        public string ID { get; set; }
        public string Type { get; set; }
        public string DisplayName { get; set; }
        public string Name { get; set; }
        public bool Hidden { get; set; }
        public bool Sealed { get; set; }
        public bool ReadOnly { get; set; }
     
        public SPColumnInfo()
        {
            //
        }
     
        public SPColumnInfo(string id, string type,
            string displayName, string name, bool hidden, 
            bool issealed, bool isreadonly)
        {
            ID = id;
            Type = type;
            DisplayName = displayName;
            Name = name;
            Hidden = hidden;
            Sealed = issealed;
            ReadOnly = isreadonly;
        }
    }

    Y el código de los métodos será el siguiente:


    public class SharePointExtensions
    {
        public static List<SPSiteInfo> getSiteSubSites(websProxy.Webs sharePointWebs)
        {
            try
            {
                XmlNode websResult = sharePointWebs.GetWebCollection();
                XDocument results = XDocument.Parse(websResult.OuterXml);
                XName name = XName.Get("Web", "http://schemas.microsoft.com/sharepoint/soap/");
                var webs = from item in results.Descendants(name)
                           select new SPSiteInfo
                               (
                               item.Attribute("Title").Value,
                               item.Attribute("Url").Value
                               );
                return webs.ToList();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
     
        public static List<SPListInfo> getSiteLists(listsProxy.Lists sharePointLists)
        {
            try
            {
                XmlNode listsResult = sharePointLists.GetListCollection();
                XDocument results = XDocument.Parse(listsResult.OuterXml);
                XName name = XName.Get("List", "http://schemas.microsoft.com/sharepoint/soap/");
                var lists = from item in results.Descendants(name)
                            where item.Attribute("ServerTemplate").Value.ToString() ==
                                Properties.Settings.Default.SERVER_TEMPLATE
                            select new SPListInfo
                                (
                                new Guid(item.Attribute("ID").Value),
                                item.Attribute("Title").Value,
                                item.Attribute("DefaultViewUrl").Value
                                );
                return lists.ToList();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
     
        public static List<SPFolderInfo> getListFolders(listsProxy.Lists sharePointLists, 
            string listName, string listText, string folderURL)
        {
            try
            {
                XmlDocument xmlDoc = new System.Xml.XmlDocument();
                XmlNode ndQuery = xmlDoc.CreateNode(XmlNodeType.Element, "Query", "");
                XmlNode ndViewFields =
                  xmlDoc.CreateNode(XmlNodeType.Element, "ViewFields", "");
     
                XmlNode ndQueryOptions = null;
                if (listName != folderURL)
                {
                    ndQueryOptions = xmlDoc.CreateNode(
                        XmlNodeType.Element, "QueryOptions", "");
                    ndQueryOptions.InnerXml = string.Format(
                        "<Folder>{0}/{1}</Folder>", listName, folderURL);
                }
     
                XName name = XName.Get("data", "urn:schemas-microsoft-com:rowset");
                XmlNode ndListItems =
                        sharePointLists.GetListItems(listText, null, ndQuery,
                        ndViewFields, null, ndQueryOptions, null);
                XDocument results = XDocument.Parse(ndListItems.OuterXml);
     
                var folders = from item in results.Descendants(name).Elements()
                              where item.Attribute("ows_ContentType").Value == "Carpeta"
                              select new SPFolderInfo
                                 (
                                 item.Attribute("ows_BaseName").Value,
                                 item.Attribute("ows_ServerUrl").Value
                                 );
                return folders.ToList();
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }
        
        public static List<SPColumnInfo> getListColumns(listsProxy.Lists sharePointLists, string siteID)
        {
            try
            {
                List<SPColumnInfo> columns =new List<SPColumnInfo>();
                XmlNode listsResult = sharePointLists.GetList(siteID);
                XDocument results = XDocument.Parse(listsResult.OuterXml);
                XName name = XName.Get("Field", "http://schemas.microsoft.com/sharepoint/soap/");
                var lists = from item in results.Descendants(name)
                            select item;
                foreach (var item in lists.ToList())
                {
                    XAttribute aID = item.Attribute("ID");
                    XAttribute aType = item.Attribute("Type");
                    XAttribute aDisplayName = item.Attribute("DisplayName");
                    XAttribute aName = item.Attribute("Name");
                    XAttribute aHidden = item.Attribute("Hidden");
                    XAttribute aSealed = item.Attribute("Sealed");
                    XAttribute aReadOnly = item.Attribute("ReadOnly");
     
                    if (aID != null && aType != null && aDisplayName != null &&
                        aName != null)
                    {
                        bool ishidden = false;
                        bool issealed = false;
                        bool isreadonly = false;
                        if (aHidden != null && aHidden.Value.ToString() == "TRUE") ishidden = true;
                        if (aSealed != null && aSealed.Value.ToString() == "TRUE") issealed = true;
                        if (aReadOnly != null && aReadOnly.Value.ToString() == "TRUE") isreadonly = true;
                        columns.Add(new SPColumnInfo(
                            aID.Value.ToString(),
                            aType.Value.ToString(),
                            aDisplayName.Value.ToString(),
                            aName.Value.ToString(),
                            ishidden, issealed, isreadonly));
                    }
                }
                return columns;
            }
            catch (Exception ex)
            {                
                throw ex;
            }
        }
     
        public static void createMetadataColumns(listsProxy.Lists sharePointLists, string listID)
        {
            int createColumnsCount = 0;
            if (!Properties.Settings.Default.OPTION_CREATE_METADATA_COLUMNS) return;
            var cols = from c in SharePointExtensions.getListColumns(
                       sharePointLists, listID)
                       where c.Hidden == false && c.Sealed == false && c.ReadOnly == false
                       select c;
     
            Dictionary<string, SPColumnInfo> columns = cols.ToDictionary(c => c.Name);
            string newFieldsList = "";
     
            if (!columns.ContainsKey(Properties.Settings.Default.COL_SUBJECT))
            {
                string subjectfield =
                    @"<Method ID='1'><Field Type='Text' DisplayName='{0}' MaxLength='255'/></Method>";
                newFieldsList += string.Format(subjectfield, Properties.Settings.Default.COL_SUBJECT);
                createColumnsCount++;
            }
            if (!columns.ContainsKey(Properties.Settings.Default.COL_TO))
            {
                string subjectfield =
                    @"<Method ID='2'><Field Type='Text' DisplayName='{0}' MaxLength='255'/></Method>";
                newFieldsList += string.Format(subjectfield, Properties.Settings.Default.COL_TO);
                createColumnsCount++;
            }
            if (!columns.ContainsKey(Properties.Settings.Default.COL_CC))
            {
                string subjectfield =
                    @"<Method ID='3'><Field Type='Text' DisplayName='{0}' MaxLength='255'/></Method>";
                newFieldsList += string.Format(subjectfield, Properties.Settings.Default.COL_CC);
                createColumnsCount++;
            }
            if (!columns.ContainsKey(Properties.Settings.Default.COL_BCC))
            {
                string subjectfield =
                    @"<Method ID='4'><Field Type='Text' DisplayName='{0}' MaxLength='255'/></Method>";
                newFieldsList += string.Format(subjectfield, Properties.Settings.Default.COL_BCC);
                createColumnsCount++;
            }
            if (!columns.ContainsKey(Properties.Settings.Default.COL_FROM))
            {
                string subjectfield =
                    @"<Method ID='5'><Field Type='Text' DisplayName='{0}' MaxLength='255'/></Method>";
                newFieldsList += string.Format(subjectfield, Properties.Settings.Default.COL_FROM);
                createColumnsCount++;
            }
            if (!columns.ContainsKey(Properties.Settings.Default.COL_SENT))
            {
                string subjectfield =
                    @"<Method ID='6'><Field Type='DateTime' DateOnly='FALSE' DisplayName='{0}'/></Method>";
                newFieldsList += string.Format(subjectfield, Properties.Settings.Default.COL_SENT);
                createColumnsCount++;
            }
            if (!columns.ContainsKey(Properties.Settings.Default.COL_SIZE))
            {
                string subjectfield =
                    @"<Method ID='7'><Field Type='Number' DisplayName='{0}'/></Method>";
                newFieldsList += string.Format(subjectfield, Properties.Settings.Default.COL_SIZE);
                createColumnsCount++;
            }
            if (!columns.ContainsKey(Properties.Settings.Default.COL_IMPORTANCE))
            {
                string subjectfield =
                    @"<Method ID='8'><Field Type='Choice' DisplayName='{0}'>;
                      <Default>{1}</Default>
                      <CHOICES>
                        <CHOICE>{2}</CHOICE>
                        <CHOICE>{3}</CHOICE>
                        <CHOICE>{4}</CHOICE>
                      </CHOICES>
                    </Field></Method>"
                newFieldsList += string.Format(subjectfield,
                    Properties.Settings.Default.COL_IMPORTANCE,
                    Properties.Settings.Default.COL_IMPORTANCE_MEDIUM,
                    Properties.Settings.Default.COL_IMPORTANCE_HIGH,
                    Properties.Settings.Default.COL_IMPORTANCE_MEDIUM,
                    Properties.Settings.Default.COL_IMPORTANCE_LOW);
                createColumnsCount++;
            }
            if (createColumnsCount > 0)
            {
                XmlNode ndList = sharePointLists.GetList(listID);
                XmlNode ndVersion = ndList.Attributes["Version"];
                XmlDocument xmlDoc = new System.Xml.XmlDocument();
                XmlNode ndNewFields = xmlDoc.CreateNode(XmlNodeType.Element, "Fields", "");
                ndNewFields.InnerXml = newFieldsList;
                try
                {
                    XmlNode ndReturn =
                       sharePointLists.UpdateList(listID,
                       null, ndNewFields, null, null,
                       ndVersion.Value);
                }
                catch (Exception ex)
                {
     
                    throw ex;
                }
            }
        }
    }

    Y con esto ya estamos en disposición de mostrar un cuadro de diálogo al usuario, para que seleccione la ubicación en el servidor de SharePoint dónde quiere guardar los elementos de correo.


    En el próximo post veremos cómo guardar los elementos de corro en la ubicación seleccionada, creando las columnas de metadatos (si así lo hemos definido), y actualizando el valor de éstas columnas para guardar los valores de los campos de correo (Asunto, De, Para, Fecha, Tamaño, etc.).


    Os recuerdo que al terminar la serie publicaré la solución con el código completo, por si a alguien le sirve.


    Espero no demorarme tanto en escribir el próximo post!


    Nos vemos, un saludo desde Andorra!


    ** crossposting desde el blog de Lluís Franco en geeks.ms **

    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (IV)

    Entradas anteriores de la serie:


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (I)


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (II)


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (III)




    bendervitruvio


    He estado un par de días sin poder continuar con la serie, pero es que el trabajo aprieta y no siempre nos podemos dedicar a lo que más nos gusta…


    Recapitulando: Cosas que necesitamos para nuestro add-in:


    • Cómo crear un complemento para Office (VSTO). visto en el post anterior.
    • Cómo registrar acciones en los menús de la aplicación host (Outlook). Lo veremos ahora.
    • Cómo conectar con un servidor MOSS/WSS con diferentes credenciales de usuario (System.Net.NetworkCredential) Esto también lo veremos ahora.
    • Cómo acceder a los sitios y listas de un site de MOSS/WSS (servicios Web de SharePoint). visto en el post anterior.
    • Cómo interpretar la información XML devuelta por los servicios (LINQ to XML). Y esto también lo veremos ahora.
    • El resto de cosas las veremos en los próximos días…

    Hoy nos centraremos en mostrar cómo agregar opciones a los menús de Outlook (para lanzar nuestras acciones personalizadas), ver cómo conectar con un sitio de SharePoint con las credenciales predeterminadas, o bien proporcionando un usuario y password. Y además veremos cómo interpretar el resultado devuelto por los servicios Web, ya que el usar LINQ to XML va a facilitarnos en mucho la tarea de lidiar con XML puro y duro.


    Demos un vistazo al futuro, a lo que va a ser este Add-In (solo Bender puede viajar al futuro):


    1 – Una vez registradas nuestras propias acciones en el menú de Outlook, éstas permitirán guardar los elementos selecionados:


    AddIn_Step1


    2 – Aparecerá una ventana en la que podremos se leccionar la biblioteca de documentos y la carpeta destino:


     


     


    AddIn_Step2


    3 – Una vez seleccionada la carpeta, mostraremos una ventana de progreso en un hilo separado para que el usuario pueda seguir interactuando con Outlook:


    AddIn_Step3


    4 – Una vez terminado, si vamos a la biblioteca de documentos aparecerán los documentos (y más adelante también los metadatos):


    AddIn_Step4


    Vayamos por partes:


    Registrar acciones en un menú de Outlook:


    Registrar el menú es sencillo, sólo debemos modificar el código del evento ‘ItemContextMenuDisplay’ (como ya vimos en el post anterior), y llamar a un método ‘RegisterMenu’ que se encarga de agregar nuestras acciones al menú contextual de Outlook.


    void Application_ItemContextMenuDisplay(
        Microsoft.Office.Core.CommandBar CommandBar, 
        Microsoft.Office.Interop.Outlook.Selection Selection)
    {
        RegisterMenu(CommandBar, false);
    }

     


     


     


    void RegisterMenu(Microsoft.Office.Core.CommandBar CommandBar, bool IsFolder)
    {
        Office.CommandBarPopup cmdSaveToMOSS = 
            (Office.CommandBarPopup)CommandBar.FindControl(
            missing, missing, "STSMOSS", missing, missing);
        if (cmdSaveToMOSS == null)
        {
            cmdSaveToMOSS = (Office.CommandBarPopup)CommandBar.Controls.Add(
                    Office.MsoControlType.msoControlPopup, missing,
                    missing, missing, false);
            if (cmdSaveToMOSS != null)
            {
                cmdSaveToMOSS.Caption = Properties.Resources.CONTEXT_MENU_CAPTION;
                cmdSaveToMOSS.Tag = "STMOSS";
                cmdSaveToMOSS.BeginGroup = true;
     
                Office.CommandBarButton btnSaveToMOSS =
                    (Office.CommandBarButton)cmdSaveToMOSS.Controls.Add(
                    Office.MsoControlType.msoControlButton, missing, missing, missing, missing);
     
                btnSaveToMOSS.Caption = Properties.Resources.CONTEXT_MENU_SENDBUTTON;
                btnSaveToMOSS.FaceId = 65;
                btnSaveToMOSS.Tag = IsFolder.ToString();
                btnSaveToMOSS.Style = MsoButtonStyle.msoButtonIconAndCaption;
                btnSaveToMOSS.Picture = getImage();
                btnSaveToMOSS.Click += new Microsoft.Office.Core.
                    _CommandBarButtonEvents_ClickEventHandler(saveToMOSS_Click);  
                
                Office.CommandBarButton btnConfigurationMOSS =
                    (Office.CommandBarButton)cmdSaveToMOSS.Controls.Add(
                    Office.MsoControlType.msoControlButton, missing, missing, missing, missing);
     
                btnConfigurationMOSS.Caption = Properties.Resources.CONTEXT_MENU_CONFIGURATIONBUTTON;
                btnConfigurationMOSS.BeginGroup = true;
                btnConfigurationMOSS.Style = MsoButtonStyle.msoButtonIconAndCaption;
                btnConfigurationMOSS.Click += new Microsoft.Office.Core.
                    _CommandBarButtonEvents_ClickEventHandler(configurationMOSS_Click);  
                
                cmdSaveToMOSS.Visible = true;
            }
        }
    }

     


    Este código crea un elemento menú y le agrega dos opciones (‘btnSaveToMOSS’ y ‘btnConfigurationMOSS’). A continuación establece sus propiedades y asigna los event handlers a sendas rutinas que se ejecutarán al pulsar sobre ellas.


    void saveToMOSS_Click(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault)
    {     
        CancelDefault = true;
        MessageBox.Show("Save");
    }
     
    void configurationMOSS_Click(Microsoft.Office.Core.CommandBarButton Ctrl, ref bool CancelDefault)
    {
        CancelDefault = true;
        MessageBox.Show("Config");
    }

    Conectar con un servidor MOSS/WSS con diferentes credenciales de usuario:


    En algunas ocasiones tal vez nos interese conectar con un sitio de SharePoint y no hacer uso de las credenciales predeterminadas (por defecto el token del usuario de Windows). En estos casos, debemos hacer uso de la clase ‘System.Net.NetworkCredential’, pasando los valores de usuario y password (y opcionalmente dominio) para acceder al sitio. En este caso he creado unas sencillas funciones que devuelven un objeto de tipo ‘ICredentials’ que posteriormente usaremos en la propiedad ‘Credentials’ de los proxies de los servicios Web:


    public static System.Net.ICredentials getCredentials()
    {
        return System.Net.CredentialCache.DefaultCredentials;
    }
     
    public static System.Net.ICredentials getCredentials(string user, string password, string domain)
    {
        return new System.Net.NetworkCredential(user, password, domain);
    }

    Interpretar el resultado devuelto por los servicios Web (LINQ to XML):


    En el post anterior vimos que el resultado devuelto por los métodos de un servicio Web dista mucho de ser fácilmente interpretable, ya que nos devuelve un objeto de tipo XmlNode, de modo que debemos analizar el DOM para obtener el resultado deseado. En nuestro caso, vamos a realizar una llamada al método ‘GetWebCollection()’, que devuelve un XmlNode con los nombres de las webs (sites) de un sitio de SharePoint. Pero vamos a utilizar LINQ to XML para obtener sólo los elementos de un tipo determinado, y además vamos a obtener aquellos atributos que nos interesan. Y lo mejor es que todo se hace una sola sentencia:


    public static List<SPSiteInfo> getSiteSubSites(websProxy.Webs sharePointWebs)
    {
        try
        {
            XmlNode websResult = sharePointWebs.GetWebCollection();
            XDocument results = XDocument.Parse(websResult.OuterXml);
            XName name = XName.Get("Web", "http://schemas.microsoft.com/sharepoint/soap/");
            var webs = from item in results.Descendants(name)
                       select new SPSiteInfo
                           (
                           item.Attribute("Title").Value,
                           item.Attribute("Url").Value
                           );
            return webs.ToList();
        }
        catch (Exception ex)
        {
            throw ex;
        }
    }

    A destacar en el código anterior el uso de un elemento XName para obtener sólo los elementos de tipo Web:


    XName_Web


    A continuación basta con seleccionar los atributos deseados de cada elemento XML (en nuestro caso título y url) e ir creando objetos que almacenaremos en una colección. Aquí utilizo una sencilla clase llamada ‘SPSiteInfo’ para devolver una lista genérica. Esta clase tiene sólo dos propiedades de tipo string (Title y URL) y un constructor en el que le pasamos ambos valores:


    public class SPSiteInfo
    {
        public string Title { get; set; }
        public string URL { get; set; }
     
        public SPSiteInfo()
        {
            //
        }
     
        public SPSiteInfo(string title, string url)
        {
            Title = title;
            URL = url;
        }
    }

    De este modo, ya tenemos una función que nos devuelve una colección de objetos ‘SPSiteInfo’. Emplearemos este mismo procedimiento para recuperar las bibliotecas de documentos de un sitio, las carpetas de una biblioteca de documentos, e incluso las columnas de una biblioteca de documentos.


    Pero ello lo veremos en el próximo post, que hay que volver a las trincheras… :-)


    PD – Os recuerdo que al final de la serie publicaré el código fuente del proyecto de ejemplo.


    ** crossposting desde el blog de Lluís Franco en geeks.ms **

    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (III)

    Entradas anteriores de la serie:


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (I)


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (II)




     BenderDrinking


    Bueno, vamos a empezar a trabajar en nuestro Add-In!


    Aunque antes, una aclaración: Como la mayoría de vosotros ya sabéis, en realidad quién hace el trabajo sucio no soy yo. Yo sólo me llevo los millones y la fama, mientras que la ingrata tarea de picar código (que no gusta a nadie :-P) se la dejo a mi Bender. Efectivamente, acabáis de descubrir uno de los secretos mejor guardados: Detrás de un MVP siempre hay alguien haciendo el trabajo real mientras ellos se llevan el mérito… Esto ha sido así desde que el pionero Rubén Vigón enseñó a codificar a su gato con Visual Basic 3.0, y poco a poco la mayoría hemos ido ‘adoptando’ esta acertada filosofía, que en mi caso me ha permitido apartarme de estas tareas mundanas y dedicarme a mis viñedos y mi afición desmedida por la calceta.


    Así que en cuanto le propuse la idea del Add-In a Bender, le pareció que debíamos ponernos a ello en seguida (seguramente animado por la visión de las 2 cajas de un chardonnay excelente que recibirá como compensación al terminar). De modo que vamos a empezar en seguida.


    Nota: Al terminar la serie publicaré el proyecto de ejemplo con todo el código al completo.
    Un poco de paciéncia porque lo voy a ir haciendo sobre la marcha, ok?


    Creando un proyecto Add-In para Outlook 2007


    Esta es la parte más sencilla, ya que con Visual Studio 2008 (supongo que todos lo tenéis actualizado con el SP1) se incorporan las plantillas básicas para crear un proyecto de este tipo. Basta con iniciar un proyecto nuevo, y dentro de la categoría ‘Office’ encontraréis las plantillas para la versión 2007.


    OutlookAddInNewProject


    Esto crea un proyecto C# con las referencias necesarias para ejecutarse como complemento de Outlook, y proporciona el esqueleto básico para interceptar los eventos que se producen al cargar y descargar nuestro complemento.


    ProjectReferences


    using Outlook = Microsoft.Office.Interop.Outlook;
    using Office = Microsoft.Office.Core;
     
    namespace OutlookToMOSS
    {
        public partial class ThisAddIn
        {
            private void ThisAddIn_Startup(object sender, System.EventArgs e)
            {
     
            }
     
            private void ThisAddIn_Shutdown(object sender, System.EventArgs e)
            {
            }
     
            #region VSTO generated code
     
            /// <summary>
            /// Required method for Designer support - do not modify
            /// the contents of this method with the code editor.
            /// </summary>
            private void InternalStartup()
            {
                this.Startup += new System.EventHandler(ThisAddIn_Startup);
                this.Shutdown += new System.EventHandler(ThisAddIn_Shutdown);
            }
            
            #endregion
        }
    }

    Observar que hay una región de código generado por el diseñador, que es la encargada de suscribir los eventos ‘StartUp’ y ‘StartDown’. En estos eventos, posteriormente agregaremos el código para inicializar y destruir nuestras referencias a los servicios Web de SharePoint. Además para poder interactuar con Outlook interceptaremos además un par de eventos más llamados ‘ItemContextMenuDisplay’ y ’FolderContextMenuDisplay’, que se producen al mostrar los menús contextuales de Outlook (por ejemplo, al pulsar el botón derecho sobre una selección de elementos de correo).


    Agregando referencias a los servicios Web de SharePoint


    Lo primero es lo primero, así que ya que vamos a interactuar con SharePoint, vamos a agregar referencias Web a aquellos servicios que vamos a consumir. Aunque tenemos una lista de varios servicios Web, de entrada vamos a trabajar con estos dos, aunque probablemente añadiremos otros más adelante:


    • Webs Web Service: Para acceder a la colección de sitios Web de un sitio de SharePoint.
    • Lists Web Service: Para acceder a la colección de bibliotecas de documentos de un sitio.

    Para crear la referencia debemos conocer la URL de nuestro SharePoint (por ejemplo http://moss). Y agregar una ‘Service Reference’ al proyecto. Esto mostrará el cuadro de diálogo para agregar servicios WCF, pero como en nuestro caso vamos a agregar una referencia a un servicio Web 2.0 hay que dar un rodeo, ir a la parte avanzada y especificar que queremos crear una ‘Web reference‘:


    AddWebReference1


    Pulsar en el botón para mostrar las características avanzadas:


    AddWebReference2


    Especificar que deseamos agregar una ‘Web reference’ y en la nueva ventana especificar la URL del servicio, que siempre sigue esta nomenclatura:


    <URL Servidor SharePoint>/_vti_bin/<Nombre del servicio Web>


    En nuestro caso:


    http://moss/_vti_bin/Webs.asmx


    http://moss/_vti_bin/Lists.asmx


    Especificando el nombre (en mi caso tengo por costumbre llamarlos websProxy y listsProxy).


    AddWebReference3


    Una vez añadidas veremos que en el árbol del proyecto aparece un elemento llamado ‘Web References’ que contiene las dos referencias a los servicios Web.


    Interceptando eventos de Outlook


    Ahora, para hacerlo más interesante, antes de ejecutar vamos a interceptar el evento ‘ItemContextMenuDisplay’ que se produce cuando el usuario muestra el menú contextual de los elementos de correo (dicho de otro modo, selecciona uno o varios emails y pulsa botón derecho). Para ello modificaremos la sección generada por el diseñador, agergando la siguiente línea al método InternalStartUp():


    this.Application.ItemContextMenuDisplay += 
        new Microsoft.Office.Interop.Outlook.ApplicationEvents_11_ItemContextMenuDisplayEventHandler(
        Application_ItemContextMenuDisplay);

     Y lógicamente ahora crearemos el método que será llamado al dispararse el evento:


     
            void Application_ItemContextMenuDisplay(
                Microsoft.Office.Core.CommandBar CommandBar, 
                Microsoft.Office.Interop.Outlook.Selection Selection)
            {
                websProxy.Webs sharePointWebs;
                sharePointWebs = new websProxy.Webs();
                sharePointWebs.Credentials = System.Net.CredentialCache.DefaultCredentials;
                XmlNode webs = sharePointWebs.GetWebCollection();
            }

    Este código es provisional, pero nos servirá para ver como se debe llamar a un método de un servicio Web. Primeramente se declara y se crea el objeto, a continuación se especifican las credenciales (en este caso las predeterminadas) y a continuación se llama al método GetWebCollection(), el cual nos devuelve un objeto… “oh sorpresa! oh dolor! oh campos de soledad, mustios collados…”, un objeto de tipo XmlNode. Efectivamente, los servicios Web de SharePoint trabajan con XML a tutiplén, con lo cual ya te aviso de que si no te gusta trabajar con XML tienes dos opciones: Dejar ahora mismo de leer este artículo escrito por Bender y hacer como yo disfrutando la de vida alegre, o bien esperar al próximo post, en el que veremos como LINQ to XML puede hacernos la vida un poco más fácil.


    Ahoravamos a establecer un punto de ruptura en la última línea de código para ver que retorna el método GetWebCollection().


    Estamos listos para ejecutar?


    Pues si, ahora vamos a probar que sucede cuando ejecutamos nuestro proyecto. Como es un Add-In de Outlook va a arrancar éste primero y cuando hagamos click con el botón derecho sobre un elemento de correo entrará en acción el depurador permitiéndonos inspeccionar el valor de la variable ‘webs’, el cual os muestro a continuación, para que os asusteis un poco (hacer click en la imagen para verla más grande):


    XmlNode_webs


    Mañana espero poder continuar con la serie. Y veremos cómo agregar elementos a los menús de Outlook, y cómo LINQ to XML nos va a permitir trabajar más cómodamente con los datos devueltos por los métodos de los servicios Web. Que todos sabemos de las bondades de XML, pero trabajar con atributos nunca resulta tan sencillo como trabajar con colecciones de objetos genéricos, verdad?


    Pues eso, mañana más… que ahora me toca manicura y luego clase de tenis :-P


    ** crossposting desde el blog de Lluís Franco en geeks.ms **

    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (II)

    Entradas anteriores de la serie:


    SharePoint 2007 + Outlook 2007: Guardar correos de forma masiva (I)




    Vale, vamos a empezar a escribir la carta a los reyes magos:


    Reyes-Magos


    Queridos reyes magos,


    Este año me he portado muy bien así que me pido un add-in para Outlook que sirva para guardar los correos en bibliotecas de documentos de SharePoint, y que tenga las siguientes cositas:


    • Que se integre con los menús de Outlook, para poder seleccionar varios correos y copiarlos a la biblioteca que yo elija.
    • Que funcione tanto para MOSS como para WSS, para poder guardar correos en un servidor WSS que tengo en hosting en USA.
    • Que permita cambiar la ubicación del servidor de SharePoint para poder guardar los correos en el que me interese.
    • Que permita cambiar las credenciales de usuario, para permitir conectarme a diferentes sitios.
    • Que se encargue de crear las columnas necesarias para guardar los metadatos del elementos de correo (De, Para, Asunto, Enviado).
    • Que cuando guardemos una cantidad importante de correos no detenga la ejecución de Outlook, y podamos seguir trabajando mientras éstos se copian.
    • Y un detalle importante: Que tenga control de versiones, por si guardo dos veces el mismo elemento que me mantenga un histórico.

    Y para desarrollar algo así necesitaremos saber varias cosas, por ejemplo:


    • Cómo crear un complemento para Office (VSTO).
    • Cómo conectar con un servidor MOSS/WSS con diferentes credenciales de usuario (System.Net.NetworkCredential)
    • Cómo acceder a los sitios y listas de un site de MOSS/WSS (servicios Web de SharePoint).
    • Cómo interpretar la información XML devuelta por los servicios (LINQ to XML).
    • Cómo guardar un fichero en una biblioteca de documentos de SharePoint con control de versiones (DocLibHelper).
    • Cómo ejecutar distintos threads y mostrar el progreso (Callbacks).
    • Cómo distribuir nuestro complemento, creando un archivo MSI (Windows Installer packages).

    Y seguramente me dejo unas cuantas cosillas… pero ya las iremos abordando a medida que nos las encontremos. Al fin y al cabo esto es un proyecto de ejemplo y personal, así que sintiéndolo mucho no voy a utilizar SCRUM ;-)


    Un saludo y mañana prometo empezar con el tema!


    ** crossposting desde el blog de Lluís Franco en geeks.ms **