(NET) NET (2004 год)

DirMSI - просмотр содержимого инсталляционного пакета. Сгрузить MSI-файл.


Я давно и подробно знаком со всевозможными технологиями инсталляции программ, но однажды мне потребовалось заглянуть в приличного размера MSI-файл, чтобы выяснить какие сборки и куда из него будут устанавливаться. Обычно такие операции можно делать с помощью ORCA, однако уж как-то больно сложно и неудобно экспортировать оттуда таблы и IDT-формат, потом открывать их Excel'ом - да и то, что получается в итоге - далеко от совершенства. Для пакетов, составляющих сотни сборок, практически невозможно понять что и куда устанавливается.

Это сподвигло меня на написание описанной ниже утилитки.


В принципе, прочитать самому MSI-файл проще простого, для этого надо импортировать функции MSI.DLL примерно вот так:

00001: Declare Auto Function MsiOpenDatabase Lib "msi.dll" (ByVal szDatabasePath As String, ByVal szPersist As String, ByRef phDatabase As Long) As UInt32
00002: Declare Auto Function MsiCloseHandle Lib "msi.dll" (ByVal Handler As Long) As UInt32
00003: Declare Auto Function MsiDatabaseApplyTransform Lib "msi.dll" (ByVal phDatabase As Long, ByVal szTransformFile As String, ByVal iErrorConditions As Integer) As UInt32
...

Затем надо из директории C:\Program Files\Microsoft Platform SDK\Include импортировать файлы .H c определением всех флагов вызова узказанных функций, MsiQuery.h, msi.h, и далее просто работать с этими функциями и флагами:

00001:        Dim Handler As UInt32
00002:        Dim Result As UInt32 = MsiOpenDatabase("F:\Visual Studio 2005\Projects\WinDump\Setup_WinDump\Debug\Setup_WinDump.msi", Nothing, handler)
...
00003:        Result = MsiCloseHandle(Handler)
но я сделал проще и быстрее. Я сгрузил отсюда готовый враппер к MSI.DLL, в который добрые люди УЖЕ импортировали все эти описания и написал свою прогу на этом враппере.

На тот случай, если страничка с определением этих функций как всегда куда-нибудь переедет, ознакомиться с ними можно тут. А тут можно посмотреть описания всех таблиц базы инсталлера. Если же проект управляемого враппера вокруг MSI.DLL тоже куда-нибудь переедет, то его сборку можно сгрузить отсюда.


Теперь пару слов по технике построения проги. Обратите внимание, что я сделал ее на Дженериках. Это новая фишка NET2, которая пришла на смену столь любимым мною типизированным спецколлекциям. Безусловно, я бы мог вместо 3-х структур подготовить 88 аналогичных классов с описаниями всех таблиц MSI-базы, примерно так же, как я сделал в утилите WinDump. И добавить в каждый класс метод его сохранения в базу. Однако, такая задача у меня не стояла. Вы вполне можете это сделать сами при необходимости. Все необходимое для этого (чтение списка таблиц) в проге уже предусмотрено. На базе этой проги при желании можно даже сделать ODBC-драйвер, чтобы читать MSI-файл как простую SQL-базу, но опять же у меня такой необходимости не было.

На рисунке ниже показано, как отрабатывает прога для инсталляционного файла XMLNotepad.




