Problem with calling a powershell function from c# - c#

I'm trying to call a function in a powershell file as follows:
string script = System.IO.File.ReadAllText(#"C:\Users\Bob\Desktop\CallPS.ps1");
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
using (Pipeline pipeline = runspace.CreatePipeline(script))
{
Command c = new Command("BatAvg",false);
c.Parameters.Add("Name", "John");
c.Parameters.Add("Runs", "6996");
c.Parameters.Add("Outs", "70");
pipeline.Commands.Add(c);
Collection<PSObject> results = pipeline.Invoke();
foreach (PSObject obj in results)
{
// do somethingConsole.WriteLine(obj.ToString());
}
}
}
The powershell function is in CallPS.ps1:
Function BatAvg
{
param ($Name, $Runs, $Outs)
$Avg = [int]($Runs / $Outs*100)/100
Write-Output "$Name's Average = $Avg, $Runs, $Outs "
}
I'm getting the following exception:
The term 'BatAvg' is not recognized as the name of a cmdlet, function, script file, or operable program.
What am I doing wrong, I admit, I know very little about PowerShell.

This seems to work for me:
using (Runspace runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.AddScript(script);
ps.Invoke();
ps.AddCommand("BatAvg").AddParameters(new Dictionary<string, string>
{
{"Name" , "John"},
{"Runs", "6996"},
{"Outs","70"}
});
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(result);
}
}

As it seems the Runspace need to be connected to a Powershell to make that work - see the sample code at MSDN.

The solution can be simplified further as in this case a non-default Runspace is not needed.
var ps = PowerShell.Create();
ps.AddScript(script);
ps.Invoke();
ps.AddCommand("BatAvg").AddParameters(new Dictionary<string, string>
{
{"Name" , "John"}, {"Runs", "6996"}, {"Outs","70"}
});
foreach (var result in ps.Invoke())
{
Console.WriteLine(result);
}
One other pitfall would be to use AddScript(script, true) in order to use a local scope. The same exception would be encountered (i.e. "The term 'BatAvg' is not recognized as the name of a cmdlet, function, script file, or operable program.").

Related

Extract output from Powershell window to a string

In my C# app, I have to run some PowerShell scripts. I copied paste from this site code about how to run the scripts.
My question: suppose I want to use the code from the link, how can I extract the PowerShell output to some string or to some .txt file?
EDIT:
If you want to test this code for answering this post, you need:
add reference to System.Management.Automation dll
add requireAdministrator inside app.manifest
Based on this link, you can extract the output with Collection<PSObject> results = pipeline.Invoke();, here a code example, note this would extract the PowerShell output to StringBuiler and would hide the PowerShell window:
string RunScript(string pathToYourScript){
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
using (var runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration))
{
runspace.Open();
runspace.SessionStateProxy.SetVariable("prog", this);
using (Pipeline pipeline = runspace.CreatePipeline())
{
if (!string.IsNullOrEmpty(path))
pipeline.Commands.AddScript(string.Format("$env:path = \"{0};\" + $env:path", pathToYourScript));
pipeline.Commands.AddScript(path);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = pipeline.Invoke();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
var outDefault = new Command("out-default");
outDefault.MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
pipeline.Commands.Add(outDefault);
return stringBuilder;
}
}
}

PowerShell Script not able to determine file paths if launched from c# code

