I have an ASP .NET WEb Forms project, and I want to execute power-shell script to update hosts file.
private void ExecutePowerShellScript(string scriptToExecute)
{
using (PowerShell powershelInstance = PowerShell.Create())
{
var authManger = powershelInstance.Runspace.RunspaceConfiguration.AuthorizationManager;
powershelInstance.AddScript(scriptToExecute);
Collection<PSObject> results = powershelInstance.Invoke();
if (powershelInstance.Streams.Error.Count > 0)
{
throw powershelInstance.Streams.Error[0].Exception;
}
foreach (var result in results)
{
}
}
}
There is the script:
$hostsPath = "$env:windir\System32\drivers\etc\hosts";
$hosts = get-content $hostsPath;
[System.Collections.ArrayList]$arr = $hosts;
$arr.Add(someValueHere);
$arr | Out-File $hostsPath -enc ascii;
# returns results;
$arr;
# end of the script";
I tried this: Invoke(Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted);
then this paste Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Unrestricted in the beginning of the script. Use this Set-ExecutionPolicy Unrestricted - same and same error. ccess to the path C:\Windows\System32\drivers\etc\hosts' is denied.
The script works perfectly if I ran into console application.
Update: I am running Visual Studio as administrator.
Update 2: OK, now I am using the ImpersonatedUser ,but another exception occur. "Requested registry access is not allowed."
StackTrace:
at System.ThrowHelper.ThrowSecurityException(ExceptionResource resource)
at Microsoft.Win32.RegistryKey.OpenSubKey(String name, Boolean writable)
at System.Environment.GetEnvironmentVariable(String variable, EnvironmentVariableTarget target)
at System.Management.Automation.ModuleIntrinsics.SetModulePath()
at System.Management.Automation.ExecutionContext.InitializeCommon(AutomationEngine engine, PSHost hostInterface)
at System.Management.Automation.AutomationEngine..ctor(PSHost hostInterface, RunspaceConfiguration runspaceConfiguration, InitialSessionState iss)
at System.Management.Automation.Runspaces.LocalRunspace.DoOpenHelper()
at System.Management.Automation.Runspaces.RunspaceBase.CoreOpen(Boolean syncCall)
at System.Management.Automation.PowerShell.Worker.CreateRunspaceIfNeededAndDoWork(Runspace rsToUse, Boolean isSync)
at System.Management.Automation.PowerShell.CoreInvokeHelper[TInput,TOutput](PSDataCollection`1 input, PSDataCollection`1 output, PSInvocationSettings settings)
at System.Management.Automation.PowerShell.CoreInvoke[TInput,TOutput](PSDataCollection1 input, PSDataCollection1 output,
using (ImpersonatedUser impersonatedUser = new ImpersonatedUser(username, domain, password))
{
using (PowerShell powershelInstance = PowerShell.Create())
{
powershelInstance.AddScript(scriptToExecute);
//When the .Invoke() method is called, an exception with message "Requested registry access is not allowed." was thrown.
Collection<PSObject> results = powershelInstance.Invoke();
if (powershelInstance.Streams.Error.Count > 0)
{
throw powershelInstance.Streams.Error[0].Exception;
}
}
}
Your ASP.NET executes the PowerShell script with the credentials of the worker process of the application pool, which is probably not administrative account (unless you changed it).
Modifying the hosts file is restricted to administrative accounts only, and you should consider very carefuly before you change the credentials of the worker process.
If you want to make this change then follow the instructions here: https://technet.microsoft.com/en-us/library/cc771170(v=ws.10).aspx
Again, this change can affect make your application more vulnerable to security exploits (since any exploit found in your application can be used with administrative privileges).
You may also need to turn off UAC (User Account Control) if its turned on.
Another way, is by using impersonation for temporarily elevation of your privilages. You can see a sample of a class that allow you this (wrap evething up) here: https://blogs.msdn.microsoft.com/joncole/2009/09/21/impersonation-code-in-c/
Hope this helps.
I found some other solution, but maybe it's not the best. In IIS server just add a new Application pool. In the advanced settings change the identity to custom account and enter your credentials to windows. Use the new application pool to the site.
Related
I'm calling a self-elevating powershell script from C# code. The Script resets DNS Settings.
The script works fine when called from unelevated powershell, but takes no effect when called from C# code with no exceptions thrown.
My Execution policy is temporarily set on unrestricted and I'm running Visual Studio as Admin.
Does anyone know what's wrong?
The C#:
class Program
{
static void Main(string[] args)
{
var pathToScript = #"C:\Temp\test.ps1";
Execute(pathToScript);
Console.ReadKey();
}
public static void Execute(string command)
{
using (var ps = PowerShell.Create())
{
var results = ps.AddScript(command).Invoke();
foreach (var result in results)
{
Console.WriteLine(result.ToString());
}
}
}
}
The script:
# Get the ID and security principal of the current user account
$myWindowsID = [System.Security.Principal.WindowsIdentity]::GetCurrent();
$myWindowsPrincipal = New-Object System.Security.Principal.WindowsPrincipal($myWindowsID);
# Get the security principal for the administrator role
$adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator;
# Check to see if we are currently running as an administrator
if ($myWindowsPrincipal.IsInRole($adminRole))
{
# We are running as an administrator, so change the title and background colour to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)";
$Host.UI.RawUI.BackgroundColor = "DarkBlue";
Clear-Host;
}
else {
# We are not running as an administrator, so relaunch as administrator
# Create a new process object that starts PowerShell
$newProcess = New-Object System.Diagnostics.ProcessStartInfo "PowerShell";
# Specify the current script path and name as a parameter with added scope and support for scripts with spaces in it's path
$newProcess.Arguments = "& '" + $script:MyInvocation.MyCommand.Path + "'"
# Indicate that the process should be elevated
$newProcess.Verb = "runas";
# Start the new process
[System.Diagnostics.Process]::Start($newProcess);
# Exit from the current, unelevated, process
Exit;
}
# Run your code that needs to be elevated here...
Set-DnsClientServerAddress -InterfaceIndex 9 -ResetServerAddresses
As you've just determined yourself, the primary problem was that script execution was disabled on your system, necessitating (at least) a process-level change of PowerShell's execution policy, as the following C# code demonstrates, which calls
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass before invoking the script file (*.ps1):
For an alternative approach that uses the initial session state to set the per-process execution policy, see this answer.
The approach below can in principle be used to persistently change the execution policy for the current user, namely by replacing .AddParameter("Scope", "Process") with .AddParameter("Scope", "CurrentUser")
Caveat: When using a PowerShell (Core) 7+ SDK, persistent changes to the local machine's policy (.AddParameter("Scope", "LocalMachine")) - which require running with elevation (as admin) - are seen by that SDK project only; see this answer for details.
Caveat: If the current user's / machine's execution policy is controlled by a GPO (Group Policy Object), it can NOT be overridden programmatically - neither per process, nor persistently (except via GPO changes).
class Program
{
static void Main(string[] args)
{
var pathToScript = #"C:\Temp\test.ps1";
Execute(pathToScript);
Console.ReadKey();
}
public static void Execute(string command)
{
using (var ps = PowerShell.Create())
{
// Make sure that script execution is enabled at least for
// the current process.
// For extra safety, you could try to save and restore
// the policy previously in effect after executing your script.
ps.AddCommand("Set-ExecutionPolicy")
.AddParameter("Scope", "Process")
.AddParameter("ExecutionPolicy", "Bypass")
.Invoke();
// Now invoke the script and print its success output.
// Note: Use .AddCommand() (rather than .AddScript()) even
// for script *files*.
// .AddScript() is meant for *strings
// containing PowerShell statements*.
var results = ps.AddCommand(command).Invoke();
foreach (var result in results)
{
Console.WriteLine(result.ToString());
}
// Also report non-terminating errors, if any.
foreach (var error in ps.Streams.Error)
{
Console.Error.WriteLine("ERROR: " + error.ToString());
}
}
}
}
Note that the code also reports any non-terminating errors that the script may have reported, via stderr (the standard error output stream).
Without the Set-ExecutionPolicy call, if the execution policy didn't permit (unsigned) script execution, PowerShell would report a non-terminating error via its error stream (.Streams.Error) rather than throw an exception.
If you had checked .Streams.Error to begin with, you would have discovered the specific cause of your problem sooner.
Therefore:
When using the PowerShell SDK, in addition to relying on / catching exceptions, you must examine .Streams.Error to determine if (at least formally less severe) errors occurred.
Potential issues with your PowerShell script:
You're not waiting for the elevated process to terminate before returning from your PowerShell script.
You're not capturing the elevated process' output, which you'd have to via the .RedirectStandardInput and .RedirectStandardError properties of the System.Diagnostics.ProcessStartInfo instance, and then make your script output the results.
See this answer for how to do that.
The following, streamlined version of your code addresses the first point, and invokes the powershell.exe CLI via -ExecutionPolicy Bypass too.
If you're using the Windows PowerShell SDK, this shouldn't be necessary (because the execution policy was already changed in the C# code), but it could be if you're using the PowerShell [Core] SDK, given that the two PowerShell editions have separate execution-policy settings.
# Check to see if we are currently running as an administrator
$isElevated = & { net session *>$null; $LASTEXITCODE -eq 0 }
if ($isElevated)
{
# We are running as an administrator, so change the title and background color to indicate this
$Host.UI.RawUI.WindowTitle = $myInvocation.MyCommand.Definition + "(Elevated)"
$Host.UI.RawUI.BackgroundColor = "DarkBlue"
Clear-Host
}
else {
# We are not running as an administrator, so relaunch as administrator
# Create a new process object that starts PowerShell
$psi = New-Object System.Diagnostics.ProcessStartInfo 'powershell.exe'
# Specify the current script path and name as a parameter with and support for scripts with spaces in its path
$psi.Arguments = '-ExecutionPolicy Bypass -File "{0}"' -f
$script:MyInvocation.MyCommand.Path
# Indicate that the process should be elevated.
$psi.Verb = 'RunAs'
# !! For .Verb to be honored, .UseShellExecute must be $true
# !! In .NET Framework, .UseShellExecute *defaults* to $true,
# !! but no longer in .NET Core.
$psi.UseShellExecute = $true
# Start the new process, wait for it to terminate, then
# exit from the current, unelevated process, passing the exit code through.
exit $(
try { ([System.Diagnostics.Process]::Start($psi).WaitForExit()) } catch { Throw }
)
}
# Run your code that needs to be elevated here...
Set-DnsClientServerAddress -InterfaceIndex 9 -ResetServerAddresses
I have a web service running some hosted Powershell. There is authentication at the front-end which passes credentials down to some impersonation in C#.
The application runs as the default IIS AppPool user. More or less everything ends up impersonated.
This works perfectly - although I had to tweak the .NET 'alwaysFlowImpersonationPolicy' setting as I discovered here:
Invoking Powershell via C# in a web application - issues with app pool identity
In an answer to the question above, it was also suggested I could use WinRM as an alternative method for executing the Powershell, which I didn't choose to do at the time.
For a bunch of different reasons I would like to experiment with doing away with the C# impersonation and move all of the permission elevation to WinRM. I am led to believe this is possible, although I am happy to acknowledge that it isn’t...
If I do something like this:
PSCredential psc = new PSCredential(user, password);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri("https://ahostname:5986/wsman"), "http://schemas.microsoft.com/powershell/Microsoft.PowerShell", psc);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
connectionInfo.SkipCNCheck = true;
connectionInfo.SkipCACheck = true;
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
connectionInfo.EnableNetworkAccess = true;
using (PowerShell powershell = PowerShell.Create())
{
string existingScript = #"C:\Users\Administrator\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1";
string scriptFileName = Path.GetFileName(existingScript);
string scriptText = ". " + existingScript + "; " + command;
if (command_logging == true)
{
string logMessage = "Running command via PSH wrapper: " + scriptText;
Tools.logToFile(logMessage);
}
powershell.AddScript(scriptText);
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = powershell.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((object)ex.Message));
}
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
string rawReturnValue = stringBuilder.ToString();
string returnValue = rawReturnValue.Replace(System.Environment.NewLine, "");
returnValue.Trim();
powershell.Dispose();
return returnValue;
}
I get a weird set of behaviour:
If my AppPool runs under the default credentials (ApplicationPoolIdentity), 99% of Powershell functions will not return anything meaningful.
The PSObject 'results' object in the snippet above returns 0. No error at all, it's almost as if it is not executing.
Some functions, 'Get-Process' for example, will return fine.
I thought perhaps my methodology may not transfer to using WSManConnectionInfo (i.e. dot sourcing the profile etc), however if I force the app to run under an administrator account, everything works perfectly (so much so I can swap my new and old Powershell 'wrapper' methods out seamlessly, running a bunch of other tests...)
The two 'default' cmdlets i tested:
'Get-Module'. This is in Microsoft.PowerShell.Core, works under the administrator account but does not work under ApplicationPoolIdentity.
'Get-Process'. Microsoft.PowerShell.Management, works under both ApplicationPoolIdentity and the administrator account.
I then wrapped 'Get-Process' in a test function, put it in my $profile and tried to call that. This works when the AppPool is running under the administrator account but not under the ApplicationPoolIdentity.
Almost certainly a permissions issue? Could someone please point me in the right direction?
I am trying to run PowerShell scripts using C# using this link as a reference.
So far I have got:
try
{
using (PowerShell PowerShellInstance = PowerShell.Create())
{
PowerShellInstance.AddCommand(scriptPath);
var PSOutput = PowerShellInstance.Invoke();
if (PowerShellInstance.Streams.Error.Count > 0)
{
foreach (var line in PowerShellInstance.Streams.Error)
{
Console.WriteLine(line);
}
return false;
}
else
{
return true;
}
}
}
catch (Exception ex)
{
return false;
}
Which keeps throwing an exception:
"AuthorizationManager check failed."
Inner Exception: A command that prompts the user failed because the
host program or the command
type does not support user interaction. The host was attempting to
request confirmation with the following message: Run only scripts that
you trust. While scripts from the internet can be useful, this script
can potentially harm your computer. If you trust this script, use the
Unblock-File cmdlet to allow the script to run without this warning
message. Do you want to run C:\PowerShellScripts\MyScript.ps1?
So looking at the Exception I can see it's asking me to confirm the script but there is no window for the user to interact, hence the exception.
So I started looking at how to stop the confirmation text and found Powershell New-Item: How to Accept Confirmation Automatically
But even adding:
PowerShellInstance.AddScript("$ConfirmPreference = \"None\"");
PowerShellInstance.Invoke();
Before executing my script didn't work. So is there a way of setting $ConfirmPreference = "None" for my PowerShell instance using C#?
While the accepted answer solved this specific problem, the correct way of setting $ConfirmImpact preference variable is via session state:
var sessionState = InitialSessionState.CreateDefault();
sessionState.Variables.Add(new SessionStateVariableEntry("ConfirmPreference", ConfirmImpact.None, ""));
using (PowerShell shell = PowerShell.Create(sessionState))
{
// execute commands, etc
}
(This is for visitors who came here from Google search results)
I think it has something to do with the Execution Policy. You can query the execution policy with the Cmdlet Get-ExecutionPolicy. You can:
change the Execution Policy to (for example): "Unrestricted" by
using Set-ExecutionPolicy Unrestricted or
run your script by running powershell.exe -ExecutionPolicy Bypass C:\PowerShellScripts\MyScript.ps1 or
unblock the script by using the Cmdlet Unblock-File C:\PowerShellScripts\MyScript.ps1
The following command works when called directly from powershell, but not when called within an ASP.NET application.
Invoke-Command -ComputerName remotesrv -ScriptBlock { 5 }
I suppose, there is some kind of user rights problem, but at this point I am stuck and don't know how to solve this.
The ASP.NET code looks as follows:
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
using (PowerShell powershell = PowerShell.Create())
{
var rs = "-ComputerName remotesrv";
powershell.AddScript("Set-ExecutionPolicy RemoteSigned");
var script = String.Format("Invoke-Command {0} -scriptblock {{ 5 }}", rs);
powershell.AddScript(script);
powershell.InvocationStateChanged += delegate(object sender, PSInvocationStateChangedEventArgs e)
{
if (e.InvocationStateInfo.State == PSInvocationState.Completed)
{
// Clean up
}
};
var output = new PSDataCollection<PSObject>();
output.DataAdded += delegate(object sender, DataAddedEventArgs e)
{
PSDataCollection<PSObject> myp = (PSDataCollection<PSObject>)sender;
Collection<PSObject> results = myp.ReadAll();
foreach (PSObject result in results)
{
if (result.BaseObject is int)
{
// Do something in the database
}
}
};
IAsyncResult asyncResult = powershell.BeginInvoke<PSObject, PSObject>(null, output);
asyncResult.AsyncWaitHandle.WaitOne();
}
}
);
If I don't add -ComputerName remotesrv, the script is executed.
What do you need to do, to be able to call a powershell script remotely from an ASP.NET application?
I would recommend you read through the Powershell remoting requirements specifically the User Permission portion. By default, the account creating the connection must be an administrator on the remote machine. If your web application is running under an identity that is not an administrator on the remote machine, the connection will fail. Note that this is the default behaviour and can be changed by tweaking the session configurations on the remote machine.
I had the same problem, my solution was change the user who execute IIS's application pool.
After set Enable-PsRemoting -Force and Set-ExecutionPolicy Unrestricted, (perhaps is better left ExecutionPolicy RemoteSigned), invoke-command still doesn't work.
Then I went to Task Manager, Process tab and look by w3wp.exe and the User that was executing that process was "DefaultAppPool", I guess that this user have not rights to remote access or execution on PowerShell. (Image shows Administrador because I already change it)
To change the user go to IIS Manager, in application groups, my site's application groups shows in Identity: ApplicationPoolIdentity, Select the application pool and click Advanced Settings, in Process Model, select Identity and click the ellipsis (the button with the three dots). In Personal Account sets an Administrator account with username and password, reboot IIS.
If I look by Task Manager, iis user has changed
I am invoking a get-msoluser cmdlet of office365 and i use the following cmdlet in powershell
Get-MsolUser -UserPrincipalName user#organization.onmicrosoft.com | ForEach-Object{ $_.licenses}
The output is a collection of licenses and i wanted the same script to be run in c#. so i have written the code as follows
private void displayLicenses(){
Command cmd = new Command("Get-MsolUser");
cmd.Parameters.Add("UserPrincipalName","user#organization.onmicrosoft.com");
Command cmd2 = new Command("ForEach-Object");
cmd2.Parameters.Add("$_.licenses.AccountSku");
Pipeline pipe = Office365Runspace.CreatePipeline();
pipe.Commands.Add(cmd);
pipe.Commands.Add(cmd2);
Console.WriteLine("Before invoking the pipe");
ICollection<PSObject> result = pipe.Invoke();
CheckForErrors(pipe);
Console.WriteLine("Executed command {0} + {1} with no error", cmd.CommandText, cmd2.CommandText);
foreach(PSObject obj in result){
foreach(PSPropertyInfo propInfo in obj.Properties){
Console.WriteLine(propInfo.Name+": "+propInfo.Value+" "+propInfo.MemberType);
}
}
}
But i still get an error on executing this function saying
Unhandled Exception:
System.Management.Automation.CommandNotFoundException: The term
'ForEach-Object' is not recognized as the name of a cmdlet, function,
scrip t file, or operable program. Check the spelling of the name, or
if a path was in cluded, verify that the path is correct and try
again.
I checked that my project has a reference to System.management.Automation.dll file that contains the ForEach-Object cmdlet.
I found the dll using this cmd in powershell
(Get-Command ForEach-Object).dll
Thanks,
Satya
I figured out the problem causing for the issue. It is due to the misconfigured runspace i created.
InitialSessionState initalState = InitialSessionState.Create();
initalState.ImportPSModule(new String[] { "msonline" });
//initalState.LanguageMode = PSLanguageMode.FullLanguage;
Office365Runspace = RunspaceFactory.CreateRunspace(initalState);
Office365Runspace.Open();
i was creating the initalstate with empty one,When i changed it to default one it worked fine.On creating the default one it includes all the modules that were obtained by default.
InitialSessionState initalState = InitialSessionState.CreateDefault();
it worked fine.
Thanks,
Satya
It sounds like your're trying to run that in the remote session at the Exchange server. Those are NoLanguage constrained sessions, meaning that you can only run the Exchange cmdlets in those sessions. If you want to use PowerShell core language cmdlets (like foreach-object), you have to do that in a local session and either use Import-PSSession to import the Exchange functions into your local session (implicit remoting) , or use Invoke-Command and point it at the remote session on the Exchange server.