miércoles, 16 de noviembre de 2016

Filtrar por carpeta en una vista de un WebPart de tipo Lista/Biblioteca

Recientemente me han solicitado que en la página de inicio de un sitio web, mostrara una vista de una carpeta concreta de una lista. Aquí lo ideal sería utilizar el WebPart por defecto de la lista, pero si jugamos con él desde la interface de SharePoint, pronto deduciremos que no es posible realizar ningún filtro para una carpeta en concreto (y este es el tipo de cosas que me encantaría que Microsoft mejorara en SharePoint, pero parece más centrada en evolucionar la arquitectura base y la integración con la nube).

Así pues, ¿Existe alguna forma de filtrar por carpeta en un WebPart de tipo Lista/Biblioteca?

Una vez más, SharePoint Designer acude a nuestro rescate (si eres, como yo, de los que quiere evitar código tanto como se pueda).

Para ello tendremos que crear una vista específica en la lista que queremos mostrar, típicamente con el nombre de la carpeta a filtrar. Esto lo podemos hacer directamente por la interface normal de SharePoint.

Una vez tengamos la filtra creada (campos a mostrar, filtros adicionales, número de elementos...), abriremos nuestro SharePoint Designer e iremos al sitio donde reside dicha lista. Entonces iremos a "Listas y Librerías", y seleccionaremos aquella en la que estamos trabajando.

Una vez dentro, en la sección de "Vistas", seleccionaremos la que acabamos de crear y, con el botón derecho, la editaremos en modo avanzado.



Ya dentro del código de la vista, buscaremos una línea que empieza por un string similar a:

<View Name="{8B3F6F46-F283-4B78-BB42-558FE0899479}"

y dentro de esa línea, buscaremos el tag <Query>.

Tras la especificación del orden de los elementos (por el tag <OrderBy>), deberíamos encontrar la sección de filtro (<Where>), que es la que nos interesa modificar. Sin eliminar todo cuanto ya exista entre el <Where> y su cierre </Where> deberíamos añadir el filtro "FileDirRef" (<FieldRef Name="FileDirRef"/>), con el valor de nuestra URL a partir de la base de nuestro site collection ("/Sitio/Librería/Carpeta")

El filtro debería quedaros tal y como se muestra en las siguientes líneas (añadiendo los filtros adicionales que podáis tener en vuestra vista):

<Where><Contains><FieldRef Name="FileDirRef"/><Value Type="Computed">/Sitio/Librería/Carpeta</Value></Contains></Where>

Si guardamos los cambios realizados en esta vista, podremos comprobar en SharePoint que la vista filtra adecuadamente en la carpeta especificada.

Ya solo quedará crear el webpart de esta Lista/Biblioteca, y en las opciones de configuración, hacer que apunte a la vista que hemos creado a tal efecto.

Y el resultado: Solo mostrará los documentos contenidos en la carpeta que le hemos configurado en SharePoint Designer.

¡¡Seguimos sorteando los obstáculos que Microsoft se empeña en dejarnos en SharePoint!!

viernes, 28 de octubre de 2016

¿Por qué "no funcionan" las políticas de retención?

Recientemente un cliente me solicitó que desactivara un "contrato" de una lista de contratos una vez la fecha de "Caducidad" fuera alcanzada. Esta lista es una custom list, con un montón de metadatos personalizados y ficheros adjuntos. La fecha de "Caducidad" es un campo de tipo "Fecha y hora" (llamado "Automatic expiration") dentro de la misma lista. Por "desactivar" entendemos que hay otro campo de tipo booleano (llamado "Active"), que indica si el contrato está activo (="Yes") o inactivo (="No").

Sabiendo que el camino más fácil y rápido para lograr este objetivo es definir una política de retención, así lo hice: Creé un workflow muy sencillo que modificaba el valor del campo booleano, pasando de "Yes" a "No", y una política de retención para ejecutarlo una vez se alcanzara la fecha indicada por el campo "Automatic expiration" + 1 día (Día después de haber caducado).


Una vez implementado este sencillo proceso, creé un contrato de prueba y esperé a que se alcanzara la fecha (al día siguiente) para comprobar si había funcionado o no.

Para variar, como muchas veces en la vida, las cosas no funcionaron a la primera. La data había vencido, pero el contrato seguía activo. ¿Por qué?

La respuesta, en esta ocasión es fácil de entender y también de solucionar:

En la administración central de SharePoint existen 2 Timer Jobs encargados de ejecutar las políticas de retención:
  • "Information management policy"
  • "Expiration policy"
