Dynamics CRM Plugin Deployment Batch

 

La tarea de depurar plugins en CRM es una de las tareas más engorrosas con las que me ha tocado bregar.

Cada vez que tocas el código de un plugin, hay que

recompilar,

parar el web de CRM,

parar el Asynchronous Processing Service,

copiar las dlls a la carpeta del servidor,

registrar en la GAC las dependencias,

iniciar el web y el servicio,

volver a probar.

 

Un engorro, vamos. Por eso, he creado este pequeño batch que hace todo eso en un click.

 

   1:  set PLUGINS_OUTPUT=C:ProjectsPluginsbinDebug
   2:  set CRM_SERVER_NAME=CRMSERVERNAME
   3:  set GACUTIL_PATH=C:Program FilesMicrosoft SDKsWindowsv6.0Abingacutil.exe
   4:  set SERVER_ASSEMBLY_LOCAL_PATH=C:Program FilesMicrosoft Dynamics CRMServerbinassembly
   5:  set SERVER_ASSEMBLY_NETWORK_PATH=\CRMSERVERNAMEC$Program FilesMicrosoft Dynamics CRMServerbinassembly
   6:   
   7:  REM STOP CRM WEB And Service
   8:  sc \%CRM_SERVER_NAME% stop MSCRMAsyncService
   9:  sc \%CRM_SERVER_NAME% stop w3svc
  10:   
  11:  REM Copy Files
  12:  copy "%PLUGINS_OUTPUT%Plugins.dll" "%SERVER_ASSEMBLY_NETWORK_PATH%" /y
  13:  copy "%PLUGINS_OUTPUT%Plugins.pdb" "%SERVER_ASSEMBLY_NETWORK_PATH%" /y
  14:  copy "%PLUGINS_OUTPUT%PluginsDependencies1.dll" "%SERVER_ASSEMBLY_NETWORK_PATH%" /y
  15:  copy "%PLUGINS_OUTPUT%PluginsDependencies2.dll" "%SERVER_ASSEMBLY_NETWORK_PATH%" /y
  16:   
  17:  REM Register files in GAC
  18:  psexec.exe \%CRM_SERVER_NAME% -c "%GACUTIL_PATH%" /if "%SERVER_ASSEMBLY_LOCAL_PATH%PluginsDependencies1.dll"
  19:  psexec.exe \%CRM_SERVER_NAME% -c "%GACUTIL_PATH%" /if "%SERVER_ASSEMBLY_LOCAL_PATH%PluginsDependencies2.dll"
  20:   
  21:  REM Delete GAC registered files
  22:  del "%SERVER_ASSEMBLY_NETWORK_PATH%PluginsDependencies1.dll"
  23:  del "%SERVER_ASSEMBLY_NETWORK_PATH%PluginsDependencies2.dll"
  24:   
  25:  REM START CRM WEB and Service
  26:  sc \%CRM_SERVER_NAME% start MSCRMAsyncService
  27:  sc \%CRM_SERVER_NAME% start w3svc

 

Variables de la cabecera:

PLUGINS_OUTPUT: Output de Visual Studio con las dll del plugin y sus dependencias.

CRM_SERVER_NAME: El nombre de red del servidor de CRM

GACUTIL_PATH: Ruta local de la utilidad GACUTIL. Sólo existe en el servidor si está instalado el SDK de .net, por tanto lo cogemos del equipo local.

SERVER_ASSEMBLY_LOCAL_PATH: Ruta Local del directorio bin de CRM del servidor.

SERVER_ASSEMBLY_NETWORK_PATH: La misma ruta, pero accesible desde la red. Al ser administradores deberíamos poder acceder al raíz usando C$.

 

La utilidad GACUTIL.exe permite registrar ensamblados en la caché de .net.

Es necesario tener permisos de administrador en el servidor de CRM.

También es necesario tener la utilidad psexec.exe de Windows Sys Internals. Esta utilidad permite ejecutar comandos en un servidor remoto. Como si fuera un telnet, pero más potente.

El comando SC.exe permite controlar servicios del servidor de forma remota.

Lo he probado contra un servidor de CRM 4. No sé qué tal irá contra un CRM 2011.

Referencias:

http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx
http://blogs.iis.net/davcox/archive/2009/07/14/where-is-gacutil-exe.aspx
http://msdn.microsoft.com/en-us/library/ex0ss12c%28v=vs.80%29.aspx
http://webhub.com/dynhelp:viewTn::catalog=TnNote,id=recycledefaultapppool
http://www.microsoft.com/resources/documentation/windows/xp/all/proddocs/en-us/sc.mspx?mfr=true

Javascript Private Shared Variables

 

 

Apunto este trozo de código pues contiene un concepto interesante que puede ayudar cuando trabajas con Javascript.

Esta técnica permite definir una variable, cuyo ámbito se limita a una función, pero cuyo valor se comparte entre distintas llamadas.

Se parece a las variables privadas estáticas que existían en Visual Basic 6. La idea es la misma.

   1:  function MyFunction() {
   2:      if (!MyFunction.Value1) { // Set the default Value
   3:          MyFunction.Value1 = "Default Value";
   4:      }
   5:      else {
   6:          // The value has been already set   
   7:          MyFunction.Value1 = "Modified Value";
   8:      }
   9:   
  10:      return MyFunction.Value1;
  11:  }
  12:   
  13:  alert("1:" + MyFunction());
  14:  alert("2:" + MyFunction());
  15:   
  16:  // The value is not accesible from outside
  17:  MyFunction.Value1 = "Outside modification";
  18:   
  19:  alert("3:" + MyFunction());

Pruébalo en JS Fiddle: http://jsfiddle.net/69bdw/

 

Básicamente, lo que hacemos es tratar a MyFunction como si fuera un objeto, y asignarle una propiedad.

El valor Value1 existe dentro del contexto de MyFunction, y sólo es modificable desde dentro de él. Además su valor persiste entre llamadas.

Una aplicación interesante de este concepto podría ser una función, que atendiese un evento que se dispara repetidamente, pero para el que sólo realizaremos una acción cada cierto tiempo. Por ejemplo, el resize de la ventana.

 

HTML:

   1:  <div style="height:100px;border:solid 2px;">
   2:      <div id='div1' style="position:relative;background-color:red;width:50px;height:50px;top:25px;" />
   3:  </div>

JS:

   1:  function SetupPosition(){
   2:      var div1 = document.getElementById('div1');
   3:      var parentContainer = div1.parentElement;
   4:      var leftValue = (parentContainer.offsetWidth / 2) -
   5:                      (div1.offsetWidth / 2);
   6:      div1.style.left = leftValue+"px";
   7:  }
   8:   
   9:  function OnWindowResize() {
  10:      /// <summary>Handler for the window resize event</summary>
  11:      
  12:      if (OnWindowResize.TimerId) { // If the timer has already been set
  13:          // Clear it in order to avoid multiple executions
  14:          window.clearTimeout(OnWindowResize.TimerId);
  15:      }
  16:      // Set a timeout to calculate the element position
  17:      OnWindowResize.TimerId = window.setTimeout(SetupPosition, 500);
  18:  }
  19:   
  20:  // Handle the window resize Event
  21:  window.onresize = OnWindowResize;

Pruébalo en JS Fiddle: http://jsfiddle.net/3DNBk/

 

La función OnWindowResize comprueba que exista una ejecución previa del window.setTimeout “mirando” en su propiedad TimerId.

Si la hay, cancela la ejecución, y vuelve a planificar otra para dentro de medio segundo.