Currupted file in non-english locale (encoding problem?) - vbscript

In my MSI Windows Installer I have a custom VBScript action which extracts some files from the 'Binary' table to the filesystem. This is the code I'm using:
Inspired by: 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
This has been used without any issues in production for 2-3 years now. However now we have a case on a Japanese Windows installation where the extracted binary files are corrupted:
As you can see, the problem typically after a '?' where the script either inserts an 'E', or overwrites the following character.
Both the ReadStream method and the CreateTextFile method have a parameter which affect encoding. The combination shown above seems to be the only one which works on my English Windows 10.
What do I need to change in the code above to make it work also on a Japanese system?

#Robert-Hegner I'll propose this as an answer, even though it is subject to your testing (I have no way of testing where I am)!
I've included an updated approach here (you will need to scroll down to the second example)
It uses msiReadStreamDirect (not msiReadStreamAnsi) to extract a string of Byte pairs, converts these into binary and creates the output file using the ADODB.Stream (not the 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

Japanese Code Page: From this blog entry: "Binary Files and the File System Object Do Not Mix": "In the Japanese code page, just-plain-chr(E0) is not even a legal character, so Chr will turn it into a zero... Do not use the FSO to read/write binary files, you're just asking for a world of hurt as soon as someone in DBCS-land runs your code."
Alternatives? How about .NET? I realized too late that you are in a custom action, I made the samples as standalone .NET console applications. The WiX framework has mechanisms to create a DTF custom action. Found this on github.com.
Rehashing?: Can we ask what you are actually doing? Why do you need to extract files this way? There could be other approaches that
are more reliable if you explain the scenario?
DTF / .NET: Though I am not a huge .NET fan for deployment use (too many layers of dependencies), I think you would do better using .NET / DTF for this. What is DTF?
Sample DTF C# Application: Below is a simple, C# sample application showing one way to extract a binary stream from the Binary table (there are several other ways, I am not a .NET expert).
Create a new C# Console App (.NET Framework).
Paste the below code in and adjust parameters.
Add reference to Microsoft.Deployment.WindowsInstaller.dll (DTF framework).
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
}
}
}
}
}
Alternative: Here is a tweak that exports the whole Binary Table to a folder called "Output" on the user's desktop.
Same procedure to create a test project as above. Only one parameter to specify: the full path to the input 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"));
}
}
}
}
}
}

Here is what I ended up with.
As suggested by Stein Åsmul I rewrote the custom action using C# (.NET / DTF). Initially I was hesitant to writing custom actions in C# as it introduces additional prerequisites to the installer. But it turns out that if the custom action targets .NET Framework 2.0, it should be supported on most machines without the need to manually install the framework (see here).
So here is my code:
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);
}
}
}
}

Related

convert a zip file to base64 using vbs in UFT

I have a requirement of converting a zip file from my local machine to base64.
Get/Read the path name from the excel sheet row
convert the file in the path (zip file) to base 64 string
Copy the base 64 value to next column in the excel sheet.
Tried a few but did not work.
Current Code:
Dim inByteArray, base64Encoded
inByteArray = readBytes("F:path/file.zip")
base64Encoded = encodeBase64(inByteArray)
Private Function readBytes(file)
Dim inStream
' ADODB stream object used
Set inStream = CreateObject("ADODB.Stream")
' open with no arguments makes the stream an empty container
inStream.Open
inStream.Type = TypeBinary
inStream.LoadFromFile(file)
readBytes = inStream.Read()
End Function
Private Function encodeBase64(bytes)
Dim DM, EL
Set DM = CreateObject("Microsoft.XMLDOM")
' Create temporary node with Base64 data type
Set EL = DM.CreateElement("tmp")
EL.DataType = "bin.base64"
' Set bytes, get encoded String
EL.NodeTypedValue = bytes
encodeBase64 = EL.Text
End Function
Error 1 in the line inStream.type = TypeBinary:
Arguments are of the wrong type, are out of acceptable range, or are in conflict with one another.
Error 2 in the line readBytes = inStream.Read():
Operation is not allowed in this context.
Error 3 in the line EL.NodeTypedValue = bytes:
Type mismatch
Looks like you got the code from here, but didn't include
Const TypeBinary = 1
Adding this will avoid the "Arguments are of the wrong type ..." error.
Perhaps careful copy will solve your other problems too.
Thanks for that :)
Further for excel sheet read and write I used the below code which helped in achieving my target. Thank you
Set objExcel = CreateObject("Excel.Application")
Set objWorkbook = objExcel.Workbooks.Open("F:\path")
Set ws = objWorkbook.Sheets("Sheet1")
Set ws2 = objWorkbook.Sheets("Sheet2")
rowcount = ws.usedrange.rows.count
for j = 1 to rowcount
fieldvalue = ws.cells(j,1)
inByteArray = readBytes(fieldvalue)
base64Encoded = encodeBase64(inByteArray)
ws2.cells(j,1) = base64Encoded
next

