I have a for loop container within my ssis package which contains a script and a sql task.
I have 3 variables.
source.string = this is folder location
file.string = i have used wildcard = *.csv
exist.int = defaulted to 0
I have the innitexpression value set to #Exists=1
and the evalexpression value set to #Exists=1
in the script I have set it to look at source variable and if file.string variable exists then set exist variable to 1
problem is it just loops it should only loop if no file there. cant see how I've done this wrong it was working before I changed the variable to be a wildcard *.csv
I have tested it using another variable which contains a filename rather than a wildcard and it works correctly the issue is when looking for a wildcard for the filename followed by the extension. why is this? can I not pass through a wildcard variable?
my script task is
public void Main()
{
// TODO: Add your code here
string Filepath = Dts.Variables["User::Source"].Value.ToString()
+ Dts.Variables["User::file"].Value.ToString();
if (
File.Exists(Filepath))
{
Dts.Variables["User::Exists"].Value = 1;
}
/// MessageBox.Show (Filepath);
/// MessageBox.Show(Dts.Variables["Exists"].Value.ToString());
Dts.TaskResult = (int)ScriptResults.Success;
}
#region ScriptResults declaration
/// <summary>
/// This enum provides a convenient shorthand within the scope of this class for setting the
/// result of the script.
///
/// This code was generated automatically.
/// </summary>
enum ScriptResults
{
Success = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Success,
Failure = Microsoft.SqlServer.Dts.Runtime.DTSExecResult.Failure
};
#endregion
}
}
Based on comments above i made 2 different solutions. The solution for you right now would be no. 2
This one can search for a specific file based on multiple files in your path. It need some tweaking but can be used if you wanna check if a specific file exists with wildcard
This one evaluates to true if any wildcard file is found.
C# Code 1
Using System.IO:
string Filepath = Dts.Variables["User::Source"].Value.ToString();
string WildCard = Dts.Variables["User::file"].Value.ToString(); // In Text form #"*.txt";
string fullpath = Filepath + WildCard;
//With for loop
string txtFile = null;
// Gets all files with wildcard
string[] allfiles = Directory.GetFiles(Filepath, WildCard);
//Loop through all files and set the filename in txtFile. Do whatever you want here
foreach(string fileName in allfiles)
{
//Check if a file contains something, it could be a prefixed name you only want
if(fileName.Contains("txt"))
{
txtFile = fileName;
if(File.Exists(txtFile))
{
Dts.Variables["User::Exists"].Value = 1;
}
}
}
C# Code 2
Using System.IO;
Using System.Linq;
string Filepath = Dts.Variables["User::Source"].Value.ToString();
string WildCard = Dts.Variables["User::file"].Value.ToString(); //In text form "*.txt";
string fullpath = Filepath + WildCard;
//With bool
bool exists = Directory.EnumerateFiles(Filepath, WildCard).Any();
if(exists == true)
{
Dts.Variables["User::Exists"].Value = 1;
}
MessageBox.Show (Filepath);
MessageBox.Show(Dts.Variables["Exists"].Value.ToString());
Related
Im have this script to add current year month to a file in a directory. It works perfect with the directory path, but I want to change the directory patch for a variable, because every month its going to be in a different folder. I already have the variable with the directory to use. But what I dont know how to do is, use the variable instead of the directory
public void Main()
{
// TODO: Add your code here
// const string DIRECTORY_PATH = #"C:\Temp\Results"; //with this works perfect
const string DIRECTORY_PATH = #"#v_ResultsExcel"; //I want to use this variable
const string FILE_NAME_TEMPLATE = "YYYY-MM";
string previousYearMonthDate = DateTime.Now.AddYears(0).AddMonths(0).ToString("yyyy-MM");
if (Directory.Exists(DIRECTORY_PATH))
{
string[] filePathList = Directory.GetFiles(DIRECTORY_PATH);
foreach (string filepath in filePathList)
{
if (File.Exists(filepath))
{
File.Move(filepath, filepath.Replace(FILE_NAME_TEMPLATE, previousYearMonthDate));
}
}
}
Dts.TaskResult = (int)ScriptResults.Success;
}
Create a variable in your SSIS package. In the script task ReadOnlyVariables select User::varDir. C# code call the read only variable. You can write the expression for this variable to point to the appropriate directory each month.
const string DIRECTORY_PATH = Dts.Variables["User::varDir"].Value.ToString();
I create a FolderBrowserDialog as follows (only an excerpt -not complete code):
string tempSetWorkingPath = null;
try
{
FolderBrowserDialog folderDlg = new System.Windows.Forms.FolderBrowserDialog();
folderDlg.ShowNewFolderButton = true;
folderDlg.Description = "Selected your working folder. This is where your PDF files will be saved.";
folderDlg.RootFolder = Environment.SpecialFolder.MyComputer;
folderDlg.SelectedPath = (Convert.ToString(WorkingPath).Trim().Length == 0) ? ((int)Environment.SpecialFolder.MyComputer).ToString() : WorkingPath;
if (folderDlg.ShowDialog() == DialogResult.OK)
{
tempSetWorkingPath = folderDlg.SelectedPath;
}
else
{
tempSetWorkingPath = "";
}
}
...
The code works well, except the only folders that are showing are the local folders. Users have DropBox and OneDrive shared folders on their systems and to select one of those directories, the user needs to cycle through the windows user directories and select the folder from there. On some systems I have seen over the last few months, I've seen the DropBox and OneDrive directories appear below the DeskTop directory ... but I have not, despite hours of searching - found a way to achive that.
How can I achieve that?
MTIA
DWE
Given I have observed a large number of queries posted here and elsewhere regarding the inclusion of the directories, including shared directories and given the response by # Mailosz, it seems that the root folder property of the folder dialog holds the key - it Gets or sets the root folder where the browsing starts from and thats what my code was missing.
The full code to the function referred to in my question appears below, in the event it assists someone else.
/// <summary>
/// presents the user with a folder dialog
/// Returns a full qualified directory chosen by the user
/// </summary>
/// <param name="WorkingPath">if a fully qualified directory name is provided, then the folder structure in the folder dialog will open to the directory selected</param>
/// <returns>Returns a full qualified directory chosen by the user or if no directory was chosen, an empty string</returns>
public string SetWorkingPath(string WorkingPath)
{
string tempSetWorkingPath = null;
try
{
FolderBrowserDialog folderDlg = new System.Windows.Forms.FolderBrowserDialog();
// check our proposed working path and if its valid
if((!string.IsNullOrEmpty(WorkingPath) && (WorkingPath != null)))
{
if (!Directory.Exists(WorkingPath))
WorkingPath = string.Empty;
}
else // if we are empty or null set us to empty
{
WorkingPath = string.Empty;
}
folderDlg.ShowNewFolderButton = true;
folderDlg.Description = "Please select your working folder. This is where your PDF files will be saved.";
folderDlg.RootFolder = Environment.SpecialFolder.Desktop;//.MyComputer;
folderDlg.SelectedPath = (Convert.ToString(WorkingPath).Trim().Length == 0) ? ((int)Environment.SpecialFolder.MyComputer).ToString() : WorkingPath;
if (folderDlg.ShowDialog() == DialogResult.OK)
{
// make sure we have a backslash on the end of our directory string
tempSetWorkingPath = PathAddBackslash(folderDlg.SelectedPath);
}
else
{
// return an empty string
tempSetWorkingPath = string.Empty;
}
}
catch (Exception ex)
{
tempSetWorkingPath = string.Empty;
throw (ex);
}
return tempSetWorkingPath;
}
public string PathAddBackslash(string path)
{
// They're always one character but EndsWith is shorter than
// array style access to last path character. Change this
// if performance are a (measured) issue.
string separator1 = Path.DirectorySeparatorChar.ToString();
string separator2 = Path.AltDirectorySeparatorChar.ToString();
// Trailing white spaces are always ignored but folders may have
// leading spaces. It's unusual but it may happen. If it's an issue
// then just replace TrimEnd() with Trim(). Tnx Paul Groke to point this out.
path = path.TrimEnd();
// Argument is always a directory name then if there is one
// of allowed separators then I have nothing to do.
if (path.EndsWith(separator1) || path.EndsWith(separator2))
return path;
// If there is the "alt" separator then I add a trailing one.
// Note that URI format (file://drive:\path\filename.ext) is
// not supported in most .NET I/O functions then we don't support it
// here too. If you have to then simply revert this check:
// if (path.Contains(separator1))
// return path + separator1;
//
// return path + separator2;
if (path.Contains(separator2))
return path + separator2;
// If there is not an "alt" separator I add a "normal" one.
// It means path may be with normal one or it has not any separator
// (for example if it's just a directory name). In this case I
// default to normal as users expect.
return path + separator1;
}
I need to loop through set of files and check if it is comma delimited or not in C#.I am very new to C#.Please help me with this.
Thanks in advance.
As someone already pointed out it is not going to be easy solution.
It could be very easy if every comma delimited file had a specified extension (for example: csv). If not the following algorigthm should work:
Retrieve all names (paths + names) of files in specified directory. If needed filter only those that may be of interest. Hint: take a look at System.IO.Directory and System.IO.File and System.IO.DirectoryInfo and System.IO.FileInfo
You have to examine every file, and check if it is comma delimited or not. This is going to be tricky part. You could build a regular expression, that will check each line of the file and and tell you if it is comma delimited or not.
Regular expressions are a bit hard to learn at the beginning but it should pay back after some time.
Here's a quick Console app that will take a directory, scan the directory for all files, then iterate through them and return a percentage of lines containing commas -vs- the total lines in the file. As has been pointed out, there are CSV libraries you can validate against. This is just a quick example to get you started.
To use this, create a new Console App project in Visual Studio and name it "TestStub" then copy and past this into the "Program.cs" file.
namespace TestStub
{
using System;
using System.IO;
using System.Text;
public class Program
{
private static char[] CSV = { ',', ',' };
private static bool csvFound = false;
/// <summary>
/// This is the console program entry point
/// </summary>
/// <param name="args">A list of any command-line args passed to this application when started</param>
public static void Main(string[] args)
{
// Change this to use args[0] if you like
string myInitialPath = #"C:\Temp";
string[] myListOfFiles;
try
{
myListOfFiles = EnumerateFiles(myInitialPath);
foreach (string file in myListOfFiles)
{
Console.WriteLine("\nFile {0} is comprised of {1}% CSV delimited lines.",
file,
ScanForCSV(file));
}
Console.WriteLine("\n\nPress any key to exit.");
Console.ReadKey();
}
catch (Exception ex)
{
Console.WriteLine(
"Error processing {0} for CSV content: {1} :: {2}",
myInitialPath,
ex.Message,
ex.InnerException.Message);
}
}
/// <summary>
/// Get a list of all files for the specified path
/// </summary>
/// <param name="path">Directory path</param>
/// <returns>String array of files (with full path)</returns>
public static string[] EnumerateFiles(string path)
{
string[] arrItems = new string[1];
try
{
arrItems = Directory.GetFiles(path);
return arrItems;
}
catch (Exception ex)
{
throw new System.IO.IOException("EnumerateFilesAndFolders() encountered an error:", ex);
}
}
/// <summary>
/// Determines if the supplied file has comma separated values
/// </summary>
/// <param name="filename">Path and filename</param>
/// <returns>Percentage of lines containing CSV elements -vs- those without</returns>
public static float ScanForCSV(string filename)
{
//
// NOTE: You should look into one of the many CSV libraries
// available. This method will not carefully scruitinize
// the file to see if there's a combination of delimeters or
// even if it's a plain-text (e.g. a newspaper article)
// It just looks for the presence of commas on multiple lines
// and calculates a percentage of them with and without
//
float totalLines = 0;
float linesCSV = 0;
try
{
using (StreamReader sReader = new StreamReader(filename))
{
int elements = 0;
string line = string.Empty;
string[] parsed = new string[1];
while (!sReader.EndOfStream)
{
++totalLines;
line = sReader.ReadLine();
parsed = line.Split(CSV);
elements = parsed.Length;
if (elements > 1)
{
++linesCSV;
}
}
}
}
catch (Exception ex)
{
throw new System.IO.IOException(string.Format("Problem accessing [{0}]: {1}", filename, ex.Message), ex);
}
return (float)((linesCSV / totalLines) * 100);
}
}
}
}
What is the proper way to receive a file as a parameter when writing a C# cmdlet? So far I just have a property LiteralPath (aligning with their parameter naming convention) that is a string. This is a problem because you just get whatever is typed into the console; which could be the full path or could be a relative path.
Using Path.GetFullPath(string) doesn't work. It thinks I'm currently at ~, I'm not. Same problem occurs if I change the property from a string to a FileInfo.
EDIT: For anyone interested, this workaround is working for me:
SessionState ss = new SessionState();
Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path);
LiteralPath = Path.GetFullPath(LiteralPath);
LiteralPath is the string parameter. I'm still interested in learning what is the recommended way to handle file paths that are passed as parameters.
EDIT2: This is better, so that you don't mess with the users current directory, you should set it back.
string current = Directory.GetCurrentDirectory();
Directory.SetCurrentDirectory(ss.Path.CurrentFileSystemLocation.Path);
LiteralPath = Path.GetFullPath(LiteralPath);
Directory.SetCurrentDirectory(current);
This is a surprisingly complex area, but I have a ton of experience here. In short, there are some cmdlets that accept win32 paths straight from the System.IO APIs, and these typically use a -FilePath parameter. If you want to write a well behaved "powershelly" cmdlet, you need -Path and -LiteralPath, to accept pipeline input and work with relative and absolute provider paths. Here's an excerpt from a blog post I wrote a while ago:
Paths in PowerShell are tough to understand [at first.] PowerShell Paths - or PSPaths, not to be confused with Win32 paths - in their absolute forms, they come in two distinct flavours:
Provider-qualified: FileSystem::c:\temp\foo.txt
PSDrive-qualified: c:\temp\foo.txt
It's very easy to get confused over provider-internal (The ProviderPath property of a resolved System.Management.Automation.PathInfo – the portion to the right of :: of the provider-qualified path above) and drive-qualified paths since they look the same if you look at the default FileSystem provider drives. That is to say, the PSDrive has the same name (C) as the native backing store, the windows filesystem (C). So, to make it easier for yourself to understand the differences, create yourself a new PSDrive:
ps c:\> new-psdrive temp filesystem c:\temp\
ps c:\> cd temp:
ps temp:\>
Now, let's look at this again:
Provider-qualified: FileSystem::c:\temp\foo.txt
Drive-qualified: temp:\foo.txt
A bit easier this time to see what’s different this time. The bold text to the right of the provider name is the ProviderPath.
So, your goals for writing a generalized provider-friendly Cmdlet (or advanced function) that accepts paths are:
Define a LiteralPath path parameter aliased to PSPath
Define a Path parameter (which will resolve wildcards / glob)
Always assume you are receiving PSPaths, NOT native provider-paths (e.g. Win32 paths)
Point number three is especially important. Also, obviously LiteralPath and Path should belong in mutually exclusive parameter sets.
Relative Paths
A good question is: how do we deal with relative paths being passed to a Cmdlet. As you should assume all paths being given to you are PSPaths, let’s look at what the Cmdlet below does:
ps temp:\> write-zip -literalpath foo.txt
The command should assume foo.txt is in the current drive, so this should be resolved immediately in the ProcessRecord or EndProcessing block like (using the scripting API here to demo):
$provider = $null;
$drive = $null
$pathHelper = $ExecutionContext.SessionState.Path
$providerPath = $pathHelper.GetUnresolvedProviderPathFromPSPath(
"foo.txt", [ref]$provider, [ref]$drive)
Now you everything you need to recreate the two absolute forms of PSPaths, and you also have the native absolute ProviderPath. To create a provider-qualified PSPath for foo.txt, use $provider.Name + “::” + $providerPath. If $drive is not $null (your current location might be provider-qualified in which case $drive will be $null) then you should use $drive.name + ":\" + $drive.CurrentLocation + "\" + "foo.txt" to get a drive-qualified PSPath.
Quickstart C# Skeleton
Here's a skeleton of a C# provider-aware cmdlet to get you going. It has built in checks to ensure it has been handed a FileSystem provider path. I am in the process of packaging this up for NuGet to help others get writing well-behaved provider-aware Cmdlets:
using System;
using System.Collections.Generic;
using System.IO;
using System.Management.Automation;
using Microsoft.PowerShell.Commands;
namespace PSQuickStart
{
[Cmdlet(VerbsCommon.Get, Noun,
DefaultParameterSetName = ParamSetPath,
SupportsShouldProcess = true)
]
public class GetFileMetadataCommand : PSCmdlet
{
private const string Noun = "FileMetadata";
private const string ParamSetLiteral = "Literal";
private const string ParamSetPath = "Path";
private string[] _paths;
private bool _shouldExpandWildcards;
[Parameter(
Mandatory = true,
ValueFromPipeline = false,
ValueFromPipelineByPropertyName = true,
ParameterSetName = ParamSetLiteral)
]
[Alias("PSPath")]
[ValidateNotNullOrEmpty]
public string[] LiteralPath
{
get { return _paths; }
set { _paths = value; }
}
[Parameter(
Position = 0,
Mandatory = true,
ValueFromPipeline = true,
ValueFromPipelineByPropertyName = true,
ParameterSetName = ParamSetPath)
]
[ValidateNotNullOrEmpty]
public string[] Path
{
get { return _paths; }
set
{
_shouldExpandWildcards = true;
_paths = value;
}
}
protected override void ProcessRecord()
{
foreach (string path in _paths)
{
// This will hold information about the provider containing
// the items that this path string might resolve to.
ProviderInfo provider;
// This will be used by the method that processes literal paths
PSDriveInfo drive;
// this contains the paths to process for this iteration of the
// loop to resolve and optionally expand wildcards.
List<string> filePaths = new List<string>();
if (_shouldExpandWildcards)
{
// Turn *.txt into foo.txt,foo2.txt etc.
// if path is just "foo.txt," it will return unchanged.
filePaths.AddRange(this.GetResolvedProviderPathFromPSPath(path, out provider));
}
else
{
// no wildcards, so don't try to expand any * or ? symbols.
filePaths.Add(this.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
path, out provider, out drive));
}
// ensure that this path (or set of paths after wildcard expansion)
// is on the filesystem. A wildcard can never expand to span multiple
// providers.
if (IsFileSystemPath(provider, path) == false)
{
// no, so skip to next path in _paths.
continue;
}
// at this point, we have a list of paths on the filesystem.
foreach (string filePath in filePaths)
{
PSObject custom;
// If -whatif was supplied, do not perform the actions
// inside this "if" statement; only show the message.
//
// This block also supports the -confirm switch, where
// you will be asked if you want to perform the action
// "get metadata" on target: foo.txt
if (ShouldProcess(filePath, "Get Metadata"))
{
if (Directory.Exists(filePath))
{
custom = GetDirectoryCustomObject(new DirectoryInfo(filePath));
}
else
{
custom = GetFileCustomObject(new FileInfo(filePath));
}
WriteObject(custom);
}
}
}
}
private PSObject GetFileCustomObject(FileInfo file)
{
// this message will be shown if the -verbose switch is given
WriteVerbose("GetFileCustomObject " + file);
// create a custom object with a few properties
PSObject custom = new PSObject();
custom.Properties.Add(new PSNoteProperty("Size", file.Length));
custom.Properties.Add(new PSNoteProperty("Name", file.Name));
custom.Properties.Add(new PSNoteProperty("Extension", file.Extension));
return custom;
}
private PSObject GetDirectoryCustomObject(DirectoryInfo dir)
{
// this message will be shown if the -verbose switch is given
WriteVerbose("GetDirectoryCustomObject " + dir);
// create a custom object with a few properties
PSObject custom = new PSObject();
int files = dir.GetFiles().Length;
int subdirs = dir.GetDirectories().Length;
custom.Properties.Add(new PSNoteProperty("Files", files));
custom.Properties.Add(new PSNoteProperty("Subdirectories", subdirs));
custom.Properties.Add(new PSNoteProperty("Name", dir.Name));
return custom;
}
private bool IsFileSystemPath(ProviderInfo provider, string path)
{
bool isFileSystem = true;
// check that this provider is the filesystem
if (provider.ImplementingType != typeof(FileSystemProvider))
{
// create a .NET exception wrapping our error text
ArgumentException ex = new ArgumentException(path +
" does not resolve to a path on the FileSystem provider.");
// wrap this in a powershell errorrecord
ErrorRecord error = new ErrorRecord(ex, "InvalidProvider",
ErrorCategory.InvalidArgument, path);
// write a non-terminating error to pipeline
this.WriteError(error);
// tell our caller that the item was not on the filesystem
isFileSystem = false;
}
return isFileSystem;
}
}
}
Cmdlet Development Guidelines (Microsoft)
Here is some more generalized advice that should help you out in the long run:
http://msdn.microsoft.com/en-us/library/ms714657%28VS.85%29.aspx
I'm relatively new to testing and still getting my head around some of the fundamentals. I have a method that I would like to test which basically creates a different file name if the supplied already exists (I've pasted the code below).
I need a way of testing that the method returns a different (but also unique) name if the file already exists. What's the best way of testing this within Visual Studio's unit testing? Is it to create a file as part of the test and then delete it or is there a better way?
public static FileInfo SafeFileName(this FileInfo value)
{
if (value == null) throw new ArgumentNullException("value");
FileInfo fi = value;
//Check the directory exists -if it doesn't create it as we won't move out of this dir
if (!fi.Directory.Exists)
fi.Directory.Create();
//It does so create a new name
int counter = 1;
string pathStub = Path.Combine(fi.Directory.FullName, fi.Name.Substring(0, fi.Name.Length - fi.Extension.Length));
// Keep renaming it until we have a safe filename
while (fi.Exists)
fi = new FileInfo(String.Concat(pathStub, "_", counter++, fi.Extension));
return fi;
}
I think a better would be to use the .Net runtime:
System.IO.Path.GetRandomFileName()
and get rid of the file name generation code all together.
GetRandomFileName
Here are two testing methods (using a Visual Studio unit testing project) for the two scenarios:
// using System.IO;
[TestMethod]
public void WhenFileExists()
{
// Create a file
string existingFilename = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
using (File.Open(existingFilename, FileMode.CreateNew)) { }
// Check its existence
Assert.IsTrue(File.Exists(existingFilename));
// Call method to be tested
string newFilename = DummyCreateFile(existingFilename);
// Check filenames are different
Assert.AreNotEqual<string>(existingFilename, newFilename);
// Check the new file exists
Assert.IsTrue(File.Exists(newFilename));
}
[TestMethod]
public void WhenFileDoesNotExist()
{
// Get a filename but do not create it yet
string existingFilename = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
// Check the file does not exist
Assert.IsFalse(File.Exists(existingFilename));
// Call method to be tested
string newFilename = DummyCreateFile(existingFilename);
// Check the file was created with the filename passed as parameter
Assert.AreEqual<string>(existingFilename, newFilename);
// Check the new file exists
Assert.IsTrue(File.Exists(newFilename));
}
private string DummyCreateFile(string filename)
{
try
{
using (File.Open(filename, FileMode.CreateNew)) { }
return filename;
}
catch
{
string newFilename = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
using (File.Open(newFilename, FileMode.CreateNew)) { }
return newFilename;
}
}
The tested method is slightly changed in that it takes (and returns) a string argument instead of FileInfo for simplicity reasons.
Use File.Exists Method.