jueves, 17 de noviembre de 2016

Implementar multiidioma en una aplicación ASP.NET con Angular translate.

Cuando nuestros pacientes aprendían idiomas antiguamente a veces se veían apurados al cambiar de uno a otro y se notaba que tenían que refrescar el conocimiento.

Ahora se les exige que sean capaces de cambiar de un idioma a otro sin que se note.

Este post requiere conocimientos de Angular al menos a nivel básico, para adquirirlos basta con leer unos cuantos capítulos de esta web:

Primeros pasos con Angular

Al menos hay que leer lo suficiente como para tener claro lo que son los controladores, los módulos, la aplicación angular, los bindings a etiquetas, las directivas y los scopes, son conceptos realmente sencillos de comprender y que llevan mucho menos tiempo del que parece si ya se domina JavaScript, cosa que se da por sentada, por tanto se anima al lector a empaparse de ello para comprender mejor lo que se explica a continuación.

El otro modo, es el de toda la vida, el de copy/paste como un loro, pero bueno, allá cada cual.

Lo que se presenta en este artículo es una solución que quizás necesita retoques por supuesto, pero que funciona, que es lo importante.

En nuestro caso, el multiidioma lo implementamos en la master de la aplicación ASP.NET para que tenga efecto sobre cualquier página que pongamos incrustada.

Lo primero es insertar las referencias a angular y al js en el cual estará el código del programador (translateApp.js en este caso), es recomendable subir las referencias a una carpeta local del proyecto para evitar, si nuestras referencias son remotas, que pudieran afectar las posibles actualizaciones sin previo aviso:

    <script src="Scripts/angular.min.js"></script>
    <script src="Scripts/angular-translate.min.js"></script>
    <script src="Scripts/translateApp.js"></script>

Dentro de un div en la master que englobe a los asp:Contents se colocará un control de selección para el idioma, si se requiere imágenes u otra cosa, se hará lo equivalente para otro control:

       <div class="container body-content" ng-app="at" ng-controller="Ctrl">
            <div class="ng-scope">
                 <select ng-model="selectedLanguage" ng-change="selectLanguage()" ng-options="x for (x, y) in languages">
                 </select>
            </div>
            <asp:ContentPlaceHolder ID="MainContent" runat="server">
            </asp:ContentPlaceHolder>
        </div>

Explicando rápidamente el tag select de arriba, el selector tiene unas etiquetas de Angular para controlar su enlazado a datos ng-model, el evento al cambiar su valor, ng-change, que va a una función llamada selectLanguage() y una expresión para llenar sus opciones en ng-options.

Por supuesto, el div que lo engloba es el que contiene los tags de Angular para referenciar a la app de Angular y al controlador que contendrá el código para manejar todo el contenido dentro de ese div.

Ahora exponemos el contenido del archivo translateApp.js, donde se hace uso de la api translate de Angular:


var app = angular.module('at', ['pascalprecht.translate']);

app.config(function ($translateProvider) {
    $translateProvider.translations('English', {
        HelloLiteral: 'Hello',
        FirstParagraphLiteral: 'This is a paragraph.'
    });
    $translateProvider.translations('German', {
        HelloLiteral: 'Hallo',
        FirstParagraphLiteral: 'Dies ist ein Paragraph.'
    });
    $translateProvider.preferredLanguage('English');
});

app.controller('Ctrl', function ($scope, $translate) {
    $scope.languages = {
        English: 'English',
        German: 'German'
    };
    $scope.selectedLanguage = $scope.languages.English;
    $scope.selectLanguage = function () {
        $translate.use($scope.selectedLanguage);
    }
});

Tanto la configuración como el controlador de la app se puede encontrar en su estado básico en la web de angular translate, de donde se ha sacado el código, lo único reseñable en este caso es haber adaptado el código original, el cual venía preparado para un botón, a un control select.

Además se ha añadido la inicialización del select al preferredLanguage, en este caso mediante código lo cual no es lo más deseable, pero con algunas modificaciones puede detectarse automáticamente.

Dentro de cada función translations se incluirá un idioma donde se abarcan todas las etiquetas de los controles que hay en la interfaz para cada texto que llevará en ese idioma.

Por simple organización y comodidad, quizás sería deseable poner las etiquetas y los literales para cada idioma en archivos json separados por idioma, esto a gusto del consumidor.

Por último, ponemos los controles allá donde se desee en las páginas de la aplicación que hereden de la master, si no heredan de la master, se deberá declarar otro controlador para esa página.

En este caso no ha sido necesario, a continuación se presenta un ejemplo de controles en una página que hereda de la master:

<div>
    <h2 translate="HelloLiteral">Hello</h2>
    <p translate="FirstParagraphLiteral">This is a paragraph.</p>
</div>

La etiqueta translate que Angular translate define, servirá para enlazar cada control con su literal.

El resultado final:



miércoles, 16 de noviembre de 2016

Devolver Json desde tus Controllers en una aplicación ASP.NET.

Una buena forma de conseguir que los métodos de tus controllers devuelvan en Json en vez de xml por defecto es poner la siguiente instrucción de formateo para la app.

Ponla en el Global.asax si tu app no es MVC o en el WebApiConfig si es MVC.

GlobalConfiguration.Configuration.Formatters.JsonFormatter.MediaTypeMappings
.Add(new RequestHeaderMapping("Accept",
                              "text/html",
                              StringComparison.InvariantCultureIgnoreCase,
                              true,
                              "application/json"));


viernes, 14 de octubre de 2016

El servicio de Sincronización de Perfiles se queda pillado en "iniciando"

Esto viene a raíz de querer hacer una sincronización de perfiles de usuarios pero encontrarnos con que el servicio de sincronización no arrancaba, se quedaba pillado en "iniciando" o "starting".

