이 코드 샘플은 프로그래밍 방식으로 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> |