I have a TeamCity (8.1) build project which runs fine most of the time. A part of my MSBuild script is, that the built executable should be code-signed, and there the build fails sometimes.
<Target Name="AfterBuild">
<Exec Command="sign.exe /D "$(OutputName)" "$(TargetPath)" "certificate.pfx" password" />
</Target>
The sign.exe is a helper command line tool, which internally calls the signtool.exe from the Microsoft Windows SDK (it detects which version is installed) using hard coded timestamp servers in an iteration, because sometimes, a timestamp server is not reachable.
foreach (var tsServer in TimestampServer)
{
var p = new Process
{
StartInfo =
{
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = signtool,
Arguments = String.Format("sign /f \"{0}\" /p {1} {4} /d \"{3}\" \"{2}\"", cert, pass, file, description ?? Path.GetFileName(file), tsServer)
}
};
p.Start();
p.WaitForExit();
if (p.ExitCode == 0)
{
// signing succeeded
Environment.Exit(0);
}
}
// if all failed issue a nonzero exit code
Environment.Exit(-1);
My custom sign.exe only returns a nonzero exit code if signing with all timestamp servers failed (edit: which not happened yet). But TeamCity marks the build as failed, because it detects, that a child process of my sign.exe exited with a nonzero exit code (edit:) even if a later call to signtool.exe was successful and sign.exe returns a zero exit code.
I know the option, to ignore nonzero exit codes, but I only want to ignore the exit codes of the child processes of my sign tool (edit) not the exit code of my sign.exe, because my tool was written exactly for the purpose to handle this issue.
Is there a way within TeamCity to handle this issue, or do I have an option to modify my custom C# command line tool to not propagate the exit codes of the child processes?
I know the option, to ignore nonzero exit codes, but I only want to ignore the exit codes of the child processes of my sign tool.
Are you sure that is a safe thing to do? It is OK in your environment if signing fails, and yet the build is created?
Is there a way within TeamCity to handle this issue, or do I have an option to modify my custom C# command line tool to not propagate the exit codes of the child processes?
I'm not sure about options within TeamCity, but since sign.exe is under your control, you can always have it return 0 whether or not the child process succeeded.
If all time servers occasionally fail, have you investigated potential causes? Perhaps a temporary internet disruption on your end? You could try pinging something highly reliable outside of your network from sign.exe after all time servers fail, to see if there is a general internet disruption.
It might be worth retrying the entire foreach loop one or more times after a delay, to allow for whatever condition causes the occasional failure to self-correct.
Related
I want to have my C# (Xamarin) program run an EXE or batch (BAT) file. The user will be running my program, and will click on one of several buttons, some of which open Web pages and others of which run external programs. These files will be on the same computer as the one running the main program and don't need greater permissions. The overall program will be in Windows, UWP.
I already have code to pull info from the database saying "the button the user clicked references a program and it's (eg) C:\Tools\MyTool.exe". (Real path more like (C:\Users\Me\source\repos\ProductNameV2\ProductName\ProductName.UWP\Assets\EXE\whatever.exe".) I used a "demo.bat" file containing nothing but echo and pause statements, or references to a built-in Windows program like Notepad or Calc that an ordinary command prompt can recognize without an explicit path (ie. that's part of the recognized system Path). Yes, the real path to the dummy file does exist; I checked. I've also explicitly added files demo.bat and dummy.txt to my C# project.
Here's roughly what I've tried so far to actually run a batch file, or an EXE, or just to try opening a text file. Nothing works.
1)
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\demo.bat"); // Returns false.
bool check = await Launcher.CanOpenAsync(#"file:///C:\Tools\dummy.txt"); // Returns true.
await Launcher.OpenAsync(#"file:///C:\Tools\demo.bat") // Seems to do nothing; silently fails.
await Launcher.OpenAsync(#"file:///C:\Tools\dummy.txt") // Same.
2)
Process batchProcess = new Process();
batchProcess.StartInfo.FileName = #"file:///C:\Tools\demo.bat"; // Same result with notepad.exe
batchProcess.StartInfo.WindowStyle = ProcessWindowStyle.Normal;
batchProcess.Start();
batchProcess.WaitForExit();
// Result: "Access is denied" error during Start().
3)
var otherProcessInfo = new ProcessStartInfo(#"file:///C:\Tools\demo.bat")
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
// Result: "The system cannot find the file specified" despite it being the same path as in previous examples.
// Also tried literally using the path C:\Tools\demo.bat, without adding that to the C# project.
// One thing that slightly works is to use:
var otherProcessInfo = new ProcessStartInfo("cmd.exe", "/c echo Hello world!");
// This version opens a window and instantly closes it again. With "/c pause" instead, it opens, saying "press any key to continue".
// Chaining multiple commands with newline or semicolon characters doesn't work as a form of batch file.
So: the only tiny success I've had here is to run cmd.exe, to run a one-line command. I suppose that depending on what the batch file must do, there's some possibility of receiving a string, breaking it into lines, then running cmd.exe using method 3 to call them one at a time. Which is ugly at best.
Is there some better way to do this -- to run a batch file or an EXE from within my program?
EDIT: Yes, I did in fact look at documentation before asking. Why did I use URIs? Because of multiple errors telling me that the simple path strings ("C:\this\that") I was using were in an "Invalid URI format". Using Process.Start("notepad.exe") silently fails, doing nothing. Using a method involving System.Diagnostics.Process (found at How to run external program via a C# program? and yes I saw that before) fails with an error of "Access denied" when using my batch file reference, or silently failing (no window opens) using plain old notepad.exe. I avoided setting Process options that say hide the window.
So to rephrase: Is there a way to make my program run some EXE somewhere on the computer, or to run a batch file that has more than one command in it? What is that way?
Using the data you collected, I was able to run a batch file by doing the following:
var strPathToExeOrBat = System.IO.Path.Combine("C:\\Tools", "demo.bat");
var otherProcessInfo = new ProcessStartInfo("cmd.exe", $"/c call \"{strPathToExeOrBat\"");
var otherProcess = Process.Start(otherProcessInfo);
otherProcess.WaitForExit();
otherProcess.Close();
I also think it would be helpful to review the capabilities of the cmd.exe application.
I found this post to be helpful:
https://stackoverflow.com/questions/515309/what-does-cmd-c-mean#:~:text=%2FC%20Carries%20out%20the%20command%20specified%20by%20the%20string%20and,switches%20by%20typing%20cmd%20%2F%3F%20.
In particular the /k option will leave the window open, if you don't want it to close after running a script.
Thank you very much for your question! It really helped me find the answer to this! (at least for my situation of a .NET MAUI windows app, but MAUI is built off of Xamarin.Forms, so you shouldn't have a problem doing the same thing)
EDIT: Updated to use file path from question and string interpolation with System.IO.Path.Combine for slightly greater cross platform capability
If I start an Electron program that has an argument with a colon, the program immediately exits.
Process process = new Process();
process.StartInfo.FileName = "C:\\Program Files (x86)\\SomeElectronApp.exe";
process.StartInfo.Arguments = "ab:c d";
process.Start();
process.WaitForExit();
Console.WriteLine(1); // break point here
However, if I swap the arguments such that the one with the colon always comes last, then the program starts successfully.
This is mentioned here:
[This] is a security mitigation against an age old windows flaw
Does anyone have insight into what this flaw is?
Update:
Found the source code here:
The function CheckCommandLineArguments has the logic:
else if (IsUrlArg(argv[i])) {
block_args = true;
}
Where IsUrlArg does:
// colon indicates that the argument starts with a URI scheme
if (c == ':') {
// it could also be a Windows filesystem path
if (p == arg + 1)
break;
return true;
}
The "flaw" is most likely unquoted filenames containing whitespace.
Consider
> "C:\Program Files (x86)\SomeElectronApp.exe" "C:\Program Files (x86)\SomeElectronFile.dat"
vs
> "C:\Program Files (x86)\SomeElectronApp.exe" C:\Program Files (x86)\SomeElectronFile.dat
or
> C:\Program Files (x86)\SomeElectronApp.exe C:\Program Files (x86)\SomeElectronFile.dat
If by mistake you have set up a file association as
path\to\exe %1
instead of
path\to\exe "%1"
then every program using the ShellExecute function will pass you an unquoted filename, even if it contains whitespace.
The thing that makes it a "windows" flaw is that on Windows, a single command-line string is passed to the spawned program, which is responsible for breaking it up into the argv array. In contrast most other OSes pass an argument array, so the launching program has to go to some effort to break filenames into multiple words.
Found the answer from looking at the commit.
It was done to fix a remote code execution vulnerability (CVE-2018-1000006). Source:
Affected versions of electron may be susceptible to a remote code execution flaw when certain conditions are met:
The electron application is running on Windows.
The electron application registers as the default handler for a protocol, such as nodeapp://.
This vulnerability is caused by a failure to sanitize additional arguments to chromium in the command line handler for Electron.
blog post here
I'd like to execute 'vsjitdebugger.exe' for some process using the code below:
var myapp = Process.Start(#"path\to\myapp.exe");
Process.Start("vsjitdebugger.exe", $"-p {myapp.Id}").WaitForExit();
But failed.
The exit code is '-1' without showing the window.
What do I have to do in order to execute vsjitdebugger.exe?
UPATE #1
I'd like to attach debugger to the specific process only instantly.
That's why I profer code way instead of registry way.
To execute as administrator:
var myapp = Process.Start(#"path\to\myapp.exe");
Process.Start(new ProcessStartInfo()
{
FileName = #"vsjitdebugger.exe",
Arguments = "-p {myapp.Id}",
Verb = "runas"
}).WaitForExit();
Failed as well after UAC screen.
Lunching the just-in-time debugger through code will not work for you as you must run with admin privileges in order to use it.
What you can do, is start the debugger Automatically on your desired process.
I'd suggest reading more about the JIT of visual studio here as well before trying to execute process through C#.
This is not safe at all and considered bad practice. Unless there is an urgent need I would suggest maxing out all your other possibilities first.
I've got a process (written in C#) that runs with administrative rights and invokes dpinst.exe to perform an automated driver installation. It writes its own custom dpinst.xml file to specify things like suppressing the EULA page, suppressing the wizard, running a "quiet install," and searching in all subdirectories.
When I invoke this manually from the command line (see example below), it seems to work fine. Because of the command line switches I'm using it prints a bunch of INFO level log messages in the console.
C:\Path\To\Drivers> dpinst.exe /C /L 0x409
I want to log what gets printed in the console, so my C# code looks something like this:
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = #"C:\Path\To\Drivers\dpinst.exe",
Arguments = "/C /L 0x409",
UseShellExecute = false,
RedirectStandardOutput = true,
CreateNoWindow = true,
Verb = "runas"
}
};
string output;
process.Start();
using (var reader = process.StandardOutput)
{
output = reader.ReadToEnd();
reader.Close();
}
However, when I run that code, the value of output is always blank. So, for my next experiment, I tried using the command line directly to pipe the output to a file, like this:
C:\Path\To\Drivers> dpinst.exe /C /L 0x409 > test.log 2>&1
That created the test.log file, but it was also blank. Interestingly enough I could still see all of the console output that dpinst.exe generates in that same console window; for some reason it didn't get redirected to the file that I specified. So the symptom is the same regardless of how I invoke the dpinst executable; it doesn't want to redirect output. I'm not sure what the underlying reason for that is, nor how to solve it. How can I capture the console output?
EDIT: If anyone wants a copy of dpinst.exe to run locally and test out themselves, I've provided one at this link, bundled with the dpinst.xml file I'm using. Remember that you need to invoke it with the /C command line switch in order to generate any command line output. Alternatively, if you're paranoid and don't want to download an executable from some random Stack Overflow question, you can get the dpinst.exe utility as part of the Windows Driver Kit from Microsoft. It's a free download, but you have to extract the utility (which is only 500 KB) from the full WDK ISO (which is ~700 MB). Up to you. :)
Your code runs perfectly fine with standard tools (like "ping") for example. So maybe for some reason dpinst writes to standard error instead? You can set RedirectStandardError = true and read StandardError to check if that is the case.
UPDATED in case anyone else will hit this problem:
It seems dpinst does not write to standard output but logs messages to console in some other way. Only way to achieve your goal which comes to my mind is: remember size of "%SystemRoot%\DPINST.LOG" file, run your command, wait for exit, then read everything between remembered position and end of file. This way you will get your logs.
I my answer will help other. When you run Dpinst.exe and you add the swicth /C to dump the log to console, it also creates a log file in this directory "C:\Windows\DPINST.LOG"
You can locate the log file there..
The uninstallers from an application normally launch new process and I am trying to make my application know about it so if after the uninstallation, it will execute some tasks.
NSIS for example.
string uninstallstring = "C:\\Program Files (x86)\\Pidgin\uninstall.exe"; // reg.GetValue("UninstallString").ToString();
Process p = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = uninstallstring
},
EnableRaisingEvents = true
};
p.Start();
p.WaitForExit();
Debug.WriteLine(p.HasExited);
Debug.WriteLine(p.ExitCode);
// clean up TASKS after uninstall here
// clean clean clean
code above will try uninstall Pidgin. It will Start() and it should wait for the uninstaller to finish its job and show in Debug panel True/False (HasExited) as well as its ExitCode.
The problem there is, after Start() is executed, the "uninstall.exe" launched a new application "Au_.exe" in "C:\Users\Jayson\AppData\Local\Temp\~nsu.tmp" and "uninstall.exe" closed, WaitForExit() has done its job and Debug panel shows
True
0
those are "HasExited" and "ExitCode" but the Pidgin Uninstall is still running which tells me that "uninstall.exe" launched the "Au_.exe" as not his child process.
My question is, how do I make sure that the uninstaller finish its job?
If you know name of application, started by unnistaller, you can try to check existence of his procces use Process.GetProcessesByName("Au_.exe") by timer. When procees appeared and then disappeared you can raise callback.
Also you can read about GetExitCodeProcess
I think there is no general solution here. You rely on external component that doesn't work as expected (normally we expect that uninstall.exe will exit only after complete uninstall and will return corresponding error code, but it doesn't).
There are several workarounds:
When uninstall.exe complete, look for Au_.exe process and wait for its completion.
After uninstall.exe (and may be Au_.exe) check is it really uninstall Pidgin. You can check filesystem or some special registry keys. Probably you expect something before your custom cleanup tasks. If it was not uninstalled properly - repeat or don't clean up (may be show some error or write log message - it depends on your application).
Made your custom uninstaller for Pidgin that will cleanup all necessary resources. Pidgin is open-source, so you can look how its installer works. May be it will be enough to simply modify existing installer.
Don't worry about Au_.exe and just cleanup your resources - may be it's really not a problem for your application.
(if possible) Ask user of your application to uninstall Pidgin. When user did it - make some simple checks (e.g. C:/Program Files/Pidgin is deleted) and do your cleanup.