Partilhar via


Resiliência do aplicativo: desbloquear os recursos ocultos do Windows Installer

 

Michael Sanford
Software 701

Março de 2005

Resumo: O Windows Installer tem vários recursos que passaram despercebidos pela comunidade de desenvolvimento. Esses recursos permitem que um aplicativo se repare em runtime ou instale componentes opcionais com base na interação do usuário com o aplicativo. (10 páginas impressas)

Baixe o exemplo de integração do MSI Code.msi.

Sumário

Introdução
Resiliência por meio da integração do Shell
Apresentando a API do Windows Installer
APIs de aplicativo de chave
Desafio nº 1: resiliência Self-Invoked
Desafio nº 2: instalar sob demanda
Conclusão

Introdução

Como desenvolvedores, nós realmente tendemos a pensar em nossos aplicativos em execução em ambientes ideais, em sistemas ideais e por usuários ideais que estão usando o aplicativo após uma instalação bem-sucedida. A realidade é que, depois que nossos aplicativos tiverem sido instalados com êxito, seu tempo de vida para esse usuário apenas começou. Os desafios que nosso aplicativo pode enfrentar ao permanecer estável e funcional são muitos, mas a maioria dos aplicativos não está preparada para lidar com alterações no ambiente operacional que poderiam tornar o aplicativo inoperável.

O Windows Installer fornece recursos de resiliência que fizeram avanços significativos para manter nossos aplicativos estáveis, mas essa funcionalidade se baseia em determinadas ações que o usuário executa ao interagir com o shell para fornecer "pontos de entrada" por meio dos quais o Windows Installer pode detectar problemas com a configuração do aplicativo e tomar medidas para repará-lo.

Aqui está uma pequena lista de "pontos de entrada" do Windows Installer:

  • Atalhos. O Windows Installer apresenta um tipo especial de atalho que, embora transparente para o usuário, contém metadados adicionais que o Windows Installer usa por meio de sua integração de shell para verificar o estado da instalação do aplicativo especificado antes de iniciar o aplicativo.
  • Associações de arquivos. O Windows Installer fornece um mecanismo para interceptar chamadas para o aplicativo associado de um documento ou arquivo para que, quando um usuário abrir um documento ou arquivo usando o shell, o Windows Installer possa verificar o aplicativo antes de iniciar o aplicativo associado.
  • PUBLICIDADE COM. O Windows Installer fornece um mecanismo que é conectado ao subsistema COM, para que qualquer aplicativo que crie uma instância de um componente COM instalado pelo Windows Installer (e configurado para usar esse recurso) receba uma instância desse componente depois que o Windows Installer tiver verificado o estado da instalação desse componente.

Em determinadas circunstâncias, os recursos internos de resiliência do Windows Installer podem não ser capazes de detectar todos os problemas com a configuração do nosso aplicativo ou nosso aplicativo pode funcionar de forma que os pontos de entrada necessários não estejam sendo ativados. Felizmente, os caras inteligentes da equipe do Windows Installer entenderam esse desafio e disponibilizaram recursos adicionais de resiliência para nós por meio da rica API do Windows Installer.

Resiliência por meio da integração do Shell

Antes de passarmos para os recursos avançados de resiliência que a API do Windows Installer fornece, vamos dar uma olhada em um cenário típico de resiliência que geralmente obtemos gratuitamente quando implantamos nossos aplicativos com o Windows Installer.

