I want to execute some PowerShell script through C# but it requires admin privilege. This is my code (I got it here):
using (new Impersonator("user", "domain", "password"))
{
// 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(scriptText);
// add parameters if any
foreach (var parameter in parameters)
{
pipeline.Commands[0].Parameters.Add(parameter.Key, parameter.Value);
}
// 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();
// 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();
}
Anyway, this doesn't work on my machine. For example, if the script text is "Set-ExecutionPolicy Unrestricted" then I get "Access to the registry key 'HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell' is denied."
And in my case, it cannot get list of virtual machines through Get-VM command. (I found that Get-VM only return results if it runs under Admin privilege.)
Do I do something wrong? Is there another solution for this problem?
This will launch PowerShell as an Administrator:
var newProcessInfo = new System.Diagnostics.ProcessStartInfo();
newProcessInfo.FileName = #"C:\Windows\SysWOW64\WindowsPowerShell\v1.0\powershell.exe";
newProcessInfo.Verb = "runas";
System.Diagnostics.Process.Start(newProcessInfo);
If you need to pass in a script to run, then use:
newProcessInfo.Arguments = #"C:\path\to\script.ps1";
Related
I have a windows service (C#) running as Local System.
I want to be able to read my database and run PowerShell commands and scripts.
I am able to run most scripts but my test machine is hanging on this one :
NET USE Z: /Delete /y
NET USE Z: \\TEST2\ProgramData
I can run these commands on the computer and it all works but when I try to run these commands from within my Windows Service it hands on the line which runs the script.
private static bool RunPSCommand(string command, out string output)
{
// 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(command);
// 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
try
{
StringBuilder stringBuilder = new StringBuilder();
Collection<PSObject> results = pipeline.Invoke();
if (pipeline.HadErrors)
{
var errors = pipeline.Error.ReadToEnd();
foreach (object error in errors)
{
stringBuilder.AppendLine(error.ToString());
}
}
// close the runspace
runspace.Close();
// convert the script result into a single string
foreach (PSObject obj in results)
{
stringBuilder.AppendLine(obj.ToString());
}
output = stringBuilder.ToString();
return true;
}
catch (CommandNotFoundException e)
{
output = e.Message;
return false;
}
catch (Exception e)
{
output = e.Message;
return false;
}
}
I am not sure why this is so difficult. I have been wracking my head on this one for three days and trying every option from net use to DOM objects
NET USE Z: /Delete /y
(New-Object -Com WScript.Network).MapNetworkDrive("z:" , "\\test2\programdata")
I was attempting to do this from a service but switched to an application running on a user session. It works this way.
How to call power shell script file by passing attributes in c#.
I'm using below code to call ps1 file by passing inputs but getting error near invoke.
error message:
System.Management.Automation.CommandNotFoundException: 'The term
'Get-Childitem C:\samplemm.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.'
namespace SCOMWebAPI.Services
{
public class MaintennceModeService
{
private static IEnumerable<PSObject> results;
internal static string post(MaintenanceMode value)
{
// create Powershell runspace
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
//Here's how you add a new script with arguments
Command myCommand = new Command("Get-Childitem C:\\samplemm.ps1");
CommandParameter testParam = new CommandParameter("mgmtserver", "NodeName");
myCommand.Parameters.Add(testParam);
pipeline.Commands.Add(myCommand);
// Execute PowerShell script
results = pipeline.Invoke();
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();
}
}
}
When you type the command Get-ChildItem C:\\samplemm.ps1 into powershell, you are actually binding the text C:\\samplemm.ps1 to the default parameter Path.
The problem with your code is that you have included the first parameter as part of the command name. Simply separate it out.
Instead of
Command myCommand = new Command("Get-Childitem C:\\samplemm.ps1");
Separate out the parameter:
Command myCommand = new Command("Get-Childitem");
CommandParameter pathParameter = new CommandParameter("Path", "C:\\samplemm.ps1");
myCommand.Parameters.Add(pathParameter);
Below is the function to keep server in SCOM maintenance mode and I would like to call this function through cs or asp.net as API call by passing variables.
function set-scomderegister {
param(
[Parameter( Mandatory = $True, ValueFromPipeline = $true)][string]
$SCOMServer,
[Parameter( Mandatory = $True, ValueFromPipeline = $true)]
$Computername
)
ForEach($Comp in $Computername)
{
New-SCManagementGroupConnection -ComputerName $SCOMServer
$numberOfMin = 100
$ReasonComment = "Server got docomissioned "
$Instance = Get-SCOMClassInstance -Name $Comp
$Time = ((Get-Date).AddMinutes($numberOfMin))
Start-SCOMMaintenanceMode -Instance $Instance -EndTime $Time -Comment $ReasonComment -Reason PlannedOther;
}
}
System.Management.Automation namespace would be useful for you.
You can install a nuget package "System.Management.Automation".
Once this is installed you will have this namespace available.
You can invoke a script with parameter as shown below:
public void RunWithParameters()
{
// create empty pipeline
PowerShell ps = PowerShell.Create();
// add command
ps.AddCommand("test-path").AddParameter("Path", Environment.CurrentDirectory); ;
var obj = ps.Invoke();
}
private 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(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();
// 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();
}
There is another option to use Process.Start to start the powershell prompt. Then pass the file path to the process.
public static int RunPowershellScript(string ps)
{
int errorLevel;
ProcessStartInfo processInfo;
Process process;
processInfo = new ProcessStartInfo("powershell.exe", "-File " + ps);
processInfo.CreateNoWindow = true;
processInfo.UseShellExecute = false;
process = Process.Start(processInfo);
process.WaitForExit();
errorLevel = process.ExitCode;
process.Close();
return errorLevel;
}
Hope this helps.
I'm planning on building an Active Directory/Exchange admin console using C# talking powershell to DC and Exchange servers.
I want to on application launch establish powershell connections to these servers and then keep them alive so I can keep running queries or scripts or whatever because it takes a couple of seconds to establish the remote connection and it just won't work to have that kind of delay on everything you do.
I'm currently just testing a local powershell runspace but every time I send a command to it it closes and I can't reuse it after the initial command.
How can I prevent the runspace from closing so I can use it over and over again?
edit: code
Very basic, just creating a runspace, planning on being able to include modules later on when I've got the basic functionality down. The idea was to create a runspace and when calling the function that executes powershell code assign that runspace to another variable so I could reuse it but I'm probably stupid. Currently I just have a dummy "Get-Process" that's sent when clicking a button and a textbox that displays the output.
public partial class MainWindow : Window
{
Runspace powerShellRunspace = RunspaceFactory.CreateRunspace();
public MainWindow()
{
InitializeComponent();
powerShellRunspace.Open();
string[] modules;
scriptOutput.Text = "test";
modules = new string[5];
modules[0] = "john";
//string result = powerShellRun("Get-Process");
//powerShellInitialize(modules);
}
public static void powerShellInitialize(string[] modules)
{
Runspace powerShellRunspace = RunspaceFactory.CreateRunspace();
powerShellRunspace.Open();
}
public string powerShellRun(string commands, Runspace powerShellRunspace)
{
Runspace powerShellRunspace2 = powerShellRunspace;
Pipeline powerShellPipeline = powerShellRunspace2.CreatePipeline();
powerShellPipeline.Commands.Add(commands);
Collection<PSObject> powerShellResult = powerShellPipeline.Invoke();
//string result="temp";
//return result;
StringBuilder stringBuilder = new StringBuilder();
foreach (PSObject obj in powerShellResult)
{
stringBuilder.AppendLine(obj.ToString());
}
return stringBuilder.ToString();
}
}
This question was already answered on Keeping Powershell runspace open in .Net
In summary, you can keep your Runspace Open, but for each independent query, you need to create a new Powershell instance.
Example:
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
//First Query
var firstQuery = PowerShell.Create();
firstQuery.Runspace = runspace;
firstQuery.AddScript("Write-Host 'hello'")
//Second Query
var secondQuery = PowerShell.Create();
secondQuery.Runspace = runspace;
secondQuery.AddScript("Write-Host 'world'")
Here is the code:
static String checkBackUp()
{
Runspace runspace = RunspaceFactory.CreateRunspace();
runspace.Open();
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.Add("Get-WBSummary");
pipeline.Commands.Add("Out-String");
Collection<PSObject> results = new Collection<PSObject>();
try
{
results = pipeline.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();
}
The problem is that this runs every cmdlet (like Get-Process for example) but when I try to verify if a backup has been made (Get-WBSummary), it spits out the following error:
The term 'Get-WBSummary' 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.
However when I put the command straight into PowerShell, it executes the command. I have already tried to add a SnapIn but this did not work.
What am I doing wrong here?
Get-WBSummary isn't a regular built-in Powershell cmdlet. You'll need to do
Add-PSSnapin Windows.ServerBackup
at some point in your code after the runspace is initialised.
You'll have to create an initial session state and add the snapin. Here is how to do it
initialSession = InitialSessionState.CreateDefault();
initialSession.ImportPSModule(new[] {"Path\to\module\here"});