sábado, 6 de junio de 2015

Problemas al enviar un mail en un workflow de Sharepoint 2013 (u Sharepoint Online)

Los workflows quedaban en un estado suspendido cuando ejecutaba una acción de envió de mail en un workflow.

image

El mensaje de error que lanzaba era: Se está reintentando la última solicitud. Próximo intento programado después de: 03/06/2015 14:04. Detalles de la última solicitud: HTTP Unauthorized a https://tenantName.sharepoint.com/sites/urlSiteCollection/_api/sp.utilities.utility.SendEmail, Id. de correlación: 610bf69b-f54b-8e29-a894-96a47cd758cb, Id. de instancia: 87ae92b3-26ad-4a93-be71-83e0e76b76ca
Acceso denegado. No tiene el permiso necesario para realizar esta acción o tener acceso a este recurso.

El problema era que se estaba enviando a un grupo, el cual tenía seteada la visualización del grupo de forma limitada (“Integrantes del Grupo”). La solución fue cambiar la configuración para que todos los usuarios puedan ver los integrantes del grupo

image

domingo, 3 de mayo de 2015

Application Insights para Sharepoint

Microsoft liberó hace unas semanas el producto “Application Insights” que permite agregar el tracking o ver la performance de sitios webs o apps.

Voy a mostrar un ejemplo de cómo integrarlo con Sharepoint (en este caso Sharepoint Online, pero se puede hacer lo mismo para Sharepoint on-premise mientras el cliente tenga acceso a internet)

