lunes, 12 de diciembre de 2011

[SharePoint 2010] - Resolver usuarios y grupos a partir de su Login Name o Email - SPUtility.ResolvePrincipal

Cuantas veces no hemos requerido buscar a un usuario a partir de su Email o de su LoginName, para obtener uno de estos datos, por ejemplo para enviarle un correo o asignarle una tarea dentro de un flujo de trabajo.

En el pasado utilizábamos el método GetLoginNameFromEmail de la clase SPUtility si conocíamos el email y necesitábamos el Login Name del usuario, sin embargo en la versión 2010 de la API de SharePoint este método ha sido marcado como obsoleto, lo que significa que si lo usamos producirá una alerta la cual nos recomienda, en su lugar, el uso del método ResolvePrincipal.

La ventaja de utilizar ResolvePrincipal es, en primera, que obtenemos de regreso un objeto del tipo SPPrincipalInfo el cual tiene varias propiedades útiles para trabajar con usuarios y grupos. Segunda, que como parámetro para realizar la búsqueda recibe el Display Name, Login Name o Email (solo uno de ellos), es decir que si conocemos cualquiera de estos tres valores de un usuario o grupo es posible resolverlo. Otro aspecto importante es que la búsqueda que realiza no está restringida a un sitio de SharePoint en particular.

A continuación un par de ejemplos de cómo utilizar este método para resolver usuarios.

SPWeb web = SPContext.Current.Site.RootWeb; //or SPControl.GetContextWeb(Context) or workflowProperties.Site.RootWeb, etc

//obtener el usuario a partir del display name
SPPrincipalInfo info = SPUtility.ResolvePrincipal(web, "Homero J. Simpson", SPPrincipalType.User, SPPrincipalSource.MembershipProvider | SPPrincipalSource.Windows, null, false);

//obtener el usuario a partir del Email
info = SPUtility.ResolvePrincipal(web, "hsimpson@sp2010.com", SPPrincipalType.All, SPPrincipalSource.All, web.AllUsers, true);

Por último, aunque no menos importante, mencionar que este método no esta disponible en soluciones SandBox.

Happy Coding!

lunes, 21 de noviembre de 2011

QI for IEnumVARIANT failed on the unmanaged server

Bueno, por si alguna vez les ha ocurrido este error al tratar de ingresar a la página de administración del servicio de búsqueda.


Inclusive al intentar recuperar algún valor mediante PowerShell este mismo error aparece, una posible solución que a mí me funciono fue reiniciar el IIS utilizando el siguiente comando

iisreset /noforce

Esto en cada uno de los servidores de crawling

Happy Configuring!

Crear custom workflow tasks forms [No Infopath]

Como recordaran en el post anterior, mostré un ejemplo de cómo crear columnas de sitio y content types programáticamente, en este post voy a dar un ejemplo de cómo crear un formulario personalizado para el tipo de contenido que creamos en ese ejemplo anterior.

Es común que creemos tipos de contenido personalizados (custom content types) para asociarlos a la lista de tareas de workflows (Workflow Tasks), esto con el fin de poder recolectar más información del usuario cuando se le asigna una tarea dentro de un flujo de trabajo, y también es muy común que necesitemos personalizar el formulario de captura que el usuario utiliza para ingresar esa información adicional que necesitamos recolectar dentro del flujo de trabajo. Por lo anterior y utilizando el tipo de contenido que creamos en el post anterior, y que de manera premeditada hice que heredara del tipo de contenido WorkflowTask (recuerdan?).
También es importante mencionar que el custom approval workflow task form que crearemos será utilizando un formulario ASP.NET, creo que la mayoría sabe que no simpatizo mucho con InfoPath, además que ejemplos de esos hay bastantes en la red.
El escenario de ejemplo es el siguiente, cree una lista llamada “Solicitud Vacaciones”, en la que los usuarios podrán capturar las peticiones para tomar días de vacaciones, una vez que registren sus solicitud se enviará un correo de aviso al usuario responsable y se le asignará una tarea para que apruebe o rechace dicha solicitud, en caso de aprobación o rechazo se le notificará al usuario solicitante.
Como primer paso, creamos un flujo de trabajo sencillo, en este caso construiré un flujo de aprobación de solicitud de vacaciones, como se aprecia en la siguiente imagen:
   
No voy a explicar el cómo construir el flujo de trabajo, dado que la finalidad de este post no es mostrar cómo hacer un flujo de trabajo,  adicional a que hay muchos ejemplos en la red. Sin embargo voy a compartir el código por si a alguien le interesa ver cómo fue construido el Workflow.

Como se aprecia en la siguiente imagen, se agregó una actividad de tipo CreateTaskWithContentType la cual recibe el Id del Content Type a partir del cual se creara la tarea que será asignada al usuario responsable de autorizar la solicitud de vacaciones.








 

Como se aprecia en la imagen, el ID del tipo de contenido se asocia a una propiedad declarada en código, la que manipularemos para buscar y asignar el ID de tipo de contenido correcto cuando el Workflow se inicialice, en el evento WorkflowActivated.


private void onWorkflowActivated(object sender, ExternalDataEventArgs e)
{
 WorkflowContext.Initialize(workflowProperties);
 workflowID = workflowProperties.WorkflowId;

 //si el tipo de contenido no ha sido asociado a la lista de tareas
 try
 {
  if (workflowProperties.TaskList.ContentTypes[contentTypeName] == null)
  {
   SPContentType ctype = WorkflowContext.Web.AvailableContentTypes[contentTypeName];
   if (ctype != null)
   {
    workflowProperties.TaskList.ContentTypes.Add(ctype);
    workflowProperties.TaskList.Update();
   }
  }

  SPContentType customContentType = WorkflowContext.Web.AvailableContentTypes[contentTypeName];
  this.CustomContentTypeId = customContentType.Id.ToString();

 }
 catch (Exception ex)
 {
  //registramos la excepcion
 }
}

