viernes, 14 de diciembre de 2012

The HTTP header ACCEPT is missing or its value is invalid making rest call using JavaScript

Es bastante común que al intentar hacer una llamada utilizando la API REST para SharePoint 2013 utilizando JavaScript, como la siguiente:

function onGetCustomers(){
 var requestUri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getByTitle('Clientes')/items/?$select=Title,Nombre,Apellidos";
 
 //$.getjson() falla
 jqCall = $.getJSON(requestUri,null, onGetCustomersSuccess);
 jqCall.error(onError);
}

Nos encontremos con un error como este:


25d
{"error":{"code":"-1, Microsoft.SharePoint.Client.ClientServiceException","message":{"lang":"en-US","value":"The HTTP header ACCEPT is missing or its value is invalid."},"innererror":{"message":"The HTTP header ACCEPT is missing or its value is invalid.","type":"Microsoft.SharePoint.Client.ClientServiceException","stacktrace":"   at Microsoft.SharePoint.Client.Rest.RestRequestProcessor.Process()\r\n   at Microsoft.SharePoint.Client.Rest.RestRequestProcessor.ProcessRequest()\r\n   at Microsoft.SharePoint.Client.Rest.RestService.ProcessQuery(Stream inputStream, IList`1 pendingDisposableContainer)"}}}
0


Y esto se debe sencillamente a que si inspeccionamos a detalle los headers enviados en la petición  encontraremos que hace falta algo adicional:

Headers enviados como parte del request:


Al hacer este tipo de llamadas debemos indicar, junto con el header Accept=application/json, el header: odata=verbose, es decir quedaría de esta forma:   Accept=application/json; odata=verbose. Esto debido a que por el momento solo se soporta el formato verbose de JSON, el formato "light" no es soportado.

Luego entonces cambiamos la llamada para incluir este header, por el siguiente código:

function onGetCustomers(){
 var requestUri = _spPageContextInfo.webAbsoluteUrl + "/_api/web/lists/getByTitle('Clientes')/items/?$select=Title,Nombre,Apellidos";

 var requestHeaders={
 "ACCEPT":"application/json;odata=verbose",
 }

 $.ajax({
 url: requestUri,
 type:"GET",
 contentType:"application/json",
 headers: requestHeaders,
 success:onGetCustomersSuccess,
 error:onError
 });
}

Después de realizar esto, la respuesta sera satisfactoria

Algunas referencias:
Important developer changes from Office and SharePoint 2013 Preview to Office and SharePoint 2013
OData Verbose JSON Format

Happy Coding!

sábado, 8 de diciembre de 2012

Add Sign In as Different User in SharePoint 2013 with custom action

Si ya han estado utilizando SharePoint 2013, por lo menos en ambientes de pruebas, se habrán dado cuenta que en el menú de acciones personales la opción "Sign as Different User" simplemente ya no esta, todos pensábamos que era solo una omisión de la versión beta, pero ahora que se han liberado la versión definitiva, vemos con asombro que sigue sin aparecer.

Nick Grattan muestra en su ejemplo de como agregar esta opcion al menu, sin embargo es necesario mencionar que no es una buena practica (sin tomar en cuenta que no es soportada por Microsoft) modificar los archivos que están en el folder 15, ademas de que en un ambiente multiservidor se deberían modificar al menos todos los WFE's.

Una mejor solución, desde mi particular punto de vista, es crear un custom action que agregue la opción al menú mediante una feature que se despliegue en la granja de SharePoint.

Nuestro Objetivo
Para lo cual, crearemos una solución en blanco de SharePoint 2013, agregando un elemento de tipo "Empty Element"


Y agregamos el siguiente código al archivo Elements.xml que se ha creado

  
            
  


Luego cambiamos el scope de la feature que se creo al agregar el archivo Elements, el scope que debemos establecer es WebApplication para que podamos aprovechar ese elemento en toda la aplicación web


Por último hacemos el deploy de nuestro custom action y ya podremos ver el nuevo elemento del menú que nos permite firmarnos con diferentes usuarios


Para concluir les comparto el código.  


Happy Coding

viernes, 7 de diciembre de 2012

Webcast - CSOM y la API REST para SharePoint 2013

Como parte de la continuación del exitoso maratón de SharePoint que se llevo a cabo el día 29 de Noviembre  y que desafortunadamente tuvo que suspenderse por motivos técnicos.

Quiero invitarlos al webcast que estaré impartiendo el día martes 11 de Diciembre de este 2012.
El horario es 02:00 – 03:00 (GMT +1), es decir (7:00 pm a 8:00 pm hora del centro de México UTC-6:00)

Ademas comparto la lista completa de webcast que se estaran impartiendo.


Espero me puedan acompañar


Entrar al evento


Happy Coding!

viernes, 30 de noviembre de 2012

SharePoint Conference 2012

Pues bien, ha sido un evento fabuloso, bastante bien organizado.

