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.
Related
How can I call Where-Object (without filter) from C#? I can't parse output, because I want to pass it to pipeline(not in example below).
PS:
Get-MailboxPermission username | Where-Object {$_.AccessRights -match "FullAccess" -and $_.IsInherited -eq $False}
C#:
Collection<PSObject> results = null;
Command command1 = new Command("Get-MailboxPermission");
command1.Parameters.Add("Identity", mailbox);
command1.Parameters.Add("DomainController", _domainController);
//I cannot use Filter. This is not possible in PS also.
//command1.Parameters.Add("Filter", "AccessRights -match \"FullAccess\"");
This question is simmilar to: PowerShell WhereObjectCommand from C# That answer is not enough for my problem.
Check the below code sample. I have updated a sample from code project here as per your requirements.
Note:
To add quotes to your command i.e. script text escape quotes by using \"
To add { or } brace to the script text use double curly braces instead to escape it inside String.Format like {{ or }}
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
//Getting all command variables
string computerName = "YourComputerName";
string matchingPattern = "con";
//Create Script command
String customScriptText = String.Format("Get-Process -ComputerName {0} | Where-Object {{$_.ProcessName -match \"{1}\"}}", computerName,matchingPattern);
pipeline.Commands.AddScript(customScriptText);
// 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.
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());
}
When you apply the above working sample to your problem you will need to change couple of lines like below:
//Getting all command variables
string username = "YourUserName";
//Create Script command
String customScriptText = String.Format("Get-MailboxPermission {0} | Where-Object {{$_.AccessRights -match \"FullAccess\" -and $_.IsInherited -eq $False}}", username);
I am trying to write a Windows Form App in C# that outputs AD Attributes for a specified user. The way I want it to work is that the user inputs a value (username) into a text box, which is passed as a parameter to the Powershell script and the output is displayed in the form.
My C# code for creating the parameter and invoking the script is as follows:
private string RunScript(string scriptText)
{
// create Powershell runspace
Runspace runspace = RunspaceFactory.CreateRunspace();
// open it
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
// create a pipeline and feed it the script text
Pipeline pipeline = runspace.CreatePipeline();
pipeline.Commands.AddScript(scriptText);
pipeline.Commands.Add(new Command("Set-ExecutionPolicy Unrestricted -Scope Process", true));
// "Get-Process" returns a collection of System.Diagnostics.Process instances.
pipeline.Commands.Add("Out-String");
//Create parameter and pass value to script
String username = textBox3.Text;
String scriptfile = #"c:\\scripts\\getpasswordexpirydate.ps1";
Command myCommand = new Command(scriptfile, false);
CommandParameter testParam = new CommandParameter("username", username);
myCommand.Parameters.Add(testParam);
pipeline.Commands.Add(myCommand);
// 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 the results of the script that has
// now been converted to text
return stringBuilder.ToString();
}
My PowerShell script is as follows:
param([string]$username)
function Get-XADUserPasswordExpirationDate() {
Param ([Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, HelpMessage="Identity of the Account")]
[Object] $accountIdentity)
PROCESS {
$accountObj = Get-ADUser $accountIdentity -properties PasswordExpired, PasswordNeverExpires, PasswordLastSet
if ($accountObj.PasswordExpired) {
echo ("Password of account: " + $accountObj.Name + " already expired!")
} else {
if ($accountObj.PasswordNeverExpires) {
echo ("Password of account: " + $accountObj.Name + " is set to never expires!")
} else {
$passwordSetDate = $accountObj.PasswordLastSet
if ($passwordSetDate -eq $null) {
echo ("Password of account: " + $accountObj.Name + " has never been set!")
} else {
$maxPasswordAgeTimeSpan = $null
$dfl = (get-addomain).DomainMode
if ($dfl -ge 3) {
## Greater than Windows2008 domain functional level
$accountFGPP = Get-ADUserResultantPasswordPolicy $accountObj
if ($accountFGPP -ne $null) {
$maxPasswordAgeTimeSpan = $accountFGPP.MaxPasswordAge
} else {
$maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
}
} else {
$maxPasswordAgeTimeSpan = (Get-ADDefaultDomainPasswordPolicy).MaxPasswordAge
}
if ($maxPasswordAgeTimeSpan -eq $null -or $maxPasswordAgeTimeSpan.TotalMilliseconds -eq 0) {
echo ("MaxPasswordAge is not set for the domain or is set to zero!")
} else {
echo ("Password of account: " + $accountObj.Name + " expires on: " + ($passwordSetDate + $maxPasswordAgeTimeSpan))
}
}
}
}
}
}
Get-XADUserPasswordExpirationDate $username
Get-ADUser $username -Properties * | Select-Object DisplayName,LockedOut,LastLogonDate,kPMG-User-GOAccountType,kPMG-User-GOCompanyGroup,kPMG-User-GOFunction,kPMG-User-GOGrade,kPMG-User-GOManagementLevel,kPMG-User-GOMemberFirmGroup,kPMG-User-GPID,kPMG-User-GOMailDisclaimer,kPMG-User-GOMailSync
If I run the script in PowerShell e.g. .\script.ps1 jsmith with 'jsmith' as the parameter it works, however when using the C# parameter it does not accept the parameter and spits out a "Cannot validate argument on parameter 'Identity'" error every time.
Is there something I have done wrong in my C# code that is causing this parameter to not pass to the script and accept it as input?
Thanks
A few thoughts:
The parameter name in the C# code is username
The parameter name in the script is accountIdentity
The error message references parameter Identity
I would think all 3 should be the same.
If that's not the problem then a possible way to debug the problem is to turn your C# code into a PS script. For me, at least, I'd feel more comfortable debugging a PS script where I can rapidly change things (like where you build myCommand) and inspect them (with get-member and select-object *) than you might with C#.
Also for debugging you might also try combining all the individual PS commands so that you end with a single invocation of AddScript(), instead of various AddCommand()s along with the AddScript(). I vaguely remember problems with mixing the two when I wrote somewhat similar code many years ago.
I'm invoking a PowerShell script from C#, I'm using a hashtable to pass through parameters however when it comes to invoking the PowerShell script I get
A positional parameter cannot be found that accepts argument
The PowerShell script has two parameters. The hashtable has two keys with one value each. PowerShell script below:
param([string]$username,[string]$path)
#Gets SID
$objUser = New-Object System.Security.Principal.NTAccount($username)
$strSID = $objUser.Translate([System.Security.Principal.SecurityIdentifier])
$SID = $strSID.Value
# delets user
net user $username /DELETE
# removes folder
rmdir /q $path
Remove-Item -Path "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$SID"
C# that calls the PowerShell script:
class RunScript
{
public static void FireScript(String script, Hashtable var)
{
RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
runspace.Open();
RunspaceInvoke scriptInvoker = new RunspaceInvoke(runspace);
Pipeline pipeline = runspace.CreatePipeline();
String scriptfile = "..\\..\\Resources\\" + script + ".ps1";
Command myCommand = new Command(scriptfile, false);
foreach (DictionaryEntry entry in var)
{
CommandParameter testParam = new CommandParameter(entry.Key.ToString(),entry.Value);
//CommandParameter testParam = new CommandParameter(null, entry.Value);
myCommand.Parameters.Add(testParam);
}
pipeline.Commands.Add(myCommand);
Collection<PSObject> psObjects;
psObjects = pipeline.Invoke();
runspace.Close();
}
}
Code that calls firescript:
Hashtable var = new Hashtable();
var.Add("username","testb");
var.Add("path", "C:\\Documents and Settings\\testb");
RunScript.FireScript("remove user",var);
I think that you need to set this parameter attribute: ValueFromPipeline which conform to this link represents an 'Optional named parameter. True indicates that the cmdlet parameter takes its value from a pipeline object. Specify this keyword if the cmdlet accesses the complete object, not just a property of the object. The default is false.'
You can check also check this link for some examples. The code could be like this:
param(
[parameter(Position=0, ValueFromPipeline=$true)][string]$username
[parameter(Position=1, ValueFromPipeline=$true)][string]$path
)
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.
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