I am currently trying to use c# to call the Get-SMBShare which can be used in Powershell... however, it's throwing this error:
Exception:Caught: "The term 'Get-SMBShare' 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." (System.Management.Automation.CommandNotFoundException)
A System.Management.Automation.CommandNotFoundException was caught: "The term 'Get-SMBShare' 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."
Time: 25/10/2015 19:17:59
Thread:Pipeline Execution Thread[6028]
My first language is PowerShell, so I'm trying to translate a GUI tool from PowerShell to C#, and the tool uses hundreds of PS commands - is there something I should be calling? I'm testing things out in console here.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using System.Threading.Tasks;
namespace ConsoleApplication2
{
class Program
{
private static void GetShareNames()
{
// Call the PowerShell.Create() method to create an
// empty pipeline.
PowerShell ps = PowerShell.Create();
ps.AddCommand("Get-SmbShare");
Console.WriteLine("Name Path");
Console.WriteLine("----------------------------");
// Call the PowerShell.Invoke() method to run the
// commands of the pipeline.
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(
"{0,-24}{1}",
result.Members["Name"].Value,
result.Members["Path"].Value);
} // End foreach.
Console.ReadLine();
} // End Main.
static void Main(string[] args)
{
GetShareNames();
}
}
}
You need to import the module first. Stick in this line before you try to execute the Get-SmbShare command:
ps.AddCommand("Import-Module").AddArgument("SmbShare");
ps.Invoke();
ps.Commands.Clear();
ps.AddCommand("Get-SmbShare");
Another way is to initialize the runspace with the SmbShare module pre-loaded e.g.:
InitialSessionState initial = InitialSessionState.CreateDefault();
initial.ImportPSModule(new[] {"SmbShare"} );
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
PowerShell ps = PowerShell.Create();
ps.Runspace = runspace;
Related
I build a 32-bit .NET DLL that executes PowerShell scripts.
I need it to be able to run scripts alternatively as 64-bit and 32-bit.
I already know how to do it with the command line:
C:\Windows\Sysnative\cmd /c powershell -ExecutionPolicy ByPass "& 'script.ps1' arguments"
C:\Windows\SysWOW64\cmd /c powershell -ExecutionPolicy ByPass "& 'script.ps1' arguments"
But I need to be able to use the interface to C#, with either the System.Management.Automation.PowerShell class or the System.Management.Automation.Runspaces.Pipeline class, in order to asynchronously collect outputs from the script.
The comment from #PetSerAl is the solution. With an out of process runspace, I can change the bitness.
I copy his code here:
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
public static class TestApplication {
public static void Main() {
Console.WriteLine(Environment.Is64BitProcess);
using(PowerShellProcessInstance pspi = new PowerShellProcessInstance()) {
string psfn = pspi.Process.StartInfo.FileName;
psfn=psfn.ToLowerInvariant().Replace("\\syswow64\\", "\\sysnative\\");
pspi.Process.StartInfo.FileName=psfn;
using(Runspace r = RunspaceFactory.CreateOutOfProcessRunspace(null, pspi)) {
r.Open();
using(PowerShell ps = PowerShell.Create()) {
ps.Runspace=r;
ps.AddScript("[Environment]::Is64BitProcess");
foreach(PSObject pso in ps.Invoke()) {
Console.WriteLine(pso);
}
}
}
}
}
}
I have been looking around here but unable to get specifics on implementing this PowerShell cmdlet in C#. I attempted the following but failed to get it to compile and run.
The cmdlet I would like to run in PowerShell from C#:
Restart-Computer -Computername (Get-Content C:\machineslist.txt) -Credential Administrator -Force
Here is my humble attempt:
PowerShell ps = PowerShell.Create();
ps.AddCommand("Restart-Computer");
ps.AddParameter("-ComputerName");
ScriptBlock filter2 = ScriptBlock.Create("(Get-Content C:\\machineslist.txt)");
ps.AddParameter("FilterScript2", filter2);
ps.AddParameter("-Credential");
ps.AddArgument("Administrator");
//not sure how to add password
ps.AddParameter("-Force");
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(
"{0,-24}{1}",
result.Members["Length"].Value,
result.Members["Name"].Value);
} // End foreach
To make this code snippet to compile and run, you will first need to reference the System.Management.Automation assembly (located under C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0).
You do not need a ScriptBlock as it increases complexity, it's easier to just add the script with AddScript.
You do not need to prefix parameters with -
To pass credentials you can use PSCredential. Normally you would provide a secure string, you can translate a string into a secure string using NetworkCredential as an helper.
You will need to handle errors as well, but this is out of scope for this question!
Enjoy :-)
using System;
using System.Management.Automation;
using System.Net;
using System.Security;
namespace Sample
{
class Program
{
static void Main(string[] args)
{
PowerShell ps = PowerShell.Create();
ps.AddScript("Get-Content C:\\machineslist.txt");
ps.AddCommand("Restart-Computer");
SecureString secureString = new NetworkCredential("", "Password").SecurePassword;
PSCredential psc = new PSCredential("Administrator", secureString);
ps.AddParameter("Credential", psc);
ps.AddParameter("Force");
// Simulation only
ps.AddParameter("WhatIf");
var results = ps.Invoke();
foreach (var error in ps.Streams.Error)
{
Console.WriteLine(error);
}
foreach (PSObject result in results)
{
Console.WriteLine(result);
//Console.WriteLine("{0,-24}{1}", result.Members["Length"].Value, result.Members["Name"].Value);
}
}
}
}
I am trying to run powershell script in c# . program runs successfully but does not show any output.
try
{
string fileName = "D:\\Script\\script.psm1";
RunspaceConfiguration config = RunspaceConfiguration.Create();
Runspace myRs = RunspaceFactory.CreateRunspace(config);
myRs.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(myRs);
scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted");
/*using (new Impersonator("myUsername", "myDomainname", "myPassword"))
{
using (RunspaceInvoke invoker = new RunspaceInvoke())
{
invoker.Invoke("Set-ExecutionPolicy Unrestricted");
}
} */
Pipeline pipeline = myRs.CreatePipeline();
pipeline.Commands.AddScript(fileName);
//...
pipeline.Invoke();
var error = pipeline.Error.ReadToEnd();
myRs.Close();
string errors = "";
if (error.Count >= 1)
{
foreach (var Error in error)
{
errors = errors + " " + Error.ToString();
}
}
return errors;
}
Your program is only checking for error output. You typically get the "standard" output as the return value of the Invoke method e.g.
Collection<PSObject> results = pipeline.Invoke();
string output = "";
foreach (var result in results)
{
output += result.ToString();
}
You aren't doing yourself any favors with that big try {} block wrapped around everything, as you can't see the exceptions that are happening.
You will need to run Visual Studio as a local administrator in order for "Set-ExecutionPolicy Unrestricted" to work, and the final executable will also have that requirement since issuing that command requires access to a protected registry key.
The pipeline.Invoke() method returns a type of Collection<PSObject>.
Collection<PSObject> results = pipeLine.Invoke();
If your intent is to ignore the output of the pipeline and only look at errors, that is fine; but if there are no errors in the script, it would be normal not to see anything.
With the .psm1 file extension on the script, you will probably get null results. The proper extension should be .ps1. The .psm1 extension is for modules that are stored in special locations on the file system and which are loaded automatically (in PowerShell 3.0+).
By default, 'Stop' type errors in PowerShell will generate an Exception in the C# program, so wrapping with try/catch is one way to see them.
Collection<PSObject> results = null;
try
{
results = pipeline.Invoke();
// results returned from PowerShell can be accessed here but may not
// necessarily be valid since a 'Continue' error could have occurred
// which would not generate an exception
}
catch (RuntimeException e)
{
Debug.WriteLine("Error: " + e.Message);
}
You can test this by adding for example the following to your script.ps1:
throw "This is an error"
Working Example:
Note:
1. You will need to add a reference to System.Management.Automation.dll in order to run this code sample. If you are using Visual Studio, you can select Add Reference then the Browse... button and in the search box of the Browse dialog enter the name of the assembly and it will likely show up in the search results. If not you may need to download the .NET portion of the Windows SDK.
2. PowerShell scripts are disabled by default in Windows, and this is code that runs PowerShell scripts. There is plenty of information on the 'Net, but the standard way to enable scripts is to open a PowerShell command prompt as a local administrator and run Set-ExecutionPolicy RemoteSigned (if needed, Unrestricted can be used instead of RemoteSigned).
3. In some environments, you will need to unblock scripts downloaded from the Internet by right-clicking on the file in Windows Explorer, going to Properties, and clicking Unblock. If there is no Unblock button then the file is OK.
4. The first thing to try if you get access errors is to run Visual Studio and/or the executable as a local administrator. Please do not attempt to impersonate an administrator and embed a password in the executable. If you are in a corporate setting, group policy can be configured to allow PowerShell scripts to run. If you are at home, you should be a local administrator.
using System.Management.Automation;
using System.Collections.ObjectModel;
using System.Management.Automation.Runspaces;
using System.Diagnostics;
namespace PowerShell
{
class Program
{
static void Main(string[] args)
{
// Create and Open a Runspace
string fileName = #"D:\script.ps1";
RunspaceConfiguration config = RunspaceConfiguration.Create();
Runspace myRs = RunspaceFactory.CreateRunspace(config);
myRs.Open();
// Attempt to configure PowerShell so we can forcefully run a script.
RunspaceInvoke scriptInvoker = new RunspaceInvoke(myRs);
scriptInvoker.Invoke("Set-ExecutionPolicy Unrestricted -Scope Process -Force");
Pipeline pipeline = myRs.CreatePipeline();
pipeline.Commands.AddScript(fileName);
Collection<PSObject> results = null;
try
{
results = pipeline.Invoke();
// Read standard output from the PowerShell script here...
foreach (var item in results)
{
Debug.WriteLine("Normal Output: " + item.ToString());
}
}
catch (System.Management.Automation.RuntimeException e)
{
Debug.WriteLine("PowerShell Script 'Stop' Error: " + e.Message);
}
myRs.Close();
}
}
}
I want to create a powershell function and use it from inside the C#
using System;
using System.Management.Automation;
using System.Text;
namespace PowerShell_eg
{
public class Program
{
public static void Main(String[] args)
{
var psFunction = #" function Get-Hostname { hostname } ";
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
??// HOW TO Add Get-Hostname & INVOKE IT ??
pipeline.Commands.AddScript(psFunction);
pipeline.Commands.Add("Get-Hostname");
var results = pipeline.Invoke();
foreach (var obj in results.Where(o => o != null))
{
Console.WriteLine("\t" + obj);
}
}
}
}
Currently I get CommandNotFound exception # Invoke.
The term 'Get-Hostname' is not recognized as the name of a cmdlet, function, script file, or operable program.
Please advice how to correctly do this!
Also it will be ideal if I can add multiple functions and cont. to use them over the life of the powershell session without having to add them again and again.
This C# code seems to work fine for me. Just add a reference to the System.Management.Automation .NET assembly.
using System;
using System.Management.Automation;
namespace PowerShellTest02
{
class Program
{
static void Main(string[] args)
{
string func = #"function Test { Write-Host 'hello' };";
PowerShell ps = PowerShell.Create();
ps.AddScript(func);
ps.Invoke();
ps.AddCommand("Test");
ps.Invoke();
Console.WriteLine("Successfully executed function");
Console.ReadLine();
}
}
}
I want to run Powershell command on remote machine. This is method that I am using (localhost:131 is because I use tunnel to remote machine's port 5985):
public string RunRemotePowerShellCommand(string command)
{
System.Security.SecureString password = new System.Security.SecureString();
foreach (char c in _password.ToCharArray())
{
password.AppendChar(c);
}
string schema = "http://schemas.microsoft.com/powershell/Microsoft.Powershell";
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(false,
"localhost", 131, "/wsman", schema, new PSCredential(_domain + #"\" + _userName, password));
using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace(connectionInfo))
{
remoteRunspace.Open();
using (PowerShell powershell = PowerShell.Create())
{
powershell.Runspace = remoteRunspace;
powershell.AddCommand(command);
powershell.Invoke();
Collection<PSObject> results = powershell.Invoke();
// convert the script result into a single string
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
}
}
I'm trying to run following command:
D:\FolderName\scriptName.ps1 -action editbinding -component "comp1","comp2","comp3","comp4"
Like this:
RunRemotePowerShellCommand(#"D:\FolderName\scriptName.ps1 -action editbinding -component ""comp1"",""comp2"",""comp3"",""comp4""");
but I get:
Error: System.Management.Automation.RemoteException: The term 'D:\FolderName\scriptName.ps1 -action editbinding -component "comp1","comp2","comp3","comp4"' is not recognized as a name of cmdlet, function, script file, or operable program. Check the spelling of the name, or if the path is included, verify that the path is correct and try again.
The method works fine with simple commands, and the command that I want to run is ok when I run it on remote machine.
Thanks in advance.
Regards,
Dusan
You need to use the powershell.AddParameter() method to add the parameters for your command. The AddCommand() call should name just the command: cmdlet name, function name, path to script, etc. From the docs:
PowerShell ps = PowerShell.Create();
ps.AddCommand("Get-Process");
ps.AddArgument("wmi*");
ps.AddCommand("Sort-Object");
ps.AddParameter("descending");
ps.AddArgument("id");
I has a similar requirement.
My solution was to create a powershell function in C# code and use it over the powershell remote session like.
using System;
using System.Management.Automation;
namespace PowerShellTest
{
class Program
{
static void Main(string[] args)
{
string func = #"function Test { Write-Host 'hello' };";
PowerShell ps = PowerShell.Create();
ps.AddScript(func);
ps.Invoke();
ps.AddCommand("Test");
ps.Invoke();
Console.WriteLine("Successfully executed function");
Console.ReadLine();
}
}
}