La calidad de profesionales que tuve oportunidad de conocer y las opiniones que compartimos fueron realmente valiosas

Las vegas es una ciudad asombrosa con muchos lugares a donde ir, aunque muy cara. De noche siempre hay algo que hacer

Sin duda un evento que quedara para la historia

Algunas fotos.









domingo, 4 de noviembre de 2012

SharePoint 2010 - New instances of this workflow template are currently disallowed

¿Les ha pasado que alguna vez quieren iniciar una instancia de un flujo de trabajo y aparece este mensaje de error?

Bueno púes a mí me había pasado muchas veces, solo que nunca al utilizar código para iniciar automáticamente un flujo de trabajo, es decir, siempre que había obtenido este mensaje de error había sido utilizando la interfaz de usuario, para lo cual existen muchas referencias en la red para solucionarlo.

SharePoint 2007 - New instances of this workflow template are currently disallowed
Error: New instances of this workflow template are currently disallowed.

¿Pero y cómo podemos resolverlo cuando hablamos de código?

Al igual que cuando lidiamos con este error al intentar inicializar in flujo de trabajo utilizando la interfaz de usuario, este error se debe a que existe más de una versión del mismo flujo relacionada con la lista o cualquier otro elemento al que se pueda asociar un workflow como librerías de documentos, sitios, etc., en mi caso específico con un tipo de contenido.

Para ejemplificar este escenario, muestro a continuación mi código de powershell que tenía este error, y el cual simplemente intentaba iniciar programáticamente todas las instancias de una lista de SharePoint con el estatus de “Cancelled”:

cls
#Add-PSSnapin microsoft.sharepoint.powershell
$siteUrl="http://sp2010/mysite"
$site = Get-SPSite -Identity $siteUrl

foreach($web in $site.AllWebs)
{
    $list=$web.Lists["List"]
    $manager=$site.WorkFlowManager
   
    foreach($ct in $list.ContentTypes)
    {
        if($ct.Name -eq "MyContentType")
        {
            $swapWorkflowAssociation = $Ct.WorkflowAssociations[0]
        }
    }
    
    #Si la referencia al objeto WorkflowAssociation es null, no intentamos iniciar el WF
    if($swapWorkflowAssociation -eq $null) { continue } 
   
    foreach($listItem in $list.Items)
    {
        foreach($workflowInstance in $listItem.Workflows)
        {
        
            if($workflowInstance.InternalState -eq [Microsoft.SharePoint.Workflow.SPWorkflowState]::Cancelled)
            {
               $data=$workflowAssociation.AssociationData
               try
               {
                    $result = $manager.StartWorkflow($listItem, $workflowAssociation, $data, $True)
               }
               catch
               {
                    #Write-Host $_.Exception.ToString()
               }
               
               $cadena =   "Se inicia el WF|" + $web.Url + "|" + $listItem.ID.ToString() + "|" + $listItem.Title + "|" + $workflowInstance.InternalState
               
               Write-Host $cadena 
            }  
        }
    } 
}


Como comente antes, en la línea marcada obtenía este mensaje de error. El error estaba en que debido al proceso de prueba y error desarrollo iterativo, el tipo de contenido tenía más de una versión del flujo de trabajo asociado, por lo que al intentar recuperar una referencia al objeto WorkflowAssociation, no se recuperaba precisamente la más “reciente” o actual. El truco está en que las versiones viejas del flujo de trabajo, además de ser establecidas con el atributo “No New Instances”, siempre son renombradas de la forma: “NombreDeMiWorkflow (fecha)”, excepto para la Asociación más reciente (la correcta).

Por lo que, para evitar este error, se debe validar que la propiedad Name del objeto WorkflowAssociation, sea exactamente como la establecimos en un principio, sin fechas (o algún otro texto adicional).
$swapWorkflowAssociation = $Ct.WorkflowAssociations[0]

Para concluir, el código corregido

cls
Start-SPAssignment –Global
#Add-PSSnapin microsoft.sharepoint.powershell
$siteUrl="http://sp2010/mysite"

$site = Get-SPSite -Identity $siteUrl

foreach($web in $site.AllWebs)
{
    $list=$web.Lists["List"]
    $manager=$site.WorkFlowManager
   
    foreach($ct in $list.ContentTypes)
    {
        if($ct.Name -eq "MyContentType")
        {
            foreach($swapWorkflowAssociation in $Ct.WorkflowAssociations)
            {
                if($swapWorkflowAssociation.Name -eq "MyWorkflowName") 
                {
                    $workflowAssociation = $swapWorkflowAssociation
                }
                
            }
        }
    }
   
   #Si la referencia al objeto WorkflowAssociation es null, no intentamos iniciar el WF
   if($swapWorkflowAssociation -eq $null) { continue } 
   
    foreach($listItem in $list.Items)
    {
        foreach($workflowInstance in $listItem.Workflows)
        {
        
            if($workflowInstance.InternalState -eq [Microsoft.SharePoint.Workflow.SPWorkflowState]::Cancelled)
            {
               $data=$workflowAssociation.AssociationData
               try
               {
                    $result = $manager.StartWorkflow($listItem, $workflowAssociation, $data, $True)
               }
               catch
               {
                    #Write-Host $_.Exception.ToString()
               }
               
               $cadena =   "Se inicia el WF|" + $web.Url + "|" + $listItem.ID.ToString() + "|" + $listItem.Title + "|" + $workflowInstance.InternalState
               
               Write-Host $cadena 
            }  
        }
    } 
}

