I wrote the following code a long time ago to determine if an FTP directory exists:
public bool DirectoryExists(string directory)
{
try
{
FtpWebRequest request = GetRequest(directory);
request.Method = WebRequestMethods.Ftp.ListDirectory;
using (FtpWebResponse response = request.GetResponse() as FtpWebResponse)
{
StreamReader sr = new StreamReader(response.GetResponseStream(), System.Text.Encoding.ASCII);
sr.ReadToEnd();
sr.Close();
response.Close();
}
return true;
}
catch { }
return false;
}
protected FtpWebRequest GetRequest(string filename = "")
{
FtpWebRequest request = WebRequest.Create(_host.GetUrl(filename)) as FtpWebRequest;
request.Credentials = new NetworkCredential(Username, Password);
request.Proxy = null;
request.KeepAlive = false;
return request;
}
This code has worked for several years, but today it doesn't. When testing a directory that does not exist, the code in DirectoryExists() no longer throws an exception, and the method incorrectly returns true.
If I assign the results of sr.ReadToEnd() to a string, it is an empty string.
In this case, the code _host.GetUrl(filename) returned "ftp://www.mydomain.com/Articles/winforms/accessing-the-windows-registry". This is the expected value. And still my DirectoryExists() method does not throw an exception when this path does not exist on the server. I even passed this non-existing directory to a method that uses WebRequestMethods.Ftp.ListDirectoryDetails to build a directory listing. This method simply returns an empty listing and also throws no exception.
I believe I first encountered this issue when I moved my code to a new computer with Visual Studio 2013. I'm using .NET 4.5 and got the same behavior when using .NET 4.5.1.
Questions:
Why doesn't this code, which has worked for years and uses the same technique used on most of the online examples I found, work? And what could possibly cause this code to suddenly stop working?
Is there a way to detect for the presence of a directory that works? I suppose the other approach is to scan the parent directory, although the logic would need to be different when the routine is supposed to verify the root directory.
I managed to reproduce your error on another site I have access to. After some playing around, here's my conclusion:-
When you make a FtpWebRequest with a FTP URL that does NOT end with a /, such as:
ftp://ftp.someftp.com/somefolder/invalidfolder
AND you specified WebRequestMethods.Ftp.ListDirectory as the method, then what it does behind the scene is to run the following command:
NLST "somefolder/invalidfolder"
Normally, NLST will list the contents of the specified folder, and throws an exception if the folder does not exist. But because you did not specify a / at the end of invalidfolder, NLST will think that invalidfolder may actually be a file (or a filename pattern). If it manages to find a folder named invalidfolder, then and only then will it treat it as a folder. Otherwise it will try to search a file named invalidfolder underneath the parent folder somefolder. If the file does not exist, then one of the following will occur, depending on which FTP server software (and its configurations) is running:
It throws an ERROR 550: File or Folder not found. (Example: spftp v1.0)
It returns an empty result. (Example: vsFTPd v2.0.5)
In your case, the FTP server returns the latter response, and your code falls over.
The solution? Just add some validation code to make sure the ftp folder you are trying to access always has a / at the end. Something like the following:-
if (!directory.EndsWith('/'))
directory += '/';
In effect ftp class has strange beahvior however you should achieve your goal in this way using a simple console application project or simply in your original project too.
VBNET VERSION
Sub Main()
Dim ftp As New FtpSystem
If ftp.DirectoryExist("p") Then
Console.WriteLine("false")
Else
Console.WriteLine("true")
End If
Console.ReadLine()
End Sub
Private Class FtpSystem
Public Function DirectoryExist(ByVal dir As String)
Dim uri As New Uri("ftp://domain.com" & "/" & dir)
Dim netCred As New NetworkCredential("user", "password")
Dim ftprq As FtpWebRequest = FtpWebRequest.Create(uri)
With ftprq
.Credentials = netCred
.KeepAlive = True
.Method = WebRequestMethods.Ftp.ListDirectory
.UsePassive = True
.UseBinary = False
End With
Dim ftprs As FtpWebResponse = Nothing
Dim Sr As StreamReader = Nothing
'if ftp try to matching folder ad if it will succeed then return true if not it will thrown an exception
'and it will return false. Is not a good way because it should be implement a very granular exception matching but
'for test is best simple approach.
Try
ftprs = DirectCast(ftprq.GetResponse, FtpWebResponse)
Sr = New StreamReader(ftprs.GetResponseStream)
Dim T As String = Sr.ReadToEnd
Console.Write(T)
Return True
Catch ex As Exception
Return False
End Try
End Function
End Class
C# VERSION(TRANSLATE ON THE FLY WITH ONLINE TOOL PLEASE CHECK CODE)
public void Main()
{
FtpSystem ftp = new FtpSystem();
if (ftp.DirectoryExist("p")) {
Console.WriteLine("false");
} else {
Console.WriteLine("true");
}
Console.ReadLine();
}
private class FtpSystem
{
public object DirectoryExist(string dir)
{
Uri uri = new Uri("ftp://domain.com" + "/" + dir);
NetworkCredential netCred = new NetworkCredential("user", "password");
FtpWebRequest ftprq = FtpWebRequest.Create(uri);
var _with1 = ftprq;
_with1.Credentials = netCred;
_with1.KeepAlive = true;
_with1.Method = WebRequestMethods.Ftp.ListDirectory;
_with1.UsePassive = true;
_with1.UseBinary = false;
FtpWebResponse ftprs = null;
StreamReader Sr = null;
//if ftp try to matching folder ad if it will succeed then return true if not it will thrown an exception
//and it will return false. Is not a good way because it should be implement a very granular exception matching but
//for test is best simple approach.
try {
ftprs = (FtpWebResponse)ftprq.GetResponse;
Sr = new StreamReader(ftprs.GetResponseStream);
string T = Sr.ReadToEnd;
Console.Write(T);
return true;
} catch (Exception ex) {
return false;
}
}
}
I hope that this help you.Bye
As mentionned above, perhaps the FTP server configuration changed. Have you tried to explicitly set the value of the UsePassive or UseBinary properties either ways ?
You can try sending ftp commands to the ftp server:
ProcessStartInfo psi = new ProcessStartInfo("cmd.exe");
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
psi.RedirectStandardError = true;
Process proc = Process.Start(psi);
// Open the batch file for reading
// Attach the output for reading
StreamReader sOut = proc.StandardOutput;
StreamReader sErr = proc.StandardError;
// Attach the in for writing
StreamWriter sIn = proc.StandardInput;
// Write each line of the batch file to standard input
while (sr.Peek() != -1)
{
sIn.WriteLine("ftp");
sIn.WriteLine("open remote-server-name");
sIn.WriteLine("username");
sIn.WriteLine("password");
sIn.WriteLine("ls");
}
string outPutStuff= sOut.ReadToEnd();
proc.WaitForExit(); //this does not appear to be needed.
string outErrStuff = sErr.ReadToEnd();
Console.WriteLine("Start FileTransfer FTP Output");
Console.WriteLine(outPutStuff);
Console.WriteLine("Any errors follow this line---");
Console.WriteLine(outErrStuff);
Console.WriteLine(outPutStuff);
sOut.Close();
sIn.Close();
Please,check if there is a file named accessing-the-windows-registry (no file extension) in winforms folder.
_host.GetUrl(filename) should return a string ended with a "/". When there is file with the same name as the intended folder name, no exception will be thrown.
If you are not getting exceptions, as a possible work-around, you could try requesting a predictable attribute of the folder, like the date it was created.
Another suggestion is using WireShark to see what FTP requests are being made behind the scenes, which will let you see if it's .NET or the server returning unhelpful responses.
Related
I'm trying to write exception log messages using StreamWriter in my MVC5 web application.
The main method --
public bool WriteApplicationException(string exp_message)
{
try
{
filename = string.Format("LogMessages\\{0}_{1}.log", "Exception", DateTime.Now.ToShortDateString());
string logFilePath = string.Format(Path.Combine(#"{0}\{1}", AppDomain.CurrentDomain.BaseDirectory, filename));
//logFilePath = logFilePath.Replace(#"\\", #"\");
StringBuilder sb = new StringBuilder();
sb.AppendLine("----------------------------------------------------");
sb.AppendLine(DateTime.Now.ToString());
sb.AppendLine(exp_message);
sb.AppendLine("----------------------------------------------------");
//StreamWriter swr = new StreamWriter(logFilePath, true)
using (StreamWriter swr= File.AppendText(logFilePath))
{
swr.Write(sb.ToString());
swr.Flush();
}
return true;
}
catch (Exception exp)
{
return false;
}
}
I am calling it on the override void OnException in my relevant controller class.
The path that's being generated is as such -
drivelabel:\LogProject.UI\LogProject.UI\LogProject.Web\LogMessages\Exception_3/2/2020.log
But the exception that is being thrown is as such -
{"Could not find a part of the path 'drivelabel:\\LogProject.UI\\LogProject.UI\\LogProject.Web\\LogMessages\\Exception_3\\2\\2020.log'."}
So, could there be any read/write permission issue that is preventing this? Or anything with firewall?
The 2nd one is most unlikely. What could it be? Like I said, the path is very much existent and within the project structure itself. Not outside. What issue here? Any idea?
Thanks.
The message is not telling you that you do not have the right permissions, it is telling you that this path or a part of it does not exist. You need to manually , (or programatically) create that path before you can write to ot.
I have a directory with about 2,000,000 files on an FTP Server. This FTP Server is Linux-based. I want to list the files in this directory (file name with the last modification date), but neither Filezilla nor Core FTP Pro can do it and the list operation would fail. I have tried to write my own application in C# using FTPWebRequest class and run the ListDirectory method, but it fails to list the files in the directory too and the ftpwebrequest timed out.
Is there any way to list the name of So many files in a directory using any protocol (such as FTP, SMB, ...) or even by executing a bash script?
The Function to list directory in c# is:
public static List<string> ListFtpDirectory(string address, string userName, string password)
{
List<string> filesName = new List<string>();
try
{
FtpWebRequest request = (FtpWebRequest) WebRequest.Create(address);
request.Method = WebRequestMethods.Ftp.ListDirectory;
request.Credentials = new NetworkCredential(userName, password);
request.UsePassive = true;
using (FtpWebResponse response = (FtpWebResponse) request.GetResponse())
using (StreamReader reader = new StreamReader(response.GetResponseStream()))
{
filesName =
reader.ReadToEnd().Split(new string[] {"\r\n"}, StringSplitOptions.RemoveEmptyEntries).ToList();
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
return filesName;
}
Any help is appreciated.
I am not fluent in C#, however you're not including the exact text of the error you're receiving.
The following is a Powershell script which should work in your environment:
$Server = "ftp://ftp.example.com/"
$User = "anonymous#example.com"
$Pass = "anonymous#anonymous.com"
Function Get-FtpDirectory($Directory) {
# Credentials
$FTPRequest = [System.Net.FtpWebRequest]::Create("$($Server)$($Directory)")
$FTPRequest.Credentials = New-Object System.Net.NetworkCredential($User,$Pass)
$FTPRequest.Method = [System.Net.WebRequestMethods+FTP]::ListDirectoryDetails
# Don't want Binary, Keep Alive unecessary.
$FTPRequest.UseBinary = $False
$FTPRequest.KeepAlive = $False
$FTPResponse = $FTPRequest.GetResponse()
$ResponseStream = $FTPResponse.GetResponseStream()
# Create a nice Array of the detailed directory listing
$StreamReader = New-Object System.IO.Streamreader $ResponseStream
$DirListing = (($StreamReader.ReadToEnd()) -split [Environment]::NewLine)
$StreamReader.Close()
# Remove first two elements ( . and .. ) and last element (\n)
# and take into account empty directories
If ($DirListing.Length -gt 3) {
$DirListing = $DirListing[2..($DirListing.Length-2)]
}
else {
$DirListing = #{} $DirListing = $DirListing[2..($DirListing.Length-2)]
}
# Close the FTP connection so only one is open at a time
$FTPResponse.Close()
# This array will hold the final result
$FileTree = #()
# Loop through the listings
foreach ($CurLine in $DirListing) {
# Split line into space separated array
$LineTok = ($CurLine -split '\ +')
# Get the filename (can even contain spaces)
$CurFile = $LineTok[8..($LineTok.Length-1)]
# Figure out if it's a directory. Super hax.
$DirBool = $LineTok[0].StartsWith("d")
# Determine what to do next (file or dir?)
If ($DirBool) {
# Recursively traverse sub-directories
$FileTree += ,(Get-FtpDirectory "$($Directory)$($CurFile)/")
} Else {
# Add the output to the file tree
$FileTree += ,"$($Directory)$($CurFile)"
}
}
Return $FileTree
}
My company has purchased the CoolUtils TotalPDFPrinterX from https://www.coolutils.com/TotalPDFPrinterX
I make an HTTP PUT from Postman to the API and I get “Could not get any response”.
When running on my Windows machine the PDF prints fine however on the server the site crashes and in the event log I get the error "A process serving application pool '[MY_APP_POOL]' failed to respond to a ping. The process id was '[MY_PROCESS_ID]'."
Here is my C# code:
PDFPrinterX ppx = new PDFPrinterX();
ppx.Print(fileName, printerName, "-ap Default");
if (ppx.ErrorMessage != null)
{
WriteToSQL(id, false, ppx.ErrorMessage, 2);
Console.WriteLine(ppx.ErrorMessage);
}
By writing to the event log I know the site crashes on this line: PDFPrinterX ppx = new PDFPrinterX(); I have also surrounded the above code with a try catch and no exception is thrown. The site still crashes.
Things I have tried:
Uninstalling and Reinstalling the CoolUtils software
Giving EVERYONE Full control to the site folder and the CoolUtils program folder
Creating a C# desktop application using the same code. THIS WORKS FINE ON THE SERVER. It's just the ASP site that crashes.
Does anyone know what might be causing this?
The more I research this thing online the more I'm inclined to say that ActiveX which is the X in PDFPrinterX doesn't seem to work well when hosted in IIS.
I've seen a few forums where they say it works fine when they debug on localhost but when deployed to server is crashes.
...works fine when used inside localhost(Visual studio)
One of their feature pages shows that it requires Win 2000/NT/XP/2003/Vista/7
You should look into whether your server supports ActiveX components that can work in conjunction with IIS.
Looking at one of their other products support page: TotalPDFConverterX:
the following note in my opinion may also apply to TotalPDFPrinterX, given its dependency on ActiveX as well.
Note: Pay attention to some details during installation Total PDF Converter X:
Do not forget to register ActiveX in your web-server account.
Total PDF Converter X supports only Internet Explorer, Mozilla and Firefox browsers.
ActiveX works only with 32-bit internet information server. 64-bit server is not supported. Use command line version instead.
Thanks to #Nkosi I was able to find a workaround.
ActiveX works only with 32-bit internet information server. 64-bit server is not supported. Use command line version instead.
Our IIS server is 64 bit so that is what probably caused the site to hang up.
Buttt... the command line still worked in printing the PDFs on the server.
Client side code (makes the HTTP POST):
private void SendToPrinter(string fileName, string printerName, int id, decimal documentSequence)
{
// use http client to make a POST to the print api
using (var client = new HttpClient())
{
// compile the values string to transfer in POST
// should finish to look something like this:
// C:\print.pdf&PRTFTW_OFIT&ValShip-155320-1
var values = new Dictionary<string, string>
{
{ "", fileName + "&" + printerName + "&ValShip-" + id + "-" + documentSequence},
};
// URL encode the values string
var content = new FormUrlEncodedContent(values);
// make the POST
// DEBUG
var response = client.PostAsync("http://localhost:54339/api/print", content);
// retrieve the response
var responseString = response.Result.ToString();
}
}
Server side code (receives the HTTP POST):
using System;
using System.Net.Http;
using System.Web;
using System.Web.Http;
namespace api.valbruna.print.Controllers
{
public class PrintController : ApiController
{
// POST api/print
public HttpResponseMessage Post(HttpRequestMessage request)
{
try
{
// parse the content recieved from the client
var content = request.Content.ReadAsStringAsync().Result;
// decode the content, certain characters such as
// '&' get encoded to URL lingo such as '%26'
content = HttpUtility.UrlDecode(content);
// split the string into 3 seperate parts
String[] str = content.Split('&');
// remove the equal sign from the first string
str[0] = str[0].Trim('=');
// compile the arguments command line string
// should finish to look something like this:
// "C:\Program Files (x86)\CoolUtils\Total PDF PrinterX\PDFPrinterX.exe" "C:\print.pdf" -p"\\PRINTERS\PRTFTW_OFIT" -ap Default -log "C:\inetpub\logs\CoolUtils\log-ValShip-155320-4.txt" -verbosity detail"
String arguments = "\"" + str[0] + "\" -p\"\\\\PRINTERS\\" + str[1] +
"\" -ap Default -log \"C:\\inetpub\\logs\\CoolUtils\\log-" + str[2] +
".txt\" -verbosity detail";
// file location for PDFPrinterX.exe
String file = #"C:\Program Files (x86)\CoolUtils\Total PDF PrinterX\PDFPrinterX.exe";
// start the process
System.Diagnostics.Process process = new System.Diagnostics.Process();
System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal;
startInfo.FileName = file;
startInfo.Arguments = arguments;
process.StartInfo = startInfo;
process.Start();
return new HttpResponseMessage() { Content = new StringContent(content) };
}
catch (Exception e)
{
return new HttpResponseMessage() { Content = new StringContent(e.Message) };
}
}
}
}
I got a small problem on how to create a directory with subdirectories in a single ftp request
I have a string s
string s = "a/b/c/d"
NOTE : The words between slashes are random and the number of items is unknown.
How to create in the FTP server the directory a/b/c/d ????
The way i'm using to achieve this is to split the string and create a folder for each part using the code below :
var ftpWebRequest = (FtpWebRequest)WebRequest.Create("ftp://domain.com/public_html/a");
ftpWebRequest.Credentials = new NetworkCredential(ftpUsername, ftpPassword);
ftpWebRequest.Method = WebRequestMethods.Ftp.MakeDirectory;
ftpWebRequest.GetResponse();
Then i create the b directory inside a, then c inside b, then d inside c by repeating some code, each time
I tried to type the url directly. like :
var ftpWebRequest = (FtpWebRequest)WebRequest.Create("ftp://doemin.com/public_html/a/b/c/d);
but it doesn't work.
Is there a short way on how can i create a folder with other subdirectories in a one single request ?
If you are willing to use a more friendly library (free and open source) like this one:
System.Net.FtpClient.dll
then you could write code like this (adapted from their example)
static ManualResetEvent m_reset = new ManualResetEvent(false);
void Main()
{
m_reset.Reset();
using (FtpClient ftp = new FtpClient())
{
ftp.Host = "yourFTPHost.com";
ftp.Credentials = new NetworkCredential("yourUserName", "yourPassword");
ftp.SetWorkingDirectory("/rootForTest");
if(ftp.DirectoryExists("test"))
ftp.DeleteDirectory("test", true);
ftp.BeginCreateDirectory("test/path/that/should/be/created", true,
new AsyncCallback(CreateDirectoryCallback), ftp);
m_reset.WaitOne();
ftp.Disconnect();
}
}
static void CreateDirectoryCallback(IAsyncResult ar)
{
FtpClient ftp = ar.AsyncState as FtpClient;
try
{
if (ftp == null)
throw new InvalidOperationException("The FtpControlConnection object is null!");
ftp.EndCreateDirectory(ar);
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
finally
{
m_reset.Set();
}
}
A side note: System.Net.FtpClient requires the full NET 4.0 Framework. (Cliente Profile is not enough)
Some servers do support FTP commands like MKD a/b/c/d . If your server doesn't, but supports execution of shell commands via SITE command, you can try to call "SITE md a/b/c/d" (but this is machine-specific). If none of the above works, then you have to create folders in a loop like you do or use some library which hides this loop in a one method.
I have a method in my windows service as follows:
System.IO.File.Copy(path, ConfigurationManager.AppSettings["BulkInsertGiftRegisterCreatorDirectory"] + System.IO.Path.GetFileName(path));
Loyalty.Entity.Base.FileInfo file = new Loyalty.Entity.Base.FileInfo();
file.FileName = path;
request.Object = file;
ResponseBase response = new ResponseBase(request);
RequestConnection connection = new RequestConnection("cn");
FileManager fileManager = new FileManager(request, connection);
response = fileManager.OfflineGiftRegisterBulkInsert();
System.IO.File.Delete(ConfigurationManager.AppSettings["BulkInsertGiftRegisterCreatorDirectory"] + System.IO.Path.GetFileName(path));
// here is the part of stored procedure that uses file
SELECT #SCRIPT= 'BULK INSERT GIFT_CARD.GIFT_TEMP'
+' FROM '''
+ #FILE_PATH
+''' WITH ('
+'FIELDTERMINATOR = '','','
+ 'KEEPNULLS'
+');'
I can delete the file from file system by hand, but this code says me "Ooops! System.IO.IOException: The process cannot access the file 'filename'
because it is being used by another process."
I've searched the similar questions on stackoverflow and else where. But I could not find anything to help me. Copy or Delete methods return void and I have no stream in my code to dispose.
How can I fix it?
Thanks in advance.
Here is a method for you to check if a file is in use:
public static System.Boolean FileInUse(System.String file)
{
try
{
if (!System.IO.File.Exists(file)) // The path might also be invalid.
{
return false;
}
using (System.IO.FileStream stream = new System.IO.FileStream(file, System.IO.FileMode.Open))
{
return false;
}
}
catch
{
return true;
}
}
Also, to wait for a file I have made:
public static void WaitForFile(System.String file)
{
// While the file is in use...
while (FileInUse(file)) ; // Do nothing.
}
I hope this helps!
Before you go looking through the code you might want to use process explorer to Identify what process has the handle. This might rule out some issue you haven't thought of
update
Since you are using a timer you must make sure that your method is reentrant and you don't have any race conditions.. E.g. the timer ticks faster than you can process the event.
See this question
And this answer
Even better solution, is to add this two lines of code before using the function FileInUse that Vercas showed:
GC.Collect();
GC.WaitForPendingFinalizers();