Can Process.Start() take the system PATH into account?
Asked Answered
H

7

9

I've been searching and experimenting for a while with this, but I have had no luck.

I am trying to make a console program to automate some tasks that I couldn't quite do with a BAT file. I want to call "signcode.exe" from the Windows SDK, the bin folder with all the tools in my system PATH, and I can call "signcode" from anywhere, but Process.Start is ignoring the path.

Current code:

System.Diagnostics.Process sign = new System.Diagnostics.Process();
sign.StartInfo.FileName         = signCommand.Substring(0, signCommand.IndexOf(' '));  // signtool.exe
sign.StartInfo.Arguments        = signCommand.Substring(signCommand.IndexOf(' ') + 1); // /sign /a file1 file2

// sign.StartInfo.EnvironmentVariables["Path"] = Environment.GetEnvironmentVariable("PATH");  // This doesn't work either
sign.StartInfo.UseShellExecute              = false;
sign.StartInfo.RedirectStandardOutput       = true;
sign.StartInfo.RedirectStandardError        = true;

sign.Start();  // Throws Win32Exception - The system cannot find the file specified

I've confirmed that StartInfo.EnvironmentVariables["Path"] matches my system path, and contains the Windows SDK folder. Setting it manually doesn't work either.

I've even tried setting TempPath as shown on the MSDN page for EnvironmentVariables Property, but that didn't work either. I wonder why you would be able to set this if it has no effect.

If System.Diagnostics.Process cannot use the path, are there any other functions I could use? I'd like to see the output of the command in my console application as well.

Here is some additional debug values:

Console.WriteLine("Sign Filename = '{0}'", sign.StartInfo.FileName);
Sign Filename = 'signtool.exe'

Console.WriteLine("Sign Arguments = '{0}'", sign.StartInfo.Arguments);
Sign Arguments = '/sign /f C:\Visual Studio\Projects\MGInsight\MGInsight\APPARENTINC.pfx /t http://timestamp.comodoca.com/authenticode "C:\Visual Studio\Projects\MGInsight\MGInsight\Publish\Application Files\\MGInsight_0_9_1_85\MGInsight.exe" "C:\Visual Studio\Projects\MGInsight\MGInsight\Publish\Application Files\\MGInsight_0_9_1_85\XPXScanner.dll" "C:\Visual Studio\Projects\MGInsight\MGInsight\Publish\Application Files\\MGInsight_0_9_1_85\NetworkCalculations.dll"'

Console.WriteLine("Sign Path = '{0}'", sign.StartInfo.EnvironmentVariables["Path"]);
Sign Path = 'C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;"C:\Program Files\Intel\WiFi\bin\";"C:\Program Files\Common Files\Intel\WirelessCommon\";"C:\Program Files (x86)\cwRsync\bin";"C:\Program Files (x86)\Git\cmd";"C:\Program Files (x86)\Git\bin";"C:\Program Files (x86)\Zend\ZendServer\bin";"C:\Program Files (x86)\Zend\ZendServer\share\ZendFramework\bin";"C:\Program Files\Java\jre6\bin";"C:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\IDE\";"C:\Program Files\Microsoft Windows Performance Toolkit\";C:\MinGW\bin;"C:\Program Files (x86)\Microsoft\ILMerge";"C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin";C:\Program Files (x86)\Nmap'

The path "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin" is where signtool.exe is, and I can run it from a command prompt by simply typing signtool, but if I run this application from the same prompt, it doesn't register that path.

Habited answered 3/11, 2011 at 23:35 Comment(7)
"Doesn't work" is no error. What exception do you get? As for the output part of your question, look at StandardOutput.Bratton
The exception and message it throws is commented after sign.Start(), it is Win32Exception.Habited
You're right, I didn't read the code block. Doesn't the Win32Exception come from the sign tool itself? That it can't find the file you want to sign? Try starting it with /h or some other parameter that doesn't require a file.Bratton
@Bratton No worries, I should have made it clearer in the question, I will give that a shot and see what I get, thanks!Habited
I tried "signtool.exe sign /?" but still got the exception instead of help contents output to STDOUT.Habited
Path should not contain any quote characters.Printmaker
Beware when adding a path with Windows 10 - the new list widget will automatically add quotes around your path - if you use the 'Edit text...' button you can remove the quotes.Hutto
C
5

I'm pretty sure Process.Start does respect PATH.

  • Are you sure your signCommand value is correct?
  • Is the directory value in PATH specified using quotes? The docs mention that such values will not be respected.

Note that FileName can also be a full path to the executable.

Certify answered 3/11, 2011 at 23:48 Comment(4)
I want to avoid using the full path to the executable since it may be located in different places. My path does quote it because it is in C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin. I think it must be quoted because there are spaces in the path, I also noticed from the docs, that a FileNotFoundException is thrown if the PATH contains quotes, but I am getting a Win32ExceptionHabited
I don't see any mention of FileNotFoundException... Win32Exception is what it always throws if it can't find the executable. What's weird is that the docs for Process.Start do mention using PATH values, but the Win32 CreateProcess docs explicitly says that the search path is not used, so I'd guess that Process.Start trues to resolve the full name using PATH before passing the value to CreateProcess, and it's probably limited in how it parses/splits PATH.Certify
You could always search PATH manually: string ResolveFullPath (string name) { return fullName = Path.IsPathRooted(name)? name : Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator).Select(dir => Path.Combine(dir.Trim('"'), name)).First(File.Exists); }Certify
I got caught out here using maven from the command line on windows - it was in the path but just using 'mvn' didnt work as it was a batch file - instead you need to use 'mvn.bat'Chiao
A
18

