jueves, 26 de septiembre de 2013

Comportamiento aparentemente errático de las Apps que muestran el contenido para algunos usuarios y a otros no en SharePoint 2013 On Premises o local con los mismos niveles de permisos

Este fue un error que me costo encontrar donde estaba el problema por lo que creo que es de utilidad compartir con ustedes la solución del problema.

Escenario:

SharePoint 2013 Idioma Español versión Standard con un hotfix de Marzo 2013 instalada.

Las apps fueron desarrolladas para para SharePoint online y nadie nos había reportado este problema pero luego de instalarla en un cliente en SharePoint 2013 On Premises, reportaron que algunos usuarios no veían el contenido de ninguna App.  Estas mostraban diferentes contenidos como imágenes, links, información de los empleados.

Probamos de todo, diferentes navegadores, diferentes computadoras, y siempre que el usuario ingresaba con su usuario y contraseña en diferentes computadoras y navegadores, n o les mostraba el contenido de ningún App.

Por lo que publique el problema en el foro de technet.microsoft.com relacionado con Apps en SharePoint 2013.

http://social.technet.microsoft.com/Forums/en-US/75b55f98-c4db-49e5-9001-4997ff5f5966/some-users-do-not-display-the-contents-of-app-part-and-do-not-show-any-errors-on-sharepoint-2013-on?prof=required

Allí me aconsejaron que descargará fiddler y revisará el tráfico entre el navegador y el servidor.  Y el error que reportaba fiddler al intentar autenticarse para acceder los archivos JS de no autorización.

Bueno luego de analizar la información y realizar muchas pruebas hayamos el problema.  Los usuarios que tenían acceso limitado al Internet eran los que las Apps no podían renderizar el contenido a pesar que tenían acceso a las listas e imágenes del App Web y  el problema se debía a que todas las Apps tenían una referencia a la librería microsoft.ajax.js en la nube y esto evitaba que el contenido lo pudiesen ver.  La línea de código era la siguiente:

<script type="text/javascript" src="https://ajax.aspnetcdn.com/ajax/4.0/1/MicrosoftAjax.js">

La Solución:

Bueno se descargó el archivo microsoftAjax.js en una carpeta de scripts dentro del App y se hizo referencia de esta.  Se publicó de nuevo cada App y el problema se resolvió.

Apps4Fun!,

Juan Manuel Herrera Ocheita

domingo, 15 de septiembre de 2013

Cómo subir masivamente fotografías a los perfiles de usuario de SharePoint 2013

Hay algunos artículos en la red sobre este tema que me ayudaron a realizar el script en PowerShell para subir de forma masiva las fotografías de los usuarios a los perfiles de usuario de SharePoint 2013.

Gracias a Toni Frankola por su artículo que me sirvicio de base para crear script personalizado para subir las fotografías de los usuarios a SharePoint 2013.  Abajo el artículo base:

http://www.sharepointusecases.com/index.php/2012/12/use-case-automatically-importing-user-profile-pictures-to-sharepoint-2013-and-2010/

El Problema:

En este escenario se cuenta con un archivo de Excel que tiene asociado un código de empleado con la ubicación de la fotografía del usuario y no con la cuenta de dominio.  Por lo que el procedimiento que se realizó para este caso en particular fue el siguiente:

1) Se actualizó el código de empleado en el Directorio Activo y para ello se utilizó un campo disponible en el Directorio como lo es la ciudad o City.

http://technet.microsoft.com/en-us/library/hh524307.aspx

3) En el servicio de aplicación perfiles de usuario en SharePoint se mapeo esta propiedad del directorio activo con una propiedad personalizada en las propiedades del perfil de usuario en SharePoint

4) Se realizó una sincronización completa de los usuarios del directorio activo en SharePoint

5) Se validó que el campo se había actualizado

6) Se creó el script que hiciera lo siguiente:

1) Lea un archivo CSV que obtuviera el código de empleado y la ruta de la imagen

2) Comparará con cada uno de los perfiles de los usuarios importados en SharePoint, el código de empleado del archivo con el código de empleado del perfil de usuario para obtener su cuenta de dominio

3) Que cargará la fotografía en memoria

4) Que la subiera a la biblioteca de imágenes de los perfiles de usuario asociada a cada perfil.

 

El Mapeo de propiedad en el UPA

En el servicio de Aplicación User Profiles o Perfiles de Usuario seleccionamos la opción “administrar propiedades del usuario” o “Manage User Profile Properties”

image

Ahora seleccionamos “Nueva Propiedad” o “New Property”

image

