domingo, 18 de enero de 2015

Event Receiver remoto – Sharepoint 2013 (remote event receiver) – Parte 1

En este post voy a crear un event receiver remoto (deployado sobre un Azure WebSite) para un Sharepoint 2013 on-premise. En una segunda parte voy a utilizar algunos servicios de Azure (Table, Services Bus, Push Notifications, etc) para ver el uso que se puede hacer con estos eventos-

Hay muchos ejemplos con Auto-hosted Apps: http://blogs.technet.com/b/sharepointdevelopersupport/archive/2013/03/13/how-to-create-a-remote-event-receiver-for-a-sharepoint-hosted-app.aspx

La idea es utilizar client object para configurar los remote event receiver, esto nos permite configurarlos sin deployar nada en los ambientes, y afectar su funcionamiento.

image

Primero creo una librería con dos campos:

ValorAnteriorDocNum = representa el valor anterior que tenia DocNum, solo lo actualizo en el eventoItemUpdating
DocNum = String (visible)      

imageimage

image

Creo una aplicación ASP.NET Empty

image

Elijo que se va a deployar en Windows Azure (host in the cloud)

image

Agrego mi subscripción

image

Una vez configurada la subscripción se crea el website en Azure

image

Agrego las referencias de “Microsoft.Sharepoint.Client.dll” y “Microsoft.Sharepoint.Client.Runtime.dll”

En general las dll´s están en : C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI

image

image

Recuerda marca la opción “Copy Local” a true

image

Agrego un WCF Service llamado “EventSynchronous.svc”

image

En el archivo “EventSynchronous.svc.cs” edito el archivo y le agrego lo siguiente.

Agrego referencias mediante using:

  • using Microsoft.SharePoint.Client;
  • using Microsoft.SharePoint.Client.EventReceivers;
  • using System.ServiceModel.Activation;

Después hago que la clase EventSynchronous herede de IRemoteEventService

image

Agrego dos métodos para satisfacer la Interface.

image

El método ProcessOneWayEvent se deja vacio, porque se usa para eventos Asyncrónicos. Definición de los métodos:

1. ProcessEvent(): This is a synchronous event that handles events that occur before an action occurs.  Such as when a user adds or deletes a list item.  Two way event that can handle current events (“-ing”) that can callback to SharePoint.

2. ProcessOneWayEvent(): This is a asynchronous event that handles events that occur after an action occurs.  Such as after a user adds an item to a list or deletes an item from a list.  This is one-way event that can handle past events (“-ed”).

En cambio el método “ProcessEvent”, tiene el siguiente contenido

image

Dependiendo del tipo de evento (ItemDeleted o ItemUpdating) hago una operación diferente, en este caso sólo hago la de ItemUpdating

Deployo la solución en el website de Azure.

image

image

image

Verifico si está andando el servicio WCF:http://eventreceiver5178.azurewebsites.net/EventSynchronous.svc

Ahora creo en el mismo proyecto de Visual Studio, una proyecto de consola

image

Agrego de nuevo las referencias del proyecto anterior

image

En Program.cs, hay 3 variables para completar

image

Después copio lo siguiente.

image

string sharePointUrl = "http://chrissp2013dev/sites/testtheme";
           string remoteWebUrl = "http://eventreceiver5178.azurewebsites.net";
           string listName = "Evento";         
           using (ClientContext clientContext = new ClientContext(sharePointUrl))
           {
               //si estas con un usuario administrador de la farm, no será necesario agregar autenticación.
               //sino usa la siguiente propiedad.
               //NetworkCredential credentials = new NetworkCredential(“username”, “pwd”, “domain”);
               //clientContext.Credentials = credentials;
               clientContext.AuthenticationMode = ClientAuthenticationMode.Default;
               List targetList = clientContext.Web.Lists.GetByTitle(listName);
               clientContext.Load(targetList);
               EventReceiverDefinitionCollection ec = targetList.EventReceivers;
               clientContext.Load(ec);
               //ITEM UPDATING
               EventReceiverDefinitionCreationInformation eventReceiver = new EventReceiverDefinitionCreationInformation();
              eventReceiver.EventType = EventReceiverType.ItemUpdating;
               eventReceiver.ReceiverAssembly = "EventReceiver";
               eventReceiver.ReceiverClass = "EventReceiver.EventSynchronous";
               eventReceiver.ReceiverName = "EventSynchronous";
               eventReceiver.ReceiverUrl = remoteWebUrl + "/EventSynchronous.svc";
               eventReceiver.SequenceNumber = 1000;
               eventReceiver.Synchronization = EventReceiverSynchronization.Synchronous;
               targetList.EventReceivers.Add(eventReceiver);
               clientContext.Web.Context.ExecuteQuery();

               //ITEM DELETING
               eventReceiver = new EventReceiverDefinitionCreationInformation();
              eventReceiver.EventType = EventReceiverType.ItemDeleting;
               eventReceiver.ReceiverAssembly = "EventReceiver";
               eventReceiver.ReceiverClass = "EventReceiver.EventSynchronous";
               eventReceiver.ReceiverName = "EventSynchronous";
               eventReceiver.ReceiverUrl = remoteWebUrl + "/EventSynchronous.svc";
               eventReceiver.SequenceNumber = 1000;
               eventReceiver.Synchronization = EventReceiverSynchronization.Synchronous;
               targetList.EventReceivers.Add(eventReceiver);
               clientContext.Web.Context.ExecuteQuery();
           }

