sábado, 30 de abril de 2016

Asignando valor predeterminado a una propiedad de un elemento Web de forma declarativa SharePoint 2013

Asignando valor predeterminado a una propiedad de un elemento Web de forma declarativa SharePoint 2013





Para agregar un valor predeterminado a una propiedad de un elemento web debemos de ir al código de servidor o Code Behind del control de usuario, como se muestra en la imagen de abajo.

Debemos definir una propiedad pública de la clase del control de usuario.  Y agregar los atributos: WebBrowsable,WebDisplayName, WebDescription,Category y Personalizable, como se muestra en el código de abajo.

Luego vamos a definir un módulo dentro del proyecto, esto nos permitirá adicionar páginas y elementos web a las páginas cuando se instale la solución y agregar el valor predeterminado a la propiedad.




Luego creamos una Application Page como plantilla para entonces crear una instancia de la página donde definamos el elemento web.




En la página se define las zonas de los elementos web para poner distribución de los elementos Web dentro de la página.




Ahora vamos a modificar el archivo elements.xml donde se define la instancia de la página y los elementos web dentro de ella.


     


Y través del nodo properties, definimos los nombres de cada propiedad con su respectivo valor.

Y eso es todo!

SharePoint4Fun!,

Juan Manuel Herrera Ocheita

martes, 26 de abril de 2016

Como conocer los usuarios que estan conectados el día de hoy en SharePoint 2013

La única forma que encontrado de lado de SharePoint es ejecutando un comando de T-SQL hacia la base de datos de estádisticas con WSS_Logging o a veces lo nombran como WSS_UsageAplication.   Esta tiene diferentes vistas y funciones que nos pueden proveer información ya relacionada para facilitarnos la consulta.

El tema es que tienes que tener configurado la colección de datos de uso y salud.   Para ello ve al Central Adminsitration, a Montoring y luego Configure usage and health data collection.




Luego navegue un poco y espere por lo menos un día para que actualice la base de datos.  Si desea ver información más actualizada puede ejecutar manualmente los siguientes trabajos de SharePoint Timer Service.





Ahora el  query t-sql para ver la información.  


DECLARE @StartTime DATETIME     = GETDATE() ,
     @EndTime DATETIME     = DATEADD(day, 1, GETDATE());
      SELECT  UserLogin AS [User] ,
        COUNT(RowId) AS Hits ,
        MAX(LogTime) AS LastAccessTime

        FROM    [dbo].[RequestUsage]
       WHERE   PartitionId IN (
          SELECT  PartitionId
          FROM    dbo.fn_PartitionIdRangeMonthly(@StartTime, @EndTime) )
          AND LogTime BETWEEN @StartTime AND @EndTime
          AND UserLogin IS NOT NULL
          AND DATALENGTH(UserLogin) > 0
      GROUP BY UserLogIn


Y eso es todo amigos,

Juan Manuel Herrera

martes, 19 de abril de 2016

Request Management un Servicio de Cuidado


Request Management es un servicio introducido de SharePoint 2013, según la definición que podemos encontrar en Technet dice: “Permite a los administradores administrar las solicitudes entrantes y determinar cómo distribuir estas solicitudes”.  https://technet.microsoft.com/en-us/library/jj712708.aspx 

Pero solo habilitarlo y no configurarlo puede ser un gravísimo error.   Si la entrega de las páginas en el navegador es de forma desordenada o que demora alrededor de un minuto redireccionar de una página a otra puede ser un síntoma que el servicio esta levantado y no está configurado.  Como se muestra en la imagen de abajo.



Este servicio deberá estar habilitado cuando contemos con más de dos servidores front-end y lo tengamos configurado con  un balanceador o NLB, y tengamos una necesidad de administrar las solicitudes a los front-end en una granja mediana o grande, no es necesario ni aplicable para una pequeña granja.  Entonces la recomendación será apague el servicio.  Este se puede realizar  a través del Central Administration, en Manage services on server.






Solo presione la acción Stop sobre la fila donde aparece Request Management.  Haga clic en el botón de Refesh del navegador sobre la página y deberá ver el contenido de forma correcta.












Más sobre request Management y como configurarlo si cuentas con más de un servidor Font-end de tu granja.



http://www.harbar.net/archive/2012/11/12/Article-Request-Management-in-SharePoint-Server-2013.aspx


Hasta la próxima,