Hagamos una traducción al inglés:

User Profile Service Stuck on Starting.

Cuando en la Central de Administración vamos a "servicios en el servidor" o "services on server" y vemos que el servicio de sincronización no termina nunca de arrancar, podemos pasar este script para desaprovisionarlo:

Add-PSSnapin Microsoft.SharePoint.Powershell
$TypeName = “User Profile Synchronization Service”
$ServerName = “SERVERNAME” #Replace with your server name where the service is stuck on Starting
$Serviceinstance = Get-SPServiceInstance | where-object {$_.TypeName -eq $TypeName -and $_.Server.Address -eq $ServerName}
$Serviceinstance.Unprovision()



Después, volvemos a servicios en el servidor y lo inicialmos de nuevo para que aprovisione bien.

Se han creado alertas correctamente, pero estos usuarios no recibirán notificaciones por correo electrónico hasta que se proporcionen direcciones válidas.


A nosotros el paciente nos alertó en inglés, algo de que no escuchaba bien, venía con serios problemas en el tímpano:

Alerts have been created successfully but these users will not receive notifications until valid e-mail or mobile addresses have been provided.

Pero para el caso es lo mismo, al intentar crear una alerta a un usuario concreto en un elemento de lista o biblioteca, aparece el siguiente mensaje:



Solucionamos el problema yendo al servicio de perfiles de usuario a realizar una sincronización, pero vimos que la sincronización de perfiles de usuario no estaba aprovisionada, para resolver esto vamos a servicios en el servidor e iniciamos el servicio de sincronización de perfiles.



Ahora, vamos al servicio de perfiles de usuario y una vez haya terminado el aprovisionamiento, haremos una sincronización COMPLETA o full sync, en la imagen a continuación, el aprovisionamiento aún no ha terminado.
Una vez terminada la sincronización, podemos observar si la cuenta tiene o no correo electrónico, hacemos clic en Manage User Profiles o Administrar Perfiles de Usuario y buscamos, como podemos observar, no tenemos correo, en otro caso, podríamos dar por resuelto el problema:


Esto se debe a que la cuenta no tiene en Directorio Activo un email asignado o bien a que la cuenta es de Office 365 y no cuenta con sincronización on-premise, no hay problema, el servicio de perfiles permite que nosotros asignemos un correo electrónico a la cuenta, para ello hacemos clic en la cuenta en la columna Account name (en la imagen de arriba) y elegimos en el combo la opción editar, después asignamos una dirección de correo electrónico y guardamos y cerramos (botón arriba y abajo de la página):



Por último, volvemos a hacer una sincronización completa, por alguna extraña razón que no nos hemos parado a destripar (aún), no sirve aparentemente las sincronizaciones incrementales.

Si todo va bien, podremos recibir alertas en esta cuenta sin problemas.

viernes, 7 de octubre de 2016

SharePoint Framework parte (I). Crea tu entorno de desarrollo.


Cambiamos el chip y nos reciclamos, de nuevo, como otras tantas veces. No vaya a pasar que como siempre los de SharePoint siempre estemos a la última. Pues esta vez no, no nos vamos a quedar descolgados y si todos los demás dicen que ahora lo que mola es que todo sea código de cliente, pues sea, ¡tirémonos al volcán!.

Para ello nace el concepto de SharePoint Framework, que ahora es el nuevo paradigma de programación que pretende ser algo más que una alternativa a las apps, cuyo aparente rechazo generalizado hace que haya que reinventar la forma de programar para SharePoint.

Aclaro, esto no quiere decir para nada que las apps queden obsoletas ni se den de lado, se pueden seguir utilizando y están perfectamente soportadas. El motivo de no utilizar apps es que existe una ambiguedad con respecto a las apps de Azure que cubren, en esencia, lo que permiten hacer las de SharePoint, y que son preferibles a las de SharePoint a la hora de desarrollar apps, aunque este no es el tema que nos ocupa.

Esta tecnología está orientada a SharePoint Online, lo cual no quiere decir que no pueda implementarse on-premise, pero implica que debemos adaptar nuestra infraestructura on-premise al soporte de apps mediante tenants, que nos permita crear sitios de desarrollador y catálogo de aplicaciones.

Si necesitamos soporte para apps es porque una aplicación desarrollada con SharePoint Framework no deja de ser finalmente un fichero .app que subiremos a nuestra colección de sitios de catálogo de aplicaciones.

SharePoint Framework no es algo integrado que podamos instalarnos sin más, es un conjunto de tecnologías funcionando todas en paz y armonía para generar aplicaciones basadas en código de cliente.

Sin extendernos más, pasamos a explicar lo que necesitamos y cómo instalarlo en un entorno de PC y sin utilizar Visual Studio según nuestra experiencia.

Necesitaremos preparar nuestro entorno con los siguientes productos/herramientas:

Un editor de código, una consola de comandos, NPM, Node.js, Yeoman y Gulp, Yeoman SharePoint Generator, algunas librerías de JavaScript.

1. Editores de código, donde desarrollarás tus proyectos.
No más Visual Studio, al menos en cuanto a estos temas, necesitamos editores ligeros y nos sirve cualquiera de propósito general, por ejemplo Visual Studio Code:

Visual Studio Code



Puedes usar otros como:

Sublime
Atom
Webstorm

2. Una consola de comandos.
Aunque utilizar cmd en Windows nos sirve perfectamente, también podemos usar PowerShell o mejor aún, emuladores de consola como cmder que nos centraliza todo, el que nostros usamos para estos temas es Cmder:

Cmder

