Send propertybag over winsock? - vb6

I am having trouble sending a propertybag over winsock. I have a client/server application that sends images through using a propertybag. I convert the propertybag into a byte array and try to send it over winsock and then when I convert the byte array back to a propertybag, it cant seem to read it. It looks like the data was corrupted when it was sent.
Client(sending propertybag)
Dim pb As PropertyBag
Dim byt() As Byte
Set pb = New PropertyBag
pb.WriteProperty "picc", Image1.Picture
byt = pb.Contents
Winsock1.SendData byt
Server(Receiving propertybag)
Dim byt() As Byte
Dim pb As PropertyBag
Set pb = New PropertyBag
Winsock1.GetData byt, vbByte
pb.Contents = byt
Image1.Picture = pb.ReadProperty("picc")
The error I received:
Run-time error '327':
Data value named 'picc' not found
When I try to do execute the code in a single program without winsock, it works just fine. The problem occurs when I send the byte array over winsock.

Most people also utilize the ADO stream object (add reference to Microsoft ActiveX Data Objects 2.5 or whatever version) by going to Project -> References.
Here is a working example you can download using a PropertyBag as well as the ADO stream object.
It's called PicturePicture.zip and written by a very Winsock & client/server-knowledgeable programmer.

Related

Why is the result of VarPtr(ByVal str) the same as StrPtr(str) ? (VB6)

In VB6 VarPtr should return the address of the variable, in this case the address of the variable str which is allocated on stack, and holds a pointer to a string in memory. StrPtr (or StrPtr) should return the address of the allocated string in the memory. ByVal should just create a copy, but in this case, it works strangely:
Dim str As String
str = "asd"
Debug.Print VarPtr(str)
Debug.Print VarPtr(ByVal str)
Debug.Print StrPtr(str)
The result is:
1636452
110882980
110882980
Why is the result of VarPtr(ByVal str) the same as StrPtr(str) ?
Strings passed ByVal pass the address of the first character of the containing C string in the BStr. StrPtr does the same.
There are two reasons that spring to mind for doing this. Passing Unicode to API calls and string building.
Passing Unicode to API calls
You could use StrPtr on a string rather than a byte array when sending Unicode strings to API functions.
Dim ByteArr() as Byte
Var1="My Text"
ByteArr = Var1
APICall(ByteArr(0))
APICall(StrPtr(Var1))
Should both pass a Unicode string to an API functions. Unicode strings are converted to ANSI strings when using the declare statement as Win 95 didn't do unicode.
String Building
On the other hand if you are string building, then that is built in to VBA using the Left, Right, and Mid statements, not functions (they are overloaded).
Sub Main()
Dim Var As String
Var = "gggggggggggg"
MsgBox StrPtr(Var)
Mid(Var, 1, 2) = "xx"
MsgBox StrPtr(Var) & " - " & Var
End Sub
ByVal Versus ByRef
Some authors like to say that the ByVal keyword is overloaded for
strings, meaning that it takes on a different meaning when applied to
strings than when applied to other variables. Frankly, I don't see it.
Writing:
ByVal str As String
tells VB to pass the contents of the BSTR (actually the ABSTR), which is the pointer to the character array. Thus, ByVal is acting
normally--it just happens that the content of the BSTR is a pointer to
another object, so this simulates a pass by reference. Similarly:
ByRef str As String
passes the address of the BSTR, as expected.
Win32 API Programming with Visual Basic, Chapter 6 Strings, O'Reilly, from MSDN Library October 2001
StrPtr
Strings in Visual Basic are stored as BSTR's. If you use the VarPtr on
a variable of type String, you will get the address of the BSTR, which
is a pointer to a pointer of the string. To get the address of the
string buffer itself, you need to use the StrPtr function. This
function returns the address of the first character of the string.
Take into account that Strings are stored as UNICODE in Visual Basic.
To get the address of the first character of a String, pass the String
variable to the StrPtr function.
Example:
Dim lngCharAddress as Long
Dim strMyVariable as String
strMyVariable = "Some String"
lngCharAddress = StrPtr(strMyVariable)
You can use this function when you need to pass a pointer to a
UNIOCODE string to an API call.
HOWTO: Get the Address of Variables in Visual Basic Q199824 Microsoft Knowledge Base, MSDN October 2001.
VarPtr is not part of the VBA/VB6 language, therefore companies that implement VBA (like Corel) may not implement it in their VBA. The VBA spec is here https://msdn.microsoft.com/en-us/library/dd361851.aspx