domingo, 3 de abril de 2016

Los secretos de las listas de SharePoint (2003,2007,2010,2013)

Investigando y evaluando la lentitud de una granja de SharePoint me llevo a indagar como SharePoint almacena las listas y aunque ya lo sabia no me habia percatado de lo siguiente:
Todas las listas de SharePoint se almacena en una sola tabla llamada AllLists en la base de datos de contenido (WSS_Content_????????????).


Segundo hallazgo todos los elementos de todas las listas incluyen las bibliotecas (que son listas especializadas) se almacenan un una sola lista llamada AllDocs.

Tercer hallazgo y aún mas revelador, los elementos eliminados permenecen en la lista hasta que el proceso de papelera de reciclaje los elimine en algún momento.  Observen lo siguiente:

En la Interfaz Web la lista aparece sin elementos
Pero a nivel de base de datos es otra historia Al ejecutar los siguientes Comandos T-SQL :

SELECT
*
FROM [WSS_Content_Reuniones].[dbo].[AllLists]
where --tp_Title like '%factura%'
tp_id = '00DA3DE4-D8AF-4EF1-A956-E44CBFEF1012'
SELECT count(*) as totalelements
FROM [WSS_Content_Reuniones].[dbo].[AllDocs]
where ListId = '00DA3DE4-D8AF-4EF1-A956-E44CBFEF1012'



Entonces si realizamos cargas grandes de documentos o elementos de las listas, aunque los eliminemos impactaran las cargas futuras de los elmentos y en las consultas que se hagan a la tabla.
Ahora cual es el campo que identifica si fue eliminado un elemento de la lista.  Veamos la siguiente consulta

SELECT
*
FROM [WSS_Content_Reuniones].[dbo].[AllDocs]
where ListId = '00DA3DE4-D8AF-4EF1-A956-E44CBFEF1012'
AND LEN(DeleteTransactionId) = 0




El campo es el DeleteTransacionId que si la logintud = 0 quiere decir que no esta eliminado, y lo único no eliminado es el encabezado de la lista y las páginas de la lista.
Para eliminar de la papelera de reciclaje los elementos podemos ejecutar las siguientes lineas:

$site = Get-SPSite http://sp2013man/sites/prueba
$site.RecycleBin | where {$_.DirName -like '*pruebaFacturas'} | foreach{ $site.RecycleBin.Delete($_.Id)}
$site.RecycleBin.Count // resultado = 3
Si ejecutamos el siguiente
SELECT count(*) as totalelements
    FROM [WSS_Content_Reuniones].[dbo].[AllDocs]
    where ListId = '00DA3DE4-D8AF-4EF1-A956-E44CBFEF1012'
El resultado es ahora:



Bueno en esta aprendimos que SharePoint para grandes volumenes de datos no es del todo la mejor opción de hecho encontré el siguiente artículo de msdn https://msdn.microsoft.com/en-us/library/ff647105.aspx
Using SharePoint Lists vs. Database Tables



Así que cuando pensemos grandes volumentes de elementos por el esquema de la base de datos de contenido de SharePoint no es una buena idea.

Hasta la proxima amigos!

sábado, 30 de enero de 2016

Script para listar cantidad de elementos en todas las listas y bibliotecas de todas las colecciones de sitios

En esta ocasión vamos a contar el total de elemntos de cada lista y biblioteca de SharePoint.  Para identificar si es una biblioteca u otra lista utilizamos la propiead BaseTemplate.
Para comprender mejor el Script es bueno repasar la arquitectura del Modelo de Objectos de Servidor de SharePoint. A través de la siguiente imagen:

En resumen el objeto SPWebApplication contiene uno o mas objectos SPSite que representa cada colección de sitios y dentro de cada colección de sitios accedemos uno o mas sub-sitios o sitios web con el objeto SPWeb. Dentro de cada SPWeb podemos acceder las listas y/o bibliotecas con el objeto SPList.  y por ultimo aunque no lo utilicemos en este script el objeto SPListItem que representa cada elemento del objeto SPList.
Ahora vamos a describir el contenido del script que nos generará el listado del total de elementos de cada lista.
--- Inicio del Archivo ListAllListElements.ps1 ----
#Obtenemos la aplicación Web
$spWebApp = get-SPWebApplication http://misitio
#Declaramos el nombre del archivo CSV donde guararemos la información
$OutputFile ="d:\infoware\totaldocumentos.csv"
#Escribimos el encabezado del contenido del archivo csv
Add-Content $OutputFile "Titulo,Sitio,Url,Lista-Id,Lista,Tipo,Cantidad Elementos"
#Obtenemos todas las colecciones de sitios de la aplicación Web
$allSites = $spWebApp.Sites
foreach($site in $allSites)
{
    #Obtenemos todos los subisitos de la la colección de sitios actuales.
$webs = $site.AllWebs
    foreach($Web in $webs)
    {
        foreach($list in $Web.Lists)
        {
        #Establecemos el valor de la variable $row con la url, titulo del sitio y la información de la lista        $row = $web.URL+","+$web.Title + ","
        +$list.Id+","+$list.Title+","+$list.BaseTemplate+","+$list.Items.Count                       
        Add-Content $OutputFile $row
        }
    }
}
--- Fin del Archivo ListAllListElements.ps1 ----
Para ejecutar el script utilizamos Management Shell for SharePoint 2013:
c:\> .\ListAllListElements.ps1
El resultado será similar a la siguiente imagen:




SharePoint4fun Amigos!.
Hasta la próxima,
Juan Manuel Herrera Ocheita

Script para obtener el espacio ocupado y la ultima modificación realizada a los documentospor sitio para SharePoint OnPremises

Estimar el espacio ocupado de un sitio no es algo fácil de obtener ya que cada estructura de sitio y de las listas y bibliotecas de SharePoint ocupan un espacio no determinado del todo.  Lo que mejor podemos hacer es sumar el espacio de los archivos almacenados en las bibliotecas de SharePoint.
Resultado de imagen para sharepoint powershell
Este script corre en SharePoint 2010 y estimo también en 2013.
A continuación veremos un script que recorre todos los sitios de una colección de sitios:
-----Inicio del script ListadoSitios.ps1 -----
$File = "D:\scripts\MiSitio-EspacioOcupado.CSV"
#Escribimos el encabezado en el archivoAdd-Content -Path $File  -Value "Titulo,Url,Size,LastDate"
#definimos la fecha más antigua para que tengamos un punto de comparación inicial la definimos global para que podamos obtener su valor fuera del método donde se asigna.
$global:LastDate = Get-Date
$global:LastDate = $global:LastDate.AddDays(-10000)

#Método que Obtiene el sitio primario y llama el método que obtenien los sub.sitios.
function GetWebSizes ($StartWeb)
{
    $web = Get-SPWeb $StartWeb
    GetSubWebSizes -Web $web
    $web.Dispose()    # Importante destruir el objeto SPWeb para liberar la memoria RAM
}

Cuando utilizar el Dispose en el siguiente enlace: https://blogs.technet.microsoft.com/stefan_gossner/2008/12/05/disposing-spweb-and-spsite-objects/

function GetSubWebSizes ($Web)
{

    #Inicializamos nuevamente la fecha de partida para comparación
    $global:LastDate = Get-Date
    $global:LastDate = $global:LastDate.AddDays(-10000)
    #Recorremos cada subsitio de forma recursiba
    foreach ($subweb in $Web.GetSubwebsForCurrentUser())
    {
        [long]$webtotal = 0
        #Recorremos cada carpeta del subistio para obtener el espacio ocupado
    foreach ($folder in $subweb.Folders)
        {
            $webtotal += GetFolderSize -Folder $folder
        }
    $content = $subweb.Url + "," + $subweb.Title + "," +  $webtotal + "," + $global:LastDate
    Add-Content -Path $File  -Value $content
        write-host $content

       #LLamada recursiva al mismo método
        GetSubWebSizes -Web $subweb
   
    }
}

function GetFolderSize ($Folder)
{
    [long]$folderSize = 0 
    foreach ($file in $Folder.Files)
    {

    #Compara la fecha de la ultima modificación del archivo con la fecha de punto de partida y si es mas reciente la del archivo entonces la reemplaza
    if ($file.TimeLastModified -gt $global:LastDate){
        $global:LastDate = $file.TimeLastModified
    }
            #Acumula el valor de cada archivo en bytes
        $folderSize += $file.Length;
    }
    foreach ($fd in $Folder.SubFolders)
    {
        $folderSize += GetFolderSize -Folder $fd
    }
    return $folderSize
}