Por defecto estos Timer Jobs están configurados para ejecutarse semanalmente, así que este tipo de políticas basadas en un día concreto, solo se verían reflejadas una vez por semana.

Sabiendo esto, si vamos a la sección Monitoring de la administración central y pulsamos sobre "Job Definitions", podemos buscar estos dos Timer Jobs en la app web que nos concierna, pulsar sobre su nombre y modificar su ejecución para que sea diaria en lugar de semanal:


Tened en cuenta que es importante que el Timer Job "Information management policy" debe ejecutarse ANTES que el "Expiration policy". En mi caso programé el primero para las 2 a.m. y el segundo para las 3.a.m.

Guardamos nuestros cambios y, ahora sí, todas la políticas de este tipo que tengamos bajo esta aplicación web, van a funcionar perfectamente (salvo caída del servicio, claro está).

Para comprobarlo podemos forzar un "Run now" y quedarnos así, con una sonrisa al ver que todo funciona como esperábamos que lo hiciera.

¡¡Saludos y hasta la próxima!!

jueves, 20 de octubre de 2016

Control permanente para autenticarse como otro usuario

Según mi experiencia, una de las "pérdidas" más incomprensibles que sufrimos al migrar a SharePoint 2013 desde SharePoint 2010 es que el usuario pierde la capacidad de autenticarse como otra persona.

En terminales compartidos, como salas de formación, puede llegar a ser un verdadero calvario tener que explicar constantemente a los usuarios que para realizar dicho proceso, deben añadir a la URL del navegador la extensión _layouts/closeConnection.aspx?loginasanotheruser=true (ver post relacionado).

Esta operación manual retorna al nuevo usuario a la raíz del site collection, donde puede ser que no tenga permisos de acceso, y es bastante engorroso encontrarse con un error de acceso no autorizado tras cada re-login.

Para hacerle la vida más fácil al usuario, y tener una solución permanente de simple uso y acceso, recomiendo fehacientemente añadir el control en el menú de usuario, tal y como se muestra en la siguiente imagen.


La solución es relativamente sencilla, siempre que tengáis acceso a los servidores de SharePoint.

Se trata de añadir en el fichero "\15\TEMPLATE\CONTROLTEMPLATES\Welcome.ascx"  el siguiente código justo antes del control que tiene el ID "ID_RequestAccess" (Editar con Notepad):

  <SharePoint:MenuItemTemplate runat="server" ID="ID_LoginAsDifferentUser" 
    Text="<%$Resources:wss,personalactions_loginasdifferentuser%>" 
    Description="<%$Resources:wss,personalactions_loginasdifferentuserdescription%>"
    MenuGroupId="100" 
    Sequence="100" 
    UseShortId="true" 
    />


El fichero resultante debería tener un aspecto como el siguiente:


Guardamos el resultado y aplicamos lo mismo a todos los servidores frontales (WFE) que tengamos en nuestra granja.

Si no queremos aplicarlo en toda nuestra granja, y tan solo lo necesitamos en un site/site collection específico, siempre podemos añadirlo a nuestra master page, añadiendo un control bajo la etiqueta <div id="suiteBarButtons"> y copiando el siguiente código entre los tags <SharePoint:FeatureMenuTemplate ....> y </SharePoint:FeatureMenuTemplate>


<SharePoint:MenuItemTemplate runat="server" id="MenuItem_SignInAsDifferentUser"
         Text="SignIn As Different User"
         Description="To log in as a different user"
         MenuGroupId="200"
         Sequence="250"
         UseShortId="true"
         ClientOnClickNavigateUrl="~siteLayouts/closeConnection.aspx?loginasanotheruser=true"
         PermissionMode="All"
/>


El resultado será el mismo, pero el ámbito estará limitado a donde aplique dicha .master page.

Con cualquiera de las dos soluciones, el control de "Sign in as different user" debería aparecer en el menú de control de usuario. Es multi-language por defecto y redirige al mismo sitio y página desde la que se efectúa la operación, así que es perfecto para nuestros propósitos.

Al ver los resultados, uno no puede evitar preguntarse: ¿Por qué no está incluido OOB? Pero todos sabemos que a Microsoft les encanta darnos trabajo a los consultores de SharePoint (cosa que, personalmente, debo agradecerles, claro está...).

En fin,  ¡Eso es todo por hoy amigos!

viernes, 23 de septiembre de 2016

Como utilizar las credenciales de forma segura en PowerShell

Powershell requiere en algunos de sus comandos el uso de las credenciales de usuario, para poder utilizarlas en llamadas a otros servicios (como por ejemplo al usar "Send-MailMessage").

