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();
Related
We have a need to remotely start/stop IIS websites and app pools, so we can remotely deploy a website.
I have a Websocket app that starts a PowerShell script to complete these activities. I built Powershell scripts to complete that tasks and they work perfectly in the Powershell prompt. However, when I try to run these scripts from the websocket, the scripts run (I have Write-Outputs in the scripts), but nothing happens the site and pool do not change. I don't see anything that says it failed, either. I would appreciate any help that can be given.
Below is an excerpt from the code:
using (PowerShell ps = PowerShell.Create())
{
string scriptContent = string.Empty;
string pathToReadScriptFile = string.Empty;
// add a script that creates a new instance of an object from the caller's namespace
if (r.StartStop.ToLower() == "stop")
{
pathToReadScriptFile = Path.Combine(scriptsPath, "StopPoolAndSite.ps1");
}
else
{
pathToReadScriptFile = Path.Combine(scriptsPath, "StartPoolAndSite.ps1");
}
using (StreamReader sr = new StreamReader(pathToReadScriptFile))
{
scriptContent = sr.ReadToEnd();
sr.Close();
}
ps.AddScript(scriptContent);
ps.AddParameter("siteName", r.SiteName);
ps.AddParameter("poolName", r.PoolName);
// invoke execution on the pipeline (collecting output)
Collection<PSObject> PSOutput = ps.Invoke();
// loop through each output object item
foreach (PSObject outputItem in PSOutput)
{
if (outputItem != null)
{
await SendMessageToAllAsync($"{outputItem.ToString()}");
}
}
}
Here is one of the powershell script code:
Param(
[Parameter(Mandatory=$True)]
[string]$siteName ,
[string]$poolName
)
if (-Not $poolName)
{
$poolName = $siteName
Write-Output "PoolName not supplied. Using $siteName as default. "
}
Import-Module WebAdministration
Write-Output "Preparing to Start AppPool: $poolName"
Write-Output "(OutPut)Preparing to Start AppPool: $poolName"
Start-WebAppPool $poolName
Write-Output "Preparing to Start Site: $siteName"
Start-WebSite $siteName
Get-WebSite $siteName
Actually i would suggest not to reinvent the wheel there is a project for that check these out :
https://github.com/microsoft/iis.administration
https://manage.iis.net/get
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;
}
}
}
This script works when running in PowerShell ISE (it sets the given user's Remote Desktop Services Profile settings in Active Directory):
Get-ADUser FirstName.LastName | ForEach-Object {
$User = [ADSI]"LDAP://$($_.DistinguishedName)"
$User.psbase.invokeset("TerminalServicesProfilePath","\\Server\Share\HomeDir\Profile")
$User.psbase.invokeset("TerminalServicesHomeDrive","H:")
$User.psbase.invokeset("TerminalServicesHomeDirectory","\\Server\Share\HomeDir")
$User.setinfo()
}
But when I try running it from a C# application I get an error for each invokeset that I call:
Exception calling "InvokeSet" with "2" argument(s):
"Unknown name. (Exception from HRESULT: 0x80020006 (DISP_E_UNKNOWNNAME))"
Here is the code, which is inside my PowerShell class:
public static List<PSObject> Execute(string args)
{
var returnList = new List<PSObject>();
using (var powerShellInstance = PowerShell.Create())
{
powerShellInstance.AddScript(args);
var psOutput = powerShellInstance.Invoke();
if (powerShellInstance.Streams.Error.Count > 0)
{
foreach (var error in powerShellInstance.Streams.Error)
{
Console.WriteLine(error);
}
}
foreach (var outputItem in psOutput)
{
if (outputItem != null)
{
returnList.Add(outputItem);
}
}
}
return returnList;
}
And I call it like this:
var script = $#"
Get-ADUser {newStarter.DotName} | ForEach-Object {{
$User = [ADSI]""LDAP://$($_.DistinguishedName)""
$User.psbase.invokeset(""TerminalServicesProfilePath"",""\\file\tsprofiles$\{newStarter.DotName}"")
$User.psbase.invokeset(""TerminalServicesHomeDrive"",""H:"")
$User.psbase.invokeset(""TerminalServicesHomeDirectory"",""\\file\home$\{newStarter.DotName}"")
$User.setinfo()
}}";
PowerShell.Execute(script);
Where newStarter.DotName contains the (already existing) AD user's account name.
I tried including Import-Module ActveDirectory at the top of the C# script, but with no effect. I also called $PSVersionTable.PSVersion in both the script running normally and the C# script and both return that version 3 is being used.
After updating the property names to
msTSProfilePath
msTSHomeDrive
msTSHomeDirectory
msTSAllowLogon
I am getting this error in C#:
Exception calling "setinfo" with "0" argument(s): "The attribute syntax specified to the directory service is invalid.
And querying those properties in PowerShell nothing (no error but also no output)
Does anyone happen to know what could cause this?
Many thanks
Updated answer: It seems that these attributes don't exist in 2008+. Try these ones instead:
msTSAllowLogon
msTSHomeDirectory
msTSHomeDrive
msTSProfilePath
See the answer in this thread for the full explanation.
Original Answer:
The comment from Abhijith pk is probably the answer. You need to run Import-Module ActiveDirectory, just like you need to do in the command line PowerShell.
If you've ever run Import-Module ActiveDirectory in the PowerShell command line, you'll know it takes a while to load. It will be the same when run in C#. So if you will be running several AD commands in your application, you would be better off keeping a Runspace object alive as a static object and reuse it, which means you only load the ActiveDirectory module once.
There is details here about how to do that in C#:
https://blogs.msdn.microsoft.com/syamp/2011/02/24/how-to-run-an-active-directory-ad-cmdlet-from-net-c/
Particularly, this is the code:
InitialSessionState iss = InitialSessionState.CreateDefault();
iss.ImportPSModule(new string[] { "activedirectory" });
Runspace myRunSpace = RunspaceFactory.CreateRunspace(iss);
myRunSpace.Open();
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();
}
}
}
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.").