Creating ODBC DSN using VBA - vba

I have looked at several similar questions so this is not for lack of trying.
I want to create a DSN to Postgres using VBA. I specifically don't want to use a DSN-less connection in Access. I'm suspecting it could be my connection string rather than the code but I'm not sure and I don't get any errors its just unsuccessful.
My code is as follows:
Option Compare Database
Option Explicit
Private Declare Function SQLConfigDataSource Lib "ODBCCP32.DLL" _
(ByVal hwndParent As Long, ByVal fRequest As Long, _
ByVal lpszDriver As String, ByVal lpszAttributes As String) _
As Long
Private Const ODBC_ADD_SYS_DSN = 4
Public Function CreateDSN(Driver As String, Attributes As _
String) As Boolean
CreateDSN = SQLConfigDataSource(0&, ODBC_ADD_SYS_DSN, _
Driver, Attributes)
End Function
Sub test()
Dim strConnection As String
strConnection = "ODBC;DSN=Postgres_Test;Driver=PostgreSQL Unicode;Server=************.*********.***.****;Port=*****;Database=example;Uid=********;Pwd=****************;"
Debug.Print CreateDSN("PostgreSQL Unicode", strConnection)
End Sub
All I have to go on here is False in the immediate window.
If someone could confirm if it is just the connection string (and what the right syntax is) that'd be useful. I tried looking at the properties of a linked table in Access of one where I manually created the DSN and its that and this that I used to generate the one I'm using already.