Llenamos el campo Nombre y nombre para desplegar, seleccionamos el tipo de dato que para este ejemplo es una cadena de caracteres o String.

image

Luego indicamos que la configuración de privacidad predeterminada es todos para que nos permita seleccionar la opción “Replicable”, luego seleccionamos la opción de configuración de edición de no permitirlo y luego las opciones de presentación que nos permita mostrar la propiedad en la sección de propiedades del perfil de usuario.

 

image

En esta área de la página de edición de la propiedad del perfil es donde se mapea con el AD, para ello seleccionamos la conexión de datos de origen hacia el AD Previamente creada y luego seleccionamos el atributo del Directorio Activo que para City es L minúscula según la tabla de abajo y seleccionamos para la dirección de actualización en Importar.

La Estructura del archivo CSV

CODIGO , FOTOGRAFIA

1009, C:\FOTOS\JUAN MARTINEZ.JPG

1010, C:\FOTOS\ROBERTO AZURDIA.JPG

1020, C:\FOTOS\ANA GONZALEZ.JPG

El Script:

#  DEFINIMOS LAS VARIABLES A UTILIZAR

$InvFile = "ListadoUsuarioYRutas.csv"

# LA DIRECCION DE MY SITES O MIS SITIOS ESTO VARIA DE INSTALACIÓN EN INSTALACIÓN DE SHAREPOINT CONSULTE AL ADMINISTRADOR O REVISE EL CENTRAL ADMINISTRACION PARA DESCUBRIR ESTA URL


$farmUrl =  http://misitio.midominio/

$site=Get-SPSite $farmUrl
$web=$site.RootWeb
$serverContext=[Microsoft.Office.Server.ServerContext]::GetContext($site)

# SE CARGA EN MEMORIA EL USER PROFILE MANAGER PARA MAS TARDE OBTENER LOS PERFILES DE LOS USUARIOS EN SHAREPOINT
$upm=New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($serverContext)


# SE CARGA EN MEMORIA EL ARCHIVO CSV

$FileExists = (Test-Path $InvFile -PathType Leaf)
if ($FileExists) {
   "Loading $InvFile for processing..."
   $tblData = Import-CSV $InvFile
} else {
   "$InvFile not found - stopping import!"
   exit
}

# Leyendo Archivo de Codigos y Fotos ..."
$contador = 0
$perfiles = $upm.GetEnumerator()
foreach ($row in $tblData)
{
   $picture = $row."FOTOGRAFIA"
   if( $picture)
   {

    # LEE CADA FILA Y CADA COLUMNA DEL ARCHIVO CSV Y OBTIENE LE CODIGO Y LA RUTA DE LA IMAGEN
    $codigoLista = $row."CODIGO".ToString()
    $PicturePath = "e:\" + $row."FOTOGRAFIA".ToString()
    $perfiles = $upm.GetEnumerator()
    # SE OBTIENE LOS PERFILES DE USUARIO DEL USER PROFILE MANGER 
    foreach($up in $perfiles)
    {

        $codigoUPA = $up["CodigoEmpleado"]

#  SI ENCONTRO UN CODIGO DE EMPLEADO EN EL PERFIL
        if ($codigoUPA)
        {

          # SE COMPARA SI EL CODIGO DE EMPLEADO DEL PERFIL DE USUARIO ES IGUAL  A DEL ARCHIVO
         if ($codigoUPA -eq $codigoLista)
         {
            $FileExists = (Test-Path $PicturePath -PathType Leaf)

            # SE VALIDA SI EL ARCHIVO IMAGEN EXISTE EN LA RUTA INDICADA
            if ($FileExists) {
                $account = $up.AccountName
                $contador = $contador + 1
               write-host $contador " - " $account " - " $codigoUPA " - " $PicturePath

         # SE EJECUTA UN METODO SET-SPPROFILEPICTURES PARA SUBIR LA IMAGEN A SHAREPOINT

    # USER PHOTOS ES LA BIBLIOTECA DE MI SITES DONDE SE GUARDA LAS IMAGENES DE LOS PERFILES DE USUARIO

         set-SPProfilePictures -weburl $farmUrl -picLibraryName "User Photos"  -localFolderPath $PicturePath -accountName $account -up $up
             }
         }
        }
    }
 
    }
}
Write-Host "End of file"

# METODO PARA SUBIR LA IMAGEN DE SHAREPOINT