What is this VB6 method doing?

We are converting a VB6 application to C# (4.0). and have come across a method in VB6 that we're battling to understand.
Public Sub SaveToField(fldAttach As ADODB.Field)
Dim bData() As Byte
Dim nSize As Long
nSize = Len(m_sEmail)
bData = LngToByteArray(nSize)
fldAttach.AppendChunk bData
If nSize > 0 Then
bData = StringToByteArray(m_sEmail)
fldAttach.AppendChunk bData
End If
nSize = Len(m_sName)
bData = LngToByteArray(nSize)
fldAttach.AppendChunk bData
If nSize > 0 Then
bData = StringToByteArray(m_sName)
fldAttach.AppendChunk bData
End If
bData = LngToByteArray(m_nContactID)
fldAttach.AppendChunk bData
End Sub
It seems like it's doing some binary file copy type thing, but I'm not quite understanding. Could someone explain so that we can rewrite it?
It serializes the members of the current class (m_sEmail, m_sName, etc.) into the fldAttach database field as a byte array. Each data element is prefixed with its size, that's why th code LngToByteArray(nSize) is there for each piece of data written out.
In C# you'd use a MemoryStream and BinaryWriter to accomplish the serialization aspect of it and then you'd write the byte array from the memory stream to the database. Something like:
byte[] byData;
using (MemoryStream oStream = new MemoryStream)
{
using (BinaryWriter oWriter = new BinaryWriter (oStream))
{
if (m_sName == null)
{
oWriter.Write ((byte) 0); // null string
}
else
{
oWriter.Write ((byte) 1); // not a null string
oWriter.Write (m_sName);
}
// other fields
}
byData = oStream.ToArray (); // get serialized byte array
}
// use byData here
Edit: MarkJ pointed out in the comments that this code doesn't write the same binary format as the original VB6 code, only something similar. If there's an existing database with records written by the VB6 code, the C# code will have to handle those.
There are two major differences: one is how strings are output - the original VB6 code doesn't deal with null strings (there's no such concept in VB6). The other is that BinaryWriter.Write(string) automatically writes a length-prefixed string - this may or may not be in the same exact format used by the VB6 code which outputs a length and then the string bytes. C# can replicate the VB6 code here using the following logic:
...
// assuming sStr is not null
byte[] byString = Encoding.Unicode.GetBytes ( sStr );
oWriter.Write ( sStr.Length );
oWriter.Write ( byString );
The C# port will have to assume no null strings or it'll have to deal with those in some way.
It may be better to write a small utility that goes through the database and updates all records to the new format in which strings have a null marker, a length-prefix and then the string bytes. I'd personally go with this solution since then new code doesn't have to deal with the quirks of and old language.
PS:
Do note that Long in VB6 maps to int in C#. This is important for handling the length prefix for the VB6 binary format.
Also, here's a link to a question about VB6 strings - this may be useful when porting happens:
VB6 equivalent of string.IsNullOrEmpty

Save mail to msg file using EWS API

