Skip to content

Instantly share code, notes, and snippets.

@saper-2
Last active June 11, 2023 19:59
Show Gist options
  • Save saper-2/13a48ca50b90bcf940731c85ecd6ad9c to your computer and use it in GitHub Desktop.
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)
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