3. Node.js
Instalar Node.js.
Node es una entorno de ejecución de código abierto para JavaScript, cuyo propósito es desarrollar aplicaciones del lado del servidor pero con código de cliente, JavaScript. Está muy sinergizado con NPM (un instalador de paquetes cuyo equivalente sería NuGet que después veremos) y con ejecutores de tareas como gulp.
En Microsoftiano, diríamos que se trata de un IIS que incluye herramientas para simplificar la tarea del desarrollo.

Descargar Node.js

Siguiente, siguiente, siguiente...

Node se instalará en modo general para que cuando utilicemos nuestra consola de comandos favorita no tengamos nada más que teclear llamadas a node o npm sin acceder a ningún directorio específico.

4. NPM.
El desarrollo de aplicaciones de cliente para SharePoint recurrirá frecuentemente a NPM para descargar paquetes y depdencias que utilizaremos en nuestros desarrollos, como hacíamos con Visual Studio y NuGet.

Si todo va bien, después de instalar Node.js, también habrá sido instalado NPM, en cualquier caso, indicamos la forma de instalar NPM desde nuestra consola de comandos favorita. Nosotros utilizamos Cmder.

npm -g install npm@3




5. Instalar las windows-build-tools
Que instalarán Visual C++ Build Tools 2015, que servirán para poder compilar algunos módulos que vienen de forma nativa, también instalará Pyton 2.7 y configurarán tu entorno apropiadamente.

npm install --global --production windows-build-tools.




6. Yeoman y Gulp.
Yeoman es una forma de acceder a un conjunto de plantillas para proyectos de código de cliente, de forma que permita generar de forma rápida la estructura de los mismos, asímismo provee de herramientas y buenas prácticas que facilitarán al desarrollador la vida cuando utilice sus editores, como facilitar código repetitivo y sitios de pruebas (workbenchs en local para que no sea necesario probar nuestras apps en SharePoint Online directamente). Gulp realiza otras tareas como la minificación del código, compresión de imágenes, validación de sintaxis, etc... de forma que no sea necesario hacer estas cosas manualmente.

Yeoman

Gulp

El siguiente comando instalará ambos:

npm i -g yo gulp




7. Yeoman SharePoint Generator.
Esto instalará la plantilla Yeoman que nos creará fácilmente un proyecto de webpart de SharePoint ahorrándonos una enorme tarea manual:

npm i -g @microsoft/generator-sharepoint




8. Librerías de JavaScript.
Evidentemente, no pensabas lanzarte sin más aunque ya podrías, viene bien, es obligado tener librerías robustas que te permitan alcanzar una potencia máxima con tu código.

Utilizaremos Yeoman para instalar las librerías y ya de paso tener un entorno preparado para generar proyectos enfocados a su uso.

Librerías en general para JavaScript, puedes elegir entre React, Knockout o Angular.

npm install -g generator-react-webpack

npm install -g grunt-cli bower yo generator-karma generator-angular

npm install generator-ko

Aunque para acceder a SharePoint recomendamos el uso de REST API, existen conectores de SharePoint, incluída la API JSOM de SharePoint.

npm install node-sharepoint-rest

npm install sharepoint-api

npm i sharepointconnector
nota: sharepointconnector sin probar.

Si todo ha ido bien, ya tenemos nuestro entorno preparado para crear nuestra primera webpart realizada enteramente en código de cliente según sugieren los nuevos tiempos.

Pero para construir una webpart, esperad al siguiente post.

jueves, 29 de septiembre de 2016

Colección de sitios en read-only no se puede desbloquear, en estado "Lock"

Hoy vino un paciente con depresión que no avanzaba en su estado mental y se obsesionaba con lo mismo: estar bloqueado.



Si una colección de sitios de SharePoint aparece en Read-Only y no hay forma de desbloquearla ni mediante la Central de Administración en "Application Management" -> "Site Collections" -> "Site Collection Quotas and Locks" (donde todos los controles aparecen inactivos) ni mediante un comando de PowerShell Set-SPSite con opción "Unlock", existen al menos un par de soluciones eficaces:

1.)
$admin =  new-object Microsoft.SharePoint.Administration.
SPSiteAdministration('http://sitiobloqueado')
$admin.ClearMaintenanceMode()
$site.MaintenanceMode


2.) Utilizando reflection para evitar crear objetos que quizás no estén disponibles en 1.)
$site = Get-SPSite http://urltofreakinlockedsite/
$site.GetType().GetProperty("MaintenanceMode").GetSetMethod($true).Invoke($site, @($false))

miércoles, 21 de septiembre de 2016

SharePoint Designer: Pasar un Workflow de un sitio web a otro.


En este caso perdimos a un paciente.

Bueno, no lo perdimos, digamos que vio la ventana abierta de la habitación del hospital, y como estaba en un bajo se escapó por ella y no lo volvimos a ver.

Menos mal que teníamos a su hermano gemelo desplegado en otro sitio y pudimos hacer un clon de él.

Para pasar un workflow hecho con SharePoint Designer de un sitio a otro haremos lo siguiente:

1. En el sitio donde está el workflow hacemos clic en exportar a Visio, elegimos una ubicación.

2. Vamos a la carpeta donde lo hemos guardado y encontraremos un archivo de tipo vwi.

3. Cambiamos la extensión de vwi a zip y descomprimimos los archivos.

4. Eliminamos el archivo de tipo .xoml.wfconfig.xml y comprimimos los restantes a zip, ni a rar ni a cualquier otra cosa, debe ser a zip.

5. Volvemos a SharePoint Designer y en el sitio donde queremos rescatar el workflow hacemos clic en importar desde Visio.

6. Seleccionamos el archivo anterior y configuramos como deseemos, en nuestro caso era un workflow reutilizable asociado a un tipo de contenido, pero puede ser de lista en otros casos.

¡A partir de ahora vigilaremos mejor las ventanas abiertas para que nuestro paciente no vuelva a tirarse!

viernes, 2 de septiembre de 2016

Depurar un sitio de SharePoint Online si no es de desarrollador. Sideloading of apps is not enabled in this site.

A veces pasa, tienes que hacer las cosas sin ver y guiándote más por la intuición porque el equipo de tu hospital está anticuado o incompleto.

Ya vale!

En este caso teníamos un sitio de SharePoint Online de tipo Project en el cual teníamos que depurar una app desarrollada en Visual Studio, de aquí inferimos que no podemos depurar nuestro desarrollo en un sitio para Project por el claro motivo de que el sitio de desarrollador no puede serlo a la vez de Project.

Ya suponemos querido lector que has configurado en Azure tu ServiceBus y has puesto su cadena de conexión correctamente en las propiedades del proyecto de SharePoint App, pues bien, al darle a F5 para iniciar tu depuración te aparece el siguiente mensaje como error:

Error en el paso de implementación 'Instalar aplicación para SharePoint': La instalación de prueba de aplicaciones no está habilitada en este sitio.

O bien en inglés, no marginemos a la lengua de Shakespeare:

Sideloading of apps is not enabled in this site.

Lo cual quiere decir que este sitio no es de desarrollador, si no que está pensado para hacer funciones de otro tipo de entorno.

¿Cómo actuar en este caso que tenemos que utilizar un sitio que no es desarrollador para desarrollar?

Habilitando la característica Sideloading en el sitio.

Primero, si no lo has hecho ya, instala la herramienta de PowerShell para Azure con sus módulos.

Después abre un PowerShell ISE o una ventana de PowerShell Azure, a gusto del consumidor, y ejecuta el siguiente script:

$programFiles = [environment]::getfolderpath("programfiles")
add-type -Path $programFiles'\SharePoint Online Management Shell\Microsoft.Online.SharePoint.PowerShell\Microsoft.SharePoint.Client.dll'
Write-Host 'To enable SharePoint app sideLoading, enter Site Url, username and password'
$siteurl = Read-Host 'Site Url'
$username = Read-Host "User Name"
$password = Read-Host -AsSecureString 'Password'
$outfilepath = $siteurl -replace ':', '_' -replace '/', '_'
try
{
    [Microsoft.SharePoint.Client.ClientContext]$cc = New-Object Microsoft.SharePoint.Client.ClientContext($siteurl)
    [Microsoft.SharePoint.Client.SharePointOnlineCredentials]$spocreds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($username, $password)
    $cc.Credentials = $spocreds
    Write-Host -ForegroundColor Yellow 'SideLoading feature is not enabled on the site:' $siteurl
    $site = $cc.Site;
    $sideLoadingGuid = new-object System.Guid "AE3A1339-61F5-4f8f-81A7-ABD2DA956A7D"
    $site.Features.Add($sideLoadingGuid, $true, [Microsoft.SharePoint.Client.FeatureDefinitionScope]::None);
    $cc.ExecuteQuery();
    Write-Host -ForegroundColor Green 'SideLoading feature enabled on site' $siteurl
}
catch
{
    Write-Host -ForegroundColor Red 'Error encountered when trying to enable SideLoading feature' $siteurl, ':' $Error[0].ToString();
}
En nuestro caso, la ruta de la dll Microsoft.SharePoint.Client estaba en C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll

Ahora prueba tu código con Visual Studio, deberías poder depurar sin que te de el anterior error, basta un F5 teniendo como proyecto de inicio el proyecto de SharePoint con el ServiceBus configurado correctamente.

Cuando termines de depurar y quieras dejar todo como estaba, sustituye la línea $site.Features.Add($sideLoadingGuid, $true, [Microsoft.SharePoint.Client.FeatureDefinitionScope]::None);
por
 $site.Features.Remove($sideLoadingGuid, $true);

Gracias: http://www.alexandervanwynsberghe.be/debugging-apps-for-sharepoint-online/

jueves, 11 de agosto de 2016

Error al utilizar SharePoint Search. Field or property “TimeZoneId” does not exist. Search API REST no funciona.

Nuestro paciente Search venía con visión borrosa, desorientado y muy confundido, incluso había olvidado lo que tenía guardado en los bolsillos, una especie de amnesia pasajera, directo a psiquiatría.

No paraba de repetir constantemente "Field or property “TimeZoneId” does not exist" cada vez que intentábamos hacer que recordase algo mediante su caja de texto en el buscador.

Además su API REST mediante la cual podríamos darle instrucciones de búsqueda estaba caída, el servicio simplemente no respondía ni mediante una simple llamada a http://<server>/_api/search/query dando un error 400 de error de procesamiento de solicitud.

Pero tranquilos, hay tratamiento, Microsoft ya señaló en su día que la actualización CU Agosto de 2015 requiere un parcheo más exhaustivo al pasar el obligado configurador de productos de SharePoint cada vez realizamos una actualización.

El parcheo es necesario si actualizamos desde antes de Agosto de 2015 a cualquier CU de fecha posterior si queremos evitar que el servicio de Search funcione mal.

Para resolver esto, una vez instalado el CU, hacemos en todos los servidores de la granja lo siguiente:

IISRESET

PSConfig.exe -cmd upgrade -inplace b2b -wait -cmd applicationcontent -install -cmd installfeatures -cmd secureresources

Información en inglés del doctor que conocía estos detalles: 

https://vigneshsharepointthoughts.com/2015/09/15/fix-for-the-search-issue-in-august-2015-cu-for-sharepoint-2013/