I am running a PowerShell script using C#. The build is not able to determine the different file path written in the script but if I run script from command line it is working fine.
Here is my code for the running script:
private const string ScriptPath = "F:\\";
private const string SubPath = "build\\Build.ps1";
public Collection<PSObject> ExecuteBuildScript(BuildParams buildParams)
{
string executablePath = String.Empty;
string[] subdirectories = Directory.GetDirectories(ScriptPath);
Collection<PSObject> psOutput = null;
//Get path of appropriate branch
foreach (var subdirectory in subdirectories)
{
if (subdirectory.Contains(buildParams.Branch))
{
executablePath = subdirectory;
break;
}
}
if (!String.IsNullOrEmpty(executablePath))
{
using (PowerShell ps = PowerShell.Create())
{
//Enable the powershell execution on the system
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
RunspaceInvoke runSpaceInvoker = new RunspaceInvoke(runspace);
runSpaceInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
ps.AddScript(Path.Combine(executablePath, SubPath));
ps.AddParameter("kit", !string.IsNullOrEmpty(buildParams.Kit) ? buildParams.Kit : "3CLogic");
ps.AddParameter("config", !string.IsNullOrEmpty(buildParams.Config) ? buildParams.Config : "Release");
ps.AddParameter("version", !string.IsNullOrEmpty(buildParams.ClientVersion) ? buildParams.ClientVersion : "latest");
ps.AddParameter("revision", !string.IsNullOrEmpty(buildParams.ClientRevision) ? buildParams.ClientRevision : "latest");
ps.AddParameter("serviceversion", !string.IsNullOrEmpty(buildParams.ServiceVersion) ? buildParams.ServiceVersion : "latest");
psOutput = ps.Invoke();
// check the other output streams (for example, the error stream)
if (ps.Streams.Error.Count > 0)
{
Console.WriteLine(ps.Streams.Error[0]);
// error records were written to the error stream.
// do something with the items found.
}
}
}
return psOutput;
}
Let say I want to import another script from called script it just failed to get path. Example importing include.ps1 from build.ps1 just not working and also Get-Location pick location of IIS server location.
. build\include.ps1
Use the $MyInvocation variable to determine the current script directory and combine the path using the Join-Path cmdlet:
$scriptPath = split-path -parent $MyInvocation.MyCommand.Definition
. (Join-Path $scriptPath 'build\include.ps1')

Load C# Embedded Resource Path into PowerShell Command Class

Update: The answer below was able to get me where I needed to be. Here is the full solution, if interested in seeing Angular / WebAPI interface with PowerShell on the backend.
I have a .ps1 file saved as an embedded resource in a Scripts folder in a C# class library. What I would like to do is pass this script into a new System.Management.Automation.Runspaces.Command class as follows:
InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ExecutionPolicy = Microsoft.PowerShell.ExecutionPolicy.Unrestricted;
using (Runspace rs = RunspaceFactory.CreateRunspace(iss))
{
rs.Open();
Command queryWmi = new Command("PowerShellAPIFramework.Core.Scripts.QueryWmi.ps1");
queryWmi.Parameters.Add("query", model.query);
queryWmi.Parameters.Add("properties", model.properties);
queryWmi.Parameters.Add("computername", model.computername);
queryWmi.Parameters.Add("wmiNamespace", model.wmiNamespace);
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = rs;
ps.Commands.AddCommand(queryWmi);
var results = ps.Invoke();
if (ps.HadErrors)
{
if (ps.Streams.Error.Count > 0)
{
foreach (var error in ps.Streams.Error)
{
Console.WriteLine(error.Exception.GetExceptionMessageChain());
}
}
}
else
{
foreach (var result in results)
{
Console.WriteLine(result.ToString());
}
}
}
}
The following exception is thrown whenever I hit ps.Invoke()
"The term 'PowerShellAPIFramework.Core.Scripts.QueryWmi.ps1' 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've run the same code, but specifying a URL for a file on my hard drive, for instance, D:\Desktop\QueryWmi.ps1, and it works just fine.
After reading PetSerAl's comment, I've updated the code. Using the Command(string command, bool isScript) constructor.
using (Stream st = new MemoryStream(Properties.Resources.yourResource))
{
using (StreamReader sr = new StreamReader(st))
{
string script = sr.ReadToEnd();
using (PowerShell ps = PowerShell.Create())
{
Command cmd = new Command(script, true);
//Add Parameters
ps.Commands.AddCommand(cmd);
ps.Invoke();
}
}
}

Import PowerShell Module from C# Failing

