Last active
June 11, 2023 19:59
-
-
Save saper-2/13a48ca50b90bcf940731c85ecd6ad9c to your computer and use it in GitHub Desktop.
Test code for checking if pageant or alike is running (C# .netFramework 4.7.2 console application)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.Linq; | |
using System.Runtime.CompilerServices; | |
using System.Runtime.InteropServices; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace ListPipes | |
{ | |
class Program | |
{ | |
[StructLayout(LayoutKind.Sequential)] | |
private struct FILETIME | |
{ | |
public uint dwLowDateTime; | |
public uint dwHighDateTime; | |
} | |
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] | |
private struct WIN32_FIND_DATA | |
{ | |
public uint dwFileAttributes; | |
public FILETIME ftCreationTime; | |
public FILETIME ftLastAccessTime; | |
public FILETIME ftLastWriteTime; | |
public uint nFileSizeHigh; | |
public uint nFileSizeLow; | |
public uint dwReserved0; | |
public uint dwReserved1; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] | |
public string cFileName; | |
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] | |
public string cAlternateFileName; | |
} | |
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] | |
private static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData); | |
[DllImport("kernel32.dll", CharSet = CharSet.Unicode)] | |
private static extern int FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData); | |
[DllImport("kernel32.dll")] | |
private static extern bool FindClose(IntPtr hFindFile); | |
[return: MarshalAs(UnmanagedType.Bool)] | |
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
private static extern bool WaitNamedPipe(string name, int timeout); | |
/// <summary> | |
/// Provides an indication if the named pipe exists. | |
/// This has to prove the pipe does not exist. If there is any doubt, this | |
/// returns that it does exist and it is up to the caller to attempt to connect | |
/// to that server. The means that there is a wide variety of errors that can occur that | |
/// will be ignored - for example, a pipe name that contains invalid characters will result | |
/// in a return value of false. | |
/// | |
/// </summary> | |
/// <param name="pipeName">The pipe to connect to</param> | |
/// <returns>false if it can be proven it does not exist, otherwise true</returns> | |
/// <remarks> | |
/// Attempts to check if the pipe server exists without incurring the cost | |
/// of calling NamedPipeClientStream.Connect. This is because Connect either | |
/// times out and throws an exception or goes into a tight spin loop burning | |
/// up cpu cycles if the server does not exist. | |
/// | |
/// Common Error codes from WinError.h | |
/// ERROR_FILE_NOT_FOUND 2L | |
/// ERROR_BROKEN_PIPE = 109 (0x6d) | |
/// ERROR_BAD_PATHNAME 161L The specified path is invalid. | |
/// ERROR_BAD_PIPE = 230 (0xe6) The pipe state is invalid. | |
/// ERROR_PIPE_BUSY = 231 (0xe7) All pipe instances are busy. | |
/// ERROR_NO_DATA = 232 (0xe8) the pipe is being closed | |
/// ERROR_PIPE_NOT_CONNECTED 233L No process is on the other end of the pipe. | |
/// ERROR_PIPE_CONNECTED 535L There is a process on other end of the pipe. | |
/// ERROR_PIPE_LISTENING 536L Waiting for a process to open the other end of the pipe. | |
/// | |
/// </remarks> | |
static public bool NamedPipeExist(string pipeName) | |
{ | |
try | |
{ | |
int timeout = 0; | |
string normalizedPath = System.IO.Path.GetFullPath(string.Format(@"\\.\pipe\{0}", pipeName)); | |
bool exists = WaitNamedPipe(normalizedPath, timeout); | |
if (exists) | |
{ | |
return true; | |
} | |
else | |
{ | |
int error = Marshal.GetLastWin32Error(); | |
if (error == 0) // pipe does not exist | |
return false; | |
else if (error == 2) // win32 error code for file not found | |
return false; | |
// all other errors indicate other issues | |
return false; | |
} | |
} | |
catch (Exception ex) | |
{ | |
throw new Exception("Failure in WaitNamedPipe()", ex); | |
} | |
} | |
[DllImport("user32.dll")] | |
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName); | |
public static IntPtr GetHandleWindow(string title) | |
{ | |
return FindWindow(null, title); | |
} | |
public static string NamedPipeExistsPartial(string partialname) | |
{ | |
WIN32_FIND_DATA data; | |
IntPtr handle = FindFirstFile(@"\\.\pipe\*", out data); | |
if (handle != new IntPtr(-1)) | |
{ | |
do | |
{ | |
if (data.cFileName.Contains(partialname)) return data.cFileName; | |
} while (FindNextFile(handle, out data) != 0); | |
FindClose(handle); | |
} | |
return string.Empty; | |
} | |
//http://pinvoke.net/default.aspx/user32/GetWindowThreadProcessId.html | |
[DllImport("user32")] | |
//private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); | |
private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out IntPtr lpdwProcessId); | |
//http://pinvoke.net/default.aspx/kernel32/GetModuleFileName.html | |
[DllImport("kernel32.dll", SetLastError = true)] | |
[PreserveSig] | |
public static extern uint GetModuleFileName([In] IntPtr hModule, [Out] StringBuilder lpFilename, [In][MarshalAs(UnmanagedType.U4)] int nSize); | |
static void Main(string[] args) | |
{ | |
/* | |
Analyzing pageant code show that it's check 2 things: | |
1. It looking for a pipe if exists: \\.\pipe\pageant.<username>.<hashed_string("Pageant")> (function: bool named_pipe_agent_exists(void)@agent-client.c:134 | |
2. It's checking if window exists [lpClassName="Pageant",lpWindowName="Pageant"] (function: wm_copydata_agent_exists(void)@agent-clcient.c:15) | |
If any of above exists then it throw in user face message that Pageant is already running. | |
As for gpg-agent that have `enable-putty-support` option enabled (in config file `%appdata%\gnupg\gpg-agent.conf`). gpg-agent create: | |
1. window "Pageant" - like pageant | |
2. create pipe: \\.\pipe\openssh-ssh-agent - this is created only if `enable-win32-openssh-support` is present in `gpg-agent.conf` file. | |
I think best would be to look for "Pageant" window to check if pagenat/or/similar is running. | |
*/ | |
Console.WriteLine("Listing all available named pipes in system..."); | |
Console.WriteLine("----------------------------------------"); | |
int cnt = 0, sshcnt=0, pgncnt=0, puttycnt=0; | |
WIN32_FIND_DATA data; | |
IntPtr handle = FindFirstFile(@"\\.\pipe\*", out data); | |
if (handle != new IntPtr(-1)) | |
{ | |
do | |
{ | |
Console.Write(data.cFileName); | |
if (data.cFileName.Contains("ssh")) | |
{ | |
Console.WriteLine(" -- <<<<< SSH word is in this pipe --"); | |
sshcnt++; | |
} | |
else if (data.cFileName.Contains("pageant")) | |
{ | |
Console.WriteLine(" -- <<<<< pageant word is in this pipe --"); | |
pgncnt++; | |
} | |
else if (data.cFileName.Contains("putty")) | |
{ | |
Console.WriteLine(" -- <<<<< putty word is in this pipe --"); | |
puttycnt++; | |
} | |
else | |
{ | |
Console.WriteLine(); | |
} | |
cnt++; | |
} while (FindNextFile(handle, out data) != 0); | |
FindClose(handle); | |
} | |
Console.WriteLine("----------------------------------------"); | |
Console.WriteLine("Named pipes found: " + cnt.ToString()); | |
Console.WriteLine("Named pipes found with SSH word: " + sshcnt.ToString()); | |
Console.WriteLine("Named pipes found with pageant word: " + pgncnt.ToString()); | |
Console.WriteLine("Named pipes found with putty word: " + puttycnt.ToString()); | |
Console.WriteLine("----------------------------------------"); | |
Console.Write("Checking for pipe (openssh-ssh-agent): "); | |
Console.WriteLine(NamedPipeExist("openssh-ssh-agent") ? " EXISTS!!!" : " none."); | |
// check for pageant pipe (pagenat v0.78 support this already) | |
string user = Environment.UserName; | |
Console.Write("Checking for pipe (pageant." + user+".###..##): "); | |
string pagpname = NamedPipeExistsPartial("pageant." + user + "."); | |
Console.WriteLine(pagpname != string.Empty ? " EXISTS!!! [" + pagpname + "]" : " none."); | |
// check if pagent window (lpWindowName="Pagenat" (& lpClassName="Pageant")) exists somewhere | |
Console.Write("Checking for Pageant window: "); | |
IntPtr h = GetHandleWindow("Pageant"); | |
if (h != IntPtr.Zero) { | |
// window was found... | |
Console.Write("found(hwnd="+h.ToString()+")! created by process "); | |
// get process for window handle | |
IntPtr procid = IntPtr.Zero; | |
uint thrid = 0; // thread id | |
thrid = GetWindowThreadProcessId(h, out procid); | |
if (thrid != 0) | |
{ | |
// got thread & PID | |
Process p = Process.GetProcessById(procid.ToInt32()); | |
Console.Write("<" + p.ProcessName + ":(" + procid.ToInt32().ToString() + ")>"); | |
} else | |
{ | |
Console.Write("<process for handle not found>"); | |
} | |
Console.WriteLine("."); | |
} else | |
{ | |
Console.WriteLine(" nope."); | |
} | |
// look for pageant.exe process or gpg-agent.exe process | |
Process[] processlist = Process.GetProcesses(); | |
foreach (Process ps in processlist) | |
{ | |
if (ps.ProcessName.ToLower() == "gpg-agent" || ps.ProcessName.ToLower() == "pageant") | |
{ | |
Console.WriteLine("Found process: {0} ID: {1}", ps.ProcessName, ps.Id); | |
} | |
} | |
Console.WriteLine("\r\n\r\nPress any key to close window."); | |
Console.ReadKey(); | |
} | |
/* | |
* This is combination of code from: | |
* https://social.msdn.microsoft.com/Forums/vstudio/en-US/f7659f5b-cd2a-4524-8cad-f1bebb34f288/systemiodirectorygetfilesquotpipequot-throws-exception?forum=csharpgeneral | |
* | |
* https://katatunix.wordpress.com/2014/09/07/code-that-check-for-the-existence-of-the-servers-named-pipe-in-c/ | |
* | |
* https://social.msdn.microsoft.com/Forums/en-US/7bbf5a0b-3c22-4836-b271-999e514c321b/namedpipeclientstreamconnect-causes-cpu-hang-is-there-a-workaround?forum=netfxnetcom | |
* | |
* https://facstaff.elon.edu/dhutchings/miscellany/appnamefromhwnd.shtml | |
*/ | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment