Table of Contents

PowerShell using ASP.NET application

У меня получилось сделать все что нужно (запустить от имени пользователя оболочку powershell, передать ей скрипт и получить обратно консольный вывод с помощью информации из этих ссылок):
Описано расширение стандартного класса Process и его примемение: https://odetocode.com/blogs/scott/archive/2004/10/29/createprocessasuser.aspx
Вот тут написано как вызывать эти методы: http://rsdn.org/forum/dotnet/3287068.1

Если необходимо, чтобы Application Pool работал в Integrated, то вызов методов, которые запускают процесс, нужно обертывать в using (WindowsImpersonationContext wic = clientId.Impersonate()). Об этом рассказано тут: https://stackoverflow.com/questions/12966286/impersonate-domain-user-with-integrated-pipeline .

Например, вот обработчик нажатия кнопки, который запускает powershell-скрипт от имени авторизованного пользователя:

protected void ExecuteCode_Click(object sender, EventArgs e)
        {

            ResultBox.Text = string.Empty;

            var current = System.Security.Principal.WindowsIdentity.GetCurrent();
            WindowsIdentity clientId = (WindowsIdentity)User.Identity;

            using (WindowsImpersonationContext wic = clientId.Impersonate())
            {
                UserSpecificProcess myProcess = new UserSpecificProcess();
                myProcess.StartInfo.FileName = "powershell.exe";
                myProcess.StartInfo.Arguments = @"-File c:\temp\test.ps1";
                myProcess.StartInfo.UseShellExecute = false;
                myProcess.StartInfo.CreateNoWindow = false;
                myProcess.StartInfo.WorkingDirectory = @"c:\temp\";
                myProcess.StartAsUser();
                StreamReader output = myProcess.StandardOutput;

                string line;
                StringBuilder builder = new StringBuilder();
                while ((line = output.ReadLine()) != null)
                {
                    // We use a string builder ton create our result text
                    builder.Append(line + "\r\n");
                }

                // Encode the string in HTML (prevent security issue with 'dangerous' caracters like < >
                ResultBox.Text = Server.HtmlEncode(builder.ToString());
            }
        }

Пример ASP.NET-приложения, запускающего скрипт от имени залогиненного пользователя

В итоге вот что у меня получилось. Солюшн для работы в VisualStudio 2017 и опубликованное приложение, которое имеет кнопку для запуска powershell-скрипта (c:\temp\test.ps1) и окошко для вывода результатов.
Протестировано на IIS под Windows Server 2012R2. Application pool .NET 4 работает в Integrated Mode, от имени Local System.
Приложение опубликовано без каких-либо двоичных файлов. То есть изменения можно вносить прямо в код опубликованного приложения.

IIS Impersonation и делегирование прав доступа к ресурсам

На эту тему написано очень много. Обычно это именуется impersonation double hop issue. Имеется в виду следующее.
Когда пользователь успешно имперсонирован (олицетворен) в приложении ASP.NET, то он получает доступ к ресурсам СЕРВЕРА, на котором работает приложение от своего имени. То есть если он обращается к файлам или SQL-базе, размещенной на этом сервере, то всё работает. Однако, если приложение пытается получить доступ к ресурсам, размещенным не на этом сервере, а например к файлам на сетевом ресурсе или SQL-базе на другом сервере, то ничего не выйдет.
Для того, чтобы все работало, необходимо, чтобы СЕРВЕР на котором исполняется приложение имел право что-то делать от имени пользователя, то есть - представлять пользователя. Это называется делегированием. Пользователь, аутентифицированный на сервере приложений ASP.NET, делегирует свои права серверу.
Права серверу на делегирование пользователей выдаются с помощью оснастки Active Directory Users and Computers. Нужно открыть свойства учетной записи сервера, на котором исполняется приложение ASP.NET, перейти на вкладку Delegation и включить делегирование.
При этом, если приложение ASP.NET запускает локальный процесс на web-сервере (например - powershell), который осуществляет доступ к нелокальным ресурсам (например подключается к экземплярам Microsoft Exchange или запрашивает данные Active Directory), то делегирование не требуется. Достаточно, чтобы имперсонированный пользователь был авторизован методом Kerberos, а процесс был запущен от имени этого пользователя.

Когда Windows Authentication и делегирование работает на **localhost**, но не работает на FQDN

Еще один важный момент.
Часто можно наблюдать ситуацию, когда при доступе к ASP.NET-приложению непосредственно с самого сервера по адресу localhost все работает. И прозрачная аутентификация и делегирование. Однако, если попытаться зайти на страничку приложения по FQDN-имени сервера (даже с самого этого сервера), то страничка запрашивает имя пользователя и пароль (хотя при заходе по localhost работает прозрачная аутентификация), нормально аутентифицирует пользователя и работает имперсонация, однако делегирование не работает и доступ к ресурсам вне web-сервера запрещен.
Это происходит из-за того, что FQDN-имя сервера не внесено в список Intranet (локальных) сайтов в конфигурации браузера. В результате, браузер аутентифицирует пользователя не по протоколу Kerberos, а по протоколу NTLM, а протокол NTLM не позволяет делегирование. Об этом мне удалось прочитать тут: http://www.justskins.com/forums/iis-and-fqdn-authentication-183966.html#
А вот тут подробнее от Microsoft: https://blogs.msdn.microsoft.com/besidethepoint/2010/05/08/double-hop-authentication-why-ntlm-fails-and-kerberos-works/
Вот тут: https://blogs.msdn.microsoft.com/friis/2013/01/08/asp-net-authentication-test-page/
приведен пример тестовой страницы, которая показывает детальную информацию о методе аутентификации текущего пользователя. Утащил к себе - authpage.zip.

Настройки браузеров для корректной работы имперсонации

Internet Explorer

Параметр настроек Internet Explorer, который предписывает использовать KERBEROS находится в Internet Explorer Optinons → Security → Custom Level и называется Automatic logon with current user name and password. Если он включен, то первым для аутентификации используется KERBEROS, а в случае неудачи - NTLM. Важно знать, что включенный параметр Prompt for user name and password разрешает аутентифицировать пользователей только по NTLM.

Firefox

Если для доступа к приложению используется Firefox, то для того, чтобы он аутентифицировался по KERBROS адрес сайта приложения нужно добавить в доверенные. Для этого открыввем в Firefox страницу about:config. В поле поиска вводим negotiate. Дважды кликаем по параметру network.negotiate-auth.trusted-uris и добавляем туда адрес сайта (например - example.com).

Chrome

Chrome в windows использует настройки Internet Explorer.
В других ОС передать Chrome список доверенных сайтов можно с помощью аргумента командной строки:

--auth-negotiate-delegate-whitelist="*.adexample.pingidentity.com"

Чтобы в приложении ASP.NET работала сквозная Windows-аутентификация, имперсонация и делегирование

В итоге. Чтобы в приложении ASP.NET работала сквозная Windows-аутентификация, имперсонация и делегирование необходимо соблюдение условий:

