viernes, 28 de octubre de 2011

Conflicto entre encuestas de SharePoint 2010 y Jquery.

Jquery es muy usado en los portales de SharePoint para animar menús, desplegar ventanas emergentes y muchas otras animaciones disponibles para Web y utilizables en SharePoint.  Pero el problema es que SharePoint utiliza el signo $ para definir variables y este mismo signo es muy utilizado por Jquery para referirse a el. 

El caso:

Dentro de la página maestra tenía la siguiente invocación:

<script src=”jquery-1.6.4.min.js” type=”text/javascript”> </script>

<script src=”jquery.scrollTo.js” type=”text/javascript”> </script>

<script src=”starter.js” type=”text/javascript”> </script>

Y al momento de agregar una nueva pregunta a una encuesta reportaba el siguiente error:

Webpage error details:

User Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729)

Timestamp: Thu, 27 Oct 2011 20:36:52 UTC

Message: 'undefined' is null or not an object

Line: 1057

Char: 4

Code: 0

URI: http://servername/_layouts/qstnew.aspx?List={247B5F06-19F6-42BC-A202-D5DB1A4BCA5B}&InitialTabId=Ribbon%2EList&VisibilityContext=WSSListAndLibrary.

Gracias a mi colega MVP de Australia Ishai Sagi pude resolver el problema.  Los dejos con el correo que me envío:

From: Ishai Sagi [mailto:ishaisagi@gmail.com]
Sent: jueves, 27 de octubre de 2011 08:50 p.m.
To: Manolo Herrera
Cc: SharePoint MVP community
Subject: Re: Web Content Management

I stopped deploying jquery in master pages. I add it in page layouts or webparts. Otherwise, make sure you apply no conflict - but that doesn't resolve some issues.

Thanks, Ishai Sagi

Extelligent Design 

www.extelligentdesign.com

Sent from my phone

+61 488 789 786

En resumen elimine el registro del script de la master page, luego agregue en el evento Load() del elemento web o del control de usuario el registro de las referencias de los archivos de Jquery como se muestra arriba y para algunas páginas las edite por medio de SharePoint Designer en modo avanzado agregue las líneas ya que habían varios elementos Web haciendo referencia al código de Jquery.  Después de estos cambios ya la encuestas funcionaron correctamente.  Otra error puede ser que la página maestra este modificada y se haya eliminado algo que este afectando el portal, restaure la original y si funciona tendrá que volverla a modificar con cuidado para evitar el mismo error.

 

SharePoint4Fun!,

Juan Manuel Herrera O.

Comprendiendo el registro de usuarios dentro de SharePoint 2010

Para comprender como funciona el registro de usuarios dentro de SharePoint es importante declarar algunas cosas como las que siguen:

En la jerarquía de objetos primaria de contenido de SharePoint  podemos decir que todos los objetos están dentro de una Web Application (SPWebApplication). La Web Application tiene por lo menos una o mas base de datos de contenido. Cada base de datos de contenido puede tener una o mas colecciones de sitio (SPSite) y una colección de sitios puede tener por lo menos un sitio primario (SPWeb) y muchos subsitios (SPWeb) que puede a su vez contener mas subsitios. Dentro de los subsitios podemos hallar una lista (SPList) o  biblioteca de SharePoint y esta contiene un o mas elementos (SPListItem) y cada elemento puede contener uno o mas campos(SPField).  Esto se puede apreciar en la siguiente imagen:

PrimaryObjectsContentHierarchy

La regla

Cuando un usuario es:

1) Registrado dentro de un grupo de SharePoint 

2) Concedido permiso a un usuario directamente en algún sitio

3) O el usuario a subido un documento o agregado un elemento.

Este es internamente registrado en una tabla de base de datos llamada UserInfo de la base de datos de contenido a la que pertenece la Web Application. Esto lo hace por cada colección de sitios.  Esto quiere decir que si existen varias colecciones de sitios dentro de la misma base de datos que solamente tiene una única tabla UserInfo entonces encontraremos varios registros del mismo usuario dentro de dicha tabla ya que cada registro representará el usuario dentro de cada colección de sitios, como se muestra en la imagen de abajo.

image

Pero no toda esta información esta disponible a través de una la lista de SharePoint llamada "User Information List".  Para los mortales no tenemos acceso a la columna SystemID o SID, vía el modelo de objetos de SharePoint. 