SQLConfigDataSource doesn't take a connection string. It takes a driver name, and attributes.
Let's dissect the connection string:
ODBC; : DAO-specific prefix indicating an ODBC connection string. Never needed outside of Access/DAO.
DSN=Postgres_Test: DSN name
Driver=PostgreSQL Unicode: Driver name, should never be combined with DSN name in a connection string as the DSN specifies the driver name
Server=************.*********.***.****;Port=*****;Database=example;Uid=********;Pwd=****************;: Driver-specific attributes.
If we look at the documentation, the driver attributes should be null-separated, not separated by ;, and the string should end with a double null separator.
So, the final call would need to look like this:
CreateDSN = SQLConfigDataSource(0&, ODBC_ADD_SYS_DSN, _
"PostgreSQL Unicode", "DSN=Postgres_Test" & vbNullChar & "SERVER=***" & vbNullChar & "Port=*****" & vbNullChar & 'Etc)
Making sure that to end on a vbNullChar
Furthermore, fRequest is a Word, and a Word corresponds to an Integer in VBA, so your declaration should be adjusted for that.
However, as Max pointed out, Access has a built-in for registering DSNs, and you should probably just use that, as it's way easier.

Related

How can I change the command text of an SQL connected table in Excel using VBA? [duplicate]

I have an Excel document that has a macro which when run will modify a CommandText of that connection to pass in parameters from the Excel spreadsheet, like so:
Sub RefreshData()
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary")
.OLEDBConnection.CommandText = "Job_Cost_Code_Transaction_Summary_Percentage_Pending #monthEndDate='" & Worksheets("Cost to Complete").Range("MonthEndDate").Value & "', #job ='" & Worksheets("Cost to Complete").Range("Job").Value & "'"
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Refresh
End Sub
I would like the refresh to not only modify the connection command but also modify the connection as I would like to use it with a different database also:
Just like the macro replaces the command parameters with values from the spreadsheet I would like it to also replace the database server name and database name from values from the spreadsheet.
A complete implementation is not required, just the code to modify the connection with values from the sheet will be sufficient, I should be able to get it working from there.
I tried to do something like this:
ActiveWorkbook
.Connections("Job_Cost_Code_Transaction_Summary")
.OLEDBConnection.Connection = "new connection string"
but that does not work. Thanks.
The answer to my question is below.
All of the other answers are mostly correct and focus on modifying the current connection, but I want just wanting to know how to set the connection string on the connection.
The bug came down to this. If you look at my screenshot you will see that the connection string was:
Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=ADCData_Doric;Data Source=doric-server5;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LHOLDER-VM;Use Encryption for Data=False;Tag with column collation when possible=False
I was trying to set that string with ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.Connection = "connection string"
I was getting an error when i was simply trying to assign the full string to the Connection. I was able to MsgBox the current connection string with that property but not set the connection string back without getting the error.
I have since found that the connection string needs to have OLEDB; prepended to the string.
so this now works!!!
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.Connection = "OLEDB;Provider=SQLOLEDB.1;Integrated Security=SSPI;Persist Security Info=True;Initial Catalog=ADCData_Doric;Data Source=doric-server5;Use Procedure for Prepare=1;Auto Translate=True;Packet Size=4096;Workstation ID=LHOLDER-VM;Use Encryption for Data=False;Tag with column collation when possible=False"
very subtle but that was the bug!
I think you are so close to achieve what you want.
I was able to change for ODBCConnection. Sorry that I couldn't setup OLEDBConnection to test, you can change occurrences of ODBCConnection to OLEDBConnection in your case.
Try add this 2 subs with modification, and throw in what you need to replace in the CommandText and Connection String. Note I put .Refresh to update the connection, you may not need until actual data refresh is needed.
You can change other fields using the same idea of breaking things up then Join it later:
Private Sub ChangeConnectionString(sInitialCatalog As String, sDataSource As String)
Dim sCon As String, oTmp As Variant, i As Long
With ThisWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection
sCon = .Connection
oTmp = Split(sCon, ";")
For i = 0 To UBound(oTmp) - 1
' Look for Initial Catalog
If InStr(1, oTmp(i), "Initial Catalog", vbTextCompare) = 1 Then
oTmp(i) = "Initial Catalog=" & sInitialCatalog
' Look for Data Source
ElseIf InStr(1, oTmp(i), "Data Source", vbTextCompare) = 1 Then
oTmp(i) = "Data Source=" & sDataSource
End If
Next
sCon = Join(oTmp, ";")
.Connection = sCon
.Refresh
End With
End Sub
Private Sub ChangeCommanText(sCMD As String)
With ThisWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection
.CommandText = sCMD
.Refresh
End With
End Sub
You could use a function that takes the OLEDBConnection and the parameters to be updated as inputs, and returns the new connection string. It's similar to Jzz's answer but allows some flexibility without having to edit the connection string within the VBA code each time you want to change it - at worst you'd have to add new parameters to the functions.
Function NewConnectionString(conTarget As OLEDBConnection, strCatalog As String, strDataSource As String) As String
NewConnectionString = conTarget.Connection
NewConnectionString = ReplaceParameter("Initial Catalog", strCatalog)
NewConnectionString = ReplaceParameter("Data Source", strDataSource)
End Function
Function ReplaceParameter(strConnection As String, strParamName As String, strParamValue As String) As String
'Find the start and end points of the parameter
Dim intParamStart As Integer
Dim intParamEnd As Integer
intParamStart = InStr(1, strConnection, strParamName & "=")
intParamEnd = InStr(intParamStart + 1, strConnection, ";")
'Replace the parameter value
Dim strConStart As String
Dim strConEnd As String
strConStart = Left(strConnection, intParamStart + Len(strParamName & "=") - 1)
strConEnd = Right(strConnection, Len(strConnection) - intParamEnd + 1)
ReplaceParameter = strConStart & strParamValue & strConEnd
End Function
Note that I have modified this from existing code that I have used for a particular application, so it's partly tested and might need some tweaking before it totally meets your needs.
Note as well that it'll need some kind of calling code as well, which would be (assuming that the new catalog and data source are stored in worksheet cells):
Sub UpdateConnection(strConnection As String, rngNewCatalog As Range, rngNewSource As Range)
Dim conTarget As OLEDBConnection
Set conTarget = ThisWorkbook.Connections.OLEDBConnection(strConnection)
conTarget.Connection = NewConnectionString(conTarget, rngNewCatalog.Value, rngNewSource.Value)
conTarget.Refresh
End Sub
I would like to give my small contribute here to this old topic.
If you have many connections in your Excel file, and you want to change the DB name and DB server for all of them, you can use the following code as well:
It iterates through all connections and extracts the connection string
Each connection string is split into an array of strings
It iterates through the array searching for the right connection values to modify, the others are not touched
The it recompose the array into the string and commit the change
This way you don't need to use replace and to know the previous value, and the rest of the string will remain intact.
Also, we can refer to a cell name, so you can have names in your Excel file
I hope it can help
Sub RelinkConnections()
Dim currConnValues() As String
For Each currConnection In ThisWorkbook.Connections
currConnValues = Split(currConnection.OLEDBConnection.Connection, ";")
For i = 0 To UBound(currConnValues)
If (InStr(currConnValues(i), "Initial Catalog") <> 0) Then
currConnValues(i) = "Initial Catalog=" + Range("DBName").value
ElseIf (InStr(currConnValues(i), "Data Source") <> 0) Then
currConnValues(i) = "Data Source=" + Range("DBServer").value
End If
Next
currConnection.OLEDBConnection.Connection = Join(currConnValues, ";")
currConnection.Refresh
Next
End Sub
This should do the trick:
Sub jzz()
Dim conn As Variant
Dim connectString As String
For Each conn In ActiveWorkbook.Connections
connectString = conn.ODBCConnection.Connection
connectString = Replace(connectString, "Catalog=ADCData_Doric", "Catalog=Whatever")
connectString = Replace(connectString, "Data Source=doric-server5", "Data Source=Whatever")
conn.ODBCConnection.Connection = connectString
Next conn
End Sub
It loops every connection in your workbook and change the Connection String (in the 2 replace statements).
So to modify your example:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").ODBCConnection.Connection = "new connection string"
I assume it is necessary for your to keep the same connection-name? Otherwise, it would be simplest to ignore it and create a new Connection.
You might rename the connection, and create a new one using the name:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Name = "temp"
'or, more drastic:
'ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").Delete
ActiveWorkbook.Connections.Add "Job_Cost_Code_Transaction_Summary", _
"a description", "new connection string", "command text" '+ ,command type
Afterwards, Delete this connection and reinstate the old connection/name. (I am unable to test this myself currently, so tread carefully.)
Alternatively, you might change the current connections SourceConnectionFile:
ActiveWorkbook.Connections("Job_Cost_Code_Transaction_Summary").OLEDBConnection.SourceConnectionFile = "..file location.."
This typically references an .odc file (Office Data Connection) saved on your system that contains the connection details. You can create this file from the Window's Control Panel.
You haven't specified, but an .odc file may be what your current connection is using.
Again, I am unable to test these suggestions, so you should investigate further and take some precautions - so that you won't risk losing the current connection details.

VB.NET / First row missing when writing to a CSV file

Wrote a program that writes name, postcode, device type, damage type to a CSV file and for some reason it misses the first row.
Thanks
Sub fileWriting(ByVal file As String, ByVal name As String, ByVal postcode As String, ByVal dmgType As String, ByVal devType As String) ' writing to files function
Dim ObjStreamWriter As StreamWriter
ObjStreamWriter = New StreamWriter(file, True)
ObjStreamWriter.Write(name & ",")
ObjStreamWriter.Write(postcode & ",")
ObjStreamWriter.Write(dmgType & ",")
ObjStreamWriter.WriteLine(devType)
ObjStreamWriter.Close()
End Sub
One point on the code above:
ObjStreamWriter = New StreamWriter(file, True)
will append to an existing file, if the file already exists. Therefore the text you're appending may exist further down in the file depending how much you've already written.
Additionally, StreamWriter implements IDisposable so the preferred method of implementation is to instantiate it in a Using block, if you're immediately closing the object.
Using ObjStreamWriter = New StreamWriter(file, True)
ObjStreamWriter.Write(name & ",")
ObjStreamWriter.Write(postcode & ",")
ObjStreamWriter.Write(dmgType & ",")
ObjStreamWriter.WriteLine(devType)
End Using
I'm not sure if these will fix your issue as there's insufficient code to really determine that, but if you're having issues with text not flushing, this ensures that you're automatically doing everything needed to properly close the file. Normally that'd be text missing from the end of the file, not the start, but it might help.

Generate a SSRS report in default browser from VB.NET

I have a VB.NET solution that stores data to a SQL database. I have written the first of several SSRS reports. Now I want to generate the reports from my VB.NET solution.
I have a subroutine that will generate the report,
Public Shared Sub GenerateReport(ByVal RptName As String, ByVal ParamArray Params() As Object)
Dim strPath As String = sqlSSRS + Replace(RptName, " ", "%20")
Dim _class As cParameters
'strPath += "&rc:Parameters=false&rs:Command=Render"
'strPath += "&rs:Command=Render"
For i As Integer = 0 To UBound(Params)
_class = DirectCast(Params(i), cParameters)
strPath += "&" & _class.ParamName & "=" & _class.Value
Next
System.Diagnostics.Process.Start(strPath)
End Sub
If I generate a path with no parameters the report will open in the default browser. So this works...
http://sqlServerName:80/Reports/report/ToolCrib/Toolbox%20by%20Installer
But neither this ...
http://sqlServerName:80/Reports/report/ToolCrib/Toolbox%20by%20Installer&#UserID=7&#ProjectID=20026&#ToolboxID=10&#ToolStatus=2
or this
http://sqlServerName:80/Reports/report/ToolCrib/Toolbox%20by%20Installer&UserID=7&ProjectID=20026&ToolboxID=10&ToolStatus=2
does.
I obviously have an issue passing parameters. In one case I don't need them but in other cases I want to provide them, which is why I wrote the GenerateReport routine with the optional Parameter array. Here is the error message I get which I know from past experience is sort of a catch all when MS doesn't "know" how else to classify an SSRS error.
The path of the item '/ToolCrib/Toolbox by Installer&UserID=7&ProjectID=20026&ToolboxID=10&ToolStatus=2' is not valid. The full path must be less than 260 characters long; other restrictions apply. If the report server is in native mode, the path must start with slash. (rsInvalidItemPath)
Any help will be greatly appreciated.
You path needs to use reportserver? instead of Reports/report when using parameters.
Try
http://sqlServerName:80/reportserver?/ToolCrib/Toolbox%20by%20Installer&UserID=7&ProjectID=20026&ToolboxID=10&ToolStatus=2
You could add a REPLACE:
strPath = Replace(strPath, "/Reports/report/", "/reportserver?/")
For more reading, you can check out
MS Docs url-access-parameter-reference

Excel VBA bug accessing HelpFile property from macro-disabled instance?

I think I've stumbled upon a bug in Excel - I'd really like to verify it with someone else though.
The bug occurs when reading the Workbook.VBProject.HelpFile property when the workbook has been opened with the opening application's .AutomationSecurity property set to ForceDisable. In that case this string property returns a (probably) malformed Unicode string, which VBA in turn displays with question marks. Running StrConv(..., vbUnicode) on it makes it readable again, but it sometimes looses the last character this way; this might indicate that the unicode string is indeed malformed or such, and that VBA therefore tries to convert it first and fails.
Steps to reproduce this behaviour:
Create a new Excel workbook
Go to it's VBA project (Alt-F11)
Add a new code module and add some code to it (like e.g. Dim a As Long)
Enter the project's properties (menu Tools... properties)
Enter "description" as Project description and "abc.hlp" as Help file name
Save the workbook as a .xlsb or .xlsm
Close the workbook
Create a new Excel workbook
Go to it's VBA project (Alt-F11)
Add a fresh new code module
Paste the code below in it
Adjust the path on the 1st line so it points to the file you created above
Run the Test routine
The code to use:
Const csFilePath As String = "<path to your test workbook>"
Sub TestSecurity(testType As String, secondExcel As Application, security As MsoAutomationSecurity)
Dim theWorkbook As Workbook
secondExcel.AutomationSecurity = security
Set theWorkbook = secondExcel.Workbooks.Open(csFilePath)
Call MsgBox(testType & " - helpfile: " & theWorkbook.VBProject.HelpFile)
Call MsgBox(testType & " - helpfile converted: " & StrConv(theWorkbook.VBProject.HelpFile, vbUnicode))
Call MsgBox(testType & " - description: " & theWorkbook.VBProject.Description)
Call theWorkbook.Close(False)
End Sub
Sub Test()
Dim secondExcel As Excel.Application
Set secondExcel = New Excel.Application
Dim oldSecurity As MsoAutomationSecurity
oldSecurity = secondExcel.AutomationSecurity
Call TestSecurity("enabled macros", secondExcel, msoAutomationSecurityLow)
Call TestSecurity("disabled macros", secondExcel, msoAutomationSecurityForceDisable)
secondExcel.AutomationSecurity = oldSecurity
Call secondExcel.Quit
Set secondExcel = Nothing
End Sub
Conclusion when working from Excel 2010:
.Description is always readable, no matter what (so it's not like all string properties behave this way)
xlsb and xlsm files result in an unreadable .HelpFile only when macros are disabled
xls files result in an unreadable .HelpFile in all cases (!)
It might be even weirder than that, since I swear I once even saw the questionmarks-version pop up in the VBE GUI when looking at such a project's properties, though I'm unable to reproduce that now.
I realize this is an edge case if ever there was one (except for the .xls treatment though), so it might just have been overlooked by Microsoft's QA department, but for my current project I have to get this working properly and consistently across Excel versions and workbook formats...
Could anyone else test this as well to verify my Excel installation isn't hosed? Preferably also with another Excel version, to see if that makes a difference?
Hopefully this won't get to be a tumbleweed like some of my other posts here :) Maybe "Tumbleweed generator" might be a nice badge to add...
UPDATE
I've expanded the list of properties to test just to see what else I could find, and of all the VBProject's properties (BuildFileName, Description, Filename, HelpContextID, HelpFile, Mode, Name, Protection and Type) only .HelpFile has this problem of being mangled when macros are off.
UPDATE 2
Porting the sample code to Word 2010 and running that exhibits exactly the same behaviour - the .HelpFile property is malformed when macros are disabled. Seems like the code responsible for this is Office-wide, probably in a shared VBA library module (as was to be expected TBH).
UPDATE 3
Just tested it on Excel 2007 and 2003, and both contain this bug as well. I haven't got an Excel XP installation to test it out on, but I can safely say that this issue already has a long history :)
I've messed with the underlying binary representation of the strings in question, and found out that the .HelpFile string property indeed returns a malformed string.
The BSTR representation (underwater binary representation for VB(A) strings) returned by the .HelpFile property lists the string size in the 4 bytes in front of the string, but the following content is filled with the ASCII representation and not the Unicode (UTF16) representation as VBA expects.
Parsing the content of the BSTR returned and deciding for ourselves which format is most likely used fixes this issue in some circumstances. Another issue is unfortunately at play here as well: it only works for even-length strings... Odd-length strings get their last character chopped off, their BSTR size is reported one short, and the ASCII representation just doesn't include the last character either... In that case, the string cannot be recovered fully.
The following code is the example code in the question augmented with this fix. The same usage instructions apply to it as for the original sample code. The RecoverString function performs the needed magic to, well, recover the string ;) DumpMem returns a 50-byte memory dump of the string you pass to it; use this one to see how the memory is layed out exactly for the passed-in string.
Const csFilePath As String = "<path to your test workbook>"
Private Declare Sub CopyMemoryByte Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Byte, ByVal Source As Long, ByVal Length As Integer)
Private Declare Sub CopyMemoryWord Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Integer, ByVal Source As Long, ByVal Length As Integer)
Private Declare Sub CopyMemoryDWord Lib "kernel32" Alias "RtlMoveMemory" (ByRef Destination As Long, ByVal Source As Long, ByVal Length As Integer)
Function DumpMem(text As String) As String
Dim textAddress As LongPtr
textAddress = StrPtr(text)
Dim dump As String
Dim offset As Long
For offset = -4 To 50
Dim nextByte As Byte
Call CopyMemoryByte(nextByte, textAddress + offset, 1)
dump = dump & Right("00" & Hex(nextByte), 2) & " "
Next
DumpMem = dump
End Function
Function RecoverString(text As String) As String
Dim textAddress As LongPtr
textAddress = StrPtr(text)
If textAddress <> 0 Then
Dim textSize As Long
Call CopyMemoryDWord(textSize, textAddress - 4, 4)
Dim recovered As String
Dim foundNulls As Boolean
foundNulls = False
Dim offset As Long
For offset = 0 To textSize - 1
Dim nextByte As Byte
Call CopyMemoryByte(nextByte, textAddress + offset, 1)
recovered = recovered & Chr(CLng(nextByte) + IIf(nextByte < 0, &H80, 0))
If nextByte = 0 Then
foundNulls = True
End If
Next
Dim isNotUnicode As Boolean
isNotUnicode = isNotUnicode Mod 2 = 1
If foundNulls And Not isNotUnicode Then
recovered = ""
For offset = 0 To textSize - 1 Step 2
Dim nextWord As Integer
Call CopyMemoryWord(nextWord, textAddress + offset, 2)
recovered = recovered & ChrW(CLng(nextWord) + IIf(nextWord < 0, &H8000, 0))
Next
End If
End If
RecoverString = recovered
End Function
Sub TestSecurity(testType As String, secondExcel As Application, security As MsoAutomationSecurity)
Dim theWorkbook As Workbook
secondExcel.AutomationSecurity = security
Set theWorkbook = secondExcel.Workbooks.Open(csFilePath)
Call MsgBox(testType & " - helpfile: " & theWorkbook.VBProject.HelpFile & " - " & RecoverString(theWorkbook.VBProject.HelpFile))
Call MsgBox(testType & " - description: " & theWorkbook.VBProject.Description & " - " & RecoverString(theWorkbook.VBProject.Description))
Call theWorkbook.Close(False)
End Sub
Sub Test()
Dim secondExcel As Excel.Application
Set secondExcel = New Excel.Application
Dim oldSecurity As MsoAutomationSecurity
oldSecurity = secondExcel.AutomationSecurity
Call TestSecurity("disabled macros", secondExcel, msoAutomationSecurityForceDisable)
Call TestSecurity("enabled macros", secondExcel, msoAutomationSecurityLow)
secondExcel.AutomationSecurity = oldSecurity
Call secondExcel.Quit
Set secondExcel = Nothing
End Sub

