Trying to do the following in powershell in C#
$certThumbrint = "someLocationToACert"
$cert = get-item $certThumbrint
Get-RoleInstanceCount -ServiceName "someServiceName" -DeploymentSlot "someSlot" -RoleName "someRole" -SubscriptionId "someId" -Certificate $cert
This works perfectly when running them one by one in the powershell comandline. But I cannot figure out how to do this by code. So far Ive done this.
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.Add("$certThumbrint = \"someLocationToACert\"");
pipeline.Commands.Add(#"$cert = get-item $certThumbrint");
Command instanceCount = new Command("Get-RoleInstanceCount");
instanceCount.Parameters.Add(new CommandParameter("ServiceName", "someServiceName"));
....
instanceCount.Parameters.Add(new CommandParameter("Certificate", "$cert"));
I then get the following exception:
"The term '$certThumbrint = "someLocation"' 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 tried to add the varibles as "AddScrips" and I also used
SessionStateVariableEntry var2 = new SessionStateVariableEntry("cert", "get-item $certThumbrint", "Initial session state MyVar1 test");
initialSessionState.Variables.Add(var2);
Before creating the runspace. Nothing is working. Also added all the code into a string and tried to run it as a script.
I actually have no way of doing this and it feels like it's a really simple thing that must be able to do... thanks.
Edit: also tried the following:
const string getInstanceCountScript = "$certThumbrint = \"somecert\" \n " +
"$cert = get-item $certThumbrint \n " +
"Get-RoleInstanceCount -ServiceName someservicename" +
...
" -Certificate $cert";
pipeline.Commands.AddScript(getInstanceCountScript);
It runs but returns an empty string. If I put the same code into a ps1 file that I call with "Add()" it runs and gives me the right output. But I really dont want to have a load of ps1 files in my project just for 3 lines of code or less.
This code perfectly works for me. Are you shure, that PS1 file contains exact same code?
static void Main(string[] args)
{
using (Runspace rs = RunspaceFactory.CreateRunspace())
{
rs.Open();
var pipeline = rs.CreatePipeline();
pipeline.Commands.AddScript("$certThumbrint = \"c:\\1.txt\"\n" +
"$cert = get-item $certThumbrint\n" +
"Get-Content $cert");
foreach (var s in pipeline.Invoke())
{
Console.WriteLine(s);
}
}
Console.ReadLine();
}
Take a look at the New-Variable commandlet.
Related
I'm writing a program with C# , that can create Users on remote Computers.
Actually it's done and working.
But I have one little problem.
In C# I use PowerShell to run a Script which runs then an Pexec, which executes a Batch file on a remote Computer.
C# :
private void executeScripts()
{
string _dirPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
string _sPath = Path.GetDirectoryName(_dirPath) + #"\ExecuteScripts\FileToExecute.ps1";
string _scriptPath = "& '" + _sPath + "'";
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(_scriptPath);
ps.Invoke();
}
}
}
}
PS Script :
#
# First there are some Copy-Items to the remote Computer
#
# Execute Above copied Bat File on remote Computer
[string] $IPAddress = "\\" + $XmlFile.ComputerSettings.LastChild.ChildNodes[1].InnerText
$PsTools = "\PsTools"
$PsToolsPath = Join-Path -path $ScriptParent -childpath $PsTools
& $PsToolsPath\PsExec.exe $IPAddress /accepteula -i -s -u $Login -p $LoginPassword Powershell C:\Path\ToBatFile\Execute.bat > log.txt
Exit
I use this PExec 3 other times in my Program, creating a User, updating a User and removing a User, i just execute different files, scripts or batch files.
And it works perfectly.
But with the Script above, the PExec executes everything but doesn't exit. Neiter does it log something.
I tried it also with the -d switch, but that didn't work either. I also put an exit /b in the batch file but no luck.
When running the script manually from Powershell it works, it executes and it exits, but when running it from my Program it doesn't.
After some waiting my C# returns a timed-out Exception end exits.
Anyone seeing what I'm doing wrong ?
Powershell class itself has a method called Stop() which makes it pretty easy to stop this.
If you want to do it asynchronously here is an example of implementation:
using(cancellationToken.Register(() => powershell.Stop())
{
await Task.Run(() => powershell.Invoke(powershellCommand), cancellationToken);
}
I want to execute a powershell script via winforms and get well-formatted output. I managed to get it to work but now I need to pass parameters to my script. I can't manage to make that happen.
Here is my RunScript function :
private string RunScript(string scriptFile)
{
Runspace runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
Pipeline pipeLine = runSpace.CreatePipeline();
Command script = new Command(scriptFile);
script.Parameters.Add("COM_USERNAME", usernameBox.Text);
script.Parameters.Add("COM_PASSWORD", passwordBox.Text);
script.Parameters.Add("installDir", installDirBox.Text);
script.Parameters.Add("TEMPVAULT_PATH", tempVaultBox.Text);
script.Parameters.Add("MAX_REQ_LIMIT", maxReqLimBox.Text);
script.Parameters.Add("MAX_BUFF_LIMIT", maxBuffLimBox.Text);
pipeLine.Commands.AddScript(script.ToString());
pipeLine.Commands.Add("Out-String");
Collection<PSObject> results = pipeLine.Invoke();
runSpace.Close();
StringBuilder strBuilder = new StringBuilder();
foreach (PSObject item in results)
{
strBuilder.AppendLine(item.ToString());
}
return strBuilder.ToString();
}
And this is the script that I am trying with:
param (
[bool] $STARTSERVICE = $false ,
[bool] $INSTALL = $false ,
[bool] $INSTALL_DASHBOARD = $false,
[bool] $DASHBOARD_SETTINGS = $false,
[bool] $DASHBOARD_CREATENEWDB = $false,
[bool] $DALIM_SETTINGS = $false,
[bool] $INSTALLIIS = $true,
[bool] $FIRST_INSTALL = $true,
[bool] $RECOVERY = $false,
[string] $COM_USERNAME,
[string] $COM_PASSWORD,
[string] $RECOVERY_ADM_NAME,
[string] $RECOVERY_ADM_PWD,
[string] $Windows2012DVDLetter = "F:",
[string] $COM_UNCPATH,
[string] $installDir = "C:\Program Files\App\",
[string] $TEMPVAULT_PATH = "C:\TempVault",
$SOAP_MaxPostSize = 4294967295,
$MAX_REQ_LIMIT = 500000000,
$MAX_BUFF_LIMIT = 500000000
)
Write-Output "`nUsername = " $COM_USERNAME
Write-Output "`nPassword = " $COM_PASSWORD
Write-Output "`nCOM_UNCPATH = " $COM_UNCPATH
Write-Output "`nMaximum Request Limit = " $MAX_REQ_LIMIT
Write-Output "`nMaximum Buff Limit = " $MAX_BUFF_LIMIT
Write-Output "`nIsFirstInstall = " $FIRST_INSTALL
Write-Output "`nInstallation Directory = " $installDir
Write-Output "`nTempVault Path = " $TEMPVAULT_PATH
Write-Output "`nRestriction level = " $RESTRICT_LVL
I have output with only the pre-registered in the script values showing, but the ones I'm trying to show (textboxes inputs) don't. Have I missed something?
Note: The following assumes that scriptFile is the path of a *.ps1 file, not that file's content (a string containing Powershell code).
See the bottom section for how to handle the latter case.
You can greatly simplify your invocation:
private string RunScript(string scriptFile)
{
using (var ps = PowerShell.Create()) {
ps.AddCommand(scriptFile) // Be sure to pass a *full path*
.AddParameter("COM_USERNAME", usernameBox.Text)
.AddParameter("COM_PASSWORD", passwordBox.Text)
.AddParameter("installDir", installDirBox.Text)
.AddParameter("TEMPVAULT_PATH", tempVaultBox.Text)
.AddParameter("MAX_REQ_LIMIT", maxReqLimBox.Text)
.AddParameter("MAX_BUFF_LIMIT", maxBuffLimBox.Text)
.AddCommand('Out-String'); // Add a pipeline segment
// Return the 1st (and in this case only) output object, as a string.
return ps.Invoke<string>()[0];
}
}
Using PowerShell.Create() creates an instance of class PowerShell, which provides a higher-level API based on an implicitly created runspace:
Methods can be chained.
Calling .AddCommand() repeatedly automatically adds new pipeline segments.
As for what you tried:
pipeLine.Commands.AddScript(script.ToString());
The .AddScript() method is for adding arbitrary pieces of PowerShell code, not for adding Command instances with associated parameters.
(Command instances represent either a PowerShell command such as Out-String or the name / path of an external executable or the path[1] to a script file (*.ps1)).
By stringifying the Command instance stored in script with .ToString(), you're effectively just passing the script path as the command to execute - all the parameters you've added with .AddParameter() are lost, which is why you only saw the default parameter values in the script's output.
Instead, you should have added your Command instance as follows:
pipeLine.Commands.Add(script)
If scriptFile is not a file path, but the contents of a script file (a string containing PowerShell code):
As you've since clarified, this is your actual use case, because the script is embedded as a resource in your executable that you pass with RunScript(Properties.Resources.<the script>)
Adapt the simplified approach at the top as follows:
// If `scriptFile` is the *contents* of a *.ps1 file,
// add it as a script [block] with .AddScript(),
// then add parameters (and the additional pipeline command).
ps.AddScript(scriptFile)
.AddParameter("COM_USERNAME", usernameBox.Text)
.AddParameter("COM_PASSWORD", passwordBox.Text)
.AddParameter("installDir", installDirBox.Text)
.AddParameter("TEMPVAULT_PATH", tempVaultBox.Text)
.AddParameter("MAX_REQ_LIMIT", maxReqLimBox.Text)
.AddParameter("MAX_BUFF_LIMIT", maxBuffLimBox.Text)
.AddCommand('Out-String'); // Add a pipeline segment
[1] PowerShell only allows by-name-only executions (e.g., foo.ps1) for executables / scripts located in a directory listed in the PATH environment variable. Otherwise, a file path must be specified, and it's safest to use a full path, because .NET's current directory usually differs from PowerShell's.
I'm wishing to run a command from C# to a container set up via docker-compose. In Powershell, I run this command and the file is created:
docker-compose exec database sh -c "mysqldump -u((username)) -p((password)) ((databasename)) > /backups/test.sql"
When I run the following code it seems to ignore the environment variables, even though I have them set. It only creates a file named backup.sql and the SQL outputted to the file indicates that no database was selected. I've verified the env variables are set by outputting the last parameters string to the console.
var exportPath = $"/backups/backup.sql {DateTime.Now}";
using (var runspace = RunspaceFactory.CreateRunspace())
{
runspace.Open();
runspace.SessionStateProxy.Path.SetLocation(Path.GetFullPath(".."));
using (var pipeline = runspace.CreatePipeline())
{
var cmd = new Command("docker-compose");
cmd.Parameters.Add("exec");
cmd.Parameters.Add("database");
cmd.Parameters.Add($"sh -c \"mysqldump - u{GetEnv("MYSQL_USERNAME")} " +
$"-p{GetEnv("MYSQL_PASSWORD")} {GetEnv("MYSQL_DATABASE")} > {exportPath} \"");
pipeline.Commands.Add(cmd);
pipeline.Invoke();
}
}
GetEnv is just a convenience method for Environment.GetEnvironmentVariable
I'm fairly certain that I am not setting the parameters right, but I don't know where to go from here.
I came across to FluentDocker which seems like a nice way to run docker-compose from c#.
Here is an example from the project page:
using (
var container =
new Builder().UseContainer()
.UseImage("kiasaki/alpine-postgres")
.ExposePort(5432)
.WithEnvironment("POSTGRES_PASSWORD=mysecretpassword")
.WaitForPort("5432/tcp", 30000 /*30s*/)
.Build()
.Start())
{
var config = container.GetConfiguration(true);
Assert.AreEqual(ServiceRunningState.Running, config.State.ToServiceState());
}
Disclaimer: I am not affiliated with the project in any way nor have attempted to use it yet.
I gave up and used CliWrap because it is easier to debug. I couldn't figure out how to set the current directory, but fortunately I could modify the rest of the program to look for stuff in the current directory.
using CliWrap;
using CliWrap.Buffered;
var now = DateTime.Now.ToString().Replace("/", "-").Replace(" ", "-");
var exportPath = $"/backups/backup.sql-{now}";
var cmd = $"exec -T database sh -c \"mysqldump -u{GetEnv("MYSQL_USER")} " +
$"-p{GetEnv("MYSQL_PASSWORD")} {GetEnv("MYSQL_DATABASE")} > {exportPath}\"";
Console.WriteLine(cmd);
var result = await Cli.Wrap("docker-compose")
.WithArguments(cmd)
.ExecuteBufferedAsync();
Console.WriteLine(result.StandardOutput);
Console.WriteLine(result.StandardError);
The library can be found here: https://github.com/Tyrrrz/CliWrap
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've been working on this issue for a couple days and have read several posts here, but I can't get my implementation to work. I am calling the powershell script during the Commit custom action. When I get to the pipeline.Invoke() method, I get an exception that says the entire script "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."
Here is my script:
Param(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string]$installPath
);
schtasks /create /TN MyTask /RU domain\account /RP password /xml $installPath\MyTaskSchedule.xml;
I've tried it with and without the trailing semi-colons, with and without a wrapping function. I've verified that the C# code is passing the correct install path and that the xml file exists in the directory before this step is hit. I can run this from PowerShell itself and it works just fine.
Here is my code:
public override void Commit( System.Collections.IDictionary savedState )
{
base.Commit( savedState );
String targetDirectory = this.Context.Parameters["TDir"].ToString();
String script = System.IO.File.ReadAllText( targetDirectory + "TaskScheduler.ps1" );
RunspaceConfiguration c = RunspaceConfiguration.Create();
using ( Runspace runspace = RunspaceFactory.CreateRunspace() )
{
runspace.Open();
using ( Pipeline pipeline = runspace.CreatePipeline() )
{
Command myCommand = new Command( script );
CommandParameter param = new CommandParameter( "installPath", targetDirectory.Replace("\\\\", "\\") );
myCommand.Parameters.Add( param );
pipeline.Commands.Add( myCommand );
try
{
pipeline.Invoke();
}
catch ( Exception e )
{
MessageBox.Show( e.Message );
}
}
}
}
When the exception is caught at pipeline.Invoke, the entire script is displayed (with the $installPath instead of the actual path) as a string before the error message detailed above. I've tried several checks within the script itself, but I get the same results no matter what, which tells me that the runspace just doesn't like the script itself.
You should pass true as the second parameter in the constructor: new Command(script, true). It tells that the command is a script code, not a command name.
Here is a working PowerShell analogue of your code:
# This script calls the external command (cmd) with passed in parameter
$script = #'
param
(
[parameter(Mandatory=$true,ValueFromPipeline=$true)]
[string]$installPath
)
cmd /c echo $installPath
'#
# Note: the second parameter $true tells that the command is a script code, not just a command name
$command = New-Object Management.Automation.Runspaces.Command $script, $true
$param = New-Object Management.Automation.Runspaces.CommandParameter "installPath", "C:\UTIL\INSTALL"
$command.Parameters.Add($param)
$rs = [Management.Automation.Runspaces.RunspaceFactory]::CreateRunspace()
$rs.Open()
$pipeline = $rs.CreatePipeline()
$pipeline.Commands.Add($command)
$pipeline.Invoke()
It prints (in the console host):
C:\UTIL\INSTALL