GetWebSizes -StartWeb HTTP://MiSharePoint
-----fin del script ListadoSitios.ps1 -----
Si necesitamos recorrer todas las colecciones de sitio solo hay que agregar las siguientes lineas de comando:
function GetWebSizes ($StartWeb)
{
$sites = Get-SPSite –StartWeb -Limit All
foreach($site in $Sites){
    $web = $site.RootWeb
    GetSubWebSizes -Web $web
    $web.Dispose()    # Importante destruir el objeto SPWeb para liberar la memoria RAM
}

}
Eso es todo amigos. SharePoint4Fun!,
Juan Manuel Herrera Ocheita

viernes, 29 de enero de 2016

Como migrar el contenido de un sitio de WordPress a las bibliotecas de SharePoint Online

La estrategia es muy sencilla y va ser la siguiente:
1) Descargar el contenido a un almacenamiento local a través de una herramienta FTP.
2) Definir una estrategia para almacenar el contenido en SharePoint Online
2) Crear un Script que lea el contenido migrado y suba los archivos a bibliotecas de SharePoint.
La herramienta
Describamos un poco la herramienta llamada FileZila.   Esta herramienta de FTP es gratuita y esta disponible para los sistemas operativos más populares.   Lo mas importante si estamos en un entrono Windows es que vamos a poder acceder el contenido de WordPress que esta en Linux.
El enlace para descargar la herramienta es el siguiente: https://filezilla-project.org/ 
Veamos ahora la conexión hacia el servicio ftp


La información que debes de solicitar para hacer la conexión es la siguiente:
Dirección IP del servidor Linux
Puerto (Opcional solo si el cliente utiliza un puerto en especifico diferente el predeterminado [21] colocarlo allí).
Luego Las credenciales un usuario con acceso y una contraseña.
Seleccionamos lo que deseamos descargar y con el clic derecho del ratón seleccionamos la opción Download.


La estrategia
Para la estrategia vamos a crear bibliotecas por cada carpeta principal ya que en SPO hay un límite de despliegue de documentos de 5,000 por razones de rendimiento.  Por lo que debemos de tomar eso en cuenta.








Para reducir el trabajo en el script vamos a crear manualmente las bibliotecas porque tampoco son muchas en mi este caso de lo contrario deberíamos tomar eso en cuenta en el script y crear con comandos de Powershell las bibliotecas.



El Script
A través de este script invocamos los comandos de CSOM Client SharePoint Object Model y no el Modelo de objetos de SharePoint.   Porque razón?, bueno porque SharePoint Online no esta local sino en la nube y la forma de acceder remotamente las librerias de SharePoint es através de CSOM que no es mas que llamadas de servicios Web por medio de un módelo de objetos definido para ese propósito.

Ahora vemos el script que hace la magia:
/* Cargamos las librerias de CSOM */
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.dll"
Add-Type -Path "C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\ISAPI\Microsoft.SharePoint.Client.Runtime.dll"

/* El método Ensure_Folder nos asegurará que la carpeta este creada antes de copiar el contenido */
Function Ensure-Folder()
{
Param(
  [Parameter(Mandatory=$True)]
  [Microsoft.SharePoint.Client.Web]$Web,

  [Parameter(Mandatory=$True)]
  [Microsoft.SharePoint.Client.Folder]$ParentFolder,

  [Parameter(Mandatory=$True)]
  [String]$FolderUrl

)
    $folderNames = $FolderUrl.Trim().Split("/",[System.StringSplitOptions]::RemoveEmptyEntries)
    $folderName = $folderNames[0]
    Write-Host "Creating folder [$folderName] ..."
    $curFolder = $ParentFolder.Folders.Add($folderName)
    $Web.Context.Load($curFolder)
    $web.Context.ExecuteQuery()
    Write-Host "Folder [$folderName] has been created succesfully. Url: $($curFolder.ServerRelativeUrl)"

    if ($folderNames.Length -gt 1)
    {
        $curFolderUrl = [System.String]::Join("/", $folderNames, 1, $folderNames.Length - 1)
        Ensure-Folder -Web $Web -ParentFolder $curFolder -FolderUrl $curFolderUrl
    }
}

