Comprimere/Decomprimere archivi Cabinet con gli assembly di Microsoft WiX

Premessa
Windows Installer XML Toolkit (WiX) è un progetto open-source supportato e sponsorizzato da Microsoft per la realizzazione di procedure di installazione basate su Windows Installer, sotto forma di pacchetti di installazione in formato .Msi.
WiX è costituito da alcuni strumenti a riga di comando e alcuni assembly in formato .dll che, tramite la scrittura di complessi documenti XML, consentono di compilare la predetta tipologia di pacchetti di installazione in maniera totalmente gratuita. Alcuni degli assembly che costituiscono WiX sono interamente scritti in codice managed, altri, invece, sono deiwrapper per l’interoperabilità con alcuni servizi COM del sistema operativo.

La produzione di pacchetti di installazione per Windows Installer prevede un’importante caratteristica, quella di implementare la compressione dei file da distribuire sotto forma di archivi compressi in formato Cabinet (.cab), un algoritmo sviluppato da Microsoft, molto diffuso, quasi uno standard nella compressione dei file. Ciò comporta che anche alcuni assembly di WiX implementino classi per la compressione/decompressione di questo particolare formato di file. Senza soffermarci sull’utilizzo per il quale WiX è stato progettato, cioè la realizzazione di pacchetti di installazione per Windows Installer utilizzando la sintassi XML, in questo articolo ci proponiamo di illustrare come richiamare le classi e i metodi implementati negli assembly di WiX per utilizzare la compressione/decompressione di file .cab nelle nostre applicazioni, tenuto conto del fatto che WiX è un progetto open-sourcee che, pertanto, i suoi componenti possono essere utilizzati gratuitamente.

Prima di proseguire nella lettura è, ovviamente, necessario che vi dotiate di WiX. Potete scaricarlo come pacchetto autonomo o come parte del Visual Studio 2005 SDK, reperibile a questo indirizzo.

Gli assembly di WiX da utilizzare 
Una volta installato WiX, localizzate la sua directory. Nel mio caso, avendolo scaricato come parte del Visual Studio 2005 SDK, si trova in C:\Programmi\Visual Studio 2005 SDK\anno.mese\VisualStudioIntegration\Tools\WiX. Curiosando nella directory potrete facilmente notare diversi strumenti a riga di comando (tra cui il compilatore di pacchettiMSICandle.exe) e alcune librerie .dll. Tra queste, utilizzeremo wix.dll e winterop.dll. La prima è la libreria di classi gestita vera e propria, che implementa le funzionalità di compressione/decompressione degli archivi .cab. La seconda è quella che permette a wix.dll di interagire con alcuni servizi COM del sistema operativo.

Le classi esposte dagli assembly di WiX non consentono, purtroppo, un controllo particolarmente approfondito e dettagliato sugli archivi Cabinet. Ad esempio, non è possibile ottenere il numero dei file contenuti in un archivio compresso. Tuttavia costituiscono un sistema rapido, veloce e, soprattutto, gratuito per implementare nelle proprie applicazioni la possibilità di creare e gestire archivi compressi in uno dei formati più utilizzati al mondo.

Un’applicazione di esempio: CabinetDemo 
Per meglio spiegare l’argomento di questo articolo, ho predisposto una piccola applicazione Windows Forms di esempio scritta in Visual Basic 2005, chiamata CabinetDemo e scaricabile dall’area Download di Visual Basic Tips & Tricks. Sebbene l’interfaccia grafica non sia particolarmente accattivante, non è su questa che dovete porre la vostra attenzione, bensì su alcuni fondamentali passaggi preliminari e sulla successiva implementazione del codice.
L’applicazione di esempio è costituita da un’unica finestra, che contiene un controllo ListView che conterrà l’elenco dei file da aggiungere all’archivio compresso, un controlloComboBox che permetterà di specificare il livello di compressione dell’archivio e tre pulsanti, uno per l’aggiunta dei file all’archivio, uno per la creazione dell’archivio e uno per l’estrazione del contenuto di archivi esistenti.

Dando per scontato che abbiate scaricato l’applicazione e l’abbiate aperta in Visual Studio 2005 (o Visual Basic Express), passiamo a descrivere le operazioni preliminari.

