VBA PDF File Upload Using HTTP POST - vba

I'm trying to upload a file to a .NET Core API using VBA code from a Word template. When the API receives the request, the file has a length of 0 and therefore any file I send to it becomes useless. I've uploaded files successfully to the same API using an Angular client instead of VBA, so I believe the problem lies within my VBA code. I've tested it with both .txt and .pdf files and the result is the same, 0 length file is received in the API (the end goal is to be able to upload a PDF file).
Do you see what's wrong with the code I'm using? Please see below. Any help is greatly appreciated.
Sub UploadBinary()
Const path = "C:\Users\REDACTED\VBA Upload Test\"
Const fileName = "testfile.txt"
Const CONTENT = "text/plain"
Const URL = "https://localhost:44327/api/fileUpload"
' generate boundary
Dim BOUNDARY, s As String, n As Integer
For n = 1 To 16: s = s & Chr(65 + Int(Rnd * 25)): Next
BOUNDARY = s & CDbl(Now)
Dim part As String, ado As Object
Dim header As String
' read file
Dim FILE, FILESIZE
Set ado = CreateObject("ADODB.Stream")
ado.Type = 1 'binary
ado.Open
ado.LoadFromFile path & fileName
ado.Position = 0
FILESIZE = ado.Size
FILE = ado.Read
ado.Close
Debug.Print "filesize", FILESIZE
part = "--" & BOUNDARY & vbCrLf
part = part & "Content-Disposition: form-data; name=""file""; filename=""" & fileName & """" & vbCrLf
part = part & "Content-Type: " & CONTENT & vbCrLf
part = part & "Content-Length: " & FILESIZE & vbCrLf & vbCrLf & vbCrLf
part = part & "--" & BOUNDARY & "--" & vbCrLf
header = "Content-Type" & ": " & "multipart/form-data; boundary=" & BOUNDARY
Debug.Print (header)
Debug.Print (part)
' combine part, fl , end
ado.Open
ado.Position = 0
ado.Type = 1 ' binary
ado.Write ToBytes(part)
ado.Write FILE
ado.Write ToBytes(vbCrLf & "--" & BOUNDARY & "---")
ado.Position = 0
Debug.Print ado.Size
'ado.savetofile "c:\tmp\debug.bin", 2 ' overwrite
' send request
'With CreateObject("WinHttp.WinHttpRequest.5.1")
With CreateObject("MSXML2.ServerXMLHTTP")
.Open "POST", URL, False
.SetRequestHeader "Content-Type", "multipart/form-data; boundary=" & BOUNDARY
.Send ado.Read
ado.Close
Debug.Print .ResponseText
End With
End Sub
Function ToBytes(str As String) As Variant
Dim ado As Object
Set ado = CreateObject("ADODB.Stream")
ado.Open
ado.Type = 2 ' text
ado.Charset = "_autodetect"
ado.WriteText str
ado.Position = 0
ado.Type = 1
ToBytes = ado.Read
ado.Close
End Function

Was able to make it work this way:
Public Sub UploadFile()
'Dim sFormData As String
Dim sFormData, bFormData
Dim d As String, DestURL As String, fileName As String, FilePath As String, FieldName As String
FieldName = "File"
DestURL = "https://localhost:44327/api/fileUpload"
'FileName = "testfile.txt"
'CONTENT = "text/plain"
fileName = "filename.pdf"
CONTENT = "application/pdf"
FilePath = "C:\path" & fileName
'Boundary of fields.
'Be sure this string is Not In the source file
Const Boundary As String = "---------------------------0123456789012"
Dim File, FILESIZE
Set ado = CreateObject("ADODB.Stream")
ado.Type = 1 'binary
ado.Open
ado.LoadFromFile FilePath
ado.Position = 0
FILESIZE = ado.Size
File = ado.Read
ado.Close
Set ado = CreateObject("ADODB.Stream")
d = "--" + Boundary + vbCrLf
d = d + "Content-Disposition: form-data; name=""" + FieldName + """;"
d = d + " filename=""" + fileName + """" + vbCrLf
d = d + "Content-Type: " & CONTENT + vbCrLf + vbCrLf
ado.Type = 1 'binary
ado.Open
ado.Write ToBytes(d)
ado.Write File
ado.Write ToBytes(vbCrLf + "--" + Boundary + "--" + vbCrLf)
ado.Position = 0
With CreateObject("MSXML2.ServerXMLHTTP")
.Open "POST", DestURL, False
.SetRequestHeader "Content-Type", "multipart/form-data; boundary=" & Boundary
.Send ado.Read
Debug.Print .ResponseText
End With
End Sub
Function ToBytes(str As String) As Variant
Dim ado As Object
Set ado = CreateObject("ADODB.Stream")
ado.Open
ado.Type = 2 ' text
ado.Charset = "_autodetect"
ado.WriteText str
ado.Position = 0
ado.Type = 1
ToBytes = ado.Read
ado.Close
End Function

Related

Send local Document via Telegram Bot API using VBA

Using VBA (Visual Basic for Applications), I am attempting to send a local document via the Telegram Bot API.
I have been able to send a Photo successfully and attempted to modify the code in order to send a document.
I am attempting to use the multipart/form-data method of loading the file.
When running the code, I get the following response from the Telegram server:
{"ok":false,"error_code":400,"description":"Bad Request: there is no document in the request"}
Here is a solution for sending a photograph and I have used this successfully:
Exel VBA send image using Telegram bot api
However, I now want to send a PDF document rather than an image and this is where I am stuck.
Below is the code adapted from the sending of an image in an attempt to send a PDF document.
#CDP1802 - perhaps you are able to assist?
Sub Telegram_PDF()
Const URL = "https://api.telegram.org/bot"
Const TOKEN = "**Token**"
Const METHOD_NAME = "/sendDocument?"
Const CHAT_ID = "**Chat ID**"
Const FOLDER = "C:\Users\rk\Downloads\"
Const JPG_FILE = "babok-30-poster.pdf"
Dim data As Object, key
Set data = CreateObject("Scripting.Dictionary")
data.Add "chat_id", CHAT_ID
' generate boundary
Dim BOUNDARY, s As String, n As Integer
For n = 1 To 16: s = s & Chr(65 + Int(Rnd * 25)): Next
BOUNDARY = s & CDbl(Now)
Dim part As String, ado As Object
For Each key In data.keys
part = part & "--" & BOUNDARY & vbCrLf
part = part & "Content-Disposition: form-data; name=""" & key & """" & vbCrLf & vbCrLf
part = part & data(key) & vbCrLf
Next
' filename
part = part & "--" & BOUNDARY & vbCrLf
part = part & "Content-Disposition: form-data; name=""Document""; filename=""" & JPG_FILE & """" & vbCrLf & vbCrLf
' read jpg file as binary
Dim jpg
Set ado = CreateObject("ADODB.Stream")
ado.Type = 1 'binary
ado.Open
ado.LoadFromFile FOLDER & JPG_FILE
ado.Position = 0
jpg = ado.read
ado.Close
' combine part, jpg , end
ado.Open
ado.Position = 0
ado.Type = 1 ' binary
ado.Write ToBytes(part)
ado.Write jpg
ado.Write ToBytes(vbCrLf & "--" & BOUNDARY & "---")
ado.Position = 0
Dim req As Object, reqURL As String
Set req = CreateObject("MSXML2.ServerXMLHTTP.6.0")
reqURL = URL & TOKEN & METHOD_NAME
With req
.Open "POST", reqURL, False
.setRequestHeader "Content-Type", "multipart/form-data; boundary=" & BOUNDARY
.send ado.read
Debug.Print .responseText
End With
End Sub
Try this.
P.S: Thanks to #CDP1802
Code:
Sub send_Document()
Const URL = "https://api.telegram.org/bot"
Const TOKEN = "*TOKEN*"
Const METHOD_NAME = "/sendDocument?"
Const CHAT_ID = "*CHAT_ID*"
Const FOLDER = "*PATH_TO_FILE*"
Const DOCUMENT_FILE = "*FILENAME*"
Dim data As Object, key
Set data = CreateObject("Scripting.Dictionary")
data.Add "chat_id", CHAT_ID
' generate boundary
Dim BOUNDARY, s As String, n As Integer
For n = 1 To 16: s = s & Chr(65 + Int(Rnd * 25)): Next
BOUNDARY = s & CDbl(Now)
Dim part As String, ado As Object
For Each key In data.keys
part = part & "--" & BOUNDARY & vbCrLf
part = part & "Content-Disposition: form-data; name=""" & key & """" & vbCrLf & vbCrLf
part = part & data(key) & vbCrLf
Next
' filename
part = part & "--" & BOUNDARY & vbCrLf
part = part & "Content-Disposition: form-data; name=""document""; filename=""" & DOCUMENT_FILE & """" & vbCrLf & vbCrLf
' read document file as binary
Dim doc
Set ado = CreateObject("ADODB.Stream")
ado.Type = 1 'binary
ado.Open
ado.LoadFromFile FOLDER & DOCUMENT_FILE
ado.Position = 0
doc = ado.read
ado.Close
' combine part, document, end
ado.Open
ado.Position = 0
ado.Type = 1 ' binary
ado.Write ToBytes(part)
ado.Write doc
ado.Write ToBytes(vbCrLf & "--" & BOUNDARY & "--")
ado.Position = 0
Dim req As Object, reqURL As String
Set req = CreateObject("MSXML2.XMLHTTP")
reqURL = URL & TOKEN & METHOD_NAME
With req
.Open "POST", reqURL, False
.setRequestHeader "Content-Type", "multipart/form-data; boundary=" & BOUNDARY
.send ado.read
MsgBox .responseText
End With
End Sub
Function ToBytes(str As String) As Variant
Dim ado As Object
Set ado = CreateObject("ADODB.Stream")
ado.Open
ado.Type = 2 ' text
ado.Charset = "_autodetect"
ado.WriteText str
ado.Position = 0
ado.Type = 1
ToBytes = ado.read
ado.Close
End Function
Change the key variables!

vba excel sms for api

I would like that every time I register an appointment, the client is notified by sms.
You will find attached a vba code for this purpose for sending sms.
The script seems to be executing.
On the other hand, do not deliver the sms as expected.
Someone to help me figure out what is missing please
Sub send_SMS_RDV()
Application.ScreenUpdating = False
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''essai code xfactor'''''''''''''''''''''
Set objHTTP = CreateObject("WinHttp.WinHttpRequest.5.1")
Dim Recipient As String
Dim Message As String
Dim rowname As String
Dim rowprestardv As String
Dim rowtimerdv, rownumber, rowdaterdv, x As String
rowtimerdv = Worksheets("PLANNING").Range("I4").Value
rowprestardv = Worksheets("PLANNING").Range("H4").Value
rowname = Worksheets("PLANNING").Range("N4").Value
rownumber = Worksheets("PLANNING").Range("O4").Value
rowdaterdv = Worksheets("PLANNING").Range("Q4").Value
x = "237"
Recipient = "x&lastrownumber"
'
If rowdaterdv = Worksheets("PLANNING").Range("P32").Value Then
'
Message = "Dear " & rowname & ", your appointment has been register at : " & rowtimerdv & " Contact us for any changes. Merci"
Else
'
Message = "Dear " & rowname & ", your appointment has been register at : " & rowdaterdv & " Contact us for any changes. Merci"""
'
'
End If
'Set vars where phone numbers and msg are set in your sheet'
URL = api.smsfactor.com/send?text=" + Message + "&to=" + Recipient
objHTTP.Open "GET", URL, False
objHTTP.SetRequestHeader "Authorization", "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIzNDcwOSIsImlhdCI6MTYwMTk5NzM4N30.VbWdRwVwtIn5JtwNYjeJ8imnM_2bYskRIg2O6uZG5fA" 'Your Token'
objHTTP.SetRequestHeader "Accept", "application/json"
objHTTP.send ("")
End Sub
Your URL variable looks wrong. That would need to be a string but it's not a proper string. You are missing a start quote mark and you are using + for concatenation where I would expect to see & instead.
Try changing it to this:
URL = "api.smsfactor.com/send?text=" & Message & "&to=" & Recipient
Thank you for your contribution.
I solved the problem.
here is what I modified to make it work.
Recipient = 237 & rownumber
and after url
url = Replace(url, " ", "%20")
However, I would like to configure with another provider but have problems again
Sub send_SMS_Fact()
Application.ScreenUpdating = False
' Declaring varibles for sending sms
Dim objWinHTTP As Object
Dim response, send As String
Dim sURL As String
Dim API As String
Dim SenderID As String
Dim Recipient, Message As String
' Declaring varibles for Application
Dim rowname As String
Dim rowtypevente As String
Dim rowamount, rownumber, x As String
Worksheets("FACTURATION").Activate
rowtypevente = Worksheets("FACTURATION").Range("H11").Text
rowamount = Worksheets("FACTURATION").Range("M11").Text
rowname = Worksheets("FACTURATION").Range("S11").Text
rownumber = Worksheets("FACTURATION").Range("T11").Text
API = "Um9kcnlnMTIzOnNhbG9tZQ=="
x = "237"
Recipient = x & rownumber
SenderID = "TechSoft-SMS"
' Preparation sms
If rowtypevente = "VENTE DIFF" Then
Message = "Dear " & rowname & ", The amount of your invoice which is: " & rowamount & " remains to be paid as soon as possible"
Else
rowtypevente = "VENTE"
Message = "Dear " & rowname & ", We thank you for your loyalty and hope to have satisfied you. Best regards and see you soon"
End If
' Checking for valid mobile number
If rownumber <> "700000000" Then
Else
Recipient = CStr(rownumber)
End If
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
''''''test protocole url'''''
Set objWinHTTP = CreateObject("WinHttp.WinHttpRequest.5.1")
sURL = app.techsoft-web-agency.com/sms/api"
sURL = Replace(sURL, " ", "%20")
Request = "&apikey=" & API & URLEncode(Apikey) & "&number=" & Recipient & URLEncode(Number)
Request = Request & "&message=" & Message & URLEncode(Message)
Request = Request & "&expediteur=" & SenderID & URLEncode(Expediteur) & "&msg_id=" & MsgID
objWinHTTP.Open "GET", URL & Request, False
objWinHTTP.SetTimeouts 30000, 30000, 30000, 30000
objWinHTTP.send
If objWinHTTP.StatusText = "OK" Then
strReturn = objWinHTTP.ResponseText
Debug.Print strReturn
End If
Set objWinHTTP = Nothing
send = strReturn
End Sub
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Public Function URLEncode(sRawURL) As String
On Error GoTo Catch
Dim iLoop As Integer
Dim sRtn As String
Dim sTmp As String
Const sValidChars = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz:/.?=_-$()~&"
If Len(sRawURL) > 0 Then
For iLoop = 1 To Len(sRawURL)
sTmp = Mid(sRawURL, iLoop, 1)
If InStr(1, sValidChars, sTmp, vbBinaryCompare) = 0 Then
sTmp = Hex(Asc(sTmp))
If sTmp = "20" Then
sTmp = "+"
ElseIf Len(sTmp) = 1 Then
sTmp = "%0" & sTmp
Else
sTmp = "%" & sTmp
End If
End If
sRtn = sRtn & sTmp
Next iLoop
URLEncode = sRtn
End If
Finally:
Exit Function
Catch:
URLEncode = ""
Resume Finally
End Function
This is how I configured the new url according to the documentation I found on their site.
But at the line "objWinHTTP.Open" GET ", URL & Request, False" I am told that the url uses an unrecognized protocol

Sending local photos via VBA to Telegram

I'm trying to send a local photo using VBA or VBScript. The solutions I found are either for sending URLs instead of files, or for other than VBA or VBScript.
Sub TelegramAuto()
Dim ws As Worksheet
Set ws = Sheets("hidden")
Set ws1 = Sheets("Dashboard")
Dim objRequest As Object
Dim strChatId As String
Dim strMessage As String
Dim strPhoto As String
Dim strPostPhoto As String
Dim strPostData As String
Dim strResponse As String
strChatId = <id>
strMessage = ws.Range("J5") & Format(ws1.Range("D2"), "mm/dd/yyyy") & " " & ws1.Range("D4") & " " & ws1.Range("D6") _
& " " & ws1.Range("K6")
strPhoto = "C:/Users/mhjong/Desktop/GP_FS_Breakdown.png"
strPostData = "chat_id=" & strChatId & "&text=" & strMessage
strPostPhoto = "chat_id=" & strChatId & "&photo=" & strPhoto
Set objRequest = CreateObject("MSXML2.XMLHTTP")
With objRequest
.Open "POST", "https://api.telegram.org/bot<token>/sendMessage?", False
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.send (strPostData)
End With
With objRequest
.Open "POST", "https://api.telegram.org/bot<token>/sendPhoto?", False
.setRequestHeader "Content-Type", "application/x-www-form-urlencoded"
.Send (strPostPhoto)
End With
End Sub
I can send messages. I cannot find the syntax to upload a local image and send it to Telegram.
strPhoto = "image link"
strPostPhoto = "chat_id=" & strChatId & "&photo=" & strPhoto
With objRequest
.Open "POST", "https://api.telegram.org/bot<Token>/sendPhoto?" & strPostPhoto, False
.send
End With
Public Function tmBotSend(Token As String, chat_id As String, Optional text As String = "", Optional filename As String = "", Optional pavd As String = "") As String
'https://www.planetaexcel.ru/forum/index.php?PAGE_NAME=message&FID=1&TID=93149&TITLE_SEO=93149-kak-sdelat-otpravku-v-telegram-iz-makrosa-vba-excel&MID=1193376#message1193376
'pavd as photo animation audio voice video document
'4096 chars for message.text, 200 chars for message.caption
Const adTypeBinary = 1
Const adTypeText = 2
Const adModeReadWrite = 3
Const telegram = "https://api.telegram.org/bot"
Dim part As String
part = bond("--") & form("chat_id") & chat_id & bond()
Dim dfn As String
If Len(filename) Then dfn = Dir(filename)
Dim caption As String
Dim send As String
If Len(dfn) Then
caption = "caption"
Select Case LCase(pavd)
Case "photo", "animation", "audio", "voice", "video", "document"
send = LCase(pavd)
Case Else
dfnA = Split(LCase(dfn), ".")
Select Case dfnA(UBound(dfnA))
Case "jpg", "jpeg", "png"
send = "photo"
Case "gif", "apng"
send = "animation"
Case "mp4"
send = "video"
Case "mp3", "m4a"
send = "audio"
Case "ogg"
send = "voice"
Case Else
send = "document"
End Select
End Select
Else
caption = "text"
send = "message"
End If
part = part & form(caption) & text
Dim file
Dim body
With CreateObject("ADODB.Stream")
If Len(dfn) Then
' filename
part = part & bond() & form(send, dfn)
' read file as binary
.Mode = adModeReadWrite
.Type = adTypeBinary
.Open
.LoadFromFile filename
.Position = 0
file = .Read
.Close
End If
' combine part, file , end
.Type = adTypeBinary
.Open
.Position = 0
.Write ToBytes(part)
'Debug.Print part
If Len(dfn) Then .Write file
.Write ToBytes(bond(suff:="--"))
.Position = 0
body = .Read
.Close
End With
With CreateObject("MSXML2.XMLHTTP")
'Debug.Print telegram & Token & "/send" & StrConv(send, vbProperCase)
.Open "POST", telegram & Token & "/send" & StrConv(send, vbProperCase), False
.setRequestHeader "Content-Type", "multipart/form-data; boundary=" & bond("", "")
.send body
tmBotSend = .responseText
'Debug.Print .responseText
End With
End Function
Function ToBytes(str As String) As Variant
Const adTypeBinary = 1
Const adTypeText = 2
Const adModeReadWrite = 3
With CreateObject("ADODB.Stream")
.Mode = adModeReadWrite
.Type = adTypeText
.Charset = "UTF-8" '"_autodetect"
.Open
.WriteText str
.Position = 0
.Type = adTypeBinary
ToBytes = .Read
.Close
End With
End Function
Private Function bond(Optional pref As String = vbCrLf & "--", Optional suff As String = vbCrLf, Optional BOUNDARY As String = "--OYWFRYGNCYQAOCCT44655,4239930556") As String
bond = pref & BOUNDARY & suff
End Function
Private Function form(ByVal name As String, Optional ByVal filename As String = "") As String
form = "Content-Disposition: form-data; name=""" & name & """"
If Len(filename) Then form = form & "; filename=""" & filename & """"
form = form & vbCrLf & vbCrLf
End Function

why are docx files corrupted by binary post, but .doc and .pdf are fine?

I'm posting files to an API in binary format.
.pdf and .doc files are fine - they arrive in the system as expected and open up without any problems.
But for some reason, .docx files show up as corrupt.
Why would that be?
Sub PostTheFile(CVFile, fullFilePath, PostToURL)
strBoundary = "---------------------------9849436581144108930470211272"
strRequestStart = "--" & strBoundary & vbCrlf &_
"Content-Disposition: attachment; name=""file""; filename=""" & CVFile & """" & vbcrlf & vbcrlf
strRequestEnd = vbCrLf & "--" & strBoundary & "--"
Set stream = Server.CreateObject("ADODB.Stream")
stream.Type = adTypeBinary '1
stream.Mode = adModeReadWrite '3
stream.Open
stream.Write StringToBinary(strRequestStart)
stream.Write ReadBinaryFile(fullFilePath)
stream.Write StringToBinary(strRequestEnd)
stream.Position = 0
binaryPost = stream.read
stream.Close
Set stream = Nothing
Set httpRequest = Server.CreateObject("MSXML2.ServerXMLHTTP.6.0")
httpRequest.Open "PATCH", PostToURL, False, "username", "pw"
httpRequest.setRequestHeader "Content-Type", "multipart/form-data; boundary=""" & strBoundary & """"
httpRequest.Send binPost
Response.write "httpRequest.status: " & httpRequest.status
Set httpRequest = Nothing
End Sub
Function StringToBinary(input)
dim stream
set stream = Server.CreateObject("ADODB.Stream")
stream.Charset = "UTF-8"
stream.Type = adTypeText
stream.Mode = adModeReadWrite
stream.Open
stream.WriteText input
stream.Position = 0
stream.Type = adTypeBinary
StringToBinary = stream.Read
stream.Close
set stream = Nothing
End Function
Function ReadBinaryFile(fullFilePath)
dim stream
set stream = Server.CreateObject("ADODB.Stream")
stream.Type = 1
stream.Open()
stream.LoadFromFile(fullFilePath)
ReadBinaryFile = stream.Read()
stream.Close
set stream = nothing
end function
Update:
Added in Stream.Close as pointed out. I fully expected that to solve the problem but it didn't :(
Update 2:
I've been testing with different stream modes and encodings, but nothing I try gives me any joy.
I've also tried debugging the DOCX document. I've been through all the xml files within the document looking for invalid xml - I thought this might give me a clue as to where it's going wrong, but it all comes out as valid.
How can I debug a corrupt docx file?
The file type of docx file is "application/vnd.openxmlformats-officedocument.wordprocessingml.document".
So you can solve this problem by defining nvarchar(max) for data type in your datasource table.
You did not close the stream after reading the binary file
function ReadBinaryFile(fullFilePath)
dim stream
set stream = Server.CreateObject("ADODB.Stream")
stream.Type = 1
stream.Open()
stream.LoadFromFile(fullFilePath)
ReadBinaryFile = stream.Read()
stream.Close 'here
set stream = nothing
end function

VB6/VBScript change file encoding to ansi

I am looking for a way to convert a textfile with UTF8 encoding to ANSI encoding.
How can i go around and achieve this in Visual Basic (VB6) and or vbscript?
If your files aren't truly enormous (e.g. even merely 40MB can be painfully slow) you can do this using the following code in VB6, VBA, or VBScript:
Option Explicit
Private Const adReadAll = -1
Private Const adSaveCreateOverWrite = 2
Private Const adTypeBinary = 1
Private Const adTypeText = 2
Private Const adWriteChar = 0
Private Sub UTF8toANSI(ByVal UTF8FName, ByVal ANSIFName)
Dim strText
With CreateObject("ADODB.Stream")
.Open
.Type = adTypeBinary
.LoadFromFile UTF8FName
.Type = adTypeText
.Charset = "utf-8"
strText = .ReadText(adReadAll)
.Position = 0
.SetEOS
.Charset = "_autodetect" 'Use current ANSI codepage.
.WriteText strText, adWriteChar
.SaveToFile ANSIFName, adSaveCreateOverWrite
.Close
End With
End Sub
UTF8toANSI "UTF8-wBOM.txt", "ANSI1.txt"
UTF8toANSI "UTF8-noBOM.txt", "ANSI2.txt"
MsgBox "Complete!", vbOKOnly, WScript.ScriptName
Note that it will handle UTF-8 input files either with or without a BOM.
Using strong typing and early binding will improve performance a hair in VB6, and you won't need to declare those Const values. This isn't an option in script though.
For VB6 programs that need to process very large files you might be better off using VB6 native I/O against Byte arrays and use an API call to convert the data in chunks. This adds the extra messiness of finding the character boundaries though (UTF-8 uses a variable number of bytes per character). You'd need to scan each data block you read to find a safe ending point for an API translation.
I'd look at MultiByteToWideChar() and WideCharToMultiByte() to get started.
Note that UTF-8 often "arrives" with LF line delimiters instead of CRLF.
I'm using these helper functions
Private Function pvReadFile(sFile)
Const ForReading = 1
Dim sPrefix
With CreateObject("Scripting.FileSystemObject")
sPrefix = .OpenTextFile(sFile, ForReading, False, False).Read(3)
End With
If Left(sPrefix, 3) <> Chr(&HEF) & Chr(&HBB) & Chr(&HBF) Then
With CreateObject("Scripting.FileSystemObject")
pvReadFile = .OpenTextFile(sFile, ForReading, False, Left(sPrefix, 2) = Chr(&HFF) & Chr(&HFE)).ReadAll()
End With
Else
With CreateObject("ADODB.Stream")
.Open
If Left(sPrefix, 2) = Chr(&HFF) & Chr(&HFE) Then
.Charset = "Unicode"
ElseIf Left(sPrefix, 3) = Chr(&HEF) & Chr(&HBB) & Chr(&HBF) Then
.Charset = "UTF-8"
Else
.Charset = "_autodetect"
End If
.LoadFromFile sFile
pvReadFile = .ReadText
End With
End If
End Function
Private Function pvWriteFile(sFile, sText, lType)
Const adSaveCreateOverWrite = 2
With CreateObject("ADODB.Stream")
.Open
If lType = 2 Then
.Charset = "Unicode"
ElseIf lType = 3 Then
.Charset = "UTF-8"
Else
.Charset = "_autodetect"
End If
.WriteText sText
.SaveToFile sFile, adSaveCreateOverWrite
End With
End Function
I found out that "native" FileSystemObject reading of ANSI and UTF-16/UCS-2 files is much faster that ADODB.Stream hack.
I'm using this script to convert any character set or code page (that i'm aware of).
This script can also handle large files (over one gigabytes), because it streams one line at a time.
' - ConvertCharset.vbs -
'
' Inspired by:
' http://www.vbforums.com/showthread.php?533879-Generate-text-files-in-IBM-850-encoding
' http://stackoverflow.com/questions/5182102/vb6-vbscript-change-file-encoding-to-ansii/5186170#5186170
' http://stackoverflow.com/questions/13130214/how-to-convert-a-batch-file-stored-in-utf-8-to-something-that-works-via-another
'
' Start Main
Dim objArguments
Dim strSyntaxtext, strInputCharset, strOutputCharset, strInputFile, strOutputFile
Dim intReadPosition, intWritePosition
Dim arrCharsets
Const adReadAll = -1
Const adReadLine = -2
Const adSaveCreateOverWrite = 2
Const adSaveCreateNotExist = 1
Const adTypeBinary = 1
Const adTypeText = 2
Const adWriteChar = 0
Const adWriteLine = 1
strSyntaxtext = strSyntaxtext & "Converts the charset of the input text file to output file." & vbCrLf
strSyntaxtext = strSyntaxtext & "Syntax: " & vbCrLf
strSyntaxtext = strSyntaxtext & WScript.ScriptName & " /InputCharset:utf-8|windows-1252|ibm850|..." & vbCrLf
strSyntaxtext = strSyntaxtext & " /OutputCharset:utf-8|windows-1252|ibm850|..." & vbCrLf
strSyntaxtext = strSyntaxtext & " /InputFile:\\path\to\inputfile.ext" & vbCrLf
strSyntaxtext = strSyntaxtext & " /OutputFile:\\path\to\outputfile.ext" & vbCrLf
strSyntaxtext = strSyntaxtext & " [/ShowAllCharSets]" & vbCrLf & vbCrLf
strSyntaxtext = strSyntaxtext & "Example:" & vbCrLf
strSyntaxtext = strSyntaxtext & WScript.ScriptName & " /InputCharset:ibm850 /OutputCharset:utf-8 /InputFile:my_dos.txt /OutputFile:my_utf-8.txt" & vbCrLf
Set objArgumentsNamed = WScript.Arguments.Named
If objArgumentsNamed.Count = 0 Then
WScript.Echo strSyntaxtext
WScript.Quit(99)
End If
arrCharsets = Split("big5,big5-hkscs,euc-jp,euc-kr,gb18030,gb2312,gbk,ibm-thai," &_
"ibm00858,ibm01140,ibm01141,ibm01142,ibm01143,ibm01144," &_
"ibm01145,ibm01146,ibm01147,ibm01148,ibm01149,ibm037," &_
"ibm1026,ibm273,ibm277,ibm278,ibm280,ibm284,ibm285,ibm297," &_
"ibm420,ibm424,ibm437,ibm500,ibm775,ibm850,ibm852,ibm855," &_
"ibm857,ibm860,ibm861,ibm862,ibm863,ibm864,ibm865,ibm866," &_
"ibm869,ibm870,ibm871,iso-2022-jp,iso-2022-kr,iso-8859-1," &_
"iso-8859-13,iso-8859-15,iso-8859-2,iso-8859-3,iso-8859-4," &_
"iso-8859-5,iso-8859-6,iso-8859-7,iso-8859-8,iso-8859-9," &_
"koi8-r,koi8-u,shift_jis,tis-620,us-ascii,utf-16,utf-16be," &_
"utf-16le,utf-7,utf-8,windows-1250,windows-1251,windows-1252," &_
"windows-1253,windows-1254,windows-1255,windows-1256," &_
"windows-1257,windows-1258,unicode", ",")
Set objFileSystem = CreateObject("Scripting.FileSystemObject")
For Each objArgumentNamed in objArgumentsNamed
Select Case Lcase(objArgumentNamed)
Case "inputcharset"
strInputCharset = LCase(objArgumentsNamed(objArgumentNamed))
If Not IsCharset(strInputCharset) Then
WScript.Echo "The InputCharset (" & strInputCharset & ") is not valid, quitting. The valid charsets are:" & vbCrLf
x = ShowCharsets()
WScript.Quit(1)
End If
Case "outputcharset"
strOutputCharset = LCase(objArgumentsNamed(objArgumentNamed))
If Not IsCharset(strOutputCharset) Then
WScript.Echo "The strOutputCharset (" & strOutputCharset & ") is not valid, quitting. The valid charsets are:" & vbCrLf
x = ShowCharsets()
WScript.Quit(2)
End If
Case "inputfile"
strInputFile = LCase(objArgumentsNamed(objArgumentNamed))
If Not objFileSystem.FileExists(strInputFile) Then
WScript.Echo "The InputFile (" & strInputFile & ") does not exist, quitting." & vbCrLf
WScript.Quit(3)
End If
Case "outputfile"
strOutputFile = LCase(objArgumentsNamed(objArgumentNamed))
If objFileSystem.FileExists(strOutputFile) Then
WScript.Echo "The OutputFile (" & strOutputFile & ") exists, quitting." & vbCrLf
WScript.Quit(4)
End If
Case "showallcharsets"
x = ShowCharsets()
Case Else
WScript.Echo "Unknown parameter, quitting: /" & objArgumentNamed & ":" & objArgumentsNamed(objArgumentNamed)
WScript.Echo strSyntaxtext
End Select
Next
If Len(strInputCharset) > 0 And Len(strOutputCharset) > 0 And Len(strInputFile) > 0 And Len(strOutputFile) Then
Set objInputStream = CreateObject("ADODB.Stream")
Set objOutputStream = CreateObject("ADODB.Stream")
With objInputStream
.Open
.Type = adTypeBinary
.LoadFromFile strInputFile
.Type = adTypeText
.Charset = strInputCharset
intWritePosition = 0
objOutputStream.Open
objOutputStream.Charset = strOutputCharset
Do While .EOS <> True
strText = .ReadText(adReadLine)
objOutputStream.WriteText strText, adWriteLine
Loop
.Close
End With
objOutputStream.SaveToFile strOutputFile , adSaveCreateNotExist
objOutputStream.Close
WScript.Echo "The " & objFileSystem.GetFileName(strInputFile) & " was converted to " & objFileSystem.GetFileName(strOutputFile) & " OK."
End If
' End Main
' Start Functions
Function IsCharset(strMyCharset)
IsCharset = False
For Each strCharset in arrCharsets
If strCharset = strMyCharset Then
IsCharset = True
Exit For
End If
Next
End Function
Function ShowCharsets()
strDisplayCharsets = ""
intCounter = 0
For Each strcharset in arrCharsets
intCounter = intCounter + Len(strcharset) + 1
strDisplayCharsets = strDisplayCharsets & strcharset & ","
If intCounter > 67 Then
intCounter = 0
strDisplayCharsets = strDisplayCharsets & vbCrLf
End If
Next
strDisplayCharsets = Mid(strDisplayCharsets, 1, Len(strDisplayCharsets)-1)
WScript.Echo strDisplayCharsets
End Function
' End Functions
#Bob77's answer did not work for me, so I converted #Ciove's answer to a simple sub routine and it works fine.
' Usage:
' EncodeFile strInFile, "UTF-8", strOutFile, "Windows-1254", 2
Sub EncodeFile(strInputFile, strInputCharset, strOutputFile, strOutputCharset, intOverwriteMode)
'5th parameter may take the following values:
'Const adSaveCreateOverWrite = 2
'Const adSaveCreateNotExist = 1
Const adReadLine = -2
Const adTypeBinary = 1
Const adTypeText = 2
Const adWriteLine = 1
Set objInputStream = CreateObject("ADODB.Stream")
Set objOutputStream = CreateObject("ADODB.Stream")
With objInputStream
.Open
.Type = adTypeBinary
.LoadFromFile strInputFile
.Type = adTypeText
.Charset = strInputCharset
objOutputStream.Open
objOutputStream.Charset = strOutputCharset
Do While .EOS <> True
strText = .ReadText(adReadLine)
objOutputStream.WriteText strText, adWriteLine
Loop
.Close
End With
objOutputStream.SaveToFile strOutputFile, intOverwriteMode
objOutputStream.Close
End Sub