Force reboot a remote computer using c# powershell - c#

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.

Related

How to call PowerShell script with azure commands in C# windows application

Below is my event, where, if I pass, simple powershell script it runs fine, but if I pass script contains any Azure commands, output is blank. (That azure command script is running fine from powershell command prompt)
private void RunScript_Click(object sender, EventArgs e)
{
string result = "";
PSDataCollection<PSObject> myOutPut = new PSDataCollection<PSObject>();
try
{
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new string[] {
#"C:\Program Files\WindowsPowerShell\Modules\AzureRM\5.2.0\AzureRM.psd1",
});
using (Runspace objRunSpace = RunspaceFactory.CreateRunspace(initial))
{
objRunSpace.Open();
using (PowerShell objPowerShell = PowerShell.Create())
{
objPowerShell.Runspace = objRunSpace;
string Script = textBoxURL.Text;
objPowerShell.AddScript(Script);
objPowerShell.AddCommand("Out-String");
IAsyncResult IvokeResult = objPowerShell.BeginInvoke<PSObject, PSObject>(null, myOutPut);
while (IvokeResult.IsCompleted == false)
{
System.Threading.Thread.Sleep(100);
}
foreach (PSObject outitem in myOutPut)
{
result += outitem.ToString();
}
}
}
textBoxOutPut.Text = result;
}
catch (Exception ex)
{
}
}
Based on my test, there is no issue with you mentioned code. I can get empty output if input a wrong command. So please make sure that your command is correct.
Another import thing is that make sure that the command it has ouput. I test the azure command Add-AzureRmAccount.
Where is you connection to Azure?
You load the module, but you have nothing establishing a connection to Azure to use the cmdlets.
In your native PoSH instance/session, you'd to have this to work:
# Set creds
$AdminCreds = Get-Credential -Credential 'admin#domain.com'
# Connect to Azure AD
Connect-AzureAD -Credential $Admincreds
Account Environment TenantId TenantDomain AccountType
------- ----------- -------- ------------ -----------
admin#contoso.onmicrosoft.com AzureCloud 11... contoso.onmicrosoft.com User
If not, you'd end up with errors like this...
Get-AzureADApplication
Get-AzureADApplication : You must call the Connect-AzureAD cmdlet before calling any other cmdlets.
At line:1 char:1
+ Get-AzureADApplication
+ ~~~~~~~~~~~~~~~~~~~~~~

Program or the command type does not support user interaction

I am running PowerShell scripts from C# successfully. In some instances I receive the following message:
A program that prompts a 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 "insert message here".
Here's a real example of the message:
I would like to handle and be able to respond to such prompts in the C# application.
Here's the code I am successfully running to execute PS scripts:
private ICollection<PSObject> PSExecute()
{
InitialSessionState iss = InitialSessionState.CreateDefault();
string script = #"C:\script\";
using (Runspace runSpace = RunspaceFactory.CreateRunspace(iss))
{
runSpace.Open();
//using (Pipeline pipeLine = runSpace.CreatePipeline())
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = runSpace;
var ps = powershell.AddScript(script);
var results = powershell.Invoke();
return results;
}
}
}
I am looking for a way to extend this so that I can handle and pass such prompts to the C# application e.g. to a MessageBox so the user can respond.

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.

Exchange PowerShell commands through 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();

how to invoke the powershell command with "format-list" and "out-file" pipeline from c#?

