c#.Net PowerShell Class - c#

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;
}

Related

ASP.NET CORE 2.1 cross-thread impersonation

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.

Get-MsolUser command error from C#

I am trying to execute a piece of Azure script to check if the user object is synced from on-prem AD to Azure as below.
username follows the pattern of a UPN. example: john.Smith#ed.com
//Check Azure to see if user is synced to office 365
private static bool IsAccountSyncedToOffice365(string username)
{
StringBuilder cmd = CreateAzureConnectScript();
//cmd.AppendLine("Get-MsolUser -UserPrincipalName " + username + " -ErrorAction SilentlyContinue");
cmd.AppendLine("$global:res = \"false\"");
cmd.AppendLine("$global:user = \"false\"");
cmd.AppendLine("try{ if(($global:user=Get-MsolUser -UserPrincipalName " + username + " -ErrorAction Stop).ImmutableId -ne $null) { $global:res = \"true\"} } Catch { $global:errorMessage = $_.Exception.Message}");
try
{
Collection<PSObject> results;
string output, error, errorMessageAzureCnn = "";
do
{
results = null;
output = "";
error = "";
var rs = CreateAzureRunspace();
var pipe = rs.CreatePipeline();
pipe.Commands.AddScript(cmd.ToString());
results = pipe.Invoke();
output = (rs.SessionStateProxy.PSVariable.GetValue("res")) != null ? rs.SessionStateProxy.PSVariable.GetValue("res").ToString() : "false";
error = (rs.SessionStateProxy.PSVariable.GetValue("errorMessage")) != null ? rs.SessionStateProxy.PSVariable.GetValue("errorMessage").ToString() : "null";
errorMessageAzureCnn = (rs.SessionStateProxy.PSVariable.GetValue("errorMessageAzureCnn")) != null ? rs.SessionStateProxy.PSVariable.GetValue("errorMessageAzureCnn").ToString() : "null";
ExceptionManager.Publish(new Exception("LOG: Queried Azure at:" + DateTime.Now + " for user:" + username + " Result: " + output + " Error: " + error + " errorMessageAzureCnn: " + errorMessageAzureCnn));
Thread.Sleep(60000); //sleep for 60 seconds
pipe.Dispose();
rs.Close();
rs.Dispose();
} while (output.Trim().ToLower() != "true");
ExceptionManager.Publish(new Exception("LOG: " + username + " is found synced to Azure at: " + DateTime.Now));
cmd.Clear();
return true;
}
catch (Exception ex)
{
ExceptionManager.Publish(new Exception("Error checking Azure to see if the user is synced to office 365 or not.. " + ex.Message));
throw ex;
}
}
private static StringBuilder CreateAzureConnectScript()
{
StringBuilder ss = new StringBuilder();
MSCredential cred = new MSCredential();
var username = cred.username;
var pwd = cred.password;
try
{
ss.AppendLine("try {");
ss.AppendLine("$password = ConvertTo-SecureString \"" + pwd + "\" -AsPlainText –Force");
ss.AppendLine("$credential = New-Object System.Management.Automation.PsCredential(\"" + username + "\",$password)");
ss.AppendLine("$cred = Get-Credential -cred $credential");
ss.AppendLine("Import-Module MSOnline");
ss.AppendLine("Start-Sleep -s 10");
ss.AppendLine("Connect-Msolservice -cred $cred");
ss.AppendLine("} Catch { $global:errorMessageAzureCnn = $_.Exception.Message }");
//ExceptionManager.Publish(new Exception("LOG:pwd: " + pwd + " uname:" + username));
return ss;
}
catch (Exception ex)
{
ExceptionManager.Publish(new Exception("Error enabling the remote mailbox.. " + ex.Message));
throw ex;
}
}
While the script executes successfully through Powershell Window on the same server having got all the latest versions of the modules installed. When trying to execute the same command from C# code it throws the below exception collected from the powershell exception handling $global:errorMessage = $_.Exception.Message.
Show Details Exception (0): [] LOG: Queried Azure at:7/30/2015 12:00:55 PM for user:testuser0385#xxx.com Result: false Error: You must call the Connect-MsolService cmdlet before calling any other cmdlets. errorMessageAzureCnn: null
Worth mentioning that I have got the same code as below working in one server but it is throwing the below error on a production server (Windows Server 2008 R2 Datacenter) and only via the code it is happening. via the powershell window it works perfectly fine.
Good to know your thoughts about what looks wrong or needed to be looked into.
Thanks!
It suggests that the sign in is failing, but you're pushing on with the Get-MsolUser anyway. The Connect-MsolService cmdlet fails if it cannot communicate to the Microsoft Online Services Sign-In Assistant. Ref: https://community.office365.com/en-us/f/156/t/252201
Has the production server got all the pre-requisites installed: Microsoft Online Services Sign-In Assistant and .NET 3.5? We had a problem in production (Azure PAAS) where the guest OS image was automatically updated and was missing .NET 3.5, which broke our Azure AD PowerShell processes.