AutoCAD Architecture Vision Tools in AutoCAD

I have both AutoCAD and AutoCAD Architecture installed on my system. AutoCAD Architecture has a tab called Vision Tools with a nifty command called Display By Layer to set the display order of objects in accordance with the layers of the drawing. Is there anyway to add this tab or use this command in AutoCAD?
Not sure if you're looking for a built-in feature or APIs for it.
For a built in feature, check the DRAWORDER command. For an API/programming approach, check the respective DrawOrderTable method. See below:
Update: please also check this 3rd party tool: DoByLayer.
[CommandMethod("SendToBottom")]
public void commandDrawOrderChange()
{
Document activeDoc
= Application.DocumentManager.MdiActiveDocument;
Database db = activeDoc.Database;
Editor ed = activeDoc.Editor;
PromptEntityOptions peo
= new PromptEntityOptions("Select an entity : ");
PromptEntityResult per = ed.GetEntity(peo);
if (per.Status != PromptStatus.OK)
{
return;
}
ObjectId oid = per.ObjectId;
SortedList<long, ObjectId> drawOrder
= new SortedList<long, ObjectId>();
using (Transaction tr = db.TransactionManager.StartTransaction())
{
BlockTable bt = tr.GetObject(
db.BlockTableId,
OpenMode.ForRead
) as BlockTable;
BlockTableRecord btrModelSpace =
tr.GetObject(
bt[BlockTableRecord.ModelSpace],
OpenMode.ForRead
) as BlockTableRecord;
DrawOrderTable dot =
tr.GetObject(
btrModelSpace.DrawOrderTableId,
OpenMode.ForWrite
) as DrawOrderTable;
ObjectIdCollection objToMove = new ObjectIdCollection();
objToMove.Add(oid);
dot.MoveToBottom(objToMove);
tr.Commit();
}
ed.WriteMessage("Done");
}
With some help from VBA it might look by this. Note i did not add fancy listbox code. I just show the worker and how to list layers. The trivial Code to add things to a listbox on a form and how to sort / rearrange listbox items can be found on any excel / VBA forum on the web . Or you just uses a predefined string like in the example. To get VBA to work download and install the acc. VBA Enabler from autocad. It is free.
'select all items on a layer by a filter
Sub selectALayer(sset As AcadSelectionSet, layername As String)
Dim filterType As Variant
Dim filterData As Variant
Dim p1(0 To 2) As Double
Dim p2(0 To 2) As Double
Dim grpCode(0) As Integer
grpCode(0) = 8
filterType = grpCode
Dim grpValue(0) As Variant
grpValue(0) = layername
filterData = grpValue
sset.Select acSelectionSetAll, p1, p2, filterType, filterData
Debug.Print "layer", layername, "Entities: " & str(sset.COUNT)
End Sub
'bring items on top
Sub OrderToTop(layername As String)
' This example creates a SortentsTable object and
' changes the draw order of selected object(s) to top.
Dim oSset As AcadSelectionSet
Dim oEnt
Dim i As Integer
Dim setName As String
setName = "$Order$"
'Make sure selection set does not exist
For i = 0 To ThisDrawing.SelectionSets.COUNT - 1
If ThisDrawing.SelectionSets.ITEM(i).NAME = setName Then
ThisDrawing.SelectionSets.ITEM(i).DELETE
Exit For
End If
Next i
setName = "tmp_" & time()
Set oSset = ThisDrawing.SelectionSets.Add(setName)
Call selectALayer(oSset, layername)
If oSset.COUNT > 0 Then
ReDim arrObj(0 To oSset.COUNT - 1) As ACADOBJECT
'Process each object
i = 0
For Each oEnt In oSset
Set arrObj(i) = oEnt
i = i + 1
Next
End If
'kills also left over selectionset by programming mistakes....
For Each selectionset In ThisDrawing.SelectionSets
selectionset.delete_by_layer_space
Next
On Error GoTo Err_Control
'Get an extension dictionary and, if necessary, add a SortentsTable object
Dim eDictionary As Object
Set eDictionary = ThisDrawing.modelspace.GetExtensionDictionary
' Prevent failed GetObject calls from throwing an exception
On Error Resume Next
Dim sentityObj As Object
Set sentityObj = eDictionary.GetObject("ACAD_SORTENTS")
On Error GoTo 0
If sentityObj Is Nothing Then
' No SortentsTable object, so add one
Set sentityObj = eDictionary.AddObject("ACAD_SORTENTS", "AcDbSortentsTable")
End If
'Move selected object(s) to the top
sentityObj.MoveToTop arrObj
applicaTION.UPDATE
Exit Sub
Err_Control:
If ERR.NUMBER > 0 Then MsgBox ERR.DESCRIPTION
End Sub
Sub bringtofrontbylist()
Dim lnames As String
'predefined layer names
layer_names = "foundation bridge road"
Dim h() As String
h = split(layernames)
For i = 0 To UBound(h)
Call OrderToTop(h(i))
Next
End Sub
'in case you want a fancy form here is how to get list / all layers
Sub list_layers()
Dim LAYER As AcadLayer
For Each LAYER In ThisDrawing.LAYERS
Debug.Print LAYER.NAME
Next
End Sub
to make it run put the cursor inside the VBA IDE inside the code of list_layers andpress F5 or choose it from the VBA Macro list.

Cant connect COM object.

I have written a COM library on C++.
There is only 1 function in this library.
STDMETHODIMP TMyCodeBitmapGenerateImpl::GenerateMyFile(BSTR ATextToEncode,
BSTR APathToSaveFile)
Library name is MyCode_COM_Library
Library is registered in the system.
trying
DataString = "data123"
Filename = "D:\img.bmp"
with CreateObject("MyCode_COM_Library.TMyCodeBitmapGenerateImpl")
.GenerateMyFile(DataString,Filename)
end with
I'm getting error Object Required
Trying again
dim ws
dim wd
Set ws = CreateObject("MyCode_COM_Library.TMyCodeBitmapGenerateImpl")
Set wd = ws.GenerateMyFile(datastring, FileName)
same error.
In VB in References i added this library
Dim wd
Dim wa As MyCodeBitmapGenerate
datastring = "data123"
FileName = "D:\img123.bmp"
wd = wa.GenerateMyFile(datastring, FileName) ' the function returns int
After that the compilator selects
.GenerateMyFile
and giving error Expected Function or Variable
Please help me to connect VB and my COM library.
Thank You.
If i'm adding library in References and writing such code in VB than all OK
Private Sub Command1_Click()
Dim azclib As AztecCodeBitmapGenerate
ds = "12343445454243442Data1234567890"
fp = "D:\imgDS.bmp"
Set azclib = New AztecCodeBitmapGenerate
azclib.GenerateAztecFile ds, fp
End Sub
But if i'm trying to write this code in VBScript like
Dim dt
Dim fp
Dim we, ws
dt = "123456789"
fp = "C:\imgDT.bmp"
Set we = CreateObject("AztecCode_COM_Library.TAztecCodeBitmapGenerateImpl")
Call we.GenerateAztecFile(dt, fp)
then i have an error Object Required - 800A01A8 on Call we.GEnerateAztecFile (dt,fp).
I think that the object "we" is not created but i'm not sure

Windows API to extract zip files?