Inicialmente, podemos crear una variable que contenga estas credenciales, y luego utilizarla a nuestro antojo. Una forma común de hacerlo sería con un script del tipo:

$userName = Get-ADUser "myUserLoginName" -Properties name | Select-Object -ExpandProperty Name
$password = "myPassword"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $userName, $secstr


Sin embargo, esta práctica no es muy recomendable, pues estamos añadiendo en nuestros scripts el password de nuestro usuario en claro, y cualquiera que abra nuestro fichero de scripts podría verlo y copiarlo sin problemas.

Para evitar esto, siempre es importante exportar nuestras credenciales cifradas en un fichero XML externo, y recuperarlas cuando sea necesario, sin necesidad de mostrar el password en ninguna de nuestras líneas.

Lo primero que haríamos sería generar ese fichero .XML en una ruta específica de nuestro servidor. Para ello ejecutaremos las siguientes líneas en nuestro PowerShell (evidentemente, directo en la consola, para no meter el password en ningún script)

$credPath = "C:\URLdeNuestraCarpetaEnElServer\myUser.xml"
$userName = Get-ADUser "myUserLoginName" -Properties name | Select-Object -ExpandProperty Name
$password = "myPassword"
$secstr = New-Object -TypeName System.Security.SecureString
$password.ToCharArray() | ForEach-Object {$secstr.AppendChar($_)}
$cred = new-object -typename System.Management.Automation.PSCredential -argumentlist $userName, $secstr | Export-Clixml $credPath


Tras ello, podremos verificar que el fichero "myUser.xml" se ha creado correctamente en la ruta indicada. Si tratamos de abrirlo, comprobaremos que el password está completamente encriptado, así que ya nadie podrá descifrarlo aunque tuviera acceso al fichero.

Ahora, en nuestros scripts de PowerShell, siempre que tengamos que utilizar esta credencial, podemos obtenerla de forma directa y sencilla con la siguiente línea:

$cred = Import-Clixml $credPath 

Podemos crear tantas credenciales de diversos usuarios como queramos, e invocarlas de la misma forma (modificando la ruta de acceso en cada caso).

Y.. ¡¡Esto es todo!! Siempre nos ahorraremos dolores de cabeza si, cuando implementamos algo en nuestra granja, lo hacemos utilizando las buenas prácticas que vamos aprendiendo a lo largo de nuestro camino.

Como siempre, espero que a alguien le sirva de ayuda.

¡¡Un saludo!!

viernes, 15 de julio de 2016

Como operar sobre un conjunto de items/ficheros filtrados con PowerShell

Últimamente estoy usando mucho más PowerShell que en el resto de mi vida profesional con SharePoint. Así que voy a empezar a escribir algunos de los scripts que siempre van bien tener a mano.

Uno de los usos que, personalmente más le suelo dar a PowerShell es la interacción sobre un grupo de elementos de una lista o biblioteca (por ejemplo, asignar un determinado permiso de seguridad a un grupo de ficheros que comparten una característica común (metadatos)).

Pero, ¿Cuál sería el script básico para obtener un grupo de elementos de una biblioteca/lista de SharePoint?

A continuación os dejo un código de ejemplo:

$webUrl = "La URL de tu site aquí"
$ListName = "El nombre de tu lista aquí"
$web = Get-SPWeb $webUrl
$list = $web.Lists[$ListName]
$items= $list.Items | where {($_['NombreCampo1'] -eq 'ValorDeseado1') -and ($_['NombreCampo2'] -eq 'ValorDeseado2') -and (($_['NombreCampo3'] -eq 'ValorDeseado3')-or ($_['NombreCampo3'] -eq 'ValorDeseado4'))}
foreach ($item in $items) {
    Write-Host ("-Realizando acciones para el ítem " + $item.ID) -ForegroundColor Yellow
}

A partir de aquí cada cual hará lo que tenga que hacer sobre cada uno de los elementos retornados. En el ejemplo tan solo escribe un mensaje con el ID del ítem en la consola, aunque en otros post compartiré cómo realizar algunas operaciones interesantes con esta base.

He puesto un ejemplo que utiliza un filtro donde contiene algún AND (-and), OR (-or) y EQUALS (-eq), para que se vea más o menos una sentencia con diversos valores, pero cada uno debería adecuar el filtro a sus propias necesidades, añadiendo o eliminando condicionantes.

Nada más por hoy, aunque en post posteriores voy a profundizar en el tema.

¡Un saludo!

viernes, 1 de julio de 2016