Happy Coding!

viernes, 12 de octubre de 2012

Extraer archivos WSP instalados en una granja SharePoint

Hace unos días mientras me encontraba en medio de una migracion de SharePoint 2007 a SharePoint 2010, me vi en la necesidad de descargar un par de soluciones personalizadas (archivos WSP) de la granja de SharePoint 2007 ya que no había código fuente de esas soluciones y se tenían que hacer ajustes para que funcionaran de manera adecuada.

Después de buscar un par de minutos me encontré con esta utilidad que pueden descargar desde MSDN, me refiero al SharePoint Farm Solution Extractor, funciona bastante bien y es muy fácil de usar.

Ademas también encontré en este foro de Microsoft, que otra forma practica de hacerlo es utilizando PowerShell, sin embargo para mi no era muy practico ya que el servidor no tenia instalado PowerShell :(

[System.Reflection.Assembly]::LoadWithPartialName("Microsoft.SharePoint")
$farm = [Microsoft.SharePoint.Administration.SPFarm]::Local
$farm.Solutions | % { 
  $filename = ($pwd.ToString() + "\" + $_.SolutionFile.Name); 
  write-host ("Saving" + $filename); 
  $_.SolutionFile.SaveAs($filename) 
}


Por ultimo, también les comparto un ejemplo de como guardar todos los archivos WSP's de una granja SharePoint 2010 en un directorio que indiquen utilizando PowerShell

cls
#path en donde se guardaran los archivos WSP's, este path debe existir antes de ejecutar el script
$path="c:\wsps\"
$farm = Get-SPFarm
foreach($item in $farm.Solutions)
{
 $file = $item.SolutionFile
 Write-Host "Guardando el archivo" $file.Name
 $file.SaveAs($path+$file.Name)
}


Happy Coding!

jueves, 6 de septiembre de 2012

SharePoint 2013 - SQL Server instance does not have the required "max degree of parallelism"

No sé ustedes pero yo personalmente aún ahora, probando el preview de SharePoint 2013, cruzo los dedos cada vez que el wizard de configuración se encuentra en el paso "Creating the configuration database…"


Precisamente el error del que hablaremos el día de hoy, ocurre al momento de estar creando la base de datos de configuración de nuestra flamante granja de SharePoint 2013:

Failed to create the configuration database.

This SQL Server instance does not have the required "max degree of parallelism" setting of 1. Database provisioning operations will continue to fail if "max degree of parallelism" is not set 1 or the current account does not have permissions to change the setting. See documentation for details on manually changing the setting.


Lo único rescatable de este escenario de error es que si te llega a ocurrir, es porque casi seguramente estas usando la filosofía de mínimos privilegios o least-privileges, es decir la cuenta con la que estas instalando SharePoint 2013 solo tiene los roles dbcreator y securityadmin de base de datos, lo cual es muy bueno pero insuficiente para modificar el setting “max degree of parallelism” por el cual ocurre el error. De hecho si lees bien el mensaje de error, si la cuenta que será usada como Farm Administrator tuviera permisos de modificar el setting, lo hubiera hecho.

Ahora bien, después de la sección de reflexiones, como se resuelve este inconveniente?

Primero le llamas al amigo DBA, o consigues una cuenta administradora de la base de datos. Para modificar el “max degree of parallelism” puedes ocupar cualquiera de las siguientes dos opciones:

Utilizando la UI
1. Abrir Microsoft SQL Server Management Studio
2. Conectarse a la instancia en la que se intenta crear la granja de SharePoint, con la cuenta administradora
3. Hacer click en Properties sobre el nombre de la instancia, aparecerá un cuadro de dialogo
4. Modificar a 1 el valor del elemento Max Degree of Parallelism


Utilizando el stored procedure sp_configure
Ejecuta las siguientes instrucciones en una ventana de query

sp_configure 'show advanced options', 1; 
GO
RECONFIGURE WITH OVERRIDE; 
GO
sp_configure 'max degree of parallelism', 1; 
GO
RECONFIGURE WITH OVERRIDE; 
GO




Después de ejecutar cualquiera de estas dos soluciones no olvides borrar la base de datos SharePoint_Config, que se creó en el intento fallido de creación de la granja. Por último vuelve a ejecutar el wizard para la configuración de la granja de SharePoint 2013

Y bueno, mientras ven como avanza el wizard, no estaría demás que leyeran las siguientes referencias:

Install and Configure SharePoint 2013 preview
Deployment guide for Microsoft SharePoint 2013 preview
General guidelines to use to configure the MAXDOP option
The ins and outs of MAXDOP

Happy Configuring!

domingo, 12 de agosto de 2012

Creando custom workflow activities para SharePoint 2010

Una de las mejores prácticas a la hora de crear flujos de trabajo (no solo para SharePoint) es encapsular funcionalidad común o reutilizable en actividades personalizadas, esto nos permitirá hacer flujos de trabajo mucho más mantenibles, disminuyendo así la cantidad de código que escribimos dentro del “code behind”, y es que al menos en mi muy humilde opinión, el código que escribimos debería ser mínimo y para casos muy específicos.

Además de todas las ventajas que implica el uso de actividades personalizadas, es bastante sencillo crearlas, a continuación explicaré el paso a paso de cómo crear una actividad que nos permita la creación de sub-sitios.

Pensemos en el siguiente escenario: El administrador de un sitio recibe muchas peticiones para generar sub sitios dentro del sitio que él administra, esta aburrido de hacer está tarea tan repetitiva, él lo que desea es salir temprano de la oficina y darle duro al PS3 Xbox 360, por lo que se le ocurre automatizar el proceso de solicitud de creación de sitios mediante una lista en la cual los solicitantes puedan ingresar la información fundamental para la creación del sitio y una justificación de porque lo necesitan, de esta manera el administrador solo recibe un mail de que tiene una tarea pendiente, entra a revisar la tarea y la aprueba (si es el caso) y automágicamente el sitio se provisiona, cool no!?. Pues entonces comencemos a  darle forma a los sueños de este administrador ficticio.

Primero creamos una solución de tipo de Sequential Workflow que nos servirá para probar la actividad personalizada que crearemos, después creamos un proyecto de tipo Workflow Activity Library que será en donde crearemos las actividades de workflow personalizadas.


Acto seguido agregamos una clase con el nombre que tendrá la actividad personalizada, en este caso CreateNewSubSite, y heredamos de la clase base Activity.


Creamos las propiedades que tendrán la información necesaria para crear el sub sitio: SiteName, SiteUrl, SiteLCID, TemplateName, SiteDescription, UseUniquePermissions y una propiedad para almacenar el contexto de ejecución del workflow.  Lo primero que debemos notar es que las propiedades que creamos son dependency properties (propiedades de dependencia), esto para permitir a cualquiera que use nuestra actividad poder enlazar (Binding) estas propiedades para que en tiempo de ejecución los valores se calculen. Si aún no sabes que es o para que se utilizan las property dependencies, lee esto.

public static readonly DependencyProperty SiteNameProperty = DependencyProperty.Register("SiteName", typeof(string), typeof(CreateNewSubSite));
public static readonly DependencyProperty SiteUrlProperty = DependencyProperty.Register("SiteUrl", typeof(string), typeof(CreateNewSubSite));
public static readonly DependencyProperty SiteLCIDProperty = DependencyProperty.Register("SiteLCID", typeof(uint), typeof(CreateNewSubSite), new PropertyMetadata((uint)1033));
public static readonly DependencyProperty TemplateNameProperty = DependencyProperty.Register("TemplateName", typeof(string), typeof(CreateNewSubSite), new PropertyMetadata("STS#1"));
public static readonly DependencyProperty SiteDescriptionProperty = DependencyProperty.Register("SiteDescription", typeof(string), typeof(CreateNewSubSite));
public static readonly DependencyProperty UseUniquePermissionsProperty = DependencyProperty.Register("UseUniquePermissions", typeof(bool), typeof(CreateNewSubSite));

public static DependencyProperty __ContextProperty = System.Workflow.ComponentModel.DependencyProperty.Register("__Context", typeof(Microsoft.SharePoint.WorkflowActions.WorkflowContext), typeof(CreateNewSubSite));

[Description("The name of the new subsite")]
public string SiteName
{
 get { return (string)GetValue(SiteNameProperty); }
 set { SetValue(SiteNameProperty, value); }
}

[Description("Relative Url of the new subsite. You should consider that any space will be codified, for example MySite=MySite, Financial Area=Financial%20Area")]
public string SiteUrl
{
 get { return (string)GetValue(SiteUrlProperty); }
 set { SetValue(SiteUrlProperty, value); }
}

[Description("Specify the LCID of the language that will be used to create the subsite. Example: English=1033, Spanish=3082. For more reference you can see http://technet.microsoft.com/en-us/library/ff463597.aspx")]
public uint SiteLCID
{
 get { return (uint)GetValue(SiteLCIDProperty); }
 set { SetValue(SiteLCIDProperty, value); }
}

[Description("Specify the template name that will be used to create the subsite. Example: GLOBAL#0 = Global template, STS#0 = Team Site, STS#1 = Blank Site.")]
public string TemplateName
{
 get { return (string)GetValue(TemplateNameProperty); }
 set { SetValue(TemplateNameProperty, value); }
}

[Description("Specify the description of the new subsite")]
public string SiteDescription
{
 get { return (string)GetValue(SiteDescriptionProperty); }
 set { SetValue(SiteDescriptionProperty, value); }
}

[Description("Specify if the site will have unique permissions")]
public bool UseUniquePermissions
{
 get { return (bool)GetValue(UseUniquePermissionsProperty); }
 set { SetValue(UseUniquePermissionsProperty, value); }
}

public WorkflowContext __Context
{
 get { return (WorkflowContext)GetValue(__ContextProperty); }
 set { SetValue(__ContextProperty, value); }
}

La parte medular para la creación de nuestra actividad personalizada es sobreescribir el método Execute, el cual contiene el código principal a ejecutar por la actividad personalizada. En este ejemplo tomamos los valores de las propiedades para crear el sub sitio, no sin antes ejecutar algunas validaciones.

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
 SPSecurity.RunWithElevatedPrivileges(() =>
  {
   using (SPSite site= new SPSite (__Context.Site.ID))
   {
    using (SPWeb web = site.AllWebs[__Context.Web.ID])
    {
     //before to add the new subsite, we need to validate some settings, remember! never trust in another dev
     //first at all, validate the LCID
     SPLanguage language = SPRegionalSettings.GlobalInstalledLanguages.Cast().FirstOrDefault(x => x.LCID == SiteLCID);
     if (language == null)
     {
      throw new ArgumentException(string.Format("The value '{0}' for SiteLCID is not supported, are you sure corresponding language pack is already installed?", this.SiteLCID));
     }
     
     //then validate the name of site template
     SPWebTemplate webTemplate = site.GetWebTemplates(this.SiteLCID).Cast().FirstOrDefault(x => x.Name == this.TemplateName);
     if (webTemplate == null)
     {
      throw new ArgumentException(string.Format("The specified TemplateName='{0}' is not supported for specified language='{1}'", this.TemplateName, language.DisplayName));
     }

     //then validate the SiteUrl is unique
     if (web.Webs.Names.Contains(SiteUrl))
     {
      throw new ArgumentException("The SiteUrl is not unique, there is another subsite with the same SiteUrl");
     }

     web.Webs.Add(SiteUrl, SiteName, SiteDescription, SiteLCID, webTemplate, UseUniquePermissions, false);
    }
   }
  }
 );

 return ActivityExecutionStatus.Closed;
}

Así de simple ya tenemos una actividad personalizada lista para ser utilizada. Para este ejemplo agregamos el proyecto del workflow (WorkfllowDemo) y el proyecto de las actividades personalizadas (CustomActivities) en la misma solución, por lo que al compilar la solución automáticamente la actividad que creamos aparece en el toolbox.

Aunque también se puede solo agregar la referencia a la dll que contiene las actividades utilizando la ventana de diálogos para agregar elementos al Toolbox, para poder utilizar las actividades sin necesidad del código fuente.
El paso siguiente es arrastrar la actividad CreateNewSubSite al diseñador de workflows y configurar sus propiedades con la información para crear el subsitio.

Como se aprecia en la imagen anterior, agregamos solo una actividad más para escribir en la lista de Workflow History que el sitio fue creado correctamente. Ahora hablemos un poco a cerca de la lista que utilizaremos para las solicitudes de nuevos sitios, la cual solo contiene dos columnas: SiteName y SiteUrl. Es a esta lista a la cual asociaremos el flujo de trabajo.

Configuramos el flujo para que se ejecute al crear un nuevo elemento en la lista y podremos ver como se crea el sitio automáticamente, además del mensaje en el historial del flujo de trabajo





Les comparto el código y si tienen algún comentario o pregunta no duden en hacerla

Happy coding!

viernes, 3 de agosto de 2012

Accediendo al contexto de SharePoint desde un flujo de trabajo

Escribo este post ya que he notado que muchos desarrolladores al crear flujos de trabajo utilizando Visual Studio 2010 para SharePoint 2010, incluso para Project Server 2010, se quejan que durante la ejecución del flujo de trabajo no tienen acceso al "contexto", es decir no pueden utilizar la instancia de la clase SPContext accediéndola de la manera común Microsoft.SharePoint.SPContext.Current.

Y si bien es cierto que durante la ejecución del flujo de trabajo si intentamos acceder al contexto de SharePoint de esta manera siempre obtendremos null, la solución es crear durante la inicialización del flujo de trabajo una instancia del objeto Microsoft.SharePoint.WorkflowActions.WorkflowContext,
la cual nos permitirá acceder a gran parte de la información del contexto de ejecución de SharePoint desde el flujo de trabajo.

Para inicializar una instancia de la clase WorkflowContext, seguimos los siguientes dos sencillos pasos:

Declaramos una propiedad del tipo WorkflowContext y posteriormente la instanciamos utilizando el constructor sin parámetros que existe.

public Microsoft.SharePoint.WorkflowActions.WorkflowContext WFContext { get; set; }

public Workflow1()
{
    WFContext = new Microsoft.SharePoint.WorkflowActions.WorkflowContext();
    InitializeComponent();
}
Después inicializamos la propiedad en el evento Invoked de la actividad OnWorkflowActivated que se agrega automáticamente cuando creamos el flujo de trabajo

private void onWorkflowActivated1_Invoked(object sender, ExternalDataEventArgs e)
{
    WFContext.Initialize(workflowProperties);
}


El código completo se vería asi:

using System;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Collections;
using System.Drawing;
using System.Linq;
using System.Workflow.ComponentModel.Compiler;
using System.Workflow.ComponentModel.Serialization;
using System.Workflow.ComponentModel;
using System.Workflow.ComponentModel.Design;
using System.Workflow.Runtime;
using System.Workflow.Activities;
using System.Workflow.Activities.Rules;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Workflow;
using Microsoft.SharePoint.WorkflowActions;

namespace WorkflowDemo.Workflow1
{
    public sealed partial class Workflow1 : SequentialWorkflowActivity
    {
        public Guid workflowId = default(System.Guid);
        public SPWorkflowActivationProperties workflowProperties = new SPWorkflowActivationProperties();

        public Microsoft.SharePoint.WorkflowActions.WorkflowContext WFContext { get; set; }

        public Workflow1()
        {
            WFContext = new Microsoft.SharePoint.WorkflowActions.WorkflowContext();
            InitializeComponent();
        }

        private void onWorkflowActivated1_Invoked(object sender, ExternalDataEventArgs e)
        {
            WFContext.Initialize(workflowProperties);
        }
    }
}

Y eso es todo, ahora podemos acceder al contexto de SharePoint utilizando esta propiedad.
Happy Coding!

martes, 10 de julio de 2012

Finaliza el soporte para SharePoint 2010

El día de hoy finaliza el soporte para SharePoint 2010 en cualquier versión que este entre RTM y el service pack 1, que quiere decir esto?

Que deben actualizar sus granjas de SharePoint 2010 al SP 1. Si su granja de SharePoint 2010 no tiene al menos el SP 1, no tendrán soporte o será solo soporte limitado

Así que no esperen mas y actualicen las su granjas de SharePoint 2010 que aun no cuenten con al menos el Service Pack 1

Lista de todos los paquetes SP1 de SharePoint 2010 y Office Server 2010
Service Pack 1 for SharePoint 2010 Products is Now Available for Download


sábado, 2 de junio de 2012

Sexto Simposio LatinoAmericano de SharePoint

Como cada año se llevó a cabo el sexto simposio latinoamericano de SharePoint, en el cual tuve el privilegio de participar en la presentación "El poder del Business Intelligence con Visio 2010 y SharePoint 2010" en conjunto con Elizabeth Contreras (Project Manager de Project y Visio en Microsoft México) y Antonio Razo (Consultor Sr de BI), a continuación les comparto la ppt que presente.

Cualquier comentario es bienvenido



Happy Coding!

martes, 1 de mayo de 2012

Sexto Simposio Latinoamericano de SharePoint en México

Como ya se está volviendo tradición, este año se llevara a cabo de nueva cuenta el Simposio Latinoamericano de SharePoint en México, siendo este el primer año en que se estará transmitiendo a través de Live Meeting.

El evento presencial será de nueva cuenta en las oficinas de Microsoft México

Aquí la invitación oficial:


No olviden realizar su registro y allá nos vemos :)

