非英语语言环境中的文件损坏(编码问题?)

时间:2019-07-26 13:19:05

标签: vbscript character-encoding wix windows-installer filesystemobject

在MSI Windows Installer中,我有一个自定义的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安装中出现一种情况,其中提取的二进制文件已损坏:

enter image description here

如您所见,问题通常在“?”之后脚本在其中插入'E'或覆盖以下字符。

ReadStream方法和CreateTextFile方法都具有影响编码的参数。上面显示的组合似乎是唯一可以在我的英语Windows 10上运行的组合。

我需要在上面的代码中进行哪些更改才能使其在日语系统上正常工作?

3 个答案:

答案 0 :(得分:2)

@ Robert-Hegner,我将以此为答案,即使它要经过您的测试(我无法测试自己所在的位置)!

我包含了一个updated approach here(您需要向下滚动到第二个示例)

它使用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

答案 1 :(得分:1)

这就是我最终得到的结果。

根据Stein Åsmul的建议,我使用C#(.NET / DTF)重新编写了自定义操作。最初,我很犹豫用C#编写自定义操作,因为它为安装程序引入了其他先决条件。但是事实证明,如果自定义操作以.NET Framework 2.0为目标,则大多数计算机上都应支持该操作,而无需手动安装该框架(请参见here)。

这是我的代码:

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);
            }
        }
    }

}

答案 2 :(得分:0)

  

日语代码页 :来自此博客条目:"Binary Files and the File System Object Do Not Mix":“ 在日语代码页中,just-plain-chr(E0)甚至都不是合法字符,因此Chr会将其变为零...不要使用FSO来读取/写入二进制文件,您只是在DBCS-land的某人运行您的文件时就要求受到伤害代码。


替代品? .NET怎么样?我意识到您正在进行自定义操作,为时已晚,我将这些示例制作为独立的.NET控制台应用程序。 WiX框架具有创建DTF自定义动作的机制。 Found this on github.com

  

重新整理? :我们可以问一下您在做什么吗?为什么需要这种方式提取文件?可能还有其他方法   如果您解释这种情况,会更可靠吗?


DTF / .NET :尽管我不是部署使用的.NET狂热者(依赖关系过多),但我认为使用会更好。 NET / DTF。 What is DTF

示例DTF C#应用程序 :以下是一个简单的C#示例应用程序,显示了一种从Binary表中提取二进制流的方法(还有其他几种方法,我不是.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
                }
            }
        }
    }
}

替代 :这是一项调整,可将整个二进制表导出到用户桌面上名为“ Output”的文件夹中。

与上述创建测试项目的步骤相同。仅需指定一个参数:输入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"));
                    }
                }
            }
        }
    }
}