Otra cosa importante a notar es que a pesar que la SharePoint Server tiene disponible los perfiles de los usuarios del directorio Activo.  El primero verifica al usuario en la tabla UserInfo.  Por ejemplo cuando accedemos el objecto SPUSer esta información la va a traer a UserInfo, cuando utilizamos el control PeoplePicker la información la va a traer primero a UserInfo y luego verifica el Directorio Activo.  Cuando utilizamos SPContext.Current.Web.CurrentUser para obtener el usuario que esta conectado va directamente a los registros de UserInfo.

El problema:

Supongamos que dispusimos que todos los usuarios tengan acceso de lectura al portal y agregamos en lectores o visitantes a los domain users del Directorio Activo.  Sucede que estos usuarios no aparecerán en la tabla UserInfo hasta que el usuario suba un documento (donde tenga permisos obviamente) o algún administrador de sitio agregue puntualmente al usuario a un grupo de SharePoint o le de permisos del sitio específicamente a este usuario.  Y recuerde que este es cierto para cada colección de sitios.  Es decir puede estar registrado en una colección de sitios y en otra no, y si hubo un cambio en la información del usuario en la ultima colección de sitios registrado este usuario se verá actualizado.  

Lo que es mas serio aún es que la información de la cuenta del usuario no se modifica sino que se queda como originalmente se registro la primera vez en SharePoint en la tabla UserInfo.  La demás información como correo, nombre completo, teléfono de trabajo se encarga para la versión SharePoint Server de sincronizarla con los perfiles de usuario. Pero si en el directorio activo se cambia ya sea el nombre de la cuenta del usuario o el dominio, este cambio nunca se reflejará en la tabla UserInfo de los registros de SharePoint. 

Y el problema esta cuando queremos comparar por cuenta de dominio del usuario (que si lo tenemos disponible) la información que registra SharePoint con la información de los perfiles de usuario para obtener alguna propiedad personalizada como lo puede ser el código de empleado. No encontraremos al usuario aunque sea el mismo, entonces una solución alternativa es acceder directamente las bases de datos vía comando T-SQL comparando los SID de ambas tablas.

El comando es el siguiente:

  SELECT TOP 1000
       up.[RecordID]
      ,up.[DocID]
      ,up.[UserID]
      ,up.[NTName]
      ,up.[PreferredName]
      ,up.[Email]
      ,up.[SID]
      ,up.[Manager]
      ,up.[SipAddress]
      ,up.[LastUpdate]
      ,up.[LastUserUpdate]
      ,up.[LastImported]
      ,up.[bDeleted]
      ,up.[DataSource]
      ,up.[MasterRecordID]
      ,up.[ProfileSubtypeID]
      ,up.[DSGuid]
      ,up.[DNId]
      ,up.[PictureUrl]
      ,up.[PartitionID]
      ,ui.[tp_SiteID]
      ,ui.[tp_ID]
      ,ui.[tp_DomainGroup]
      ,ui.[tp_SystemID]
      ,ui.[tp_Deleted]
      ,ui.[tp_SiteAdmin]
      ,ui.[tp_IsActive]
      ,ui.[tp_Login]
      ,ui.[tp_Title]
      ,ui.[tp_Email]
      ,ui.[tp_Notes]
      ,ui.[tp_Token]
      ,ui.[tp_ExternalToken]
      ,ui.[tp_ExternalTokenLastUpdated]
      ,ui.[tp_Locale]
      ,ui.[tp_CalendarType]
      ,ui.[tp_AdjustHijriDays]
      ,ui.[tp_TimeZone]
      ,ui.[tp_Time24]
      ,ui.[tp_AltCalendarType]
      ,ui.[tp_CalendarViewOptions]
      ,ui.[tp_WorkDays]
      ,ui.[tp_WorkDayStartHour]
      ,ui.[tp_WorkDayEndHour]
      ,ui.[tp_Mobile]
      ,ui.[tp_Flags]
  FROM [User Profile Service Application_ProfileDB_123205d384044ee8a656082df38f38b2].[dbo].[UserProfile_Full] as UP,
  [WSS_Content].[dbo].[UserInfo] UI
  where UP.SID = UI.tp_systemid

Con esto podremos obtener la información del mismo usuario de las dos tablas.  Tomar nota que el nombre de las bases de datos pueden variar dependiendo de su instalación. Tiene que averiguar el nombre de la base de datos de Perfiles de Usuario y la de Contenido del portal o donde esta la colección de sitios desea.