Hi I'm working on a C# program to call exchange 2010 powershell cmdlets in remote runspace. The ps command is:
"Get-MailboxDatabase -Server EX2010SVR1 -Status | Format-List
Identity,Guid,mounted,CircularLoggingEnabled,Recovery | Out-File
'C:\db.txt' -Encoding UTF8 -Width 8192".
My code is similar to:
static int Main(string[] args)
{
const string SHELL_URI = "http://schemas.microsoft.com/powershell/Microsoft.Exchange";
const string COMMAND = "Get-MailboxDatabase -Server EX2010SVR1 -Status | Format-List Identity,Guid,mounted,CircularLoggingEnabled,Recovery | Out-File 'C:\db.txt' -Encoding UTF8 -Width 8192";
System.Uri serverUri = new Uri("http://EX2010SVR1/powershell?serializationLevel=Full");
PSCredential creds = (PSCredential)null; // Use Windows Authentication
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(serverUri, SHELL_URI, creds);
try
{
using (Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo))
{
rs.Open();
PowerShell psh = PowerShell.Create();
psh.Runspace = rs;
psh.AddCommand(COMMAND);
Collection results = psh.Invoke();
rs.Close();
}
}
catch (Exception ex)
{
System.Console.WriteLine("exception: {0}", ex.ToString());
}
return 0;
}
When I run the c# program on Win2008 R2 which is hosting exchange 2010 server, I always get exception:
System.Management.Automation.RemoteException: The term 'Format-List' 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.
at System.Management.Automation.PowerShell.CoreInvoke[TOutput](IEnumerable input, PSDataCollection`1 output, PSInvocationSettings settings)
at System.Management.Automation.PowerShell.Invoke(IEnumerable input, PSInvocationSettings settings)
at System.Management.Automation.PowerShell.Invoke()
at RemotePS.Program.Main(String[] args)
The program is working fine without "Format-List" and "Out-File" pipelines. The entire command is also working fine in exchange 2010 management shell. I also confirmed it's powershell 2.0 on the system.
Could any one help to figure out what's going on? Any help is much appreciated.
Tom
I've got the same problem with the first embeded PowerShell I wrote. I look for a trace, but I can't find it anymore.
Here is something working for me that I adapt to your code :
static void Main(string[] args)
{
const string SHELL_URI = "http://schemas.microsoft.com/powershell/Microsoft.PowerShell";
const string COMMAND = #"get-process | format-List | Out-File -file c:\temp\jpb.txt";
System.Uri serverUri = new Uri("http://WM2008R2ENT/powershell?serializationLevel=Full");
PSCredential creds = (PSCredential)null; // Use Windows Authentication
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false,
"WM2008R2ENT",
5985,
"/wsman",
SHELL_URI,
creds);
try
{
using (Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo))
{
rs.Open();
Pipeline pipeline = rs.CreatePipeline();
string cmdLine;
cmdLine = string.Format("&{{{0}}}", COMMAND);
pipeline.Commands.AddScript(cmdLine);
Collection<PSObject> results = pipeline.Invoke();
rs.Close();
}
}
catch (Exception ex)
{
System.Console.WriteLine("exception: {0}", ex.ToString());
}
return;
}
Be carefull, I'am not using Exchange PowerShell
In the example I use pipeline, perhaps your problem comes from the way you pass the command.
You can try to work with the 'Command'-Object.
Runspace rs = RunspaceFactory.CreateRunspace();
PowerShell ps = PowerShell.Create();
Pipeline pipeline = rs.CreatePipeline();
Command cmd1 = new Command("Get-MailboxDatabase");
cmd1.Parameters.Add("Server", "EX2010SVR1");
cmd1.Parameters.Add("Status");
pipeline.Commands.Add(cmd1);
Command cmd2 = new Command("Format-List");
cmd2.Parameters.Add("Property", "Identity, Guid, mounted, CircularLoggingEnabled, Recovery");
pipeline.Commands.Add(cmd2);
Command cmd3 = new Command("Format-List");
cmd3.Parameters.Add("FilePath", "C:\db.txt");
cmd3.Parameters.Add("Encoding", "UTF8");
cmd3.Parameters.Add("Width", "8192");
pipeline.Commands.Add(cmd3);
Collection<PSObject> output = pipeline.Invoke();
See also here: Invoking powershell cmdlets from C#
I realize this is an old thread, but I wanted to present my findings, however short they are.
I ran into this same problem just recently with a colleague of mine. We managed to track the problem down to the missing runspaces. We also had to connect to the Microsoft.Exchange runspace and when we do it, the Format-List commandlet becomes unavailable. If we don't use the runspace, the commandlet works just fine.
We didn't get to solving it yet, but I intend to explore the possibility of using the RunspacePool instead of just Runspace, thus allowing the execution of both commandlets in the pipeline.

Categories

Resources