Operazioni preliminari: aggiunta di riferimenti e inclusione di file 
Come accennato in precedenza, per utilizzare da codice la compressione/decompressione di archivi .cab implementata dagli assembly di WiX, faremo riferimento alle libreriewix.dll e winterop.dll. Ciò posto, qualunque sia il tipo di applicazione che creerete, le operazioni fondamentali da eseguire sono le seguenti:

  1. Aggiunta di un riferimento all’assembly wix.dll;
    questo consentirà all’applicazione di utilizzare le classi esposte da wix.dll;
  2. Inclusione nella soluzione del file winterop.dll;
    poiché in fase di esecuzione wix.dll va alla ricerca di winterop.dll, dovreste ricordarvi di copiare manualmente il file winterop.dll nella cartella bin\debug o bin\release (in caso contrario otterreste delle spiacevoli eccezioni). Onde sopperire a questo inconveniente, nella finestra Esplora Soluzioni potete fare click destro sul nome del progetto, selezionare “Aggiungi elemento esistente” dal menu contestuale, quindi, dalla finestra di dialogo, andare a selezionare il file winterop.dll nella cartella dove avete precedentemente trovato WiX. Quando Visual Studio aggiunge il file al progetto, fateci clic sopra e, nella Finestra delle Proprietà, impostate la proprietà “Copia nella directory di output” su “Copia sempre“. Così facendo, sarete sicuri che tutti i file necessari saranno presenti nella cartella ove risiede il vostro eseguibile.

Nota bene: nello zip contenente il progetto non ho incluso i file wix.dll e winterop.dll per due motivi; il primo è che così ho ridotto le dimensioni dello zip, il secondo è che io potrei avere una versione diversa degli assembly da quella che potete avere voi. Sebbene, come sapete, questo in .NET non costituisca un grosso problema, è pregferibile che vi dotiate della versione più recente e che compiate manualmente i passaggi sopra esposti anche come esercizio.

Ancora un pizzico di teoria 
Una volta aggiunto il riferimento a wix.dll, siamo pronti per utilizzare la compressione/decompressione di file .cab. L’assembly espone un namespace chiamatoMicrosoft.WindowsInstallerXml.Cab, che espone, a sua volta, le due classi principali che andremo ad utilizzare: WiXCreateCab e WiXExtractCabWiX consente, inoltre, di specificare il livello di compressione da applicare agli archivi compressi, mediante l’enumerazione CompressionLevel, che prevede i seguenti livelli: alto, medio, basso, compatibilità zip, nessuno. Sono previste anche due eccezioni custom, WiXCabCreationException e WiXCabExtractionException, che utilizzeremo per gestire gli eventuali errori.

Il codice 
Passiamo ora ad esaminare il codice dell’applicazione di esempio. Ho inserito al suo interno alcuni commenti, in modo che possiate capire meglio durante il suo esame, ma faremo ulteriori considerazioni al termine di ogni paragrafo. Iniziamo aggiungendo la seguente direttiva Imports all’inizio del codice, per abbreviare le chiamate al namespaceMicrosoft.WindowsInstallerXml.Cab:

Imports Microsoft.Tools.WindowsInstallerXml

Fatto questo, ci addentriamo subito nell’utilizzo di WiX, esaminando il codice per estrarre il contenuto di un archivio .cab esistente. Eccolo:

  Public Class Form1

    Private compression As Cab.CompressionLevel  'memorizza il livello di compressione da applicare

    Private Sub ExtractCabButton_Click(ByVal sender As System.Object, _
                                       ByVal e As System.EventArgs) Handles ExtractCabButton.Click

      Dim archiveName As String  'variabile per memorizzare il nome dell'archivio da estrarre

      'Mostra la finestra di dialogo per selezionare il file .cab
      With OFD1
        .Title = "Seleziona un archivio CAB"
        .Filter = "Archivi Cabinet|*.CAB|Tutti i file|*.*"
        .Multiselect = False

        'Se l'utente clicca OK e il nome del file non è vuoto
        If .ShowDialog = Windows.Forms.DialogResult.OK _
            AndAlso String.IsNullOrEmpty(.FileName) = False Then

          'memorizza il nome del file
          archiveName = .FileName

          'mostra la finestra di dialogo per selezionare la directory di destinazione
          With FBD1
            'Se l'utente clicca OK e la directory non è vuota
            If .ShowDialog = Windows.Forms.DialogResult.OK _
                And String.IsNullOrEmpty(.SelectedPath) = False Then

              Try

                'istanzia un oggetto WixExtractCab
                Dim myCab As New Cab.WixExtractCab

                'estrae il contenuto dell'archivio nel percorso specificato
                myCab.Extract(archiveName, .SelectedPath)

                'rilascia l'oggetto
                myCab.Close()

                'apre Esplora Risorse per mostrare l'esito dell'operazione
                Process.Start("Explorer.exe", .SelectedPath)

              Catch ex As WixCabExtractionException
                MessageBox.Show("Errore durante l'estrazione dell'archivio: " & _
                  ex.InnerException.ToString)
              Catch ex As Exception
                MessageBox.Show(ex.ToString)
              End Try

            End If
          End With

        End If
      End With
    End Sub
  End Class