I have seen quite a number of solutions, but none addresses my problem.
I am trying to import a custom PowerShell module called "DSInternals" to my C# DLL.
https://github.com/MichaelGrafnetter/DSInternals
Everything in my code seems just fine, but when I try to get the available module it's not loaded.
The stream responds with
The term 'Get-ADReplAccount' 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.
Where Am I going wrong with this code?
InitialSessionState init = InitialSessionState.CreateDefault();
init.ImportPSModule(new string[] { #"D:\\DSInternals\\dsinternals.psd1" }); //location of the module files
Runspace runspace = RunspaceFactory.CreateRunspace(init);
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
ps.Commands.AddCommand("Get-ADReplAccount"); //this command is un-recognized
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(result); //this always returns null
}
The issue was the .NET framework version in which the module was built in.
Adding a module which was built with a higher version of the .NET framework to the C# class will not work.
The module was built in 4.5.1 and I was working with version 2, adding
init.ThrowOnRunspaceOpenError=true;
Helped in catching the cause of the error.
Here is my final code that works
InitialSessionState init = InitialSessionState.CreateDefault();
init.ImportPSModule(new string[] { #"D:\\DSInternals\\dsinternals.psd1" }); //location of the module files
init.ThrowOnRunspaceOpenError = true;
Runspace runspace = RunspaceFactory.CreateRunspace(init);
runspace.Open();
var script =
"Get-ADReplAccount -SamAccountName peter -Domain BLABLA -Server dc.BLABLA.co.za -Credential $cred -Protocol TCP"; //my powershell script
_powershell = PowerShell.Create().AddScript(script);
_powershell.Runspace = runspace;
var results = _powershell.Invoke();
foreach (var errorRecord in _powershell.Streams.Progress)
Console.WriteLine(errorRecord);
foreach (var errorRecord in _powershell.Streams.Debug)
Console.WriteLine(errorRecord);
foreach (var errorRecord in _powershell.Streams.Error)
Console.WriteLine(errorRecord);
foreach (var errorRecord in _powershell.Streams.Warning)
Console.WriteLine(errorRecord);
var stringBuilder = new StringBuilder();
foreach (var obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();

Create SharePoint Site Collection Web Service

I've been attempting to create a site collection from a custom web service I have built in C#. I already had some methods in this that ran some powershell commands so I figured I would just create the site using powershell commands like the code below. This code runs fine without error but does not create a site collection, seems as if it is doing nothing. Is there a better way or can someone see what may be wrong below?
public string CreateSiteCollection(string urlroot, string urlname, string database, string primaryadmin, string secondadmin, string language, string description, string title, string template)
{
//Find language and template code
string lang_code = get_lang_code(language);
string temp_code = get_temp_code(template);
Call the PowerShell.Create() method to create an empty pipeline
PowerShell ps = PowerShell.Create();
// Place our script in the string myscript
string myscript = string.Format(#"
Add-PSSnapin Microsoft.SharePoint.Powershell -erroraction 'silentlycontinue'
$template = Get-SPWebTemplate ""{0}""
New-SPSite {1}{2} -OwnerAlias '{3}' -SecondaryOwnerAlias '{4}' -Language {5} -Description '{6}' -ContentDatabase {7} -Template $template -Name '{8}'
", temp_code, urlroot, urlname, primaryadmin, secondadmin, lang_code, description, database, title);
// Create PowerShell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline(); // create pipepline then feed it myscript
pipeline.Commands.AddScript(myscript);
pipeline.Commands.Add("Out-String");
Collection<PSObject> results;
try
{
// Executing the script
results = pipeline.Invoke();
}
catch (Exception e)
{
// An error occurred, return the exception
return string.Format("Exception caught: {0}", e);
}
runspace.Close();
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
string output = stringBuilder.ToString().Trim();
if (output == "")
{
return "PowerShell Commands ran sucessfully with no output";
}
else
{
return String.Format("PowerShell Commands ran sucessfully and returned the following: {0}", output);
}
} // End of CreateSiteColleciton
I may use the Admin.asmx web service instead, but it would be easier if I could get this working because it allows me more customization.
Is there a reason why you can't call the SharePoint server side object model directly from your web service like this, http://msdn.microsoft.com/en-us/library/office/ms411953(v=office.14).aspx.
Or is there a requirement to go through a set of CmdLets?
I decided to go the with the object model just because it makes more sense. Below is the code for creating a new site collection:
SPSecurity.RunWithElevatedPrivileges(delegate()
{
using (SPSite site = new SPSite("http://servername:port/"))
{
using (SPWeb web = site.OpenWeb())
{
HttpContext.Current = null;
site.AllowUnsafeUpdates = true;
web.AllowUnsafeUpdates = true;
var newSite = site.WebApplication.Sites.Add(....);
}
}
});

Categories