/* El método Upload-File  recibe como parametros el url relativo de la carpeta destino y la ubicación local del archivo */
Function Upload-File()
{
Param(
  [Parameter(Mandatory=$True)]
  [Microsoft.SharePoint.Client.Web]$Web,

  [Parameter(Mandatory=$True)]
  [String]$FolderRelativeUrl,

  [Parameter(Mandatory=$True)]
  [System.IO.FileInfo]$LocalFile

)
    try {
     $folderUrl = $LocalFile.Name.ToLower().Replace($SourceFolderPath.ToLower(),"")
    
     $fileUrl = $FolderRelativeUrl + "/" + $folderUrl

       write-host "FileUrl:" $fileUrl
       Write-Host "Uploading file [$($LocalFile.FullName)] ..."
       [Microsoft.SharePoint.Client.File]::SaveBinaryDirect($Web.Context, $fileUrl, $LocalFile.OpenRead(), $true)
       Write-Host "File [$($LocalFile.FullName)] has been uploaded succesfully. Url: $fileUrl"
    }
    catch {
       write-host "An error occured while uploading file [$($LocalFile.FullName)]"
    }
}

/* El método Upload-Files es la principal ya que localiza cata archivo local y define la url destino para subir el archivo  a SPO.  Este método recibe como parameto la url destino, el usuario de O365, la contraeña, el nombre de la biblioteca de SharePoint destino, y la rutal local donde esta ubicado el contenido.  Como es recursivo solo debemos de dar el punto de partida de donde deseamos iniciar la carga de información. */

function Upload-Files()
{

Param(
  [Parameter(Mandatory=$True)]
  [String]$Url,

  [Parameter(Mandatory=$True)]
  [String]$UserName,

  [Parameter(Mandatory=$False)]
  [String]$Password,

  [Parameter(Mandatory=$True)]
  [String]$TargetListTitle,

  [Parameter(Mandatory=$True)]
  [String]$SourceFolderPath

)
    if($Password) {
       $SecurePassword = $Password | ConvertTo-SecureString -AsPlainText -Force
    }
    else {
      $SecurePassword = Read-Host -Prompt "Enter the password" -AsSecureString
    }

# Abajo la autenticación a O365 para acceder los objetos de SharePoint Online
    $Context = New-Object Microsoft.SharePoint.Client.ClientContext($Url)
    $Credentials = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($UserName,$SecurePassword)
    $Context.Credentials = $Credentials
    #Instanciamos el Web Site que vamos acceder
    $web = $Context.Web
    $Context.Load($web)

    # Instanciamos la biblioteca destino
    $list = $web.Lists.GetByTitle($TargetListTitle);
    $Context.Load($list.RootFolder)

#Ejecutamos el conjunto de lineas anteriores
    $Context.ExecuteQuery()
#Recorremos cada directorio local del parametró de ruta origen que indicamos al ejecutar el método

    Get-ChildItem $SourceFolderPath -Recurse | % {
       if ($_.PSIsContainer -eq $True) {
          $folderUrl = $_.FullName.ToLower().Replace($SourceFolderPath.ToLower(),"")
          $folderUrl = $folderUrl.Replace("\","/").Replace(".","")  
          if($folderUrl) {
             Ensure-Folder -Web $web -ParentFolder $list.RootFolder -FolderUrl $folderUrl
          } 
       }
       else{

          # Basado en la ubicación local creamos un Url Relativo reciproco en la biblioteca de SPO
          $folderRelativeUrl = $list.RootFolder.ServerRelativeUrl + $_.DirectoryName.ToLower().Replace($SourceFolderPath.ToLower(),"").Replace("\","/").Replace(".","") 

          #Ejecutamos el métod que subira el archivo a la ruta destino
          Upload-File -Web $web -FolderRelativeUrl $folderRelativeUrl -LocalFile $_
       }
    }
}

#End of file
La ejecución
Para ejecutar el archivo uploadfiles-csom.ps1 deberemos declarar los siguientes parámetros:

$Url =
https://miempresa.sharepoint.com/sites/miSitio
$UserName = misuario@midominio.onmicrosoft.com
$Password = "******"
$TargetListTitle = "BibliotecaDestino"   #Target Library
$SourceFolderPath = "X:\www\data\uploads\CarpetaOrigen"  #Source Physical Path


#Comando
Upload-Files -Url $Url -UserName $UserName -Password $Password -TargetListTitle $TargetListTitle -SourceFolderPath $SourceFolderPath


Y eso es todo amigos.
Hasta la próxima, SharePoint4Fun!
Juan Manuel Herrera Ocheita