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.
Related
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 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 was trying to invoke powershell commands in a c# .net environment and manipulate the outputs, for example: I would like to iterate through each parent and child nodes and get their Innertext value and map them into my classes, but all I get is the whole xml content: below is my code so far:
Runspace rs = RunspaceFactory.CreateRunspace();
rs.Open();
Pipeline pipe = rs.CreatePipeline();
//create powershell
using (PowerShell powershell = PowerShell.Create())
{
//Add the commnads to the PowerShell object.
powershell.AddCommand("Get-Content").AddArgument("C:\\XMLFileSettings.xml");
try
{
foreach (PSObject result in powershell.Invoke())
{
Console.WriteLine("{0}", result.ToString());
}
}
finally
{
}
}
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";
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"});