Pipelist в PowerShell
Наиболее распространенным методом получения списка именованных каналов среди буржуинов является незатейливая команда:
Однако тем, кому ранее доводилось использовать pipelist из набора Sysinternals Suite, вывод данной команды явно покажется малоинформативным.
Доступ к пайпам через метод GetFiles возможен благодаря двум WinAPI функциям, оберткой для которых он является: FindFirstFile и FindNextFile. Те в свою очередь используют ряд NativeAPI функций, одна из которых — NtQueryDirectoryFile (ее описание вполне можно найти в MSDN) — согласно дизассемблеру задействуется в утилите pipelist.exe напрямую. Давайте посмотрим как это все работает в приближении.
Здесь изобретать ничего не будем, а воспользуемся рефлексией для демонстрации механизма заложенного в методе GetFiles.
Вывод становится более приближенным к pipelist.exe, не хватает разве что графы «Max Instances», но это максимум, который можно выжать в данном случае.
В сборках, которые я успел расковырять, эта функция нигде не значится, а потому в данном случае рефлексия идет лесом. Можно, конечно, обратиться за помощью к динамическим методам вкупе с обобщенными делегатами или командлету Add-Type, но лично мне кажется более простым написать все изначально на C#, оформив это как командлет.
Идеологически правильным было бы использовать что-то вроде WriteObject вместо Console.WriteLine при создании командлета, код же выше — всего лишь пример, в котором можно поступиться идеологией.
Непосредственно сборка:
Сборку импортируем как модуль:
Вызываем наш командлет:
Вот теперь у нас полноценный pipelist.exe. При желании можно дополнить код «расшифровкой» отдельно взятого пайпа, скажем, keysvc относится к одному из криптографических сервисов IKeySvc или ICertProtect, а ntsvcs — к сервисам plug and play, но это нечто из разряда свистоперделок.
[IO.Directory]::GetFiles(($$='\\.\pipe\'))|%{$_.Replace($$, '')}
Однако тем, кому ранее доводилось использовать pipelist из набора Sysinternals Suite, вывод данной команды явно покажется малоинформативным.
Доступ к пайпам через метод GetFiles возможен благодаря двум WinAPI функциям, оберткой для которых он является: FindFirstFile и FindNextFile. Те в свою очередь используют ряд NativeAPI функций, одна из которых — NtQueryDirectoryFile (ее описание вполне можно найти в MSDN) — согласно дизассемблеру задействуется в утилите pipelist.exe напрямую. Давайте посмотрим как это все работает в приближении.
Find[First|Next]File
Здесь изобретать ничего не будем, а воспользуемся рефлексией для демонстрации механизма заложенного в методе GetFiles.
#извлекаем FindFirstFile и FindNextFile
[Object].Assembly.GetType(
'Microsoft.Win32.Win32Native'
).GetMethods(
[Reflection.BindingFlags]40
) | Where-Object {
$_.Name -cmatch '\AFind(First|Next)File\Z'
} | ForEach-Object {
Set-Variable $_.Name $_
}
#обе функции требуют структуру WIN32_FIND_DATA
$WIN32_FIND_DATA = [Object].Assembly.GetType(
'Microsoft.Win32.Win32Native+WIN32_FIND_DATA'
).GetConstructor(
[Reflection.BindingFlags]20, $null, [Type[]]@(), $null
).Invoke($null)
#если вдруг не удалось получить доступ к пайпам
if (($fff = $FindFirstFile.Invoke($null, @(
'\\.\pipe\*', $WIN32_FIND_DATA
))).IsInvalid) {
(New-Object ComponentModel.Win32Exception(
[Runtime.InteropServices.Marshal]::GetLastWin32Error()
)).Message
break
}
#лямбда-функция, выводящая данные из структуры WIN32_FIND_DATA
&($lambda = {
$WIN32_FIND_DATA.GetType().GetFields(
[Reflection.BindingFlags]36
) | ForEach-Object {$data = @{}}{
$data[$_.Name] = $_.GetValue($WIN32_FIND_DATA)
}{
$data | Select-Object @{N='PipeName';E={$_.cFileName}}, @{
N='Instances';E={$_.nFileSizeLow}
}
}
})
#вызываем FindNextFile до тех пор, пока она не вернет $false
while ($FindNextFile.Invoke($null, @($fff, $WIN32_FIND_DATA))) {
&$lambda
}
#освобождаем ресурсы
if ($fff) {
$fff.Dispose()
$fff.Close()
}
Вывод становится более приближенным к pipelist.exe, не хватает разве что графы «Max Instances», но это максимум, который можно выжать в данном случае.
NtQueryDirectoryFile
В сборках, которые я успел расковырять, эта функция нигде не значится, а потому в данном случае рефлексия идет лесом. Можно, конечно, обратиться за помощью к динамическим методам вкупе с обобщенными делегатами или командлету Add-Type, но лично мне кажется более простым написать все изначально на C#, оформив это как командлет.
Код командлета
using System;
using System.IO;
using System.ComponentModel;
using Microsoft.Win32.SafeHandles;
using System.Management.Automation;
using System.Runtime.InteropServices;
using System.Diagnostics.CodeAnalysis;
namespace Pipelist {
[StructLayout(LayoutKind.Explicit, Size = 8)]
internal struct LARGE_INTEGER {
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
[FieldOffset(0)]internal Int64 QuadPart;
[FieldOffset(0)]internal UInt32 LowPart;
[SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")]
[FieldOffset(4)]internal Int32 HighPart;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct FILE_DIRECTORY_INFORMATION {
internal UInt32 NextEntryOffset;
internal UInt32 FileIndex;
internal LARGE_INTEGER CreationTime;
internal LARGE_INTEGER LastAccessTime;
internal LARGE_INTEGER LastWriteTime;
internal LARGE_INTEGER ChangeTime;
internal LARGE_INTEGER EndOfFile;
internal LARGE_INTEGER AllocationSize;
internal UInt32 FileAttributes;
internal UInt32 FileNameLength;
internal UInt16 FileName;
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
internal Int32 FileNameOffset { //дабы не вызывать непосредственно в цикле
get {
return Marshal.OffsetOf(
typeof(FILE_DIRECTORY_INFORMATION), "FileName"
).ToInt32();
}
}
}
internal static class NativeMethods {
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
internal static extern SafeFileHandle CreateFile(
String lpFileName,
UInt32 dwDesiredAccess,
FileShare dwShareMode,
IntPtr lpSecuriryAttributes,
FileMode dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile
);
[DllImport("ntdll.dll")]
internal static extern Int32 NtQueryDirectoryFile(
SafeFileHandle FileHandle,
IntPtr Event,
IntPtr ApcRoutine,
IntPtr ApcContext,
out IntPtr IoStatusBlock,
[Out] IntPtr FileInformation,
UInt32 Length,
UInt32 FileInformationClass,
[MarshalAs(UnmanagedType.Bool)]
Boolean ReturnSingleEntry,
IntPtr FileName,
[MarshalAs(UnmanagedType.Bool)]
Boolean RestartScan
);
}
[Cmdlet(VerbsCommon.Get, "PipeList")]
public sealed class GetPipeListCommand : PSCmdlet {
const UInt32 FileDirectoryInformation = 1;
const UInt32 GENERIC_READ = 0x80000000;
const Int32 STATUS_SUCCESS = 0x00000000;
const Int32 BufferLength = 0x00001000;
private static void GetLastError() {
Console.WriteLine(
new Win32Exception(Marshal.GetLastWin32Error()).Message
);
}
private static T PtrToStruct(IntPtr p) {
return (T)Marshal.PtrToStructure(p, typeof(T));
}
protected override void ProcessRecord() {
SafeFileHandle pipes;
IntPtr dir, tmp, isb;
Boolean query = true;
pipes = NativeMethods.CreateFile( //цепляемся к пайпам
@"\\.\\pipe\", GENERIC_READ, FileShare.Read, IntPtr.Zero, FileMode.Open, 0, IntPtr.Zero
);
if (pipes.IsInvalid) {
GetLastError();
return;
}
Console.WriteLine("{0,-40}{1,14}{2,20}", "Pipe Name", "Instances", "Max Instances");
Console.WriteLine("{0,-40}{1,14}{2,20}", "---------", "---------", "-------------");
dir = Marshal.AllocHGlobal(BufferLength);
try {
while (true) { //"опрашиваем" до тех пор, пока ничего не останется
if (NativeMethods.NtQueryDirectoryFile(
pipes, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, out isb, dir,
BufferLength, FileDirectoryInformation, false, IntPtr.Zero, query
) != STATUS_SUCCESS) break;
tmp = dir;
while (true) {
FILE_DIRECTORY_INFORMATION fdi = PtrToStruct(tmp);
IntPtr name = (IntPtr)(fdi.FileNameOffset + tmp.ToInt32());
Console.WriteLine("{0,-40}{1,14}{2,20}",
Marshal.PtrToStringUni(name, (Int32)(fdi.FileNameLength / 2)),
fdi.EndOfFile.LowPart,
(Int32)fdi.AllocationSize.LowPart
);
if (fdi.NextEntryOffset == 0) break;
tmp = (IntPtr)(tmp.ToInt32() + fdi.NextEntryOffset);
}
query = false;
}
}
catch (Exception e) {
Console.WriteLine(e);
}
finally {
Marshal.FreeHGlobal(dir);
pipes.Dispose();
}
pipes.Close();
}
}
}
Идеологически правильным было бы использовать что-то вроде WriteObject вместо Console.WriteLine при создании командлета, код же выше — всего лишь пример, в котором можно поступиться идеологией.
Сборка командлета из хоста PowerShell такова: прописываем в переменной PATH путь до компилятора C# и переходим в каталог с исходником.
PS E:\> $env:path += ';E:\Windows\Microsoft.NET\vXXXX' #XXXX -версия фреймворка
PS E:\> pushd proj
PS E:\proj>
Непосредственно сборка:
PS E:\proj> copy $([PSObject].Assembly.Location)
PS E:\proj> csc /nologo /t:library /out:PipeList.dll /optimize+ /debug:pdbonly /r:System.Management.Automation.dll source.cs
Сборку импортируем как модуль:
PS E:\proj> Import-Module .\PipeList.dll
Вызываем наш командлет:
PS E:\proj> Get-PipeList
Вот теперь у нас полноценный pipelist.exe. При желании можно дополнить код «расшифровкой» отдельно взятого пайпа, скажем, keysvc относится к одному из криптографических сервисов IKeySvc или ICertProtect, а ntsvcs — к сервисам plug and play, но это нечто из разряда свистоперделок.