I’ve got some PowerShell scripts that are executed by a service running as “SYSTEM” which runs everything in Session 0 isolation and I needed to be able to launch a program as the logged in user. After some research I came across this awesome C# code: Launch Program from Session 0
Well in PowerShell we can write and execute C# code directly as described here: Using C# code in PowerShell
So if we copy the entire C# source code from the first link’s answer into the Source variable like such:
$Source = @' using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Security; namespace Session0 { /// /// Class that allows running applications with full admin rights. In /// addition the application launched will bypass the Vista UAC prompt. /// public class AppLaunch { #region Structures [StructLayout(LayoutKind.Sequential)] public struct SECURITY_ATTRIBUTES { public int Length; public IntPtr lpSecurityDescriptor; public bool bInheritHandle; } [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 PROCESS_INFORMATION { public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; } #endregion #region Enumerations enum TOKEN_TYPE : int { TokenPrimary = 1, TokenImpersonation = 2 } enum SECURITY_IMPERSONATION_LEVEL : int { SecurityAnonymous = 0, SecurityIdentification = 1, SecurityImpersonation = 2, SecurityDelegation = 3, } #endregion #region Constants public const int TOKEN_DUPLICATE = 0x0002; public const uint MAXIMUM_ALLOWED = 0x2000000; public const int CREATE_NEW_CONSOLE = 0x00000010; public const int IDLE_PRIORITY_CLASS = 0x40; public const int NORMAL_PRIORITY_CLASS = 0x20; public const int HIGH_PRIORITY_CLASS = 0x80; public const int REALTIME_PRIORITY_CLASS = 0x100; #endregion #region Win32 API Imports [DllImport("kernel32.dll", SetLastError = true)] private static extern bool CloseHandle(IntPtr hSnapshot); [DllImport("kernel32.dll")] static extern uint WTSGetActiveConsoleSessionId(); [DllImport("advapi32.dll", EntryPoint = "CreateProcessAsUser", SetLastError = true, CharSet = CharSet.Ansi, CallingConvention = CallingConvention.StdCall)] public extern static bool CreateProcessAsUser(IntPtr hToken, String lpApplicationName, String lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandle, int dwCreationFlags, IntPtr lpEnvironment, String lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); [DllImport("kernel32.dll")] static extern bool ProcessIdToSessionId(uint dwProcessId, ref uint pSessionId); [DllImport("advapi32.dll", EntryPoint = "DuplicateTokenEx")] public extern static bool DuplicateTokenEx(IntPtr ExistingTokenHandle, uint dwDesiredAccess, ref SECURITY_ATTRIBUTES lpThreadAttributes, int TokenType, int ImpersonationLevel, ref IntPtr DuplicateTokenHandle); [DllImport("kernel32.dll")] static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId); [DllImport("advapi32", SetLastError = true), SuppressUnmanagedCodeSecurity] static extern bool OpenProcessToken(IntPtr ProcessHandle, int DesiredAccess, ref IntPtr TokenHandle); #endregion /// /// Launches the given application with full admin rights, and in addition bypasses the Vista UAC prompt /// /// The name of the application to launch /// Process information regarding the launched application that gets returned to the caller /// public static bool Start(String applicationName, string startingDir, out PROCESS_INFORMATION procInfo) { uint winlogonPid = 0; IntPtr hUserTokenDup = IntPtr.Zero, hPToken = IntPtr.Zero, hProcess = IntPtr.Zero; procInfo = new PROCESS_INFORMATION(); // obtain the currently active session id; every logged on user in the system has a unique session id uint dwSessionId = WTSGetActiveConsoleSessionId(); // obtain the process id of the winlogon process that is running within the currently active session // -- chaged by ty // Process[] processes = Process.GetProcessesByName("winlogon"); Process[] processes = Process.GetProcessesByName("explorer"); foreach (Process p in processes) { if ((uint)p.SessionId == dwSessionId) { winlogonPid = (uint)p.Id; } } // obtain a handle to the winlogon process hProcess = OpenProcess(MAXIMUM_ALLOWED, false, winlogonPid); // obtain a handle to the access token of the winlogon process if (!OpenProcessToken(hProcess, TOKEN_DUPLICATE, ref hPToken)) { CloseHandle(hProcess); return false; } // Security attibute structure used in DuplicateTokenEx and CreateProcessAsUser // I would prefer to not have to use a security attribute variable and to just // simply pass null and inherit (by default) the security attributes // of the existing token. However, in C# structures are value types and therefore // cannot be assigned the null value. SECURITY_ATTRIBUTES sa = new SECURITY_ATTRIBUTES(); sa.Length = Marshal.SizeOf(sa); // copy the access token of the winlogon process; the newly created token will be a primary token if (!DuplicateTokenEx(hPToken, MAXIMUM_ALLOWED, ref sa, (int)SECURITY_IMPERSONATION_LEVEL.SecurityIdentification, (int)TOKEN_TYPE.TokenPrimary, ref hUserTokenDup)) { CloseHandle(hProcess); CloseHandle(hPToken); return false; } // By default CreateProcessAsUser creates a process on a non-interactive window station, meaning // the window station has a desktop that is invisible and the process is incapable of receiving // user input. To remedy this we set the lpDesktop parameter to indicate we want to enable user // interaction with the new process. STARTUPINFO si = new STARTUPINFO(); si.cb = (int)Marshal.SizeOf(si); si.lpDesktop = @"winsta0default"; // interactive window station parameter; basically this indicates that the process created can display a GUI on the desktop // flags that specify the priority and creation method of the process int dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE; // create a new process in the current user's logon session bool result = CreateProcessAsUser(hUserTokenDup, // client's access token null, // file to execute applicationName, // command line ref sa, // pointer to process SECURITY_ATTRIBUTES ref sa, // pointer to thread SECURITY_ATTRIBUTES false, // handles are not inheritable dwCreationFlags, // creation flags IntPtr.Zero, // pointer to new environment block startingDir, // name of current directory ref si, // pointer to STARTUPINFO structure out procInfo // receives information about new process ); // invalidate the handles CloseHandle(hProcess); CloseHandle(hPToken); CloseHandle(hUserTokenDup); return result; // return the result } } } '@
For brevity I renamed the namespace, class, and function:
SuperAwesomeNameSpaceOfJustice = Session0
ApplicationLoader = AppLaunch
StartProcessAndBypassUAC = Start
Now we can then do this:
Add-Type -TypeDefinition $Source -Language CSharp $procInfo = New-Object Session0.AppLaunch+PROCESS_INFORMATION
Now we can run whatever we want like this:
[Session0.AppLaunch]::Start("cmd.exe","c:\windows\system32",[ref]$procInfo)
“CreateProcessAsUser” will return either True or False depending on if it was successful or not, which is the return of the Start function.
This just launches cmd.exe as the logged in user but theoretically you could use it to launch anything…
Enjoy!
Hi,
This is a brilliant script. I am planning on using a powershell script with a GUI as a notification-alert system in my organization. The problem is, if i wanted to invoke a remote script on remote machines, i need to use my admin account. But i tried launching this bit of code on an admin account and the start function always returns as false / no GUI pops up. What can be the remedy? Thanks!
Using PowerShell ISE, I run the following AS IS and it works just fine…
This post is exactly what I was looking for so to solve a similar issue I have with powershell.
thank you very much
I tryed to use your script and it is working well. Unfortunally the UI only appears under Windows 10 while the login-fields are visible. In the moment when there is only the picture you cannot see it. Any ideas for that?