У меня получилось сделать все что нужно (запустить от имени пользователя оболочку 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()); } }
В итоге вот что у меня получилось. Солюшн для работы в VisualStudio 2017 и опубликованное приложение, которое имеет кнопку для запуска powershell-скрипта (c:\temp\test.ps1) и окошко для вывода результатов.
Протестировано на IIS под Windows Server 2012R2. Application pool .NET 4 работает в Integrated Mode, от имени Local System.
Приложение опубликовано без каких-либо двоичных файлов. То есть изменения можно вносить прямо в код опубликованного приложения.
На эту тему написано очень много. Обычно это именуется 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, а процесс был запущен от имени этого пользователя.
Еще один важный момент.
Часто можно наблюдать ситуацию, когда при доступе к 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, который предписывает использовать 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, то для того, чтобы он аутентифицировался по KERBROS адрес сайта приложения нужно добавить в доверенные. Для этого открыввем в Firefox страницу about:config. В поле поиска вводим negotiate. Дважды кликаем по параметру network.negotiate-auth.trusted-uris и добавляем туда адрес сайта (например - example.com).
Chrome в windows использует настройки Internet Explorer.
В других ОС передать Chrome список доверенных сайтов можно с помощью аргумента командной строки:
--auth-negotiate-delegate-whitelist="*.adexample.pingidentity.com"
В итоге. Чтобы в приложении 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/
https://hodgkins.io/automating-with-jenkins-and-powershell-on-windows-part-1
https://hodgkins.io/automating-with-jenkins-and-powershell-on-windows-part-2
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.
<?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>
if ($args.count -ge 2) { $text = $args[1]} write-output $text
<%@ 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(); } } } }
Для доступа к ресурсам за пределами сервера нужна аутентификация 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"); } }
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); } }