Прерванный файл на неанглийском языке (проблема с кодировкой?)

В моем установщике Windows MSI у меня есть настраиваемое действие VBScript, которое извлекает некоторые файлы из «двоичной» таблицы в файловую систему. Это код, который я использую:

На основе: https://www.itninja.com/question/how-to-call-an-exe-which-is-stored-in-a-binary-table-through-a-vbscript-custom-action-in-the-msi

Function ExtractFromBinary(ByVal binaryName, ByVal binaryOutputFile)

 Dim oFSO : Set oFSO = CreateObject("Scripting.FileSystemObject")

 Const msiReadStreamInteger = 0
 Const msiReadStreamBytes = 1
 Const msiReadStreamAnsi = 2 
 Const msiReadStreamDirect = 3

 Dim binaryView : Set binaryView = Session.Database.OpenView("SELECT Data FROM Binary WHERE Name = '" & binaryName & "'") 
 binaryView.Execute

 Dim binaryRecord : Set binaryRecord = binaryView.Fetch 
 Dim binaryData : binaryData = binaryRecord.ReadStream(1, binaryRecord.DataSize(1), msiReadStreamAnsi) 
 Set binaryRecord = Nothing

 Dim binaryStream : Set binaryStream = oFSO.CreateTextFile(binaryOutputFile, True, False) 
 binaryStream.Write binaryData
 binaryStream.Close
 Set binaryStream = Nothing 

End Function

Это без проблем используется в производстве уже 2-3 года. Однако теперь у нас есть случай с японской установкой Windows, где извлеченные двоичные файлы повреждены:

введите здесь описание изображения

Как видите, проблема обычно стоит после символа "?" где сценарий либо вставляет «E», либо перезаписывает следующий символ.

И метод ReadStream, и метод CreateTextFile имеют параметр, который влияет на кодирование. Показанная выше комбинация кажется единственной, которая работает на моей английской Windows 10.

Что мне нужно изменить в приведенном выше коде, чтобы он работал и в японской системе?


person Robert Hegner    schedule 26.07.2019    source источник
comment
Прежде всего, пара ссылок: Вы пробовали это? и затем Двоичные файлы и объект файловой системы не смешиваются. Для этой цели я мог бы выбрать .NET и DTF вместо VBScript. Возможно, позже образец откашлят.   -  person Stein Åsmul    schedule 26.07.2019
comment
@ robert-hegner Я рад, что мой код вдохновляет людей 6 лет спустя! Как вы упомянули кодировку, вы пробовали oFSO.CreateTextFile(binaryOutputFile, True, True) на японской сборке? stackoverflow.com/a/47449590/4181058   -  person Captain_Planet    schedule 27.07.2019
comment
@Captain_Planet Я пробовал это на своей английской сборке, и это не работает. Так что я даже не позволил своему японскому тестировщику попробовать это.   -  person Robert Hegner    schedule 29.07.2019


Ответы (3)


@ Robert-Hegner Я предлагаю это в качестве ответа, даже если это подлежит вашему тестированию (у меня нет возможности проверить, где я нахожусь)!

Я добавил обновленный подход здесь (вам нужно будет прокрутить вниз до второго примера)

Он использует msiReadStreamDirect (не msiReadStreamAnsi) для извлечения строки пар байтов, преобразует их в двоичный файл и создает выходной файл с использованием ADODB.Stream (не FSO).

Dim oFSO : Set oFSO = CreateObject("Scripting.FileSystemObject")

Dim tempFolder : tempFolder = oFSO.GetSpecialFolder(2) 
Dim outputFile : outputFile = tempFolder & "\notepad.exe"

extractFromBinary "notepad", outputFile

Function MultiByteToBinary(MultiByte)
  'obtained from http://www.motobit.com
  'MultiByteToBinary converts multibyte string To real binary data (VT_UI1 | VT_ARRAY)
  'Using recordset
  Dim RS, LMultiByte, Binary
  Const adLongVarBinary = 205
  Set RS = CreateObject("ADODB.Recordset")
  LMultiByte = LenB(MultiByte)
  If LMultiByte>0 Then
    RS.Fields.Append "mBinary", adLongVarBinary, LMultiByte
    RS.Open
    RS.AddNew
      RS("mBinary").AppendChunk MultiByte & ChrB(0)
    RS.Update
    Binary = RS("mBinary").GetChunk(LMultiByte)
  End If
  Set RS = Nothing
  MultiByteToBinary = Binary