00001: ''' <summary>
00002: ''' Содержимое MSI-файла (С) VBNET2000."
00003: ''' </summary>
00004: Module Module1
00005:     Sub Main()
00006:         Console.WriteLine("Содержимое MSI-файла (С) VBNET2000.")
00007:         Console.WriteLine()
00008:         Dim PRM As Collections.ObjectModel.ReadOnlyCollection(Of String) = My.Application.CommandLineArgs
00009:         'Название обрабатываемого MSI-файла берем из параметра
00010:         If PRM.Count = 0 Then
00011:             Console.WriteLine("Программа требует параметр - наименование простматриваемого MSI-файла.")
00012:             Console.WriteLine("Если имя файла содержит пробелы, то имя надо взять в кавычки.")
00013:             Console.ReadLine()
00014:             Exit Sub
00015:         End If
00016:         If Not System.IO.File.Exists(PRM(0)) Then
00017:             Console.WriteLine("Файл - " & PRM(0) & " не существует.")
00018:             Console.ReadLine()
00019:             Exit Sub
00020:         End If
00021:         Dim MSI_File As New Pahvant.MSI.Database(PRM(0), Pahvant.MSI.Database.OpenMode.ReadOnly)
00022:         'Сначала прочитаем ВСЕ таблицы, содержащиеся в MSI-файле
00023:         Dim Content_View As New Pahvant.MSI.View(MSI_File, "select * from _Tables")
00024:         Content_View.Execute()
00025:         Dim One As New Pahvant.MSI.Record(2)
00026:         Dim Contents As Collections.Generic.IList(Of String) = New Collections.Generic.List(Of String)
00027:         Do While True
00028:             One = Content_View.Fetch
00029:             If One Is Nothing Then Exit Do
00030:             Contents.Add(One.GetString(1))
00031:         Loop
00032:         Content_View.Close()
00033:         'На самом деле для этой конкретной проги чтение оглавления MSI-файла (те всех таблиц) не обязательно - только для проверки наличия таблиц
00034:         If Not Contents.Contains("File") Or Not Contents.Contains("Component") Or Not Contents.Contains("Directory") Then
00035:             Console.WriteLine("MSI-файл не содержит таблиц File, Directory, Component и не может быть обработан.")
00036:             Console.ReadLine()
00037:             Exit Sub
00038:         End If
00039:         'Читаем таблу File
00040:         Dim Files As Collections.Generic.IList(Of File) = New Collections.Generic.List(Of File)
00041:         Dim Table_View As New Pahvant.MSI.View(MSI_File, "select Component_,FileName,FileSize,Version from File")
00042:         Table_View.Execute()
00043:         Do While True
00044:             One = Table_View.Fetch
00045:             If One Is Nothing Then Exit Do
00046:             Files.Add(New File(One.GetString(1), One.GetString(2), One.GetInteger(3), One.GetString(4)))
00047:         Loop
00048:         Table_View.Close()
00049:         'Читаем таблу Component
00050:         Dim Components As Collections.Generic.IList(Of Component) = New Collections.Generic.List(Of Component)
00051:         Table_View = New Pahvant.MSI.View(MSI_File, "select Component,Directory_ from Component")
00052:         Table_View.Execute()
00053:         Do While True
00054:             One = Table_View.Fetch
00055:             If One Is Nothing Then Exit Do
00056:             Components.Add(New Component(One.GetString(1), One.GetString(2)))
00057:         Loop
00058:         Table_View.Close()
00059:         'Читаем таблу Directory
00060:         Dim Directories As Collections.Generic.IList(Of Dir) = New Collections.Generic.List(Of Dir)
00061:         Table_View = New Pahvant.MSI.View(MSI_File, "select * from Directory")
00062:         Table_View.Execute()
00063:         Do While True
00064:             One = Table_View.Fetch
00065:             If One Is Nothing Then Exit Do
00066:             Directories.Add(New Dir(One.GetString(1), One.GetString(2), One.GetString(3)))
00067:         Loop
00068:         Table_View.Close()
00069:         'Выводим реляционную структуру на печать
00070:         For Each F As File In Files
00071:             For Each C As Component In Components
00072:                 If C.ID = F.ToComponentID Then
00073:                     For Each D As Dir In Directories
00074:                         If D.ID = C.ToDir Then
00075:                             GetParentDir(Directories, D, F)
00076:                             Console.WriteLine(F.FullPath & F.FileName & ", version (" & F.Version & "), size (" & F.FileSize.ToString & ")")
00077:                             GoTo OK
00078:                         End If
00079:                     Next
00080:                     Console.WriteLine("Ошибка. Не найдена директория <" & C.ToDir & "> для файла <" & F.FileName)
00081:                     Console.ReadLine()
00082:                 End If
00083:             Next
00084:             Console.WriteLine("Ошибка. Не найден компонент <" & F.ToComponentID & "> для файла <" & F.FileName)
00085:             Console.ReadLine()
00086: ok:
00087:         Next
00088:         Console.ReadLine()
00089:     End Sub
00090: 
00091:     ''' <summary>
00092:     ''' Рекурсивная прога расчета директории - на вход принимает таблу со списом директорий, текущую директорию и место, куда надо накапливать Path
00093:     ''' </summary>
00094:     Private Sub GetParentDir(ByVal Directories As Collections.Generic.IList(Of Dir), ByVal Current As Dir, ByRef F As File)
00095:         If Current.Parent = "" Then
00096:             F.FullPath = "[" & Current.Dir & "]\" & F.FullPath
00097:         Else
00098:             For Each Z As Dir In Directories
00099:                 If Z.ID = Current.Parent Then
00100:                     F.FullPath = Current.Dir & "\" & F.FullPath
00101:                     GetParentDir(Directories, Z, F)
00102:                     Exit Sub
00103:                 End If
00104:             Next
00105:             F.FullPath = Current.Dir & "\" & F.FullPath
00106:         End If
00107:     End Sub
00108: End Module
00109: 
00110: ''' <summary>
00111: ''' Одна запись об инсталлируемом файле
00112: ''' </summary>
00113: Friend Structure File
00114:     Dim FileName As String
00115:     Dim FileSize As Integer
00116:     Dim Version As String
00117:     Dim ToComponentID As String
00118:     Dim FullPath As String
00119:     Public Sub New(ByVal a_ToComponentID As String, ByVal a_FileName As String, ByVal a_FileSize As Integer, ByVal a_Version As String)
00120:         FileName = a_FileName.Substring(a_FileName.IndexOf("|") + 1)
00121:         ToComponentID = a_ToComponentID
00122:         FileSize = a_FileSize
00123:         Version = a_Version
00124:     End Sub
00125: End Structure
00126: 
00127: ''' <summary>
00128: ''' Таблица отношений между компонентами, директориями и файлами
00129: ''' </summary>
00130: Friend Structure Component
00131:     Dim ID As String
00132:     Dim ToDir As String
00133:     Public Sub New(ByVal a_ID As String, ByVal a_ToDir As String)
00134:         ID = a_ID
00135:         ToDir = a_ToDir
00136:     End Sub
00137: End Structure
00138: 
00139: ''' <summary>
00140: ''' Одна запись о директории
00141: ''' </summary>
00142: Friend Structure Dir
00143:     Dim ID As String
00144:     Dim Parent As String
00145:     Dim Dir As String
00146:     Public Sub New(ByVal a_Directory As String, ByVal a_ParentDir As String, ByVal a_DefaultDir As String)
00147:         ID = a_Directory
00148:         Parent = a_ParentDir
00149:         Dir = CStr(IIf(a_DefaultDir.IndexOf("|") > 0, a_DefaultDir.Substring(a_DefaultDir.IndexOf("|") + 1), a_DefaultDir))
00150:     End Sub
00151: End Structure

Утилиту можно использовать не только для изучения полученный извне новых MSI-файлов, но и для инспекции системы на предмет кто и зачем инсталлировал тот или иной файл в систему. Для этого ее рекументуется применять совместно с утилитой фиксации состояния системы. Например, я просмотрел все установленные в моей системе 195 пакетов программ и получил в виде текстового файла список всех установленные когда либо в мою систему файлы в разрезе инсталляционных пакетов.



Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/windows/DirMSI/index.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>