What are relative paths relative to? - c#

Consider the following statement in a C# console application:
Process.Start("3rdParty/SomeTool.exe");
This statement starts SomeTool.exe in the 3rdParty folder relative to... what, exactly? The folder where the application's .exe resides? The current working directory (which can be changed during the application's lifetime)? Something else?

It is relative to the current working directory of your process.
You can determine your current working directory using Directory.GetCurrentDirectory() and change it using Directory.SetCurrentDirectory().

Well, why don't we find out?
Let's create a simple console application and have some fun with it:
namespace ConsoleApplication1
{
class Program
{
public static void Main()
{
Directory.CreateDirectory("Test");
Console.WriteLine($"Absolute path is: { Path.GetFullPath("Test")}");
Console.ReadLine();
}
}
}
Build it in release mode (we are deploying it after all) and put it in some accesible location. Now double click on it and see what output you get.
Absolute path is: {SomeAccesibleLocationPath}\Test
Hmmm, it seems like the relative path is relative to the directory where the executable was launched. Is this always so?
Let's build another app, we'll call it ConsoleApplication2, and play some more:
class Program
{
public static void Main()
{
Console.WriteLine($"TEST #1: {LaunchProcessAndGetAbsolutePath()}");
Console.WriteLine($"TEST #2: {LaunchProcessAndGetAbsolutePath(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments))}");
Console.ReadLine();
}
private static string LaunchProcessAndGetAbsolutePath(string workingDirectory = null)
{
var startInfo = new ProcessStartInfo(#"{SomeAccesibleLocationPath}\ConsoleApplication1.exe");
startInfo.RedirectStandardOutput = true;
startInfo.RedirectStandardInput = true;
startInfo.CreateNoWindow = true;
startInfo.UseShellExecute = false;
if (workingDirectory != null)
{
startInfo.WorkingDirectory = workingDirectory;
}
using (var p = Process.Start(startInfo))
{
var ret = p.StandardOutput.ReadLine();
p.StandardInput.WriteLine();
p.WaitForExit();
return ret;
}
}
}
If we run this, you'll see that the output is the following:
TEST #1: {MyConsoleApplication2ExecutableDirectory}\Test
TEST #2: {MyDocumentsPath}\Test
Important facts to consider:
The relative paths are always relative to the working directory.
When starting a process from another process, the default working directory is the working directory of the "parent" process, its not the directory of the launched "child" process.
When launching a process by double clicking on the executable, the working directory is set to the executable's directory.
In general, the working directory need not be the executable's directory. Your program's correctness should not rely on this condition ever.

As mentioned, it is relative to the current working directory. The value can be changed and determined via Directory.SetCurrentDirectory(), resp. Directory.GetCurrentDirectory()
Some notes:
The current working directory CAN be the folder where the executable resides; this is the default working directory if none is specified. But it can also be set to a different folder when a process is started or while running. On start either by setting the property ProcessStartInfo.WorkingDirectory when launching a process, or also by using the working directory field in the symbolic link dialog in e.g. the start menu. The latter can be done by a user. The current working directory CAN also be changed by any 3rd party library that is being loaded into the process.
Therefore relative paths in an application without checking the working directory are unreliable and usually cause unexpected behavior.

Related

Launching C# program from another C# program

Due to me having knowledge of launching apps I am aware that you have multiple ways of launching an application in C# .NET, but I'm running into a issue that occurs when attempting to launch a SDL2 application.
I have attempted the following using the Process class to:
Start the .exe file of the build.
Start the application using "cmd.exe /K" or "cmd.exe /c" followed by "exec" or "call" or "start" followed by "{path to file}" or "{path to batch file to launch the application}". Launching the application via a batch file and CMD works fine. But, whenever I attempt to even launch the application (even in a new instance of Command-Prompt launched from cmd.exe /? start cmd.exe ?params) it will yield no result.
What I can observe is that the application tries to open. It takes forever to launch into the Window mode (starting the 3D environment). After a timeout it will either, render a couple of frames of a blank window before closing or close immediately after opening the window.
So my question is, does anyone have succesfully made a launcher application for a SDL app written in C# .NET? Or knows a way to debug this behaviour? Because unfortunately, the app does not send out a error message and since SDL safely closes the application I can't observe a crash either.
Edit #1
I'm not doing anything fancy with parameters as there shouldn't be any. I already have another one functioning that launches a normal C# application as my launcher requires to open 2 programs. 1 SLD application, 1 COM:VBA controlling application.
Given:
string audioSpectrumProgram = "AudioSpectrum.exe";
string audioSpectrumBatchProgram = "AudioSpectrum.bat";
private void BtnLaunchPPTApp_OnClick()
{
//Powerpoint controlling application
pVBAApp = Process.Start(presenterProgram, $"\"{this.path}\" {this.audioFormatParams[0]} {((this.ckboxGenerate.Checked) ? "--create" : "")} lang={this.languageCodesParams[this.cboxLanguage.SelectedIndex]}");
}
Method 1:
private void BtnLaunchSDLApp_OnClick()
{
pVizualizer = Process.Start(audioSpectrumProgram); //file launched from local path (is correct)
}
Method 2:
pVizualizer = Process.Start(audioSpectrumBatchProgram); //file launched from local path (is correct)
Method 3:
ProcessStartInfo info = new ProcessStartInfo("cmd.exe");
FileInfo spectrumFileInfo = new FileInfo(audioSpectrumProgram);
if (spectrumFileInfo.Exists)
info.Arguments = $"/c \"{spectrumFileInfo.FullName}\"";
pVizualizer = Process.Start(info);
Method 4:
based on senario of method 3. You don't have to parse arguments using ProcessStartInfo.
pVizualizer = Process.Start($"cmd.exe /K call \"{spectrumFileInfo.FullName}\"") //to observe what happens to the application
Edit #2
Not affected by changing the UseShellExecute to true or false
private void btnOpenVisualizer_Click(object sender, EventArgs e)
{
FileInfo spectrumFileInfo = new FileInfo(audioSpectrumProgram);
ProcessStartInfo info = new ProcessStartInfo(spectrumFileInfo.FullName);
info.UseShellExecute = true;
pVizualizer = new Process();
pVizualizer.StartInfo = info;
pVizualizer.EnableRaisingEvents = true;
pVizualizer.Exited += new EventHandler(myProcess_Exited);
pVizualizer.Start();
}
private void myProcess_Exited(object sender, System.EventArgs e)
{
Console.WriteLine(
$"Exit time : {pVizualizer.ExitTime}\n" +
$"Exit code : {pVizualizer.ExitCode}\n"
);
}
A general way of analyzing startup issues is to use SysInternals Process Monitor.
Record the application that is not starting up properly. Use a filter for your application. Then go through all items which don't have SUCCESS in the result column. Typically you want to do that bottom-up, since the last error is the one stopping your application from loading.
Like this you'll find common startup issues like:
missing DLLs or other dependencies
old DLLs or DLLs loaded from wrong location (e.g. registered COM components)
wrong working directory, e.g. access to non-existent config files
Ok For Future reference:
Pathing to the files can be correct and everything might be in order but if you are using DLLs for imports. Change the process's working directory.
The project will run, libs can "sometimes" be found but can cause a weird unknown bug like this one. So the most optimal way of running another C# instance with SDL or any other kind of library:
private void RunSDLProgram()
{
FileInfo spectrumFileInfo = new FileInfo("pathToFile.exe");
ProcessStartInfo info = new ProcessStartInfo(spectrumFileInfo.FullName);
info.RedirectStandardOutput = true;
info.RedirectStandardError = true;
info.UseShellExecute = false;
info.WorkingDirectory = spectrumFileInfo.DirectoryName;
pVizualizer = new Process();
pVizualizer.StartInfo = info;
pVizualizer.EnableRaisingEvents = true;
pVizualizer.Exited += new EventHandler(myProcess_Exited);
pVizualizer.Start();
}
private void myProcess_Exited(object sender, System.EventArgs e)
{
Console.WriteLine(
$"Exit time : {pVizualizer.ExitTime}\n" +
$"Exit code : {pVizualizer.ExitCode}\n" +
$"output : {pVizualizer.StandardOutput}\n" +
$"err : {pVizualizer.StandardError}\n"
);
}
Running a batch file will look at it's own directory and makes all references local, but it won't alter the working directory. (already had my suspicions about changing the work directory but I didn't see a way to call 2 opperations in process.start("cmd.exe");)

.net core can't open file from python script

Someone i know has made a python script that checks validity of some files. Right now that only works local, but he wants it to work cloud based. He gave me the .exe file with a few other files that i can use. I'm using .net core 2 for my backend. I can start the .exe file just fine. But when the python code tries to open the config file he made, the program returns it cannot find the file.
try:
json_data=open('config.txt').read()
except FileNotFoundError:
sys.exit('FATAL ERROR: config.txt not found!') <--returns this
except Exception as e:
sys.exit('FATAL ERROR: {}'.format(e))
The file is in the same folder as the .exe. Whenever i just run it straight from windows it works perfect and it can find the file. But whenever i run the program from within my .net core webapp it gives back that error. I'm using visual studio 2017 for development and debugging.
Here is my c# code i got from a post on stackoverflow to run the exe:
public IActionResult Index(string cmd, string args)
{
try
{
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = "Python\\Windows 64-bit\\mdqt.exe";
start.Arguments = string.Format("\"{0}\" \"{1}\"", cmd, args);
start.UseShellExecute = false;
start.CreateNoWindow = false;
start.RedirectStandardOutput = true;
start.RedirectStandardError = true;
using (Process process = Process.Start(start))
{
string result = "";
while (!process.HasExited)
{
while (!process.StandardOutput.EndOfStream)
{
result += process.StandardOutput.ReadLine();
}
while (!process.StandardError.EndOfStream)
{
result += process.StandardError.ReadLine();
}
}
return Ok(result);
}
}
catch (Exception e)
{
return Ok(e);
}
}
Could it be some file permissions issue? If so how can i fix it?
EDIT:
I have set the working directory using the following code:
start.WorkingDirectory = Path.Combine(hostingEnvironment.ContentRootPath.ToString(), "Python\\Windows 64-bit");
It now gives me an error it cannot find the folder. Yet when I put the link into the filemanager(and ofcourse delete 1 backslash everywhere), it directs me to the right folder. Could this be a permissions issue? The folder I'm linking to is within the project folder, so IIS should be able to reach that folder as it has permission to interact with the parent folders. What am I overlooking?

recognize if program was run from Desktop

Is there any way to recognize if the application has been run from a shortcut instead of executable file? I need to make my users to copy exe file to their desktops rather than create shortcuts to it due to personalization issues. Any ideas?
Edit: creating the installer is not an option.
I don't know if this helps, but if you want your exe file to be on the desktop, this could work:
string path = Directory.GetCurrentDirectory();
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
if (!path.Equals(desktopPath))
{
Console.WriteLine("file is not at desktop");
}
If you do have an app in the windows shared folder you can configure it to prevent execution of the applications.
Or you can provide user just with link to .bat file instead of .exe and it would do something like this (using robocopy):
robocopy \\remote\server\exe %AppData%\your\folder app.exe /XO
start %AppData%\your\folder\app.exe
And on the C# side you can just check application path and do something like this:
public class Program
{
public int Main()
{
string original_path = System.IO.Path.GetFullPath(#"\\remote\app.exe");
string current_path = System.IO.Path.GetFullPath(
System.Reflection.Assembly.GetExecutingAssembly().Location);
if(original_path == current_path){
System.IO.File.Copy(original_path, #"C:\foo\bar\app.exe", true);
System.Diagnostics.Process.Start(#"C:\foo\bar\app.exe");
return 0;
}
// Run program normally here
}
}

How to get Directory while running unit test

Hi when running my unit test I'm wanting to get the directory my project is running in to retrieve a file.
Say I have a Test project named MyProject. Test I run:
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
and I receive "C:\\Source\\MyProject.Test\\bin\\Debug".
This is close to what I'm after. I don't want the bin\\Debug part.
Anyone know how instead I could get "C:\\Source\\MyProject.Test\\"?
I would do it differently.
I suggest making that file part of the solution/project. Then right-click -> Properties -> Copy To Output = Copy Always.
That file will then be copied to whatever your output directory is (e.g. C:\Source\MyProject.Test\bin\Debug).
Edit: Copy To Output = Copy if Newer is the better option
Usually you retrieve your solution directory (or project directory, depending on your solution structure) like this:
string solution_dir = Path.GetDirectoryName( Path.GetDirectoryName(
TestContext.CurrentContext.TestDirectory ) );
This will give you the parent directory of the "TestResults" folder created by testing projects.
Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
This will give you the directory you need....
as
AppDomain.CurrentDomain.SetupInformation.ApplicationBase
gives nothing but
Directory.GetCurrentDirectory().
Have alook at this link
http://msdn.microsoft.com/en-us/library/system.appdomain.currentdomain.aspx
Further to #abhilash's comment.
This works in my EXE's, DLL's and when tested from a different UnitTest project in both Debug or Release modes:
var dirName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location.Replace("bin\\Debug", string.Empty));
/// <summary>
/// Testing various directory sources in a Unit Test project
/// </summary>
/// <remarks>
/// I want to mimic the web app's App_Data folder in a Unit Test project:
/// A) Using Copy to Output Directory on each data file
/// D) Without having to set Copy to Output Directory on each data file
/// </remarks>
[TestMethod]
public void UT_PathsExist()
{
// Gets bin\Release or bin\Debug depending on mode
string baseA = AppDomain.CurrentDomain.SetupInformation.ApplicationBase;
Console.WriteLine(string.Format("Dir A:{0}", baseA));
Assert.IsTrue(System.IO.Directory.Exists(baseA));
// Gets bin\Release or bin\Debug depending on mode
string baseB = AppDomain.CurrentDomain.BaseDirectory;
Console.WriteLine(string.Format("Dir B:{0}", baseB));
Assert.IsTrue(System.IO.Directory.Exists(baseB));
// Returns empty string (or exception if you use .ToString()
string baseC = (string)AppDomain.CurrentDomain.GetData("DataDirectory");
Console.WriteLine(string.Format("Dir C:{0}", baseC));
Assert.IsFalse(System.IO.Directory.Exists(baseC));
// Move up two levels
string baseD = System.IO.Directory.GetParent(baseA).Parent.FullName;
Console.WriteLine(string.Format("Dir D:{0}", baseD));
Assert.IsTrue(System.IO.Directory.Exists(baseD));
// You need to set the Copy to Output Directory on each data file
var appPathA = System.IO.Path.Combine(baseA, "App_Data");
Console.WriteLine(string.Format("Dir A/App_Data:{0}", appPathA));
// C:/solution/UnitTestProject/bin/Debug/App_Data
Assert.IsTrue(System.IO.Directory.Exists(appPathA));
// You can work with data files in the project directory's App_Data folder (or any other test data folder)
var appPathD = System.IO.Path.Combine(baseD, "App_Data");
Console.WriteLine(string.Format("Dir D/App_Data:{0}", appPathD));
// C:/solution/UnitTestProject/App_Data
Assert.IsTrue(System.IO.Directory.Exists(appPathD));
}
I normally do it like that, and then I just add "..\..\" to the path to get up to the directory I want.
So what you could do is this:
var path = AppDomain.CurrentDomain.SetupInformation.ApplicationBase + #"..\..\";
For NUnit this is what I do:
// Get the executing directory of the tests
string dir = NUnit.Framework.TestContext.CurrentContext.TestDirectory;
// Infer the project directory from there...2 levels up (depending on project type - for asp.net omit the latter Parent for a single level up)
dir = System.IO.Directory.GetParent(dir).Parent.FullName;
If required you can from there navigate back down to other directories if required:
dir = Path.Combine(dir, "MySubDir");
According to https://github.com/nunit/nunit/issues/742#issuecomment-121964506
For NUnit3 , System.Environment.CurrentDirector is never changed, so it shall be the path of solution.
Eg:
string szProjectPath = System.Environment.CurrentDirectory + #"\where\your\project\is";
I prefer fixed location rather than GetParent().
One drawback of GetParent is when build is changed from AnyCPU to x86, default path would be changed from bin\Debug to bin\x86\Debug.
Need to get another parent, and it's pain in the neck.
Also, you may still access to you test assemblies at TestContext.CurrentContext.TestDirectory and get output from TestContext.CurrentContext.WorkDirectory
Edit:
Note: There are many changes in NUnit3. I will suggest reading through the documentation about "Breaking changes"
The best solution I found was to put the file as an embedded resource on the test project and get it from my unit test. With this solution I don´t need to care about file paths.
I'm not sure if this helps, but this looks to be briefly touched on in the following question.
Visual Studio Solution Path environment variable
In general you may use this, regardless if running a test or console app or web app:
// returns the absolute path of assembly, file://C:/.../MyAssembly.dll
var codeBase = Assembly.GetExecutingAssembly().CodeBase;
// returns the absolute path of assembly, i.e: C:\...\MyAssembly.dll
var location = Assembly.GetExecutingAssembly().Location;
If you are running NUnit, then:
// return the absolute path of directory, i.e. C:\...\
var testDirectory = TestContext.CurrentContext.TestDirectory;
My approach relies on getting the location of the unit testing assembly and then traversing upwards. In the following snippet the variable folderProjectLevel will give you the path to the Unit test project.
string pathAssembly = System.Reflection.Assembly.GetExecutingAssembly().Location;
string folderAssembly = System.IO.Path.GetDirectoryName(pathAssembly);
if (folderAssembly.EndsWith("\\") == false) {
folderAssembly = folderAssembly + "\\";
}
string folderProjectLevel = System.IO.Path.GetFullPath(folderAssembly + "..\\..\\");
You can do it like this:
using System.IO;
Path.GetFullPath(Path.Combine(AppDomain.CurrentDomain.SetupInformation.ApplicationBase, #"..\..\"));
use StackTrace
internal static class Extensions
{
public static string GetSourceDirectoryName(this Type type)
{
StackTrace stackTrace = new StackTrace(true);
foreach (var frame in stackTrace.GetFrames())
{
if (frame.GetMethod() is { } method && method.DeclaringType == type)
{
return Path.GetDirectoryName(frame.GetFileName());
}
}
throw new Exception($"未找到{type.Name}源文件目录");
}
}

Need to delete files in Program files using c#, files wont delete

Why wont the files in the test folder delete?? How can i get admin access??
namespace Delete
{
using System;
using System.Windows.Forms;
using System.IO;
public class Delete
{
public Delete()
{
if (Directory.Exists(#"C:\Program Files (x86)\test\"))
{
string[] filePaths = Directory.GetFiles(#"C:\Program Files (x86)\test\");
foreach (string file in filePaths) { File.Delete(file); }
}
}
}
}
You need to rethink your strategy.
If you are adding/removing files programatically from within your application, they should be stored in a separate location (that won't need admin privs to elevate for writing/deleting, etc.):
like the user's data directory/your company/your application, or
the user's documents/your company/your application
The Program Files directory is for application specific files (DLL's, etc) that are installed with the program but don't change once installed/updated.
Here's an example of the User's Data directory by application:
public static DirectoryInfo ApplicationVersionDirectory()
{
return new DirectoryInfo(System.Windows.Forms.Application.UserAppDataPath);
}
This is due to the UAC. So either run your executable as admin by right clicking -> "Run as Administrator" or if you want to do it programatically refer to other posts like Windows 7 and Vista UAC - Programmatically requesting elevation in C#
In order to delete files from "Program Files" folder you need to start application as an administrator. Otherwise you will not be able to get an access to %PROGRAMFILES%.
Here is the sample code to restart current app and run it as admin:
ProcessStartInfo proc = new ProcessStartInfo();
proc.UseShellExecute = true;
proc.FileName = Application.ExecutablePath;
proc.Verb = "runas";
try
{
Process.Start(proc);
}
catch
{
// The user refused the elevation.
// Do nothing and return directly ...
return;
}
Application.Exit(); // Quit itself

Categories

Resources