Para verificar el estado de los event receiver, ejecuto el siguiente script powershell

$web=Get-SPWeb "http://chrissp2013dev/sites/testtheme"
$list=$web.Lists["Evento"]
$list.EventReceivers |ForEach-Object {Write-host $_.Type  $_.Name}
$web.Dispose()

image

Después en nuestro ambiente on-premise de Sharepoint, primero verificamos que tengamos acceso al website de azure (habilitar puertos 80 o 443 dependiendo si usaste certificados y el proxy interno para que Sharepoint pueda acceder a contenidos publicados en internet).

Subo un documento y verifico el funcionamiento.

image

Se dispara el evento ItemUpdating y actualiza el campo ValorAnteriorDocNum, ya que las librerías funcionan diferentes a las listas, cuando subes el documento se dispara el evento ItemAdding, y cuando te aparecen las propiedades para actualizarlas se dispara el evento ItemUpdating.

Me queda así el campo ValorAnteriorDocNum

image

Recuerda la siguiente tabla para las listas:

image

Recuerda la siguiente tabla para las librerias:

image

jueves, 8 de enero de 2015

Cambiar la foto de tu perfil con Powershell

Cambien los valores en rojo.

URLMySite: hace referencia al web applicationde MySites

SiglaAD: es la sigla del usuario al cual quieren cambiar la foto

Add-PSSnapin Microsoft.SharePoint.PowerShell -ErrorAction SilentlyContinue
$mySiteUrl = "http://URLMySite"
 
#Connect to the User Profile Manager
$site = Get-SPSite $mySiteUrl
$context = Get-SPServiceContext $site
$profileManager = New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($context)
$up = $profileManager.GetUserProfile("SiglaAD")
$up["PictureURL"].Value = "http://URLMySite/User Photos/Profile Pictures/SiglaAD_MThumb.jpg"
$up.Commit()
write-host $up.DisplayName" --> ", $up["PictureURL"].Value

martes, 6 de enero de 2015

Sharepoint 2013 - User Profile Synchronization Status queda en un estado “Stopping” o “Syncronizing”

Si “Profile Syncronization Status” queda en un estado “Stopping” o “Syncronizing” por más de 1 hora, revisa si el servicio de “Forefront Identity Manager Service” está detenido

image

image

Este servicio es el que gestiona las sincronizaciones.

domingo, 4 de enero de 2015

Obtener todos los websites de un web application mediante powershell

Add-PSSnapin Microsoft.SharePoint.PowerShell –erroraction SilentlyContinue
$webApplication = Get-SPWebApplication http://url_webApplication
$filePath = "C:\AllSites.txt"
$sites = $webApplication.Sites
foreach ($site in $sites)
{
    foreach ($web in $site.AllWebs)
    {      
        $web.Url | Out-File -FilePath $filePath -Append -Width 256        
    }   
}

sábado, 3 de enero de 2015

IntelliTrace Collector–Sharepoint 2013

Siempre es útil debuggear apps en producción, pero no siempre nos dejan instalar Visual Studio para hacer el debugging. En estos casos podemos usar IntelliTrace collector para guardar información de diagnóstico en un Intellitrace log file (.iTrace files). Nos permite registrar que pasa en nuestra app sin instalar Visual Studio o hacer cambios en el ambiente.

image

Folder: C:\IntelliTraceCollector

Abro una consola de powershell con permisos de administrador, y ejecuto lo siguiente:

cd C:\IntelliTraceCollector

expand /f:* IntelliTraceCollection.cab. C:\IntelliTraceCollector

image

Ejecuto el siguiente comando en la consola de powershell:
Import-Module C:\IntelliTraceCollector\Microsoft.VisualStudio.IntelliTrace.PowerShell.dll

image

Para saber sobre que App Pool voy a ejecutar el tracer, ejecuto el siguiente comando:

[Microsoft.SharePoint.Administration.SPWebService]::ContentService.ApplicationPools | ft Name

Este nos retorna todos los app pools de los web application. En el caso que quieras debuggear alguna feature del Central Administration, ejecuta el siguiente comando:

[Microsoft.SharePoint.Administration.SPWebService]::AdministrationService.ApplicationPools | ft Name

El mismo nos retorna el nombre del pool del Central Administration.

Ahora ejecuto lo siguiente:

Start-IntelliTraceCollection "DefaultAppPool (6.20.2014 10.27.31 AM)" "C:\IntelliTraceCollector\collection_plan.ASP.NET.default.xml" "C:\IntelliTraceCollector"

image

Ahora ejecuto el siguiente comando

Get-IntelliTraceCollectionStatus

El cual nos lista todos los App Pool y el estado del trace.

image

collection_plan.ASP.NET.default.xml: Colecciona solo IntelliTrace events y SharePoint events, incluyendo exceptions, database calls, y Web server requests. Si queres tener un trace más detallado tenes que ejecutarlo con collection_plan.ASP.NET.trace.xml

DefaultAppPool (6.20.2014 10.27.31 AM) –> es el nombre del Pool

C:\IntelliTraceCollector –> es el output de salida del archivo iTrace

Después de haber verificado que está OK el trace , reproduzco el problema, y recorro un poco el sitio de Sharepoint para coleccionar datos.

Una vez que reproduje el error, ejecuto el siguiente comando:

Checkpoint-IntelliTraceCollection "DefaultAppPool (6.20.2014 10.27.31 AM)"

image

Para finalizar el trace, ejecuto el comando:

Stop-IntelliTraceCollection "DefaultAppPool (6.20.2014 10.27.31 AM)"

Get-IntelliTraceCollectionStatus

En el directorio, está el archivo iTrace. El cual se puede abrir con Visual Studio 2013.

image

Después puedo recorrer el trace

image

Por ejemplo, puedes agregar el correlation ID de la excepción de Sharepoint para buscar la sección del trace que le corresponde.

Puedes ver las excepciones, web request, la información del sistema en el momento que ejecutaste el trace, la lista de threads que se dispararon y los modulos que se cargaron (.dll)

image

image

image

image

image

Para mayor información: http://msdn.microsoft.com/en-us/library/vstudio/hh398365.aspx

http://blogs.msdn.com/b/visualstudioalm/archive/2012/12/11/debugging-sharepoint-apps-with-intellitrace-in-visual-studio.aspx

jueves, 1 de enero de 2015

Troubleshooting problemas de performance de SQL Server para Sharepoint–Parte 1

Voy a arrancar una serie de post relacionados al troubleshooting de problemas de performance de SQL Server para Sharepoint. Anteriormente había creado una serie de post sobre las mejores prácticas de SQL Server para Sharepoint:

Parte 1: Optimización de los servidores de SQL Server

Parte 2: Optimización de la instancia de SQL Server

Parte 3: Mantenimiento de SQL Server

En este caso, me voy a enfocar especificamente en los problemas de performance (lentitud de consultas, consumo alto de RAM o CPU, etc).

Hoy voy a revisar el parámetro “Page Life Expectancy” (PLE). El cual es el tiempo esperado, en segundos, que una página de un data file es sacada (pushed out) del buffer pool (in-memory cache de data files pages) para hacer espacio para otra página diferente de un data file. En resumen, indica la presión sobre el buffer pool para leer páginas de data files. Un número alto es mejor, ya que si no hay memoria suficiente en el sistema, se sacan más rapidamente las páginas del buffer pool.

Microsoft recomendaba que este contador supere los 300 ms (>). Si nuestro ambiente. nos da que PLE ronda los 300 ms, significa que el buffer pool completo se saca (flushed) y se leer de forma completa cada 5 minutos. Este valor fue cambiando con los sistemas actuales, que tienen muchos GB de RAM.

La fórmula correcta para evaluar este parámetro es: ( Buffer pool memory in GB / 4 ) x 300

Con la siguiente query, puedo saber el valor de este contador.

SELECT object_name, counter_name, cntr_value
FROM sys.dm_os_performance_counters
WHERE [object_name] LIKE '%Buffer Manager%'
AND [counter_name] = 'Page life expectancy'

image

Recomiendo ejecutar este comando cada 6 horas, para tener una certeza más correcta de este comando. Ya que hay operaciones tales como DBCC CHECKDB o index rebuilds pueden afectar el valor de este contador.

En este caso, ejecuté el comando sobre un servidor con 16 GB de RAM (SQL Server 2012).

Para saber el tamaño del buffer pool memory, ejecuto el siguiente comando

select DB_NAME(database_id) Database_Name, count(*) Pages
from sys.dm_os_buffer_descriptors
group by database_id 
order by Pages desc

El cual nos devuelve todas las bases de datos que tienen páginas en el buffer

image

