Exchange PowerShell commands through C# - c#

I am using C# to send PowerShell commands interacting with Exchange. I have a method called initconnection which sets up my connection to Exchange.
I have another method that I call when I click a button that will send a command to powershell after the connection is established. However I am not able to continue the created connection. When I try to run a command it says the command is not found. More than likely because it doesn't have the exchange cmdlets.
Runspace runspace = System.Management.Automation.Runspaces.RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript("Set-ExecutionPolicy Unrestricted -Scope process -Force;$password = ConvertTo-SecureString -AsPlainText -Force " + password + ";$mycred = new-object -typename System.Management.Automation.PSCredential -argumentlist " + username + ",$password;$LiveCred = Get-Credential -Credential $mycred; $Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://ps.outlook.com/powershell/ -Credential $LiveCred -Authentication Basic –AllowRedirection; Import-PSSession $Session");
// pipeline.Commands.Add("Out-String");
pipeline.Invoke();
mpeAdd.Hide();
This is the initconnection method that creates the connection.
protected void Get_Mailboxes(object sender, EventArgs e) {
PowerShell powershell = PowerShell.Create();
PSCommand command = new PSCommand();
command = new PSCommand();
command.AddCommand("Get-Mailbox");
powershell.Commands = command;
powershell.Runspace = runspace; //Also it says runsapce doesn't exist in this context.
Collection<PSObject> commandResults = powershell.Invoke();
StringBuilder sb = new StringBuilder();
ArrayList boxesarray = new ArrayList();
foreach (PSObject ps in commandResults)
{
boxesarray.Add(ps.Properties["Alias"].Value.ToString());
}
boxes.DataSource = boxesarray;
boxes.DataBind();
}
This is my method I call when I click a button after the connection is create however it is not working.

You have to add the Exchange snap-in to your runspace. Take a look at Exchange for developers.

If "runspace" doesn't exist, that explains why the Get-Mailbox command is failing. Rather than managing a runspace, you could create a PowerShell instance in your initConnection method and use it wherever needed. Note this is shown with native code rather than a script.
ps = PowerShell.Create();
Set the execution policy.
ps.ClearCommands()
.AddCommand("Set-ExecutionPolicy")
.AddParameter("Scope", "Process")
.AddParameter("ExecutionPolicy", "Unrestricted")
.AddParameter("Confirm", false)
.AddParameter("Force", true)
.Invoke();
Create the credentials. Note that you should not need to call Get-Credential.
SecureString pass;
var creds = new PSCredential(username, pass);
Create and import a session.
var newSession = ps.ClearCommands()
.AddCommand("New-PSSession")
.AddParameter("ConfigurationName", "Microsoft.Exchange")
.AddParameter("ConnectionUri", "https://ps.outlook.com/powershell/")
.AddParameter("Credential", creds)
.AddParameter("Authentication", "Basic")
.AddParameter("AllowRedirection", true)
.Invoke();
var session = newSession[0];
var import = ps.ClearCommands()
.AddCommand("Import-PSSession")
.AddParameter("Session", session)
.Invoke();
ps.ClearCommands() is an extension method, added so it can be chained with AddCommand(), AddParameter(), etc:
public static PowerShell ClearCommands(this PowerShell ps)
{
if (ps.Commands != null)
ps.Commands.Clear();
return ps;
}
Use it in Get_Mailboxes()
protected void Get_Mailboxes(object sender, EventArgs e) {
var commandResults = ps.ClearCommands().AddCommand("Get-Mailbox").Invoke();
StringBuilder sb = new StringBuilder();
ArrayList boxesarray = new ArrayList();
foreach (PSObject ps in commandResults)
{
boxesarray.Add(ps.Properties["Alias"].Value.ToString());
}
boxes.DataSource = boxesarray;
boxes.DataBind();
}
When you close the app, or somewhere appropriate:
ps.ClearCommands()
.AddCommand("Get-PSSession")
.AddCommand("Remove-PSSession")
.Invoke();
ps.Dispose();

Related

Force reboot a remote computer using c# powershell

I'm trying to create a powershell script inside c# that will allow my company to paste a computer name in the field, click restart, and then force a restart on a remote computer.
Here's what I have:
private void button1_Click(object sender, EventArgs e) {
string pcName = textBox1.Text;
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
runspace.SessionStateProxy.SetVariable("Computer", pcName);
var script = string.Format("Restart-Computer -ComputerName " + pcName + " -Credential Get-Credential GLMC\\Admin -Force");
pipeline.Commands.AddScript(script);
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder sB = new StringBuilder();
foreach (PSObject pSObject in results)
sB.AppendLine(pSObject.ToString());
textBox2.Text = sB.ToString();
}
Taken from this powershell script (that works):
$Computer = Read-Host 'Enter computer name' -Verbose
$Creds = Get-Credential GLMC\Admin
Write-Host -ForegroundColor Yellow "Starting process..."
Restart-Computer -ComputerName $Computer -Credential $Creds -Force
$End= Read-Host 'Finished, press enter to continue' -Verbose
I keep getting an error in the credentials part that says A command that prompts the user failed because the host program or the command type does not support user interaction.
Instead of adding a text script to the pipeline, pass it properly as a Command with parameters. You should also add -Confirm $false.
You also need to find a way to get credentials without using Get-Credential. You could just prompt the user and create a PSCredential from that.
var creds = new PSCredential("GLMC\Admin", AskForSecurePassword());
pipeline.Commands
.AddCommand("Restart-Computer")
.AddParameter("ComputerName", pcname)
.AddParameter("Credential", creds)
.AddParameter("Force")
.AddParameter("Confirm", false);
Collection<PSObject> results = pipeline.Invoke();
I suggest you find a better way of passing credentials. Ideally you would require the whole application to be launched with admin rights, then you wouldn't need to pass the credentials separately, just rely on what it has already.

How to call Powershell script using web API (cs or asp.net)

Below is the function to keep server in SCOM maintenance mode and I would like to call this function through cs or asp.net as API call by passing variables.
function set-scomderegister {
param(
[Parameter( Mandatory = $True, ValueFromPipeline = $true)][string]
$SCOMServer,
[Parameter( Mandatory = $True, ValueFromPipeline = $true)]
$Computername
)
ForEach($Comp in $Computername)
{
New-SCManagementGroupConnection -ComputerName $SCOMServer
$numberOfMin = 100
$ReasonComment = "Server got docomissioned "
$Instance = Get-SCOMClassInstance -Name $Comp
$Time = ((Get-Date).AddMinutes($numberOfMin))
Start-SCOMMaintenanceMode -Instance $Instance -EndTime $Time -Comment $ReasonComment -Reason PlannedOther;
}
}
System.Management.Automation namespace would be useful for you.
You can install a nuget package "System.Management.Automation".
Once this is installed you will have this namespace available.
You can invoke a script with parameter as shown below:
public void RunWithParameters()
{
// create empty pipeline
PowerShell ps = PowerShell.Create();
// add command
ps.AddCommand("test-path").AddParameter("Path", Environment.CurrentDirectory); ;
var obj = ps.Invoke();
}
private string RunScript(string scriptText)
{
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
// add an extra command to transform the script
// output objects into nicely formatted strings
// remove this line to get the actual objects
// that the script returns. For example, the script
// "Get-Process" returns a collection
// of System.Diagnostics.Process instances.
pipeline.Commands.Add("Out-String");
// execute the script
Collection<psobject /> results = pipeline.Invoke();
// close the runspace
runspace.Close();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
There is another option to use Process.Start to start the powershell prompt. Then pass the file path to the process.
public static int RunPowershellScript(string ps)
{
int errorLevel;
ProcessStartInfo processInfo;
Process process;
processInfo = new ProcessStartInfo("powershell.exe", "-File " + ps);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
process = Process.Start(processInfo);
process.WaitForExit();
errorLevel = process.ExitCode;
process.Close();
return errorLevel;
}
Hope this helps.

Powershell script working on Console app but not working on ASP.NET

I Have a powershell script to connect on Skype for Business Online and it is working on powershell also on Console application but when I call from ASP.NET not working
The exception when I run through ASP.NET:
"The term 'Get-CsPowerShellEndpoint' 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"
string command = #"$PlainPassword ='****';
$UserName = '****';
$SecurePassword = $PlainPassword | ConvertTo-SecureString-AsPlainText -Force;
$SkypeOnlineCred = New - Object - TypeName System.Management.Automation.PSCredential -ArgumentList $UserName, $SecurePassword;
Remove-Variable -Name PlainPassword;
Remove-Variable -Name SecurePassword;
Remove-Variable -Name UserName;
$SkypeOnlineSession = New-CsOnlineSession Credential $SkypeOnlineCred;
Import-PSSession -Session $SkypeOnlineSession | Out-Null;";
var initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] {
"C:\\Program Files\\Common Files\\Skype for Business Online\\Modules\\SkypeOnlineConnector\\SkypeOnlineConnectorStartup.psm1"
});
using (Runspace runspace = RunspaceFactory.CreateRunspace(initial))
{
// Open runspace
runspace.Open();
// Initialize PowerShell engine
using (PowerShell shell = PowerShell.Create())
{
shell.Runspace = runspace;
// Add the script to the PowerShell object
shell.Commands.AddScript(command);
try
{
// Execute the script
var results = shell.Invoke();
if (shell.Streams.Error.Count > 0)
{
throw new Exception(shell.Streams.Error[0].Exception.Message);
}
// display results, with BaseObject converted to string
// Note : use |out-string for console-like output
return results;
}
catch (Exception e)
{
throw new Exception("On Invoke" + e.Message);
}
}
}