En este artículo se explicó sobre algunos aspectos no muy claros pero importantes sobre los usuarios como se registran en SharePoint especialmente cuando desarrollamos aplicaciones para SharePoint 2010 y en 2007 es lo mismo.

SharePoint4Fun!,

Juan Manuel Herrera O.

jueves, 27 de octubre de 2011

PowerShell para SharePoint 2010: Como sacar una copia de los web.config de las Aplicaciones Web de nuestra granja

He estado trabajando en la automatización del mantenimiento de los servidores de la granja de SharePoint 2010, y surgió la necesidad de sacar una copia de respaldo de estos archivos que guardan llaves de configuración, cadenas de conexión y otras configuraciones especificas de la instalación de SharePoint, por ello decidí utilizar PowerShell para SharePoint para automatizar esta tarea la cual la explico en las siguientes líneas:

get-ChildItem -recurse c:\inetpub\wwwroot\wss\virtualdirectories -include *.config

Esta línea de comando nos permitirá obtener todas las rutas de los archivos con extensión .config.   utilizamos el signo pipa para agregar otro comando relacionado con el anterior.

foreach{$ruta = get-itemproperty  $_.pspath;write-host $ruta }

La línea de arriba obtendrá la propiedad pspasth de cada línea que devuelva get-ChildItem y la desplegará (para ello utilizamos write-host) y el loop foreach para recorrer cada elemento obtenido de get-ChildItem.  Ahora vamos agregar luego de ruta a través de “;” otra línea para reemplazar el valor de la variable $ruta por el destino donde deseamos ubicar la copia de los archivos.

$destino = $ruta -replace "inetpub\\wwwroot\\wss\\virtualdirectories","migracion\reporte";

Coloqué doble \\ para que pudiese encontrar y reemplazar el valor. El valor de reemplazo no es necesario el doble \\.

$destino = $destino -replace "c:","f:";$directorio = $destino -replace "\web.config","";write-host $destino;

Ahora vamos a reemplazar el drive lógico por el del destino y por ultimo eliminar el nombre de archivo que no lo vamos a necesitar. Por último mandamos a imprimir el valor para asegurarnos que fue reemplazado con éxito.

new-item -itemType Directory -path $directorio -force ;copy-item $ruta -destination $destino –force;

Esta última línea va a crear la estructura de directorio deseado y luego copia el valor de la variable $ruta al destino que estuvimos preparando. 

La línea de comando completa es la siguiente:

get-ChildItem -recurse c:\inetpub\wwwroot\wss\virtualdirectories -include *.config | foreach{$ruta = get-itemproperty  $_.pspath;write-host $ruta ;$destino = $ruta -replace "inetpub\\wwwroot\\wss\\virtualdirectories", "migracion\reporte";$destino = $destino -replace "c:","f:";$directorio = $destino -replace "\web.config","";write-host $destino; new-item -itemType Directory -path $directorio -force ;copy-item $ruta -destination $destino -force }

En este artículo vimos como automatizar la copia de los achivos web.config de las aplicaciones web de los servidores front-end de SharePoint 2010.

SharePoint4Fun!,

Juan Manuel Herrera

viernes, 14 de octubre de 2011

Acceso de negado al intentar activar una característica de colección de sitios en SharePoint (Access Denied Activating Feature)

Esto sucede en ocasiones especialmente cuando se trata de una característica del temporizador de trabajos o Timer Job.

Como resolverlo es muy sencillo si el problema es de permisos.  Solo debe de activarse en línea de comando por medio de PowerShell para SharePoint 2010 y antes de ejecutar la ventana de línea de comando.  Hay un pequeño truco debe de sostener la tecla Shift de su computadora y presionar el clic derecho de su mouse y le mostrará un menú contextual el cual debe de seleccionar ejecutar con un usuario diferente para que levante una ventana de dialogo y le pida las credenciales.  Escriba las credenciales de el administrador de la granja (por estándar es SPFarm).

Luego ejecute la siguiente línea de comando:

enable-SPFeature –identity [folder feature name] –url [url web application]

Si no sabe el nombre del folder de la feature vaya a la siguiente dirección:

c:\program files\common files\microsoft shared\web server extensions\14\template\feature

Busque la feature y copie el nombre que aparece allí.

Eso es todo!.

SharePoint4Fun!,

Juan Manuel Herrera O.

domingo, 9 de octubre de 2011

Desarrolla tu propio mapa de Sitos con Client Object Model de SharePoint 2010