function set-SPProfilePictures([string]$webUrl, [string]$picLibraryName, [string]$localFolderPath, [string]$accountName, $up)
{
    # CARGA EL SITIO WEB DONDE ESTA LA BIBLIOTECA DE IMAGENES

    $web = Get-SPWeb $webUrl

    # $picLibraryName = “USER PHOTOS”
    $picFolder = $web.Folders[$picLibraryName]
    if(!$picFolder)
    {
        Write-Host "Picture Library Folder not found"
        return
     }
 
        $username = [IO.Path]::GetFileNameWithoutExtension($localFolderPath);
        $fileName = [IO.Path]::GetFileName($localFolderPath);
 
        #SE CARGA EN MEMORIA LA IMAGEN

        $fileStream = ([System.IO.FileInfo] ($localFolderPath)).OpenRead()
        $contents = new-object byte[] $fileStream.Length
        $fileStream.Read($contents, 0, [int]$fileStream.Length);
        $fileStream.Close();
 
        write-host "Copying" $username "to" $picLibraryName "in" $web.Title "..."
 
        #SE ADICIONAL LA IMAGEN EN MEMORIA EN LA BIBLIOTECA USER PHOTOS
        $spFile = $picFolder.Files.Add($picFolder.Url + "/" + $fileName, $contents, $true)
        $spItem = $spFile.Item
 
        # SE OBTIENE LA PROPIEDAD DEL USUARIO QUE GUARDA EL URL DONDE ESTA ALMACENADA LA IMAGEN O FOTOGRAFIA
        $picturePropertyName = GetProfilePropertyName -UserProfileManager $upm -PropertyName "PictureUrl";
 
        if($up -ne $null)
        {
            if (-not [System.String]::IsNullOrEmpty($picturePropertyName))
            {
                $PortraitUrl = CombineUrls -baseUrl $spFile.Web.Url -relUrl $spFile.ServerRelativeUrl;
                Write-Host $PortraitUrl

                # SE ACTUALIZA
                $up.get_Item($picturePropertyName).Value = $PortraitUrl;
                $up.Commit();
            }
        }
 
   
 
    Write-Host "Updating User Profile Photo Store..." -foregroundcolor yellow

    # FINALMENTE ACTUALIZA LA FOTO. MY IMPORTANTE EL PARAMETRO CreateThumbnailsForImportedPhotos SI NO SE INCLUYE A UN ERROR QUE NO PUEDE SER SUBIDA LA IMAGEN SIGUIENTE
    Update-SPProfilePhotoStore -CreateThumbnailsForImportedPhotos 1  –MySiteHostLocation $webUrl
    Write-Host "Done" -foregroundcolor green
}

Y eso es todo, demora la actualización, le sugiero ejecute un debug con el editor de power shell para comprender mejor lo que hace y comentarle que esto demora y esta sujeto a errores sino la imagen no esta correctamente copiada o bien tiene caracteres especiales como tíldes y eñes.  Por lo demás funciona muy bien, pero demora la actualización, al final es mejor que subirla manualmente y pueda que el script lo mejore y optimice para su conveniencia pero depende de usted.  Abajo lo dejo con el script completo que utilice para actualizar las fotografías.

Scripting4Fun!,

Juan Manuel Herrera Ocheita

#--- PRINCIPIO DEL SCRIPT -----

if((Get-PSSnapin | Where {$_.Name -eq "Microsoft.SharePoint.PowerShell"}) -eq $null) {
    Add-PSSnapin Microsoft.SharePoint.PowerShell;
}
function ToSimpleString([string]$value, [bool]$trim = $true, [bool]$removeSpaces = $true,

[bool]$toLower = $true)
{
    if ($value -eq $null) { return [System.String]::Empty; }
 
    if ($trim)
    {
        $value = $value.Trim();
    }
 
    if ($removeSpaces)
    {
        $value = $value.Replace(" ", "");
    }
 
    if ($toLower)
    {
        $value = $value.ToLower();
    }
 
    return $value;
}

function CombineUrls([string]$baseUrl, [string]$relUrl)
{
    [System.Uri]$base = New-Object System.Uri($baseUrl, [System.UriKind]::Absolute);
    [System.Uri]$rel = New-Object System.Uri($relUrl, [System.UriKind]::Relative);
 
    return (New-Object System.Uri($base, $rel)).ToString();
}

function GetProfilePropertyName($userProfileManager, $propertyName)
{
    $propertyName = (ToSimpleString -value $propertyName);
    $propertyName = $propertyName.Replace("sps-", "");
 
    foreach($prop in $userProfileManager.Properties)
    {
        [string]$n = (ToSimpleString -value $prop.DisplayName);
        $n = $n.Replace("sps-", "");
        if ($propertyName -eq $n) { return $prop.Name.ToString(); }
 
        $n = (ToSimpleString -value $prop.Name);
        $n = $n.Replace("sps-", "");
        if ($propertyName -eq $n) { return $prop.Name.ToString(); }
    }
 
    return $null;
}
 
