Powershell resultset not being picked up in C# - 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;

Related

Asynchronously running multiple PowerShell scripts from C#

Short description of what I`m trying to do:
I am working on a .Net WinForm application from where I am trying to run multiple PowerShell scripts on a remote server and display results on the form.
At this moment I`m executing the scripts synchronously and this is causing me problems with long running scripts.
Any idea on how I could make this function to be executed Asynchronously?
public string NewPsSession(string ServerName, string command)
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
PowerShell psSession = PowerShell.Create();
psSession.Commands.AddScript("$sessions = New-PSSession -ComputerName " + ServerName + Environment.NewLine
+ "Invoke-Command -session $sessions -ScriptBlock {" + command + "}" + Environment.NewLine
+ "Remove-PSSession -Session $sessions" + Environment.NewLine);
psSession.Commands.AddCommand("Out-String");
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = psSession.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((object)ex.Message));
}
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
Any suggestion would be much appreciated. Thanks!
Change your NewPsSesson method to async and return a Task<string>. Then move your code into a Task<string>.Run() block and await it. Then you can either await your NewPsSession() method or monitor it as a task as I have done in Main()
class Program
{
public static void Main(string[] args)
{
Task<string> task = NewPsSession("", "");
while (!task.IsCompleted)
{
Task.Delay(500).Wait();
Console.WriteLine("Waiting...");
}
Console.WriteLine(task.Result);
Console.WriteLine("Done");
}
public static async Task<string> NewPsSession(string ServerName, string command)
{
var result = await Task<string>.Run(() =>
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
PowerShell psSession = PowerShell.Create();
psSession.Commands.AddScript("$sessions = New-PSSession -ComputerName " + ServerName + Environment.NewLine
+ "Invoke-Command -session $sessions -ScriptBlock {" + command + "}" + Environment.NewLine
+ "Remove-PSSession -Session $sessions" + Environment.NewLine);
psSession.Commands.AddCommand("Out-String");
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = psSession.Invoke();
}
catch (Exception ex)
{
results.Add(new PSObject((object)ex.Message));
}
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
});
return result;
}
}

Call Powershell function from C#

Hi to all I want to call in C# a powershell script. Inside ps1 file I have implemented a function.
Powershell script:
Add-Type -path 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.Runtime.dll'
Add-Type -path 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\16\ISAPI\Microsoft.SharePoint.Client.dll'
#$CourseName = Read-host "Enter site name"
CreateBlogSubsite($SiteName)
Function CreateBlogSubsite($SiteName)
{
$user = "johndoe#tenant.onmicrosoft.com";
$pass = "P3003ksi434!";
$secpw = $pass | ConvertTo-SecureString -AsPlainText -Force
$SiteURL = "https://tenant.sharepoint.com/sites/SalesSite/"
$Context = New-Object Microsoft.SharePoint.Client.ClientContext($SiteURL);
$Creds = New-Object Microsoft.SharePoint.Client.SharePointOnlineCredentials($user,$secpw);
$Context.Credentials = $Creds;
Try {
#Specify Subsite details
$WebCI = New-Object Microsoft.SharePoint.Client.WebCreationInformation
$WebCI.Title = $SiteName + " Blog" # sitename
$WebCI.WebTemplate = "Blog#0" #Blog Site #site template
$WebCI.Url = $SiteName + "_Blog"
$SubWeb = $Context.Web.Webs.Add($WebCI)
$Context.ExecuteQuery()
$URI = $SiteURL + $WebCI.Url
return $URI
#Write-host "Subsite Created Successfully! Url is: " + $SiteName + "1Blog" -ForegroundColor Green
}
catch {
write-host "Error: $($_.Exception.Message)" -foregroundcolor Red
}
}
Here is my console program, where I call PS script:
static void Main(string[] args)
{
// Execute powershell script
// Initialize PowerShell engine
var shell = PowerShell.Create();
//Add the script via a pre-made ps1 file
shell.Commands.AddScript(#"C:\\Users\\zeb\\source\\CreateCourseBlog.ps1");
shell.Commands.AddParameter("$SiteName", "Desti");
// Execute the script
var results = shell.Invoke(); // I want to get output of Poweshell function
Console.Write(results);
}
But it does not works :( . So, does not create subsite when I call script from c#
This should work:
Runspace rs = RunspaceFactory.CreateRunspace();
rs.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = rs;
ps.AddScript($#"C:\Users\zeb\source\CreateCourseBlog.ps1 -SiteName Desti");
ps.AddCommand("Out-String");
var psOutput = ps.Invoke();
foreach (var item in psOutput)
{
if (item == null) continue;
Console.WriteLine(item.BaseObject.ToString());
}
if (ps.Streams.Error.Count > 0)
Console.WriteLine($"Errors ({ps.Streams.Error.Count}):\n");
foreach (var err in ps.Streams.Error)
Console.WriteLine(" - " + err);
}
In addition to this code you should add next code to the top of your powershell script:
Param(
[string] $SiteName
)

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.

Execute Powershell Script from C# with users input command line

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.

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