Estableciendo el formulario de edición al tipo de Contenido

Regresando a la definición del tipo de contenido que creamos en el post anterior, que cómo podrán recordar fue provisionado programáticamente. El siguiente paso es establecer por lo menos el formulario de edición del tipo de contenido, es decir, el formulario que el usuario visualizará cuando vaya a aprobar o rechazar la tarea asignada. Para que el Content Type sepa que formulario debe invocar, utilizaremos la propiedad EditFormUrl la que estableceremos con el valor del formulario ASP.NET que vamos a crear en el siguiente paso.

Noten la línea 131:

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;

namespace WF1.Features.Feature1
{
    /// 
    /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade.
    /// 
    /// 
    /// The GUID attached to this class may be used during packaging and should not be modified.
    /// 

    [Guid("dd6b6ec4-3391-46de-a659-55244237f346")]
    public class Feature1EventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            try
            {
                SPWeb web = null;
                if (properties.Feature.Parent is SPSite)
                {
                    SPSite sites = (SPSite)properties.Feature.Parent;
                    web = sites.RootWeb;
                }
                else
                {
                    web = (SPWeb)properties.Feature.Parent;
                }
                if (web == null)
                    return;

                //Nombre del grupo de Content Types que crearemos
                string contentTypeGroupName = "Blog - Content Types";
                //Nombre del tipo de contenido que crearemos
                string contentTypeName = "Blog - Response Workflow Content Type";
                //Nombre del grupo de columnas de sitio que crearemos
                string columnGroupName = "Blog - Site Columns";
                //nombre de la columna de sitio que permitira al usuario seleccionar su respuesta (Approve | Reject)
                string choiceFieldName = "ChoiceWFResponse";
                //nombre de la columna de sitio que permitira al usuario capturar comentarios a cerca de su respuesta
                string commentsFieldName = "CommentsWFResponse";


                SPFieldMultiLineText approveRejectCommentsField = null;
                SPFieldChoice approveRejectChoiceField = null;

                /*Columna de sitio para seleccionar Aprobar o Rechazar*/
                //mucho ojo! Validamos si la columa de sitio ya existe
                if (!web.Fields.ContainsField(choiceFieldName))
                {
                    string approveReject = web.Fields.Add(choiceFieldName, SPFieldType.Choice, true);
                    approveRejectChoiceField = (SPFieldChoice)web.Fields.GetFieldByInternalName(approveReject);
                    approveRejectChoiceField.Group = columnGroupName;
                    approveRejectChoiceField.EditFormat = SPChoiceFormatType.RadioButtons;
                    approveRejectChoiceField.Choices.Add("Approve");
                    approveRejectChoiceField.Choices.Add("Reject");
                    approveRejectChoiceField.DefaultValue = "Approve";
                    approveRejectChoiceField.Update();
                }
                //Si ya existe, solo obtenemos una referencia a la columna
                else
                {
                    approveRejectChoiceField = web.Fields[choiceFieldName] as SPFieldChoice;
                }

                /*Columna de sitio para permitir al usuario ingresar un comentario*/
                if (!web.Fields.ContainsField(commentsFieldName))
                {
                    string comments = web.Fields.Add(commentsFieldName, SPFieldType.Note, true);
                    approveRejectCommentsField = (SPFieldMultiLineText)web.Fields.GetFieldByInternalName(comments);
                    approveRejectCommentsField.Group = columnGroupName;
                    approveRejectCommentsField.Update();
                }
                else
                {
                    approveRejectCommentsField = web.Fields[commentsFieldName] as SPFieldMultiLineText;
                }

                //
                //Ahora creamos el tipo de contenido
                //

                //heredamos del WorkflowTask Content Type
                SPContentType workflowTaskCType = web.AvailableContentTypes[SPBuiltInContentTypeId.WorkflowTask];
                SPContentType approvalRejectCType = null;
                bool isUpdating = false;

                //validamos si el tipo de contenido ya existe
                if (web.AvailableContentTypes[contentTypeName] == null)
                {
                    approvalRejectCType = new SPContentType(workflowTaskCType, web.ContentTypes, contentTypeName);
                    approvalRejectCType = web.ContentTypes.Add(approvalRejectCType);
                    approvalRejectCType.Group = contentTypeGroupName;
                }
                else
                {
                    approvalRejectCType = web.ContentTypes[contentTypeName];
                    isUpdating = true;
                }

                if (!isUpdating)
                {
                    //agregamo la columna de Seleccion
                    SPFieldLink approveRejectFieldRef = new SPFieldLink(approveRejectChoiceField);
                    approveRejectFieldRef.Required = true;
                    approveRejectFieldRef.DisplayName = "Approve or Reject the task";
                    approvalRejectCType.FieldLinks.Add(approveRejectFieldRef);

                    //agregamo la columna de Comentarios
                    SPFieldLink commentsFieldRef = new SPFieldLink(approveRejectCommentsField);
                    commentsFieldRef.DisplayName = "Comments";
                    approvalRejectCType.FieldLinks.Add(commentsFieldRef);
                }
                else
                {
                    //Actualizamos la columna de Seleccion
                    SPFieldLink approveRejectFieldRef = approvalRejectCType.FieldLinks[approveRejectChoiceField.Id];
                    approveRejectFieldRef.Required = true;
                    approveRejectFieldRef.DisplayName = "Approve or Reject the task";

                    //Actualizamos la columna de Comentarios
                    SPFieldLink commentsFieldRef = approvalRejectCType.FieldLinks[approveRejectCommentsField.Id];
                    commentsFieldRef.DisplayName = "Comments";
                }

