I am in need of an example, that can let me pass a parameter
e.g. executing delete.exe /killme.txt
So it will use the "MoveFile" to delete killme.txt after reboot.
Although please not the MS precompiled version, as it has an annoying disclaimer, every time its run on a different computer.
You'll need the P/Invoke declarations for MoveFileEx:
[Flags]
internal enum MoveFileFlags
{
None = 0,
ReplaceExisting = 1,
CopyAllowed = 2,
DelayUntilReboot = 4,
WriteThrough = 8,
CreateHardlink = 16,
FailIfNotTrackable = 32,
}
internal static class NativeMethods
{
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
public static extern bool MoveFileEx(
string lpExistingFileName,
string lpNewFileName,
MoveFileFlags dwFlags);
}
And some example code:
if (!NativeMethods.MoveFileEx("a.txt", null, MoveFileFlags.DelayUntilReboot))
{
Console.Error.WriteLine("Unable to schedule 'a.txt' for deletion");
}
Because you want to perform this after reboot as a requirement, you could use the Windows Task Scheduler API. You can invoke this in C# by adding a reference to the COM library TaskScheduler 1.1 Type Library. Below is a full code example on running Notepad.exe at logon.
Also, here is another resource: http://bartdesmet.net/blogs/bart/archive/2008/02/23/calling-the-task-scheduler-in-windows-vista-and-windows-server-2008-from-managed-code.aspx
You could call the system command DEL from Windows Command line, potentially with this code.
namespace TaskSchedulerExample {
using System;
using TaskScheduler;
class Program {
static void Main(string[] args) {
var scheduler = new TaskSchedulerClass();
scheduler.Connect(null, null, null, null);
ITaskDefinition task = scheduler.NewTask(0);
task.RegistrationInfo.Author = "DCOM Productions";
task.RegistrationInfo.Description = "Demo";
ILogonTrigger trigger = (ILogonTrigger)task.Triggers.Create(_TASK_TRIGGER_TYPE2.TASK_TRIGGER_LOGON);
trigger.Id = "Logon Demo";
IExecAction action = (IExecAction)task.Actions.Create(_TASK_ACTION_TYPE.TASK_ACTION_EXEC);
action.Id = "Delete";
action.Path = "c:\\delete.exe"; // Or similar path
action.WorkingDirectory = "c:\\"; // Working path
action.Arguments = "c:\\killme.txt"; // Path to your file
ITaskFolder root = scheduler.GetFolder("\\");
IRegisteredTask regTask = root.RegisterTaskDefinition("Demo", task, (int)_TASK_CREATION.TASK_CREATE_OR_UPDATE, null, null, _TASK_LOGON_TYPE.TASK_LOGON_INTERACTIVE_TOKEN, "");
//Force run task
//IRunningTask runTask = regTask.Run(null);
}
}
}
This gives you some flexibility. You could run your own delete.exe, or you could potentially invoke the Windows Command Line to execute the DEL command.
Related
i have recently started using pythonNet for executing scripts from Csharp, on an algorithm i was doing in csharp up until now, it works pretty well:
using (Py.GIL())
{
PythonEngine.Initialize();
using (var scope = Py.CreateScope())
{
string code = File.ReadAllText(fileName);
var scriptCompiled = PythonEngine.Compile(code, "Analyze.py");
scope.Execute(scriptCompiled);
dynamic func = scope.Get("predictFromData");
PyList Pydata = new PyList(data.ToPython());
PyTuple rettp = new PyTuple(func(Pydata));
PyList pyIndexList = new PyList(rettp[0]);
foreach (PyObject intobj in pyIndexList)
{
indexList.Add(intobj.As<int>());
}
}
}
But i'd like to know if there is a way to check if the code can be executed before actually running it, since it works with compiled code, and since PythonNet does require an external python installation to see if every modules are here ect... And then switch back to my previous csharp algorithm if it is not possible in python.
For now i'm thinking about simply executing a python unit test importing modules and testing functions with dummy values and returning exceptions and units tests values to csharp code, but i'd prefer a cleaner way if anyone has an idea.
Cheers.
There are few things you can check here:
first is to see if Python code has correct syntax, it can be done with the code like this:
public static IReadOnlyList<ScriptCompilationDiagnostic> CheckErrors(ScriptEngine engine, string script, string fileName, RunFlagType mode)
{
try
{
PythonEngine.Compile(script, fileName, mode);
}
catch (PythonException e)
{
dynamic error = e.Value;
return new[]
{
new ScriptCompilationDiagnostic
{
Kind = ScriptCompilationDiagnosticKind.Error,
Line = error.lineno - 1,
Column = error.offset - 1,
Message = error.msg,
Code = error.text,
FileName = error.filename,
},
};
}
return new ScriptCompilationDiagnostic[0];
}
second is that you can check if Python is installed on a target machine, with the code like this:
var pythonHome = TryGetFullPathFromPathEnvironmentVariable("python.exe");
private static string? TryGetFullPathFromPathEnvironmentVariable(string fileName)
{
if (fileName.Length >= MAXPATH)
throw new ArgumentException($"The executable name '{fileName}' must have less than {MAXPATH} characters.", nameof(fileName));
var sb = new StringBuilder(fileName, MAXPATH);
return PathFindOnPath(sb, null) ? sb.ToString() : null;
}
[DllImport("shlwapi.dll", CharSet = CharSet.Unicode, SetLastError = false)]
private static extern bool PathFindOnPath([In, Out] StringBuilder pszFile, [In] string[]? ppszOtherDirs);
If your script is using third-party modules, you may check that they're installed as well:
public bool IsModuleInstalled(string module)
{
string moduleDir = Path.Combine(PythonHome, "Lib", "site-packages", module);
return Directory.Exists(moduleDir) && File.Exists(Path.Combine(moduleDir, "__init__.py"));
}
Please note that Python.NET does not officially support the latest Python version 3.9, so alternatively you can distribute and install embedded python with your application from here:
https://www.python.org/ftp/python/3.7.3/
alongside with all required third-party modules as wheels.
We use this approach in our AlterNET Studio product to check if Python is installed for our Python debugger based on Debug Adapter Protocol, and install embedded Python with wheels for our Python.NET based scripter/debugger.
I'm trying to enable the SeDebugPrivilege for my process by using the AdjustTokenPrivileges function, it succeeds but no privileges are enabled and the last error code is ERROR_NOT_ALL_ASSIGNED.
I read the documentation of the function and looked for examples both here and on other sites to understand if I was doing something wrong but I couldn't find any problems.
I checked if my user account has this privilege and it does (the Administrator group does have it and the account is part of the group).
The application is running elevated.
This is the method that I use to enable the privilege:
public static bool EnableRequiredPrivilegesForCurrentProcess()
{
IntPtr TokenHandle = GetCurrentProcessTokenForWriting();
Win32Structures.LUID_AND_ATTRIBUTES[] PrivilegesLUIDs = new Win32Structures.LUID_AND_ATTRIBUTES[1];
if (Win32SecurityFunctions.LookupPrivilegeValue(null, Win32Constants.SE_DEBUG_NAME, out Win32Structures.LUID DebugPrivilegeLUID))
{
Win32Structures.LUID_AND_ATTRIBUTES Attribute = new()
{
Luid = DebugPrivilegeLUID,
Attributes = (uint)Win32Enumerations.PrivilegeLUIDAttributes.SE_PRIVILEGE_ENABLED //0x00000002
};
PrivilegesLUIDs[0] = Attribute;
}
else
{
return false;
}
int StructureSize = Marshal.SizeOf(typeof(Win32Structures.LUID_AND_ATTRIBUTES));
IntPtr LuidAttributesArrayPointer = Marshal.AllocHGlobal(StructureSize * PrivilegesLUIDs.Length);
Marshal.StructureToPtr(PrivilegesLUIDs[0], LuidAttributesArrayPointer, false);
Win32Structures.TOKEN_PRIVILEGES NewPrivileges = new()
{
PrivilegeCount = 1,
Privileges = LuidAttributesArrayPointer
};
if (Win32TokenFunctions.AdjustTokenPrivileges(TokenHandle, false, ref NewPrivileges, 0, IntPtr.Zero, out _))
{
Marshal.DestroyStructure(LuidAttributesArrayPointer, typeof(Win32Structures.LUID_AND_ATTRIBUTES));
Marshal.FreeHGlobal(LuidAttributesArrayPointer);
return true;
}
else
{
Win32OtherFunctions.CloseHandle(TokenHandle);
Marshal.DestroyStructure(LuidAttributesArrayPointer, typeof(Win32Structures.LUID_AND_ATTRIBUTES));
Marshal.FreeHGlobal(LuidAttributesArrayPointer);
return false;
}
}
This is the GetCurrentProcessTokenForWriting() method referenced in the first line:
private static IntPtr GetCurrentProcessTokenForWriting()
{
if (Win32TokenFunctions.OpenProcessToken(Win32OtherFunctions.GetCurrentProcess(), Win32Enumerations.TokenAccessRights.TOKEN_WRITE, out IntPtr TokenHandle))
{
return TokenHandle;
}
else
{
return IntPtr.Zero;
}
}
My application gathers a wide array of info from all processes in the system by calling various functions which require different access rights on the process handle, I require the SeDebugPrivilege to be able to open an handle to a process with PROCESS_ALL_ACCESS access right (if possible).
In my program I try to start a new process (open video file in a default player). This part works OK. Later when I try to close the process (player) I get an error:
System.InvalidOperationException: No process is associated with this object.
My code:
string filename = "747225775.mp4";
var myProc = new Process()
{
StartInfo = new ProcessStartInfo(filename)
};
myProc.Start();
Thread.Sleep(5000);
try
{
myProc.Kill(); //Error is here
}
catch (Exception ex)
{
Debug.WriteLine(ex);
Debugger.Break();
}
What is wrong?
Process.Start associates the Process object with native process handle only when it spawns new process directly. When filename is used as an argument instead of executable name, Process searches registry for association settings via shell32.dll functions and the exact behavior depends on them.
When association is configured in traditional way, to call command line and transfer file name as 1st argument (such as for Notepad), Process.Start spawns new process directly and correctly associates object with native handle. However, when association is configured to execute COM-object (such as for Windows Media Player), Process.Start only creates some RPC query to execute COM object method and returns without associating object with process handle. (The actual process spawn occurs in svchost.exe context, according to my tests)
This issue can be solved by following modified process start method:
using System;
using System.ComponentModel;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
namespace ProcessTest
{
public partial class Form1 : Form
{
[DllImport("Shlwapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern uint AssocQueryString(AssocF flags, AssocStr str, string pszAssoc, string pszExtra, [Out] StringBuilder pszOut, ref uint pcchOut);
/*Modified Process.Start*/
public static Process TrueProcessStart(string filename)
{
ProcessStartInfo psi;
string ext = System.IO.Path.GetExtension(filename);//get extension
var sb = new StringBuilder(500);//buffer for exe file path
uint size = 500;//buffer size
/*Get associated app*/
uint res = AssocQueryString(AssocF.None, AssocStr.Executable, ext,null, sb, ref size);
if (res != 0)
{
Debug.WriteLine("AssocQueryString returned error: " + res.ToString("X"));
psi = new ProcessStartInfo(filename);//can't get app, use standard method
}
else
{
psi = new ProcessStartInfo(sb.ToString(), filename);
}
return Process.Start(psi);//actually start process
}
public Form1()
{
InitializeComponent();
}
private void button2_Click(object sender, EventArgs e)
{
string filename = "c:\\images\\clip.wmv";
var myProc = TrueProcessStart(filename);
if (myProc == null)
{
MessageBox.Show("Process can't be killed");
return;
}
Thread.Sleep(5000);
try
{
myProc.Kill();
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
[Flags]
enum AssocF : uint
{
None = 0,
Init_NoRemapCLSID = 0x1,
Init_ByExeName = 0x2,
Open_ByExeName = 0x2,
Init_DefaultToStar = 0x4,
Init_DefaultToFolder = 0x8,
NoUserSettings = 0x10,
NoTruncate = 0x20,
Verify = 0x40,
RemapRunDll = 0x80,
NoFixUps = 0x100,
IgnoreBaseClass = 0x200,
Init_IgnoreUnknown = 0x400,
Init_FixedProgId = 0x800,
IsProtocol = 0x1000,
InitForFile = 0x2000,
}
enum AssocStr
{
Command = 1,
Executable,
FriendlyDocName,
FriendlyAppName,
NoOpen,
ShellNewValue,
DDECommand,
DDEIfExec,
DDEApplication,
DDETopic,
InfoTip,
QuickTip,
TileInfo,
ContentType,
DefaultIcon,
ShellExtension,
DropTarget,
DelegateExecute,
SupportedUriProtocols,
Max,
}
}
Here we get the file type's associated application via AssocQueryString. The returned value is then passed to ProcessStartInfo. However it does not always work, so we sometimes have to resort to standart method. For example, image files does not have any associated exe, it's just dll being loaded into explorer's process. So we can't outright kill process in this case.
to answer your question: "What is wrong?"
I can say the underline cause of this is related to Windows Apps that are launched to handle the type of file (.mp4).
From what I can determine.. there isn't anything wrong with your code sample except that it doesn't account for this scenario (in which, admittingly, I do not understand why it behaves this way).
To replicate this, I used your code sample and a image file (.png). the program launches with 'Photos' by default.
I changed .png files to launch with Paint application by default, then ran the program again. The code sample you've provided worked fine on the desktop application.
Based on the MSDN documentation of File.Exists, the File.Exists method should return false on any error, including the caller not having access to read the file.
I would expect it to return false both when the file is set to FullControl denied to the user and FullControl denied to the user to the directory the file lives in.
What I'm seeing is when the user has access to the directory, but not the file, File.Exists returns true; however, if the user has no access to the directory, File.Exists returns false.
I wrote a small program that demonstrates what I'm talking about:
using System;
using System.DirectoryServices;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.AccessControl;
using System.Security.Principal;
namespace ConsoleApplication1
{
internal class Program
{
private const string DirName = "TestDir";
private const string FileName = "File.txt";
private const string Password = "Password1";
private const string UserName = "PermissionTestUser";
private static WindowsImpersonationContext Identity = null;
private static IntPtr LogonToken = IntPtr.Zero;
public enum LogonProvider
{
LOGON32_PROVIDER_DEFAULT = 0,
LOGON32_PROVIDER_WINNT35 = 1,
LOGON32_PROVIDER_WINNT40 = 2,
LOGON32_PROVIDER_WINNT50 = 3
};
public enum LogonType
{
LOGON32_LOGON_INTERACTIVE = 2,
LOGON32_LOGON_NETWORK = 3,
LOGON32_LOGON_BATCH = 4,
LOGON32_LOGON_SERVICE = 5,
LOGON32_LOGON_UNLOCK = 7,
LOGON32_LOGON_NETWORK_CLEARTEXT = 8, // Win2K or higher
LOGON32_LOGON_NEW_CREDENTIALS = 9 // Win2K or higher
};
public static void Main(string[] args)
{
string filePath = Path.Combine(DirName, FileName);
try
{
CreateUser();
CreateDir();
CreateFile(filePath);
// grant user full control to the dir
SetAccess(DirName, AccessControlType.Allow);
// deny user full control to the file
SetAccess(filePath, AccessControlType.Deny);
// impersonate user
Impersonate();
Console.WriteLine("File.Exists (with dir permissions): {0}", File.Exists(filePath));
UndoImpersonate();
// deny access to dir
SetAccess(DirName, AccessControlType.Deny);
// impersonate user
Impersonate();
Console.WriteLine("File.Exists (without dir permissions): {0}", File.Exists(filePath));
UndoImpersonate();
}
finally
{
UndoImpersonate();
DeleteDir();
DeleteUser();
}
}
[DllImport("kernel32.dll", CharSet = CharSet.Auto)]
private static extern bool CloseHandle(IntPtr handle);
private static void CreateDir()
{
Directory.CreateDirectory(DirName);
}
private static void CreateFile(string path)
{
File.Create(path).Dispose();
}
private static void CreateUser()
{
DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntry newUser = ad.Children.Add(UserName, "user");
newUser.Invoke("SetPassword", new object[] { Password });
newUser.Invoke("Put", new object[] { "Description", "Test user" });
newUser.CommitChanges();
}
private static void DeleteDir()
{
Directory.Delete(DirName, true);
}
private static void DeleteUser()
{
DirectoryEntry ad = new DirectoryEntry("WinNT://" + Environment.MachineName + ",computer");
DirectoryEntries users = ad.Children;
DirectoryEntry user = users.Find(UserName, "user");
if (user != null)
{
users.Remove(user);
}
}
private static void Impersonate()
{
if (LogonUser(UserName, ".", Password, (int)LogonType.LOGON32_LOGON_INTERACTIVE, (int)LogonProvider.LOGON32_PROVIDER_DEFAULT, ref LogonToken))
{
Identity = WindowsIdentity.Impersonate(LogonToken);
return;
}
}
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LogonUser(string lpszUserName,
string lpszDomain,
string lpszPassword,
int dwLogonType,
int dwLogonProvider,
ref IntPtr phToken);
private static void SetAccess(string path, AccessControlType type)
{
FileSecurity fs = File.GetAccessControl(path);
FileSystemAccessRule far = new FileSystemAccessRule(UserName, FileSystemRights.FullControl, type);
fs.AddAccessRule(far);
File.SetAccessControl(path, fs);
}
private static void UndoImpersonate()
{
if (Identity != null)
{
Identity.Undo();
Identity = null;
}
if (LogonToken != IntPtr.Zero)
{
CloseHandle(LogonToken);
LogonToken = IntPtr.Zero;
}
}
}
}
The result of running this program is:
File.Exists (with dir permissions): True
File.Exists (without dir permissions): False
Can anyone explain why they differ? In both instances, the user doesn't have read access to the file.
That is the default behavior of the File.Exist. According to MSDN:
File.Exist
Return Value Type: System.Boolean
true if the caller has
the required permissions and path contains the name of an existing
file; otherwise, false. This method also returns false if path is
null, an invalid path, or a zero-length string. If the caller does not
have sufficient permissions to read the specified file, no exception
is thrown and the method returns false regardless of the existence of
path.
And additionally
The Exists method should not be used for path validation, this method
merely checks if the file specified in path exists. Passing an invalid
path to Exists returns false.
In other words, the required permission here, is the required permission to know the existence of the file (as the method name implies, File.Exist). And this means that as long as a user has access to the directory, it can know if the file exists or not.
Whether the user has file access or not doesn't affect the user's knowledge of the existence of the file, given the directory permission. But without directory permission, a user cannot know the existence of the file, and thus File.Exist returns false
Edit (after feedback from comments):
And probably the rather confusing part would be the last sentence:
If the caller does not
have sufficient permissions to read the specified file, no exception
is thrown and the method returns false regardless of the existence of
path.
The sufficient permissions to read the specified file is depending on the read-access of the parent directory rather than read-access of the specified file. (Additional comment by Mr. Rob). The word "sufficient" may give some hint about the behavior that it will only depend on read-access to the parent directory is needed, not the read-access to the specified file.
But I admit that the explanation and the choice of word may sound rather counter-intuitive as people may intuitively interpret "sufficient permissions to read the specified file" as the read-access to the specified file rather than to the parent directory.
If the file does exist but File.Exists(filePath) returns false, that means the application has the read permission to neither directory nor file, which can be proved.
if you really want to test if a file exists or not, you can invoke File.GetAccessControl(filePath). If the call returns without exception, it means the file does exist.
I had intended to implement the Manzana.dll library in order to detect iPhone connection events and interact with the device. The problem is that it only seems to work if the client machine has the iTunes dlls and resources installed, which I cannot rely on. Therefore I am trying to use a custom implementation of the Manzana source code to point it's references to the necessary iTunes files that I am including with the project.
Although everything looks ok the compiled library throws a NullReferenceException when used from my application. The application load and initializes ok, but when an iPhone is connected the connectedevent throws an exception.
The actual error is:
System.TypeInitializationException: The type initializer for 'istreamwrapper.MobileDevice' threw an exception. ---> System.NullReferenceException: Object reference not set to an instance of an object.
at istreamwrapper.MobileDevice..cctor()
--- End of inner exception stack trace ---
at istreamwrapper.MobileDevice.AMDeviceNotificationSubscribe(DeviceNotificationCallback callback, UInt32 unused1, UInt32 unused2, UInt32 unused3, Void*& am_device_notification_ptr)
at istreamwrapper.iPhone.doConstruction()
I was able to use that to narrow down the problem to to this method from my iPhone class
private unsafe void doConstruction()
{
void* voidPtr;
this.dnc = new DeviceNotificationCallback(this.NotifyCallback);
this.drn1 = new DeviceRestoreNotificationCallback(this.DfuConnectCallback);
this.drn2 = new DeviceRestoreNotificationCallback(this.RecoveryConnectCallback);
this.drn3 = new DeviceRestoreNotificationCallback(this.DfuDisconnectCallback);
this.drn4 = new DeviceRestoreNotificationCallback(this.RecoveryDisconnectCallback);
int num = MobileDevice.AMDeviceNotificationSubscribe(this.dnc, 0, 0, 0, out voidPtr);
if (num != 0)
{
throw new Exception("AMDeviceNotificationSubscribe failed with error " + num);
}
num = MobileDevice.AMRestoreRegisterForDeviceNotifications(this.drn1, this.drn2, this.drn3, this.drn4, 0, null);
if (num != 0)
{
throw new Exception("AMRestoreRegisterForDeviceNotifications failed with error " + num);
}
this.current_directory = "/";
}
}
The issue comes from
num = MobileDevice.AMDeviceNotificationSubscribe(this.dnc, 0, 0, 0, out voidPtr);
which points to this code which is located in my MobileDevice class
[DllImport("iTunesMobileDevice.dll", CallingConvention = CallingConvention.Cdecl)]
public static unsafe extern int AMDeviceNotificationSubscribe(DeviceNotificationCallback callback, uint unused1, uint unused2, uint unused3, out void* am_device_notification_ptr);
That in turn seems to reference this in it's own class
namespace istreamwrapper
{
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate void DeviceNotificationCallback(ref AMDeviceNotificationCallbackInfo callback_info);
}
which then points to another class with:
namespace istreamwrapper
{
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal struct AMDeviceNotificationCallbackInfo
{
internal unsafe void* dev_ptr;
public NotificationMessage msg;
public unsafe void* dev
{
get
{
return this.dev_ptr;
}
}
}
}
The vast majority of this code was copied straight from the Manzana.dll, the only thing I changed was where the itunesmobiledevice files are located (which is now a set path, rather detected at run time)
Old code:
namespace Manzana
{
internal class MobileDevice
{
private static readonly FileInfo iTunesMobileDeviceFile = new FileInfo(Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Apple Inc.\\Apple Mobile Device Support\\Shared", "iTunesMobileDeviceDLL", (object) "iTunesMobileDevice.dll").ToString());
private static readonly DirectoryInfo ApplicationSupportDirectory = new DirectoryInfo(Registry.GetValue("HKEY_LOCAL_MACHINE\\SOFTWARE\\Apple Inc.\\Apple Application Support", "InstallDir", (object) Environment.CurrentDirectory).ToString());
private const string DLLName = "iTunesMobileDevice.dll";
static MobileDevice()
{
string str = MobileDevice.iTunesMobileDeviceFile.DirectoryName;
if (!MobileDevice.iTunesMobileDeviceFile.Exists)
{
str = Environment.GetFolderPath(Environment.SpecialFolder.CommonProgramFiles) + "\\Apple\\Mobile Device Support\\bin";
if (!File.Exists(str + "\\iTunesMobileDevice.dll"))
str = "C:\\Program Files\\Common Files\\Apple\\Mobile Device Support";
}
Environment.SetEnvironmentVariable("Path", string.Join(";", new string[3]
{
Environment.GetEnvironmentVariable("Path"),
str,
MobileDevice.ApplicationSupportDirectory.FullName
}));
}
New code:
namespace istreamwrapper
{
class MobileDevice
{
static MobileDevice()
{
string str = "[XX_MYPATHHERE_XX]\\Apple\\Mobile Device Support";
string AppSuppDirectory = #"[XX_MYPATHHERE_XX]\Apple\Apple Application Support";
Environment.SetEnvironmentVariable("Path", string.Join(";", new string[3] { Environment.GetEnvironmentVariable("Path"), str, AppSuppDirectory }));
}
Is there something I'm missing that is causing that call to return null? I'll admit I don't fully understand everything that is happening in the above code so it's entirely possible it's something simple.
Yes, I believe the answer was really that the path was incorrect and thus could not find the file. I just didn't realize this because the error it was throwing was too generic.