I wrote this .NET Windows Service application that is basically a file watcher. The service will monitor incoming .csv files, parse data from them, and add the data to spreadsheets. I installed the service on a server and tried to start it. I was given the warning, "The service started and then stopped. Some services stop automatically if they are not in use by other services or programs." In a debug attempt, I removed all the code and just had a bare service and it started/stopped fine. So I added the file watcher object and it popped the warning again. Next I changed the service to run with a local administrative account instead of the "LocalService" account then it worked. I added the rest of my code and it worked for awhile. I finished development and added the EventLog object and I was right back to the warning. I removed the EventLog object but still got the warning. I just do not know what is causing this to not start. Here is my service:
using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using OfficeOpenXml;
using System.Linq;
using System.ServiceProcess;
using System.Text;
namespace COD_Automation
{
public partial class COD_AUTO : ServiceBase
{
FileSystemWatcher eWatcher;
String remoteSrc;
public static String filePath;
public static String fileName;
public static Boolean fileCheck;
public static String modifiedDT;
public static String remodifiedDT;
public static String SampNum;
public static String SampDate;
public static String AnalysisInitials;
public static int SampResult;
public static double Dilution;
public static FileInfo efile;
public int rowIndex = 8;
public int filterID = 1;
public COD_AUTO()
{
InitializeComponent();
if (!System.Diagnostics.EventLog.SourceExists("COD_Automation"))
{
System.Diagnostics.EventLog.CreateEventSource(
"COD_Automation", "COD Automation Log");
}
serviceLog.Source = "COD_Automation";
serviceLog.Log = "COD Automation Log";
}
protected override void OnStart(string[] args)
{
serviceLog.WriteEntry("COD Automation Service has started.");
//Define the remote folder location to watch
remoteSrc = "\\\\mkfiler01\\ops\\Envcom\\EXEC\\ENVCOM\\LAB\\COD\\Exports\\DataLog";
//Create a new FileSystemWatcher and set its properties
eWatcher = new FileSystemWatcher(remoteSrc, "*.csv");
//Add event handler
eWatcher.Created += new FileSystemEventHandler(eWatcher_Created);
//Begin watching
eWatcher.EnableRaisingEvents = true;
}
protected override void OnStop()
{
serviceLog.WriteEntry("COD Automation Service has stopped.");
eWatcher.EnableRaisingEvents = false;
}
private void eWatcher_Created(object source, FileSystemEventArgs e)
{
filePath = e.FullPath;
fileName = e.Name;
ParseData(filePath);
FileCheck(fileName);
CreateExcelFile(fileCheck);
AddSample(SampNum, SampDate, AnalysisInitials, SampResult, Dilution);
}
public void ParseData(String filePath)
{
//Create a dictionary collections with int keys (rowNums) and String values (each line of Strings)
Dictionary<int, String> eachCSVLine = new Dictionary<int, string>();
//String array that holds the contents of the specified row
String[] lineContent;
int rowNum = 1;
foreach (string line in File.ReadLines(filePath))
{
eachCSVLine.Add(rowNum, line);
rowNum++;
}
//Get the required line and split it by "," into an array
String reqLine = eachCSVLine[5];
lineContent = reqLine.Split(',');
//Get the required values(index 2 for parsed Operator ID, index 4 for parsed Sample Number, index 11 for Sample Result)
AnalysisInitials = lineContent.GetValue(2).ToString();
SampNum = lineContent.GetValue(3).ToString(); //sample number
String result = lineContent.GetValue(11).ToString();
String dilute = lineContent.GetValue(8).ToString();
Dilution = Double.Parse(dilute);
SampResult = Int32.Parse(result); //sample result
}
public void AddSample(String SampleNum, String SampleDate, String AnalysisInitials, int SampleResult, double Diluted)
{
try
{
using (ExcelPackage excelPackage = new ExcelPackage(efile))
{
ExcelWorksheet worksheet = excelPackage.Workbook.Worksheets[1];
var cell = worksheet.Cells;
//check to see if this is the first sample added --if true, add the first sample --if false, increment rowindex & filterID then add the sample to next available row
if (cell["A8"].Value == null)
{
cell["B5"].Value = SampleDate;
cell["B6"].Value = AnalysisInitials;
cell[rowIndex, 1].Value = filterID; //Filter ID
cell[rowIndex, 2].Value = SampleNum; //Sample Number
cell[rowIndex, 3].Value = Dilution; //Dilution
cell[rowIndex, 4].Value = SampleResult; //Meter Reading
}
else
{
while (!(cell["A8"].Value == null))
{
rowIndex++;
filterID++;
if (cell[rowIndex, 1].Value == null) //ensures that the new row is blank so the loop can break to continue adding the sample
{ break; }
}
//add the sample to the next empty row
cell[rowIndex, 1].Value = filterID; //Filter ID
cell[rowIndex, 2].Value = SampleNum; //Sample Number
cell[rowIndex, 3].Value = Dilution; //Dilution
cell[rowIndex, 4].Value = SampleResult; //Meter Reading
}
excelPackage.Save();
}
}
catch (Exception e)
{
serviceLog.WriteEntry("Sample could not be added to the spreadsheet because of the following error: " + e.Message + ".");
}
}
public Boolean FileCheck(String fileName)
{
//Get the date of the .csv file
String[] fNames = fileName.Split('_');
String fDate = fNames.ElementAt(3);
DateTime dt = Convert.ToDateTime(fDate);
//format the file date into the proper format and convert to a string
modifiedDT = dt.ToString("MMddyy");
//modify the "modifiedDT to get the sample date to insert into spreadsheet
String mdate = modifiedDT.Insert(2, "/");
remodifiedDT = mdate.Insert(5, "/");
SampDate = remodifiedDT; //sample date
//assign an excel filename
String exFile = #"\\mkfiler01\ops\Envcom\EXEC\ENVCOM\LAB\COD\Imports\" + modifiedDT + "COD-" + AnalysisInitials + ".xlsx";
//check for file existence
if (File.Exists(exFile))
{ fileCheck = true; }
else
{ fileCheck = false; }
return fileCheck;
}
public void CreateExcelFile(Boolean fileCheck)
{
if (fileCheck)
{
efile = new FileInfo(#"\\mkfiler01\ops\Envcom\EXEC\ENVCOM\LAB\COD\Imports\" + modifiedDT + "COD-" + AnalysisInitials + ".xlsx");
using (ExcelPackage excelPackage = new ExcelPackage(efile))
{
//Read the existing file to see if the Analysis Initials match the AnalysisInitial variable value
ExcelWorksheet worksheet = excelPackage.Workbook.Worksheets[1];
String initials = worksheet.Cells["B6"].Value.ToString();
//If initials = AnalysisIntials then assign the existing file the WB variable, else create a new file for the different AnalysisInitials
if (initials.Equals(AnalysisInitials))
{
excelPackage.Save();
}
else
{
try
{
//Excel COD Template to use to create new Excel spreadsheet
FileInfo template = new FileInfo(#"\\mkfiler01\ops\Envcom\EXEC\ENVCOM\LAB\COD\COD TEMPLATE.xlsx");
//The new Excel spreadsheet filename
FileInfo newFile = new FileInfo(#"\\mkfiler01\ops\Envcom\EXEC\ENVCOM\LAB\COD\Imports\" + modifiedDT + "COD-" + AnalysisInitials + ".xlsx");
// Using the template to create the newfile
using (ExcelPackage excelPackage1 = new ExcelPackage(newFile, template))
{
// save the new Excel spreadsheet
excelPackage1.Save();
}
}
catch (Exception ex)
{
serviceLog.WriteEntry("Excel file could not be created because " + ex.Message);
}
}
}
}
else
{
try
{
efile = new FileInfo(#"\\mkfiler01\ops\Envcom\EXEC\ENVCOM\LAB\COD\Imports\" + modifiedDT + "COD-" + AnalysisInitials + ".xlsx");
//Excel COD Template to use to create new Excel spreadsheet
FileInfo template = new FileInfo(#"\\mkfiler01\ops\Envcom\EXEC\ENVCOM\LAB\COD\COD TEMPLATE.xlsx");
//The new Excel spreadsheet filename
FileInfo newFile = new FileInfo(#"\\mkfiler01\ops\Envcom\EXEC\ENVCOM\LAB\COD\Imports\" + modifiedDT + "COD-" + AnalysisInitials + ".xlsx");
// Using the template to create the newfile
using (ExcelPackage excelPackage = new ExcelPackage(newFile, template))
{
// save the new Excel spreadsheet
excelPackage.Save();
}
}
catch (Exception ex)
{
serviceLog.WriteEntry("Excel file could not be created because " + ex.Message);
}
}
}
}
}
My EventViewer was showing details of an ArgumentException in regards to the Source and Log properties of the EventLog object. I changed the following code:
if (!System.Diagnostics.EventLog.SourceExists("COD_Automation"))
{
System.Diagnostics.EventLog.CreateEventSource(
"COD_Automation", "COD Automation Log");
}
serviceLog.Source = "COD_Automation";
serviceLog.Log = "COD Automation Log";
into the following code:
if (!EventLog.SourceExists("COD_Automation"))
{
EventLog.CreateEventSource("COD_Automation", "Application");
}
serviceLog.Source = "COD_Automation";
serviceLog.Log = "Application";
This fixed my problem. I was initially trying to register the COD_Automation source in the COD_Automation Log which doesn't exist. So I set the Log property to the correct log of "Application".
The LocalService account will not have access to a UNC share like \\mkfiler01. My guess is that you are getting an access denied error when trying to access that file share.
Set the service to run under a domain account that has access to that share.
Related
I'm trying to make a program that automatically copies and sorts the pictures from an SD card to an external hard drive using Metadata Extractor 2.4.3
I don't seem to find any prolem but every time I run the code, an unhandled exception prompts up.
Here's the error:
Unhandled Exception: MetadataExtractor.ImageProcessingException: File format could not be determined
at MetadataExtractor.ImageMetadataReader.ReadMetadata(Stream stream)
at MetadataExtractor.ImageMetadataReader.ReadMetadata(String filePath)
at file_sorter.File..ctor(String filepath) in
C:\Users\ropra\Documents\file_sorter\file_sorter\File.cs:line 27
at file_sorter.Program.Main(String[] args) in
C:\Users\ropra\Documents\file_sorter\file_sorter\Program.cs:line 27
Here's the code:
Program.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MetadataExtractor;
namespace file_sorter
{
class Program
{
static void Main(string[] args)
{
// Define paths
string sandisk = #"Z:/Images/RAW";
string sd = #"Y:/DCIM/100_FUJI";
// Count elements in sd
string[] photoPaths = System.IO.Directory.GetFiles(sd);
Console.WriteLine("Counting elements in SD card...");
// Create object array
File[] photos = new File[photoPaths.Count()];
for (int i = 0; i < photos.Count(); i++)
{
photos[i] = new File(photoPaths[i]);
}
// Create tree and copy files
foreach (var item in photos)
{
string fileName = item.filename;
string sourcePath = item.sourcepath;
string targetPath = sandisk + "/" + item.year + "/" + item.month + "/" + item.day;
string sourceFile = System.IO.Path.Combine(sourcePath, fileName);
string destFile = System.IO.Path.Combine(targetPath, fileName);
Console.WriteLine("Now copying: {0} into {1}", fileName, targetPath);
System.IO.Directory.CreateDirectory(targetPath);
System.IO.File.Copy(sourceFile, destFile, true);
}
}
}
}
File.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MetadataExtractor;
namespace file_sorter
{
public class File
{
public string filename;
public string path;
public string year;
public string month;
public string day;
public string sourcepath;
public File(string filepath)
{
path = filepath;
filename = path.Substring(path.LastIndexOf("\\") + 1);
sourcepath = path.Substring(0, path.LastIndexOf("\\"));
string rawDate = "";
var metadata = ImageMetadataReader.ReadMetadata(path);
for (int i = 0; i < metadata.Count(); i++)
{
for (int j = 0; j < metadata[i].TagCount; j++)
{
if (metadata[i].Name == "Exif IFD0" && metadata[i].Tags[j].Name == "Date/Time")
{
rawDate = metadata[i].Tags[j].Description;
}
}
}
int separator = rawDate.IndexOf(":");
year = rawDate.Substring(0, separator);
string sub = rawDate.Substring(separator + 1);
separator = sub.IndexOf(":");
month = sub.Substring(0, separator);
sub = sub.Substring(separator + 1);
separator = sub.IndexOf(" ");
day = sub.Substring(0, separator);
}
public void ShowFormatedDate()
{
Console.WriteLine("Path: {0}", path);
Console.WriteLine("File: {0}", filename);
Console.WriteLine("Dir: {0}", sourcepath);
Console.WriteLine("Year: {0}", year);
Console.WriteLine("Month: {0}", month);
Console.WriteLine("Day: {0}", day);
Console.WriteLine("");
}
}
}
Thank you in advance.
OK, so having given up and sorting all files manually, I came across an .xml file containing the metadata of a single .RAF file, I'm guessing my camera put it there.
This program does not handle MetadataExtractor non-supported files, hence the problem.
ImageProcessingException: File format could not be determined
This exception means that the library didn't identify the file it was given as anything it knows how to read. For example, if you give a text file to the library, you will see this exception.
Perhaps you could catch this exception type and use a different code path for such files.
I've got a page where by intervals of around 10 minutes a csv file is uploaded to a folder (received by an http link). This csv file has to be uploaded to sql. I've managed to get the csv files and save them in the folder, but the problem that I've got is that when I try and read the data it shows that the file is empty (but it is not)... It doesn't throw any errors, but when I run it with a debug, it shows an "Object reference not set to an instance of an object".
This is my code...
Method of what has to happen for this entire process:
private void RunCSVGetToSqlOrder()
{
DAL d = new DAL();
GetGeoLocations();
string geoLocPath = Server.MapPath("~\\GeoLocations\\GeoLocation.csv");
string assetListPath = Server.MapPath("~\\GeoLocations\\AssetList.csv");
d.InsertAssetGeoLocation(ImportGeoLocations(geoLocPath));
d.InsertAssetList(ImportAssetList(assetListPath));
DeleteFileFromFolder();
}
Getting the csv and saving into a folder (working):
private void GetGeoLocations()
{
string linkGeoLoc = "http://app03.gpsts.co.za/io/api/asset_group/APIAssetGroupLocation.class?zqcEK60SxfoP4fVppcLoCXFWUfVRVkKS#auth_token#auth_token";
string filepathGeoLoc = Server.MapPath("~\\GeoLocations\\GeoLocation.csv");
using (WebClient wc = new WebClient())
{
wc.DownloadFileAsync(new System.Uri(linkGeoLoc), filepathGeoLoc);
}
}
Read csv file and import to sql:
private static DataTable ImportGeoLocations(string csvFilePath)
{
DataTable csvData = new DataTable();
try
{
using (TextFieldParser csvReader = new TextFieldParser(csvFilePath))
{
// csvReader.TextFieldType = FieldType.Delimited;
csvReader.SetDelimiters(new string[] { "," });
csvReader.HasFieldsEnclosedInQuotes = true;
csvReader.TrimWhiteSpace = true;
string[] colFields = csvReader.ReadFields();
foreach (string column in colFields)
{
DataColumn datecolumn = new DataColumn(column);
datecolumn.AllowDBNull = true;
csvData.Columns.Add(datecolumn);
}
while (!csvReader.EndOfData)
{
string[] fieldData = csvReader.ReadFields();
//Making empty value as null
for (int i = 0; i < fieldData.Length; i++)
{
if (fieldData[i] == "")
{
fieldData[i] = null;
}
}
csvData.Rows.Add(fieldData);
}
}
}
catch (Exception ex)
{
}
return csvData;
}
The above code gives the error of "Object reference not set to an instance of an object" on the line, but this is most probably due because it's reading the csv as empty(null)...
string[] colFields = csvReader.ReadFields();
I'm not sure what I'm doing wrong... Any advice would be greatly appreciated...
------------ EDIT --------------
The csv file after the download looks as follows:
-------- Solution ------------
Below is the solution:
private void RunCSVGetToSqlOrder()
{
GetGeoLocations();
DeleteFileFromFolder();
}
private void GetGeoLocations()
{
string linkGeoLoc = "http://app03.gpsts.co.za/io/api/asset_group/APIAssetGroupLocation.class?zqcEK60SxfoP4fVppcLoCXFWUfVRVkKS#auth_token#auth_token";
string filepathGeoLoc = Server.MapPath("~\\GeoLocations\\GeoLocation.csv");
using (WebClient wc = new WebClient())
{
wc.DownloadFileAsync(new System.Uri(linkGeoLoc), filepathGeoLoc);
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(wc_DownloadFileCompletedGeoLoc);
}
string linkAssetList = "http://app03.gpsts.co.za/io/api/asset_group/APIAssetGroupLocation.class?zqcEK60SxfoP4fVppcLoCXFWUfVRVkKS#auth_token#auth_token";
string filepathAssetList = Server.MapPath("~\\GeoLocations\\AssetList.csv");
using (WebClient wc = new WebClient())
{
wc.DownloadFileAsync(new System.Uri(linkAssetList), filepathAssetList);
wc.DownloadFileCompleted += new AsyncCompletedEventHandler(wc_DownloadFileCompletedAssetList);
}
}
void wc_DownloadFileCompletedGeoLoc(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
DAL d = new DAL();
string geoLocPath = Server.MapPath("~\\GeoLocations\\GeoLocation.csv");
d.InsertAssetGeoLocation(ImportGeoLocations(geoLocPath));
}
void wc_DownloadFileCompletedAssetList(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
DAL d = new DAL();
string assetListPath = Server.MapPath("~\\GeoLocations\\AssetList.csv");
d.InsertAssetList(ImportAssetList(assetListPath));
}
The call to wc.DownloadFileAsync returns a Task object and the downloaded file is only complete after the Task is completed.
This issue is hard to catch with the debugger, because the task will have enough time to complete when a breakpoint is reached or the file is manually inspected later. However, when the code runs without break point, the call d.InsertAssetGeoLocation(ImportGeoLocations(geoLocPath)) will be reached before the download is complete and therefore the csv file will be empty.
Possible solutions:
Redesign the code with async / await
Use wd.DownloadFileCompleted event for continuation
Use the synchronous wd.DownloadFile method
... just to name a few.
From the screenshot, it looks like the CSV file is not read properly on your computer. Depending on the language and region settings, the .CSV sometimes has ";" as seperators instead of ",".
Could you try manually replacing all "," with ";" and see if it solves the issue?
I am using CSV Helper library to produce CSV files for the user to
to populate and upload into the system. My issue is that the WriteHeader method just writes the attributes of a class with names like "PropertyValue", which is not user friendly. Is there a method I can use to make the text produced user friendly but is still able to successfully map the class to the files data?
My code looks like the following:
public ActionResult UploadPropertyCSV(HttpPostedFileBase file)
{
List<PropertyModel> properties = new List<PropertyModel>();
RIMEDb dbContext = new RIMEDb();
bool success = false;
foreach (string requestFiles in Request.Files)
{
if (file != null && file.ContentLength > 0 && file.FileName.EndsWith(".csv"))
{
using(StreamReader str = new StreamReader(file.InputStream))
{
using(CsvHelper.CsvReader theReader = new CsvHelper.CsvReader(str))
{
while (theReader.Read())
{
RIMUtil.PropertyUploadCSVRowHelper row = new RIMUtil.PropertyUploadCSVRowHelper()
{
UnitNumber = theReader.GetField(0),
StreetNumber = theReader.GetField(1),
StreetName = theReader.GetField(2),
AlternateAddress = theReader.GetField(3),
City = theReader.GetField(4)
};
Property property = new Property();
property.UnitNumber = row.UnitNumber;
property.StreetNumber = row.StreetNumber;
property.StreetName = row.StreetName;
property.AlternateAddress = row.AlternateAddress;
property.City = dbContext.PostalCodes.Where(p => p.PostalCode1 == row.PostalCode).FirstOrDefault().City;
dbContext.Properties.Add(property);
try
{
dbContext.SaveChanges();
success = true;
}
catch(System.Data.Entity.Validation.DbEntityValidationException ex)
{
success = false;
RIMUtil.LogError("Ptoblem validating fields in database. Please check your CSV file for errors.");
}
catch(Exception e)
{
RIMUtil.LogError("Error saving property to database. Please check your CSV file for errors.");
}
}
}
}
}
}
return Json(success);
}
I'm wondering if theres some metadata tag or something I can put on top of each attribute in my PropertyUploadCSVRowHelper class to put the text I want produced in the file
Thanks in advance
Not sure if this existed 2 years ago but now, we can change the property/column name by using the following attribute function:
[CsvHelper.Configuration.Attributes.Name("Column/Field Name")]
Full code:
using CsvHelper;
using System.Collections.Generic;
using System.IO;
namespace Test
{
class Program
{
class CsvColumns
{
private string column_01;
[CsvHelper.Configuration.Attributes.Name("Column 01")] // changes header/column name Column_01 to Column 01
public string Column_01 { get => column_01; set => column_01 = value; }
}
static void Main(string[] args)
{
List<CsvColumns> csvOutput = new List<CsvColumns>();
CsvColumns rows = new CsvColumns();
rows.Column_01 = "data1";
csvOutput.Add(rows);
string filename = "test.csv";
using (StreamWriter writer = File.CreateText(filename))
{
CsvWriter csv = new CsvWriter(writer);
csv.WriteRecords(csvOutput);
}
}
}
}
This might not be answering your question directly as you said you wanted to use csvhelper, but if you're only writing small size files (this is a simple function that I use to generate csv. Note, csvhelper will be much better for larger files as this is just building a string and not streaming the data.
Just customise the columns array in the code below variable to suit your needs.
public string GetCsv(string[] columns, List<object[]> data)
{
StringBuilder CsvData = new StringBuilder();
//add column headers
string[] s = new string[columns.Length];
for (Int32 j = 0; j < columns.Length; j++)
{
s[j] = columns[j];
if (s[j].Contains("\"")) //replace " with ""
s[j].Replace("\"", "\"\"");
if (s[j].Contains("\"") || s[j].Contains(" ")) //add "'s around any string with space or "
s[j] = "\"" + s[j] + "\"";
}
CsvData.AppendLine(string.Join(",", s));
//add rows
foreach (var row in data)
{
for (int j = 0; j < columns.Length; j++)
{
s[j] = row[j] == null ? "" : row[j].ToString();
if (s[j].Contains("\"")) //replace " with ""
s[j].Replace("\"", "\"\"");
if (s[j].Contains("\"") || s[j].Contains(" ")) //add "'s around any string with space or "
s[j] = "\"" + s[j] + "\"";
}
CsvData.AppendLine(string.Join(",", s));
}
return CsvData.ToString();
}
Here is a fiddle example of how to use it: https://dotnetfiddle.net/2WHf6o
Good luck.
I need to copy a particular file from one library to another library.
At first, need to check if file is existing in that library.
If Existing, then need to overwrite file content and new sharepoint version should be updated for that document.
I need to do this using c# CSOM and sharepoint version is 2013.
Thanks in advance :)
public static void CopyDocuments(string srcUrl, string destUrl, string srcLibrary, string destLibrary, Login _login)
{
// set up the src client
SP.ClientContext srcContext = new SP.ClientContext(srcUrl);
srcContext.AuthenticationMode = SP.ClientAuthenticationMode.FormsAuthentication;
srcContext.FormsAuthenticationLoginInfo = new SP.FormsAuthenticationLoginInfo(_login.UserName, _login.Password);
// set up the destination context (in your case there is no needs to create a new context, because it would be the same library!!!!)
SP.ClientContext destContext = new SP.ClientContext(destUrl);
destContext.AuthenticationMode = SP.ClientAuthenticationMode.FormsAuthentication;
destContext.FormsAuthenticationLoginInfo = new SP.FormsAuthenticationLoginInfo(_login.UserName, _login.Password);
// get the list and items
SP.Web srcWeb = srcContext.Web;
SP.List srcList = srcWeb.Lists.GetByTitle(srcLibrary);
SP.ListItemCollection col = srcList.GetItems(new SP.CamlQuery());
srcContext.Load(col);
srcContext.ExecuteQuery();
// get the new list
SP.Web destWeb = destContext.Web;
destContext.Load(destWeb);
destContext.ExecuteQuery();
foreach (var doc in col)
{
try
{
if (doc.FileSystemObjectType == SP.FileSystemObjectType.File)
{
// get the file
SP.File f = doc.File;
srcContext.Load(f);
srcContext.ExecuteQuery();
// build new location url
string nLocation = destWeb.ServerRelativeUrl.TrimEnd('/') + "/" + destLibrary.Replace(" ", "") + "/" + f.Name;
// read the file, copy the content to new file at new location
SP.FileInformation fileInfo = SP.File.OpenBinaryDirect(srcContext, f.ServerRelativeUrl);
SP.File.SaveBinaryDirect(destContext, nLocation, fileInfo.Stream, true);
}
if (doc.FileSystemObjectType == SP.FileSystemObjectType.Folder)
{
// load the folder
srcContext.Load(doc);
srcContext.ExecuteQuery();
// get the folder data, get the file collection in the folder
SP.Folder folder = srcWeb.GetFolderByServerRelativeUrl(doc.FieldValues["FileRef"].ToString());
SP.FileCollection fileCol = folder.Files;
// load everyting so we can access it
srcContext.Load(folder);
srcContext.Load(fileCol);
srcContext.ExecuteQuery();
foreach (SP.File f in fileCol)
{
// load the file
srcContext.Load(f);
srcContext.ExecuteQuery();
string[] parts = null;
string id = null;
if (srcLibrary == "My Files")
{
// these are doc sets
parts = f.ServerRelativeUrl.Split('/');
id = parts[parts.Length - 2];
}
else
{
id = folder.Name;
}
// build new location url
string nLocation = destWeb.ServerRelativeUrl.TrimEnd('/') + "/" + destLibrary.Replace(" ", "") + "/" + id + "/" + f.Name;
// read the file, copy the content to new file at new location
SP.FileInformation fileInfo = SP.File.OpenBinaryDirect(srcContext, f.ServerRelativeUrl);
SP.File.SaveBinaryDirect(destContext, nLocation, fileInfo.Stream, true);
}
}
}
catch (Exception ex)
{
Log("File Error = " + ex.ToString());
}
}
}
Source: https://sharepoint.stackexchange.com/questions/114033/how-do-i-move-files-from-one-document-library-to-another-using-jsom
I strongly advise against using the approach suggested by Nikerym. You don't want to download the bytes only to upload them unmodified. It's slow and error-prone. Instead, use the built-in method provided by the CSOM API.
https://learn.microsoft.com/en-us/previous-versions/office/sharepoint-server/mt162553(v=office.15)?redirectedfrom=MSDN
var srcPath = "https://YOUR.sharepoint.com/sites/xxx/SitePages/Page.aspx";
var destPath = $"https://YOUR.sharepoint.com/sites/xxx/SitePages/CopiedPage.aspx";
MoveCopyUtil.CopyFileByPath(ctx, ResourcePath.FromDecodedUrl(srcPath), ResourcePath.FromDecodedUrl(destPath), false, new MoveCopyOptions());
ctx.ExecuteQuery();
You can configure the override behavior by adjusting the 4th and 5th arguments of the function signature.
[...]
bool overwrite,
MoveCopyOptions options
https://learn.microsoft.com/en-us/previous-versions/office/sharepoint-server/mt844930(v=office.15)
I have never programmatically generated and sent email notifications before. What I need to do is this, if this program fails to move files or encounters an exception to generate an email and (preferably) attach a Log.txt file to the email then send it. This app will be ran from an enterprise scheduler and ran 1/hour to manage the files and folders.
Here is the functional code that I currently have (no email implementation exists yet) I have commented in the locations that I would like to send an email
using System;
using System.Configuration;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Runtime.InteropServices;
namespace FileOrganizer
{
class Program
{
//These are the folder to organize and delimeter that is in the filenames to split off a folder from
static string folder = ConfigurationManager.AppSettings["folder"]; //"C:\Users\MyUserName\Desktop\Organize Me"
static string delim = ConfigurationManager.AppSettings["delim"]; // delim = "__"
//location for the log.txt file
static string logPath = ConfigurationManager.AppSettings["logPath"]; //C:\Users\MyUserName\Desktop\Log.txt
//contact list from the app.config
static string emailTo = ConfigurationManager.AppSettings["notifyEmailAddress"]; //multiple email addresses delimited by a ; example = tom#domain.com;dick#differentDomain.com;harry#domain.com
//for the log.txt reporting
static int filesMoved = 0;
static int filesNOTmoved = 0;
static string fileNamesNOTmoved = "";
static int foldersCreated = 0;
static string message = "";
static string stackTrace = "";
//used for passing all the objects into the log method
static object[] details = new object[6];
static void Main(string[] args)
{
try
{
using (StreamWriter w = File.AppendText(logPath))
{
int total = organizeFolder(delim, folder);
details[0] = filesMoved;
details[1] = filesNOTmoved;
details[2] = fileNamesNOTmoved;
details[3] = foldersCreated;
details[4] = "No Errors Found";
details[5] = "No Errors Found";
Log(details, w);
if (filesNOTmoved > Convert.ToInt32(filesNOTmoved))
{
//generate email to notify of filesNames not moved (prefer to send Log.txt as attachment)
}
}
using (StreamReader r = File.OpenText(logPath))
{
DumpLog(r);
}
}
catch (Exception ex)
{
using (StreamWriter w = File.AppendText(logPath))
{
details[0] = filesMoved;
details[1] = filesNOTmoved;
details[2] = fileNamesNOTmoved;
details[3] = foldersCreated;
details[4] = ex.Message.ToString();
details[5] = ex.StackTrace.ToString();
Log(details, w);
}
//generate email to notify of Error and (prefer to send Log.txt as attachment)
using (StreamReader r = File.OpenText(logPath))
{
DumpLog(r);
}
}
}
/*Takes a folder and organizes it into folder by naming folder whatever
* is in front of the delimeter Example -->(folderName__fileName.txt would be moved
* to a new or existing folder named folderName. Final path for file would
* look like this folderName\folderName__fileName.txt)
*
* It will not move file if it already exists in the new location, instead
* it prompts user that they should rename file and run program again.*/
static int organizeFolder(string delimeter, string rootPath)
{
//counter for total files in Folder
int Count = 0;
int totalMoved = 0;
FileInfo[] fileNames;
DirectoryInfo di = new DirectoryInfo(rootPath);
//set all file names as a string from network directory into an array
fileNames = di.GetFiles("*.*");
Count = fileNames.Length;
//Count = 0;//just to test exception handling
//if no files exist in network folder throw error message.
if (Count == 0)
{
fileNamesNOTmoved = "No Files Were In Directory to Move.";
}
else //files exist in network folder
{
int delimIndex = 0;
string folderName;
for (int i = 0; i < Count; i++)
{
if (fileNames[i].ToString().Contains(delimeter))
{
delimIndex = fileNames[i].ToString().IndexOf(delimeter);
folderName = fileNames[i].ToString().Substring(0, delimIndex);
//if folder doesnt exist create it here.
folderCreation(rootPath, folderName);
if (!File.Exists(rootPath + #"\" + folderName + #"\" + fileNames[i].Name))
{
File.Move(rootPath + #"\" + fileNames[i].ToString(), rootPath + #"\" + folderName + #"\" + fileNames[i].Name);
filesMoved++;
}
else
{
if (fileNamesNOTmoved == "")
fileNamesNOTmoved += "| ";
fileNamesNOTmoved += fileNames[i].Name + " | ";
filesNOTmoved++;
}
totalMoved++;
}
}
}
return totalMoved;
}
//if folder does not exist this method will create it
private static void folderCreation(string strTempPath, string folderName)
{
strTempPath = strTempPath + #"\" + folderName;
if (!Directory.Exists(strTempPath))
{
//Create \folderName
Directory.CreateDirectory(strTempPath);
foldersCreated++;
}
}
//adds entries to the log.txt file
public static void Log(object[] details, TextWriter w)
{
w.Write("\r\nLog Entry : ");
w.WriteLine("{0} {1}", DateTime.Now.ToShortTimeString(), DateTime.Now.ToShortDateString());
w.WriteLine(" :");
w.WriteLine(" :{0}", "Files Moved = " + details[0].ToString());
w.WriteLine(" :{0}", "Folders Created = " + details[3].ToString());
w.WriteLine(" :{0}", "Files Not Moved = " + details[1].ToString());
w.WriteLine(" :{0}", "Files Names Not Moved = {" + details[2].ToString() + "}");
w.WriteLine(" :{0}", "Error Message = " + details[4].ToString());
w.WriteLine(" :{0}", "Error Stack Trace = " + details[5].ToString());
w.WriteLine("---------------------------------------------------------------------------------------");
}
public static void DumpLog(StreamReader r)
{
string line;
while ((line = r.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}
}
Basically would like the email to say:
Subject: Data Management Error Notification
Body: This is an Auto-Generated email to notify you there
was an error attempting to clean up the Files in the Directory.
Please refer to Attached Log.txt File for more detailed information.
Attachment: Log.txt
As it turns out SMTP was the way to go and Due to restrictions on my local machine I could only access it from the Dev, Qua, & Prd Servers. Once tested as admin from any of those locations.. email sent perfectly!!!
using the App.Config to hold all my To's, Froms, and the SMTP Host info and passing HTML as a string object into the method, here is the method I used to send email.
public static void sendMail(string msg)
{
SmtpClient smtpClient = new SmtpClient();
MailMessage message = new MailMessage();
MailAddress fromAddress = new MailAddress(emailFrom);
smtpClient.Host = smtpHost;
message.From = fromAddress;
message.Subject = "Test Email";
message.IsBodyHtml = true;
message.Body = msg;
message.To.Add(emailTo);
message.Priority = MailPriority.High;
smtpClient.Send(message);
}