Especificación de PSConfig.exe:

https://technet.microsoft.com/es-es/library/cc263093(v=office.14).aspx

miércoles, 10 de agosto de 2016

Search Service: Exception while communicating with Host Controller. Node IndexComponent x for system cannot be deployed on this host due to insufficient physical memory


En esta ocasión a nuestro paciente se le comenzó a ulcerar la cara con un aspecto algo preocupante mientras intentaba aprovisionar una topología de búsqueda en dicho servicio con un Set-SPEnterpriseSearchTopology -Identity $NewSearchTopology:

[DBG]: PS C:\Windows\system32>> 
Set-SPEnterpriseSearchTopology : Topology activation failed. Service call 
AddNodeMapping failed with message 'Exception while communicating with Host 
Controller at ctshpliv01: Node deployment failed [System = E308E7, Node = 
IndexComponent5] [Cause: Node IndexComponent5 for system E308E7 cannot be 
deployed on this host due to insufficient physical memory, 2000 MB required 
but only 1106 MB available.]'

A pesar de lo que pueda parecer este mensaje puede despistar un poco, y aunque lo primero que nos viene a la cabeza es echarle unos cuantos gigas extra a nuestra memoria física, eso no resolverá el problema si observamos el indicador de memoria en Task Manager mientras ejecutamos Set-SPEnterpriseSearchTopology.

La solución vino al adecuar mejor la arquitectura de topología a los recursos de que disponemos, por tanto al reducir el número de componentes de índice que se pretendía crear a la mitad, de ocho a cuatro (finalmente 2 por máquina), el problema fue subsanado y el servicio creado sin problemas. 

Finalmente, la arquitectura creada fue la siguiente:


Y el script adaptado a nuestro caso utilizado el siguiente, el cual fue sacado originalmente de https://blogs.msdn.microsoft.com/chandru/2013/02/18/sharepoint-2013-configuring-search-service-application-and-topology-using-powershell/

El script original presentaba la topología con 8 índices, lo cual dio el actual problema de úlceras faciales que presentamos en nuestro caso.

#==============================================================
    #Search Service Application Configuration Settings
     Add-PSSnapin "Microsoft.SharePoint.PowerShell" -ErrorAction SilentlyContinue
#==============================================================

$SearchApplicationPoolName = "SearchApplicationPool"
$SearchApplicationPoolAccountName = "dominio\cuentaSvc"
$SearchServiceApplicationName = "Search Service Application"
$SearchServiceApplicationProxyName = "Search Service Application Proxy"
$DatabaseServer = "DATABASESERVER"
$DatabaseName = "SEARCH_DATABASE_NAME"
$IndexLocationServer = "C:\Indexes"  
$NoLocalServerName = "server2"

#==============================================================
          #Search Application Pool

                   #==============================================================
                   
Write-Host -ForegroundColor DarkGray "Checking if Search Application Pool exists"
$SPServiceApplicationPool = Get-SPServiceApplicationPool -Identity $SearchApplicationPoolName -ErrorAction SilentlyContinue
if (!$SPServiceApplicationPool)
{
    Write-Host -ForegroundColor Yellow "Creating Search Application Pool"
    #$SPServiceApplicationPool = New-SPServiceApplicationPool -Name $SearchApplicationPoolName -Account $SearchApplicationPoolAccountName -Verbose
   
}
 $SPServiceApplicationPool = Get-SPServiceApplicationPool -Identity "SearchApplicationPool"

#==============================================================
          #Search Service Application
#==============================================================
Write-Host -ForegroundColor DarkGray "Checking if SSA exists"
$SearchServiceApplication = Get-SPEnterpriseSearchServiceApplication -Identity $SearchServiceApplicationName -ErrorAction SilentlyContinue
if (!$SearchServiceApplication)
{
   Write-Host -ForegroundColor Yellow "Creating Search Service Application"    
   $SearchServiceApplication = New-SPEnterpriseSearchServiceApplication -Name $SearchServiceApplicationName -ApplicationPool $SPServiceApplicationPool -DatabaseServer  $DatabaseServer -DatabaseName $DatabaseName 
}
Write-Host -ForegroundColor DarkGray "Checking if SSA Proxy exists"
$SearchServiceApplicationProxy = Get-SPEnterpriseSearchServiceApplicationProxy -Identity $SearchServiceApplicationProxyName -ErrorAction SilentlyContinue
if (!$SearchServiceApplicationProxy)
{
    Write-Host -ForegroundColor Yellow "Creating SSA Proxy"
    New-SPEnterpriseSearchServiceApplicationProxy -Name $SearchServiceApplicationProxyName -SearchApplication $SearchServiceApplicationName
}


 #==============================================================
          #Start Search Service Instance on Server1 -local
 #==============================================================
 $SearchServiceInstanceServer1 = Get-SPEnterpriseSearchServiceInstance -local 
 Write-Host -ForegroundColor DarkGray "Checking if SSI is Online on Server1"
 if($SearchServiceInstanceServer1.Status -ne "Online")
 {
   Write-Host -ForegroundColor Yellow "Starting Search Service Instance"
   Start-SPEnterpriseSearchServiceInstance -Identity $SearchServiceInstanceServer1
   While ($SearchServiceInstanceServer1.Status -ne "Online")
   {
       Start-Sleep -s 5
   }
   Write-Host -ForegroundColor Yellow "SSI on Server1 is started"
 }

  #==============================================================
         #Start Search Service Instance on Server2 liv03
 #==============================================================
 $SearchServiceInstanceServer2 = Get-SPEnterpriseSearchServiceInstance -Identity $NoLocalServerName #liv03
 Write-Host -ForegroundColor DarkGray "Checking if SSI is Online on Server2"
 if($SearchServiceInstanceServer2.Status -ne "Online")
 {
   Write-Host -ForegroundColor Yellow "Starting Search Service Instance"
   Start-SPEnterpriseSearchServiceInstance -Identity $SearchServiceInstanceServer2
   While ($SearchServiceInstanceServer2.Status -ne "Online")
   {
       Start-Sleep -s 5
   }
   Write-Host -ForegroundColor Yellow "SSI on Server2 is started"
 }

 #==============================================================
 #Cannot make changes to topology in Active State.
 #Create new topology to add components
 #=========================================
 $InitialSearchTopology = $SearchServiceApplication | Get-SPEnterpriseSearchTopology -Active 