Un punto importante, para ver mediante esta consulta, es que nos da una idea de que bases de datos tienen más páginas en el buffer, con lo cual nos da un parámetro para saber que bases de Sharepoint son las que más actividad tienen. En la imagen superior, la base de contenido “WSS_Content_SPS_2” tiene casi 9 GB de datos cargados en la RAM (1146634*8/1024), lo cual es demasiado para una base. En este caso recomiendo crear otra base de contenidos y mover algunos sites collection a esa base, para bajar este número. Más adelante daré otros tips para subir el PLE lo cual puede mejorar la cantidad de páginas para esta base de contenido.

La suma de todas las páginas (de todas las bases) es : 1347406

(1347406*8)/1024 =~ 10526.61 –> 10.28 GB.

Agrego este valor a la fórmula anterior, y evaluo el resultado

(10.28GB /4) *300= 771 lo cual es un valor adecuado para el contador PLE.

Si este contador nos da demasiado bajo, podemos revisar lo siguiente:

  • Índices no actualizados
  • Estadísticas no actualizados
  • Fragmentación de datafiles

La base de search en general tiene bastantes páginas en el buffer, ya que en general se obtienen datos random de las queries.

Una manera de revisar que queries son las que causan más lecturas, es ejecutando la siguiente query.

SELECT TOP 25 cp.usecounts AS [execution_count]
      ,qs.total_worker_time AS CPU
      ,qs.total_elapsed_time AS ELAPSED_TIME
      ,qs.total_logical_reads AS LOGICAL_READS
      ,qs.total_logical_writes AS LOGICAL_WRITES
      ,qs.total_physical_reads AS PHYSICAL_READS
      ,SUBSTRING(text,
                   CASE WHEN statement_start_offset = 0
                          OR statement_start_offset IS NULL 
                           THEN 1 
                           ELSE statement_start_offset/2 + 1 END,
                   CASE WHEN statement_end_offset = 0
                          OR statement_end_offset = -1 
                          OR statement_end_offset IS NULL 
                           THEN LEN(text) 
                           ELSE statement_end_offset/2 END -
                     CASE WHEN statement_start_offset = 0
                            OR statement_start_offset IS NULL
                             THEN 1 
                             ELSE statement_start_offset/2  END + 1
                  )  AS [Statement]       
FROM sys.dm_exec_query_stats qs 
   join sys.dm_exec_cached_plans cp on qs.plan_handle = cp.plan_handle
   CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
ORDER BY qs.total_physical_reads DESC;

En este caso, veo que la query

SELECT          SUBSTRING(ADS.Content, @Offset, @Length)      FROM          TVF_AllDocStreams_SiteDocIdInternalVersion(@SiteId, @DocId, @InternalVersion) AS ADS     

image

AllDocStreams es la tabla donde se almacenan los documentos de Sharepoint.

Otra query para saber el tamaño en MB de las páginas que están en el buffer pool

SELECT count(*)*8/1024 AS 'Data Cache Size(MB)' ,CASE database_id WHEN 32767 THEN 'RESOURCEDB' ELSE db_name(database_id) END AS 'DatabaseName' FROM sys.dm_os_buffer_descriptors GROUP BY db_name(database_id) ,database_id ORDER BY 'Data Cache Size(MB)' DESC

Hay mucha información sobre PLE en Internet, la idea es mostrarle un par de consultas y parámetros para ayudarlos en algún problema que tengan con su SQL de Sharepoint.

Popups (SP.UI.ModalDialog.showModalDialog) en una página WIKI en Sharepoint 2013

Creo una página Wiki

image

Inserto un Script Editor (Embed Code)

image

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<script language="javascript">
 
  function openBasicDialog(tUrl, tTitle) {
         var options = {
             url: tUrl,
             title: tTitle
         };
         SP.UI.ModalDialog.showModalDialog(options);
     }
    
</script>
</head>
<body>
<input type="button" value="Página de Microsoft" onclick="openBasicDialog('http://www.microsoft.com','Microsoft Page');"/>
</body>
</html>

image

Al hacer click en el botón, me lanza un PopUp modal

image

Este script también nos permite mostrar el formulario de NewItems para Listas o el formulario de Upload para Librerias.

Por ej: si utilizo la url de upload de una Librería con el parámetro IsDlg, el cual permite mostrar un form preparado para PopUp (sin barras de desplazamiento, tamaño más reducido, etc). Por ejemplo la siguiente URL

http://URLSite/_layouts/15/Upload.aspx?List={ID_Lista}&RootFolder=&IsDlg=1

image

Entonces lo único que debo hacer es cambiar en el button, la url

<input type="button" value="Agregar Nuevo Documento" onclick="openBasicDialog('http://URLSite/_layouts/15/Upload.aspx?List={ID_Lista}&RootFolder=&IsDlg=1','Librería de Documentos');"/>

Lo cual resulta en

image

image