domingo, 22 de abril de 2012

[SharePoint Troubleshooting] - Access Denied PRTH_E_ACCESS_DENIED en servcio de búsqueda

En los pasados días me encontré con el siguiente escenario en la granja de SharePoint 2010 de uno de mis clientes. Se tiene el servicio de búsqueda creado y configurado adecuadamente, de hecho ya tiene unos meses funcionando con normalidad y la búsqueda funciona en todos los sitios de la compañía. De pronto un día notan que en un Web Application en particular la búsqueda no está mostrando resultados.

De manera sistemática, algunas de los primeros puntos a revisar en este escenario fueron:
  • Privilegios de la cuenta de acceso al contenido
  • Que el host header del web application que no devuelve resultados este agregado dentro de las direcciones a rastrear dentro de alguno de los orígenes de contenido
  • Que el servicio de búsqueda esté funcionando y/o verificar cuando fue la última vez que se realizo un rastreo

Después de revisar los puntos anteriores, nos damos cuenta que todo parece ir bien con el servicio de búsqueda, solo vemos un par de errores en el log de rastreo de uno de los orígenes de contenido, y que precisamente corresponde al sitio en que no se muestran resultados de las búsquedas, pero no hay mayor información de cuál es el origen de estos errores.
Después de mucho buscar en los logs de SharePoint, encontramos los siguientes mensajes.
CHttpAccessorHelper::InitRequestInternal - Access Denied PRTH_E_ACCESS_DENIED on request for "http://extranet.xxxxx.xxx" hr = 0x80041205 [httpacchelper.cxx:268] d:\office\source\search\native\gather\protocols\http\httpacchelper.cxx
CHttpProbeHelper::ProbeServer:InitiRequest failed for "http://extranet.xxxxxx.xxx" Return error to caller, hr=80041205 [stscommon.cxx:439] d:\office\source\search\native\gather\protocols\common\stscommon.cxx

