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-аутентификация, имперсонация и делегирование необходимо соблюдение условий:
- Для приложения вцелом (Classic Application Pool mode) или для куска кода (Integrated Application Pool mode) включена имперсонация.
- Для приложения отключена аутентификация анонимных пользователей, потому, что IIS применяет первый успешный метод аутентификации.
- Принудительно включен Kerberos (Windows Authentication → Providers (справа) оставитьтолько Negotiate:Kerberos)
- Если адрес к которому обращаются пользователи отличается от имени web-сервера, то для учетки web-сервера должен быть корректно прописан SPN. Толково написано тут: http://winitpro.ru/index.php/2016/05/18/nastrojka-kerberos-avtorizacii-na-sajte-iis/
- Для учетной записи web-сервера разрешено делегирование.
- Адрес сайта, на котором размещено приложение добавлен в список Интранет сайтов.
В противном случае - не будет корректно работать делегирование и будут ошибки при доступе к сетевым ресурсам и 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-скрипт потребуется:
- Разместить aspx-файл в корневой папке web-проекта.
- Разместить web.config в корневой папке web-проекта.
- Если web-приложения работает от имени Network Service, то дать этой службе права на чтение.
- Собственно сам скрипт PowerShell
- Библиотека (dll) System.Management.Automation в папке .\bin
Для вывода строк следует использовать 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); } }
Discussion