Come potete vedere, abbiamo in primo luogo dichiarato una variabile compression di tipo CompressionLevel che ci servirà successivamente per la creazione di archivi. Trattandosi di una dichiarazione, l’abbiamo inserita all’inizio del codice.

In sintesi, il codice mostra una finestra di dialogo dalla quale selezionare l’archivio .cab da estrarre. Se l’utente clicca su OK e la stringa contenente il nome del file non è vuota, viene mostrata un’altra finestra di dialogo dove andrà specificata la directory di destinazione. Se l’utente clicca su OK ed ha correttamente specificato una directory, viene avviato il processo.
Fate attenzione al blocco Try…End Try, che svolge il lavoro di estrazione. Si istanzia un oggetto myCab di tipo WixExtractCab. Di questo oggetto è sufficiente richiamare il metodoExtract passando come argomenti il nome dell’archivio da estrarre e la directory di destinazione. Al termine, il metodo Close permette di chiudere il file. Con sole tre righe di codice è stato estratto il contenuto dell’archivio.

Come vedremo tra breve, il codice per la creazione di un archivio compresso è leggermente più lungo ma non difficile. Nota bene: potreste ottenere dei messaggi di errore se utilizzate nomi lunghi per i nomi dei file o delle directory, comprensivi di spazi. Vi consiglio di utilizzare nomi senza spazi, oppure di prevedere l’utilizzo della funzione APIGetShortPathname per memorizzare in una stringa il nome del file e/o directory nel formato 8.3. Attenzione anche all’utilizzo in Windows Vista, poiché l’User Access Controlpotrebbe impedire la scrittura dei file su disco.

Creare un archivio compresso 
Il codice dell’applicazione di esempio, volto alla creazione di un archivio compresso, può essere suddiviso in due parti: la prima, che consente di aggiungere un elenco di file a una ListView; la seconda, che legge l’elenco dei file dalla ListView e crea un archivio compresso basato su tale elenco. Ecco il codice della prima parte:

  Private Sub AddFilesButton_Click(ByVal sender As System.Object, _
                                   ByVal e As System.EventArgs) Handles AddFilesButton.Click

    'Aggiunge i file alla listbox. E' possibile selezionare più file contemporaneamente
    '(proprietà MultiSelect)
    With OFD1
      .Multiselect = True
      .Title = "Seleziona i file da aggiungere all'archivio"
      .Filter = "Tutti i file|*.*"

      'se l'utente clicca OK e la matrice contiene almeno un file
      If .ShowDialog = Windows.Forms.DialogResult.OK AndAlso .FileNames.Length > 0 Then

        'aggiunge ogni file nella matrice alla listbox
        For Each nameFile As String In .FileNames
          ListView1.Items.Add(nameFile)
        Next

      End If

    End With
  End Sub

Notate la proprietà Multiselect del controllo OpenFileDialog impostata su True, in modo da consentire la selezione multipla di file e il ciclo For…Each che legge il contenuto della matrice di stringhe contenente i file selezionati ed aggiunge ciascun file alla ListView. Come esercizio, potreste migliorare questo frammento implementando del codice che impedisca l’aggiunta di nomi duplicati.