$NewSearchTopology = $SearchServiceApplication | New-SPEnterpriseSearchTopology


#==============================================================
        #Search Service Application Components on Server1
        #Creating all components except Index (created later)     
#==============================================================

New-SPEnterpriseSearchAnalyticsProcessingComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1
New-SPEnterpriseSearchContentProcessingComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1
New-SPEnterpriseSearchQueryProcessingComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1
New-SPEnterpriseSearchAdminComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1

#==============================================================
#Search Service Application Components on Server2.
#Crawl, Query, and CPC
#==============================================================

New-SPEnterpriseSearchContentProcessingComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer2
New-SPEnterpriseSearchQueryProcessingComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer2
New-SPEnterpriseSearchCrawlComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer2 


#==============================================================
        #Index Components with replicas
#==============================================================
$IndexLocationServer = "S:\Indexes"

New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1  -IndexPartition 0 -RootDirectory $IndexLocationServer
New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer1  -IndexPartition 1 -RootDirectory $IndexLocationServer 
New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer2  -IndexPartition 0 -RootDirectory $IndexLocationServer 
New-SPEnterpriseSearchIndexComponent -SearchTopology $NewSearchTopology -SearchServiceInstance $SearchServiceInstanceServer2  -IndexPartition 1 -RootDirectory $IndexLocationServer 

#==============================================================
  #Setting Search Topology using Set-SPEnterpriseSearchTopology
#==============================================================

Set-SPEnterpriseSearchTopology -Identity $NewSearchTopology

#==============================================================
                #Clean-Up Operation
#==============================================================

Write-Host -ForegroundColor DarkGray "Deleting old topology"
Remove-SPEnterpriseSearchTopology -Identity $InitialSearchTopology -Confirm:$false
Write-Host -ForegroundColor Yellow "Old topology deleted"

#==============================================================
                #Check Search Topology
#==============================================================
Get-SPEnterpriseSearchStatus -SearchApplication $SearchServiceApplication -Text
Write-Host -ForegroundColor Yellow "Search Service Application and Topology is configured!!"



viernes, 15 de julio de 2016

Voracidad de Search afecta a los Workflows de Project Server. HTTP 500 The requested service, xxx could not be activated.


"Cuando una mariposa agita sus alas en Nueva Zelanda, alguien se come un puñado de garbanzos torraos en Albacete".

O algo así decía el refrán creo recordar, pero viene a cuento por la extraña sucesión de causas encadenadas de hoy.

Nuestro paciente venía aquejado de errores en un flujo intestinal de trabajo, el cual daba un error para algunos proyectos que se quedaban bloqueados, uno de estos diversos errores que los workflows de Project se afanan, con mucha creatividad siempre, en producir:


RequestorId: dd6ae4d8-59fc-a419-0000-000000000000. Details: An unhandled exception occurred during the execution of the workflow instance. Exception details: System.ApplicationException: HTTP 500 The requested service, &#39;http://es1intra02v:32843/32dbfb0876734f6493e48270df96c4cf/AppMng.svc&#39; could not be activated. See the server&#39;s diagnostic trace logs for more information. {"SPRequestGuid":["dd6ae4d8-59fc-a419-8ae7-fb670df49745"],"request-id":["dd6ae4d8-59fc-a419-8ae7-fb670df49745"],"X-FRAME-OPTIONS":["SAMEORIGIN"],"SPRequestDuration":["9294"],"SPIisLatency":["0"],"MicrosoftSharePointTeamServices":["15.0.0.4569"],"X-Content-Type-Options":["nosniff"],"X-MS-InvokeApp":["1; RequireReadOnly"],"Date":["Tue, 12 Jul 2016 10:04:41 GMT"],"Server":["Microsoft-IIS\/8.5"],"X-Powered-By":["ASP.NET"]} at Microsoft.Activities.Hosting.Runtime.Subroutine.SubroutineChild.Execute(CodeActivityContext context) at System.Activities.CodeActivity.InternalExecute(ActivityInstance instance, ActivityExecutor executor, BookmarkManager bookmarkManager) at System.Activities.Runtime.ActivityExecutor.ExecuteActivityWorkItem.ExecuteBody(ActivityExecutor executor, BookmarkManager bookmarkManager, Location resultLocation)

En negrita dejo lo importante del asunto, y lo importante no es que ese servicio falle, si no que un servicio falla con "could not be activated", investigando un poquito en el vademecum google, hallamos que la caída de servicios con un "could not be activated" suele estar íntimamente relacionada a la falta de recursos en el servidor.

Efectivamente, así era:

Memoria, casi a tope.


Disco duro, como vimos en otro post, no respeta la recomendación de Microsoft de mantener libre en disco al menos el doble de la memoria RAM instalada.

Task Manager, indicando que el proceso noderunner.exe, el encargado de dar vida al sistema Search de SharePoint, estaba siendo un elemento voraz con los recursos:


El fonendoscopio Health Analyzer, nuestro sempiterno amigo que llevamos siempre colgado del cuello:



Para salir del embrollo por ahora, bastó con reiniciar los servicios de Windows SharePoint Search y Host Controller:


Y reiniciar los workflows que estaban dando problemas desde la configuración de PWA en "Change or restart workflows", causando a los usuarios la molestia de comenzar de nuevo con los flujos, el menor de los males en estos casos.

Debido a que esto es pan para hoy y hambre para mañana, se ha echado la bronca recomendado al cliente que aumente los recursos de sus máquinas, poniendo unos 40 o 50 GB más de disco duro en cada servidor como mínimo tanto en los servidores SharePoint como en el de SQL Server, donde además debería echar un vistazo al crecimiento de las bases de datos, y que aumente la RAM hasta los 24 GB, para evitar que esto se siga repitiendo.

jueves, 14 de julio de 2016

Ejecutar scripts no firmados en PowerShell. Script is not digitally signed. The script will not execute on the system.


Al intentar darle la medicina a nuestro paciente intentando ejecutar un script ps1, éste parecía presentar un cuadro alérgico que le impedía aceptar el tratamiento, generando una respuesta autoinmune de falta de confianza por no estar firmado:


PS Microsoft.PowerShell.Core\FileSystem::\\...> .
\patchfarm.ps1
.\patchfarm.ps1 : File \\...\PatchFarm.ps1
cannot be loaded. The file \\...\PatchFarm.ps1
is not digitally signed. The script will not execute on the system. For more
information, see about_Execution_Policies at
http://go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ .\patchfarm.ps1
+ ~~~~~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

Para que esto dejase de producirse se ha pedido al sistema que confíe en nuestro script mediante un Set-ExecutionPolicy, pero ojo, no de cualquier forma, ya que si no indicamos explícitamente un ámbito, podríamos estar generando un enorme agujero de seguridad, dejando la granja disponible para cualquier malhechor.



De modo que nuestro comando queda así.

Set-ExecutionPolicy Unrestricted -Scope Process

Indicar el ámbito Process hará que al cerrar la ventana de PowerShell todo vuelva a su estado original, de modo que si volvemos a abrir una ventana los scripts sin firmar no se ejecutarán.

Especificación formal de Set-ExecutionPolicy:

https://technet.microsoft.com/es-es/library/hh849812.aspx

lunes, 4 de julio de 2016

La mejor manera (que sepamos) de instalar Cumulative Updates o Service Pack 1


A veces nuestros pacientes llegan a nuestra clínica porque simplemente tienen algún complejo, partes de su cuerpo que funcionan mal y requieren correcciones o simplemente quieren sentirse mejor con su aspecto, por lo que se apuntan a hacerse un lifting, una liposucción para verse mejor en el espejo.

Qué mejor que una buena acutalización de software para que nuestros SharePoints salgan de la clínica con una nueva vida por delante llena de mejoras y fortalezas.

La parte fea del asunto es que hay que someter a nuestros pacientes a intensos dolores mientras dura la cirugía, y estos dolores afectan a sus usuarios, que podrían quejarse si les dejamos sin servicio demasiado tiempo o la cosa se nos va de las manos y la cirugía se complica haciendo que nuestro paciente pueda quedar tendido en la cama de operaciones sin constantes vitales.

Para evitar problemas, hemos elaborado buscando aquí y allá tras una serie de experiencias, una guía para realizar las operaciones sin muchos quebraderos de cabeza.

Necesitamos:

1.) Parche que queremos instalar, bien sea el Service Pack 1 o un Cumulative Update de cualquier mes, debemos recordar que para instalar cualquier Cumulative Update después de Abril de 2013 es obligatorio tener instalado el Service Pack 1.

Guia de actualizaciones de SharePoint.

2.) Un script de aprovisionamiento provisto por MSFT, código al final del artículo, que prepara el entorno para la instalación desactivando algunos servicios, algunos muy críticos cuya desactivación es muy importante como el servicio de búsquedas, instalando el parche y reactivando de nuevo los servicios para que todo vuelva a la normalidad

3.) Pasar el asistente de tecnologías y productos SharePoint una vez hecho lo anterior, preferentemente con un comando de PowerShell psconfig.

Es importante que tanto el parche como el script lo tengamos en la misma carpeta de Windows, la instalación puede ser hecha desde una ruta de red sin problemas.


Pasos:

1.) Ingresamos en el sistema con la cuenta de instalación y abrimos un PowerShell como administrador para ejecutar PatchFarm.ps1

                            

Recomendamos a los doctores aprovechar para tomar un relaxing cup of café con leche mientras se instala todo, a no ser que tengan más pacientes en lista, que deberían estar siendo atendidos, no invitamos desde aquí en absoluto a la inactividad habiendo cosas por hacer.

chaca-chaca-chaca...


chaca-chaca-chaca...


chaca-chaca-chaca...


2.) Una vez haya terminado exitosamente la instalación del parche, ejecutamos el asistente de productos y tecnologías con:

PSConfig.exe -cmd upgrade -inplace b2b -wait -force

Con esto ya habremos acabado la intervención, el post operatorio exige a nuestro paciente un leve reposo basado en el reinicio del servidor.

3.) Verificar desde la Central de Aministración que la actualización se ha llevado a cabo con éxito, desde patch installation status:



Código del script PathFarm.ps1:

<#============================================================
  //
  // Microsoft provides programming examples for illustration only,
  // without warranty either expressed or implied, including, but not
 // limited to, the implied warranties of merchantability and/or
  // fitness for a particular purpose.
  //
  // This sample assumes that you are familiar with the programming
  // language being demonstrated and the tools used to create and debug
  // procedures. Microsoft support professionals can help explain the
  // functionality of a particular procedure, but they will not modify
  // these examples to provide added functionality or construct
  // procedures to meet your specific needs. If you have limited
  // programming experience, you may want to contact a Microsoft
  // Certified Partner or the Microsoft fee-based consulting line at
  //  (800) 936-5200 .
  //
  // For more information about Microsoft Certified Partners, please
  // visit the following Microsoft Web site:
  // https://partner.microsoft.com/global/30000104 
  //
  // Author: Russ Maxwell (russmax@microsoft.com)
  //
  // ---------------------------------------------------------- #>
###########################
##Ensure Patch is Present##
###########################
$patchfile = Get-ChildItem | where{$_.Extension -eq ".exe"}
if($patchfile -eq $null)
{
  Write-Host "Unable to retrieve the file.  Exiting Script" -ForegroundColor Red
  Return
}
########################
##Stop Search Services##
########################
##Checking Search services##
$srchctr = 1
$srch4srvctr = 1
$srch5srvctr = 1
$srv4 = get-service "OSearch15"
$srv5 = get-service "SPSearchHostController"
If(($srv4.status -eq "Running") -or ($srv5.status-eq "Running"))
  {
    Write-Host "Choose 1 to Pause Search Service Application" -ForegroundColor Cyan
    Write-Host "Choose 2 to leave Search Service Application running" -ForegroundColor Cyan
    $searchappresult = Read-Host "Press 1 or 2 and hit enter"
    Write-Host
  
   if($searchappresult -eq 1)
    {
        $srchctr = 2
        Write-Host "Pausing the Search Service Application" -foregroundcolor yellow
        Write-Host "This could take a few minutes" -ForegroundColor Yellow
        $ssa = get-spenterprisesearchserviceapplication
        $ssa.pause()
    }
  
    elseif($searchappresult -eq 2)
    {
        Write-Host "Continuing without pausing the Search Service Application"
    }
    else
    {
        Write-Host "Run the script again and choose option 1 or 2" -ForegroundColor Red
        Write-Host "Exiting Script" -ForegroundColor Red
        Return
    }
  }
Write-Host "Stopping Search Services if they are running" -foregroundcolor yellow
if($srv4.status -eq "Running")
  {
    $srch4srvctr = 2
    set-service -Name "OSearch15" -startuptype Disabled
    $srv4.stop()
  }
if($srv5.status -eq "Running")
  {
    $srch5srvctr = 2
    Set-service "SPSearchHostController" -startuptype Disabled
    $srv5.stop()
  }
do
  {
    $srv6 = get-service "SPSearchHostController"
    if($srv6.status -eq "Stopped")
    {
        $yes = 1
    }
    Start-Sleep -seconds 10
  }
  until ($yes -eq 1)
Write-Host "Search Services are stopped" -foregroundcolor Green
Write-Host
#######################
##Stop Other Services##
#######################
Set-Service -Name "IISADMIN" -startuptype Disabled
Set-Service -Name "SPTimerV4" -startuptype Disabled
Write-Host "Gracefully stopping IIS W3WP Processes" -foregroundcolor yellow
Write-Host
iisreset -stop -noforce
Write-Host "Stopping Services" -foregroundcolor yellow
Write-Host
$srv2 = get-service "SPTimerV4"
  if($srv2.status -eq "Running")
  {$srv2.stop()}
Write-Host "Services are Stopped" -ForegroundColor Green
Write-Host
Write-Host
##################
##Start patching##
##################
Write-Host "Patching now keep this PowerShell window open" -ForegroundColor Magenta
Write-Host
$starttime = Get-Date
$filename = $patchfile.basename
$arg = "/passive"
Start-Process $filename $arg
Start-Sleep -seconds 20
$proc = get-process $filename
$proc.WaitForExit()
$finishtime = get-date
Write-Host
Write-Host "Patch installation complete" -foregroundcolor green
Write-Host

##################
##Start Services##
##################
Write-Host "Starting Services Backup" -foregroundcolor yellow
Set-Service -Name "SPTimerV4" -startuptype Automatic
Set-Service -Name "IISADMIN" -startuptype Automatic
##Grabbing local server and starting services##
$servername = hostname
$server = get-spserver $servername
$srv2 = get-service "SPTimerV4"
$srv2.start()
$srv3 = get-service "IISADMIN"
$srv3.start()
$srv4 = get-service "OSearch15"
$srv5 = get-service "SPSearchHostController"
###Ensuring Search Services were stopped by script before Starting"
if($srch4srvctr -eq 2)
{
    set-service -Name "OSearch15" -startuptype Automatic
    $srv4.start()
}
if($srch5srvctr -eq 2)
{
    Set-service "SPSearchHostController" -startuptype Automatic
    $srv5.start()
}
###Resuming Search Service Application if paused###
if($srchctr -eq 2)
{
    Write-Host "Resuming the Search Service Application" -foregroundcolor yellow
    $ssa = get-spenterprisesearchserviceapplication
    $ssa.resume()
}
Write-Host "Services are Started" -foregroundcolor green
Write-Host
Write-Host
Write-Host "Script Duration" -foregroundcolor yellow
Write-Host "Started: " $starttime -foregroundcolor yellow
Write-Host "Finished: " $finishtime -foregroundcolor yellow
Write-Host "Script Complete"


Referencias del presente artículo, aparte de la propia experiencia:

https://www.linkedin.com/pulse/step-by-step-cumulative-update-sharepoint-2013-dyung-ngo
http://blogs.msdn.com/b/russmax/archive/2013/04/01/why-sharepoint-2013-cumulative-update-takes-5-hours-to-install.aspx