Lo que nos da una pequeña pista, se trata de permisos de la cuenta de acceso. Intentamos acceder al sitio utilizando esta cuenta desde el servidor que tiene el servicio de búsqueda,  después de ingresar las credenciales, nos aparece la pantalla de login de manera repetida (tres veces) hasta que nos presenta un error de autentificación. Estonces llegamos a la raíz del error, no es un problema de permisos de la cuenta sino un tema de configuración, ya que es un comportamiento que he visto bastante y que se refiere a la configuración de la característica de seguridad DisableLoopbackCheck, de la cual ya he hablado anteriormente.

Todo se soluciona, siguiendo el KB 896861 y aplicando las configuraciones que correspondan en los servidores SharePoint, Si no sabes si deshabilitar la caracteristica DisableLoopbackCheck o agregar una entrada en BackConnectionHostNames, te recomiendo leer mi post en donde hablo de cuando aplicar una u otra solución
Happy configuring!

domingo, 15 de abril de 2012

Warning - SPTraceV4 utiliza una cuenta integrada para su ejecución

Siempre es importante revisar las alertas que emite en servicio de SharePoint Health Analyzer, sobre todo cuando tienen el nivel de severidad 1 – Error, además de que no hay pretextos ya que es bastante llamativa la forma en que SharePoint nos avisa que debemos revisar algún issue detectado por la ejecución de este servicio.