                approvalRejectCType.EditFormUrl = "_layouts/WF1/CustomApprovalTask.aspx";

                //actualizamos el tipo de contenido
                approvalRejectCType.Update(true);
            }
            catch (Exception ex)
            {
                //log Exception
            }
        }

    }
}

Existen algunas otras propiedades con las cuales podemos indicar que formulario debe mostrarse dependiendo de la vista que sea solicitada, tales como: DisplayFormUrl, MobileDisplayFormUrl, etc, etc

Creación del formulario

El siguiente paso es la creación del formulario que será invocado cuando el usuario haga clic en editar tarea. Para esto agregamos un elemento Application Page y la nombramos como CustomApprovalTask.aspx, el cual usaremos para crear la forma de edición para el tipo de contenido.



A continuación el código del formulario, en realidad es bastante sencillo solo presenta un par de controles, una caja de texto para capturar los comentarios y un dropdownlist para elegir una opción (Aprobar, Rechazar).



using System;
using System.Collections;
using System.Web;
using System.Web.UI.WebControls;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using Microsoft.SharePoint.Workflow;

namespace WF1.Layouts
{
    public partial class CustomApprovalTask : LayoutsPageBase
    {
        protected SPList targetTasksList;
        protected SPListItem targetTask;
        protected SPWorkflow workflowInstance;
        protected SPWorkflowModification workflowModification;

        string choiceFieldName = "ChoiceWFResponse";
        //nombre de la columna de sitio que permitira al usuario capturar comentarios a cerca de su respuesta
        string commentsFieldName = "CommentsWFResponse";


        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);
            /**/
            // Retrieve the current task list and task item
            targetTasksList = Web.Lists[new Guid(Request.Params["List"])];
            targetTask = targetTasksList.GetItemById(int.Parse(Request.Params["ID"]));

            //Obtiene el autorizador de la tarea
            string Aprobador = Convert.ToString(targetTask[SPBuiltInFieldId.AssignedTo]);
            int IdAporbador = Convert.ToInt32(Aprobador.Remove(Aprobador.IndexOf(';'), Convert.ToInt32(Aprobador.Length - Aprobador.IndexOf(";"))));

            if ((Web.CurrentUser.ID != IdAporbador) )
            {
                ClosePopup();
            }

            /**/
            if (!this.Page.IsPostBack)
            {
                // Populate fields on the form
                lblTitle.Text = targetTask["Title"] != null ? targetTask["Title"].ToString() : string.Empty;
                txtCommnents.Text = targetTask[commentsFieldName] != null ? targetTask[commentsFieldName].ToString() : string.Empty;

                ListItem selectedItem = ddlResponse.Items.FindByValue(targetTask[choiceFieldName].ToString());

                if (selectedItem != null)
                {
                    this.ddlResponse.ClearSelection();
                    selectedItem.Selected = true;
                }
            }

        }

        protected void UpdateTaskFromControls(SPListItem targetTask, System.Collections.Hashtable taskProperties)
        {
            // Update task item fields
            taskProperties[SPBuiltInFieldId.TaskStatus] = SPWorkflowStatus.Completed;
            taskProperties[commentsFieldName] = txtCommnents.Text;
            taskProperties[choiceFieldName] = ddlResponse.SelectedValue;
        }

        public void btnSaveTask_Click(object sender, EventArgs e)
        {
            try
            {
                Hashtable taskProperties = new Hashtable();
                UpdateTaskFromControls(targetTask, taskProperties);
                SPWorkflowTask.AlterTask(targetTask, taskProperties, false);

            }
            catch (Exception exception)
            {
                SPUtility.Redirect("Error.aspx", SPRedirectFlags.RelativeToLayoutsPage,
                HttpContext.Current,
                "ErrorText=" + SPHttpUtility.UrlKeyValueEncode(exception.Message));
            }
            ClosePopup();
        }

        public void Cancel_Click(object sender, EventArgs e)
        {
            ClosePopup();
        }

        protected void ClosePopup()
        {
            this.Response.Clear();
            this.Response.Write("Closing ...");
            this.Response.Flush();
            this.Response.End();
        }
    }
}





Pueden descargar el código de aquí

Happy Coding!

miércoles, 12 de octubre de 2011

Creando Site Columns y Content Types programáticamente

El día de hoy quiero compartir con ustedes un ejemplo de cómo crear columnas de sitio y tipos de contenido programáticamente. El objetivo final es crear un tipo de contenido que herede del tipo de contenido WorkflowTask (el que se utiliza para las tareas que se asignan en los flujos de trabajo), utilizando dos columnas de sitio, la primera será una columna que permitirá al usuario capturar su respuesta (Aprobar | Rechazar), la segunda permitirá capturar comentarios acerca de su respuesta.

Sin mucho preámbulo comencemos creando un proyecto en blanco con un elemento Feature con scope Site, al cual agregaremos un Event Receiver
Inmediatamente después comenzamos a tirar un poco de código en el método FeatureActivated para crear en primera instancia las dos columnas de sitio que utilizaremos:
SPWeb web = null;
if (properties.Feature.Parent is SPSite)
{
    SPSite sites = (SPSite)properties.Feature.Parent;
    web = sites.RootWeb;
}
else
{
    web = (SPWeb)properties.Feature.Parent;
}
if (web == null)
    return;
                