function set-SPProfilePictures([string]$webUrl, [string]$picLibraryName, [string]$localFolderPath, [string]$accountName, $up)
{
    #Get web and picture library folder that will store the pictures
    $web = Get-SPWeb $webUrl
    $picFolder = $web.Folders[$picLibraryName]
    if(!$picFolder)
    {
        Write-Host "Picture Library Folder not found"
        return
     }
 
        $username = [IO.Path]::GetFileNameWithoutExtension($localFolderPath);
        $fileName = [IO.Path]::GetFileName($localFolderPath);
 
        #Create file stream object from file
        $fileStream = ([System.IO.FileInfo] ($localFolderPath)).OpenRead()
        $contents = new-object byte[] $fileStream.Length
        $fileStream.Read($contents, 0, [int]$fileStream.Length);
        $fileStream.Close();
 
        write-host "Copying" $username "to" $picLibraryName "in" $web.Title "..."
 
        #Add file
        $spFile = $picFolder.Files.Add($picFolder.Url + "/" + $fileName, $contents, $true)
        $spItem = $spFile.Item
 
       
        $picturePropertyName = GetProfilePropertyName -UserProfileManager $upm -PropertyName "PictureUrl";
 
        if($up -ne $null)
        {
            if (-not [System.String]::IsNullOrEmpty($picturePropertyName))
            {
                $PortraitUrl = CombineUrls -baseUrl $spFile.Web.Url -relUrl $spFile.ServerRelativeUrl;
                Write-Host $PortraitUrl
                $up.get_Item($picturePropertyName).Value = $PortraitUrl;
                $up.Commit();
            }
        }
 
   
 
    Write-Host "Updating User Profile Photo Store..." -foregroundcolor yellow
    Update-SPProfilePhotoStore –MySiteHostLocation $webUrl
    Write-Host "Done" -foregroundcolor green
}

$InvFile = "arhivoCodigosyRutas.csv"
$farmUrl =  http://misitio.sudominio/

$site=Get-SPSite $farmUrl
$web=$site.RootWeb
$serverContext=[Microsoft.Office.Server.ServerContext]::GetContext($site)
$upm=New-Object Microsoft.Office.Server.UserProfiles.UserProfileManager($serverContext)


# Get Data from Inventory CSV File
$FileExists = (Test-Path $InvFile -PathType Leaf)
if ($FileExists) {
   "Loading $InvFile for processing..."
   $tblData = Import-CSV $InvFile
} else {
   "$InvFile not found - stopping import!"
   exit
}

# Loop through file  compare an get an account

"Uploading data to SharePoint...."
$contador = 0
$perfiles = $upm.GetEnumerator()
foreach ($row in $tblData)
{
   $picture = $row."FOTOGRAFIA"
   if( $picture)
   {
    $codigoLista = $row."CODIGO".ToString()
    $PicturePath = "e:\" + $row."FOTOGRAFIA".ToString()
    $perfiles = $upm.GetEnumerator()
 
    foreach($up in $perfiles)
    {
        $codigoUPA = $up["Codigo"]
        if ($codigoUPA)
        {
         if ($codigoUPA -eq $codigoLista)
         {
            $FileExists = (Test-Path $PicturePath -PathType Leaf)
            if ($FileExists) {
                $account = $up.AccountName
                $contador = $contador + 1
                write-host $contador " - " $account " - " $codigoUPA " - " $PicturePath
                set-SPProfilePictures -weburl $farmUrl -picLibraryName "User Photos"  -localFolderPath $PicturePath -accountName $account -up $up
             }
         }
        }
    }
 
    }
}
#User%20Photos
Write-Host "End of file"
# -- FINAL DEL SCRIPT ----

 

Tabla de propiedades del Directorio Activo

Active Directory user attribute
Microsoft.AD.User property
physicaldeliveryofficename Office
displayname displayname
company Company
employeeid Employeeid
department Department
telephonenumber BusinessPhone
homePhone HomePhone
facsimileTelephoneNumber Fax
mobile Mobile
pager Pager
mail Email
givenname FirstName
initials Initials
sn LastName
distinguishedname Distinguishedname
title Title
manager manager
samaccountname UserName
l City
StreetAddress StreetAddress
st State
postalCode Zip
co Country
localeID Locale
msRTCSIP-PrimaryUserAddress SipAddress
objectSid SID
Domain Domain

Referencia oficial y completa en http://technet.microsoft.com/en-us/library/hh524307.aspx