Ingreso al portal nuevo de Azure (https://portal.azure.com) y creo un servicio de “Application Insights”. Seleccionar en el tipo de aplicación “Aplicación web ASP.NET”

image

image

image

Una vez creado el servicio hago click en el icono de inicio rápido.

image

Después selecciono “Obtener código para supervisar mis páginas webs”

image

Copio el código, y voy a una página de Sharepoint, agrego un web part de código (Script Editor).

image

Y pego el código

image

Después de un tiempo de navegar la página, vemos que ya tenemos resultados

image

image

image

image

Podemos agregar custom eventos, por ejemplo: Qué usuario estaba logueado

appInsights.trackEvent("UsuarioLogueado:christianazcon");

image

En la página lo veremos así

image

Para tomar el usuario loguedo en Sharepoint podemos usar la siguiente variable de javascript “_spPageContextInfo.userLoginName”. Esta variable no siempre está disponible, ya que depende del tipo de página y feature que se tiene habilitada. Por ello también podés usar REST o JSOM.

image

Al revisar en Azure cómo quedo el evento vemos esto.

image

Cómo podemos hacer para que se deploye en cada página de Sharepoint? Tenés dos opciones: agregar el código en la master page, o utilizar inject JavaScript (javascript embed). El último es más recomendado, ya que es más mantenible.

En otro post mostraré cómo podemos usar esta funcionalidad en nuestros servidores on-premise descargando el cliente para Windows Server

image

Más información:

http://azure.microsoft.com/en-us/documentation/articles/app-insights-web-track-usage/

Azure Services Bus (Events Hubs) con Sharepoint

Event Hubs es un servicio de Azure que permite agregar y procesar millones de eventos por segundo. Es muy útil para almacenar datos en tiempo real o de forma batch.

image

Hace un tiempo se liberó el sdk para trabajar vía Javascript (+ REST) con Event Hubs. https://github.com/ddobric/ServiceBusJavaScriptSdk

En este post lo usaremos para hacer un custom analytics para sitios de Sharepoint o para evaluar en tiempo real quién se conecta al sitio. Hace algunas semanas Microsoft libero Azure Insights para hacer el tacking del uso en sitios (http://azure.microsoft.com/en-us/services/application-insights/). La idea es mostrar cómo se podría hacer con Azure Event Hubs, y tener tu propio Azure Insights.

image

image

image

el sdk requiere algunos parámetros

var queuePath = "sharepointanalytics";
var ehClient = new EventHubClient(
            {
                'name': queuePath,
                'devicename': 'urlsitio', // es la especificación para “partition key”
                'namespace': "sharepointanalytics-ns",
                'sasKey': "gIAsGZEd3kObjQN9gvnx7wRjm3Hf97aBmFyXWbszBtQ",
                'sasKeyName': "RootManageSharedAccessKey",
                'timeOut': 10,
            });

‘queuePath’ es el nombre del Event Hub

image

'devicename' se usa cómo key de partición, ver el siguiente link: https://msdn.microsoft.com/en-us/library/azure/dn836025.aspx

'namespace': es el nombre del service bus

image

'sasKey': es el nombre de la policy de acceso del service bus, 'sasKeyName': es la primary key de la policy de acceso.

image

image

Ej: Endpoint=sb://sharepointanalytics-ns.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=gIAsGZEd3kObjQN9gvnx7wRjm3Hf97aBmFyXWbszBtQ=

En el sdk, hay un archivo llamado EventHubSamples.html, lo edito y

image

Agrego el siguiente código html (lo importante en amarillo)

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
          "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>  
    <title>Service Bus JavaScrcipt SDK QueueClient Test Page</title>

   <link rel="stylesheet" href="css/main.css" type="text/css" />
    <script type="text/javascript" src="http://code.jquery.com/jquery-latest.js"> </script>
    <script src="Scripts/servicebusjssdk-1.2.min.js"></script>
    <script type="text/javascript">
        $(document).ready(function () {

           var queuePath = "sharepointanalytics";
            var ehClient = new EventHubClient(
            {
                'name': queuePath,
                'devicename': 'urlsitio', // This is by specification partition key.
                'namespace': "sharepointanalytics-ns",
                'sasKey': "gIAsGZEd3kObjQN9gvnx7wRjm3Hf97aBmFyXWbszBtQ=",
                'sasKeyName': "RootManageSharedAccessKey",
                'timeOut': 10,
            });

            $("#btnSend").click(function () {

                var eventBody = { "Horario": "22/02/2015 10:00 am", "Pagina": "Home.aspx", "sitio": "http://contoso.com", "usuario": "christianazcon"   };

                var msg = new EventData(eventBody);               

                ehClient.sendMessage(msg, function (messagingResult) {
                    $("#result").html(messagingResult.result);
                    $("#eventData").html(JSON.stringify(eventBody));
                });
            });

        });
    </script>
</head>
<body>
     <button id="btnSend">Send</button>
    <div class="message-wrapper">
                <p>Event Data:</p>
                <div id="eventData">
                </div><br />
                <p>Message Body:</p>
                <div id="result">
                </div><br />
      </div> <!-- END: .message-wrapper --></body>
</html>

En la página se ve así:

image

Presiono Send, me retorna “Sucess”

image

Ahora abro Service Bus explorer, https://code.msdn.microsoft.com/windowsazure/Service-Bus-Explorer-f2abca5a

Y veo que ya aparece el mensaje que envie

image

image

Ahora tendrás que revisar en que partición cayó el mensaje, ej: 5

image

Presiono Start

image

image

Cómo pueden ver, aparece el texto del mensaje.{"Horario":"22/02/2015 10:00 am","Pagina":"Home.aspx","sitio":"http://contoso.com","usuario":"christianazcon"}

Esto nos permite agregar un montón de eventos a las páginas mediante javascript y guardarlas en un Event Hub, y procesarlos a posteriori.

Por ejemplo para tomar el usuario logueado, puedo usar el siguiente script: https://lixuan0125.wordpress.com/2013/05/10/sharepoint-2013-get-current-user-loginname-using-jsom-rest/

Ej:

  • Eventos que realiza el usuario (donde hace click, cuanto tiempo está en cada página, etc)
  • Información de navegadores
  • etc

sábado, 11 de abril de 2015

CAML para filtrar las tareas pendientes que tengo asignadas o la de mis grupos a los cual pertenezco

Cuando trabajamos con Workflows, podemos asignar tareas a grupos determinados dentro del proceso de aprobación. EL principal problema que tenemos, es que desde la vista web sólo podemos filtrar las tareas asignadas a nosotros ([Yo] o [Me] en inglés). Pero cómo hacemos el filtrado de las tareas asignadas a grupos donde pertenecemos??

image

Se debe usar Sharepoint Designer para editar la consulta CAML

Abran la página donde tienen la vista, y editen con la vista avanzada, la sección de View

image

Busquen “<Query>” y reemplacen la query con lo siguiente.

<Query><Where><And><Or><Eq><FieldRef Name="AssignedTo" LookupId="TRUE"/><Value Type="Integer"><UserID /></Value></Eq><Membership Type="CurrentUserGroups"><FieldRef Name="AssignedTo"/></Membership></Or><Eq><FieldRef Name="Status" /><Value Type='Choice'>No iniciada</Value></Eq></And></Where><OrderBy><FieldRef Name="DueDate"/></OrderBy> </Query>

La query filtra todas las tareas asignadas a mi o a un grupo al cual pertenezco, y están en estado “No Iniciada”.

El principal problema que tiene hacerlo por Designer, primero que hacemos páginas custom, lo cual puede ser un problema en una migración. Y segundo, si voy a editar la vista desde la web no voy a ver estos cambios, y todos los cambios que requiera lo deberé hacer desde el designer (Ej: agregar campos a la view)

image

Local variable “ExpandGroups” Sharepoint

En el caso que estemos haciendo un custom workflow, y asignemos un grupo de Sharepoint o AD como aprobador, y quisieramos que para usuario de ese grupo se le asigne una tarea particular por usuario, en vez de una sola tarea por grupo, tendremos que usar la variable “ExpandGroups”. Seteandola a “Yes”, lo que hará Sharepoint es crear una tarea por cada usuario de un grupo, y hasta que no esté aprobada todas las tareas de los usuarios de ese grupo, no pasará al siguiente estado.

image

image

martes, 7 de abril de 2015

Agregar Javascript en una custom action–Sharepoint 2013

Con el Sharepoint Designer, ingreso a una lista, y en la ribbon selecciono “View Ribbon”

image

Le agrego un nombre

image

En el campo “Navegar a la dirección URL” (en inglés aparece como Navigate to URL) escribo lo siguiente javascript:alert("Item Seleccionado " + {SelectedItemId})

image

En la lista, me aparecerá lo siguiente.

image

Al hacer click me lanza el PopUp.

image

Ahora con el item 2

image

domingo, 5 de abril de 2015

Client Object Sharepoint Online - The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel

Les voy a comentar un problema que tuve configurando una consola que utiliza Client Object para conectarse a Sharepoint Online en un servidor Windows Server 2008 R2. La consola se ejecutaba en una tarea planificada de windows (Task Scheduler), el cual se ejecutaba con un usuario de servicio que tenía acceso a internet

En el app.config tenía configurado que utilice el proxy configurado por default. Tenía configurado en el IE el proxy, el cual no era necesario autenticarse, ya que usaba autenticación anónima.

<system.net>  
      <defaultProxy useDefaultCredentials="true">
        <proxy bypassonlocal="True" usesystemdefault="True"/>
      </defaultProxy> 
  </system.net>

Cuando ejecutaba la consola desde mi Windows, se ejecutaba sin problemas. Por lo cual excluí algún problema de autenticación o de acceso vía el proxy.

Lo que hice es poner la url del sitio de Sharepoint Online en el IE del Windows Server, y me generaba un error de certificado. Me decía “Untrusted Certificate”

image

Lo que hice es agregar la siguiente línea al inicio de la consola, antes de hacer cual llamada a Sharepoint Online.

ServicePointManager.ServerCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true; };

Lo que hace esta línea de código, es ignorar cual error de certificados de SSL.

Otra posibilidad es agregar la entidad que genera los certificados para Sharepoint Online en el store “Trusted Root Certification Authorities”: https://technet.microsoft.com/en-us/library/cc754841.aspx

image

sábado, 4 de abril de 2015

Sharepoint Change Log–Office 365

Microsoft SharePoint incluye un “change log” para hacer tracking de los cambios sobre listas, Web site, site collection, o content database (no soportado sobre Office 365). Por ejemplo, si un item es agregado a una lista, actualizado, renombrado, movido o borrado. Esta información es guardada en el change log junto con la hora que se realizó el cambio y un identificador único para el item.

El uso de change log de Sharepoint, no siempre es el más apropiado para algunos escenarios, ya que se puede usar otras soluciones, tales como event receivers o workflows.

Por default la información se almacena por 60 días. Pueden ver un overview en el siguiente link:

https://msdn.microsoft.com/en-us/library/office/bb417456(v=office.14).aspx (algunas cosas no están soportadas por Office 365.)

En Office 365 tenemos disponibles los siguientes métodos:

Cada método retorna una colleción de objetos, que son descendientes del objeto al cual se hizo referencia. Ej: si yo utilizo List.GetChanges, sólo retornará cambios realizados sobre la lista que se le pasó como parámetro, y NO cambios de otras listas.

Las propiedades del objeto ChangeQuery pueden dividirse en dos: change actions y objetos cambiados.

Change Actions (https://msdn.microsoft.com/en-us/library/office/bb447550(v=office.14).aspx#sectionSection2)  

  • Add    
  • DeleteObject    
  • GroupMembershipAdd    
  • GroupMembershipDelete    
  • Move    
  • Rename
  • Restore
  • RoleAssignmentAdd
  • RoleAssignmentDelete
  • RoleDefinitionAdd
  • RoleDefinitionDelete
  • RoleDefinitionUpdate
  • SystemUpdate
  • Update

Objetos cambiados (https://msdn.microsoft.com/en-us/library/office/bb447550(v=office.14).aspx#sectionSection1):

  • Alert
  • ContentType
  • Field
  • File
  • Folder
  • Group
  • Item
  • List
  • Navigation
  • SecurityPolicy
  • Site
  • User
  • View
  • Web

El método ChangeQuery tiene el siguiente encabezado

image

Si yo uso ChangeQuery siteCQ = new ChangeQuery(true, true);: voy a buscar todos los cambios de todos los objetos.

En cambio si uso ChangeQuery siteCQ = new ChangeQuery(false, false): puedo definir que cambios de acciones buscar y que objetos.

Ej: siteCQ.Item = true (sólo busco items cambiados)

Ej: siteCQ.DeleteObject = true; (busco acciones de borrado de objetos)

Veamos un ejemplo con Sharepoint Online, con Sharepoint On-premise es similar, sólo cambia la manera de hacer login y algunos eventos más (Ej: para content database o web applications)

image

Con las siguientes dos líneas, estoy indicando que voy a traer todos los cambios de todos los objetos, y lo haré desde el site collection para abajo.

ChangeQuery siteCQ = new ChangeQuery(true, true);
var siteChanges = site.GetChanges(siteCQ);
La siguiente línea, indica, que si el cambio es de tipo ChangeItem, lo proceso. Se puede hacer por cada tipo de cambio.

if (change is Microsoft.SharePoint.Client.ChangeItem)
{
}

El resultado es el siguiente:

image

También es posible limitar los cambios desde un inicio hasta un final, o desde la última vez que se ejecutó.

Ej:

// Construct a query.
SPChangeQuery query = new SPChangeQuery(false,  // Specify object types
                                        false   // Specify change types
                                        );
// Specify object types.
query.User = true;
query.Group = true;

// Specify change types.
query.Add = true;
query.Delete = true;
query.Update = true;
query.GroupMembershipAdd = true;
query.GroupMembershipDelete = true;
query.ChangeTokenStart = lastProcessedToken

En el caso que quisieramos limitar los resultados entre dos tokens de ejecución, puedo usar la propiedad ChangeTokenEnd
Para mantener el último token procesado, podemos ver el siguiente ejemplo, el cual usa un archivo .dat para almacenar el último token ejecutado.

https://msdn.microsoft.com/en-us/library/office/ff408387(v=office.14).aspx

Para mayor información:

https://msdn.microsoft.com/en-us/library/office/microsoft.sharepoint.client.changequery_members.aspx

https://msdn.microsoft.com/en-us/library/office/bb447550(v=office.14).aspx

Código Fuente:

using (ClientContext clientContext = new ClientContext(ConfigurationManager.AppSettings["SiteUrl"]))
            {
                SecureString passWord = new SecureString();
                foreach (char c in ConfigurationManager.AppSettings["Password"].ToCharArray()) passWord.AppendChar(c);
                clientContext.AuthenticationMode = ClientAuthenticationMode.Default;
                clientContext.Credentials = new SharePointOnlineCredentials(ConfigurationManager.AppSettings["Usuario"], passWord);

                var site = clientContext.Site;
                clientContext.Load(site);
                ChangeQuery siteCQ = new ChangeQuery(true, true);

                var siteChanges = site.GetChanges(siteCQ);
                clientContext.Load(siteChanges);
                clientContext.ExecuteQuery();

                foreach (Change change in siteChanges)
                {
                    //Console.WriteLine("{0}, {1}, {2}",change.Time, change.ChangeType, change.TypedObject);
                  
                    if (change is Microsoft.SharePoint.Client.ChangeItem)
                    {
                        ChangeItem ci = change as ChangeItem;   
                        Console.WriteLine("Sobre el Item {0} (lista {1}) se ejecutó la siguiente acción {2}", ci.ItemId.ToString(), ci.ListId.ToString(), ci.ChangeType);
                    }

                    if (change is Microsoft.SharePoint.Client.ChangeList)
                    {
                        ChangeList cl = change as ChangeList;
                        Console.WriteLine("Sobre la lista {0} se ejecutó la siguiente acción {1}", cl.ListId.ToString(), cl.ChangeType);
                    }
                }
            }