I have a really weird issue in ASP.NET core 2.1
I am writing a simple web application, which at needed scenario calls Powershell scripts.
Impersonation works perfectly on C# level, but as soon as I invoke Powershell script, it is being invoked with default identity (the one that IIS Express is running with).
I know that in ASP.NET it was easy to enable cross-thread impersonation via aspnet.config file, but I have no clue how to perform similar implementation in ASP.NET Core.
Below code is function in question (though function is not a problem I guess).
internal void InvokeScript()
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Impersonation.LogonUser(userName, domain, password, 2, 0, out
SafeAccessTokenHandle handle);
WindowsIdentity.RunImpersonated(handle, () => {
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
//Setting needed variables
Collection<PSObject> PreFixOutput = new Collection<PSObject>();
Collection<PSObject> FixOutput = new Collection<PSObject>();
Collection<PSObject> PostFixOutput = new Collection<PSObject>();
// Setting directory to needed KB subdir and finding needed stage files
string KBtoWorkWith = RepositoryLocation + #"\" + KB.Number;
Directory.SetCurrentDirectory(KBtoWorkWith);
// Reading scripts from .ps1 and adding needed variables
string commandToRun1 = string.Format("$Computer = '{0}'; $Username = '{1}'; $KB = '{2}' ; {3}", IP, Username, KB.Number, File.ReadAllText("Pre" + KB.Number + ".ps1"));
string commandToRun2 = string.Format("$Computer = '{0}'; $Username = '{1}'; $KB = '{2}'; {3}; {4}; $success; $verbose", IP, Username, KB.Number, File.ReadAllText(KB.Number + ".ps1"), InnerFunction);
string commandToRun3 = string.Format("$Computer = '{0}'; $Username = '{1}'; $KB = '{2}' ; {3}", IP, Username, KB.Number, File.ReadAllText("Post" + KB.Number + ".ps1"));
// Invoking formed PS scripts & collecting returned data
ps.AddScript(commandToRun1);
PreFixOutput = ps.Invoke();
OutputToUser = PreFixOutput[1].BaseObject.ToString();
if (PreFixOutput[0].BaseObject.ToString() == "True")
{
IsFixApplicable = true;
if ((PreFixOutput[2].BaseObject.ToString()) != "")
{
InnerFunction = PreFixOutput[2].BaseObject.ToString();
}
else
{
InnerFunction = "";
}
FixOutput = ps.AddScript(commandToRun2).Invoke();
OutputToUser = FixOutput[1].BaseObject.ToString();
if (FixOutput[0].BaseObject.ToString() == "True")
{
PostFixOutput = ps.AddScript(commandToRun3).Invoke();
OutputToUser = PostFixOutput[1].BaseObject.ToString();
if (PostFixOutput[0].BaseObject.ToString() == "True")
{
WasFixApplied = true;
}
else
{
WasFixApplied = false;
}
}
else
{
WasFixApplied = false;
}
}
else
{
IsFixApplicable = false;
WasFixApplied = false;
}
//runspace.Close();
});
}
Just for everyone to know. I have never solved this in DEV environment. Neithet on ASP.NET CORE, nor on ASP.NET 4.5.2 . This looks to be an IIS Express issue. As soon as I have hosted fully functional IIS on my machine and published my app impersonation started working.
Related
I'm trying to use a PowerShell object with a remote runspace.
The initial connection is established, the test returns back with the correct responses, all appears good. I then assign my powershell object some commands to run, and it pops up with the following error message:
System.Management.Automation.PSObjectDisposedException: 'Cannot perform operation because object "PowerShell" has already been disposed. Object name: 'PowerShell'.'
I've not actually disposed of anything (I'd rather keep things open while I'm developing, and optimise it later).
RemoteShell (works fine, no problems):
Private PowerShell RemoteShell (string Hostname, PSCredential psCred)
{
PowerShell psCon = PowerShell.Create();
WSManConnectionInfo connectionInfo = new WSManconnectionInfo
{
Port = 5985,
AuthenticationMechanism = AuthenticationMechanism.Kerberos,
ComputerName = Hostname,
Credential= psCred,
ShellUri = "http://schemas.microsoft.com/powershell/microsoft.powershell",
IdleTimeout = 99999999
}
using (Runspace remoterunspace = RunspaceFactory.CreateRunespace(connectionInfo))
{
try {
remoteRunspace.Open();
using (psCon) {
psCon.Runspace = remoteRunspace;
psCon.AddCommand("whoami");
psCon.AddStatement().AddScript("[System.Net.Dns]::GetHostByName(($env:computerName))";
Collection<PSObject> results= psCon.Invoke();
string resultsstr = "Shell created under: " + results[0].ToString() + System.Environment.NewLine + "Shell created on: " + results[1].ToString();
AddToOutput(--- not really relevant ---);
} catch (Exception e) {
AddToOutput (--- not really relevant ---);
}
}
return psCon;
}
JobAsync (dies every time it hits the *** line):
Private Boolean jobAsync (string type, List<ScriptModule>JobList, PSCredential psCred)
{
PowerShell psCon;
<type switch kind of irrelevant>
psCon = RemoteShell(targetFQDN, psCred);
foreach(ScriptModule curMod in JobList)
{
psCon.Commands.Clear();
List<String[]> ResultsArr = new List<string[]>();
int TestNum = 0;
foreach(ScriptObject curScr in curMod.moduleScripts)
{
<< some more irrelevant stuff assembling strings >>
**** psCon.AddStatement().AddScript("$param" + pC + " = '" + paramVal + "';"); ****
}
The **** line constantly generates a PowerShell disposed error exception preventing any further execution.
This same code is also used on a local shell (running from the machine the application is running on) and this works fine.
The only difference in the code is the remoteShell utilising a runspace...
For reference, here's the localShell code:
private PowerShell LocalShell()
{
PowerShell psCon = PowerShell.Create();
try {
psCon.AddCommand("whoami");
psCon.AddStatement().AddScript("[System.Net.Dns]::GetHostByName(($env:computerName))");
Collection<PSObject> results = psCon.Invoke();
<< more irrelevant string stuff >>
} catch (Exception e) {
<< more irrelevant string stuff >>
}
return psCon;
}
my application is in C# MVC with framework 4.5.2 and want to run external file(.m) and get output from that and it is written in Octave.
Note: Output result is in a string, so I have to show that output in C# html page.
Algorithm is written in octave and also in algorithm excel file is there to read data and external parameters are also passing through C#.
I tried the things which is already posted, the main concern is that my application is hosted in azure cloud. So, I can not install octave in cloud.
Can you suggest me any other way that directly run external file(.m) with dynamic excel file and parameters.
I can show my code what I have done. Its work in local with all but I can not install Octave in Azure cloud..
I put my installed folder to root path on server but its also not working.
public Octave(string pathToOctaveBinaries, bool createWindow)
{
StartOctave(pathToOctaveBinaries, createWindow);
}
private void StartOctave(string pathToOctaveBinaries, bool createWindow)
{
_ptob = pathToOctaveBinaries;
cw = createWindow;
this.OctaveEchoString = Guid.NewGuid().ToString();
OctaveProcess = new Process();
ProcessStartInfo pi = new ProcessStartInfo();
if (pathToOctaveBinaries[pathToOctaveBinaries.Length - 1] != '\\')
pathToOctaveBinaries = pathToOctaveBinaries + "\\";
pi.FileName = pathToOctaveBinaries + "octave-cli.exe";
pi.RedirectStandardInput = true;
pi.RedirectStandardOutput = true;
pi.RedirectStandardError = true;
pi.UseShellExecute = false;
pi.CreateNoWindow = !createWindow;
pi.Verb = "open";
//
pi.WorkingDirectory = ".";
OctaveProcess.StartInfo = pi;
OctaveProcess.Start();
OctaveProcess.OutputDataReceived += new DataReceivedEventHandler(OctaveProcess_OutputDataReceived);
OctaveProcess.BeginOutputReadLine();
OctaveEntryText = ExecuteCommand(null);
}
public string ExecuteCommand(string command, int timeout)
{
if (OctaveProcess.HasExited)
{
StartOctave(_ptob, cw);
if (OctaveRestarted != null) OctaveRestarted(this, EventArgs.Empty);
}
_exitError = false;
Thread tmp = new Thread(new ParameterizedThreadStart(WorkThread));
tmp.Start(command);
if (!tmp.Join(timeout))
{
tmp.Abort();
throw new Exception("Octave timeout");
}
if (_exitError)
{
throw new Exception(_errorMessage);
}
return SharedBuilder.ToString();
}
This above is Octave class file. Where I dynamically pass the path of .exe to be run as thread.
Controller Code:
Octave octave = new Octave(OctaveFilePath, false);
string fileData = result.Data.ToString();
fileData = fileData.Replace("#ExcelFilePath#", excelFilePath);
fileData = fileData.Replace("#ABCD#", historyData);
string rasp = octave.ExecuteCommand(fileData, 30000);
From here I get string and that I show into html page.
Please refer the sample below:
RunspaceSample(string.Empty); fails with The term 'Add-VMToCluster' is not recognized as the name of a cmdlet.
Whereas the RunspaceSample("localhost") or RunspaceSample("somecomputerName") succeed. Any pointer why is it ? Does including RunspaceConnectionInfo changes the powershell version used or RunspaceConfiguration used for execution ?
Also what would be the performance impact if I create Runspace with RunSpaceConnectionInfo with ComputerName = "localhost" even for executing powershell script on a local computer ?.
Thanks
public static void RunspaceSample(string computerName)
{
Runspace rs;
if (string.IsNullOrEmpty(computerName))
{
rs = RunspaceFactory.CreateRunspace();
}
else
{
var connectionInfo = new WSManConnectionInfo
{
OperationTimeout = 10000,
OpenTimeout = 10000,
ComputerName = computerName
};
rs = RunspaceFactory.CreateRunspace(connectionInfo);
}
rs.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = rs;
string script = #"$ComputerName = 'somevm'; $null = Add-VMToCluster -Name $ComputerName -VMName $ComputerName -ErrorAction SilentlyContinue -verbose";
ps.AddScript(script);
Console.WriteLine("Script: {0}", script);
Console.WriteLine("------------------------");
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(result.ToString());
}
if (ps.HadErrors)
{
var sb = new StringBuilder();
foreach (var errorRecord in ps.Streams.Error)
{
sb.AppendFormat("\nError: {0} CategoryInfo: {1}", errorRecord.Exception.Message, (errorRecord.CategoryInfo != null) ? errorRecord.CategoryInfo.ToString() : string.Empty);
}
var errorMessage = sb.ToString();
Console.WriteLine(errorMessage);
}
}
The FailoverClusters module is only available in 64-bit PowerShell sessions. Making this assembly as 64 bit assembly resolved the module not found issue.
I am trying to return a PrimarySMTPAddress to a variable, in Powershell the code I run is this:
Get-Mailbox -identity UserName | select PrimarySMTPAddress
And it returns the correct value, I want to get this in my C# Code, I have done the following:
string getPrimarySMTP = "Get-Mailbox -identity " + username + "| select PrimarySMTPAddress";
var runSpace = RunspaceFactory.CreateRunspace(Utility.CreateConnectionInfo());
runSpace.Open();
var pipeline = runSpace.CreatePipeline();
pipeline.Commands.AddScript(getPrimarySMTP);
var primarySmtp = pipeline.Invoke();
runSpace.Dispose();
I would Expect this to return the same data, but it doesn't. I just get an exception:
The term 'select' 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.
Is this the way to return values from a powershell command?
For what version of Exchange ? for 2010 up you need to use Remote Powershell see https://msdn.microsoft.com/en-us/library/office/ff326159%28v=exchg.150%29.aspx . (even in 2007 your code would work because you haven't loaded the snapin).
Cheers
Glen
You may need to add an additional space character before the pipe. It' being concatenated to the username, with the resulting string becoming ... -identity UserName| select ..."
Here's the corrected statement:
string getPrimarySMTP = "Get-Mailbox -identity " + username + " | select PrimarySMTPAddress";
Thanks for asking this question, it helped me to lead to the answer I needed. My code resulted in the following using RemoteRunspace to an Exchange 2013 environment:
try
{
var target = new Uri(Uri);
SecureString PSPassword = new SecureString();
foreach (char c in ConfigurationManager.AppSettings["Password"])
{
PSPassword.AppendChar(c);
}
//var cred = (PSCredential)null;
PSCredential cred = new PSCredential(ConfigurationManager.AppSettings["Username"], PSPassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(target, shell, cred);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Default;
connectionInfo.OperationTimeout = 1 * 60 * 1000; // 4 minutes.
connectionInfo.OpenTimeout = 1 * 30 * 1000; // 1 minute.
using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
remoteRunspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = remoteRunspace;
powershell.AddScript(PSSnapin);
powershell.Invoke();
powershell.Streams.ClearStreams();
powershell.Commands.Clear();
Pipeline pipeline = remoteRunspace.CreatePipeline();
Command getMailBox = new Command("Get-Mailbox");
getMailBox.Parameters.Add("Identity", Username);
Command cmd = new Command("Select-Object");
string[] Parameter = new string[] { "PrimarySMTPAddress" };
cmd.Parameters.Add("Property", Parameter);
pipeline.Commands.Add(getMailBox);
pipeline.Commands.Add(cmd);
Collection<PSObject> results = pipeline.Invoke();
primarySMTPAddress = results[0].ToString();
primarySMTPAddress = primarySMTPAddress.ToUpper().Replace("#{PRIMARYSMTPADDRESS=", "");
primarySMTPAddress = primarySMTPAddress.ToUpper().Replace("}", "");
}
remoteRunspace.Close();
}
return primarySMTPAddress;
}
catch
{
return "Error";
}
Hope this helps anyone in future.
I am trying to query the users of two different Office 365 accounts simultaneously using C#. It works fine when trying from two Powershell Windows or when connecting and getting users one account after other by code. But not working when doing simultaneously using code.
On checking I found that only one log file is generated when trying from C#. But two different log files are generated when trying from PowerShell window.
Log folder location: %userprofile%\appdata\Local\Microsoft\Office365\Powershell
Which implies that when running from code, it works like running with single PowerShell window even with two runspaces.
Main method code:
Thread t1 = new Thread(() => connectandExec("admin#domain1.onmicrosoft.com", "Pwdd#123"));
Thread t2 = new Thread(() => connectandExec("admin#domain2.onmicrosoft.com", "Pwdd#123"));
t1.Start();
t2.Start();
Method that connects and gets the user:
public static void connectandExec(String userName, String password) {
InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new String[] { "MSOnline" });
Runspace runspace = RunspaceFactory.CreateRunspace(iss);
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
Command cmd = new Command("Connect-MsolService");
System.Security.SecureString pwd = new System.Security.SecureString();
foreach (Char c in password.ToCharArray()) {
pwd.AppendChar(c);
}
log("Connecting to : " + userName);
PSCredential pscred = new PSCredential(userName, pwd);
cmd.Parameters.Add("Credential", pscred);
ps.Commands.AddCommand(cmd);
ps.Invoke();
if (ps.Streams.Error.Count > 0) {
log("Error when connecting: " + userName);
foreach (ErrorRecord errRecord in ps.Streams.Error) {
log(userName + errRecord.ToString());
}
} else {
log("Connected to : " + userName);
}
ps.Commands.Clear();
try {
ps.Commands.AddScript("Get-MsolUser -All");
ICollection<PSObject> results = ps.Invoke();
if (ps.Streams.Error.Count > 0) {
log("Error when getting users: " + userName);
foreach (ErrorRecord errRecord in ps.Streams.Error) {
log(userName + errRecord.ToString());
}
} else {
foreach (PSObject obj in results) {
if (obj != null && obj.ToString() != "") {
Object val = obj.Members["UserPrincipalName"].Value;
if (val != null) {
log(userName + ":" + val.ToString());
}
}
}
}
} catch (Exception ex) {
log(userName + ":Exception during getUsers: " + ex.ToString());
}
}
Your code is trying to use threads to do something which should be done in different application domains: "An application domain forms an isolation boundary for security".
The Office 365 library will no doubt use the app domain of the current thread - and you're just using two threads which belong to the same app domain, hence the confusion / failure.