runasuser.psm1
$script:source = @"
using Microsoft.Win32.SafeHandles; using System; using System.Runtime.InteropServices; using System.Text; namespace RunAsUser { internal class NativeHelpers { [StructLayout(LayoutKind.Sequential)] public struct PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public int dwProcessId; public int dwThreadId; } [StructLayout(LayoutKind.Sequential)] public struct STARTUPINFO { public int cb; public String lpReserved; public String lpDesktop; public String lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; } [StructLayout(LayoutKind.Sequential)] public struct WTS_SESSION_INFO { public readonly UInt32 SessionID; [MarshalAs(UnmanagedType.LPStr)] public readonly String pWinStationName; public readonly WTS_CONNECTSTATE_CLASS State; } public struct SECURITY_ATTRIBUTES { public Int32 nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; } } internal class NativeMethods { [DllImport("kernel32", SetLastError = true)] public static extern int WaitForSingleObject( IntPtr hHandle, int dwMilliseconds); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool CloseHandle( IntPtr hSnapshot); [DllImport("userenv.dll", SetLastError = true)] public static extern bool CreateEnvironmentBlock( ref IntPtr lpEnvironment, SafeHandle hToken, bool bInherit); [DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateProcessAsUserW( SafeHandle hToken, String lpApplicationName, StringBuilder lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandle, uint dwCreationFlags, IntPtr lpEnvironment, String lpCurrentDirectory, ref NativeHelpers.STARTUPINFO lpStartupInfo, out NativeHelpers.PROCESS_INFORMATION lpProcessInformation); [DllImport("userenv.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DestroyEnvironmentBlock( IntPtr lpEnvironment); [DllImport("advapi32.dll", SetLastError = true)] public static extern bool DuplicateTokenEx( SafeHandle ExistingTokenHandle, uint dwDesiredAccess, IntPtr lpThreadAttributes, SECURITY_IMPERSONATION_LEVEL ImpersonationLevel, TOKEN_TYPE TokenType, out SafeNativeHandle DuplicateTokenHandle); [DllImport("advapi32.dll", SetLastError = true)] public static extern bool GetTokenInformation( SafeHandle TokenHandle, uint TokenInformationClass, SafeMemoryBuffer TokenInformation, int TokenInformationLength, out int ReturnLength); [DllImport("wtsapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)] public static extern bool WTSEnumerateSessions( IntPtr hServer, int Reserved, int Version, ref IntPtr ppSessionInfo, ref int pCount); [DllImport("wtsapi32.dll")] public static extern void WTSFreeMemory( IntPtr pMemory); [DllImport("kernel32.dll")] public static extern uint WTSGetActiveConsoleSessionId(); [DllImport("Wtsapi32.dll", SetLastError = true)] public static extern bool WTSQueryUserToken( uint SessionId, out SafeNativeHandle phToken); [DllImport("kernel32.dll", SetLastError = true)] public static extern IntPtr CreatePipe( ref IntPtr hReadPipe, ref IntPtr hWritePipe, ref NativeHelpers.SECURITY_ATTRIBUTES lpPipeAttributes, Int32 nSize); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool SetHandleInformation( IntPtr hObject, int dwMask, int dwFlags); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool ReadFile( IntPtr hFile, byte[] lpBuffer, int nNumberOfBytesToRead, ref int lpNumberOfBytesRead, IntPtr lpOverlapped/*IntPtr.Zero*/); [DllImport("kernel32.dll", SetLastError = true)] public static extern bool PeekNamedPipe( IntPtr handle, byte[] buffer, uint nBufferSize, ref uint bytesRead, ref uint bytesAvail, ref uint BytesLeftThisMessage); [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool DuplicateHandle(IntPtr hSourceProcessHandle, ushort hSourceHandle, IntPtr hTargetProcessHandle, out IntPtr lpTargetHandle, uint dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, uint dwOptions); } internal class SafeMemoryBuffer : SafeHandleZeroOrMinusOneIsInvalid { public SafeMemoryBuffer(int cb) : base(true) { base.SetHandle(Marshal.AllocHGlobal(cb)); } public SafeMemoryBuffer(IntPtr handle) : base(true) { base.SetHandle(handle); } protected override bool ReleaseHandle() { Marshal.FreeHGlobal(handle); return true; } } internal class SafeNativeHandle : SafeHandleZeroOrMinusOneIsInvalid { public SafeNativeHandle() : base(true) { } public SafeNativeHandle(IntPtr handle) : base(true) { this.handle = handle; } protected override bool ReleaseHandle() { return NativeMethods.CloseHandle(handle); } } internal enum SECURITY_IMPERSONATION_LEVEL { SecurityAnonymous = 0, SecurityIdentification = 1, SecurityImpersonation = 2, SecurityDelegation = 3, } internal enum SW { SW_HIDE = 0, SW_SHOWNORMAL = 1, SW_NORMAL = 1, SW_SHOWMINIMIZED = 2, SW_SHOWMAXIMIZED = 3, SW_MAXIMIZE = 3, SW_SHOWNOACTIVATE = 4, SW_SHOW = 5, SW_MINIMIZE = 6, SW_SHOWMINNOACTIVE = 7, SW_SHOWNA = 8, SW_RESTORE = 9, SW_SHOWDEFAULT = 10, SW_MAX = 10 } internal enum TokenElevationType { TokenElevationTypeDefault = 1, TokenElevationTypeFull, TokenElevationTypeLimited, } internal enum TOKEN_TYPE { TokenPrimary = 1, TokenImpersonation = 2 } internal enum WTS_CONNECTSTATE_CLASS { WTSActive, WTSConnected, WTSConnectQuery, WTSShadow, WTSDisconnected, WTSIdle, WTSListen, WTSReset, WTSDown, WTSInit } public class Win32Exception : System.ComponentModel.Win32Exception { private string _msg; public Win32Exception(string message) : this(Marshal.GetLastWin32Error(), message) { } public Win32Exception(int errorCode, string message) : base(errorCode) { _msg = String.Format("{0} ({1}, Win32ErrorCode {2} - 0x{2:X8})", message, base.Message, errorCode); } public override string Message { get { return _msg; } } public static explicit operator Win32Exception(string message) { return new Win32Exception(message); } } public static class ProcessExtensions { #region Win32 Constants private const int CREATE_UNICODE_ENVIRONMENT = 0x00000400; private const int CREATE_NO_WINDOW = 0x08000000; private const int CREATE_NEW_CONSOLE = 0x00000010; private const uint INVALID_SESSION_ID = 0xFFFFFFFF; private static readonly IntPtr WTS_CURRENT_SERVER_HANDLE = IntPtr.Zero; private const int HANDLE_FLAG_INHERIT = 0x00000001; private const int STARTF_USESTDHANDLES = 0x00000100; #endregion // Gets the user token from the currently active session private static SafeNativeHandle GetSessionUserToken(bool elevated) { var activeSessionId = INVALID_SESSION_ID; var pSessionInfo = IntPtr.Zero; var sessionCount = 0; // Get a handle to the user access token for the current active session. if (NativeMethods.WTSEnumerateSessions(WTS_CURRENT_SERVER_HANDLE, 0, 1, ref pSessionInfo, ref sessionCount)) { try { var arrayElementSize = Marshal.SizeOf(typeof(NativeHelpers.WTS_SESSION_INFO)); var current = pSessionInfo; for (var i = 0; i < sessionCount; i++) { var si = (NativeHelpers.WTS_SESSION_INFO)Marshal.PtrToStructure( current, typeof(NativeHelpers.WTS_SESSION_INFO)); current = IntPtr.Add(current, arrayElementSize); if (si.State == WTS_CONNECTSTATE_CLASS.WTSActive) { activeSessionId = si.SessionID; break; } } } finally { NativeMethods.WTSFreeMemory(pSessionInfo); } } // If enumerating did not work, fall back to the old method if (activeSessionId == INVALID_SESSION_ID) { activeSessionId = NativeMethods.WTSGetActiveConsoleSessionId(); } SafeNativeHandle hImpersonationToken; if (!NativeMethods.WTSQueryUserToken(activeSessionId, out hImpersonationToken)) { throw new Win32Exception("WTSQueryUserToken failed to get access token."); } using (hImpersonationToken) { // First see if the token is the full token or not. If it is a limited token we need to get the // linked (full/elevated token) and use that for the CreateProcess task. If it is already the full or // default token then we already have the best token possible. TokenElevationType elevationType = GetTokenElevationType(hImpersonationToken); if (elevationType == TokenElevationType.TokenElevationTypeLimited && elevated == true) { using (var linkedToken = GetTokenLinkedToken(hImpersonationToken)) return DuplicateTokenAsPrimary(linkedToken); } else { return DuplicateTokenAsPrimary(hImpersonationToken); } } } private static IntPtr out_read; private static IntPtr out_write; private static IntPtr err_read; private static IntPtr err_write; private static int BUFSIZE = 4096; public static string StartProcessAsCurrentUser(string appPath, string cmdLine = null, string workDir = null, bool visible = true, int wait = -1, bool elevated = true, bool redirectOutput = true) { NativeHelpers.SECURITY_ATTRIBUTES saAttr = new NativeHelpers.SECURITY_ATTRIBUTES(); saAttr.nLength = Marshal.SizeOf(typeof(NativeHelpers.SECURITY_ATTRIBUTES)); saAttr.bInheritHandle = 0x1; saAttr.lpSecurityDescriptor = IntPtr.Zero; if (redirectOutput) { NativeMethods.CreatePipe(ref out_read, ref out_write, ref saAttr, 0); NativeMethods.CreatePipe(ref err_read, ref err_write, ref saAttr, 0); NativeMethods.SetHandleInformation(out_read, HANDLE_FLAG_INHERIT, 0); NativeMethods.SetHandleInformation(err_read, HANDLE_FLAG_INHERIT, 0); } var startInfo = new NativeHelpers.STARTUPINFO(); startInfo.cb = Marshal.SizeOf(startInfo); uint dwCreationFlags = CREATE_UNICODE_ENVIRONMENT | (uint)(visible ? CREATE_NEW_CONSOLE : CREATE_NO_WINDOW); startInfo.wShowWindow = (short)(visible ? SW.SW_SHOW : SW.SW_HIDE); startInfo.hStdOutput = out_write; startInfo.hStdError = err_write; startInfo.dwFlags |= (uint)STARTF_USESTDHANDLES; StringBuilder commandLine = new StringBuilder(cmdLine); var procInfo = new NativeHelpers.PROCESS_INFORMATION(); using (var hUserToken = GetSessionUserToken(elevated)) { IntPtr pEnv = IntPtr.Zero; if (!NativeMethods.CreateEnvironmentBlock(ref pEnv, hUserToken, false)) { throw new Win32Exception("CreateEnvironmentBlock failed."); } try { if (!NativeMethods.CreateProcessAsUserW(hUserToken, appPath, // Application Name commandLine, // Command Line IntPtr.Zero, IntPtr.Zero, redirectOutput, dwCreationFlags, pEnv, workDir, // Working directory ref startInfo, out procInfo)) { throw new Win32Exception("CreateProcessAsUser failed."); } try { NativeMethods.WaitForSingleObject(procInfo.hProcess, wait); } finally { NativeMethods.CloseHandle(procInfo.hThread); NativeMethods.CloseHandle(procInfo.hProcess); } } finally { NativeMethods.DestroyEnvironmentBlock(pEnv); } } if (redirectOutput) { var sb = new StringBuilder(); byte[] buf = new byte[BUFSIZE]; int dwRead = 0; while (true) { if (Readable(out_read)) { bool bSuccess = NativeMethods.ReadFile(out_read, buf, BUFSIZE, ref dwRead, IntPtr.Zero); if (!bSuccess || dwRead == 0) break; sb.AppendLine(Encoding.Default.GetString(buf).TrimEnd(new char[] { (char)0 })); } else { break; } } NativeMethods.CloseHandle(out_read); NativeMethods.CloseHandle(err_read); NativeMethods.CloseHandle(out_write); NativeMethods.CloseHandle(err_write); return sb.ToString(); } else { return procInfo.dwProcessId.ToString(); } } private static bool Readable(IntPtr streamHandle) { byte[] aPeekBuffer = new byte[1]; uint aPeekedBytes = 0; uint aAvailBytes = 0; uint aLeftBytes = 0; bool aPeekedSuccess = NativeMethods.PeekNamedPipe( streamHandle, aPeekBuffer, 1, ref aPeekedBytes, ref aAvailBytes, ref aLeftBytes); if (aPeekedSuccess && aPeekBuffer[0] != 0) return true; else return false; } private static SafeNativeHandle DuplicateTokenAsPrimary(SafeHandle hToken) { SafeNativeHandle pDupToken; if (!NativeMethods.DuplicateTokenEx(hToken, 0, IntPtr.Zero, SECURITY_IMPERSONATION_LEVEL.SecurityImpersonation, TOKEN_TYPE.TokenPrimary, out pDupToken)) { throw new Win32Exception("DuplicateTokenEx failed."); } return pDupToken; } private static TokenElevationType GetTokenElevationType(SafeHandle hToken) { using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, 18)) { return (TokenElevationType)Marshal.ReadInt32(tokenInfo.DangerousGetHandle()); } } private static SafeNativeHandle GetTokenLinkedToken(SafeHandle hToken) { using (SafeMemoryBuffer tokenInfo = GetTokenInformation(hToken, 19)) { return new SafeNativeHandle(Marshal.ReadIntPtr(tokenInfo.DangerousGetHandle())); } } private static SafeMemoryBuffer GetTokenInformation(SafeHandle hToken, uint infoClass) { int returnLength; bool res = NativeMethods.GetTokenInformation(hToken, infoClass, new SafeMemoryBuffer(IntPtr.Zero), 0, out returnLength); int errCode = Marshal.GetLastWin32Error(); if (!res && errCode != 24 && errCode != 122) // ERROR_INSUFFICIENT_BUFFER, ERROR_BAD_LENGTH { throw new Win32Exception(errCode, String.Format("GetTokenInformation({0}) failed to get buffer length", infoClass)); } SafeMemoryBuffer tokenInfo = new SafeMemoryBuffer(returnLength); if (!NativeMethods.GetTokenInformation(hToken, infoClass, tokenInfo, returnLength, out returnLength)) throw new Win32Exception(String.Format("GetTokenInformation({0}) failed", infoClass)); return tokenInfo; } } } "@ $Public = @(Get-ChildItem -Path $PSScriptRoot\Public\*.ps1 -ErrorAction SilentlyContinue) foreach ($import in @($Public)) { try { . $import.FullName } catch { Write-Error -Message "Failed to import function $($import.FullName): $_" } } |