在MSI Windows Installer中,我有一个自定义的VBScript操作,该操作将某些文件从“二进制”表中提取到文件系统中。这是我正在使用的代码:
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上运行的组合。
我需要在上面的代码中进行哪些更改才能使其在日语系统上正常工作?
答案 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专家)。
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"));
}
}
}
}
}
}