Execute Powershell Script from C# with users input command line - c#

I created a website that does remotely execute powershell to specific server. I would like to know how it work to pass 2 user selected values to commandline from dropdownlist on asp.net website along with powershell script file?
I already have working code with powershell script file but now adding 2 arguments in script file.The powershell arguments is,
[string]$ContainerIn=$args[0]
[int]$ips2get=$args[1]
Here a C# working codes,
//These 3 input varaibles will pass to powershell script to get specific results
string env = "";
string container = "";
string numIPs = "";
//assign dropdown selected value to variable to pass to script
container = DropDownListContainer.SelectedValue;
numIPs = DropDownListIP.SelectedValue;
if (container == "H02" || container == "H07" || container == "H08")
{
env = "Prod";
}
else
{
env = "NonProd";
}
// Create a Powershell
Runspace runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
Pipeline pipeline = runSpace.CreatePipeline();
Command invokeScript = new Command("Invoke-Command");
RunspaceInvoke invoke = new RunspaceInvoke();
//Add powershell command/script functions into scriptblock
//Somewhere on this codes that it need to add command line to go with Get-FreeAddress.ps1 file script
ScriptBlock sb = invoke.Invoke(#"{D:\Scripts\Get-FreeAddress.ps1}")[0].BaseObject as ScriptBlock;
//ScriptBlock sb = invoke.Invoke("{" + PowerShellCodeBox.Text + "}")[0].BaseObject as ScriptBlock;
invokeScript.Parameters.Add("scriptBlock", sb);
invokeScript.Parameters.Add("computername", TextBoxServer.Text);
pipeline.Commands.Add(invokeScript);
Collection<PSObject> output = pipeline.Invoke();
//splitting results in new lines
foreach (PSObject psObject in output)
{
str = str + psObject + "\r\n";
//str = psObject + "\r\n";
//str += "\n" + psObject;
//str = str + Environment.NewLine + psObject;
}
if (str == "")
{
str = "Error";
ResultBox.ForeColor = System.Drawing.ColorTranslator.FromHtml("#FF0000");
}
//print out powershell output result
ResultBox.Text = str;
}

