【问题标题】:Extracting Binary Data from MSI从 MSI 中提取二进制数据
【发布时间】:2018-01-26 13:58:12
【问题描述】:

我正在尝试使用 Powershell 从 MSI 文件中提取二进制数据。 我可以获取任何其他数据,但我似乎无法提取二进制信息。

$Query = "SELECT Data FROM Binary WHERE Name = 'bannrbmp'"
$View = $Database.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $Database, ($Query))
$View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
$Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)

$BinaryData = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1)

它在最后一行中断,这让我相信“StringData”存在问题,但我可能会偏离目标。这是这张桌子在 Orca 中打开时的样子。

当提取文本数据时,此代码将成功完成,如下所示。

$Query = "SELECT Component FROM FeatureComponents WHERE Feature = 'OrcaHelp'"
$View = $Database.GetType().InvokeMember("OpenView", "InvokeMethod", $null, $Database, ($Query))
$View.GetType().InvokeMember("Execute", "InvokeMethod", $null, $View, $null)
$Record = $View.GetType().InvokeMember("Fetch", "InvokeMethod", $null, $View, $null)

$Data = $Record.GetType().InvokeMember("StringData", "GetProperty", $null, $Record, 1)

我似乎在网上找不到任何东西,如果有人能够提供帮助,将不胜感激。

【问题讨论】:

  • 你是什么意思它在最后一行中断?伴随此有错误吗?
  • bannerbmp(基于 banner 和 bmp)很可能是位图。还有其他几个包含 ico,这表明它们是图标。为什么您希望位图或图标支持StringData?就此而言,您为什么希望 any 二进制数据自动支持StringData?不是字符串数据,是二进制数据。
  • 看看 Record.ReadStream。但老实说,所有这些 COM 互操作在 IMO 中都非常混乱,而且您并没有关闭句柄。如果您有兴趣,我可以为您指出其他方法。
  • Matt 它只是抛出一个异常,什么也没说。 Ken,我正在尝试从 MSI 中的 exe 中提取图标,我知道它不是字符串数据。 @ChristopherPainter 我很想知道我可以用来解决这个问题的任何方法。
  • 其他人做对了 - 二进制数据是使用 ReadStream 类型的 API 提取的。

标签: powershell windows-installer


【解决方案1】:

这是一个提取二进制文件并将其写入磁盘的 C++ 代码片段。您可以使用它,或者至少查看流和读取的流。这些是所有脚本语言最终调用的基本 Win32 API 调用,不需要互操作。它需要包含一些 stdio.h、windows.h、msiquery.h 和打包在程序或 Dll 中才能调用 - 我不知道你的 C++ 舒适度。这应该可以正常工作,即使我最近没有测试过。

PMSIHANDLE hDatabase;
PMSIHANDLE hBinaryView;
PMSIHANDLE hBinaryRecord;

//Get the handle to the active database. we need this to do view manipulation
UINT nr = MsiOpenDatabase ("some.msi", MSIDBOPEN_READONLY, &hDatabase);

//Get a view of the binary table based on the SQL Query
char sQuery []  = {"SELECT * FROM Binary WHERE Name='somebinary'"}; // Binary

nr = MsiDatabaseOpenView(hDatabase, sQuery, &hBinaryView);
if (nr!= ERROR_SUCCESS)
   return 1;

//MsiViewExecute Needs to to be called for MsiFetchView.
//We pass it null because the query above is as granular as we can get
//so we do not need to take it further by specifying an additional value.
nr = MsiViewExecute(hBinaryView, NULL);  
if (nr == ERROR_SUCCESS)
    //Fetch the view into a record.  We do this because we can only do
    //streams out of a record and not out of the view.
    nr = MsiViewFetch(hBinaryView, &hBinaryRecord);

