domingo, 3 de mayo de 2015

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);
                    }
                }
            }

viernes, 3 de abril de 2015

Office 365 – Cambiar imagen de inicio de login

Por default cuando ingreso a Office 365, veo la siguiente imagen

image

Para cambiarla, has lo siguiente.

  • En tu portal de administración de Office 365, ingresa a “Azure AD”

image

  • En el portal de azure, selecciona tu Active Directory relacionado al dominio de Office 365, y a continuación selecciona “Configurar”. Después selecciona “Personalizar la información de marca”

image

En el caso que ya hayas configurado anteriormente el dominio, te aparecerá la siguiente pantalla.

image

En caso contrario, directamente te mostrará la siguiente pantalla

image

A continuación carga toda la información de tu página de login de Office 365

Sign In Page

Para probar la página ingrese a (tarda 5 minutos en replicar la información) :

https://login.microsoftonline.com/?whr=dominioOffice365.com

Para mayor información:

https://msdn.microsoft.com/en-us/library/azure/dn532270.aspx

jueves, 2 de abril de 2015

Redis Cache para Sharepoint 2016 ???

En el siguiente post (http://blogs.msdn.com/b/appfabric/archive/2015/04/02/windows-server-appfabric-1-1-ends-support-4-2-2016.aspx) se menciona que finalizará el soporte de “Microsoft AppFabric 1.1 for Windows Server”.

“Microsoft recommends all applications currently using AppFabric 1.1 for Windows Server to migrate off this technology by April 2, 2016”

En el post recomiendan utilizar Azure Redis Cache en reemplazo de AppFabric.

Sharepoint 2016 vendrá configurado para usar Redis como cache? Muy pronto empezaremos a escuchar más información de la versión 16. La última versión de Redis para Windows es la 2.8.19 https://msopentech.com/blog/2015/03/03/redis-windows-2-8-19-released/

Les dejo un par de links que comparan AppFabric con Redis:

http://siliconangle.com/blog/2012/02/27/redis-on-windows-vs-microsofts-appfabric/

http://www.techwars.io/fight/redis/appfabric/

lunes, 2 de marzo de 2015

Installation Error: Could not find SOFTWARE\Microsoft\Microsoft SQL Server\110 registry key.

Al tratar de crear el Service Application de reporting Services, me lanzo el siguiente error:

Installation Error: Could not find SOFTWARE\Microsoft\Microsoft SQL Server\110 registry key.

image

Para solucionarlo, instale la feature “Reporting Services – Sharepoint”

image

sábado, 21 de febrero de 2015

Tips de Troubleshooting para People Picker de Sharepoint

Voy a mostrar algunos tips útiles para verificar el funcionamiento correcto del People Picker de Sharepoint.

Entorno (montado sobre Azure):

  • AD: Windows Server 2012
  • 1 Granja (1 WFE, 1 App Server, 1 SQL Server)
  • Sharepoint 2013 SP 1, CU de febrero 2015

El people picker de Sharepoint es el responsible de consultar el AD en búsqueda de usuarios (accounts) y grupos, se usa para dar permisos a los sitios de Sharepoint.

imageimage

En este link podrás ver todas las configuraciones posibles del People Picker.

En este link y en este podrás ver la secuencia que se realizar cuando haces un “check name” en el people picker. En 50 pasos ves cómo funciona el people picker.

Algunos pasos resumidos:

  • Cuando haces una consulta en el people picker, el WFE consulta via una DNS Query al Global Catalog Service (se llama “LDAP Global Catalog Search Request”)
  • En esta query, se pregunta por usuarios y grupos que tengan un string de búsqueda (wildcard search).
  • Cuando se busca en el AD, se busca por los siguientes atributos

# User objects: 'name', 'displayName', 'cn', 'sn', 'SamAccountName', 'mail', 'proxyAddresses'

# Group objects: 'name', 'displayName', 'cn', or 'SamAccountName' attributes.

Ahora mostremos algunos tips útiles para hacer throu

1-Evaluar la conectividad desde el WFE hacia el Global catalog.

Supongamos que estamos en el dominio “contoso.com”.

image

Supongamos que estamos buscando todas las cuentas que tengan “sp” en el display name o en el atributo “SamAccountName”. Sharepoint cómo les comenté buscará en varios atributos del container de usuarios y grupos de AD (user Objects y groups Objects)

Supongamos que también buscamos por un grupo específico (Ej: que tengan en el display name “Domain”). En amarillo las líneas más importantes.

El siguiente query (descargar) nos permite evaluar la conexión contra nuestro el AD, buscando un usuario y un grupo (usando wildcards). La funcionalidad es parecida a cómo busca Sharepoint.


function SearchUsers($cn) {   
   $strFilter = "(&(objectClass=User)(cn=$cn))" 
   $objDomain =New-Object System.DirectoryServices.DirectoryEntry("LDAP://CONTOSO")
   $ds = New-Object System.DirectoryServices.DirectorySearcher 
   $ds.SearchRoot = $objDomain 
   $ds.PageSize = 1000 
   $ds.PropertiesToLoad.Add("displayName") 
   $ds.PropertiesToLoad.Add("name") 
   $ds.PropertiesToLoad.Add("cn") 
   $ds.PropertiesToLoad.Add("sn") 
   $ds.PropertiesToLoad.Add("SamAccountName") 
   $ds.PropertiesToLoad.Add("mail") 
   $ds.PropertiesToLoad.Add("proxyAddresses") 
   $ds.Filter = $strFilter 
   $ds.SearchScope = "Subtree"   
   Write-Host " >> Buscando usuarios: " $cn 
   $colResults = $ds.Findall()
   foreach ($usrTmp in $colResults)
    {
      Write-Host $usrTmp.Properties["name"]
    }

function SearchGroup($cn) {   
   $strFilter = "(&(objectClass=group)(cn=$cn))" 
   $objDomain =New-Object System.DirectoryServices.DirectoryEntry("LDAP://CONTOSO")
   $ds = New-Object System.DirectoryServices.DirectorySearcher 
   $ds.SearchRoot = $objDomain 
   $ds.PageSize = 1000 
   $ds.PropertiesToLoad.Add("displayName") 
   $ds.PropertiesToLoad.Add("name") 
   $ds.PropertiesToLoad.Add("cn") 
   $ds.PropertiesToLoad.Add("sn") 
   $ds.PropertiesToLoad.Add("SamAccountName") 
   $ds.Filter = $strFilter 
   $ds.SearchScope = "Subtree"    
   Write-Host " >> Buscando grupos: " $cn 
   $colResults = $ds.Findall()
   foreach ($usrTmp in $colResults)
    {
      Write-Host $usrTmp.Properties["name"]
    }
}

$startDTM = (Get-Date)
SearchUsers("*sp*") 
$endDTM = (Get-Date)
"La consulta tardo: $(($endDTM-$startDTM).totalseconds) segundos"

$startDTM = (Get-Date)
 SearchGroup("*domain*") 
$endDTM = (Get-Date)
"La consulta tardo: $(($endDTM-$startDTM).totalseconds) segundos"
 

Cuando lo ejecuto me retorna lo siguiente.

image

image

Es cómo si hubiera hecho esto.

image

image

Este script nos permite evaluar la performance de búsqueda de perfiles y grupos contra nuestro Global Catalog. Cómo pueden ver los tiempos son muy buenos.

2-Evaluar la conectividad mediante Message Analyzer

Ejecuto lo siguiente en la línea de comandos (run as a administrator)

netsh trace start persistent=yes capture=yes tracefile=C:\nettrace-sharepoint.etl

image

Después realizo algunas querys desde el People Picker, ej: sp_

image

Después detengo el trace.

netsh trace stop

image

Abro el Message Analyzer cómo administrador (run as a administrator)

Selecciono New Session / Files

image

Agrego el archivo del trace

image

En la sección de filtros agrego lo siguiente: contains "displayName"

image

En los mensajes veo que las query al AD, en el sumary dice:

Search Operation Search For RootDSE

Cuando entro a un mensaje, por ejemplo 1003

image

Hago click en el atributo LDAP.

En filter dice lo siguiente

(((objectCategory == person) && ((SubstringFilter{Type=anr,SubStrings=[Sp_]}) || (SubstringFilter{Type=SamAccountName,SubStrings=[Sp_]}))) || ((objectCategory == group) && (MatchingRuleAssertion{MatchingRule=1.2.840.113556.1.4.803 (LDAP_MATCHING_RULE_BIT_AND),Type=groupType,MatchValue=2147483648,DnAttributes=False}) && ((SubstringFilter{Type=anr,SubStrings=[Sp_]}) || (SubstringFilter{Type=SamAccountName,SubStrings=[Sp_]}))))

Pero si entro en más detalle, en la parte de Filter, veo que tiene dos contenidos (objectCategory==person y objectCategory==group). Es decir en la misma query, consulta por usuarios y grupos que coincidan con “sp_”

image

Los atributos que cargo en la query son

image

Si filtro solamente el mensaje 1003 vero que el tiempo que tardo en hacer la consulta fue de 0.0024204 ms

image

Y necesitó 4 paquetes para obtener los resultados

image

El AD (10.0.0.4) le retorno 3 paquetes, si ven en el summary, ven que dice LDAP Message, Search Result Entry, MessageID: 43

image

Si ves el field data, podes ver que aparece (SP_farm y SP_setup) lo mismo que buscaste por Sharepoint

image

image

Algunos links útiles:

http://thesharepointfarm.com/2014/01/people-picker-troubleshooting-tips/