//Nombre del grupo de Content Types que crearemos
string contentTypeGroupName = "Blog - Content Types";
//Nombre del tipo de contenido que crearemos
string contentTypeName = "Blog - Response Workflow Content Type";
//Nombre del grupo de columnas de sitio que crearemos
string columnGroupName = "Blog - Site Columns";
//nombre de la columna de sitio que permitira al usuario seleccionar su respuesta (Approve | Reject)
string choiceFieldName = "ChoiceWFResponse";
//nombre de la columna de sitio que permitira al usuario capturar comentarios a cerca de su respuesta
string commentsFieldName = "CommentsWFResponse";


SPFieldMultiLineText approveRejectCommentsField = null;
SPFieldChoice approveRejectChoiceField = null;
                
/*Columna de sitio para seleccionar Aprobar o Rechazar*/
//mucho ojo! Validamos si la columa de sitio ya existe
if (!web.Fields.ContainsField(choiceFieldName))
{
    string approveReject = web.Fields.Add(choiceFieldName, SPFieldType.Choice, true);
    approveRejectChoiceField = (SPFieldChoice)web.Fields.GetFieldByInternalName(approveReject);
    approveRejectChoiceField.Group = columnGroupName;
    approveRejectChoiceField.EditFormat = SPChoiceFormatType.RadioButtons;
    approveRejectChoiceField.Choices.Add("Approve");
    approveRejectChoiceField.Choices.Add("Reject");
    approveRejectChoiceField.DefaultValue = "Approve";
    approveRejectChoiceField.Update();
}
//Si ya existe, solo obtenemos una referencia a la columna
else
{
    approveRejectChoiceField = web.Fields[choiceFieldName] as SPFieldChoice;
}

/*Columna de sitio para permitir al usuario ingresar un comentario*/
if (!web.Fields.ContainsField(commentsFieldName))
{
    string comments = web.Fields.Add(commentsFieldName, SPFieldType.Note, true);
    approveRejectCommentsField = (SPFieldMultiLineText)web.Fields.GetFieldByInternalName(comments);
    approveRejectCommentsField.Group = columnGroupName;
    approveRejectCommentsField.Update();
}
else
{
    approveRejectCommentsField = web.Fields[commentsFieldName] as SPFieldMultiLineText;
}

Una vez que las hemos creado, procedemos a crear el tipo de contenido utilizando las columnas de sitio.
//
//Ahora creamos el tipo de contenido
//

//heredamos del WorkflowTask Content Type
SPContentType workflowTaskCType = web.AvailableContentTypes[SPBuiltInContentTypeId.WorkflowTask];
SPContentType approvalRejectCType = null;
bool isUpdating = false;

//validamos si el tipo de contenido ya existe
if (web.AvailableContentTypes[contentTypeName] == null)
{
    approvalRejectCType = new SPContentType(workflowTaskCType, web.ContentTypes, contentTypeName);
    approvalRejectCType = web.ContentTypes.Add(approvalRejectCType);
    approvalRejectCType.Group = contentTypeGroupName;
}
else
{
    approvalRejectCType = web.ContentTypes[contentTypeName];
    isUpdating = true;
}

if (!isUpdating)
{
    //agregamo la columna de Seleccion
    SPFieldLink approveRejectFieldRef = new SPFieldLink(approveRejectChoiceField);
    approveRejectFieldRef.Required = true;
    approveRejectFieldRef.DisplayName = "Approve or Reject the task";
    approvalRejectCType.FieldLinks.Add(approveRejectFieldRef);

    //agregamo la columna de Comentarios
    SPFieldLink commentsFieldRef = new SPFieldLink(approveRejectCommentsField);
    commentsFieldRef.DisplayName = "Comments";
    approvalRejectCType.FieldLinks.Add(commentsFieldRef);
}
else
{
    //Actualizamos la columna de Seleccion
    SPFieldLink approveRejectFieldRef = approvalRejectCType.FieldLinks[approveRejectChoiceField.Id];
    approveRejectFieldRef.Required = true;
    approveRejectFieldRef.DisplayName = "Approve or Reject the task";

    //Actualizamos la columna de Comentarios
    SPFieldLink commentsFieldRef = approvalRejectCType.FieldLinks[approveRejectCommentsField.Id];
    commentsFieldRef.DisplayName = "Comments";
}

//actualizamos el tipo de contenido
approvalRejectCType.Update(true);

Hacemos Deploy de nuestro proyecto y validamos que se hayan creado las columnas de sitio y el tipo de contenido


Listo!

Pueden descargar el código de aquí

Happy Coding!

martes, 20 de septiembre de 2011

Reemplazando el diagrama de Workflow en SharePoint 2010 usando un Delegate Control

Como sabemos, los controles delegados (Delegate Controls) son básicamente contenedores que encapsulan contenido y que SharePoint permite substituir este contenido con contenido personalizado diferente a la implementación predeterminada. Una de sus funciones primordiales es proveer de una manera fácil de hacer Branding de los sitios SharePoint. SharePoint define varios controles delegados dentro de la master page v4.


En este post, les propongo usar esta característica para sustituir (y mejorar) la manera predeterminada en que se muestran los diagramas de los flujos de trabajo en la página de detalle del flujo de trabajo en SharePoint 2010.


Y poder adicionarle un poco de vida!