End Function

Function SaveBinaryData(FileName, ByteArray)
  Const adTypeBinary = 1
  Const adSaveCreateOverWrite = 2

  'Create Stream object
  Dim BinaryStream
  Set BinaryStream = CreateObject("ADODB.Stream")

  'Specify stream type - we want To save binary data.
  BinaryStream.Type = adTypeBinary

  'Open the stream And write binary data To the object
  BinaryStream.Open
  BinaryStream.Write ByteArray

  'Save binary data To disk
  BinaryStream.SaveToFile FileName, adSaveCreateOverWrite

  Set BinaryStream = Nothing
End Function

Function extractFromBinary(ByVal binaryName, ByVal binaryOutputFile)

    Const msiReadStreamInteger = 0 
    Const msiReadStreamBytes = 1 
    Const msiReadStreamAnsi = 2  
    Const msiReadStreamDirect = 3

    Dim binaryView : Set binaryView = Session.Database.OpenView("SELECT * FROM Binary WHERE Name = '" & binaryName & "'")  
    binaryView.Execute

    Dim binaryRecord : Set binaryRecord = binaryView.Fetch  
    Dim binaryData : binaryData = binaryRecord.ReadStream(2, binaryRecord.DataSize(2), msiReadStreamDirect)  
    Set binaryRecord = Nothing  

    'convert to string of byte pairs to binary
    binaryData = MultiByteToBinary(binaryData)

    'save binary data
    SaveBinaryData binaryOutputFile, binaryData

End Function

Set oFSO = Nothing
person Captain_Planet    schedule 29.07.2019
comment
Спасибо за Ваш ответ. Я решил использовать настраиваемое действие C #, так как VBScript слишком болезненен для обслуживания и устранения неполадок. Я уверен, что ваш ответ поможет другим решить проблему в VBScript. - person Robert Hegner; 30.07.2019

Японская кодовая страница: из этой записи в блоге: " Двоичные файлы и объект файловой системы не смешиваются ":" На японском кодовая страница, just-plain-chr (E0) даже не является допустимым символом, поэтому Chr превратит его в ноль ... Не используйте FSO для чтения / записи двоичных файлов, вы просто просите мир будет больно, как только кто-то из DBCS запустит ваш код. "


Альтернативы? Как насчет .NET? Я слишком поздно понял, что вы находитесь в настраиваемом действии, я сделал образцы как автономные консольные приложения .NET. Платформа WiX имеет механизмы для создания настраиваемого действия DTF. Нашел на github.com.

Перефразирование?. Можно спросить, чем вы на самом деле занимаетесь? Зачем нужно извлекать файлы таким образом? Могут быть другие подходы, более надежные, если вы объясните сценарий?


DTF / .NET: хотя я не большой поклонник .NET для развертывания (слишком много уровней зависимостей), я думаю, вам лучше использовать .NET / DTF. для этого. Что такое DTF?

Пример приложения DTF C #. Ниже приведен простой пример приложения C #, показывающий один способ извлечения двоичного потока из двоичной таблицы (есть несколько других способов, я не .NET эксперт).

  1. Создайте новое консольное приложение C # (.NET Framework).
  2. Вставьте приведенный ниже код и настройте параметры.
  3. Добавьте ссылку на Microsoft.Deployment.WindowsInstaller.dll (структура DTF).
using Microsoft.Deployment.WindowsInstaller;

namespace MSIExtractBinaryTableEntry
{
    class Program
    {
        static void Main(string[] args)
        {
            // ADJUST 1: Name of Binary Table Entry
            var binarytableentry = "ImageBmp"; 

            // ADJUST 2: Source MSI path
            var msifullpath = @"C:\MySetup.msi";

            // ADJUST 3: Output target path for binary stream
            var binaryfileoutputpath = @"C:\Output.XXX";

            using (var db = new Database(msifullpath, DatabaseOpenMode.ReadOnly))
            {
                using (var binaryView = db.OpenView("SELECT Name, Data FROM Binary WHERE Name='" + binarytableentry + "'"))
                {
                    binaryView.Execute();
                    binaryView.Fetch().GetStream(2, binaryfileoutputpath); // force overwrites output path
                }
            }
        }
    }
}

Альтернатива: вот настройка, которая экспортирует всю двоичную таблицу в папку с именем «Вывод» на рабочем столе пользователя.