Using C# To Return Data From PowerShell

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.

Unable to execute two Office 365 commands simultaneously using different accounts in C#

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.

System.Diagnostics.Process.StandardOutput returning bad string that have accentuation

I have this code that execute shell commands:
public void ExecuteShellCommand(string _FileToExecute, string _CommandLine, ref string _outputMessage, ref string _errorMessage)
{
//Set process variable.
//Provides access to local and remote processes and enables you to start and stop local system processes.
System.Diagnostics.Process _Process = null;
try
{
_Process = new System.Diagnostics.Process();
_Process.StartInfo.Verb = "runas";
//Invokes the cmd process specifying the command to be executed.
var culture = new System.Globalization.CultureInfo("pt-BR", true);
Thread.CurrentThread.CurrentUICulture = new CultureInfo("pt-BR", false);
string _CMDProcess = string.Format(culture, #"{0}\cmd.exe",
new object[] { Environment.SystemDirectory });
//Pass executing file to cmd (Windows command interpreter) as a arguments
// /C tells cmd we want it to execute the comand that follows, then exit.
string _Arguments = string.Format(culture, "/C {0}",
new object[] { _FileToExecute });
//Pass any command line parameters for execution
if (!string.IsNullOrEmpty(_CommandLine))
{
_Arguments += string.Format(culture, " {0}",
new object[] { _CommandLine, culture });
}
var _ProcessStartInfo =
new System.Diagnostics.ProcessStartInfo(_CMDProcess, _Arguments);
//Sets a value indicating not to start the process in a new window.
_ProcessStartInfo.CreateNoWindow = true;
//Sets a value indicating now to use the operating system shell to start the process.
_ProcessStartInfo.UseShellExecute = false;
//Sets the value that indicates the output/input/error of an aplication is written to the Process.
_ProcessStartInfo.RedirectStandardOutput = true;
_ProcessStartInfo.RedirectStandardInput = true;
_ProcessStartInfo.RedirectStandardError = true;
_Process.StartInfo = _ProcessStartInfo;
//Starts a process resource and associates it with a Process component.
_Process.Start();
//Instructs the Process component t wait indefitely for the associated process to exit.
_errorMessage = _Process.StandardError.ReadToEnd();
_Process.WaitForExit();
//Instructs the Process component to wait indefinitely for the associated process to exit.
_outputMessage = _Process.StandardOutput.ReadToEnd();
_Process.WaitForExit();
}
catch (Win32Exception _Win32Exception)
{
//Error
MessageBox.Show("Win32 Exception caught in process: " + _Win32Exception.ToString());
}
catch (Exception _Exception)
{
//Error
MessageBox.Show("Exception caught in process: " + _Exception.ToString());
}
finally
{
_Process.Close();
_Process.Dispose();
_Process = null;
}
}
The problem is that my system language is pt-BR, the output:
_outputMessage = _Process.StandardOutput.ReadToEnd();
returns broken strings:
Returned string: "Autentica‡Æo"
Expected string: "Autenticação"
But if I use the same command inside CMD, everything returns okay, no erros or broken strings...
What is wrong with my code?
EDIT:
I'm trying execute shell commands via code. Using cmd.exe + arguments.
Working:
_ProcessStartInfo.StandardOutputEncoding = Encoding.GetEncoding(850);
Now, the encoding matches.
It is code page 850, the MS-Dos code page for Portuguese. ç = 0x87, ã = 0xc6.
Your program is currently incorrectly using code page 1252, 0x87 = ‡, 0xc6 = Æ.

Categories

Resources