WinAPI FTPGetFile Conversion From ANSI to Unicode

Premise: Copying files from Linux to Windows over FTP using WinInet FtpGetFile.
Objective: The files originate as ANSI and are needed in Unicode.
Progress:
The only issue I am having is that I need LF characters from the original file to be CRLF characters in the destination file.
I have tried:
Public Declare Function FtpGetFile Lib "wininet.dll" Alias "FtpGetFileW" (ByVal hFTP As Long, ByVal sRemoteFile As String, ByVal sNewFile As String, ByVal bFailIfExists As Boolean, ByVal lFlagsAndAttributes As Long, ByVal lFlags As Long, ByVal lContext As Long) As Boolean
Public Sub test(hConn as Long, strSrcPath as String, strDestPath as String)
'All code works other than the file not converting to having CR chars
ftpGetFile(hConn, StrConv(strSrcPath, vbUnicode), StrConv(strDestPath, vbUnicode), True, 0, 0, 0)
End Sub
(FAILS to convert) using the Unicode version of the FtpGetFile method (Alias FtpGetFileW), passing the arguments using StrConv(<string>, vbUnicode). The files show up with only LF chars at the end of the lines.
(WORKS, manually) copying files manually using WinSCP. It automatically makes the output files Unicode but I can't find the method/settings associated with this. I cannot use the WinSCP.dll at my work as I cannot register it.
(WORKS, slowly) using a work-around. using the either version of the FtpGetFile. Opening file, reading to variable, closing file and then opening file for write, writing Replace(variable,Chr(10),Chr(13)&Chr(10)). Also, files appear to ~double in size.
How do I get a file using the WinAPI functions and have it convert in one shot (if possible)?
Related articles:
Unicode turns ANSI after FTP transfer
Writing ANSI string to Unicode file over FTP
Source Info:
How to Create FTP Components CodeGuru
MSDN for WinInet
The following appears to be working near instantaneously. If anyone has any better suggestions on how to automate this (preferably without this work-around or to make my work-around better) please provide them. Otherwise, I'll probably be choosing this as the answer in a few days. ftpReadFile is a custom function that uses InternetReadFile and spits out the entire file as a string.
Public Function ftpGetFileToUnicode(hConn As Long, strFromPath As String, strDestPath As String) As Boolean
Dim hFile As Long
Dim objFS As New FileSystemObject, objFile As TextStream
If Not objFS.FileExists(strDestPath) Then
Set objFile = objFS.CreateTextFile(strDestPath, ForWriting)
objFile.Write Replace(ftpReadFile(hConn, strFromPath), Chr(10), Chr(13) & Chr(10))
objFile.Close
If objFS.GetFile(strDestPath).Size > 0 Then
ftpGetFileToUnicode = True
Exit Function
End If
End If
ftpGetFileToUnicode = False
End Function
Note: Creates a 0 byte file if the file doesn't exist. Can easily be changed to not do that.
Disclaimer: I know nothing about VB. But FtpGetFile says it supports ASCII mode transfers, which have implicit line ending conversion:
ftpGetFile(hConn, StrConv(strSrcPath, vbUnicode), StrConv(strDestPath, vbUnicode),
True, 0, FTP_TRANSFER_TYPE_ASCII, 0)