Antes de empezar a explicar el código una breve explicación sobre el Modelo de Objetos Cliente de SharePoint 2010 o Client OM.

El Modelo de Objetos Cliente de SharePoint 2010 nos permite construir soluciones que consumen la información contenida en de SharePoint dese una aplicación que no se ejecuta en el servidor de SharePoint.

Los objetos cliente son análogos a sus objetos Servidor. Por ejemplo la clase SPWeb del servidor tiene su correspondiente análogo clase Web.  La clase SPList del servidor tiene su correspondiente clase de lado del cliente List.

El Modelo de Objetos Cliente de SharePoint nos permiten concentrarnos en acceder el contenido de SharePoint y no preocuparnos de la comunicación y la forma de conectarnos al servicio de SharePoint.   Pero que es lo que realmente pasa atrás de bastidores, lo explicaremos con una la siguiente imagen:

image

La aplicación cliente utiliza el Client OM este a su vez hace un request en XML consumiendo un servicio de WCF llamado Client.svc y este se encarga de solicitar al Modelo de Objetos del Servidor que a su vez hace las consultas a la base de datos de contenido de SharePoint.  De esta forma no nos tenemos que preocupar sino entender su funcionamiento para trabajar con el modelo ya que no será lógico esperar por cada  instrucción que invocamos que nos devuelva un resultado sino mas bien como un conjunto de instrucciones sean enviadas de una vez y nos devuelvan el resultado final a la aplicación cliente que desarrollemos.  Para eso utilizaremos los métodos Load() y ExecuteQuery().

Lo siguiente será agregar al proyecto los ensamblados para utilizar el Client Object Model.  Si tienes aplicado el SP1 de Visual Studio 2010 tendrás una nueva pestaña de SharePoint para buscarlos fácilmente.  De lo contrario puedes encontrarlos en c:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI y los ensamblados son:

Microsoft.SharePoint.Client y Microsoft.SharePoint.Client.Runtime.

image

Ahora vamos a instanciar el contexto de la Aplicación Web a la que se hace referencia en urlAbsolutaColeccionSitios. 

ClientContext miContexto = new ClientContext(_urlAbsolutaColeccionSitios);


Luego instanciamos el objeto Web para luego obtener la lista

Web miWebsite = miContexto.Web;
Ahora vamos a a invocar el método ListarSubWeb el cual llamaremos recursivamente. Por medio del método Load() indicamos las propiedades que nos interesa obtener del objeto Web. Luego ejecutamos la consulta a través del método ExecuteQuery.
class Program
{
private static string _miSitioUrl ;
static void Main(string[] args)
{
Console.Write("Url de la coleccion de sitios:");
_miSitioUrl = Console.ReadLine();
ListarTodosLosSitios(_miSitioUrl);
}

private static void ListarTodosLosSitios(string _miSitioUrl)
{
ClientContext miContexto = new ClientContext(_miSitioUrl);
Web miWebsite = miContexto.Web;
IList<Sitio> misSitios = new List<Sitio>();

ListarSubWebs(miContexto, miWebsite, string.Empty);
miContexto.Load(miWebsite);
miContexto.ExecuteQuery();
Console.Read();
}

private static void ListarSubWebs(ClientContext miContexto, Web miWeb, string tituloSitioPadre)
{
miContexto.Load(miWeb, website => website.Webs, webSite => webSite.Id, webSite => webSite.Title, webSite => webSite.Description, webSite => webSite.LastItemModifiedDate, webSite => webSite.ServerRelativeUrl);
miContexto.ExecuteQuery();
Console.WriteLine("Sitio:\t{0}",miWeb.Title);
Console.WriteLine("Url:\t{0}", miWeb.ServerRelativeUrl);
Console.WriteLine("Ult-Cambio:\t{0}", miWeb.LastItemModifiedDate);
Console.WriteLine("Id:\t{0}", miWeb.LastItemModifiedDate);
Console.WriteLine("Padre:\t{0}", tituloSitioPadre);
Console.WriteLine("==============================");

for (int i = 0; i != miWeb.Webs.Count; i++)
{
tituloSitioPadre = miWeb.Title;
Web miSubWeb = miWeb.Webs[i];
ListarSubWebs(miContexto, miSubWeb, tituloSitioPadre);
}
}
}

El resultado será una aplicación de consola que liste todos los sitios de una colección de sitios, como se muestra a continuación:


image


image


Y eso es todo.  SharePoint4Fun!,


Juan Manuel Herrera


MVP SharePoint Server