Adding to mhutch's answer: It does indeed take PATH into consideration, but I have noticed you actually need to restart Visual Studio to pick up any path changes. It is kind of sneaky.

Artisan answered 4/10, 2012 at 14:46 Comment(2)
Wow, you just saved me 1 hour (or more) looking for this... :)Loxodrome
Used a couple of hours to try to figure this out, until i stumbled upon this solution. Wow :)Maines
C
5

I'm pretty sure Process.Start does respect PATH.

  • Are you sure your signCommand value is correct?
  • Is the directory value in PATH specified using quotes? The docs mention that such values will not be respected.

Note that FileName can also be a full path to the executable.

Certify answered 3/11, 2011 at 23:48 Comment(4)
I want to avoid using the full path to the executable since it may be located in different places. My path does quote it because it is in C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin. I think it must be quoted because there are spaces in the path, I also noticed from the docs, that a FileNotFoundException is thrown if the PATH contains quotes, but I am getting a Win32ExceptionHabited
I don't see any mention of FileNotFoundException... Win32Exception is what it always throws if it can't find the executable. What's weird is that the docs for Process.Start do mention using PATH values, but the Win32 CreateProcess docs explicitly says that the search path is not used, so I'd guess that Process.Start trues to resolve the full name using PATH before passing the value to CreateProcess, and it's probably limited in how it parses/splits PATH.Certify
You could always search PATH manually: string ResolveFullPath (string name) { return fullName = Path.IsPathRooted(name)? name : Environment.GetEnvironmentVariable("PATH").Split(Path.PathSeparator).Select(dir => Path.Combine(dir.Trim('"'), name)).First(File.Exists); }Certify
I got caught out here using maven from the command line on windows - it was in the path but just using 'mvn' didnt work as it was a batch file - instead you need to use 'mvn.bat'Chiao
V
4

If you updated PATH recently, be sure to restart Visual Studio. Environment variables are loaded at the launch of Visual Studio. Note that this applies to DEBUG mode execution.

Voltmer answered 7/11, 2012 at 22:9 Comment(0)
H
2

Well, I guess the problem was related to what mhutch said, from the MSDN docs:

If you have a path variable declared in your system using quotes, 
you must fully qualify that path when starting any process found 
in that location. Otherwise, the system will not find the path. For
example, if c:\mypath is not in your path, and you add it using
quotation marks: path = %path%;"c:\mypath", you must fully qualify
any process in c:\mypath when starting it.

I saw that initially, but it seemed strange so I disregard it. Not sure why that's the case but it seems to be.

I tried copying signtool.exe to C:\sign\tool\bin and added that to my path, and then my code worked, so I guess because I have quotes in that path due to the spaces, I am SOL and will have to manually search the path for the windows SDK path unless there is some way to add something with spaces to the path without using quotes.

Habited answered 4/11, 2011 at 0:22 Comment(0)
P
1

I believe you're looking for the ProcessStartInfo.WorkingDirectory property.

Promotive answered 3/11, 2011 at 23:45 Comment(1)
I don't want to change the working directory since I will run this application from a certain folder and reference files relative to this path and would like that to be the working directory, but I'd prefer not to have to specify the full path to signtool.exe.Habited
T
1

The StartInfo filename is actually the full path to the executable

For example, on a wrapper I have for x264, it looks like this:

x264Start.FileName = Directory.GetCurrentDirectory() + @"\Tools\x264\x264x86-r1995.exe";

I'd sense-check the code by adding a try {}, catch {} in there and writing the actual filename you are trying to call into the debug if you have an error.

Otherwise add something like the below:

    if (File.exists(sign.FileName))
    {
        sign.Start();
    }
    else
    {
        Console.WriteLine("Can't find {0}", sign.FileName);
        throw new Exception("File doesn't exist");
    }

EDIT: Added a full example - this searches for CCleaner and then runs it with the "/AUTO" switch. Just tested and works fine.

        // detect if 64-bit system
        string programFiles = "";
        if (Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE").Contains("64"))
        {
            Console.WriteLine("#info# x64 detected");
            programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles);
        }
        else
        {
            Console.WriteLine("#info# x86 detected");
            programFiles = Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86);
        }

        // search
        string[] dirs = Directory.GetDirectories(programFiles, "CCleaner", SearchOption.AllDirectories);
        string[] exes = Directory.GetFiles(programFiles, "CCleaner64.exe", SearchOption.AllDirectories);

        //debug only
        foreach (string s in dirs)
        {
            Console.WriteLine(s);
        }

        foreach (string s in exes)
        {
            Console.WriteLine(s);
        }

        // access directly
        ProcessStartInfo CCleaner = new ProcessStartInfo(exes[0], "/AUTO");
        Process.Start(CCleaner);
Trichinosis answered 3/11, 2011 at 23:47 Comment(0)
T
0

Your code does seem to take the path into account for me.

It's hard to say what might be wrong in your case, but you might try running your command through cmd.exe:

sign.StartInfo.FileName = "cmd";
sign.StartInfo.Arguments = "/c signtool.exe ...";
Tiemannite answered 3/11, 2011 at 23:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.