Primero se debe crear un proyecto nuevo y vacío (obviamente de SharePoint 2010)

Agregamos un usercontrol, en el cual vamos a escribir el código aspx que sustituirá al diagrama de flujo. De hecho este user control tendra el mismo contenido que el WorkflowStatus.ascx, y solo agregaremos una referencia a un archivo js que será el encargado de manipular el diagrama de Visio utilizando la API de Visio Services.

Agregamos una nueva Feature con Scope Site a la solución, la cual se encargará de desplegar el elemento que crearemos en el siguiente paso

La magia viene aquí; agregamos un nuevo elemento a la solución de tipo Empty Element y agregamos la siguiente definición:

El valor del atributo Id y el atributo Sequence son importantes ya que el Id debe coincidir con la definición del control que queremos reemplazar, y el valor de la propiedad Sequence debe ser menor a la especificada en la definición original del control para que tenga precedencia sobre este. A continuación como fue definido originalmente el control en la feature VisioWebAccess

Si quieren saber como poder agregar este tipo de mejoras a los diagramas de Visio, pueden checar dos de mis anteriores posts:

Anticipando algunas de sus posibles preguntas:
Porque utilizar un delegate control solo para adicionar la referencia a un archivo js?
Se podría haber agregado la referencia a este archivo .js desde la Master Page, incluso se puede sustituir el archivo workflowstatus.js que es el que se encarga de manera predeterminada del dibujado de todos los diagramas de Visio para los flujos de trabajo, sin embargo esto es muy invasivo ya que sería un cambio global y de la manera que este post propone el cambio sería a nivel de Sitio, es decir, lo menos invasivo posible.

Happy Coding!

martes, 6 de septiembre de 2011

Descargar los prerequisitos de SharePoint 2010 offline

Como muchos de ustedes saben, el nuevo instalador de SharePoint 2010 nos permite instalar los prerequisitos que nos hagan falta antes de comenzar con el proceso de instalación, lo que es bastante cómodo si recordamos la forma manual que se utilizaba para instalar SharePoint 2007.


Sin embargo hay algunos escenarios en el mundo real en los cuales no podemos utilizar esta ventajosa herramienta, específicamente me refiero al siguiente escenario:

Estamos haciendo un despliegue de una granja SharePoint en un ambiente (Desarrollo, QA, Producción, DRP, etc.) en el cual no tenemos acceso a internet y aunque hemos suplicado por él, el equipo de Seguridad no lo permitiría jamás.

Luego entonces lo que necesitamos hacer es descargar en una máquina en la cual si tengamos acceso a internet, todos los componentes de prerequisitos para SharePoint 2010 y posteriormente copiar esos archivos a cada uno de los servidores en los que haremos el despliegue de la granja.

ü  SQL Server 2008 Native Client (sqlncli.msi)
ü  Microsoft Chart Controls for the Microsoft .NET Framework 3.5 (MSChart.exe)
ü  Microsoft .NET Framework version 3.5 SP1 (dotnetfx35.exe)
ü  Windows PowerShell 2.0 (Windows6.0-KB968930-x64.msu)
ü  KB976394 (Windows6.0-KB976394-x64.msu)
ü  KB976492 (Windows6.1-KB976462-v2-x64.msu)
ü  Windows Identity Foundation (WIF) for Windows Server 2008 R2 (Windows6.1-KB974405-x64.msu)
ü  Microsoft Sync Framework Runtime (Synchronization-v2.0-x64-ENU.msi)
ü  Microsoft Filter Pack 2.0 (FilterPack64bit.exe)
ü  Microsoft SQL Server 2008 Analysis Services ADOMD.NET (SQLSERVER2008_ASADOMD10.msi)
ü  SQL Server 2008 R2 November CTP Reporting Services Add-in (rsSharePoint.msi)
ü  Microsoft Server Speech Platform (SpeechPlatformRuntime.msi)
ü  Speech recognition language for English (MSSpeech_SR_en-US_TELE.msi)

Listado de requisitos de software para SharePoint 2010

No obstante, esta solución es demasiado manual y nos consume demasiado tiempo, (el Facebook no se actualiza solo, o si?)

Por lo que les recomiendo utilicen mejor, el script “Download-All_SP2010_PreReqs.ps1” que forma parte de la utilería AutoSPInstaller, la cual podemos descargar de Codeplex, y que nos permite hacer esa descarga de manera automática hacia una carpeta para posteriormente copiarlos hacia los servidores que no tienen acceso a internet.

Al ejecutarlo nos pedirá la ruta en la cual se descargaran todos los componentes, la indicamos y hacemos click en OK

El script comienza a trabajar

Y ya solo nos resta esperar a que se terminen de descargar todos los componentes, los cuales quedarán en la carpeta "PrerequisiteInstallerFiles".

Espero que este tip les sirva.

Happy Configuring

domingo, 14 de agosto de 2011

MOSS MA not found

Aprovecho este espacio para dejar constancia de otro de los posibles inconvenientes a los que nos enfrentamos al tratar de desplegar el servicio de profile (UPS – User Profile Service) de manera completa en SharePoint 2010.


El error: “MOSS MA not found”, algo más que confuso y hasta cierto punto gracioso, obviamente no la primera vez que te enfrentas a él.

Cuando aparece: Cuando se está creando o editando la conexión hacia el AD (Directorio Activo), después de hacer click en OK.

