I am have problems with an older classic ASP application where read receipts are employed in the code. This particular routine has been used for a number of years successfully but recently we have migrated to a new server with Windows 2008 r2, IIS 7.5 and SmarterMail 11.07 client. Now whenever the application attempts to send a server generated email, the SmarterMail logs are showing it successfully receives and authenticates the submission, but seems to terminate or abort the connection without cause and won't send the email without any errors. I'm at a loss here because like I stated, this routine has worked without problems for some time now and if we don't select the "read-receipt" option in the ASP application the email distributes just fine. I have included a shortened version of the code below and mainly looking for validation that the code for all intended purposes should work as is and if anyone may know of similar issues related to this within SmarterMail itself, like some kind of default security setting I am unaware of. Incidentally, if we manually use the SmarterMail webmail interface, we can successfully send mail with read receipts, so I know it should be possible.
- Thank you kindly!
Code Example
set Mail = Server.CreateObject("CDO.Message")
'Basic configuration
Mail.AutoGenerateTextBody = 0
Mail.Configuration.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserver") = strHost
Mail.Configuration.Fields("http://schemas.microsoft.com/cdo/configuration/sendusing") = 2
Mail.Configuration.Fields("http://schemas.microsoft.com/cdo/configuration/smtpserverport") = strPort
Mail.Configuration.Fields.Update
Mail.Fields("urn:schemas:mailheader:X-Mailer") = "Custom EMailer"
Mail.Fields.update
'Priority
Mail.Fields("urn:schemas:mailheader:X-Priority") = emPriority
Mail.Fields("urn:schemas:mailheader:X-MSMail-Priority") = emPriorityTxt
Mail.Fields("urn:schemas:httpmail:importance") = emPriority
Mail.Fields.update
'Addressing
With Mail
.From = strFrom
.ReplyTo = strReplyTo
.To = strTo
.CC = strCc
.BCC = strBCc
End With
'Attachments
if len(sfls) > 0 then
if instr(1,sfls,",",1) > 0 then
strAttachments = split(sfls,",",-1,1)
else
strAttachments = sfls
end if
if isarray(strAttachments) then
for x = 0 to ubound(strAttachments)
Mail.AddAttachment strAttachments(x)
next
else
Mail.AddAttachment strAttachments
end if
end if
'Body
Mail.Subject = strSubject
if isHTML then
Mail.HTMLBody = strBody
else
Mail.TextBody = strBodyTxt
end if
'###################################################################
'If this section is included the email will not send, times out or
'connection is terminated, according to the SmarterMail logs
'###################################################################
' Read/Delivery Receipt
if isRead then
Mail.Fields("urn:schemas:mailheader:disposition-notification-to") = rrReplyTo
Mail.Fields("urn:schemas:mailheader:return-receipt-to") = rrReplyTo
Mail.DSNOptions = 14
Mail.Fields.Update
end if
'###################################################################
'Send Message
strErr = ""
bSuccess = False
On Error Resume Next
Mail.Send
If Err <> 0 Then
strErr = Err.Description
else
bSuccess = True
End If
set Mail = nothing
============================================================
Related
I am working on a Simple MAPI interface with Visual Studio 2008 to send email and attachments from an application. Works great with Thunderbird and Outlook 6 but Outlook 2013 is giving me all sorts of grief.
There are two key issues:
1) The email goes to Outlook's outbox but when it sends it bounces back (or appears to as I think this is internal) with the message "None of your e-mail accounts could send to this recipient."
If I compose a new message in Outlook with that exact email address it works fine. I've tried two different outbound SMTP accounts that I know both work, and work manually, but with IMAP they are stuck. The IMAP code I used didn't have originator data and I've hacked that in to try to make it work (the code I'm posting is a bit raw because I'm still trying to figure this out)
2) Outlook will display "A program is trying to send an e-mail message on your behalf".
I've set the the Outlook Trust Centre access to "Never warn about activity" as described in loads of online help but the problem persists. I can't help but think these might be related?
I'm wondering if Outlook 2013 is needing more data than I'm providing or if I'm making some cockeyed assumption.
Note: these test are on three different machines - Thunderbird on my main Win 10 development machine, Outlook 6 on a XP virtual box, and Outlook 2013 is on another Windows 10 machine.
Note on the code: I'm using CPtrArrays to store the data passed by the calling function. You'll see the GetAt() in setting up the recipients.
Thanks!
MapiRecipDesc sender[1];
MapiRecipDesc recipient[50];
MapiFileDesc fileDesc[20];
sender[0].ulRecipClass = MAPI_ORIG;
sender[0].lpszAddress = "me#me.net";
sender[0].lpszName = "Me";
sender[0].lpEntryID = 0;
sender[0].ulEIDSize = 0;
sender[0].ulReserved = 0;
iToCount = 0;
iIndex = 0;
while (iIndex < m_paTo.GetCount()) {
recipient[iToCount].ulRecipClass = MAPI_TO;
recipient[iToCount].lpszAddress = (char *) m_paTo.GetAt(iToCount);
recipient[iToCount].lpszName = (char *) m_paTo.GetAt(iToCount);
recipient[iToCount].lpEntryID = 0;
recipient[iToCount].ulEIDSize = 0;
recipient[iToCount].ulReserved = 0;
iIndex++;
iToCount++;
}
iIndex = 0;
while (iIndex < m_paCC.GetCount()) {
recipient[iToCount].ulRecipClass = MAPI_CC;
recipient[iToCount].lpszAddress = (char *) m_paCC.GetAt(iIndex);
recipient[iToCount].lpszName = (char *) m_paCC.GetAt(iIndex);
recipient[iToCount].lpEntryID = 0;
recipient[iToCount].ulEIDSize = 0;
recipient[iToCount].ulReserved = 0;
iIndex++;
iToCount++;
}
iIndex = 0;
while (iIndex < m_paBCC.GetCount()) {
recipient[iToCount].ulRecipClass = MAPI_BCC;
recipient[iToCount].lpszAddress = (char *) m_paBCC.GetAt(iIndex);
recipient[iToCount].lpszName = (char *) m_paBCC.GetAt(iIndex);
recipient[iToCount].lpEntryID = 0;
recipient[iToCount].ulEIDSize = 0;
recipient[iToCount].ulReserved = 0;
iIndex++;
iToCount++;
}
iFileCount = 0;
iIndex = 0;
while (iIndex < m_paAttachments.GetCount()) {
fileDesc[iFileCount].flFlags = 0;
fileDesc[iFileCount].lpFileType = 0;
fileDesc[iFileCount].lpszFileName = (char *) m_paAttachments.GetAt(iIndex);
fileDesc[iFileCount].lpszPathName = (char *) m_paAttachments.GetAt(iIndex);
fileDesc[iFileCount].nPosition = -1;
fileDesc[iFileCount].ulReserved = 0;
iIndex++;
iFileCount++;
}
TCHAR szSubject[_MAX_PATH];
TCHAR szMessage[5001];
::StrCpy(szSubject, m_sSubject);
::StrCpy(szMessage, m_sMessage);
MapiMessage message;
::ZeroMemory(&message, sizeof(message));
message.lpszSubject = szSubject;
message.nRecipCount = iToCount;
message.lpRecips = recipient;
message.nFileCount = iFileCount;
message.lpFiles = fileDesc;
message.lpszNoteText = szMessage;
message.flFlags = MAPI_SENT | MAPI_UNREAD;
message.lpszConversationID = "123";
message.lpOriginator = sender;
//int nError = SendMail(0, (ULONG_PTR)hWndParent, &message, MAPI_LOGON_UI|MAPI_DIALOG, 0);
int nError = SendMail(0, (ULONG_PTR)hWndParent, &message, MAPI_LOGON_UI, 0);
if (nError != SUCCESS_SUCCESS &&
nError != MAPI_USER_ABORT &&
nError != MAPI_E_LOGIN_FAILURE) {
CString sMessage;
CString sTest = recipient[0].lpszAddress;
sMessage.Format("MapiMail:: SendMail Error code %d Recip count %d: first Recip: %s", nError, message.nRecipCount, sTest);
AfxMessageBox(sMessage);
lLog.WriteString(sMessage);
return false;
}
With Barmak Shemirani's advice I'm posting my own answer. If anyone else is looking for this info, maybe I can save them some time by posting it here in one place.
The "None of your e-mail accounts could send to this recipient." problem can be solved by putting the email address of the recipient in pointy brackets in the name field in the recipient structure. Leave the address field blank. So, for example
::StrCpy(szTo, "<address#email.com>");
recipient[iToCount].ulRecipClass = MAPI_TO;
recipient[iToCount].lpszAddress = 0;
recipient[iToCount].lpszName = szTo;
recipient[iToCount].lpEntryID = 0;
recipient[iToCount].ulEIDSize = 0;
recipient[iToCount].ulReserved = 0;
I've tested this with Outlook 2013 and 2016 and with Outlook 6, Thunderbird, and EM Client and they're all happy with it. Apparently you can put the name as well PersonName <name#email.com>, but I haven't tested that.
The issue of Outlook displaying:
A program is trying to send an e-mail message on your behalf
is a software configuration issue. Most websites advise using the Trust Centre to set the Programmatic Access to allow other applications access via MAPI. You have to set this running the program as administrator but you also have to run Outlook as administrator for it to work
The workaround is to edit the registry and add a DWORD PromptSimpleMAPISend with a value of 2 in:
Computer\HKEY_CURRENT_USER\Software\Policies\Microsoft\Office\x.0\Outlook\Security
Bonus answer. If anyone is wondering about HTML in MAPI, it isn't supported. MAPI apparently pre-dates the common usage of HTML email.
There is a workaround. You can leave your message body blank and instead put your message in html in a file and put that file as the first attachment in the MAPI message. I named my file with an .html extension.
It is a bit of fluke - the email clients pick it up and display the HTML in the body of the email. Your html file will still be an attachment followed by any other attachments.
I've tested with Thunderbird, Outlook, and EM Client. I took a quick look at one web email reader and it did not display the html text (although the attachment was available to read).
I have a windows service with a timer. About 3 times a day the timer uploads files to different ftp servers. I set the timer, upload the files, then set the next time. This worked fine for a while, until I added another ftpserver for uploading files. When uploading to that ftpserver the project hangs at manualresetevent.waitone (even though the folder was uploaded)
Here part of the code, let me know if more is needed.
Dim state As New FtpState
Dim request As FtpWebRequest = DirectCast(WebRequest.Create(target), FtpWebRequest)
request.Method = WebRequestMethods.Ftp.UploadFile
request.Credentials = mycredentials
state.Request = request
state.FileName = fileName
' Get the event to wait on.
waitObject = state.OperationComplete
' Asynchronously get the stream for the file contents.
request.BeginGetRequestStream(New AsyncCallback(AddressOf EndGetStreamCallback), state)
' Block the current thread until all operations are complete.
waitObject.WaitOne()
' The operations either completed or threw an exception.
If state.OperationException IsNot Nothing Then
Throw New Exception(state.OperationException.ToString)
Else
Publish.sendMail("Upload completed for filename:" & fileName & state.StatusDescription)
End If
End If
This ftpserver works a little different than the others that I'm using and I'm not sure if thats the cause of the problem.
Here is the difference: I upload a zip folder (not just files) which can be quite large and soon after it's uploaded, it is being moved from that ftpserver.
(Whereas the other ftpservers leave the files on the ftpserver)
I think this problem only started once the zipfolder got larger.
I know that it is uploaded and then deleted from there.
So if the upload completed, why does it get stuck at waitone?
Here my endstreamcallback function
Private Shared Sub EndGetStreamCallback(ByVal ar As IAsyncResult)
Dim state As ftpState = DirectCast(ar.AsyncState, ftpState)
Dim requestStream As Stream = Nothing
' End the asynchronous call to get the request stream.
Try
requestStream = state.Request.EndGetRequestStream(ar)
' Copy the file contents to the request stream.
Const bufferLength As Integer = 2048
Dim buffer As Byte() = New Byte(bufferLength - 1) {}
Dim count As Integer = 0
Dim readBytes As Integer = 0
Dim stream As FileStream = File.OpenRead(state.FileName)
Do
readBytes = stream.Read(buffer, 0, bufferLength)
requestStream.Write(buffer, 0, readBytes)
count += readBytes
Loop While readBytes <> 0
'Console.WriteLine("Writing {0} bytes to the stream.", count)
' IMPORTANT: Close the request stream before sending the request.
requestStream.Close()
' Asynchronously get the response to the upload request.
state.Request.BeginGetResponse(New AsyncCallback(AddressOf EndGetResponseCallback), state)
' Return exceptions to the main application thread.
Catch e As Exception
Publish.sendMail("Could not get the request stream.")
state.OperationException = e
state.OperationComplete.[Set]()
Return
End Try
End Sub
' The EndGetResponseCallback method
' completes a call to BeginGetResponse.
Private Shared Sub EndGetResponseCallback(ByVal ar As IAsyncResult)
Dim state As FtpState = DirectCast(ar.AsyncState, FtpState)
Dim response As FtpWebResponse = Nothing
Try
response = DirectCast(state.Request.EndGetResponse(ar), FtpWebResponse)
response.Close()
state.StatusDescription = response.StatusDescription
' Signal the main application thread that
' the operation is complete.
state.OperationComplete.[Set]()
' Return exceptions to the main application thread.
Catch e As Exception
Publish.sendMail("Error getting response.")
state.OperationException = e
state.OperationComplete.[Set]()
End Try
End Sub
I have written a script which downloads data from yahoo finance into excel using querytable. It should loop through each URL and download the data but it can't get past the second loop and fails at the .Refresh BackroundQuery:=False with the error code 1004 - An unexpected error has occurred.
Here is the code:
rowOffset = 0
url = Worksheets("Yahoo codes").Range("b2").Offset(rowOffset, 0)
Do While url <> ""
With ActiveSheet.QueryTables.Add(Connection:="URL;" & url, Destination:=Worksheets("Yahoo Data").Range("A65536").End(xlUp).Offset(1, 0))
.RefreshStyle = xlOverwriteCells
.SaveData = True
.BackgroundQuery = True
.Refresh BackgroundQuery:=False
'.Refresh
End With
rowOffset = rowOffset + 1
url = Worksheets("Yahoo Data").Range("a2").Offset(rowOffset, 0)
Loop
It looks like you get the first URL from B2 and subsequent URLs from A3...
When you get the error, got to the Immediate Window (Ctl+G) and type
?Worksheets("Yahoo Data").QueryTables(2).Connection
and see if it looks right. If not, I suspect your second url = ... statement is wrong.
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
I have a ColdFusion application that I use to transfer files between our development and production servers. The code that actually sends the files is as follows:
ftp = new Ftp();
ftp.setUsername(username);
ftp.setPassword(password);
ftp.setServer(server);
ftp.setTimeout(1800);
ftp.setConnection('dev');
ftp.open();
ftp.putFile(transferMode="binary",localFile=localpath,remoteFile=remotepath);
ftp.close(connection='dev');
When sending a file using the above code, I cap out at just under 100KB/s(monitored via FileZilla on the receiving server). If I send the exact same file using the Windows command-line FTP tool, my speeds are upwards of 1000KB/s.
I created a brand new file with nothing but the code above and that has no effect on the transfer speed, so I know it has nothing to do with the surrounding code in the original application.
So, what could be causing these abysmally low speeds?
Edit: All tests are being done transferring files from my production server to my development server. I also tried using the <cfftp> tag instead of cfscript, and I have the same results.
Edit #2: I ended up using cfexecute, the code is as follows:
From my FTP script:
public function sendFiles(required string localpath, required string remotepath) {
this.writeFtpInstructions(localpath);
exe = "C:\Windows\system32\ftp.exe";
params = "-s:" & request.localapproot & "/" & "upload.txt";
outputfile = request.localapproot & '/ftp.log';
timeout = 120;
Request.cliExec(exe,params,outputfile,timeout);
}
public function writeFtpInstructions(required string localpath) {
instructions = request.localapproot & "/" & "upload.txt";
crlf = chr(13) & chr(10);
data = "";
data &= "open " & this.server & crlf;
data &= this.username & crlf;
data &= this.password & crlf;
data &= "cd " & request.remoteapproot & crlf;
data &= "put " & localpath & crlf;
data &= "quit";
FileWrite(instructions, data);
}
The cliExec() function(necessary to create a wrapper since there is no equivalent of cfexecute in cfscript):
<cffunction name="cliExec">
<cfargument name="name">
<cfargument name="arguments">
<cfargument name="outputfile">
<cfargument name="timeout">
<cfexecute
name="#name#"
arguments="#arguments#"
outputFile="#outputfile#"
timeout="#timeout#" />
</cffunction>
With my experience using cfftp on CF9, it was impossible to transfer larger files. If you view the active transfers on the FTP server side, you will notice that the transfers start out at top speed, but as more & more data is transmitted the speeds keep dropping. After 100 MB had been transfered, the reduction started to become very drastic, until they eventually reached a single digit crawl. Eventually the transfer timed out & failed. I was trying to work with a 330 MB file & found it impossible to transfer using cfftp. The cfexecute was not an option for me using the standard windows ftp.exe, because it doesn’t seem to support SFTP.
I ended up seeking out an external java class to use through coldfusion & settled on JSch (http://www.jcraft.com/jsch/). Ironically, CF9 appears to use a variation of this class for CFFTP (jsch-0.1.41m.jar), but the results are much different using this latest downloaded version (jsch-0.1.45.jar).
Here is the code that I put together for a proof of concept:
<cfscript>
stAppPrefs = {
stISOFtp = {
server = 'sftp.server.com',
port = '22',
username = 'youser',
password = 'pa$$w0rd'
}
};
/* Side-Load JSch Java Class (http://www.jcraft.com/jsch/) */
try {
// Load Class Using Mark Mandel's JavaLoader (http://www.compoundtheory.com/?action=javaloader.index)
/*
Add Mark's LoaderClass To The ColdFusion Class Path Under CF Admin:
Java and JVM : ColdFusion Class Path : C:\inetpub\wwwroot\javaloader\lib\classloader-20100119110136.jar
Then Restart The Coldfusion Application Service
*/
loader = CreateObject("component", "javaloader.JavaLoader").init([expandPath("jsch-0.1.45.jar")]);
// Initiate Instance
jsch = loader.create("com.jcraft.jsch.JSch").init();
}
catch(any excpt) {
WriteOutput("Error loading ""jsch-0.1.45.jar"" java class: " & excpt.Message & "<br>");
abort;
}
/* SFTP Session & Channel */
try {
// Create SFTP Session
session = jsch.getSession(stAppPrefs.stISOFtp.username, stAppPrefs.stISOFtp.server, stAppPrefs.stISOFtp.port);
// Turn Off & Use Username/Password
session.setConfig("StrictHostKeyChecking", "no");
session.setPassword(stAppPrefs.stISOFtp.password);
session.connect();
// Create Channel To Transfer File(s) On
channel = session.openChannel("sftp");
channel.connect();
}
catch(any excpt) {
WriteOutput("Error connecting to FTP server: " & excpt.Message & "<br>");
abort;
}
// Returns Array Of Java Objects, One For Each Zip Compressed File Listed In Root DIR
// WriteDump(channel.ls('*.zip'));
// Get First Zip File Listed For Transfer From SFTP To CF Server
serverFile = channel.ls('*.zip')[1].getFilename();
/* Debug */
startTime = Now();
WriteOutput("Transfer Started: " & TimeFormat(startTime, 'hh:mm:ss') & "<br>");
/* // Debug */
/* Transfer File From SFTP Server To CF Server */
try {
// Create File On Server To Write Byte Stream To
transferFile = CreateObject("java", "java.io.File").init(expandPath(serverFile));
channel.get(serverFile, CreateObject("java", "java.io.FileOutputStream").init(transferFile));
// Close The File Output Stream
transferFile = '';
}
catch(any excpt) {
WriteOutput("Error transfering file """ & expandPath(serverFile) & """: " & excpt.Message & "<br>");
abort;
}
/* Debug */
finishTime = Now();
WriteOutput("Transfer Finished: " & TimeFormat(finishTime, 'hh:mm:ss') & "<br>");
expiredTime = (finishTime - startTime);
WriteOutput("Duration: " & TimeFormat(expiredTime, 'HH:MM:SS') & "<br>");
WriteOutput("File Size: " & NumberFormat(Evaluate(GetFileInfo(ExpandPath(serverFile)).size / 1024), '_,___._') & " KB<br>");
WriteOutput("Transfer Rate: " & NumberFormat(Evaluate(Evaluate(GetFileInfo(ExpandPath(serverFile)).size / 1024) / Evaluate(((TimeFormat(expiredTime, 'H') * 60 * 60) + (TimeFormat(expiredTime, 'M') * 60) + TimeFormat(expiredTime, 'S')))), '_,___._') & " KB/Sec <br>");
/* // Debug */
channel.disconnect();
session.disconnect();
</cfscript>
Results:
Transfer Started: 09:37:57
Transfer Finished: 09:42:01
Duration: 00:04:04
File Size: 331,770.8 KB
Transfer Rate: 1,359.7 KB/Sec
The transfer speed that was achieved is on par with what I was getting using the FileZilla FTP Client & manually downloading. I found this method to be a viable solution for the inadequacy of cfftp.
I have been looking and I dont have an answer about why it is slower. But, in theory, you should be able to use cfexecute to do this through the windows command line. You might could even create a batch file and do it in one call.