In Windows Explorer you can extract a compressed folder (a zip file)
Is there an API or command line to extract a zip file using the same method programatically?
You can use this VBScript script:
'Adapted from http://www.robvanderwoude.com/vbstech_files_zip.html
strFile = "c:\filename.zip"
strDest = "c:\files"
Set objFSO = CreateObject("Scripting.FileSystemObject")
If Not objFSO.FolderExists(strDest) Then
objFSO.CreateFolder(strDest)
End If
UnZipFile strFile, strDest
Sub UnZipFile(strArchive, strDest)
Set objApp = CreateObject( "Shell.Application" )
Set objArchive = objApp.NameSpace(strArchive).Items()
Set objDest = objApp.NameSpace(strDest)
objDest.CopyHere objArchive
End Sub
Check Compress Zip files with
Windows Shell API and C#
You could use SharpZipLib which
is free for a dot net project.
I tried the above function Sub UnZipFile(...) under Excel 2010 and it was not working: Run-time error '91' (Object variable or With block not set) in the line
Set objArchive = objApp.Namespace(strArchive).Items()
and the line
Set objDest = objApp.Namespace(strDest)
is silently also not working: After execution the objDest is still nothing!
Microsoft's .Namespace() accepts as parameter either an object, a string constant or a string variable. With string variables there are often suspicious problems, which are requiring an workaround:
Set objArchive = objApp.Namespace(**CStr(** strArchive **)**).Items()
Set objDest = objApp.Namespace(**CStr(** strDest **)**)
or an alternative workaround
Set objArchive = objApp.Namespace(**"" &** strArchive).Items()
Set objDest = objApp.Namespace(**"" &** strDest)
And the line objDest.CopyHere objArchive was also not working: The destination folder remained empty!
Here a version, which is working in Excel 2010 and most probably also in other environments:
Sub UnZipFile(strZipArchive As String, strDestFolder As String)
Dim objApp As Object
Dim vItem As Variant
Dim objDest As Object
Set objApp = CreateObject("Shell.Application")
Set objDest = objApp.Namespace(CStr(strDestFolder))
For Each vItem In objApp.Namespace(CStr(strZipArchive)).Items
objDest.CopyHere vItem
Next vItem
End Sub
For C# or VB users, you can check the answer from MSDN:
https://msdn.microsoft.com/en-us/library/ms404280(v=vs.100).aspx
For .net 4.x, here is the sample code from MSDN
using System;
using System.IO;
using System.IO.Compression;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
string startPath = #"c:\example\start";
string zipPath = #"c:\example\result.zip";
string extractPath = #"c:\example\extract";
ZipFile.CreateFromDirectory(startPath, zipPath);
ZipFile.ExtractToDirectory(zipPath, extractPath);
}
}
}

Can I use VBScript to base64 encode a gif?

What I'm trying to do is encode a gif file, to include in an XML document.
This is what I have now, but it doesn't seem to work.
Function gifToBase64(strGifFilename)
On Error Resume Next
Dim strBase64
Set inputStream = WScript.CreateObject("ADODB.Stream")
inputStream.LoadFromFile strGifFilename
strBase64 = inputStream.Text
Set inputStream = Nothing
gifToBase64 = strBase64
End Function
I recently wrote a post about this very subject for implementations in JScript and VBScript. Here is the solution I have for VBScript:
Public Function convertImageToBase64(filePath)
  Dim inputStream
  Set inputStream = CreateObject("ADODB.Stream")
  inputStream.Open
  inputStream.Type = 1  ' adTypeBinary
  inputStream.LoadFromFile filePath
  Dim bytes: bytes = inputStream.Read
  Dim dom: Set dom = CreateObject("Microsoft.XMLDOM")
  Dim elem: Set elem = dom.createElement("tmp")
  elem.dataType = "bin.base64"
  elem.nodeTypedValue = bytes
  convertImageToBase64 = "data:image/png;base64," & Replace(elem.text, vbLf, "")
End Function
In your comment to Tomalak you state you don't want to use external dlls but in your attempted example you try to use ADODB. I suspect therefore what you mean is you don't want to install dlls that aren't natively present on a vanilia windows platform.
If that is so then MSXML may be your answer:-
Function Base64Encode(rabyt)
Dim dom: Set dom = CreateObject("MSXML2.DOMDocument.3.0")
Dim elem: Set elem = dom.appendChild(dom.createElement("root"))
elem.dataType = "bin.base64"
elem.nodeTypedValue = rabyt
Base64Encode = elem.Text
End Function
Take a look here: Base64 Encode & Decode Files with VBScript. This example relies on the free XBase64 component and merely provides a wrapper for file handling.
You can also go for a pure VBScript implementation, but here you have to care for the file handling yourself. Should not be too difficult, but encoding performance will be not as good. For a few small image files it will be enough, though.
Google will turn up more.

Resources