Resolución: Si alguno de los servicios de Forefront están detenidos, entonces primero debes asegurarte que se inicien, yo personalmente recomiendo que no traten de iniciar los servicios manualmente, sino desde el Sitio Central de Administración de SharePoint. Si los servicios de Forefront están ya iniciados, pues…. entonces reinícienlos :), esto debería permitirles editar o crear su conexión sin que se vuelva a presentar el error.



lunes, 8 de agosto de 2011

Unable to connect to the Synchronization Service

Si ustedes como yo, son uno más de los desafortunados a los que les ha tocado pagar todos los pecados de sus vidas anteriores, con el servicio de User Profile Synchronization que esta a nuestra disposición en SharePoint 2010. Tal vez ya se hayan encontrado con varios de esos "comunes", y hasta cierto punto aceptados,  errores de configuración.

El día de hoy quiero dejar precedente de uno de los últimos con los que he tenido que lidiar. El mensaje de error es el mismo que intitula este post y se presenta al intentar abrir la herramienta que acompaña al servicio de sincronización, me refiero al FIM Client (Forefront Identity Manager), el cual se encuentra usualmente en la siguiente ruta:

%programfiles%\Microsoft Office Server\14.0\Synchronization Service\UIShell\miisclient.exe

Y el cual nos ayuda a rastrear que hace y la mayoria del tiempo los errores que se presentan en este servicio.
Unable to connect to the Synchronization Service

Como siempre, mi primera recomendación es que respiren profundo.

Como en pocas ocasiones, el mensaje de error que se muestra es perfectamente claro y muy conciso.

  1. Revisen si el servicio esta ejecutándose en el servidor desde el cual están intentando acceder al FIM Client, si el servicio no esta iniciado, es imposible abrir el FIM Client.
  2. Revisen si el usuario con el que se está ejecutando este utilería pertenece al grupo de FIMSyncAdmins, el cual se crea cuando se provisiona el servicio de sincronización y al cual, en teoría, el usuario con el cual se ejecuta el servicio es agregado automáticamente.

Si estas dos sugerencias ya las han revisado y asegurado que se cumplen, es posible que estén en el mismo escenario que el mío, y es que, acabando recién de provisionar el servicio de sincronización UPS (User Profile Synchronization), cuando me disponía a utilizar el FIM client, para mi sorpresa apareció este error .

La raíz y solución del contratiempo: como bien mencioné, al provisionar el servicio, este asigna los permisos y roles necesarios a la cuenta de servicio, entre ellos, se asigna al grupo FIMSyncAdmins. Solo que para que estos permisos sean efectivos es necesario efectuar un logoff y posteriormente un logon. Eso es todo!

Happy configuring!

jueves, 28 de julio de 2011

Fast Search Center Template is Missing in SharePoint 2010

Les ha ocurrido que ya después de haber pasado penurias en desplegar Fast Search Server 2010 for SharePoint (FS4SP), configurar los services search applications (SSA) necesarios, y habilitar su comunicación por medio de la importación/exportación de certificados para que SharePoint se comunique con FS4SP… que después de todo este trabajo, vayan ilusos confiados a su Sitio Web a crear un nuevo sitio que utilice el template de Fast Search Center para proveer a los usuarios de la capacidad de búsquedas empresariales y que al intentar seleccionar este template del apartado de Search, este template no aparezca!?


Evidentemente, lo primero que se nos cruza por la mente es validar que las dos características a nivel de sitecollection requeridas estén activadas…. Y si, si están activadas

Ok, después de esta sucia broma del destino, lo que necesitamos hacer para que este template aparezca como opción al momento de crear un nuevo sitio es activar la característica de SearchExtensions, la cual es de tipo hidden y que pueden activar utilizando el pequeño scritp de powershell que a continuación muestro:
Enable-SPFeature –Id SearchExtensions –url http://sp2010/

miércoles, 8 de junio de 2011

[Tip] Agregar automáticamente los cmdlets de SharePoint a PowerShell ISE

Si a ustedes como a mi, les gusta usar Windows PowerShell ISE a la hora de escribir scripts PowerShell, principalmente,  aunque no únicamente, para SharePoint Server 2010, les mostraré un tip para evitar el uso del comando Add-PSSnapin para registrar los cmdlets de SharePoint cada vez que abrimos esta aplicación.

Antes que nada, si ustedes aún no usan Windows PowerShell ISE, lo recomiendo ampliamente por sus características de Debugging, si aún no lo usan porque no lo tienen activado, recuerden que Windows PowerShell ISE es instalado en Windows 2008/R2 por default, solo que no es activado, y esto se logra mediante la activación de la característica “Windows PowerShell Integrated Scripting Environment (ISE)”.

Una vez que ya lo han activado e iniciado, se darán cuenta que si intentan usar algún cmdlet de SharePoint, este no funciona y es porque no ha sido registrado el paquete de cmdlets de SharePoint, para esto usamos el cmdlet Add-PSSnapin Microsoft.SharePoint.PowerShell, ahora bien, volviendo a la intención original de este post, que es evitar escribir este comando cada vez que abrimos el Windows PowerShell ISE, podemos editar ó crear (en caso de no existir) una archivo de perfil de usuario, con el fin de que cada vez que abramos esta aplicación, este archivo de perfil de usuario se encargue de cargar todos los paquetes de cmdlets que hayamos indicado.

Existen cuatro diferentes ámbitos de perfiles en los cuales se puede crear este archivo, para mayor información revisen este link de technet, yo personalmente recomiendo que usen el que aplica a todos los usuarios y todos los shells, con esto se evitaran repetir esta acción entre diferentes usuarios y diferentes consolas de Shell, este archivo lo pueden referenciar a través de $profile.AllUsersAllHosts. A continuación los pasos que pueden usar para crear este archivo y agregar los paquetes de cmdlets que deben cargarse cada que se lanza la aplicación, en este ejemplo solo agrego el de SharePoint 2010.

