如何在PowerShell中获取不同权限进程的实际当前工作目录?
获取跨权限进程的当前工作目录(CWD)
Windows的进程权限隔离机制导致跨权限读取CWD存在限制,以下是针对不同场景的可行解决方案:
权限基础规则
- 非管理员进程:仅能读取同权限(非管理员)进程的CWD,无法读取管理员进程的CWD(Windows安全限制,无合法绕过方式)。
- 管理员进程:默认可读取非管理员进程的CWD;读取其他管理员/系统进程的CWD需额外启用
SeDebugPrivilege权限。
解决方案
1. 用CIM/WMI实现(最简方案)
适用于大多数常规场景,无需手动处理API调用:
function Get-ProcessCwd { param( [Parameter(Mandatory=$true)] [int]$ProcessId ) $process = Get-CimInstance Win32_Process -Filter "ProcessId = $ProcessId" if ($process) { return $process.CurrentDirectory } Write-Error "进程ID $ProcessId 不存在或无权限访问" } # 示例调用 Get-ProcessCwd -ProcessId 13896
- 权限限制:
- 非管理员:仅能获取非管理员进程的CWD。
- 管理员:可获取所有非管理员进程的CWD,部分管理员进程可能因权限问题返回空,需启用SeDebugPrivilege。
2. 管理员专属:启用SeDebugPrivilege后调用Native API
若管理员身份下仍无法读取目标进程CWD(如遇到错误998),需临时启用SeDebugPrivilege权限,再通过NtQueryInformationProcess获取:
# 定义Native API调用类 Add-Type @" using System; using System.Runtime.InteropServices; using System.ComponentModel; public class ProcessCwdUtils { [DllImport("ntdll.dll")] private static extern int NtQueryInformationProcess( IntPtr ProcessHandle, int ProcessInformationClass, IntPtr ProcessInformation, int ProcessInformationLength, out int ReturnLength); [DllImport("kernel32.dll")] private static extern IntPtr OpenProcess( uint dwDesiredAccess, bool bInheritHandle, int dwProcessId); [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr hObject); [DllImport("kernel32.dll")] private static extern bool IsWow64Process(IntPtr hProcess, out bool isWow64); [DllImport("kernel32.dll", CharSet = CharSet.Auto)] private static extern int GetFinalPathNameByHandle(IntPtr hFile, char[] lpszFilePath, int cchFilePath, uint dwFlags); private const int ProcessCurrentDirectory = 28; private const uint PROCESS_QUERY_INFORMATION = 0x0400; private const uint PROCESS_VM_READ = 0x0010; public static string GetProcessCurrentDirectory(int pid) { IntPtr processHandle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid); if (processHandle == IntPtr.Zero) { throw new Win32Exception(Marshal.GetLastWin32Error()); } try { bool isWow64 = false; if (IntPtr.Size == 4) { IsWow64Process(processHandle, out isWow64); } int bufferSize = isWow64 ? 8 : IntPtr.Size; IntPtr buffer = Marshal.AllocHGlobal(bufferSize); try { int returnLength; int status = NtQueryInformationProcess(processHandle, ProcessCurrentDirectory, buffer, bufferSize, out returnLength); if (status != 0) { throw new Exception($"NtQueryInformationProcess失败,状态码: {status}"); } IntPtr directoryHandle = isWow64 ? Marshal.ReadIntPtr(buffer, 4) : Marshal.ReadIntPtr(buffer); if (directoryHandle == IntPtr.Zero) { return null; } char[] pathBuffer = new char[260]; int pathLength = GetFinalPathNameByHandle(directoryHandle, pathBuffer, pathBuffer.Length, 0); if (pathLength == 0) { throw new Win32Exception(Marshal.GetLastWin32Error()); } return new string(pathBuffer, 0, pathLength).TrimEnd('\0'); } finally { Marshal.FreeHGlobal(buffer); } } finally { CloseHandle(processHandle); } } } "@ # 启用SeDebugPrivilege的函数 Add-Type @" using System; using System.Runtime.InteropServices; using System.Security.Principal; public class PrivilegeManager { [DllImport("advapi32.dll", SetLastError = true)] private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, ref IntPtr TokenHandle); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, ref LUID lpLuid); [DllImport("advapi32.dll", SetLastError = true)] private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint BufferLength, IntPtr PreviousState, IntPtr ReturnLength); [DllImport("kernel32.dll")] private static extern IntPtr GetCurrentProcess(); [DllImport("kernel32.dll")] private static extern bool CloseHandle(IntPtr hObject); [StructLayout(LayoutKind.Sequential)] private struct LUID { public uint LowPart; public int HighPart; } [StructLayout(LayoutKind.Sequential)] private struct LUID_AND_ATTRIBUTES { public LUID Luid; public uint Attributes; } [StructLayout(LayoutKind.Sequential)] private struct TOKEN_PRIVILEGES { public uint PrivilegeCount; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)] public LUID_AND_ATTRIBUTES[] Privileges; } private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020; private const uint TOKEN_QUERY = 0x0008; private const uint SE_PRIVILEGE_ENABLED = 0x00000002; public static bool EnableSeDebugPrivilege() { if (!new WindowsPrincipal(WindowsIdentity.GetCurrent()).IsInRole(WindowsBuiltInRole.Administrator)) { return false; } IntPtr tokenHandle = IntPtr.Zero; try { if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, ref tokenHandle)) { return false; } LUID luid = new LUID(); if (!LookupPrivilegeValue(null, "SeDebugPrivilege", ref luid)) { return false; } TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES(); tp.PrivilegeCount = 1; tp.Privileges = new LUID_AND_ATTRIBUTES[1]; tp.Privileges[0].Luid = luid; tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; if (!AdjustTokenPrivileges(tokenHandle, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero)) { return false; } return true; } finally { if (tokenHandle != IntPtr.Zero) { CloseHandle(tokenHandle); } } } } "@ # 使用示例 if ([PrivilegeManager]::EnableSeDebugPrivilege()) { try { [ProcessCwdUtils]::GetProcessCurrentDirectory(13896) } catch { Write-Error "获取CWD失败: $_" } } else { Write-Error "需要管理员权限才能启用SeDebugPrivilege" }
- 说明:
- 必须以管理员身份运行PowerShell。
- 启用
SeDebugPrivilege后,可读取所有进程(包括系统进程、其他管理员进程)的CWD。 - 错误998(ERROR_NOACCESS)的核心原因是未获取足够的进程访问权限或未启用SeDebugPrivilege。
3. 非管理员的限制说明
非管理员身份下,Windows严格禁止读取管理员进程的CWD,这是系统权限隔离的核心机制,不存在合法绕过方法。任何声称无需提权即可读取管理员进程CWD的方案均不可信。
常见问题排查
- 错误998:
- 非管理员:目标进程为管理员进程,无权限读取。
- 管理员:未启用SeDebugPrivilege,或目标进程为受保护的系统进程。
- Get-CimInstance返回空:目标进程已终止,或当前身份无权限访问该进程。
内容的提问来源于stack exchange,提问作者Vic Zhang
相关产品推荐
相关产品推荐

