I have a C# console application that I use as an SVN Pre Commit Hook. The console app is started perfectly. However, as soon as I try to do a Write using SharpSvn, I get this error:
Commit failed (details follow):
Commit blocked by pre-commit hook (exit code -1066598274) with output:
Unhandled Exception: System.Runtime.InteropServices.SEHException: External
component has thrown an exception.
at svn_client_cat2(svn_stream_t* , SByte* , svn_opt_revision_t* ,
svn_opt_revision_t* , svn_client_ctx_t* , apr_pool_t* )
at SharpSvn.SvnClient.Write(SvnTarget target, Stream output, SvnWriteArgs args)
at SharpSvn.SvnClient.Write(SvnTarget target, Stream output)
at SvnPreCommitHook.Program.Main(String[] args)
I have tried to do the svn.Write command from my own machine, pointing to svn://svn-server instead of localhost - and that works fine. I guess it is something on the server. TortoiseSVN is installed, although I don't see any context menus...
My code looks like this:
private static EventLog _serviceEventLog;
static void Main(string[] args)
{
_serviceEventLog = new EventLog();
if (!System.Diagnostics.EventLog.SourceExists("Svn Hooks"))
{
System.Diagnostics.EventLog.CreateEventSource("Svn Hooks", "Svn Hooks");
}
_serviceEventLog.Source = "Svn Hooks";
_serviceEventLog.Log = "Svn Hooks";
SvnHookArguments ha;
if (!SvnHookArguments.ParseHookArguments(args, SvnHookType.PreCommit, false, out ha))
{
/*Console.Error.WriteLine("Invalid arguments");
Environment.Exit(1);*/
}
using (SvnLookClient cl = new SvnLookClient())
{
SvnChangeInfoEventArgs ci;
cl.GetChangeInfo(ha.LookOrigin, out ci);
if (!ci.LogMessage.Equals("Svn Hook Test"))
{
AllowCommit();
return;
}
var checkoutDir = #"C:\SvnTemp\" + DateTime.Now.Ticks.ToString();
foreach (SvnChangeItem i in ci.ChangedPaths)
{
var checkoutFilepath = checkoutDir + "\\" + Path.GetFileName(i.Path);
if (!Directory.Exists(checkoutDir))
{
Directory.CreateDirectory(checkoutDir);
}
using (SvnClient svn = new SvnClient())
{
using (StreamWriter sw = new StreamWriter(checkoutFilepath))
{
svn.Write(SvnTarget.FromString("svn://localhost/" + i.RepositoryPath), sw.BaseStream);
}
}
var fileContents = File.ReadAllText(checkoutFilepath);
if (fileContents.Contains("Martin Normark"))
{
RemoveTempDirectory(checkoutDir);
PreventCommit("Name is not allowed!");
}
}
RemoveTempDirectory(checkoutDir);
}
AllowCommit();
}
Maybe one of the following:
64bit vs 32 bit
vcredist missing on the server
Maybe you should first catch the thrown exception by using the HandleProcessCorruptedStateExceptionsAttribute:
[HandleProcessCorruptedStateExceptions]
static void Main() // main entry point
{
try
{
}
catch (Exception ex)
{
// Handle Exception here ...
}
}
Related
I have an issue with my (visual studio) pre-build action calling an executable, which requires a console input.
The code looks like:
using System;
using System.Diagnostics;
using System.Text;
namespace MyMinumumExample
{
internal class MinimumExample
{
private StringBuilder stdOutput = null;
private readonly string assemblyFilePath;
private readonly Process gitProcess = new Process()
{
StartInfo = new ProcessStartInfo()
{
FileName = "git",
RedirectStandardOutput = true,
UseShellExecute = false,
},
};
public MinimumExample(string path)
{
assemblyFilePath = path;
}
private static int Main(string[] args)
{
string path = args[0];
var program = new MinimumExample(path);
if (program.CheckIfFileIsDirty(path))
{
Console.WriteLine("Changes will be discarded after compiling. Do you want to proceed?");
while (true)
{
Console.Write("[y/n]: ");
ConsoleKeyInfo key = Console.ReadKey();
if (key.KeyChar == 'y')
break;
if (key.KeyChar == 'n')
return 1;
Console.WriteLine();
}
}
return 0;
}
private bool CheckIfFileIsDirty(string path)
{
string gitCmdArgs = string.Format("diff --shortstat -- {0}", path);
stdOutput = new StringBuilder();
gitProcess.StartInfo.Arguments = gitCmdArgs;
gitProcess.Start();
gitProcess.BeginOutputReadLine();
gitProcess.OutputDataReceived += GitProcessOutputHandler;
gitProcess.WaitForExit();
gitProcess.OutputDataReceived -= GitProcessOutputHandler;
if (gitProcess.ExitCode != 0) throw new Exception(string.Format("Process 'git {0}' failed with code {1}", gitCmdArgs, gitProcess.ExitCode));
return !string.IsNullOrWhiteSpace(stdOutput.ToString());
}
private void GitProcessOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
if (!string.IsNullOrWhiteSpace(outLine.Data))
{
stdOutput.Append(outLine.Data);
}
}
}
}
If I run this from cmd-shell or from a batch file everything works fine. If I put it into the pre build action, I get an exeption in line 38:
ConsoleKeyInfo key = Console.ReadKey();
The pre build action looks like:
call $(SolutionDir)MinimumExample.exe $(ProjectDir)dirtyExampleFile.cs
IF %ERRORLEVEL% GTR 0 (
EXIT 1
)
If I call the executable with start <exe> the example works, but I don't get the exit code of the exe (but probably of the new cmd shell).
Either, I can get the exit code of cmd shell to verify that the executable did exit cleanly, or I find a way to read keyboard input from the build event console.
Does anyone has an idea, how to solve this?
Best regards and thanks in advance!
I found the solution to get the exit code:
Using start with /wait option solved my problem.
PreBuildEvent is now:
start "Update version of $(ProjectName)" /wait $(SolutionDir)MinimumExample.exe $(ProjectDir)dirtyExampleFile.cs
I know how to prevent running multiple instances of a given app on Windows:
Prevent multiple instances of a given app in .NET?
This code does not work under Linux using mono-develop though. It compiles and runs but it does not work. How can I prevent it under Linux using mono?
This is what I have tried but the code deos not work under linux only on windows.
static void Main()
{
Task.Factory.StartNew(() =>
{
try
{
var p = new NamedPipeServerStream("SomeGuid", PipeDirection.In, 1);
Console.WriteLine("Waiting for connection");
p.WaitForConnection();
}
catch
{
Console.WriteLine("Error another insance already running");
Environment.Exit(1); // terminate application
}
});
Thread.Sleep(1000);
Console.WriteLine("Doing work");
// Do work....
Thread.Sleep(10000);
}
I came up with this answer. Call this method passing it a unique ID
public static void PreventMultipleInstance(string applicationId)
{
// Under Windows this is:
// C:\Users\SomeUser\AppData\Local\Temp\
// Linux this is:
// /tmp/
var temporaryDirectory = Path.GetTempPath();
// Application ID (Make sure this guid is different accross your different applications!
var applicationGuid = applicationId + ".process-lock";
// file that will serve as our lock
var fileFulePath = Path.Combine(temporaryDirectory, applicationGuid);
try
{
// Prevents other processes from reading from or writing to this file
var _InstanceLock = new FileStream(fileFulePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
_InstanceLock.Lock(0, 0);
MonoApp.Logger.LogToDisk(LogType.Notification, "04ZH-EQP0", "Aquired Lock", fileFulePath);
// todo investigate why we need a reference to file stream. Without this GC releases the lock!
System.Timers.Timer t = new System.Timers.Timer()
{
Interval = 500000,
Enabled = true,
};
t.Elapsed += (a, b) =>
{
try
{
_InstanceLock.Lock(0, 0);
}
catch
{
MonoApp.Logger.Log(LogType.Error, "AOI7-QMCT", "Unable to lock file");
}
};
t.Start();
}
catch
{
// Terminate application because another instance with this ID is running
Environment.Exit(102534);
}
}
I am interested in how to inforce a single instance policy for dotnetcore console apps. To my surprise it seems like there isn't much out there on the topic. I found this one stacko, How to restrict a program to a single instance, but it doesnt seem to work for me on dotnetcore with ubuntu. Anyone here do this before?
Variation of #MusuNaji's solution at: How to restrict a program to a single instance
private static bool AlreadyRunning()
{
Process[] processes = Process.GetProcesses();
Process currentProc = Process.GetCurrentProcess();
logger.LogDebug("Current proccess: {0}", currentProc.ProcessName);
foreach (Process process in processes)
{
if (currentProc.ProcessName == process.ProcessName && currentProc.Id != process.Id)
{
logger.LogInformation("Another instance of this process is already running: {pid}", process.Id);
return true;
}
}
return false;
}
This is a little more difficult on .NET core than it should be, due to the problem of mutex checking on Linux/MacOS (as reported above). Also Theyouthis's solution isn't helpful as all .NET core apps are run via the CLI which has a process name of 'dotnet' which if you are running multiple .NET core apps on the same machine the duplicate instance check will trigger incorrectly.
A simple way to do this that is also multi-platform robust is to open a file for write when the application starts, and close it at the end. If the file fails to open it is due to another instance running concurrently and you can handle that in the try/catch. Using FileStream to open the file will also create it if it doesn't first exist.
try
{
lockFile = File.OpenWrite("SingleInstance.lck");
}
catch (Exception)
{
Console.WriteLine("ERROR - Server is already running. End that instance before re-running. Exiting in 5 seconds...");
System.Threading.Thread.Sleep(5000);
return;
}
Here is my implementation using Named pipes. It supports passing arguments from the second instance.
Note: I did not test on Linux or Mac but it should work in theory.
Usage
public static int Main(string[] args)
{
instanceManager = new SingleInstanceManager("8A3B7DE2-6AB4-4983-BBC0-DF985AB56703");
if (!instanceManager.Start())
{
return 0; // exit, if same app is running
}
instanceManager.SecondInstanceLaunched += InstanceManager_SecondInstanceLaunched;
// Initialize app. Below is an example in WPF.
app = new App();
app.InitializeComponent();
return app.Run();
}
private static void InstanceManager_SecondInstanceLaunched(object sender, SecondInstanceLaunchedEventArgs e)
{
app.Dispatcher.Invoke(() => new MainWindow().Show());
}
Your Copy-and-paste code
public class SingleInstanceManager
{
private readonly string applicationId;
public SingleInstanceManager(string applicationId)
{
this.applicationId = applicationId;
}
/// <summary>
/// Detect if this is the first instance. If it is, start a named pipe server to listen for subsequent instances. Otherwise, send <see cref="Environment.GetCommandLineArgs()"/> to the first instance.
/// </summary>
/// <returns>True if this is tthe first instance. Otherwise, false.</returns>
public bool Start()
{
using var client = new NamedPipeClientStream(applicationId);
try
{
client.Connect(0);
}
catch (TimeoutException)
{
Task.Run(() => StartListeningServer());
return true;
}
var args = Environment.GetCommandLineArgs();
using (var writer = new BinaryWriter(client, Encoding.UTF8))
{
writer.Write(args.Length);
for (int i = 0; i < args.Length; i++)
{
writer.Write(args[i]);
}
}
return false;
}
private void StartListeningServer()
{
var server = new NamedPipeServerStream(applicationId);
server.WaitForConnection();
using (var reader = new BinaryReader(server, Encoding.UTF8))
{
var argc = reader.ReadInt32();
var args = new string[argc];
for (int i = 0; i < argc; i++)
{
args[i] = reader.ReadString();
}
SecondInstanceLaunched?.Invoke(this, new SecondInstanceLaunchedEventArgs { Arguments = args });
}
StartListeningServer();
}
public event EventHandler<SecondInstanceLaunchedEventArgs> SecondInstanceLaunched;
}
public class SecondInstanceLaunchedEventArgs
{
public string[] Arguments { get; set; }
}
Unit test
[TestClass]
public class SingleInstanceManagerTests
{
[TestMethod]
public void SingleInstanceManagerTest()
{
var id = Guid.NewGuid().ToString();
var manager = new SingleInstanceManager(id);
string[] receivedArguments = null;
var correctArgCount = Environment.GetCommandLineArgs().Length;
manager.SecondInstanceLaunched += (sender, e) => receivedArguments = e.Arguments;
var instance1 = manager.Start();
Thread.Sleep(200);
var manager2 = new SingleInstanceManager(id);
Assert.IsFalse(manager2.Start());
Thread.Sleep(200);
Assert.IsTrue(instance1);
Assert.IsNotNull(receivedArguments);
Assert.AreEqual(correctArgCount, receivedArguments.Length);
var receivedArguments2 = receivedArguments;
var manager3 = new SingleInstanceManager(id);
Thread.Sleep(200);
Assert.IsFalse(manager3.Start());
Assert.AreNotSame(receivedArguments, receivedArguments2);
Assert.AreEqual(correctArgCount, receivedArguments.Length);
}
}
The downside of deandob's solution is that one can launch the application from another path. So you may prefer some static path or a tmp path for all users.
Here is my attempt:
//second instance launch guard
var tempPath = Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Machine)
??
Path.GetTempPath();
var lockPath = Path.Combine(tempPath, "SingleInstance.lock");
await using var lockFile = File.OpenWrite(lockPath);
here I'm trying to get TEMP system variable at the scope of machine (not the user TEMP) and if its empty - fallback to the user's temp folder on windows or shared /tmp on some linuxes.
im trying to use pipe via .net v4.0 on winforms application background
heres my code -->
public void main()
{
using (NamedPipeServerStream pipeserver = new NamedPipeServerStream("colors", PipeDirection.Out,4))
{
pipeserver.WaitForConnection();
try
{
using (StreamReader sr = new StreamReader(pipeserver))
{
string data = sr.ReadLine();
ConvertToGuiColor(data);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
trying to understand why Exception "All instances Pipe Are busy"
thought maybe because
pipeserver.WaitForConnection();
,but the Exception is throwing in this :
"using (NamedPipeServerStream pipeserver = new NamedPipeServerStream("colors", PipeDirection.Out,4))"
command
ty for helpers
sKY Walker
I create a Windows Service project in VS2010 that contains multiple services. I tried to cull together a way to install it without a complex installer. But, it seems to rollback and not work.
Here's Program.cs:
static class Program
{
static void Main(string[] args)
{
bool install = false, uninstall = false, console = false;
WindowsServiceInstaller inst = new WindowsServiceInstaller();
if (args.Length > 0)
{
foreach (string arg in args)
{
switch (arg)
{
case "-i":
case "-install":
install = true;
break;
case "-u":
case "-uninstall":
uninstall = true;
break;
case "-c":
case "-console":
console = true;
break;
default:
Console.Error.WriteLine("Argument not expected: " + arg);
break;
}
}
}
if (uninstall)
{
inst.InstallServices(false, args);
}
if (install)
{
inst.InstallServices(true, args);
}
ServiceBase[] ServicesToRun;
ServicesToRun = new ServiceBase[]
{
// scans Email table for outbound email jobs; uses multiple threads to lock and work on data in Email table
new EmailLogScanner()
// generates email digest of document status on secheduled basis; single thread
, new EmailStatusDigester()
// keeps Fax table and third-party fax service accounts synchronized; uses a fixed nb of threads, one thread syncs one account at a time
, new FaxSynchronizer()
};
if (console)
{
foreach (IDebuggableService srv in ServicesToRun)
{
string[] strs = new string[] { String.Empty };
srv.DebugStart(strs);
}
Console.WriteLine("Press any key to terminate...");
Console.ReadKey();
foreach (IDebuggableService srv in ServicesToRun)
{
srv.DebugStop();
}
Console.WriteLine("Service has exited.");
}
else
{
ServiceBase.Run(ServicesToRun);
}
}
}
Here's WindowsServiceInstaller.cs:
[RunInstaller(true)]
public class WindowsServiceInstaller : Installer
{
public WindowsServiceInstaller()
{
ServiceProcessInstaller serviceProcessInstaller = new ServiceProcessInstaller();
serviceProcessInstaller.Account = ServiceAccount.NetworkService;
serviceProcessInstaller.Username = null;
serviceProcessInstaller.Password = null;
Installers.Add(serviceProcessInstaller);
ServiceInstaller emailLogScannerInstaller = new ServiceInstaller();
emailLogScannerInstaller.DisplayName = "Email Scanner";
emailLogScannerInstaller.StartType = ServiceStartMode.Automatic;
emailLogScannerInstaller.ServiceName = "EmailLogScanner"; // must match the ServiceBase ServiceName property
emailLogScannerInstaller.Description = "Scan for and sends out pending emails in stack.";
Installers.Add(emailLogScannerInstaller);
ServiceInstaller emailStatusDigesterInstaller = new ServiceInstaller();
emailStatusDigesterInstaller.DisplayName = "Status Digester";
emailStatusDigesterInstaller.StartType = ServiceStartMode.Automatic;
emailStatusDigesterInstaller.ServiceName = "EmailDigester";
emailStatusDigesterInstaller.Description = "Prepares document status email digests.";
Installers.Add(emailStatusDigesterInstaller);
ServiceInstaller faxSynchronizerInstaller = new ServiceInstaller();
faxSynchronizerInstaller.DisplayName = "Fax Synchronizer";
faxSynchronizerInstaller.StartType = ServiceStartMode.Automatic;
faxSynchronizerInstaller.ServiceName = "FaxSynchronizer";
faxSynchronizerInstaller.Description = "Synchronizes database with external fax service(s).";
Installers.Add(faxSynchronizerInstaller);
}
public void InstallServices(bool doInstall, string[] args)
{
try
{
using (AssemblyInstaller aInstaller = new AssemblyInstaller(typeof(Program).Assembly, args))
{
IDictionary state = new Hashtable();
aInstaller.UseNewContext = true;
try
{
if (doInstall)
{
aInstaller.Install(state);
aInstaller.Commit(state);
}
else
{
aInstaller.Uninstall(state);
}
}
catch
{
try
{
aInstaller.Rollback(state);
}
catch { }
throw;
}
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex.Message);
}
}
}
The logged output (when I run daemon.exe -i in a command window, as admin) shows the text below. Also, I get the "cannot start service from the command line" dialog:
Installing assembly 'C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.exe'.
Affected parameters are:
i =
assemblypath = C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.exe
logfile = C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.InstallLog
Installing service EmailLogScanner...
Service EmailLogScanner has been successfully installed.
Creating EventLog source EmailLogScanner in log Application...
See the contents of the log file for the C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.exe assembly's progress.
The file is located at C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.InstallLog.
Rolling back assembly 'C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.exe'.
Affected parameters are:
logtoconsole =
i =
assemblypath = C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.exe
logfile = C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.InstallLog
Restoring event log to previous state for source EmailLogScanner.
Service EmailLogScanner is being removed from the system...
Service EmailLogScanner was successfully removed from the system.
UPDATE: When I comment out the try...catch block around the 'aInstaller.Install(state)' line, I get a slightly different output:
Installing assembly 'C:\Users\xxx\Documents\~Business\Projects\Daemon\bin\Release\Daemon.exe'.
Affected parameters are:
i =
assemblypath = C:\Users\xxx\Documents\~Business\Projects\Da
emon\bin\Release\Daemon.exe
logfile = C:\Users\xxx\Documents\~Business\Projects\Daemon\
bin\Release\Daemon.InstallLog
Installing service EmailLogScanner...
Creating EventLog source EmailLogScanner in log Application...
Source EmailLogScanner already exists on the local computer.
Is it because I already have Event Log sources setup? If so, how do I skip that step in the AssemblyInstaller? If not, wha? :)
You should put this
if (EventLog.SourceExists("YourEventSourceName"))
EventLog.DeleteEventSource("YourEventSourceName");
when service installation begins.