Bueno, pues en un día normal de un SharePoint Farm Administrator me encontre con el siguiente warning, es decir tenía el nivel de advertencia y no de error, aún así se deben tomar medidas para revisarlo y resolverlo.


Para la mayoría de los servicios de SharePoint puedes solo navegar a la página de Administración de cuentas de servicio de la granja para modificar la cuenta con la cual se ejecuta, sin embargo no es posible hacerlo para este servicio debido a que no aparece listado ahí. Luego entonces lo más lógico y sencillo es hacerlo desde la Consola de Administración de Servicios de Windows, desde donde podemos ver que efectivamente el servicio está utilizando una cuenta integrada (en este caso Local Service) para su ejecución, lo recomendado es que sea una cuenta de dominio.


Lo primero que se podría pensar es en cambiarlo directamente ahí… desafortunadamente esto no soluciona el problema, en primera instancia porque habría que hacerlo para todos los servidores de la granja. Y una vez que hayamos aplicado este cambio en todos los servidores de la granja intentaríamos ejecutar de nuevo el análisis de la regla, solo para darnos cuenta que no sirve de nada.
 

La explicación es que el servicio de Health Analyzer checa el valor en la base de datos de configuración de SharePoint y no directamente del valor del servicio. Entonces lo que se debe hacer es ejecutar el siguiente script, como se indica en este thread del foro de SharePoint de Microsoft.

