이 코드 샘플은 프로그래밍 방식으로 WSL 배포판 안의 사용자를 조회할 수 있는 방법을 설명합니다. Win32 API를 주로 사용하였고, 일부 편의를 위하여 C# 코드를 이용했지만, C++ 코드에서도 같은 기능을 구현할 수 있습니다.
프로그램 코드를 테스트해보기 위해서는 .NET 5 이상의 SDK를 설치해야 합니다. 그 다음, 이 Gist 리포지터리를 Git으로 체크아웃하고 dotnet run
명령으로 실행해봅니다.
// Requires .NET 5 | |
using Microsoft.Win32; | |
using System; | |
using System.IO; | |
using System.Runtime.InteropServices; | |
using static stdio; | |
using static wslapi; | |
using static windows; | |
using Microsoft.Win32.SafeHandles; | |
using System.Text; | |
using var lxssKey = Registry.CurrentUser.OpenSubKey( | |
Path.Combine("SOFTWARE", "Microsoft", "Windows", "CurrentVersion", "Lxss"), | |
false); | |
foreach (var keyName in lxssKey.GetSubKeyNames()) | |
{ | |
if (!Guid.TryParse(keyName, out Guid parsedGuid)) | |
continue; | |
using var distroKey = lxssKey.OpenSubKey(keyName); | |
var distroName = distroKey.GetValue("DistributionName", default(string)) as string; | |
if (string.IsNullOrWhiteSpace(distroName)) | |
continue; | |
var basePath = Path.GetFullPath(distroKey.GetValue("BasePath", default(string)) as string); | |
var kernelCommandLine = (distroKey.GetValue("KernelCommandLine", default(string)) as string ?? string.Empty); | |
var stdin = GetStdHandle(STD_INPUT_HANDLE); | |
var stdout = GetStdHandle(STD_OUTPUT_HANDLE); | |
var stderr = GetStdHandle(STD_ERROR_HANDLE); | |
printf("==== [%s] ====\n", distroName); | |
printf("* Distro ID: %s\n", parsedGuid.ToString()); | |
printf("* Base Path: %s\n", basePath); | |
printf("* Kernel Command Line: %s\n", string.Join(' ', kernelCommandLine)); | |
var isRegistered = WslIsDistributionRegistered(distroName); | |
if (isRegistered) | |
{ | |
var hr = WslGetDistributionConfiguration( | |
distroName, | |
out int distroVersion, | |
out int defaultUserId, | |
out WslDistributionFlags flags, | |
out IntPtr environmentVariables, | |
out int environmentVariableCount); | |
if (hr >= 0) | |
{ | |
printf("* WSL Version: %d\n", distroVersion); | |
printf("* Default UID: %d\n", defaultUserId); | |
printf("* Flag: %x\n", (int)flags); | |
unsafe | |
{ | |
byte*** lpEnvironmentVariables = (byte***)environmentVariables.ToPointer(); | |
for (int i = 0; i < environmentVariableCount; i++) | |
{ | |
byte** lpArray = lpEnvironmentVariables[i]; | |
var content = Marshal.PtrToStringAnsi(new IntPtr(lpArray)); | |
printf(" * Environment Variable [%d]: %s\n", i, content); | |
Marshal.FreeCoTaskMem(new IntPtr(lpArray)); | |
} | |
Marshal.FreeCoTaskMem(new IntPtr(lpEnvironmentVariables)); | |
} | |
SECURITY_ATTRIBUTES attributes = new SECURITY_ATTRIBUTES | |
{ | |
lpSecurityDescriptor = IntPtr.Zero, | |
bInheritHandle = true, | |
}; | |
attributes.nLength = Marshal.SizeOf(attributes); | |
if (CreatePipe(out IntPtr readPipe, out IntPtr writePipe, ref attributes, 0)) | |
{ | |
hr = WslLaunch(distroName, "cat /etc/passwd", false, stdin, writePipe, stderr, out IntPtr child); | |
if (hr >= 0) | |
{ | |
WaitForSingleObject(child, INFINITE); | |
if ((GetExitCodeProcess(child, out int exitCode) == false) || (exitCode != 0)) | |
{ | |
hr = E_INVALIDARG; | |
} | |
var processClosed = CloseHandle(child); | |
if (hr >= 0) | |
{ | |
printf("* Fetching user names from `%s`.\n", distroName); | |
var length = 65536; | |
var buffer = Marshal.AllocHGlobal(length); | |
FillMemory(buffer, length, 0x00); | |
var read = 0; | |
var readFileResult = ReadFile(readPipe, buffer, length - 1, out read, IntPtr.Zero); | |
var lastError = Marshal.GetLastWin32Error(); | |
var passwdContents = new StringBuilder(); | |
passwdContents.Append(Marshal.PtrToStringAnsi(buffer, read)); | |
Marshal.FreeHGlobal(buffer); | |
foreach (var eachLine in passwdContents.ToString().Split( | |
new char[] { '\r', '\n', }, | |
StringSplitOptions.RemoveEmptyEntries)) | |
{ | |
StringBuilder | |
userName = new StringBuilder(), | |
comment = new StringBuilder(), | |
homedir = new StringBuilder(), | |
shell = new StringBuilder(); | |
if (sscanf(eachLine, "%[^:]:%*[^:]:%u:%u:%[^:]:%[^:]:%[^:]", | |
userName, out int uid, out int gid, comment, homedir, shell) > 0) | |
{ | |
if (1000 <= uid && uid <= 60000) | |
{ | |
printf(" << %s >>\n", userName.ToString()); | |
printf(" * UID: %d\n", uid); | |
printf(" * GID: %d\n", gid); | |
printf(" * Comment: %s\n", comment.ToString()); | |
printf(" * Home Directory: %s\n", homedir.ToString()); | |
printf(" * Shell: %s\n", shell.ToString()); | |
printf("\n"); | |
} | |
} | |
} | |
} | |
else | |
printf("* Cannot obtain passwd contents: 0x%08x\n", hr); | |
} | |
else | |
printf("* Cannot launch WSL process: 0x%08x\n", hr); | |
CloseHandle(readPipe); | |
CloseHandle(writePipe); | |
} | |
else | |
printf("* Cannot create pipe for I/O.\n"); | |
} | |
else | |
printf("* Cannot obtain detail information: 0x%08x\n", hr); | |
} | |
printf("\n"); | |
} |
using System; | |
using System.Runtime.InteropServices; | |
using System.Text; | |
static class stdio | |
{ | |
[DllImport("msvcrt.dll", | |
CallingConvention = CallingConvention.Cdecl, | |
CharSet = CharSet.Ansi, | |
ExactSpelling = true)] | |
public static extern int printf(string fmt); | |
[DllImport("msvcrt.dll", | |
CallingConvention = CallingConvention.Cdecl, | |
CharSet = CharSet.Ansi, | |
ExactSpelling = true)] | |
public static extern int printf(string fmt, int arg0); | |
[DllImport("msvcrt.dll", | |
CallingConvention = CallingConvention.Cdecl, | |
CharSet = CharSet.Ansi, | |
ExactSpelling = true)] | |
public static extern int printf(string fmt, string arg0); | |
[DllImport("msvcrt.dll", | |
CallingConvention = CallingConvention.Cdecl, | |
CharSet = CharSet.Ansi, | |
ExactSpelling = true)] | |
public static extern int printf(string fmt, int arg0, string arg1); | |
[DllImport("msvcrt.dll", | |
CallingConvention = CallingConvention.Cdecl, | |
CharSet = CharSet.Ansi, | |
ExactSpelling = true)] | |
public static extern int sscanf(string str, string fmt, | |
StringBuilder arg0, | |
[MarshalAs(UnmanagedType.U4)] out int arg1, | |
[MarshalAs(UnmanagedType.U4)] out int arg2, | |
StringBuilder arg3, | |
StringBuilder arg4, | |
StringBuilder arg5); | |
} |
using System; | |
using System.Runtime.InteropServices; | |
static class windows | |
{ | |
[DllImport("kernel32.dll", | |
CallingConvention = CallingConvention.Winapi, | |
SetLastError = false, | |
EntryPoint = "RtlFillMemory", | |
CharSet = CharSet.None, | |
ExactSpelling = true)] | |
public static extern void FillMemory( | |
IntPtr destination, | |
[MarshalAs(UnmanagedType.U4)] int length, | |
byte fill); | |
[Serializable, StructLayout(LayoutKind.Sequential)] | |
public struct SECURITY_ATTRIBUTES | |
{ | |
[MarshalAs(UnmanagedType.U4)] | |
public int nLength; | |
public IntPtr lpSecurityDescriptor; | |
[MarshalAs(UnmanagedType.Bool)] | |
public bool bInheritHandle; | |
} | |
[DllImport("kernel32.dll", | |
CallingConvention = CallingConvention.Winapi, | |
SetLastError = true, | |
CharSet = CharSet.None, | |
ExactSpelling = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CreatePipe( | |
out IntPtr hReadPipe, | |
out IntPtr hWritePipe, | |
ref SECURITY_ATTRIBUTES lpPipeAttributes, | |
[MarshalAs(UnmanagedType.U4)] int nSize); | |
[DllImport("kernel32.dll", | |
CallingConvention = CallingConvention.Winapi, | |
SetLastError = true, | |
CharSet = CharSet.None, | |
ExactSpelling = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool ReadFile( | |
IntPtr hFile, | |
IntPtr lpBuffer, | |
[MarshalAs(UnmanagedType.U4)] int nNumberOfBytesToRead, | |
[MarshalAs(UnmanagedType.U4)] out int lpNumberOfBytesRead, | |
IntPtr lpOverlapped); | |
public static readonly int | |
E_INVALIDARG = unchecked((int)0x80070057); | |
public static readonly int | |
STD_INPUT_HANDLE = -10, | |
STD_OUTPUT_HANDLE = -11, | |
STD_ERROR_HANDLE = -12; | |
[DllImport("kernel32.dll", | |
CallingConvention = CallingConvention.Winapi, | |
SetLastError = true, | |
CharSet = CharSet.None, | |
ExactSpelling = true)] | |
public static extern IntPtr GetStdHandle( | |
[MarshalAs(UnmanagedType.U4)] int nStdHandle); | |
public static readonly int | |
INFINITE = unchecked((int)0xFFFFFFFF); | |
[DllImport("kernel32.dll", | |
CallingConvention = CallingConvention.Winapi, | |
SetLastError = true, | |
CharSet = CharSet.None, | |
ExactSpelling = true)] | |
[return: MarshalAs(UnmanagedType.U4)] | |
public static extern int WaitForSingleObject( | |
IntPtr hHandle, | |
[MarshalAs(UnmanagedType.U4)] int dwMilliseconds); | |
public static readonly int | |
WAIT_ABANDONED = 0x00000080, | |
WAIT_OBJECT_0 = 0x00000000, | |
WAIT_TIMEOUT = 0x00000102, | |
WAIT_FAILED = unchecked((int)0xFFFFFFFF); | |
[DllImport("kernel32.dll", | |
CallingConvention = CallingConvention.Winapi, | |
SetLastError = true, | |
CharSet = CharSet.None, | |
ExactSpelling = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool GetExitCodeProcess( | |
IntPtr hProcess, | |
[MarshalAs(UnmanagedType.U4)] out int lpExitCode); | |
[DllImport("kernel32.dll", | |
CallingConvention = CallingConvention.Winapi, | |
SetLastError = true, | |
CharSet = CharSet.None, | |
ExactSpelling = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool CloseHandle(IntPtr hObject); | |
} |
using System; | |
using System.Runtime.InteropServices; | |
static class wslapi | |
{ | |
[Flags, Serializable] | |
public enum WslDistributionFlags | |
{ | |
None = 0x0, | |
EnableInterop = 0x1, | |
AppendNtPath = 0x2, | |
EnableDriveMouting = 0x4, | |
} | |
[DllImport("wslapi.dll", | |
CallingConvention = CallingConvention.Winapi, | |
CharSet = CharSet.Unicode, | |
ExactSpelling = true)] | |
[return: MarshalAs(UnmanagedType.Bool)] | |
public static extern bool WslIsDistributionRegistered( | |
string distributionName); | |
[DllImport("wslapi.dll", | |
CallingConvention = CallingConvention.Winapi, | |
CharSet = CharSet.Unicode, | |
ExactSpelling = true, | |
PreserveSig = true)] | |
[return: MarshalAs(UnmanagedType.U4)] | |
public static extern int WslGetDistributionConfiguration( | |
string distributionName, | |
[MarshalAs(UnmanagedType.I4)] out int distributionVersion, | |
[MarshalAs(UnmanagedType.I4)] out int defaultUID, | |
[MarshalAs(UnmanagedType.I4)] out WslDistributionFlags wslDistributionFlags, | |
out IntPtr defaultEnvironmentVariables, | |
[MarshalAs(UnmanagedType.I4)] out int defaultEnvironmentVariableCount); | |
[DllImport("wslapi.dll", | |
CallingConvention = CallingConvention.Winapi, | |
CharSet = CharSet.Unicode, | |
ExactSpelling = true, | |
PreserveSig = true)] | |
[return: MarshalAs(UnmanagedType.U4)] | |
public static extern int WslLaunch( | |
string distributionName, | |
string command, | |
bool useCurrentWorkingDirectory, | |
IntPtr stdIn, | |
IntPtr stdOut, | |
IntPtr stdErr, | |
out IntPtr process); | |
} |
<Project Sdk="Microsoft.NET.Sdk"> | |
<PropertyGroup> | |
<OutputType>Exe</OutputType> | |
<TargetFramework>net5.0</TargetFramework> | |
<StartupObject></StartupObject> | |
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="Microsoft.Win32.Registry" Version="4.7.0" /> | |
</ItemGroup> | |
</Project> |