I finally made this work,
I just need to modify to
ScriptBlock sb = invoke.Invoke(#"{D:\Scripts\Get-FreeAddress.ps1 '"+container+"' "+numIPs+"}")[0].BaseObject as ScriptBlock;
The powershell script argument will get container and numIPs variables.

Related

Run Powershell Graph script from Asp.Net

I have the following script to schedule an event on an O365 calendar.
The goal is to run this script from an Asp.net C# page.
If I run it in WindowPowershell ISE, it works perfectly.
The problem is when I run it from the page.
It returns me the following error: "The term 'Connect-MgGraph' 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."
C# CODE
public static string SetEvent(string Subject, string Date, string StartHour, string EndHour, string Location, string[] Attendees, string Body)
{
string result = "";
try
{
//Subject
string strSubject = Subject;
//Param StartDateHour && EndDateHour
DateTime datDate = Convert.ToDateTime(Date);
string strStartDateHour = datDate.Year.ToString() + "-" + datDate.Month.ToString() + "-" + datDate.Day.ToString() + "T" + StartHour;
string strEndDateHour = datDate.Year.ToString() + "-" + datDate.Month.ToString() + "-" + datDate.Day.ToString() + "T" + EndHour;
// Location
string strLocation = Location;
// Attendees
string strAttendees = "#(";
foreach (string item in Attendees)
{
strAttendees += "#{EmailAddress = #{" +
"Address = ' + item.ToString() + ' " +
"} " +
"Type = 'Required'" +
"}";
}
strAttendees += ")";
// Body
string strBody = Body;
//Secrets!
string strAppID = HIDDEN;
string strTenantID = HIDDEN;
string strClientSecret = HIDDEN;
// create Powershell runspace
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
string scriptfile = HttpContext.Current.Server.MapPath(#"~/PowerShell/scripts/Calendar/SetEvent.ps1");
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
Command myCommand = new Command(scriptfile);
CommandParameter SubjectParam = new CommandParameter("subject", strSubject);
myCommand.Parameters.Add(SubjectParam);
CommandParameter StartDateHourParam = new CommandParameter("startdatehour", strStartDateHour);
myCommand.Parameters.Add(StartDateHourParam);
CommandParameter EndDateHourParam = new CommandParameter("enddatehour", strEndDateHour);
myCommand.Parameters.Add(EndDateHourParam);
CommandParameter LocationParam = new CommandParameter("location", strLocation);
myCommand.Parameters.Add(LocationParam);
CommandParameter AttendeesParam = new CommandParameter("attendees", strAttendees);
myCommand.Parameters.Add(AttendeesParam);
CommandParameter BodyParam = new CommandParameter("body", strBody);
myCommand.Parameters.Add(BodyParam);
CommandParameter AppIDParam = new CommandParameter("appid", strAppID);
myCommand.Parameters.Add(AppIDParam);
CommandParameter TenantIDParam = new CommandParameter("tenantid", strTenantID);
myCommand.Parameters.Add(TenantIDParam);
CommandParameter ClientSecretParam = new CommandParameter("clientsecret", strClientSecret);
myCommand.Parameters.Add(ClientSecretParam);
pipeline.Commands.Add(myCommand);
pipeline.Invoke();
runspace.Close();
}
catch (Exception ex)
{
result = ex.Message;
}
return result;
}
POWERSHELL CODE
[string]$subject,
[string]$startdatehour,
[string]$enddatehour,
[string]$location,
[string]$attendees,
[string]$appid,
[string]$tenantid,
[string]$clientsecret
)
Import-Module MSAL.PS
Import-Module Microsoft.Graph.Calendar
#Generate Access Token to use in the connection string to MSGraph
$AppId = $appid
$TenantId = $tenantid
$ClientSecret = $clientsecret
$MsalToken = Get-MsalToken -TenantId $TenantId -ClientId $AppId -ClientSecret ($ClientSecret | ConvertTo-SecureString -AsPlainText -Force)
#Connect to Graph using access token
Connect-MgGraph -AccessToken $MsalToken.AccessToken -Scopes "User.Read.All","Calendars.ReadWrite"
$params = #{
Subject = $subject
Start = #{
DateTime = $startdatehour
TimeZone = "GMT Standard Time"
}
End = #{
DateTime = $enddatehora
TimeZone = "GMT Standard Time"
}
Location = #{
DisplayName = $location
LocationType = "Default"
}
IsOnlineMeeting = $true
OnlineMeetingProvider = "teamsForBusiness"
Attendees = $attendees
}
# Create event
New-MgUserEvent -UserId "a#a.net" -BodyParameter $params´´´

Calling powershell from .Net Console application