# Get the tracing service.
$farm = Get-SPFarm
$tracingService = $farm.Services | where {$_.Name -eq "SPTraceV4"}  
 
# Get the "farm" managed account.
$managedAccount = Get-SPManagedAccount "DOMAIN\spfarm"
 
# Set the tracing service to run under the managed account.
$tracingService.ProcessIdentity.CurrentIdentityType = "SpecificUser"
$tracingService.ProcessIdentity.ManagedAccount = $managedAccount
$tracingService.ProcessIdentity.Update()
 
# This actually changes the "Run As" account of the Windows service.
$tracingService.ProcessIdentity.Deploy()

Por último recordarles que la cuenta de dominio que utilicen para el servicio SPTraceV4 debe reunir los siguientes requisitos:

  1. Tener permisos de lectura y escritura sobre la carpeta en que se guardaran los logs, que por default es C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\LOGS
  2. La cuenta debe estar dentro de los grupos locales Performance Monitor Useres y Performance Log Users

Happy Configuring!

miércoles, 4 de abril de 2012

MVP SharePoint Server 2012


Pues nada, que he recibido ÉL famoso e-mail:

“Estimado Gerardo Reyes,

Enhorabuena. Nos complace presentarle el programa de nombramiento MVP de Microsoft® de 2012. Este nombramiento se concede a los líderes excepcionales de la comunidad técnica que comparten de forma activa su experiencia de alta calidad y de la vida real con otras personas. Le agradecemos especialmente la contribución que ha realizado en las comunidades técnicas en el área de SharePoint Server a lo largo del pasado año.”