Access denied when accessing powershell in webapplication

Below is my powershell script:
#Accept input parameters
Param(
[Parameter(Position=0, Mandatory=$false, ValueFromPipeline=$true)]
[string] $Office365Username,
[Parameter(Position=1, Mandatory=$false, ValueFromPipeline=$true)]
[string] $Office365Password,
[string]$GroupName
)
#Remove all existing Powershell sessions
Get-PSSession | Remove-PSSession
#Did they provide creds? If not, ask them for it.
if (([string]::IsNullOrEmpty($Office365Username) -eq $false) -and ([string]::IsNullOrEmpty($Office365Password) -eq $false))
{
$SecureOffice365Password = ConvertTo-SecureString -AsPlainText $Office365Password -Force
#Build credentials object
$Office365Credentials = New-Object System.Management.Automation.PSCredential $Office365Username, $SecureOffice365Password
}
else
{
#Build credentials object
$Office365Credentials = Get-Credential
}
#Create remote Powershell session
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri https://outlook.office365.com/powershell-liveid/ -Credential $Office365Credentials -Authentication Basic -AllowRedirection
#Import the session
Import-PSSession $Session
#cmds
$result=Get-DistributionGroup -ResultSize Unlimited
return $result
#Clean up session
Remove-PSSession $Session
Below is my C# code
public string GetAllGroups()
{
int count = 1;
string getDListScript = #"C:\inetpub\wwwroot\O365Service\Scripts\GetDList.ps1";
string userName = "j*****";
string password = "****";
try
{
using (var ps = PowerShell.Create())
{
Runspace runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
ps.Runspace = runSpace;
ps.AddCommand(getDListScript).AddParameter("Office365Username", userName).AddParameter("Office365Password", password);
//IAsyncResult async = ps.BeginInvoke();
//StringBuilder stringBuilder = new StringBuilder();
var results = ps.Invoke();
PSDataCollection<ErrorRecord> errors = ps.Streams.Error;
if (errors != null && errors.Count > 0)
{
StringBuilder sb = new StringBuilder();
foreach (ErrorRecord err in errors)
{
sb.Append(err.ToString());
}
System.IO.File.WriteAllText(#"C:\inetpub\wwwroot\RestService\bin\err.text", sb.ToString());
}
count = results.Count;
}
}
catch (Exception ex)
{
return ex.Message.ToString();
}
return count.ToString();
}
When i am executing the C# code on server from Console application it is working fine but when i am executing it from Webservice it is giving me the below exception
[outlook.office365.com] Connecting to remote server
outlook.office365.com failed with the following error message : Access
is denied. For more information, see the about_Remote_Troubleshooting
Help topic.Cannot validate argument on parameter 'Session'. The
argument is null. Provide a valid value for the argument, and then try
running the command again.The term 'Get-DistributionGroup' 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.
I have set execution policy to unrestricted of the server.

To call a powershell script file (example.ps1) from C#

I tried running a script localwindows.ps1 from C# using the following Code :
PSCredential credential = new PSCredential(userName, securePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, "machineName", 5985, "/wsman", shellUri, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
String file = "C:\\localwindows.ps1";
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(file);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
}
But getting exception :'The term 'C:\localwindows.ps1' is not recognized as the name of a cmdlet, function, script file, or operable program.
So I tried the following :
PSCredential credential = new PSCredential(userName, securePassword);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false, "machineName", 5985, "/wsman", shellUri, credential);
using (Runspace runspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
runspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = runspace;
PSCommand new1 = new PSCommand();
String machinename = "machinename";
String file = "C:\\localwindows.ps1";
new1.AddCommand("Invoke-Command");
new1.AddParameter("computername", machinename);
new1.AddParameter("filepath", file);
powershell.Commands = new1;
Console.WriteLine(powershell.Commands.ToString());
Collection<PSObject> results = powershell.Invoke();
}
I am getting the error : "Cannot find path 'C:\localwindows.ps1' because it does not exist."
But using command 'Invoke-Command -ComputerName "machineName" -filepath C:\localwindows.ps1' ,from powershell in local machine created a new account in the remote machine.
How to call the script localwindows.ps1 from C#?
How to execute the command 'Invoke-Command -ComputerName "machineName" -filepath C:\localwindows.ps1' through C#?
The script localwindows.ps1 is
$comp = [adsi]“WinNT://machinename,computer”
$user = $comp.Create(“User”, "account3")
$user.SetPassword(“change,password.10")
$user.SetInfo()
Actually your invocation style should work. But in both of your examples, the script c:\localwindows.ps1 must reside on the local computer. In the Invoke-Command case, it will be copied from the local computer to the remote computer.
If, in the Invoke-Command case, the script already exists on the remote computer and you don't need to copy it over, remove the FilePath parameter and add this:
new1.AddParameter("Scriptblock", ScriptBlock.Create(file));
I've got an article that describes an easy way to run Powershell through WinRM from .NET at http://getthinktank.com/2015/06/22/naos-winrm-windows-remote-management-through-net/.
The code is in a single file if you want to just copy it and it's also a NuGet package that includes the reference to System.Management.Automation.
It auto manages trusted hosts, can run script blocks, and also send files (which isn't really supported but I created a work around). The returns are always the raw objects from Powershell.
// this is the entrypoint to interact with the system (interfaced for testing).
var machineManager = new MachineManager(
"10.0.0.1",
"Administrator",
MachineManager.ConvertStringToSecureString("xxx"),
true);
// for your specific issue I think this would be easier
var results = machineManager.RunScript(
File.ReadAllText("C:\\LocalWindows.ps1"));
// will perform a user initiated reboot.
machineManager.Reboot();
// can run random script blocks WITH parameters.
var fileObjects = machineManager.RunScript(
"{ param($path) ls $path }",
new[] { #"C:\PathToList" });
// can transfer files to the remote server (over WinRM's protocol!).
var localFilePath = #"D:\Temp\BigFileLocal.nupkg";
var fileBytes = File.ReadAllBytes(localFilePath);
var remoteFilePath = #"D:\Temp\BigFileRemote.nupkg";
machineManager.SendFile(remoteFilePath, fileBytes);
Please mark as answer if this helps. I've been using this for a while with my automated deployments. Please leave comments if you find issues.

Categories

Resources