domingo, 18 de abril de 2010
Como hacer un setup personalizado en 15min con Visual Studio .NET
Muy buenas de nuevo, (y nunca mejor dicho lo de “nuevo”).
Llevo ya un tiempo sin aparecer por aquí, y la verdad, es que desde entonces no han dejado de aparecer novedades. Me siento como un niño pequeño que quiere jugar con sus nuevos juguetitos y, de alguna manera “no le dejan”, en fin….. Ya os contaré algunas cosas poco a poco.
En un intento de retomar mi aparición por aqui, os muestro a continuación y un pequeño artículo sobre la generación de Setup personalizados que escribí hace poco. A ver si os sirve:
Hace ya un tiempo quería comentar la facilidad de .net para la generación de setup’s personalizados y sobre todo como encaminarlos a la distribución vía SCCM (el antiguo SMS) o incluso, como sustitución a la generación de diferentes ficheros de configuración para nuestros “famosos” y diferentes entornos; desarrollo, integración y producción.
- ¿Por qué copiar de acá para allá diferentes ficheros de configuración que cambian de un día para otro?
- ¿No es mejor disponer de un mismo fichero de configuración y no compilar nada para el paso entre entornos?
Vamos a seguir detalladamente los pasos adecuados a partir de un sencillo ejemplo que consiste en un formulario Windows que utiliza un texto fijo recuperado del fichero de configuración y que podría ser cualquiera de nuestros proyectos actuales
1. Añadimos a la solución existente un proyecto de tipo “Setup and Deployment” con el nombre <NombreProyecto>Setup:
2. Añadimos un nuevo proyecto de tipo “Class library” y denominarlo por ejemplo, <NombreProyecto>CustomInstall:
3. Añadimos una nueva clase “CustomParameters.cs” o renombrar la clase “Class1.cs” generada por defecto con la siguiente información:
using System.Collections;
using System.Configuration.Install;
namespace DeployEn15MinCustomInstall
{
public class CustomParameters
{
private static System.Collections.IDictionary state = null;
public static string PREFIJO_SALUDO = "PrefijoSaludo";
private string _prefijoSaludo;
/// <summary>
/// Constructor para los instalables sin parametros de entrada
/// </summary>
/// <param name="savedState"></param>
public CustomParameters(IDictionary savedState)
{
state = savedState;
}
public string PrefijoSaludo
{
get { return _prefijoSaludo; }
set { _prefijoSaludo = value; }
}
/// <summary>
/// Recupera los parametros pasados al msi.
/// </summary>
/// <param name="installContext"></param>
public void LoadContext(InstallContext installContext)
{
this.PrefijoSaludo = installContext.Parameters[PREFIJO_SALUDO];
}
}
}
Donde; la constante “PREFIJO_SALUDO”, el atributo y la propiedad “PrefijoSaludo” serán sustituidos por cuantos parámetros sean necesarios pasar al ”.msi” para la instalación.
4. Añadimos una nueva clase “ConfigManager” con objeto de manejar la sección de “AppSettings” del fichero de configuración de la aplicación y poder añadir así nuevos valores a las key’s ya existentes.
public class ConfigManager
{
#region Attributos
private string _ConfigPath;
private Configuration _Config;
private AppSettingsSection _AppSettings;
#endregion
/// <summary>
/// Inicializa una nueva instancia de la clase<see cref="ConfigUpdater"/>.
/// </summary>
/// <param name="configPath">The config path.</param>
public ConfigManager(string configPath)
{
_ConfigPath = (configPath == null ? String.Empty : configPath);
try
{
_Config = ConfigurationManager.OpenExeConfiguration(_ConfigPath);
_AppSettings = _Config.AppSettings;
}
catch (Exception ex)
{
throw new ApplicationException(
String.Format("Error adding new configuration parameters into: '{0}'.{1}{2}",
_ConfigPath, ex.Message, ex.StackTrace));
}
}
/// <summary>
/// Añade una nueva clave (keyName) a la sección "appSettings"
/// del fichero de configuración con un valor (value).
/// </summary>
/// <param name="keyName">Nombre de la clave a añadir.</param>
/// <param name="value">Valor a asignar a la nueva clave.</param>
public void AddParam(string keyName, string value)
{
if (_AppSettings.Settings[keyName] == null)
_AppSettings.Settings.Add(keyName, value);
}
/// <summary>
/// Elimina del fichero de configuración una clave (keyName) de la sección "appSettings"
/// del fichero de configuración.
/// </summary>
/// <param name="keyName">Nombre de la clave a eliminar..</param>
public void RemoveParam(string keyName)
{
_AppSettings.Settings.Remove(keyName);
}
/// <summary>
/// Modifica o añade una clave (keyName) de la sección "appSettings".
/// </summary>
/// <param name="keyName">Nombre de la clare a añadir o modificar.</param>
/// <param name="value">Valor a asignar a la clave</param>
public void SaveParam(string keyName, string value)
{
if (_AppSettings.Settings[keyName] != null)
_AppSettings.Settings[keyName].Value = value;
else
AddParam(keyName, value);
}
/// <summary>
/// Guarda el fichero de configuración con los cambios realizados
/// en la sección "appSetting".
/// </summary>
public void Save()
{
if (!_AppSettings.ElementInformation.IsLocked)
_Config.Save();
else
throw new ApplicationException("Section was locked, could not update");
}
}
5. Añadimos al proyecto “CustomIntall” un nuevo ítem de tipo “Installer Class” y lo denominamos por ejemplo “CustomActions.cs”.
El contenido de este fichero será:
using System;
using System.Collections;
using System.ComponentModel;
using System.Configuration.Install;
using System.Diagnostics;
using System.IO;
using System.Reflection;
namespace DeployEn15MinCustomInstall
{
[RunInstaller(true)]
public partial class CustomActions : Installer
{
private const string PAREMETER_NOT_SPECIFIED = "El parámetro de instalación '{0}' no ha sido especificado. La instalación será cancelada.";
private CustomParameters _parameters = null;
// Añadir al array todos los parámetros requeridos durante la instalación.
// Dejarlo vacío sino hay parámetros requeridos.
private ArrayList requiredParameters = new ArrayList { CustomParameters.PREFIJO_SALUDO };
public CustomActions()
{
InitializeComponent();
}
public override void Install(System.Collections.IDictionary stateSaver)
{
#if DEBUG
Debugger.Launch();
#endif
base.Install(stateSaver);
// Si la instalación es ejectuada previo paso de parámetros.
if (this.Context != null && this.Context.Parameters.Count > 0)
{
_parameters = new CustomParameters(stateSaver);
_parameters.LoadContext(this.Context);
ConfigureFileConfig();
}
}
private void ConfigureFileConfig()
{
string assemblyPath = this.Context.Parameters["assemblyPath"];
string fileName = Path.GetFileNameWithoutExtension(assemblyPath).Replace("CustomInstall", ".exe");
string exeConfigPath = Path.Combine(Directory.GetParent(assemblyPath).FullName, fileName);
ConfigManager config = new ConfigManager(exeConfigPath);
this.UpdateAppSettings(config);
// Si todo es correcto se realiza el commit del fichero de configuración.
config.Save();
config = null;
}
private void UpdateAppSettings(ConfigManager config)
{
PropertyInfo[] props = _parameters.GetType().GetProperties();
foreach (PropertyInfo prop in props)
{
string propValue = prop.GetValue(_parameters, null) as string;
if (!String.IsNullOrEmpty(propValue))
{
config.SaveParam(prop.Name, propValue);
}
else
{
// Parámetro requerido no especificado
if (requiredParameters.Contains(prop.Name))
{
throw new InstallException(
String.Format(PAREMETER_NOT_SPECIFIED, prop.Name));
}
}
}
}
}
}
Las siguientes instrucciones
Las siguientes instrucciones van a permitir realizar una parada en tiempo de depuración con objeto de facilitar dicha labor. Esta instrucción tendrá más sentido cuando el ejecutable no se inicie directamente desde el Visual Studio.
#if DEBUG
Debugger.Launch();
#endif
6. Añadimos al proyecto de Setup dos items del tipo, “Primary Output”;
- “DeployEn15Min”(Ejecutable Windows Form) y,
- “DeployEn15MinCustomInstall”(Class Library que contiene el “Installer class”) .
7. Finalizado el paso anterior, nuestra solución tendrá la siguiente apariencia además de proyecto de Test correspondiente (como en cualquier proyecto debemos tener siempre):
8. A continuación seleccionamos el proyecto “DeployEn15MinSetup” y hacemos click en el icono de “Custom Actions”.
9. Sobre la carpeta “Install” de las Custom Actions, hamemos click derecho y seleccionamos del “Application Folder” el “Primary output” referente al “CustomInstall”.
10. Generamos un fichero de comandos “.bat” con la siguiente instrucción y lo ejecutamos en la misma carpeta en donde se encuentre el Setup:
msiexec /l* "DeployEn15Min.log" /i DeployEn15MinSetup.msi TARGETDIR="C:\Test\" allUsers=[ALLUSERS] PREFIJOSALUDO="Deploy en 15 minutos v1.1"
Ahora, el ejecutable recibe los parámetros pasados en el comando. Los que se muestran durante la instalación son parámetros estándares, sin embargo, si después de la instalación comprobamos el fichero de configuración resultante, veremos como el valor de la key “PrefijoSaludo” del “appSettings”, ha cambiado por el nuevo valor.
Es en este punto, donde cobra fuerza la instrucción, “Debugger.Launch();” antes comentada.
Para conseguir la modificación de todos nuestros parámetros de configuración durante la instalación, tendremos que añadir valores a la propiedad “CustomActionData” del Custom Action “Install” tal y como se indica en la siguiente figura para nuestro parámetro “PrefijoSaludo”:
Se añadirán tantos valores como parámetros sean susceptibles de recuperar en tiempo de instalación.
Los “custom actions”; “Commit”, “Rollback” y “Uninstall”, serán implementados de la misma manera siempre y cuando en nuestra clase DeployEn15MinCustomInstall.CustomInstall.cs sobrescribamos los métodos correspondientes. Adicionalmente podrán sobrescribirse otros métodos que ocurren antes o después de estas acciones, con lo que tendremos mayor control sobre cualquier “cosa” que queramos realizar durante y después de la instalación.
El comando “msiexec” recibe un parámetro /l* que permite indicar un fichero de log en el que se registrarán todos pasos generados durante la instalación. Su objetivo, detectar cualquier posible error durante dicho proceso.
Nota: Con objeto de mejorar la instalación de prerrequisitos, actualizaciones, parches etc. de nuestra aplicación, podemos utilizar un “.vbs” (Visual Basic Script) en lugar de un “.bat”, permitiendo de esta manera mayor control; acceso al registro de Windows, etc. El objetivo de esto es permitir a los administradores de sistemas las modificaciones adecuadas según las necesidades concretas de la distribución. Un ejemplo claro podría ser, comprobar una clave del registro de Windows para verificar la existencia de otro programa o prerrequisito, de una versión anterior, etc.
Un saludo desde mi “nueva casa”
Juanlu, El Guerre
Etiquetas: Framework .NET, msi, Setup, Visual Studio .NET