Que me acredita como MVP en SharePoint Server para este 2012, y que sin duda me motiva para seguir capacitándome y compartiendo mis experiencias del día a día con las comunidades técnicas.

Gracias a toda la gente que ha influido de una u otra forma en mi crecimiento como consultor y como persona. Especiales agradecimientos y #FF a:

@SrBichi
@mmonterroca
@haarongonzalez
@HardBit
@Virtualman
@jluisestrada
@jrwarrior
@ferglo

Happy Coding!

lunes, 27 de febrero de 2012

Asociar flujo de trabajo a una lista de SharePoint con PowerShell

Consideren el siguiente escenario:

Han desarrollado un flujo de trabajo con Visual Studio. El flujo de trabajo será utilizado en múltiples sitios dentro de una estructura de sitios específica. Cada sitio de esta estructura contiene una lista a la cual el flujo debe quedar asociado.  Son más de trescientos sitios. Muy posiblemente la solución ideal hubiera sido crear un tipo de contenido, agregar el tipo de contenido a la lista y generar una plantilla para crear multiples instancias de ese template en cada uno de los sitios, después construir el workflow contra ese tipo de contenido (workflow reutilizable), desafortunadamente este no es el caso.

Una vez que planteamos el problema a resolver, tal vez lo primero que se les ocurra es invitar a algunos amigos para apoyarles a cambio de unas pizzas, pero créanme, esas reuniones siempre terminan en algún bar.

Una buena solución sería escribir un pequeño script de PowerShell con el que esto se pudiera llevar a cabo de manera automática, de tal manera que el flujo que desarrollaron se asocie con cada una de las listas que se desea, sin tener que hacerlo de forma manual.

A continuación les comparto el código de un script que hace exactamente eso. Recorre los sitios de la estructura y busca la lista a la cual se le asociará el flujo de trabajo. Como siempre recordándoles que el código es proporcionado *as is* y que ustedes deben modificarlo para utilizarlo en sus ambientes.

cls
#variables globales
Start-SPAssignment –Global
#Id del template del flujo de trabajo
$workflowTemplateId = new-object -TypeName System.Guid -ArgumentList "b55fa91f-2fdd-47b8-a80b-c58c32c1b2b6";
$site = Get-SPSite -Identity http://sp2010:123/sites/MiSitio

#Elimina cualquier asociación del mismo Workflow que pudiera existir en la lista
function DeleteAllWorkflowAssociations($list)
{    
    $oldAssociations = New-Object "System.Collections.Generic.List``1[System.Guid]"
    #buscamos las asociaciones viejas del flujo de trabajo, si existen
    foreach($workflowAssociationItem in $list.WorkflowAssociations)
    {
       if($workflowAssociationItem.BaseTemplate.Id.Equals($workflowTemplateId))
       {
   $oldAssociations.Add($workflowAssociationItem.Id)
       }
    }
    
    if($oldAssociations.Count -gt 0)
    {
        foreach($oldAssociationId in $oldAssociations)
        {
            Write-Host "Asociacion "  $oldAssociationId  " eliminada."
            $list.WorkflowAssociations.Remove($oldAssociationId)
        }
    }
}

#recorremos todos los sitios para buscar las listas en las que se asociará el workflow
foreach($web in $site.AllWebs)
{
 #En este caso mi sitio esta en español, por lo que las listas de "Tasks" y "Workflow History" se llaman asi "Tareas" e "Historial del flujo de trabajo" respectivamente
    $taskList = $web.Lists["Tareas"] #Lista de tareas que utilizara el workflow
    $historyList=$web.Lists["Historial del flujo de trabajo"] #Workflow History

    foreach($list in $web.Lists)
    {
  #En este caso yo busco las listas que tienen "Docs", esa es la forma de identificar las listas a las que se asociará el workflow, para mi caso en particular
        if($list.Title.Contains("Docs "))
        {
   #primero borramos cualquier posible asociacion anterior
            DeleteAllWorkflowAssociations($list)
            
            $workflowTemplate = $web.WorkflowTemplates[$workflowTemplateId]
            $associationName = "WF " + $list.Title + " - v2" #Este es el nombre que tendrá la instancia del workflow en la lista
            $listAssociation = [Microsoft.SharePoint.Workflow.SPWorkflowAssociation]::CreateListAssociation($workflowTemplate, $associationName, $taskList, $historyList)
            $listAssociation.AllowManual=$true #Le permite al usuario iniciar el flujo
            $listAssociation = $list.WorkflowAssociations.Add($listAssociation)
            Write-Host "Se creo satisfactoriamente la asociacion "  $associationName 
            Write-Host
        }
    }
}

Stop-SPAssignment –Global


Happy scripting!

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)