Cómo situar el menú de puntos suspensivos sobre otra columna en una lista/biblioteca

Recientemente me han solicitado cambiar la columna que contiene el menú de puntos suspensivos en una lista de SharePoint. Para ser honestos, al principio dudé que esto fuera posible (tan acostumbrados como estamos a la columna "Title" o "Name" en caso de bibliotecas), pero finalmente encontré una forma realmente sencilla de conseguir el resultado deseado.

Para cambiar el menú de opciones sobre un elemento a otra columna de la lista o biblioteca, tendremos que abrir nuestro "querido" SharePoint Designer sobre el sitio en cuestión. Entonces vamos a la lista que queremos modificar, y seleccionamos la vista de la lista a retocar.


En la pantalla de edición de la vista, veremos una línea bajo el <XmlDefinition> que empieza más o menos así (con el GUID distinto en cada caso, por supuesto)

<View Name="{85B6C86B-A760-4B9F-9A29-09901B043AB3}" DefaultView="TRUE" MobileView="TRUE"...

Si hacemos scroll lateral sobre esta línea, veremos que están listados en ella todos los campos que aparecen en la vista.

Para añadir el menú de puntos suspensivos sobre uno de esos campos, tan solo tendremos que añadir el par nombre=valor siguiente al lado del nombre de la columna: ListItemMenu='TRUE'


En el momento que guardemos nuestra modificación, ya estará operativa en nuestra vista. Como veréis en la siguiente captura de pantalla, el menú de puntos suspensivos está a la derecha de la columna "IDNumber", no bajo "Title", como suele ser lo habitual.


Y como extra, un par de trucos antes de finalizar:
  1. Si en lugar de ListItemMenu="TRUE" utilizáis CalloutMenu="TRUE" También tendréis el "document preview" en la columna elegida.
  2. Si, además añadís linkToItem="TRUE", la columna enlazará directamente con el documento (como lo hace por defecto la columna "Nombre").

¡Eso es todo por hoy!

¡Un saludo!

viernes, 1 de abril de 2016

Ordenar las reglas de consulta de búsqueda

Recientemente he estado retocando la el motor de búsqueda de SharePoint 2013.

Uno de los aspectos que más solemos utilizar cuando personalizamos este servicio son las reglas de consulta de búsqueda (Query Rules), que básicamente nos ayudan a especificar qué resultados queremos priorizar en determinadas situaciones.

En mi caso me encontré con que tenía 3 fuentes de contenido (Result Sources) distintas, que el cliente quería ordenadas de la siguiente forma (todas ellas en la página results.aspx de nuestro centro de búsqueda):

  1. Local People Results
  2. WordPress external Results
  3. Local SharePoint Results 
Puesto que "Local SharePoint Results" incluye todos los resultados excepto los relativos a personas, fue este el que utilicé de base, para crear 2 nuevas reglas de búsqueda: Una para añadir un bloque de resultados de búsqueda de personas, y otro para añadir un bloque de resultados de artículos provenientes de una plataforma WordPress externa.

Sin embargo, una vez había creado ambas reglas dentro de la fuente "Local SharePoint Results" obtuve un resultado no deseado: Las entradas de WordPress se mostraban antes que las de personas, y yo necesitaba que fuera a la inversa. Entonces fue cuando me pregunté por primera vez: ¿Cómo ordeno las reglas de consulta de búsqueda? Y aquí tenéis la solución.

Lo primero que tendremos que hacer es ir al panel de administración del servicio de búsqueda dentro de la Administración Central de SharePoint (si las reglas hubieran sido definidas a nivel de Site Collection, entonces tendríamos que ir a la administración de búsqueda del site collection), y pulsar sobre el enlace a las reglas de consulta de búsqueda.


Una vez en el panel de administración de las reglas de consulta de búsqueda, tendremos que seleccionar todas aquellas reglas que queramos ordenar, marcando su casilla de selección correspondiente. Después pulsaremos en "Ordenar las reglas seleccionadas" (Order Selected Rules).


Nos aparecerá entonces una ventana donde tendremos que agrupar las reglas seleccionadas. Para ello escribiremos el nombre del grupo donde deseemos  agruparlas (escribid el nombre que más os convenga), y después pulsar en el botón de "OK". 



Al hacerlo veremos cómo han aparecido unos números de ordenación delante de nuestras reglas seleccionadas. Ahora ya podremos modificarlos a nuestro gusto.


Esperamos unos pocos segundos, probamos nuestra búsqueda de nuevo... ¡¡Y todo aparece según el orden establecido!!