Il codice seguente, invece, crea un archivio compresso a partire dall’elenco di file specificati. Si tenga presente che vengono utilizzati alcuni valori assegnati al controlloComboBox e impostati a design-time, per i quali vi rimando al sorgente accluso:

  Private Sub CreateCabButton_Click(ByVal sender As System.Object, _
                                    ByVal e As System.EventArgs) Handles CreateCabButton.Click

    'Mostra la finestra di dialogo per specificare il nuovo archivio
    With SFD1
      .Title = "Seleziona il nome per l'archivio CAB"
      .Filter = "Archivi .cab|*.cab"

      'Se l'utente clicca OK e il nome del file non è vuoto
      If .ShowDialog = Windows.Forms.DialogResult.OK _
            AndAlso String.IsNullOrEmpty(.FileName) = False Then

        'legge il contenuto della ComboBox relativo al livello di compressione e lo memorizza
        Select Case ComboBox1.Text
          Case Is = "Alto"
            compression = Cab.CompressionLevel.High
          Case Is = "Medio"
            compression = Cab.CompressionLevel.Medium
          Case Is = "Basso"
            compression = Cab.CompressionLevel.Low
          Case Is = "Compatibilità Zip"
            compression = Cab.CompressionLevel.Mszip
          Case Is = "Nessuno"
            compression = Cab.CompressionLevel.None
        End Select

        Try
          'istanzia un nuovo oggetto WixCreateCab, passando il nome del file specificato, 
          'la sua directory, le sue dimensioni massime, il livello di compressione
          Dim myCab As New Cab.WixCreateCab(IO.Path.GetFileName(.FileName), _
                                            IO.Path.GetDirectoryName(.FileName), _
                                            Integer.MaxValue, Integer.MaxValue, compression)

          'per aggiungere file all'archivio è necessario specificare, per ciascun file,
          'il percorso e un suo identificatore. Questo avviene tramite due matrici di stringhe
          Dim AF(), IDs() As String

          ReDim AF(ListView1.Items.Count - 1), IDs(ListView1.Items.Count - 1)

          'Per ogni elemento della listbox scrive il corrispondente valore nelle matrici
          For Each NewItem As ListViewItem In ListView1.Items

            AF(NewItem.Index) = NewItem.Text
            IDs(NewItem.Index) = NewItem.Text

          Next

          'aggiunge i file all'archivio..
          myCab.AddFiles(AF, IDs)

          '..e rilascia le risorse
          myCab.Close()

        Catch ex As WixCabCreationException
          MessageBox.Show("Errore durante la creazione dell'archivio .cab: " & _
                          ex.InnerException.ToString)
        Catch ex As Exception
          MessageBox.Show(ex.ToString)
        End Try

      End If
    End With
  End Sub

Concettualmente il codice, per il quale ho inserito appositi commenti, esegue i seguenti passaggi:

  1. richiede di specificare un nome per il file da scrivere;
  2. memorizza il livello di compressione specificato nella ComboBox in un oggetto di tipo CompressionLevel;
  3. istanzia un oggetto myCab di tipo WixCreateCab, al quale va passato il nome dell’archivio (senza la directory), la directory dove va scritto il file, le dimensioni massime dell’archivio in byte (io ho usato Integer.MaxValue ma potete specificare un intero a seconda delle vostre necessità, ad esempio la dimensione di un floppy), il livello di compressione;
  4. genera due matrici di stringhe; ogni elemento della matrice rappresenta, rispettivamente, il nome del file e un suo identificatore all’interno dell’archivio, che viaggiano come su due binari paralleli. Per comodità il mio codice assegna il nome dei file a entrambe le matrici, ma voi potreste, ad esempio, assegnare come identificatore l’indice della ListView corrispondente al file.
  5. aggiunge all’archivio i file specificati comprimendoli, a partire dalle due matrici generate (metodo AddFiles);
  6. rilascia le risorse tramite il metodo Close;
  7. qualora si verifichino errori, viene intercettata un’eccezione di tipo WixCabCreationException, specifica per gli errori relativi alla creazione di archivi compressi.

Se volete verificare la validità del risultato raggiunto, aprite l’archivio compresso con altre utilità come WinZip(r). Vedrete che l’archivio viene interpretato correttamente.

Documentazione
Potete fare riferimento al sito di WiX per leggere ulteriore documentazione su questo strumento, oppure la guida del Visual Studio 2005 SDK se avete installato WiX scegliendo questa modalità.

Both comments and pings are currently closed.

Comments are closed.