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 пакетов программ и получил в виде текстового файла список всех установленные когда либо в мою систему файлы в разрезе инсталляционных пакетов.
<SITEMAP> <MVC> <ASP> <NET> <DATA> <KIOSK> <FLEX> <SQL> <NOTES> <LINUX> <MONO> <FREEWARE> <DOCS> <ENG> <CHAT ME> <ABOUT ME> < THANKS ME> |