Resistencia de la aplicación: desbloquear las características ocultas de Windows Installer
Michael Sanford
701 Software
Marzo de 2005
Resumen: Windows Installer tiene varias características que han pasado desapercibidas por la comunidad de desarrollo. Estas características permiten a una aplicación repararse en tiempo de ejecución o instalar componentes opcionales en función de la interacción del usuario con la aplicación. (10 páginas impresas)
Descargue el ejemplo de integración de MSI Code.msi.
Contenido
Introducción
Resistencia mediante la integración de Shell
Introducción a la API de Windows Installer
API de aplicación de claves
Desafío 1: Self-Invoked resistencia
Desafío n.º 2: Instalar a petición
Conclusión
Introducción
Como desarrolladores, realmente tienden a pensar en nuestras aplicaciones que se ejecutan en entornos ideales, en sistemas ideales y por usuarios ideales que usan la aplicación después de una instalación correcta. La realidad es que después de que nuestras aplicaciones se hayan instalado correctamente, su duración solo ha comenzado. Los desafíos que nuestra aplicación puede enfrentar en el resto estable y funcional son muchos, pero la mayoría de las aplicaciones no están preparadas para tratar los cambios en el entorno operativo que podrían representar la aplicación inoperable.
Windows Installer proporciona características de resistencia que han realizado importantes avances para mantener estables nuestras aplicaciones, pero esta funcionalidad se basa en determinadas acciones que el usuario realiza al interactuar con el shell para proporcionar "puntos de entrada" a través de los cuales Windows Installer puede detectar problemas con la configuración de la aplicación y tomar medidas para repararla.
Esta es una breve lista de "puntos de entrada" de Windows Installer:
- Métodos abreviados. Windows Installer presenta un tipo especial de acceso directo que, al mismo tiempo que es transparente para el usuario, contiene metadatos adicionales que Windows Installer usa a través de su integración de shell para comprobar el estado de la instalación de la aplicación especificada antes de iniciar la aplicación.
- Asociaciones de archivos. Windows Installer proporciona un mecanismo para interceptar llamadas a una aplicación asociada de un documento o archivo para que cuando un usuario abra un documento o archivo mediante el shell, Windows Installer puede comprobar la aplicación antes de iniciar la aplicación asociada.
- Publicidad COM. Windows Installer proporciona un mecanismo que se enlaza al subsistema COM, de modo que cualquier aplicación que cree una instancia de un componente COM instalado por Windows Installer (y configurado para usar esta característica) recibirá una instancia de ese componente después de que Windows Installer haya comprobado el estado de la instalación de ese componente.
En determinadas circunstancias, es posible que las características de resistencia integradas de Windows Installer no puedan detectar todos los problemas con la configuración de la aplicación, o nuestra aplicación puede funcionar de tal manera que los puntos de entrada necesarios no se activen. Afortunadamente, los chicos inteligentes del equipo de Windows Installer entendieron este desafío y hicieron que las características de resistencia adicionales estén disponibles para nosotros a través de la API enriquecida de Windows Installer.
Resistencia mediante la integración de Shell
Antes de pasar a las características avanzadas de resistencia que proporciona la API de Windows Installer, echemos un vistazo a un escenario de resistencia típico que normalmente obtenemos gratis al implementar nuestras aplicaciones con Windows Installer.
En este escenario, vamos a implementar una aplicación de edición de texto simple que llamaremos a SimplePad. Para crear la instalación, vamos a usar el kit de herramientas wiX de código abierto de Microsoft (para obtener más información, consulte https://sourceforge.net/projects/wix/.), pero puede lograr lo mismo con cualquier herramienta de su elección.
<Directory Id="TARGETDIR" Name="SourceDir">
<Component Id="SimplePad" Guid="BDDFA5DC-BD69-4232-998E-5167814C21B9"
KeyPath="no">
<File Id="SimplePadConfig" Name="SP.cfg"
src="$(var.SrcFilesPath)SimplePad.exe.config"
LongName="SimplePad.exe.config" Vital="yes" KeyPath="no" DiskId="1" />
<File Id="SimplePad" Name="Simple~1.exe"
src="$(var.SrcFilesPath)SimplePad.EXE" LongName="SimplePad.exe" Vital="yes"
KeyPath="yes" DiskId="1" >
<Shortcut Id="SC1" Advertise="yes" Directory="ProgramMenuFolder"
Name="SimpPad" LongName="Run SimplePad" />
</File>
</Component>
<Directory Id="ProgramMenuFolder" Name="ProgMenu"></Directory>
</Directory>
Como puede ver en el fragmento XML anterior, hemos creado una instalación muy sencilla con un archivo (SimplePad.exe) y un único acceso directo ubicado en el menú Inicio del usuario. Es importante tener en cuenta que en este ejemplo, el acceso directo que estamos creando es el punto de entrada que Windows Installer usará para detectar el estado de la aplicación y repararlo según sea necesario.
En este momento, podemos compilar nuestro instalador, instalar la aplicación y usar el acceso directo del menú Inicio recién creado para ejecutarlo. Según lo previsto, la aplicación funcionará exactamente según lo previsto. Para probar las características de resistencia integradas de Windows Installer, podemos eliminar el archivo SimplePad.exe e intentar ejecutar la aplicación desde el acceso directo del menú Inicio. De nuevo, como se esperaba, Windows Installer detecta que falta SimplePad.exe y se inicia una reparación. Durante la operación de reparación, Windows Installer lee la información de configuración necesaria de su copia almacenada internamente en caché del paquete de instalación y, por último, reemplaza el archivo que falta, que solicita al usuario los medios de instalación de origen si no está presente en la ubicación original desde la que se instaló. Una vez completada la operación de reparación, la aplicación se inicia de la forma normal.
Figura 1. Operación de reparación en curso
La resistencia de la aplicación también la proporciona Windows Installer a través de un par de otros mecanismos que también merece la pena mencionar aquí. El segundo método más común para garantizar que las aplicaciones sigan estando altamente disponibles es a través de asociaciones de archivos de Windows Installer. Este mecanismo funciona muy de la misma manera que los accesos directos de Windows Installer, pero en lugar de vincular directamente al archivo ejecutable de una aplicación, la asociación se realiza mediante un tipo de archivo registrado. Como puede ver en la figura 2, las asociaciones de archivos de Windows Installer se definen mediante el mismo mecanismo que usan las asociaciones de archivos estándar, con una excepción. Observe en la figura 2 que un valor adicional aparece en la clave del Registro típica "shell\Open\command". El valor adicional (también denominado "comando") es donde Windows Installer se ve cada vez que hace doble clic en un archivo desde el shell de Windows. Esta cadena de aspecto críptico, a veces denominada "Descriptor darwin", es realmente una representación codificada de un producto, componente y característica específicos. Si existe este valor adicional, Windows Installer descodificará los datos y lo usará para realizar comprobaciones en ese producto y componente. Si se encuentra que falta o está dañado el componente, Windows Installer iniciará una reparación para restaurar el archivo o los datos que faltan y, por último, iniciará la aplicación a la que se hace referencia como normal, pasando las opciones de línea de comandos adecuadas.
Ilustración 2. Ver un "Descriptor Darwin" para una asociación de archivos
El mecanismo de resistencia final que analizaremos hoy se conoce comúnmente como publicidad COM. Antes de examinar la mecánica de la publicidad COM, es importante comprender el caso de uso subyacente. Supongamos que era un proveedor de componentes que proporciona una biblioteca compartida basada en COM que proporciona tarifas postales en tiempo real. Dado que muchos productos diferentes pueden usar este componente, se instala en una única ubicación compartida en el sistema del usuario final. Para asegurarse de que el componente siempre se instala en la misma ubicación y, para asegurarse de que el componente sigue estando altamente disponible, lo envía a los clientes en un módulo de mezcla configurado correctamente para aprovechar las ventajas de la publicidad COM. Por supuesto, dado que la solución se distribuye como un único archivo de .dll sin interfaz de usuario, los demás mecanismos de resistencia simplemente no serán suficientes. En este caso, podemos confiar en la publicidad COM para asegurarnos de que nuestro componente permanece correctamente instalado y registrado en el sistema del usuario. Cuando una aplicación crea una instancia de este componente a través de mecanismos COM normales, Windows Installer "enlaza" al proceso de la misma manera que lo vimos con las asociaciones de archivos. Observe en la figura 3 que esta vez se almacena un "Descriptor Darwin" en el valor del Registro InprocServer32 para el registro COM de nuestro componente. De nuevo, esta información se descodifica y usa Windows Installer para asegurarse de que nuestro componente está instalado y configurado correctamente, realizando las reparaciones según sea necesario, antes de devolver finalmente una instancia del componente a la aplicación que realiza la llamada.
Merece la pena señalar que esta característica única funciona completamente independientemente de la aplicación mediante el componente . En otras palabras, incluso si la aplicación que usa el componente no se instaló mediante Windows Installer, la publicidad COM empleada por el componente seguirá funcionando correctamente, incluso si la aplicación que realiza la llamada es simplemente un VBScript.
Figura 3. Ver un "Descriptor Darwin" para un servidor COM
Hasta ahora, todo lo que hemos hablado y demostrado ha aprovechado las funcionalidades de Windows Installer sin necesidad de escribir una sola línea de código, pero ahora es el momento de pasar a una implementación más completa y sólida.
Introducción a la API de Windows Installer
El comportamiento predeterminado de Windows Installer funcionó bien para nosotros en el escenario anterior, pero a menudo, en el mundo real, tenemos aplicaciones ligeramente más sofisticadas. Vamos a expandir nuestro escenario de ejemplo para abordar un escenario más complicado.
A menudo, una aplicación consta de más de un archivo ejecutable. Un ejemplo podría ser una aplicación que usa un ejecutable de programa previo para comprobar e instalar actualizaciones en una aplicación, como se muestra en el bloque de aplicación del actualizador. En este caso, el primer ejecutable es el que se invoca cuando el usuario hace clic en un acceso directo en el menú Inicio. A su vez, inicia el segundo ejecutable que contiene la interfaz de usuario principal de la aplicación. Dependiendo de cómo se configuró la instalación, hay una buena probabilidad de que los problemas con el ejecutable de la aplicación principal no se detecten en el motor de Windows Installer. Aunque una opción podría ser escribir un montón de código que se ejecuta en el inicio que inspecciona el entorno en tiempo de ejecución, esto simplemente no funcionará si el archivo ejecutable en sí no está dañado o está dañado y, además, no sería capaz de reparar fácilmente el problema. Una solución mucho más eficaz es aprovechar el conocimiento de la configuración de la aplicación que ya está definida en el paquete de implementación.
La API de Windows Installer expone los mismos mecanismos para comprobar la integridad de una aplicación que usa cuando el usuario interactúa con el shell. Al usar estas llamadas API desde dentro de nuestra aplicación, podemos estar seguros de seguir logrando las mismas ventajas sin depender de los "puntos de entrada" del shell descritos anteriormente.
Esta es una lista de escenarios no cubiertos por las características de resistencia de integración de shell de Windows Installer:
- Aplicaciones que comienzan con el sistema operativo (ejecutar o ejecutar claves del Registro una vez)
- Servicios del sistema
- Tareas programadas
- Aplicaciones ejecutadas por otras aplicaciones
- Aplicaciones de línea de comandos
Estoy seguro de que hay muchos más escenarios que podríamos agregar a la lista anterior, pero creo que tienes la idea. En el ejemplo siguiente, mostraré cómo podemos obtener las ventajas de la resistencia de Windows Installer sin depender de las características de integración de shell que hemos analizado anteriormente. Cuando hayamos terminado, podrá tomar estos conceptos y aplicarlos fácilmente a casi cualquier escenario que implique ejecutar código ejecutable.
API de aplicación de claves
Antes de profundizar en algunos escenarios de ejemplo, echemos un vistazo a algunas de las API clave de Windows Installer que puede usar desde dentro de las aplicaciones. Para obtener información específica sobre el uso de cada una de estas API, consulte la Referencia de finction de Windows Installer en el SDK de plataforma.
Funciones clave de Windows Installer | Descripción |
---|---|
MsiProvideComponent | Recupera la ubicación instalada de un componente, instalando o reparando según sea necesario para asegurarse de que el componente está disponible. |
MsiQueryFeatureState | Devuelve el estado de instalación de una característica determinada. Por ejemplo, esta función indica si la característica está instalada, no instalada o anunciada. |
MsiQueryProductState | Devuelve el estado de instalación de un producto. Por ejemplo, esta función indica si el producto está instalado, anunciado, instalado para otro usuario o no instalado en absoluto. |
MsiConfigureProduct MsiConfigureProductEx |
Estas dos funciones permiten instalar o desinstalar una aplicación mediante programación. MsiConfigureProductEx proporciona un mayor control al permitirle especificar opciones similares a las que normalmente haría en la línea de comandos. |
MsiConfigureFeature | Esta función permite instalar, desinstalar o anunciar una característica específica de una aplicación. |
MsiGetUserInfo | Esta función devuelve el nombre del usuario, la organización y el número de serie del producto recopilados durante la secuencia de instalación del producto. |
MsiGetComponentPath MsiLocateComponent |
Estas dos funciones le ayudan a determinar la ubicación física de un archivo de componente o una clave del Registro en el sistema de destino. MsiGetComponentPath devuelve la ruta de acceso de la instancia del componente instalada por un producto específico, mientras que MsiLocateComponent devuelve la primera instancia de un componente instalado por CUALQUIER producto. |
Desafío 1: resistencia de Self-Invoked
Anteriormente hablamos de un escenario muy básico en el que podríamos eliminar realmente el ejecutable de la aplicación del sistema y usar un acceso directo para hacer que Windows Installer detecte y repare el problema volviendo a instalar el archivo que falta. Aunque ese escenario funcionó bien para demostrar la integración del shell que aprovecha Windows Installer, para profundizar en estos conceptos, vamos a echar un vistazo a un escenario ligeramente más sofisticado.
En este escenario, nuestra aplicación se compone de un único archivo .exe y varios archivos de texto que proporcionan información de configuración crítica a la aplicación.
El personal de soporte técnico de nuestra empresa hipotética de software ha estado recibiendo una gran cantidad de solicitudes de soporte técnico que revelan que Windows Installer no resuelve los problemas de configuración de la aplicación debido al hecho de que los usuarios ejecutan la aplicación directamente haciendo doble clic en el ejecutable en el Explorador de Windows en lugar de usar el acceso directo del menú Inicio creado por nuestra instalación.
Después de consultar con el experto en implementación residente del equipo, nuestro equipo de ingenieros decide que la aplicación se beneficiaría en gran medida al realizar su propia comprobación de resistencia en el inicio para asegurarse de que está configurado correctamente. Para ello, el equipo simplemente agrega una llamada a la API MsiProvideComponent para asegurarse de que los componentes críticos definidos en el paquete de instalación de la aplicación están correctamente instalados y configurados.
<DllImport("msi.dll")> _
Private Shared Function MsiProvideComponent(ByVal szProduct As String, ByVal _
szFeature As String, ByVal szComponent As String, ByVal dwInstallMode As _
MSI_REINSTALLMODE, ByVal lpPathBuf As System.Text.StringBuilder, ByRef _
pcchPathBuf As IntPtr) As Integer
End Function
Public Shared Function ProvideComponent(ByVal productCode As String, ByVal _
featureName As String, ByVal compID As String) As String
Dim iRet As Integer
Dim cbBuffer As Integer = 255
Dim buffer1 As New System.text.StringBuilder(cbBuffer)
Dim pSize As New IntPtr(cbBuffer)
iRet = MsiProvideComponent(productCode, featureName, compID, _
MSI_INSTALLMODE.INSTALLMODE_DEFAULT, buffer1, pSize)
Return buffer1.ToString
End Function
Para encapsular mejor este código, se agrega una nueva clase denominada WIHelper al proyecto para hospedar las declaraciones de métodos de la API de Windows Installer y los métodos contenedor. Llamar a este código era una cuestión sencilla de agregar algunas líneas al controlador de eventos Load del formulario principal.
Private CONST PRODUCTID As String = "PRODUCT_GUID_HERE"
Private CONST MAIN_FEATUREID As String = "DefaultFeatureKey"
Private CONST COMPID_1 As String = "COMP1_GUID_HERE"
Private CONST COMPID_2 As String = "COMP2_GUID_HERE"
Private Sub MainForm_Load() Handles MyBase.Load
If WIHelper.IsProductInstalled(PRODUCTID) Then
WIHelper.ProvideComponent(PRODUCTID, MAIN_FEATUREID, COMPID_1)
WIHelper.ProvideComponent(PRODUCTID, MAIN_FEATUREID, COMPID_2)
End If
End Sub
En el código de ejemplo anterior, primero estamos probando para ver si nuestra aplicación se ha instalado realmente a través de su paquete de instalación o no. Este es un concepto importante, ya que queremos asegurarnos de que, incluso si se depura en el entorno de desarrollo, la aplicación seguirá funcionando correctamente. Para ello, llamamos a un método en nuestra clase auxiliar llamada IsProductInstalled. A su vez, este método simplemente llama a MsiQueryProductState para determinar si el producto se ha instalado en el sistema. Si nuestra llamada a IsProductInstalled revela que nuestro producto se ha instalado, realizamos una serie de llamadas al método ProvideComponent en nuestra clase auxiliar. Este método es, de nuevo, un contenedor sencillo alrededor de la API MsiProvideComponent , que devuelve la ruta de acceso completa al componente especificado y garantiza que el componente está instalado y listo para su uso correctamente. Dependiendo de las necesidades de sus productos específicos, puede llamar al método ProvideComponent tantas veces como desee para asegurarse de que la aplicación esté totalmente disponible para el usuario.
Desafío 2: Instalación a petición
Nuestros hipotéticos ejecutivos de ventas han oído muchos comentarios de los clientes que les gustaría ver un conjunto de plantillas estándar entregadas con SimplePad. Aunque algunos clientes han expresado un fuerte deseo de esta característica, otros han expresado su preocupación por la instalación de datos extraños que la mayoría de sus usuarios pueden no necesitar.
Después de ensayar a los ingenieros cómo tratar este nuevo requisito, nuestro ingeniero de instalación intrépido salta y señala que Windows Installer puede manejarlo fácilmente con una pequeña cantidad de codificación adicional.
Después de una planeación rápida, el equipo decide que implementará un nuevo elemento de menú Plantillas en el menú Archivo de la aplicación. Si la característica de plantillas se instala localmente en el sistema del usuario, el usuario verá un menú flotante que enumera cada una de las plantillas disponibles y una opción para desinstalar la característica de plantillas. Si no se ha instalado la característica de plantillas, el menú flotante de plantillas tendrá una sola entrada, lo que permite al usuario instalar las plantillas adicionales.
Private Sub mnuFile_Popup(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles mnuFile.Popup
Dim newItem As MenuItem
With mnuTemplates.MenuItems
.Clear()
If WIHelper.IsFeatureInstalled(PRODUCTID, TEMPLATES_FEATUREID) Then
Dim dirInfo As New DirectoryInfo(Application.ExecutablePath)
For Each dirFile As FileInfo In dirInfo.Parent.GetFiles("*.tpl")
Dim mi As New MenuItem(Path.GetFileNameWithoutExtension(dirFile.Name))
AddHandler mi.Click, AddressOf OpenTemplate
.Add(mi)
Next
.Add("-")
newItem = New MenuItem("Uninstall Templates")
AddHandler newItem.Click, AddressOf UninstallTemplates
.Add(newItem)
Else
newItem = New MenuItem("Install Templates")
AddHandler newItem.Click, AddressOf InstallTemplates
.Add(newItem)
End If
End With
End Sub
Como puede ver, primero comprobamos si la característica de plantillas está instalada. Si es así, enumeramos los archivos de nuestra carpeta de aplicaciones que tienen la extensión "tpl" y agregamos el nombre de cada plantilla al menú. Si no es así, simplemente agregamos una opción para que el usuario instale las plantillas. Antes de examinarlo, veamos primero cómo se determina si la característica de plantillas está instalada.
<DllImport("msi.dll")> _
Private Shared Function MsiQueryFeatureState(ByVal szProduct As String,
ByVal szFeature As String) As MSI_INSTALLSTATE
End Function
Public Shared Function IsFeatureInstalled(ByVal pid As String, ByVal fid As String) As Boolean
Return MsiQueryFeatureState(pid, fid) = MSI_INSTALLSTATE.INSTALLSTATE_LOCAL
End Function
En esta función simple, simplemente llamamos a la función MsiQueryFeatureState de Windows Installer, pasando productCode de nuestra aplicación y el nombre de la característica que estamos preguntando. Si Windows Installer devuelve INSTALLSTATE_LOCAL, se devuelve true, ya que esto significa que la característica se instala localmente.
La instalación y desinstalación de nuestra característica de plantillas se realiza de la forma más sencilla.
<DllImport("msi.dll")> _
Private Shared Function MsiConfigureFeature(ByVal szProduct As String, ByVal szFeature As String, ByVal eInstallState As MSI_INSTALLSTATE) As Integer
End Function
Public Shared Function InstallFeature(ByVal pid As String, ByVal fid As String)
As Boolean
Return MsiConfigureFeature(pid, fid, MSI_INSTALLSTATE.INSTALLSTATE_LOCAL) = ERROR_SUCCESS
End Function
Public Shared Function UninstallFeature(ByVal pid As String, ByVal fid As String) As Boolean
Return MsiConfigureFeature(pid, fid,
MSI_INSTALLSTATE.INSTALLSTATE_ABSENT) = ERROR_SUCCESS
End Function
Cuando el usuario hace clic en el elemento de menú "Instalar plantillas", se realiza una llamada a MsiConfigureFeature con ProductCode, el nombre de la característica que queremos configurar y un valor de enumeración que indica que queremos instalar la característica localmente. El usuario verá que aparece brevemente un cuadro de diálogo de progreso de Windows Installer mientras se instala la característica de plantillas. Cuando desaparezca el cuadro de diálogo, las plantillas se instalarán y estarán listas para su uso. Cuando el usuario vuelve al menú Archivo, el submenú templates se rellenará con los nombres de las plantillas como se ha descrito anteriormente.
Conclusión
Aprovechar las características "gratuitas" y la API expuestas por Windows Installer nos proporciona algunas funcionalidades interesantes que van un largo camino hacia la reducción de los costos de soporte técnico, el aumento de la estabilidad de la aplicación y la mejora de la experiencia del usuario. Los ejemplos que se muestran aquí son algo triviales por naturaleza, pero espero que formen un gran punto de partida para implementar sus propias soluciones únicas. Hemos visto algunas de las API disponibles, pero sin duda no las hemos cubierto. Tómese algún tiempo para explorar todas las características de la API de Windows Installer y sé que te sorprenderá gratamente por la facilidad con la que puedes aprovechar estas características relativamente no asignadas de Windows Installer.
Acerca del autor
Michael Sanford es presidente y arquitecto jefe de software para 701 Software (http://www.701software.com). Antes de formar 701, Michael fue Presidente y CEO de ActiveInstall Corporation, que fue adquirido por Zero G Software. ActiveInstall ha logrado la notoriety para sus soluciones de creación de Windows Installer. Michael es desarrollador de soluciones certificadas por Microsoft (MCSD), ingeniero de sistemas certificados de Microsoft (MCSE) y MVP de Windows Installer. Puede leer el blog de Michael en http://msmvps.com/michael.