Nesse cenário, estamos implantando um aplicativo de edição de texto simples que chamaremos de SimplePad. Para criar a instalação, vamos usar o Kit de Ferramentas WiX de Software Livre da Microsoft (para obter mais informações, consulte https://sourceforge.net/projects/wix/.), mas você pode fazer o mesmo com qualquer ferramenta de sua escolha.

<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 você pode ver no fragmento XML acima, criamos uma instalação muito simples com um arquivo (SimplePad.exe) e um único atalho localizado no menu Iniciar do usuário. É importante observar que, neste exemplo, o atalho que estamos criando é o ponto de entrada que o Windows Installer usará para detectar o estado do nosso aplicativo e repará-lo conforme necessário.

Neste ponto, podemos criar nosso instalador, instalar o aplicativo e usar o atalho de menu Iniciar recém-criado para executá-lo. Conforme esperado, o aplicativo funcionará exatamente como esperado. Para testar os recursos internos de resiliência do Windows Installer, podemos excluir o arquivo SimplePad.exe e tentar executar o aplicativo no atalho do menu Iniciar. Novamente, como esperado, o Windows Installer detecta que SimplePad.exe está ausente e um reparo é iniciado. Durante a operação de reparo, o Windows Installer lê as informações de configuração necessárias de sua cópia armazenada em cache internamente do pacote de instalação e, por fim, substitui o arquivo ausente, solicitando ao usuário a mídia de instalação de origem se ele não estiver presente no local original do qual ele foi instalado. Depois que a operação de reparo for concluída, o aplicativo será iniciado normalmente.

Figura 1. Operação de reparo em andamento

A resiliência do aplicativo também é fornecida pelo Windows Installer por meio de um par de outros mecanismos que vale a pena mencionar aqui também. O segundo método mais comum para garantir que os aplicativos permaneçam altamente disponíveis é por meio de associações de arquivos do Windows Installer. Esse mecanismo opera da mesma maneira que os atalhos do Windows Installer, mas, em vez de vincular diretamente ao arquivo executável de um aplicativo, a associação é feita por um tipo de arquivo registrado. Como você pode ver na Figura 2, as associações de arquivos do Windows Installer são definidas usando o mesmo mecanismo que as associações de arquivo padrão usam, com uma exceção. Observe na Figura 2 que um valor extra está listado na chave típica do Registro "shell\Open\command". O valor extra (também chamado de "comando") é onde o Windows Installer procura sempre que você clica duas vezes em um arquivo de dentro do shell do Windows. Essa cadeia de caracteres de aparência enigmática, às vezes conhecida como "Descritor darwiniano", é na verdade uma representação codificada de um produto, componente e recurso específicos. Se esse valor extra existir, o Windows Installer decodificará os dados e os usará para executar verificações nesse produto e componente. Se o componente estiver ausente ou corrompido, o Windows Installer iniciará um reparo para restaurar o arquivo ou os dados ausentes e, por fim, iniciará o aplicativo referenciado normalmente, passando as opções de linha de comando apropriadas para ele.

Figura 2. Exibindo um "Descritor Darwin" para uma associação de arquivos

O mecanismo de resiliência final que discutiremos hoje é comumente conhecido como Publicidade COM. Antes de examinarmos a mecânica do COM Advertising, é importante entender o caso de uso por trás dele. Digamos que você seja um fornecedor de componentes que fornece uma biblioteca compartilhada baseada em COM que fornece taxas postais em tempo real. Como esse componente pode ser usado por muitos produtos diferentes, ele é instalado em um único local compartilhado no sistema do usuário final. Para garantir que o componente sempre seja instalado no mesmo local e para garantir que o componente permaneça altamente disponível, envie-o para seus clientes em um Módulo de Mesclagem configurado corretamente para aproveitar a publicidade COM. É claro que, como sua solução é fornecida como um único arquivo .dll sem interface do usuário, os outros mecanismos de resiliência simplesmente não serão suficientes. Nesse caso, podemos contar com o COM Advertising para garantir que nosso componente permaneça instalado e registrado corretamente no sistema do usuário. Quando um aplicativo cria uma instância desse componente por meio de mecanismos COM normais, o Windows Installer "conecta" o processo da mesma maneira que vimos isso com associações de arquivo. Observe na Figura 3 que, desta vez, um "Descritor Darwin" é armazenado no valor do Registro InprocServer32 para o registro COM do componente. Novamente, essas informações são decodificadas e usadas pelo Windows Installer para garantir que nosso componente esteja instalado e configurado corretamente, executando os reparos conforme necessário, antes de finalmente retornar uma instância do componente para o aplicativo de chamada.

Vale ressaltar que esse recurso exclusivo funciona completamente independentemente do aplicativo usando o componente . Em outras palavras, mesmo que o aplicativo que usa o componente não tenha sido instalado usando o Windows Installer, o COM Advertising empregado pelo componente continuará funcionando corretamente, mesmo que o aplicativo de chamada seja apenas um VBScript.

Figura 3. Exibindo um "Descritor Darwin" para um servidor COM

Até agora, tudo o que falamos e demonstramos aproveitou os recursos do Windows Installer sem precisar escrever uma única linha de código, mas agora é hora de passar para uma implementação mais rica e robusta.

Apresentando a API do Windows Installer

O comportamento padrão do Windows Installer funcionou bem para nós no cenário anterior, mas muitas vezes, no mundo real, temos aplicativos um pouco mais sofisticados. Vamos expandir nosso cenário de exemplo para lidar com um cenário mais desafiador.

Geralmente, um aplicativo é composto por mais de um arquivo executável. Um exemplo pode ser um aplicativo que usa um executável de bootstrapper para marcar e instalar atualizações em um aplicativo, como visto no Bloco de Aplicativos do Atualizador. Nesse caso, o primeiro executável é aquele que é invocado quando o usuário clica em um atalho no menu Iniciar. Por sua vez, ele inicia o segundo executável que contém o main interface do usuário do seu aplicativo. Dependendo de como a instalação foi configurada, há uma boa chance de que os problemas com o executável do aplicativo main não sejam detectados pelo mecanismo do Windows Installer. Embora uma opção possa ser escrever um monte de código que é executado na inicialização que inspeciona o ambiente de runtime, isso simplesmente não funcionará se o executável em si estiver ausente ou corrompido e, além disso, não seria capaz de reparar facilmente o problema. Uma solução muito mais eficaz é aproveitar o conhecimento do Windows Installer sobre a configuração do aplicativo que já está definida no pacote de implantação.

A API do Windows Installer expõe os mesmos mecanismos para verificar a integridade de um aplicativo que ele usa quando o usuário interage com o shell. Usando essas chamadas à API de dentro de nosso aplicativo, podemos ter certeza de ainda obter os mesmos benefícios sem a dependência dos "pontos de entrada" do shell discutidos anteriormente.

Aqui está uma lista de cenários não cobertos pelos recursos de resiliência de integração de shell do Windows Installer:

  • Aplicativos que começam com o sistema operacional (executar ou executar chaves do Registro uma vez)
  • Serviços do sistema
  • Tarefas Agendadas
  • Aplicativos executados por outros aplicativos
  • Aplicativos de linha de comando

Tenho certeza que há muito mais cenários que poderíamos adicionar à lista acima, mas acho que você tem a idéia. No exemplo a seguir, demonstrarei como podemos obter os benefícios da resiliência do Windows Installer sem a dependência dos recursos de integração do shell discutidos anteriormente. Quando terminarmos, você poderá usar esses conceitos e aplicá-los facilmente a praticamente qualquer cenário que envolva a execução de código executável.

APIs de aplicativo de chave

Antes de nos aprofundarmos em alguns cenários de exemplo, vamos dar uma olhada em algumas das principais APIs do Windows Installer que você pode usar em seus aplicativos. Para obter informações específicas sobre o uso de cada uma dessas APIs, consulte a Referência de Finção do Windows Installer no SDK da Plataforma.

Principais funções do Windows Installer Descrição
MsiProvideComponent Recupera o local instalado de um componente, instalando ou reparando conforme necessário para garantir que o componente esteja disponível.
MsiQueryFeatureState Retorna o estado de instalação de um determinado recurso. Por exemplo, essa função informa se o recurso está instalado, não instalado ou anunciado.
MsiQueryProductState Retorna o estado de instalação de um produto. Por exemplo, essa função informa se o produto está instalado, anunciado, instalado para outro usuário ou não está instalado.
MsiConfigureProduct
MsiConfigureProductEx
Essas duas funções permitem que você instale ou desinstale programaticamente um aplicativo. MsiConfigureProductEx fornece maior controle, permitindo que você especifique opções semelhantes ao que você normalmente faria na linha de comando.
MsiConfigureFeature Essa função permite que você instale, desinstale ou anuncie um recurso específico de um aplicativo.
MsiGetUserInfo Essa função retorna o nome do usuário, da organização e do número de série do produto coletados durante a sequência de instalação do produto.
MsiGetComponentPath
MsiLocateComponent
Essas duas funções ajudam você a determinar a localização física de um arquivo de componente ou chave do Registro no sistema de destino. MsiGetComponentPath retorna o caminho da instância do componente instalada por um produto específico, enquanto MsiLocateComponent retorna a primeira instância de um componente instalado pelo produto ANY.

Desafio nº 1: resiliência Self-Invoked

Anteriormente, falamos sobre um cenário muito básico no qual poderíamos realmente excluir o executável do nosso aplicativo do sistema e usar um atalho para fazer com que o Windows Installer detecte e reparasse o problema reinstalando o arquivo ausente. Embora esse cenário tenha funcionado bem para demonstrar a integração de shell que o Windows Installer aproveita, para aprofundar esses conceitos, vamos dar uma olhada em um cenário um pouco mais sofisticado.

Nesse cenário, nosso aplicativo é composto por um único arquivo .exe e vários arquivos de texto que fornecem informações de configuração críticas para o aplicativo.

A equipe de suporte técnico da empresa de software hipotética vem recebendo muitas solicitações de suporte que revelam que os problemas de configuração do aplicativo não estão sendo resolvidos pelo Windows Installer devido ao fato de que os usuários estão executando o aplicativo diretamente clicando duas vezes no executável no Windows Explorer em vez de usar o atalho de menu Iniciar criado por nossa instalação.

Depois de consultar o especialista em implantação residente da equipe, nossa equipe de engenheiros decide que o aplicativo se beneficiaria muito executando sua própria resiliência marcar na inicialização para garantir que ele esteja configurado corretamente. Para fazer isso, a equipe simplesmente adiciona uma chamada à API MsiProvideComponent para garantir que os componentes críticos definidos no pacote de instalação do aplicativo sejam instalados e configurados corretamente.

<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 melhor esse código, uma nova Classe chamada WIHelper é adicionada ao projeto para abrigar declarações de método de API do Windows Installer e métodos wrapper. Chamar esse código era uma questão simples de adicionar algumas linhas ao manipulador de eventos Load do main formulário.

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
 

No código de exemplo acima, estamos primeiro testando para ver se nosso aplicativo foi realmente instalado por meio de seu pacote de instalação ou não. Esse é um conceito importante, pois queremos ter certeza de que, mesmo que estejamos depurando no ambiente de desenvolvimento, nosso aplicativo ainda funcionará corretamente. Para fazer isso, chamamos um método em nossa classe auxiliar chamada IsProductInstalled. Esse método, por sua vez, simplesmente chama MsiQueryProductState para determinar se o produto foi instalado no sistema. Se nossa chamada para IsProductInstalled revelar que nosso produto foi instalado, faremos uma série de chamadas para o método ProvideComponent em nossa classe auxiliar. Esse método é, novamente, um wrapper simples em torno da API MsiProvideComponent , que retorna o caminho completo para o componente especificado e garante que o componente esteja instalado corretamente e pronto para uso. Dependendo das necessidades de seus produtos específicos, você pode chamar o método ProvideComponent quantas vezes quiser para garantir que seu aplicativo esteja totalmente disponível para o usuário.

Desafio nº 2: Instalar sob Demanda

Nossos hipotéticos executivos de vendas de empresas têm ouvido muitos comentários dos clientes de que gostariam de ver um conjunto de modelos padrão entregues com o SimplePad. Embora alguns clientes tenham expressado um forte desejo por esse recurso, outros expressaram preocupação com a instalação de dados desnecessários que a maioria de seus usuários talvez não precise.

Depois de ouvir os engenheiros discutindo como lidar com esse novo requisito, nosso intrépido Engenheiro de Instalação entra e aponta que o Windows Installer pode facilmente lidar com isso com apenas uma pequena quantidade de codificação adicional.

Após um planejamento rápido, a equipe decide que implementará um novo item de menu Modelos no menu Arquivo do aplicativo. Se o recurso de modelos estiver instalado localmente no sistema do usuário, o usuário verá um menu suspenso listando cada um dos modelos disponíveis e uma opção para desinstalar o recurso de modelos. Se o recurso de modelos não tiver sido instalado, o menu suspenso de modelos terá uma única entrada, permitindo que o usuário instale os modelos adicionais.

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 você pode ver, primeiro marcar para ver se o recurso de modelos está instalado. Se for, enumeramos por meio dos arquivos em nossa pasta de aplicativos que têm a extensão "tpl" e adicionamos o nome de cada modelo ao menu. Se não estiver, simplesmente adicionaremos uma opção para o usuário instalar os modelos. Antes de examinarmos isso, vamos primeiro examinar como determinamos se o recurso de modelos está instalado.

<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

Nessa função simples, simplesmente chamamos a função MsiQueryFeatureState do Windows Installer, passando o ProductCode do aplicativo e o nome do recurso sobre o qual estamos consultando. Se o Windows Installer retornar INSTALLSTATE_LOCAL retornaremos true, pois isso significa que o recurso está instalado localmente.

Instalar e desinstalar nosso recurso de modelos é feito com a mesma facilidade.

<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

Quando o usuário clica no item de menu "Instalar Modelos", uma chamada é feita para MsiConfigureFeature com o ProductCode, o nome do recurso que desejamos configurar e um valor de enumeração indicando que queremos instalar o recurso localmente. O usuário verá uma caixa de diálogo de progresso do Windows Installer aparecer brevemente enquanto o recurso de modelos estiver instalado. Quando a caixa de diálogo desaparecer, os modelos serão instalados e prontos para uso. Quando o usuário voltar para o menu Arquivo, o submenu de modelos será preenchido com os nomes dos modelos, conforme descrito acima.

Conclusão

Aproveitar os recursos "gratuitos" e a API expostos pelo Windows Installer nos fornece alguns recursos interessantes que vão muito longe para reduzir os custos de suporte, aumentar a estabilidade do aplicativo e aprimorar a experiência do usuário. Os exemplos demonstrados aqui são um pouco triviais por natureza, mas esperamos formar um ótimo ponto de partida para implementar suas próprias soluções exclusivas. Examinamos algumas das APIs disponíveis, mas certamente não abordamos todas elas. Reserve algum tempo para explorar todos os recursos da API do Windows Installer e sei que você ficará agradavelmente surpreso com a facilidade com que pode aproveitar esses recursos relativamente inexplorados do Windows Installer.

 

Sobre o autor

Michael Sanford é presidente e arquiteto-chefe de software da 701 Software (http://www.701software.com). Antes de formar a 701, Michael era presidente e CEO da ActiveInstall Corporation, que foi adquirida pela Zero G Software. O ActiveInstall obteve notoriedade por suas soluções de Criação do Windows Installer. Michael é um MCSD (Microsoft Certified Solution Developer), um MCSE (Microsoft Certified Systems Engineer) e um MVP do Windows Installer. Você pode ler o Blog do Michael em http://msmvps.com/michael.