Primero se debe abrir el archivo de perfil (profile.ps1) si existe de lo contrario crearlo, para esto se puede abrir directamente desde el file system o mediante un comando de PowerShell como se observa a continuación:


if (!(test-path $profile.AllUsersAllHosts))
{
    new-item -type file -path $profile.AllUsersAllHosts-force
}

Una vez que el archivo está abierto solo se debe agregar el siguiente código:
#se edita el archivo de perfil
psEdit $profile.AllUsersAllHosts
If ((Get-PSSnapIn -Name Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue) -eq $null )
{ 
    Add-PSSnapIn -Name Microsoft.SharePoint.PowerShell 
    #agregue aqui sus modulos favoritos!
}


Después guardar el archivo y listo, la siguiente vez que se lance la aplicación automáticamente se cargarán los módulos indicados.

Happy Coding!

lunes, 30 de mayo de 2011

[Tip] Convertir un Diagrama de Visio a un archivo .xap de Silverlight

Algunos lectores del blog me han comentado que aunque les parecen muy atractivos los resultados que se pueden obtener al personalizar un diagrama utilizando JavaScript, también me comentan que les parece muy complicado estar "buscando" objetos en el DOM, una vez que el diagrama ha sido dibujado.

Me explico, una vez que el diagrama ha sido rendereado, es entonces cuando podemos comenzar a interactuar con él, en términos de manipular los objetos (Silverlight principalmente), para esta manipulación casi siempre necesitamos saber el ID del control, para posteriormente modificar alguna de sus propiedades, inclusive para removerlo del diagrama. Importante es mencionar que estos ID's se autogeneran cuando el diagrama es transformado a .xap, por lo que no tenemos certeza de cuál es el nombre una vez generado.

Es entonces este proceso de "búsqueda" de ID's de los controles a modificar el que se vuelve un poco tedioso, luego entonces, el motivo de este post es mostrarles un workaround proceso alternativo con el que podemos tener acceso al archivo .XAP de un diagrama de Visio, lo cual nos permitirá ver la jerarquía de controles, sus respectivos ID's e inclusive los datos que son enviados al cliente (de esto hablaremos en un post siguiente).

Y lo único que tenemos que hacer es guardar el diagrama como página web y una vez que concluya el asistente, abrir la carpeta de archivos que genera y examinar los archivos .xaml que se encuentran dentro, puede haber tantos archivos .xaml como paginas tenga el diagrama, en mi ejemplo el diagrama contiene tres paginas.



Happy Coding!

jueves, 26 de mayo de 2011

Reseña Cuarto Simposio Latinoamericano de SharePoint 2011

Como muchos de ustedes saben, tuve el privilegio de ser invitado a participar como expositor con una charla en el pasado Cuarto Simposio Latinoamericano de SharePoint 2011, que se llevo acabo el 29 de Abril pasado en las instalaciones de Microsoft México.  En aquella ocasión, mi participación fue acerca de Visio Services en SharePoint 2010.

Dejo el link de la reseña oficial del evento, desde el sitio de la comunidad SharePoint de México.
También una reseña apócrifa realizada por mi buen amigo Haaron Gonzalez



La presentación la pueden descargar de aquí

Solo queda volver a agradecer a Luis Du Solier por la invitación y confianza.

domingo, 22 de mayo de 2011

Agregando controles Silverlight a un diagrama de Visio Services











Hasta el momento he omitido intencionalmente el mencionar uno de los hechos mas relevantes, y que tiene que ver con la forma en que el diagrama es dibujado (rendereado) en el navegador.

Existen dos únicas formas en las que el diagrama de Visio puede ser dibujado en el navegador, en formato Raster ó usando Silverlight!!!.

Así es, una de las tecnologías favoritas de este humilde desarrollador, vuelve a hacerse presente en un escenario en el que la interacción del usuario con el navegador es parte fundamental de la experiencia final de uso. Ahora bien, habrá quienes en este punto aún no tengan claro el porqué de mi exaltación por el uso de Silverlight para renderear el diagrama de Visio, es decir, si, todos ya sabíamos que Silverlight es usando ampliamente en varios aspectos de SharePoint 2010 porque esto brinda, sin lugar a dudas, una experiencia de usuario mejorada. Pero… volviendo a Visio Services… y qué más da que se use Silverlight para dibujar el diagrama en el navegador?

Pues el hecho relevante es, como todos sabemos, que Silverlight expone una API en JavaScript para la manipulación y creación de objetos desde el lado del cliente (JavaScript), puede sonar redundante puesto que Silverlight de por sí se ejecuta del lado del cliente (navegador). Ahora sí, las piezas comienzan a encajar en este rompecabezas.

La razón de este post es demostrar cómo podemos, mediante el uso de la API de Silverlight en JavaScript, agregar objetos al diagrama, una vez que ha sido dibujado en el navegador. Este es el punto medular, ya que con un poco de imaginación y algunas líneas de código (en su mayoría JavaScript) podemos crear diagramas muy atractivos al usuario final.

Dado que ha sido demasiado preámbulo, nada mejor que algunas líneas de código que ejemplifiquen mucho mejor de que estamos hablando.

Agregando objetos Silverlight(xaml) al diagrama