В противном случае - не будет корректно работать делегирование и будут ошибки при доступе к сетевым ресурсам и Active Directory (ошибки типа Exception calling “GetComputerSite” with “0” argument(s): “An operations error occurred.”
Траблшутинг аутентификации Kerberos толково описан тут: https://blogs.msdn.microsoft.com/friis/2009/12/31/things-to-check-when-kerberos-authentication-fails-using-iisie/

Для одного из сервисов я описал настройку web-сервера на базе Windows Server 2016: IIS Web Server Setup for correct impersonating

Еще немного полезных ссылок

Вот этот гайд описывает как запускать процесс от имени имперсонированного пользователя. Вторая ссылка - как получить то что выводится в стандартный вывод.
ASP.NET Run Process As Impersonated User: https://support.microsoft.com/en-us/help/889251/how-to-spawn-a-process-that-runs-under-the-context-of-the-impersonated
Вот тут написано как человек пытается получить вывод от процесса запущенного с помощью CreateProcessAsUser:
https://groups.google.com/forum/#!topic/microsoft.public.dotnet.framework.interop/dM6jE-3eRq4
А вот тут представлен класс, который по идее это все делает. но у меня не получилось :( https://stackoverflow.com/questions/27986399/capture-standard-output-from-createprocessasuser-in-c-sharp

Вот тут написано как из приложения ASP.NET запустить процесс от имени импесонированного пользователя, указав имя и пароль (Impersonate a Specific User in Code): https://support.microsoft.com/en-us/help/306158/how-to-implement-impersonation-in-an-asp-net-application
Вот тут понятные классы, которые помогают запустить процесс от имени конкретного пользователя: https://habr.com/ru/post/135299/

Вот ссылки описывают как создать проект, однако в них не работает имперсонализация пользователя. http://jeffmurr.com/blog/?p=142
https://incoherenttruth.wordpress.com/2011/01/05/execute-powershell-cmdlets-from-asp-net-with-user-impersonation/
Change ASP.NET Settings per pool. https://weblogs.asp.net/owscott/setting-an-aspnet-config-file-per-application-pool
https://discussions.citrix.com/topic/277547-impersonation-seems-not-to-flow-to-powershell-runspace/

PowerShell UI Using Jenkins

https://hodgkins.io/automating-with-jenkins-and-powershell-on-windows-part-1
https://hodgkins.io/automating-with-jenkins-and-powershell-on-windows-part-2

Powershell UI Using ASP

http://waynes-world-it.blogspot.ru/2008/05/running-powersell-scripts-from-aspnet.html http://elbsolutions.com/projects/making-powershell-script-run-aspx-worked-12-hour/

Для создания web-страницы, которая сможет запускать PowerShell-скрипт потребуется:

Для вывода строк следует использовать Out-String. Для передачи параметров следует использовать $args.

web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.net>
 <defaultProxy enabled="false"/>
    </system.net>
    <system.web>
 <pages validateRequest="false"/>
 <compilation defaultLanguage="c#" debug="false"/>
 <customErrors mode="Off"/>
 <authentication mode="Windows"/>
 <identity impersonate="true"/>
    </system.web>
</configuration>

Powershell скрипт test.ps1

if ($args.count -ge 2)
{ $text = $args[1]}
write-output $text

Страница aspx - Test.aspx

<%@ Page language="c#" AutoEventWireup="true" Debug="true" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.IO" %>
<%@ Import Namespace="System.Management.Automation.Runspaces" %>
<%@ Import Namespace="System.Management.Automation" %>
<%@ Import Namespace="System.Collections.ObjectModel" %>


<script language="C#" runat="server">

// The previous lines use <%...%> to indicate script code, and they specify the namespaces to import. As mentioned earlier, the assemblies must be located in the \Bin subdirectory of the application's starting point.
// http://msdn.microsoft.com/en-us/library/aa309354(VS.71).aspx


private void Button3_Click(object sender, System.EventArgs e)
{
String fp = Server.MapPath(".") + "\\" + tPowerShellScriptName.Text;
StreamReader sr = new StreamReader(fp);
tPowerShellScriptCode.Text = sr.ReadToEnd();
sr.Close();
}

private void Button2_Click(object sender, System.EventArgs e)
{
tPowerShellScriptResult.Text = RunScript(tPowerShellScriptCode.Text);
}

// http://msdn.microsoft.com/en-us/library/ms714635(VS.85).aspx

private string RunScript(string scriptText)
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();

// Create a new runspaces.command object of type script
Command cmdScript = new Command(scriptText, true, false);
cmdScript.Parameters.Add("-t", txtInput.Text);
pipeline.Commands.Add(cmdScript);
//You could also use: pipeline.Commands.AddScript(scriptText);

// Re-format all output to strings
pipeline.Commands.Add("Out-String");

// Invoke the pipeline
Collection<PSObject> results = pipeline.Invoke();

//String sresults = pipeline.Output.Count.ToString();
//sresults = sresults + "," + results.Count.ToString();
String sresults = "";

foreach (PSObject obj in results)
{
sresults = sresults + obj.ToString();
}

// close the runspace and set to null
runspace.Close();
runspace = null;

return sresults;
}


</script>

<form id="Form1" method="post" runat="server">
<P> <asp:Label id="Label1" runat="server" Width="104px">Parameter:</asp:Label>
<asp:TextBox id="txtInput" runat="server"></asp:TextBox></P>
<P> <asp:Button id="Button3" runat="server" Text="Load" OnClick="Button3_Click"></asp:Button> </P>
<P> <asp:Button id="Button2" runat="server" Text="Run" OnClick="Button2_Click"></asp:Button> </P>
<P> <asp:Label id="Label2" runat="server" >Relative script name:</asp:Label>
<asp:TextBox id="tPowerShellScriptName" Text="test.ps1" runat="server"></asp:TextBox></P>
<P> <asp:TextBox rows="20" columns="120" TextMode="multiline" id="tPowerShellScriptCode" runat="server"></asp:TextBox></P>
<P> <asp:TextBox rows="8" columns="120" TextMode="multiline" id="tPowerShellScriptResult" runat="server"></asp:TextBox></P>
</form>

Запуск процессов от имени имперсонированного пользователя

using System;
using System.Runtime.InteropServices;
using System.Security.Principal;
using System.Text;
using System.Diagnostics;
using System.IO;
using System.ComponentModel;
using System.Reflection;
using Microsoft.Win32.SafeHandles;

namespace vmware_net
{


    /// <summary>
    /// UserSpecificProcess extends the standard Process object to 
    /// create new processes under a different user than the calling parent process.
    /// Also, the standard output of the child process redirected back to the parent process.
    /// This is class is designed to operate inside an ASP.NET web application.
    /// The assumption is that the calling thread is operating with an impersonated security token.
    /// The this class will change the imperonated security token to a primary token, 
    /// and call CreateProcessAsUser.
    /// A System.Diagnostics.Process object will be returned with appropriate properties set.
    /// To use this function, the following security priviliges need to be set for the ASPNET account 
    /// using the local security policy MMC snap-in. CreateProcessAsUser requirement.
    /// "Replace a process level token"/SE_ASSIGNPRIMARYTOKEN_NAME/SeAssignPrimaryTokenPrivilege
    /// "Adjust memory quotas for a process"/SE_INCREASE_QUOTA_NAME/SeIncreaseQuotaPrivilege
    ///
    /// This class was designed for .NET 1.1 for operating systems W2k an higher.
    /// Any other features or platform support can be implemented by using the .NET reflector and 
    /// investigating the Process class.
    /// </summary>
    public class UserSpecificProcess : Process
    {
        [StructLayout(LayoutKind.Sequential)]
        public class CreateProcessStartupInfo
        {
            public int cb;
            public string lpReserved;
            public string lpDesktop;
            public string lpTitle;
            public int dwX;
            public int dwY;
            public int dwXSize;
            public int dwYSize;
            public int dwXCountChars;
            public int dwYCountChars;
            public int dwFillAttribute;
            public int dwFlags;
            public short wShowWindow;
            public short cbReserved2;
            public IntPtr lpReserved2;
            public IntPtr hStdInput;
            public IntPtr hStdOutput;
            public IntPtr hStdError;
            public CreateProcessStartupInfo()
            {
                this.cb = Marshal.SizeOf(typeof(CreateProcessStartupInfo));
                this.lpReserved = null;
                this.lpDesktop = null;
                this.lpTitle = null;
                this.dwX = 0;
                this.dwY = 0;
                this.dwXSize = 0;
                this.dwYSize = 0;
                this.dwXCountChars = 0;
                this.dwYCountChars = 0;
                this.dwFillAttribute = 0;
                this.dwFlags = 0;
                this.wShowWindow = 0;
                this.cbReserved2 = 0;
                this.lpReserved2 = IntPtr.Zero;
                this.hStdInput = IntPtr.Zero;
                this.hStdOutput = IntPtr.Zero;
                this.hStdError = IntPtr.Zero;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public class CreateProcessProcessInformation
        {
            public IntPtr hProcess;
            public IntPtr hThread;
            public int dwProcessId;
            public int dwThreadId;
            public CreateProcessProcessInformation()
            {
                this.hProcess = IntPtr.Zero;
                this.hThread = IntPtr.Zero;
                this.dwProcessId = 0;
                this.dwThreadId = 0;
            }
        }

        [StructLayout(LayoutKind.Sequential)]
        public class SecurityAttributes
        {
            public int nLength;
            public IntPtr lpSecurityDescriptor;
            public bool bInheritHandle;
            public SecurityAttributes()
            {
                this.nLength = Marshal.SizeOf(typeof(SecurityAttributes));
            }
        }

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true, ExactSpelling = true)]
        public static extern bool CloseHandle(HandleRef handle);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool
        CreateProcess([MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, StringBuilder lpCommandLine, SecurityAttributes lpProcessAttributes, SecurityAttributes lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, CreateProcessStartupInfo lpStartupInfo, CreateProcessProcessInformation lpProcessInformation);

        [DllImport("advapi32.dll")]
        public static extern int LogonUserA(String lpszUserName, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateProcessAsUserW(IntPtr token, [MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPTStr)] string lpCommandLine, SecurityAttributes lpProcessAttributes, SecurityAttributes lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, CreateProcessStartupInfo lpStartupInfo, CreateProcessProcessInformation lpProcessInformation);

        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        public static extern bool CreateProcessAsUser(IntPtr token, [MarshalAs(UnmanagedType.LPTStr)] string lpApplicationName, [MarshalAs(UnmanagedType.LPTStr)] string lpCommandLine, SecurityAttributes lpProcessAttributes, SecurityAttributes lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, [MarshalAs(UnmanagedType.LPTStr)] string lpCurrentDirectory, CreateProcessStartupInfo lpStartupInfo, CreateProcessProcessInformation lpProcessInformation);

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
        public static extern IntPtr GetStdHandle(int whichHandle);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr CreateFile(string lpFileName, int dwDesiredAccess, int dwShareMode, SecurityAttributes lpSecurityAttributes, int dwCreationDisposition, int dwFlagsAndAttributes, HandleRef hTemplateFile);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern IntPtr CreateNamedPipe(string name, int openMode, int pipeMode, int maxInstances, int outBufSize, int inBufSize, int timeout, SecurityAttributes lpPipeAttributes);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern int GetConsoleOutputCP();

        [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool DuplicateTokenEx(HandleRef hToken, int access, SecurityAttributes tokenAttributes, int impersonationLevel, int tokenType, ref IntPtr hNewToken);

        // WinNT.h ACCESS TYPES
        const int GENERIC_ALL = 0x10000000;

        // WinNT.h enum SECURITY_IMPERSONATION_LEVEL
        const int SECURITY_IMPERSONATION = 2;

        // WinNT.h TOKEN TYPE
        const int TOKEN_PRIMARY = 1;

        // WinBase.h
        const int STD_INPUT_HANDLE = -10;
        const int STD_ERROR_HANDLE = -12;

        // WinBase.h STARTUPINFO
        const int STARTF_USESTDHANDLES = 0x100;

        // Microsoft.Win23.NativeMethods
        static IntPtr INVALID_HANDLE_VALUE = (IntPtr)(-1);
        public static HandleRef NullHandleRef = new HandleRef(null, IntPtr.Zero);

        public const int LOGON32_LOGON_INTERACTIVE = 2;
        public const int LOGON32_PROVIDER_DEFAULT = 0;

        /// <summary>
        /// Starts the process with the security token of the calling thread.
        /// If the security token has a token type of TokenImpersonation,
        /// the token will be duplicated to a primary token before calling
        /// CreateProcessAsUser.
        /// </summary>
        /// <param name="process">The process to start.</param>
        public void StartAsUser()
        {
            StartAsUser(WindowsIdentity.GetCurrent().Token);
        }

        /// <summary>
        /// Starts the process with the given security token.
        /// If the security token has a token type of TokenImpersonation,
        /// the token will be duplicated to a primary token before calling
        /// CreateProcessAsUser.
        /// </summary>
        /// <param name="process"></param>
        public void StartAsUser(IntPtr userToken)
        {
            if (StartInfo.UseShellExecute)
            {
                throw new InvalidOperationException("can't call this with shell execute");
            }

            // just assume that the securityToken is of TokenImpersonation and create a primary.
            IntPtr primayUserToken = CreatePrimaryToken(userToken);

            CreateProcessStartupInfo startupInfo = new CreateProcessStartupInfo();
            CreateProcessProcessInformation processInformation = new CreateProcessProcessInformation();

            IntPtr stdinHandle;
            IntPtr stdoutReadHandle;
            IntPtr stdoutWriteHandle = IntPtr.Zero;
            IntPtr stderrHandle;
            try
            {
                stdinHandle = GetStdHandle(STD_INPUT_HANDLE);
                MyCreatePipe(out stdoutReadHandle, out stdoutWriteHandle, false);
                stderrHandle = GetStdHandle(STD_ERROR_HANDLE);

                //assign handles to startup info
                startupInfo.dwFlags = STARTF_USESTDHANDLES;
                startupInfo.hStdInput = stdinHandle;
                startupInfo.hStdOutput = stdoutWriteHandle;
                startupInfo.hStdError = stderrHandle;

                string commandLine = GetCommandLine();
                //Set Creation Flags value 0x8000000 to hide window. To show window set it to 0
                int creationFlags = 0x8000000;
                //int creationFlags = 0;
                IntPtr environment = IntPtr.Zero;
                string workingDirectory = GetWorkingDirectory();

                // create the process or fail trying.
                //if (!CreateProcessAsUserW(
                if (!CreateProcessAsUser(
                primayUserToken,
                null,
                commandLine,
                null,
                null,
                true,
                creationFlags,
                environment,
                workingDirectory,
                startupInfo,
                processInformation))
                {
                    throw new Win32Exception();
                }
            }
            finally
            {
                // close thread handle
                if (processInformation.hThread != INVALID_HANDLE_VALUE)
                {
                    CloseHandle(new HandleRef(this, processInformation.hThread));
                }

                // close client stdout handle
                CloseHandle(new HandleRef(this, stdoutWriteHandle));
            }
            // }
            // get reader for standard output from the child
            Encoding encoding = Encoding.GetEncoding(GetConsoleOutputCP());
            //
            //StreamReader standardOutput = new StreamReader(new FileStream(stdoutReadHandle, FileAccess.Read, true, 0x1000, true), encoding);
            SafeFileHandle SafeStdOutReadHandle = new SafeFileHandle(stdoutReadHandle, true);
            StreamReader standardOutput = new StreamReader(new FileStream(SafeStdOutReadHandle, FileAccess.Read, 0x1000, true), encoding);

            // set this on the object accordingly.
            typeof(Process).InvokeMember("standardOutput",
            BindingFlags.SetField | BindingFlags.NonPublic | BindingFlags.Instance,
            null, this, new object[] { standardOutput });

            // scream if a process wasn't started instead of returning false.
            if (processInformation.hProcess == IntPtr.Zero)
            {
                throw new Exception("failed to create process");
            }

            // configure the properties of the Process object correctly
            //typeof(Process).InvokeMember("SetProcessHandle",
            //BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
            //null, this, new object[] { processInformation.hProcess });
            typeof(Process).InvokeMember("SetProcessId",
            BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance,
            null, this, new object[] { processInformation.dwProcessId });
        }

        /// <summary>
        /// Starts the process as user with provided username, domain name and password.
        /// Method loggs user with provided cretential, get token and pass it to StartAsUser method.
        /// </summary>
        public void StartAsUserWithLogon(string UserName, string Domain, string Password)
        {
            IntPtr UserToken = IntPtr.Zero;
            if (LogonUserA(UserName, Domain, Password, LOGON32_LOGON_INTERACTIVE, LOGON32_PROVIDER_DEFAULT, ref UserToken) !=0)
            {
                StartAsUser(CreatePrimaryToken(UserToken));
            }
            else
            {
                throw new Win32Exception();
            }
        }

        /// <summary>
        /// Creates a primayToken out of an existing token.
        /// </summary>
        /// <param name="userToken"></param>
        private IntPtr CreatePrimaryToken(IntPtr userToken)
        {
            SecurityAttributes securityAttributes = new SecurityAttributes();
            IntPtr primaryUserToken = IntPtr.Zero;
            if (!DuplicateTokenEx(new HandleRef(this, userToken), GENERIC_ALL, securityAttributes, SECURITY_IMPERSONATION, TOKEN_PRIMARY, ref
            primaryUserToken))
            {
                throw new Win32Exception();
            }
            return primaryUserToken;
        }

        /// <summary>
        /// Gets the appropriate commandLine from the process.
        /// </summary>
        /// <param name="process"></param>
        /// <returns></returns>
        private string GetCommandLine()
        {
            StringBuilder builder1 = new StringBuilder();
            string text1 = StartInfo.FileName.Trim();
            string text2 = StartInfo.Arguments;
            bool flag1 = text1.StartsWith("\"") && text1.EndsWith("\"");
            if (!flag1)
            {
                builder1.Append("\"");
            }
            builder1.Append(text1);
            if (!flag1)
            {
                builder1.Append("\"");
            }
            if ((text2 != null) && (text2.Length > 0))
            {
                builder1.Append(" ");
                builder1.Append(text2);
            }
            return builder1.ToString();
        }

        /// <summary>
        /// Gets the working directory or returns null if an empty string was found.
        /// </summary>
        /// <returns></returns>
        private string GetWorkingDirectory()
        {
            return (StartInfo.WorkingDirectory != string.Empty) ?
            StartInfo.WorkingDirectory : null;
        }

        /// <summary>
        /// A clone of Process.CreatePipe. This is only implemented because reflection with
        /// out parameters are a pain.
        /// Note: This is only finished for w2k and higher machines.
        /// </summary>
        /// <param name="parentHandle"></param>
        /// <param name="childHandle"></param>
        /// <param name="parentInputs">Specifies whether the parent will be performing the writes.</param>
        public static void MyCreatePipe(out IntPtr parentHandle, out IntPtr childHandle, bool parentInputs)
        {
            string pipename = @"\\.\pipe\Global\" + Guid.NewGuid().ToString();

            SecurityAttributes attributes2 = new SecurityAttributes();
            attributes2.bInheritHandle = false;

            parentHandle = CreateNamedPipe(pipename, 0x40000003, 0, 0xff, 0x1000, 0x1000, 0, attributes2);
            if (parentHandle == INVALID_HANDLE_VALUE)
            {
                throw new Win32Exception();
            }

            SecurityAttributes attributes3 = new SecurityAttributes();
            attributes3.bInheritHandle = true;
            int num1 = 0x40000000;
            if (parentInputs)
            {
                num1 = -2147483648;
            }
            childHandle = CreateFile(pipename, num1, 3, attributes3, 3, 0x40000080, NullHandleRef);
            if (childHandle == INVALID_HANDLE_VALUE)
            {
                throw new Win32Exception();
            }
        }
    }
}

Примеры использования

Запуск процесса (powershell.exe) от имени залогиненного имперсонированного пользователя

Для доступа к ресурсам за пределами сервера нужна аутентификация kerberos (обязательно), а также включенная делегация kerberos для учетки, от имени которой исполняется Application Pool. То есть, например AppPool Identity - LocalSystem, а для учетки компьютера в AD включена делегация kerberos.
На уровне всего приложения <identity impersonate=“false”/>. От имперсонированного пользователя запускается только кусок кода в using {…}.

//Get User Identity for Initializing Impersonation Context
var current = System.Security.Principal.WindowsIdentity.GetCurrent();
WindowsIdentity clientId = (WindowsIdentity)User.Identity;
string ScriptFilePath = @"";
string Arguments = @"";

using (WindowsImpersonationContext wic = clientId.Impersonate())
{
    UserSpecificProcess myProcess = new UserSpecificProcess();
    myProcess.StartInfo.FileName = "powershell.exe";
    myProcess.StartInfo.Arguments = @"-NoLogo -NoProfile -executionpolicy bypass -File " + ScriptFilePath + " " + Arguments;

    //ResultBuilder.Append(ScriptFilePath + " " + Arguments + "\r\n");

    myProcess.StartInfo.UseShellExecute = false;
    myProcess.StartInfo.CreateNoWindow = false;
    myProcess.StartInfo.WorkingDirectory = ScriptWorkingDirectory;
    myProcess.StartAsUser();
    StreamReader output = myProcess.StandardOutput;

    //wait then kill process
    if (!myProcess.WaitForExit(ScriptExecutionTimeout))
    {
        myProcess.Kill();
    }

    string line;
    while ((line = output.ReadLine()) != null)
    {
        // We use a string builder ton create our result text
        ResultBuilder.Append(line + "\r\n");
    }
}

Запуск процесса (powershell.exe) от имени конкретного пользователя

protected void RunScript()
{
    string ScriptFilePath = @"c:\temp\script.ps1";
    string Arguments = @"";
    string ScriptWorkingDirectory = @"c:\temp\";
    string UserName = "Username";
    string Domain = "DomainName_or_MachineName";
    string Password = "superpassword";

    UserSpecificProcess proc = new UserSpecificProcess();

    proc.StartInfo.UseShellExecute = false;
    proc.StartInfo.CreateNoWindow = false;
    proc.StartInfo.RedirectStandardError = true;
    proc.StartInfo.RedirectStandardOutput = true;
    proc.StartInfo.FileName = "powershell.exe";
    proc.StartInfo.WorkingDirectory = ScriptWorkingDirectory;
    proc.StartInfo.Arguments = @"-NoLogo -NoProfile -executionpolicy bypass -command " + '"' + ScriptFilePath + " " + Arguments + '"';

    proc.StartAsUserWithLogon(UserName, Domain, Password);

    while (!proc.HasExited && proc.Responding)
    {
        System.Threading.Thread.Sleep(1000);
    }
}