Та же процедура для создания тестового проекта, что и выше. Только один параметр, который нужно указать: полный путь к входному MSI.

using System;
using System.IO;
using Microsoft.Deployment.WindowsInstaller;

namespace MSIExtractBinaryTableEntry
{
    class Program
    {
        static void Main(string[] args)
        {
            // ADJUST 1: Specify MSI file path
            var msifullpath = @"C:\MySetup.msi";

            var outputpath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), @"Output\");
            Directory.CreateDirectory(outputpath);

            using (var db = new Database(msifullpath, DatabaseOpenMode.ReadOnly))
            {
                using (var binaryView = db.OpenView("SELECT Name, Data FROM Binary"))
                {
                    binaryView.Execute();

                    foreach (var rec in binaryView)
                    {
                        rec.GetStream("Data", outputpath + rec.GetString("Name"));
                    }
                }
            }
        }
    }
}
person Stein Åsmul    schedule 26.07.2019
comment
Спасибо за эти детали и образцы кода. Меня беспокоит то, что пользовательские действия .NET могут вводить дополнительные требования для установщика (см. Мой новый вопрос здесь: stackoverflow.com/q/ 57248623/487356). Если я найду способ иметь настраиваемое действие C # без введения новых предварительных условий, то это определенно будет путь ... - person Robert Hegner; 29.07.2019
comment
Да, я говорю об этом много лет и полностью согласен. У настраиваемого действия должно быть минимум зависимостей - во всяком случае, зависимость должна быть минимальной, поскольку она должна иметь возможность запускаться на any system в any state в any language на < b> any OS version таргетировано. Однако похоже, что VBScript не может правильно писать Unicode, а если вы нацеливаетесь на .NET 2.0 - он почти везде? Это будет работать в любой системе, где .NET полностью отключен (если это вообще возможно). Я думаю, что платформа .NET правильно глобализирована / локализована и учитывает языковые стандарты? - person Stein Åsmul; 29.07.2019
comment
Не могли бы вы извлечь этот файл из своего приложения? Что он делает во время установки? Возможно, вы сможете получить файл из кэшированного MSI-файла в системе, поскольку это запись в двоичной таблице. Не совсем тривиально и может даже быть подвержено вредоносным сканерам, определяющим это как подозрительную активность, но технически это должно быть возможно. - person Stein Åsmul; 29.07.2019
comment
Теперь я успешно протестировал свою реализацию C # на английском и японском языках. Stein Åsmul, вы хотите, чтобы я отредактировал ваш ответ, указав свою реальную реализацию, прежде чем я приму ваш ответ? - person Robert Hegner; 30.07.2019
comment
Да можно просто добавить новый раздел? Или просто добавьте свой ответ, если он сильно отличается? - person Stein Åsmul; 30.07.2019

Вот что у меня получилось.

Как было предложено Штейном Осмулом, я переписал настраиваемое действие, используя C # (.NET / DTF). Изначально я не решался писать настраиваемые действия на C #, поскольку он вводит дополнительные предварительные условия для установщика. Но оказывается, что если настраиваемое действие нацелено на .NET Framework 2.0, оно должно поддерживаться на большинстве машин без необходимости вручную устанавливать платформу (см. здесь).

Итак, вот мой код:

public static class TemporaryFilesExtractor
{

    [CustomAction]
    public static ActionResult ExtractTemporaryFiles(Session session)
    {
        ExtractFromBinary(session, "binaryname1", "<filePath1>");
        ExtractFromBinary(session, "binaryname2", "<filePath2>");
        return ActionResult.Success;
    }

    private static void ExtractFromBinary(Session session, string binaryName, string binaryOutputFile)
    {
        session.Log($"Extracting {binaryName} to {binaryOutputFile}");
        byte[] buffer = new byte[4096];

        using (var view = session.Database.OpenView("SELECT Data FROM Binary WHERE Name = '{0}'", binaryName))
        {
            view.Execute();
            using (var record = view.Fetch())
            using (var dbStream = record.GetStream(1))
            using (var fileStream = File.OpenWrite(binaryOutputFile))
            {
                int count;
                while ((count = dbStream.Read(buffer, 0, buffer.Length)) != 0)
                    fileStream.Write(buffer, 0, count);
            }
        }
    }

}
person Robert Hegner    schedule 30.07.2019