Tomando como base el diagrama del post anterior, vamos a modificar un poco el código que utilizamos para contar los shapes que existen dentro del diagrama para agregar los objetos Silverlight.

Sys.Application.add_load(onApplicationLoad)

//declaramos algunas variables globales que ocuparemos
var vwaControl;
var vwaPage;
var vwaShapes;
var vwaShapeCount;


function onApplicationLoad() {
    //obtenemos la instancia de la Visio Web Access Web Part
    getVwaControl();

    //Nos subscribimos a dos de los eventos que son expuesto por la API
    if (vwaControl != null) {
        vwaControl.addHandler("diagramcomplete", onDiagramComplete);
        vwaControl.addHandler("shapeselectionchanged", onShapeSelectionChanged);           
    }
    else {
        alert("Error connecting to Visio Web Part.  Please contact the site administrator.");
    }
}

function onDiagramComplete() {
 vwaPage = vwaControl.getActivePage();
    //obtenemos la pagina activa con el metodo getActivePage()
    vwaPage.setZoom(80);
 
 //buscamos el contenedor del diagrama, casi siempre un objeto 
 var visioSilverlightID = findVwaRenderingObject();
    var visioSLObject = document.getElementById(visioSilverlightID);
    var canvaselement = visioSLObject.content.findName("Workspace");
 
 try
 {
 //agregamos un objeto 
 var xaml = "";
 //agregamos un objeto 
 xaml+="";
 //agregamos un objeto 
 xaml+=" " ;
 //agregamos un objeto 
 xaml+="";
 //agregamos un objeto 
 xaml+="";
 xaml+="";

 xaml+="";
    var newControls  = visioSLObject.content.CreateFromXaml(xaml,true);
    canvaselement.Children.Add(newControls);
 }
 catch(ex)
 {
  alert(ex.toString());
  throw ex;
 }
}

function onShapeSelectionChanged(source, args){

}
En realidad el código es muy simple, sin mebargo es importante hacer mención aparte de la función que se encarga de crear los controles Silverlight a partir del código XAML, la función createFromXaml, acto seguido solo basta agregarlos al contenedor del diagrama (un objeto Canvas).

Es importante tambien mencionar que no todos los controles Silverlight se pueden crear a partir de esta API (aunque si se pueden manipular, si ya existen). Aquí puedes checar que objetos se pueden crear y cuales no, a través de la API de Silverlight en JavaScript.

Por último algunas referencias y el código js

JavaScript API for Silverlight
Objetos Silverlight expuesto por la API en JavaScript
Visio Services API
Función createFromXaml



Happy Coding!

Etiquetas

SharePoint 2010 (38) Microsoft (32) Desarrollo SharePoint (31) Gerardo Reyes Ortiz (27) SharePoint (20) SharePoint 2013 (18) Errores SharePoint (12) México (10) PowerShell (9) Silverlight (8) Visio Services (7) Features (6) MVP (6) Silverlight 3 (6) WebCast (6) Workflows (6) Configuracion SharePoint 2010 (5) D.F. (5) API REST (4) Configuracion SharePoint 2010; (4) Troubleshooting (4) Visual Studio 2010 (4) Visual studio (4) WSS (4) Web parts (4) Apps (3) Comunidad SharePoint (3) Configuración SharePoint 2013 (3) ODATA (3) SharePoint Server (3) SharePoint; Instalación SharePoint; Troubleshooting; Search Service (3) Silverlight 3.0 (3) Silverlight Toolkit (3) WebParts (3) javascript (3) jquery (3) Eventos SharePoint (2) Office 2010 (2) PeoplePicker (2) REST (2) SQL Server (2) Scripting (2) Search Service Application (2) SharePoint Designer (2) UPA (2) UPS (2) Workflows SharePoint (2) host header (2) Apps Development (1) Big Bang (1) CAS (1) CSOM (1) Codeplex (1) CompartiMOSS (1) Configuracion SharePoint 2010; Errores SharePoint (1) Configuracion SharePoint 2010; SharePoint 2010 (1) Custom Actions (1) Custom Editor Parts (1) Delegate Controls (1) Deployment (1) DisableLoopbackCheck (1) Document Library (1) Entrevista (1) Examenes de Certificación (1) Extract WSP (1) FBA (1) FS4SP (1) Fakes (1) Fast Search Server 2010 For SharePoint (1) Fiddler (1) HTTP.SYS (1) HTTPS (1) JSON (1) Language Pack's (1) Latam (1) MAXDOP (1) MCSM (1) MSExpertos (1) MVC (1) Microsoft México (1) Microsoft; Codeplex; Screencast; (1) My Sites (1) SQL Server 2012 (1) SQL Server Reporting Services (1) Screencast (1) Screencast; (1) Service Applications (1) Service Pack (1) SharePoint 2007 (1) SharePoint 2010 SP 1 (1) SharePoint API (1) SharePoint Conference (1) SharePoint Emulators (1) SharePoint Farm (1) SharePoint Health Analyzer (1) SharePoint Magazine (1) SharePoint Online (1) SharePoint Search (1) SharePoint Test (1) SharePoint; Desarrollo SharePoint (1) Shims (1) Simposio (1) Simposio Latinoamericano (1) SkyDrive Pro (1) Soporte Microsoft (1) Templates (1) Tip (1) VSeWSS (1) Virtual Machine (1) Visual Studio 2012 (1) WCF (1) WSS; IIS 7 (1) Web API (1) Web Content Management (1) Web Services (1) Windows 8 (1) Windows Live ID (1) Xml (1) appcmd (1) iOS (1) jqGrid (1) onload function (1)