//Make sure that the entry was found in the table
if (nr == ERROR_SUCCESS)
{

    //Build the path to write the file to
    TCHAR FileName [MAX_PATH] = {"somefile.ext"}; 
    char bStream [4096] = {0};
    BOOL bOkay=TRUE;

    HANDLE hFile = CreateFile(FileName, GENERIC_WRITE, 0, 0, 
                        CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
    if (hFile == INVALID_HANDLE_VALUE)
        nr = -1;
    else
    {
        long nTotal = 0;            
        long nattr = 0;
        DWORD nWritten, nBuffer;
        do
        {   // Read the stream into a buffer, 1023 bytes at a time
            nBuffer=1023;
            nr = MsiRecordReadStream(hBinaryRecord, 2, bStream, &nBuffer); // Binary & cab are 2
            if ((ERROR_SUCCESS == nr) && (nBuffer > 0))
            {
                //Write the buffer to a file. 
                nr = WriteFile(hFile, bStream, nBuffer, &nWritten, NULL);

                if (nr != 0)// 0 is bad
                    nTotal = nTotal + nBuffer; // debug only
                else
                    bOkay = FALSE;
            }
            else
            if (nr != ERROR_SUCCESS)
                bOkay = FALSE;
        } // record record stream
        while (bOkay == TRUE && (nBuffer > 0));

        // done copying file
        CloseHandle(hFile);
        }// create file

    // we only needed one row, so close the view
    MsiViewClose(hBinaryView);

    // done with query
    MsiCloseHandle(hBinaryRecord);
}

// done with binary table
MsiCloseHandle(hBinaryView);
// done with MSI database
MsiCloseHandle(hDatabase);

【讨论】:

    【解决方案2】:

    以下是如何提取嵌入在 msi 二进制表中的每个二进制数据条目的示例。 原始文件名丢失,但嵌入数据的名称列作为输出文件的名称。

    在我的情况下,选择标志“msiReadStreamAnsi”提取的文件与嵌入的文件完全相同,但还有其他标志: https://docs.microsoft.com/en-us/windows/win32/msi/record-readstream

    function Get-Property ($Object, $PropertyName, [object[]]$ArgumentList) {
      return $Object.GetType().InvokeMember($PropertyName, 'Public, Instance, GetProperty', $null, $Object, $ArgumentList)
    }
    
    function Invoke-Method ($Object, $MethodName, $ArgumentList) {
      return $Object.GetType().InvokeMember($MethodName, 'Public, Instance, InvokeMethod', $null, $Object, $ArgumentList)
    }
    
    $msiPath = 'X:\install\setup.msi' # FULL PATH TO MSI*
    $msiOpenDatabaseModeReadOnly = 0
    $Installer = New-Object -ComObject WindowsInstaller.Installer
    
    $Database = Invoke-Method $Installer OpenDatabase @($msiPath, $msiOpenDatabaseModeReadOnly)
    
    $ViewTableBinary = Invoke-Method $Database OpenView @("SELECT Name FROM _Tables WHERE Name='Binary'")
    Invoke-Method $ViewTableBinary Execute
    $TableBinary = Invoke-Method $ViewTableBinary Fetch
    Invoke-Method $ViewTableBinary Close @()
    if ($TableBinary) {
      $msiReadStreamAnsi = 2
      $ViewBinary = Invoke-Method $Database OpenView @("SELECT Name, Data FROM Binary")
      Invoke-Method $ViewBinary Execute
      Do {
        $Binary = Invoke-Method $ViewBinary Fetch
        if ($Binary) {
          $Name = Get-Property $Binary StringData 1
          $DataSize = Get-Property $Binary DataSize 2
          Write-Output "Found Binary: $msiPath/$Name ($DataSize bytes)"
          $BinaryData = Invoke-Method $Binary ReadStream @(2, $DataSize, $msiReadStreamAnsi)
          $BinaryData | Set-Content -NoNewLine $Name
        }
      }
      While ($Binary)
      Invoke-Method $ViewBinary Close @()
      Remove-Variable -Name Binary, ViewBinary
    }  
    Remove-Variable -Name TableBinary, ViewTableBinary, Database, Installer
    

    * msi文件的完整路径是必须的,否则会出现异常:

    使用“5”参数调用“InvokeMember”的异常:“OpenDatabase,DatabasePath,OpenMode”

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-01-10
      • 1970-01-01
      • 2014-02-15
      • 2017-11-20
      • 2015-08-30
      • 1970-01-01
      相关资源
      最近更新 更多