I have written a console application wherein I have called a powershell script from the console. In the powershell script I have written hello world as a return variable and it is running as expected but next time when I change the string from hello world to How are you it is not displaying the changed string. I cannot figure out myself what needs to be done to clear the pipeline or cache.
I have used the below namespace apart from default namespaces
using System.Management;
using System.Management.Automation;
using System.Collections.ObjectModel;
using System.Management.Automation.Runspaces;
static void Main(string[] args)
{
string _str = string.Empty;
_str= RunScript(#"C:\Powershell_Scripts\Test.ps1");
Console.WriteLine("Input String is =" + str);
Console.Read();
}
private static 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("Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted");
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();
pipeline.Streams.ClearStreams();
// 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();
}
Powershell Script i.e. Test1.ps1
sleep 3
$a=""
$a = "Hello word"
return $a
Here is a sample how to use the PowerShell in a Runspace
using System;
using System.Collections.Generic;
using System.Text;
using System.Management;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
// Create a runspace.
using (Runspace myRunSpace = RunspaceFactory.CreateRunspace())
{
myRunSpace.Open();
using (PowerShell powershell = PowerShell.Create())
{
// Create a pipeline with the Get-Command command.
powershell.AddScript("Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted");
powershell.AddScript(#"C:\Users\you\Desktop\a.ps1");
// add an extra command to transform the script output objects into nicely formatted strings
// remove this line to get the actual objects
powershell.AddCommand("Out-String");
// execute the script
var results = powershell.Invoke();
powershell.Streams.ClearStreams();
powershell.Commands.Clear();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
Console.WriteLine(stringBuilder.ToString());
}
}
}
}
}
Further reference: Creating a constrained runspace
Here is a code which runs as expected
enter code here
try
{
if (System.IO.File.Exists(#"c:\disk\op.txt") && (new FileInfo(#"c:\disk\op.txt").Length != 0))
{
System.IO.File.Move(#"c:\disk\op.txt", #"c:\disk\Previous_op_" + DateTime.Now.ToString("dd_MM_yyyy hh mm") + ".txt");
log.Info("Previous Output File Successfully Renamed");
}
// TODO: Add delete logic here
log.Info("Input ActionResult - Server " + Server);
log.Info("Input ActionResult - Volume " + Volume);
log.Info("Input ActionResult - Size " + size);
string userID = "dir\\" + Session["Uname"].ToString();
string userpassword = Session["Upwd"].ToString();
log.Info("username " + userID);
StringBuilder stringBuilder = new StringBuilder();
var con = new WSManConnectionInfo();
log.Info("Pushing username in PSCredential- " + userID.ToString().Trim());
con.Credential = new PSCredential(userID.ToString().Trim(), userpassword.ToString().Trim().ToSecureString());
Runspace runspace = RunspaceFactory.CreateRunspace(con);
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript("Set-ExecutionPolicy -Scope Process -ExecutionPolicy Unrestricted");
string _str = #"-Server " + Server + " -Volumeletter " + Volume + ": -deltasize " + size + " -Logfile c:\\disk\\op.txt -username " + userID + " -password " + userpassword;
log.Info("Parameter string format- " + _str.Substring(0, _str.IndexOf("-password") + 9));
pipeline.Commands.AddScript(#"C:\disk\diskerr.ps1 " + _str.ToString());
pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
pipeline.Commands.Add("Out-String");
var results = pipeline.Invoke();
runspace.Close();
runspace.Dispose();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
log.Info("Output from powershell: " + obj.ToString());
}
if (System.IO.File.Exists(#"c:\disk\op.txt") && (new FileInfo(#"c:\disk\op.txt").Length != 0))
{
fileStream = new FileStream(#"c:\test\op.txt", FileMode.Open, FileAccess.Read);
using (var streamReader = new StreamReader(fileStream, Encoding.UTF8))
{
_consoleOutput = streamReader.ReadToEnd();
}
_output = Regex.Replace(_consoleOutput, #"\r\n?|\n", "<br />");
}
return Content(_output);
}
catch (Exception ex)
{
log.Info("Error stackTrace inside Input ActionResult " + ex.StackTrace.ToString());
log.Info("Error Message inside Input ActionResult " + ex.Message.ToString());
return View();
}
You need to change the directory path and the powershell file name.

Powershell resultset not being picked up in C#

When button1 is clicked, the below code is executed which will run a PowerShell script to get the current SQL Server Instances. However when this is run, the result set (results variable) has a count of 0 rows from the PowerShell output. When I run the same code in native PowerShell it displays 3 rows with the instance names.
Can anyone advise if I am missing something?
private void button1_Click(object sender, EventArgs e)
{
//If the logPath exists, delete the file
string logPath = "Output.Log";
if (File.Exists(logPath))
{
File.Delete(logPath);
}
string[] Servers = richTextBox1.Text.Split('\n');
//Pass each server name from the listview to the 'Server' variable
foreach (string Server in Servers)
{
//PowerShell Script
string PSScript = #"
param([Parameter(Mandatory = $true, ValueFromPipeline = $true)][string] $server)
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force;
Import-Module SQLServer;
Try
{
Set-Location SQLServer:\\SQL\\$server -ErrorAction Stop;
Get-ChildItem | Select-Object -ExpandProperty Name;
}
Catch
{
echo 'No SQL Server Instances';
}
";
//Create PowerShell Instance
PowerShell psInstance = PowerShell.Create();
//Add PowerShell Script
psInstance.AddScript(PSScript);
//Pass the Server variable in to the $server parameter within the PS script
psInstance.AddParameter("server", Server);
//Execute Script
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = psInstance.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((Object)ex.Message));
}
//Loop through each of the results in the PowerShell window
foreach (PSObject result in results)
{
File.AppendAllText(logPath, result + Environment.NewLine);
// listBox1.Items.Add(result);
}
psInstance.Dispose();
}
}
To get an possible PowerShell error I would try sth. like this:
private void button1_Click(object sender, EventArgs e)
{
//If the logPath exists, delete the file
string logPath = "Output.Log";
if (File.Exists(logPath)) {
File.Delete(logPath);
}
string[] Servers = richTextBox1.Text.Split('\n');
//Pass each server name from the listview to the 'Server' variable
foreach (string Server in Servers) {
//PowerShell Script
string PSScript = #"
param([Parameter(Mandatory = $true, ValueFromPipeline = $true)][string] $server)
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned -Force;
Import-Module SQLServer;
Try
{
Set-Location SQLServer:\\SQL\\$server -ErrorAction Stop;
Get-ChildItem | Select-Object -ExpandProperty Name;
}
Catch
{
echo 'No SQL Server Instances';
}
";
using (PowerShell psInstance = PowerShell.Create()) {
psInstance.AddScript(PSScript);
psInstance.AddParameter("server", Server);
Collection<PSObject> results = psInstance.Invoke();
if (psInstance.Streams.Error.Count > 0) {
foreach (var errorRecord in psInstance.Streams.Error) {
MessageBox.Show(errorRecord.ToString());
}
}
foreach (PSObject result in results) {
File.AppendAllText(logPath, result + Environment.NewLine);
// listBox1.Items.Add(result);
}
}
}
}
The reason it isn't working is that psInstance.AddParameter only adds parameters to commands, it doesn't work with a script. You'll need to find another way of getting the $server parameter into the script. Try these two powershell examples to see what I mean. The first will output all processes (ignores the AddParameter) while the second only shows svchost processes.
1)
$ps = [system.management.automation.powershell]::create()
$ps.AddScript("get-process")
$ps.AddParameter("name","svchost")
$ps.invoke()
2)
$ps = [system.management.automation.powershell]::create()
$ps.AddCommand("get-process")
$ps.AddParameter("name","svchost")
$ps.invoke()
I managed to get round this issue by using the Win32_service instead of SQLPS.
Param([Parameter(Mandatory = $true, ValueFromPipeline = $true)][string] $server)
$localInstances = #()
[array]$captions = GWMI Win32_Service -ComputerName $server | ?{$_.Name -match 'mssql *' -and $_.PathName -match 'sqlservr.exe'} | %{$_.Caption}
ForEach($caption in $captions)
{
if ($caption -eq 'MSSQLSERVER')
{
$localInstances += 'MSSQLSERVER'
}
else
{
$temp = $caption | %{$_.split(' ')[-1]} | %{$_.trimStart('(')} | %{$_.trimEnd(')')}
$localInstances += ""$server\$temp""
}
}
$localInstances;

Unable to read Registry value using PowerShell in C#

I am not able to read specific registry values using PowerShell in C#. Here is the code:
Calling function:
public static string UserDisplayName()
{
// PowerShell Command: (Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\SessionData\1').LoggedOnDisplayName
return GetPowerShellOutputString(#"(Get-ItemProperty -Path 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\LogonUI\SessionData\1').LoggedOnDisplayName");
}
Function definition:
private static string GetPowerShellOutputString(string PsCmd)
{
try
{
string PsOut = string.Empty;
Debug.Write("PsCmd: " + PsCmd + "; ");
Runspace rs = RunspaceFactory.CreateRunspace();
rs.Open();
Pipeline pipeline = rs.CreatePipeline();
pipeline.Commands.AddScript(PsCmd);
Collection<PSObject> results = pipeline.Invoke();
rs.Close();
foreach (PSObject obj in results)
if (obj != null) PsOut += obj.ToString() + ", ";
PsOut = (PsOut == string.Empty) ? strUnavailableString : PsOut.TrimEnd(',', ' ');
Debug.WriteLine("PsOut: " + PsOut);
return PsOut;
}
catch (Exception ex)
{
Debug.WriteLine("! " + ex.Message + ex.InnerException + "\n");
return strUnavailableString;
}
}
However, the same Function definition is working perfectly if I am trying to read any other registry value e.g.:
public static string UserOUPath()
{
try
{
if (UserDomain() == SystemInformation.ComputerName) return strUnavailableString; // Non-domain account
//For consistant performance, grab OU from registry instead of AD.
string userSID = WindowsIdentity.GetCurrent().User.ToString();
string ouPath = #"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Group Policy\State\" + userSID;
string ou = GetPowerShellOutputString("(Get-ItemProperty -Path '" + ouPath + "').'Distinguished-Name'");
ou = ou.Remove(0, ou.IndexOf(",", 0) + 1); // Drop leading CN stuff
ou = ou.Remove(ou.IndexOf(",DC=", 0), ou.Length - ou.IndexOf(",DC=", 0)); // Drop trailing DC stuff
ou = ou.Replace(",OU=", "/");
ou = ou.Replace("OU=", "/");
ou = FlipOU(ou);
if (ou == null) throw new NullReferenceException();
return ou;
}
catch
{
return strUnavailableString;
}
}
For the first call (UserDisplayName()) when I did Debug mode, the results object is returning null. However if I run the same PowerShell Command in PowerShell window it is giving the value.
I am stumbled upon this as I am not able to get why and what is happening?
A couple of things. First I agree with all the comments about just using the Microsoft.Win32.Registry* classes directly in C#. However to answer your question about doing this from PowerShell, I believe what you are running into a registry virtualization issue. If your C# project is AnyCPU and run under Visual Studio it is likely running 32-bit and that registry path will be virtualized to the SysWow64 registry node. So your reg path won't exist. That can be fixed by making the exe compile as x64. Another option is to use the .NET Registry.OpenBaseKey() method will allows you to specify which reg hive you want to view (32-bit or 64-bit).
Second, you can simplify your code by using the PowerShell class e.g.:
var ps = PowerShell.Create();
ps.AddScript(PsCmd);
var results = ps.Invoke();
foreach (PSObject obj in results)
if (obj != null) PsOut += obj.ToString() + ", ";
Third, with the PowerShell class, after the Invoke() call, check the Error stream for errors like so:
foreach (var error in ps.Streams.Error)
Console.Error.WriteLine(error);

How to call powershell script with configfile,parameters in c#

I am new powershell script in c#. I have a powershell script file ps.ps1 and powershell settingfile ConsoleSettings.psc1
C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe -psconsolefile "D:\e\ConsoleSettings.psc1" -noexit -command ". 'D:\e\ps.ps1'"
run it and get "
Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false
-PasswordNotChangedFor 60 -enabled
my function result correctly.
Now, i want to get this result in c# . My code is;
private void button1_Click(object sender, EventArgs e)
{
RunScript(LoadScript(#"d:\e\ps.ps1"));
}
private string RunScript(string scriptText)
{
PSConsoleLoadException x = null; ;
RunspaceConfiguration rsconfig = RunspaceConfiguration.Create(#"d:\e\ConsoleSettings.psc1", out x);
Runspace runspace = RunspaceFactory.CreateRunspace(rsconfig);
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
pipeline.Commands.Add("Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false -PasswordNotChangedFor 60 -enabled");
Collection<PSObject> results = pipeline.Invoke();
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
private string LoadScript(string filename)
{
try
{
using (StreamReader sr = new StreamReader(filename))
{
StringBuilder fileContents = new StringBuilder();
string curLine;
while ((curLine = sr.ReadLine()) != null)
{
fileContents.Append(curLine + "\n");
}
return fileContents.ToString();
}
}
catch (Exception e)
{
string errorText = "The file could not be read:";
errorText += e.Message + "\n";
return errorText;
}
}
And then i have a error : the term "Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false -PasswordNotChangedFor 60 -enabled" 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.
How to solve this problem, or how to call powershell script with configfile, parameter like (Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false -PasswordNotChangedFor 60 -enabled) in c#
please help me...
You are adding your command line as a Command rather than a script. Commands are intended for things like cmdlets or functions without parameters. You will use the additional methods to add the parameters. A simple solution would be to just use AddScript again.
pipeline.AddScript("Get-RST -SearchRoot 'erd/user' -PasswordNeverExpires:$false -PasswordNotChangedFor 60 -enabled");

Categories