I'm using Exchange Web Services Managed API 1.1 to connect to Exchange server 2010 and then find out new emails received. Now I want to save a copy of the .msg file to a folder on the disk.
I do not want to use any paid third party to integrate.
Any help will be appreciated.
If you are happy to save into the .eml format instead, it can be done very easily just using EWS and no third party libraries. The .eml file will contain all the same information and can be opened by Outlook in the same way as .msg (and also by other programs).
message.Load(new PropertySet(ItemSchema.MimeContent));
MimeContent mc = message.MimeContent;
FileStream fs = new FileStream("c:\test.eml", FileMode.Create);
fs.Write(mc.Content, 0, mc.Content.Length);
fs.Close();
Cleaned up code:
message.Load(new PropertySet(ItemSchema.MimeContent));
var mimeContent = message.MimeContent;
using (var fileStream = new FileStream(#"C:\Test.eml", FileMode.Create))
{
fileStream.Write(mimeContent.Content, 0, mimeContent.Content.Length);
}
There is no native support for MSG files using EWS. It's strictly an Outlook format.
The MSG spec is published at http://msdn.microsoft.com/en-us/library/cc463912%28EXCHG.80%29.aspx. It's a little complicated to understand, but do-able. You would need to pull down all of the properties for the message and then serialize it into an OLE structured file format. It's not an easy task.
In the end, you are probably better off going with a 3rd party library otherwise it might be a big task to accomplish.
You can easily access the MIME contents of the message through message.MimeContent and save the message as an EML file. The latest (2013 and 2016) versions of Outlook will be able to open EML files directly.
message.Load(new PropertySet(ItemSchema.MimeContent));
MimeContent mimcon = message.MimeContent;
FileStream fStream = new FileStream("c:\test.eml", FileMode.Create);
fStream.Write(mimcon.Content, 0, mimcon.Content.Length);
fStream.Close();
If you still need to convert to the MSG format, you have a few options:
MSG file format is documented - it is an OLE store (IStorage) file. See https://msdn.microsoft.com/en-us/library/cc463912(v=exchg.80).aspx
Use a third party MSG file wrapper, such as the one from Independentsoft: http://www.independentsoft.de/msg/index.html. Setting all properties that Outlook expects can be challenging.
Convert EML file to MSG directly using Redemption (I am its author):
set Session = CreateObject("Redemption.RDOSession") set Msg = Session.CreateMessageFromMsgFile("c:\test.msg") Msg.Import("c:\test.eml", 1024) Msg.Save
Keep in mind that MIME won't preserve all MAPI specific properties. You can use the Fast Transfer Stream (FTS) format used by the ExportItems EWS operation (which, just like the MSG format, preserves most MAPI properties). The FTS data can then be converted (without any loss of fidelity) to the MSG format using Redemption (I am its author) - RDOSession.CreateMessageFromMsgFile / RDOMail.Import(..., olFTS) / RDOMail.Save
RDOSession session = new RDOSession(); RDOMail msg = session.CreateMessageFromMsgFile(#"c:\temp\test.msg"); msg.Import(#"c:\temp\test.fts", rdoSaveAsType.olFTS); msg.Save();
This suggestion was posted as a comment by #mack, but I think it deserves its own place as an answer, if for no other reason than formatting and readability of answers vs. comments.
using (FileStream fileStream =
File.Open(#"C:\message.eml", FileMode.Create, FileAccess.Write))
{
message.Load(new PropertySet(ItemSchema.MimeContent));
MimeContent mc = message.MimeContent;
fileStream.Write(mc.Content, 0, mc.Content.Length);
}
If eml format is an option and php is the language use base64_decode on the Mimencontent before save on file.
If using https://github.com/Heartspring/Exchange-Web-Services-for-PHP or https://github.com/hatsuseno/Exchange-Web-Services-for-PHP need to add
$newmessage->mc = $messageobj->MimeContent->_;
on line 245 or 247.
This is how I solved the problem to download from EWS the email message in .eml format via vbs code
' This is the function that retrieves the message:
function CreaMailMsg(ItemId,ChangeKey)
Dim MailMsg
Dim GetItemSOAP,GetItemResponse,Content
LogFile.WriteLine (Now() & "-" & ":CreaMailMsg:ID:" & ItemId)
GetItemSOAP=ReadTemplate("GetItemMsg.xml")
GetItemSOAP=Replace(GetItemSOAP, "<!--ITEMID-->", ItemId)
GetItemSOAP=Replace(GetItemSOAP, "<!--ITEMCHANGEKEY-->", ChangeKey)
LogFile.WriteLine (Now() & ":GetItemSOAP:" & GetItemSOAP)
set GetItemResponse=SendSOAP(GetItemSOAP,TARGETURL,"",USERNAME,PASSWORD)
' Check we got a Success response
if not IsResponseSuccess(GetItemResponse, "m:GetItemResponseMessage","ResponseClass") then
LogFile.WriteLine (Now() & "-" & ":ERRORE:Fallita GetItemMsg:" & GetItemResponse.xml)
Chiusura 1
end if
' LogFile.WriteLine (Now() & "-" & ":DEBUG:riuscita GetItemMsg:" & GetItemResponse.xml)
Content = GetItemResponse.documentElement.getElementsByTagName("t:MimeContent").Item(0).Text
' LogFile.WriteLine (Now() & ":Contenuto MIME" & Content)
CreaMailMsg = WriteAttach2File(Content,"OriginaryMsg.eml")
' MailMsg.close
CreaMailMsg = true
end function
'###########################################################################
' These are the functions the save the message in .eml format
'###########################################################################
function WriteAttach2File(Content,nomeAttach)
Dim oNode,oXML,Base64Decode
' Read the contents Base64 encoded and Write a file
set oXML=CreateObject("MSXML2.DOMDocument")
set oNode=oXML.CreateElement("base64")
oNode.DataType="bin.base64"
oNode.Text = Content
Base64Decode = Stream_Binary2String(oNode.nodeTypedValue,nomeAttach)
Set oNode = Nothing
Set oXML = Nothing
end function
'###########################################################################
function Stream_Binary2String(binary,nomeAttach)
Const adTypeText = 2
Const adTypeBinary = 1
Dim BinaryStream
Set BinaryStream=CreateObject("ADODB.Stream")
BinaryStream.Type=adTypeBinary' Binary
BinaryStream.Open
BinaryStream.Write binary
BinaryStream.Position=0
BinaryStream.Type=adTypeText
BinaryStream.CharSet = "us-ascii"
Stream_Binary2String=BinaryStream.ReadText
'msgbox Stream_Binary2String
BinaryStream.SaveToFile ShareName & "\" & nomeAttach,2
Set BinaryStream=Nothing
end function
If you are going from Outlook's EntryID via VSTO (Hex) to EwsID, you need to look here: http://bernhardelbl.wordpress.com/2013/04/15/converting-entryid-to-ewsid-using-exchange-web-services-ews/
Saved me. I kept getting a "Data is corrupt." message.
You can download all the attachments using EWS API and C# . Below is the example given:
byte[][] btAttachments = new byte[3][]; //To store 3 attachment
if (item.HasAttachments) {
EmailMessage message = EmailMessage.Bind(objService, new ItemId(item.Id.UniqueId.ToString()), new PropertySet(BasePropertySet.IdOnly, ItemSchema.Attachments));
noOfAttachment = message.Attachments.Count;
// Iterate through the attachments collection and load each attachment.
foreach(Attachment attachment in message.Attachments)
{
if (attachment is FileAttachment)
{
FileAttachment fileAttachment = attachment as FileAttachment;
// Load the file attachment into memory and print out its file name.
fileAttachment.Load();
//Get the Attachment as bytes
if (i < 3) {
btAttachments[i] = fileAttachment.Content;
i++;
}
}
// Attachment is an item attachment.
else
{
// Load attachment into memory and write out the subject.
ItemAttachment itemAttachment = attachment as ItemAttachment;
itemAttachment.Load(new PropertySet(EmailMessageSchema.MimeContent));
MimeContent mc = itemAttachment.Item.MimeContent;
if (i < 3) {
btAttachments[i] = mc.Content;
i++;
}
}
}
}
Above code converts all the attachment into bytes. Once you have bytes, you can convert bytes into your required format.
To Convert bytes into files and save in the disk follow the below links:
Write bytes to file
http://www.digitalcoding.com/Code-Snippets/C-Sharp/C-Code-Snippet-Save-byte-array-to-file.html

vbscript: convert unicode string to array of bytes

I have unicode string passed to vbscript procedure (not visual basic 6, but vbscript). I want to iterate unicode string char by char, get code for every symbol, truncate code to byte range [0..255] and create array of bytes.
This way new array of bytes should be twice smaller in memory compared to original unicode string. I am going save this array to file via ADODB.Stream object further
How can I convert unicode string to bytes array with symbol code truncated to byte range?
Thank you in advance!
Firstly, translating unicode to ascii will only work if your string only contains ascii characters. Since unicode contains ascii, it is just a matter of removing every second character.
Look up unicode on the internet for details.
EDIT: In unicode, every ascii character is proceeded with a NULL (0) byte. Remove this byte to convert the string to ASCII.
It seems there is no way to create array of bytes in vbs (though it's very straightforward in visual basic) -- all arrays are arrays of variants.
The task was to send binary stream from server to vbs script via string type. I have found the solution by creating Xml Document on the server with CDATA section that contains base64 coded array of bytes as string data.
Client (vbs) do the following:
set xmlDoc = CreateObject("Microsoft.XmlDom")
xmlDoc.loadXML(dataFromServer)
base64str = xmlDoc.DocumentElement.Text ' it's base64 coded binary stream
arrayOfBytes = decodeBase64(base64str)
Function decodeBase64(base64)
set dm = CreateObject("Microsoft.XMLDOM")
set el = dm.createElement("tmp")
el.DataType = "bin.base64"
el.Text = base64
decodeBase64 = el.NodeTypedValue
set dm = Nothing
End Function
This function creates an array of bytes:
' http://www.motobit.com/tips/detpg_binarytostring/
Function MultiByteToBinary(MultiByte)
'� 2000 Antonin Foller, 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
MultiByteToBinary = Binary
End Function
This function creates a multi-byte string.
' http://www.motobit.com/help/regedit/pa26.htm
'Converts unicode string to a multibyte string
Function StringToMB(S)
Dim I, B
For I = 1 To Len(S)
B = B & ChrB(Asc(Mid(S, I, 1)))
Next
StringToMB = B
End Function

Can't create object: ADODB.Stream

I am trying to create ADODB.Stream object in VBscript. This is the function:
Function ByteArray2Text(varByteArray)
'Convert byte array into a string with ADODB.Stream
'Data should be real plain text because binary data will be mangled
Dim byt
Const adTypeText = 2
Const adTypeBinary = 1
Set byt = CreateObject("ADODB.Stream")
byt.Type = adTypeBinary
byt.Open
byt.Write varByteArray
byt.Position = 0
byt.Type = adTypeText
byt.CharSet = "us-ascii"
ByteArray2Text = byt.ReadText
byt.Close
Set byt = Nothing
End Function
When I try to run this function i am getting error:
Microsoft VBScript runtime error: ActiveX component can't create object: 'ADODB.Stream'
What i need to do, to create this ADODB.Stream object?
Make sure that you have MDAC installed.
You also can try Microsoft Jet 4.0
You can also register these DLLs:
REGSVR32 "(path to "common files")\System\ole db\sqloledb.dll"
REGSVR32 "(path to "common files")\System\ole db\Oledb32.dll"
REGSVR32 "(path to "common files")\System\ole db\Msdasql.dll"
REGSVR32 "(path to "common files")\System\msadc\Msadce.dll"
They have relation with ADOdb
Make sure that:
The Stream component exits on your computer.
If it exists, type this at run dialog:
regsvr32 "path\stream_file_here.dll"
Chances are that the steam component file has been unregistered in the registry and you can't create an object of that.

Resources