Reputation: 131
I have written some stuff to execute Powershell via C# using RunspaceFactory.
I am loading the default Powershell profile like this:
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
string scriptText = @". .\" + scriptFileName + "; " + command;
Pipeline pipeline = runspace.CreatePipeline(scriptText);
Command = a function from the profile i know works.
All of this Powershell stuff is wrapped in Impersonator.
For the avoidance of doubt, $profile = C:\Users\Administrator\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1
This is a web application running under IIS 7.5
If my IIS app runs under the 'administrator' account, it works. Under any other account it throws the error:
"The term '.\Microsoft.PowerShell_profile.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again."
As I am impersonating the 'administrator' account I assumed the location would be correct.
Some logging with an invoked 'get-location' reports the directory is what it should be.
Out of bloody mindedness i tried to force it with:
System.Environment.CurrentDirectory = dir;
... and also an attempt at an invoked 'set-location'. These work, but if I invoke 'get-location' from a runspace it reports the same directory as before (the correct one).
I thought that, perhaps, there might be a problem with the impersonation, so i wrote some tests that touch the file system in ways the app pool shouldn't be able to do. These work.
I also checked this:
string contextUserName = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
Which reports the correct user both when code is 'wrapped' in impersonator, and when it is executed via the app pool identity. Then I got desperate and tried invoke:
@"cmd /c dir"
... (and also get-Childitem). Both commands return:
{}
...when running under the app pool identity (regardless of impersonation), but a full and accurate directory listing of the correct directory when the app pool is running as 'administrator'.
I am sure I am missing something stupid and fundamental here, if anyone could give me some guidance on where I've made a mistake in my thinking (and code), that would be great.
Upvotes: 5
Views: 2924
Reputation: 141588
This is kind of a "well known" issue with using PowerShell in ASP.NET when in an impersonated context: it doesn't work the way people think it should work.
The reason for that is because PowerShell, behind the covers, spins up another thread to actually do all of its work. That new thread inside PowerShell does not inherit the impersonated context.
The fix isn't exactly pretty. This MSDN blog post recommends setting ASP.NET to always flow the impersonation policy (so that thread behind the scenes gets the identity):
<configuration>
<runtime>
<legacyImpersonationPolicy enabled="false"/>
<alwaysFlowImpersonationPolicy enabled="true"/>
</runtime>
</configuration>
Another, even uglier approach (though less hacky) is to use WinRM. You can use a loopback PowerShell session (connect to localhost) with PowerShell, and let WinRM handle the impersonation. This requires version 3.0.0.0 of System.Management.Automation.
var password = "HelloWorld";
var ss = new SecureString();
foreach (var passChar in password)
{
ss.AppendChar(passChar);
}
var psCredential = new PSCredential("username", ss);
var connectionInfo = new WSManConnectionInfo(new Uri("http://localhost:5985/wsman"), "http://schemas.microsoft.com/powershell/Microsoft.PowerShell", psCredential);
using (var runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
connectionInfo.EnableNetworkAccess = true;
using (var powershell = PowerShell.Create())
{
This is also a tacky solution because it requires WinRM's Windows Service to be running, and configuring WinRM. WinRM isn't exactly something "that just works", however it gets the job done in the first option isn't suitable.
The URL used in the WSManConnectionInfo is a localhost WinRM endpoint, by default it listens on port 5985 in version 3, and credentials are specified for the connection.
Upvotes: 3