I am trying to run a PowerShell script via C# in a Windows Form.
The problem is that I have two enums, and I can't get them in the code right:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Collections.ObjectModel;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
namespace WindowsFormsApp6
{
static class Program
{
/// <summary>
/// Der Haupteinstiegspunkt für die Anwendung.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
} *here
}
Just so I understand, do I have to add the following under the static void? (at *here):
using (PowerShell PowerShellInstance = PowerShell.Create())
{
}
Then, do I copy paste it in there?
But of course it's not that easy; I Google'd it, but I don't understand what I have to do to get it working...
Enum RandomFood
{#Add Food here:
Pizza
Quesedias
Lasagne
Pasta
Ravioli
}
Enum Meat
{#Add Food here:
Steak
Beaf
Chicken
Cordonbleu
}
function Food {
Clear-Host
$Foods = [Enum]::GetValues([RandomFood]) | Get-Random -Count 6
$Foods += [Enum]::GetValues([Meat]) | Get-Random -Count 1
$foodsOfWeek = $Foods | Get-Random -Count 7
Write-Host `n "Here is you'r List of Meals for this week :D" `n
foreach ($day in [Enum]::GetValues([DayOfWeek])) {
([string]$day).Substring(0, 3) + ': ' + $foodsOfWeek[[DayOfWeek]::$day]
}
}
In the end, I would like to be able to just press on a button on the form, and then have it run the script which outputs it to a textbox.
Is that even possible?
Thanks for any help!
You could place your PowerShell script into a separate file and call it on a bound event.
// When a button is clicked...
private void Button_Click(object sender, EventArgs e)
{
// Create a PS instance...
using (PowerShell instance = PowerShell.Create())
{
// And using information about my script...
var scriptPath = "C:\\myScriptFile.ps1";
var myScript = System.IO.File.ReadAllText(scriptPath);
instance.AddScript(myScript);
instance.AddParameter("param1", "The value for param1, which in this case is a string.");
// Run the script.
var output = instance.Invoke();
// If there are any errors, throw them and stop.
if (instance.Streams.Error.Count > 0)
{
throw new System.Exception($"There was an error running the script: {instance.Streams.Error[0]}");
}
// Parse the output (which is usually a collection of PSObject items).
foreach (var item in output)
{
Console.WriteLine(item.ToString());
}
}
}
In this example, you would probably make better use of the passed in event arguments, and perform some better error handling and output logging, but this should get you down the right path.
Note that running your current script as-is will only declare your Food function, but won't actually run it. Make sure there is a function invocation in your script or C# code.
Related
I'm currently writing a C# cmdlet using the PowerShell 5.0 SDK.
I'm trying to pipe the StandardError of a third party executable to the cmdlet output when run from powershell in "real time".
I'm currently using the MedallionShell library to handle running the process. I've tried this with a normal C# win form and used the Command.StandardError.PipeToAsync(Console.OpenStandardOutput()) to get the output to print as the executable generated it to the console in "real time".
I tried to create my own Stream object that calls WriteVerbose but it didn't seem to print anything to the powershell screen (I am passing -Verbose to cmdlet when I run it).
My current flow looks something like this:
Open Powershell ISE
Load my module (C# dll)
Call my cmdlet with parameters
Command.Run
Command.StandardError.PipeToAsync(???)
Command.Wait (During this step, output should be flowing to powershell window)
Check Command.Result.Success.
Can anyone point me in the right direction on this?
You can not just call Cmdlet's Write methods (like WriteVerbose) from arbitrary thread. You need to marshal calls to this methods back to pipeline thread. A way to do that is to implement message loop, which would process messages from others threads, when other threads want to invoke something in pipeline thread.
Add-Type #‘
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Management.Automation;
using System.Threading;
[Cmdlet(VerbsLifecycle.Invoke, "Process")]
public class InvokeProcessCmdlet : Cmdlet {
[Parameter(Position = 1)]
public string FileName { get; set; }
[Parameter(Position = 2)]
public string Arguments { get; set; }
protected override void EndProcessing() {
using(BlockingCollection<Action> messageQueue = new BlockingCollection<Action>()) {
using(Process process = new Process {
StartInfo=new ProcessStartInfo(FileName, Arguments) {
UseShellExecute=false,
RedirectStandardOutput=true,
RedirectStandardError=true
},
EnableRaisingEvents=true
}) {
int numberOfCompleteRequests = 0;
Action complete = () => {
if(Interlocked.Increment(ref numberOfCompleteRequests)==3) {
messageQueue.CompleteAdding();
}
};
process.OutputDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteObject(args.Data));
}
};
process.ErrorDataReceived+=(sender, args) => {
if(args.Data==null) {
complete();
} else {
messageQueue.Add(() => WriteVerbose(args.Data));
}
};
process.Exited+=(sender, args) => complete();
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
foreach(Action action in messageQueue.GetConsumingEnumerable()) {
action();
}
}
}
}
}
’# -PassThru | Select-Object -First 1 -ExpandProperty Assembly | Import-Module
And you can test it with something like this:
Invoke-Process icacls 'C:\* /c' -Verbose
if you derive from PSCmdlet instrad of Cmdlet you will have access to this.Host.UI.WriteVerboseLine which can be called from any thread at your own risk (i think it doesn't prevents in any way that the outputed string will be mixed in the wrong way)
Anyway, in my experience it have always worked well so far, and if the cmdlet is something that only you will consume i think the risk may be acceptable.
Again, it works well if used in a console, i don't know if it behaves the intended way if you later on redirect the verbose stream somewhere else than the console or something that doesn't have a "UI"
For sure #PetSerAl solution is more appropriate if you have some more time to implement it
I am currently studying C# and am trying to prepare for next weeks lessons which will be the introduction of classes and methods. To that end i have attempted to build a class called MaxBox which is meant to be a general utility class that I can store some general functions in like 'Displaying a String' or 'Play Again'. I've built my main file (Program) and my class file (MaxBox) and lines #23, #28 and #59 return the same general error 'An object reference is required for the non-static field, method, or property 'program.MaxBox.DisplayStr(string)'. #57 returns a similar error 'An object reference is required for the non-static field, method, or property 'program.MaxBox.PlayAgain()'
I'm a total newb really, and i'm wrestling with objects, I've done some research to get myself this far but I don't understand the language enough yet to be able to understand what the resources I've read are saying I guess to solve this error. Help and guidance is greatly appreciated. I'm still in my first weeks and really I know nothing.
Program:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading; // needed for close
using System.Threading.Tasks;
namespace a020_Breakcase_MeaningOfNames_C
{
class Program
{
public void Play()
{
/*
* Conditionals - Use switch/Case statement too:
* Evaluate user data (name)
* Return meaning of name evaluated
* OR
* Return 'Name not found' error message
* Say goodbye
*/
MaxBox.DisplayStr("What's in a name? Let's find out!");
Console.Write("\n\n");
do
{
MaxBox.DisplayStr("Enter Name: ");
string uName = Console.ReadLine().ToLower();
switch (uName)
{
case "doyle":
Console.WriteLine("Doyle means descendant of Dubhghalle");
break;
case "fiona":
Console.WriteLine("Fiona is considered to be a Latinised form of the Gaelic word fionn, meaning \"white\", \"fair\".");
break;
case "hunter":
Console.WriteLine("Hunter means to search with purpose");
break;
case "liam":
Console.WriteLine("This name is a short form of the Irish name Uilliam (William) which is now use independently as a given name. As a Hebrew name, Liam means \"my people; I have a nation\".");
break;
case "max":
Console.WriteLine("Short for of Maximilian, Maxwell, and the various name using it as a first syllable.");
break;
case "portia":
Console.WriteLine("It is of Latin origin. Feminine form of a Roman clan name. Portia was used by Shakespeare as the name of a clever, determined young heroine in \"The Merchant of Venice\" who disguises herself as a lawyer to save her husband's life.");
break;
default:
Console.WriteLine("I'm sorry but I don't know the meaning of the name " + uName + ".");
break;
}
} while (MaxBox.PlayAgain());
MaxBox.DisplayStr("C#eers!");
}
static void Main(string[] args)
{
Program myProgram = new Program();
myProgram.Play();
Console.Read();
}
}
}
My Class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace a020_Breakcase_MeaningOfNames_C
{
class MaxBox
{
/*
* MaxBox is my general functions class
* Contains
* DisplayStr() - Display the string given
* PlayAgain() - If True, runs program again
*/
public String uName;
public String command;
public void DisplayStr(String StrTxt)
{ Console.Write(StrTxt); }
public Boolean PlayAgain()
{
Console.Write("\n\nDo you want to play again? (y)es or (n)o: ");
String command = Console.ReadLine().ToLower().Trim();
if (command == "y" || command == "yes") return true;
return false;
}
}
}
MaxBox's methods are not static, you need an instance of it.
MaxBox maxBox = new MaxBox();
at the beginning of your main class. Then
maxBox.DisplayStr(....)
also, just to get you thinking, you can replace:
if (command == "y" || command == "yes") return true;
return false;
with
return (command == "y" || command == "yes");
The methods PlayAgain and DisplayStr are instance methods on the type MaxBox. In order to call them you need an instance of MaxBox on which to call them. Right now you are trying to call them via the type name which only works for static methods
MaxBox.DisplayStr("hello world"); // wrong
MaxBox mb = new MaxBox();
mb.DisplayStr("hello world"); // right
It is possible to define methods such that you can invoke them from the type name. But doing so requires that they be marked as static
class MaxBox {
public static void DisplayStr2(string str){ ... }
}
MaxBox.DisplayStr2("hello world"); // right
So there's a program i saw, coded in c#. I keep getting errors on it. System.IndexOutOfRangeException is the main one, its happening at "args[0]". This is the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Skype4COMUserProfile
{
class Program
{
private static SKYPE4COMLib.Skype skype = new SKYPE4COMLib.Skype();
[STAThread]
static void Main(string[] args)
{
if (!skype.Client.IsRunning)
{
Environment.Exit(1);
}
skype.Client.OpenUserInfoDialog(args[0]);
}
}
}
I will be very grateful if someone could tell me how to fix this. thank you in advance!
Well that will fail if args is empty. Presumably you're meant to start the program by specifying a user name, or something like that.
You could always check for that:
if (args.Length == 0)
{
// Show an error dialog here
return;
}
I'm new to PowerShell and running PowerShell cmd-lets in C#. Specifically, I'm trying to use Citrix's XenDesktop SDK to write a web app to manage our XenDesktop environment.
Just as a quick test, I made a reference to the Citrix BrokerSnapIn.dll, which looks like it gives me good C# classes. However, when I hit the .Invoke with this error message:
"Cmdlets derived from PSCmdlet cannot be invoked directly."
I've searched and tried a bunch of stuff, but don't know how to call PSCmdlets. I'm kinda left thinking that I have to use strings and a runspace/pipeline, etc, to do this.
Thanks In Advanced,
NB
using System;
using System.Management.Automation;
using System.Management.Automation.Runspaces;
using Citrix.Broker.Admin.SDK;
namespace CitrixPowerShellSpike
{
class Program
{
static void Main(string[] args)
{
var c = new GetBrokerCatalogCommand {AdminAddress = "xendesktop.domain.com"};
var results = c.Invoke();
Console.WriteLine("all done");
Console.ReadLine();
}
}
}
You need to host the PowerShell engine in order to execute a PSCmdlet e.g. (from the MSDN docs):
// Call the PowerShell.Create() method to create an
// empty pipeline.
PowerShell ps = PowerShell.Create();
// Call the PowerShell.AddCommand(string) method to add
// the Get-Process cmdlet to the pipeline. Do
// not include spaces before or after the cmdlet name
// because that will cause the command to fail.
ps.AddCommand("Get-Process");
Console.WriteLine("Process Id");
Console.WriteLine("----------------------------");
// Call the PowerShell.Invoke() method to run the
// commands of the pipeline.
foreach (PSObject result in ps.Invoke())
{
Console.WriteLine(
"{0,-24}{1}",
result.Members["ProcessName"].Value,
result.Members["Id"].Value);
}
}
I am creating a program to see if I can run a byte array in C#.
The program should grab a byte array "MyBinaryData" and Load+Run it as a new program.
There will be a text box where you can enter the bytes to see the outcome (it's a experiment ;) ).
I have tried this:
byte[] binaryData = System.IO.File.ReadAllBytes("MyBytes.txt"); // the bytes are in a .txt file for simple tests before becoming a textbox.
Assembly LoadByte = Assembly.Load(binaryData);
MethodInfo M = LoadByte.EntryPoint;
if (M != null)
{ object o = LoadByte.CreateInstance(M.Name);
M.Invoke(o, new Object[] { null }); // this gives the error
}
else {
..... fail code here....
}
The problem is that it gives this error:
"System.Reflection.TargetInvocationException:......SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application."
My second test was:
Assembly assembly = Assembly.Load(binaryData);
Type bytesExe = assembly.GetType(); // problem: the GetType(); needs to know what class to run.
Object inst = Activator.CreateInstance(bytesExe);
But this needs to know what class in the byte array it needs to run.
I then tried:
var bytes = Assembly.Load(binaryData);
var entryPoint = bytes.EntryPoint;
var commandArgs = new string[0];
var returnValue = entryPoint.Invoke(null, new object[] { commandArgs });
But it gave me this:
"System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: SetCompatibleTextRenderingDefault must be called before the first IWin32Window object is created in the application."
My program.cs is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace Crypter
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form2());
}
}
}
What other way can I do this to have the Whole program opened?
Thanks in advance.
You have two way
first way make .exe from that byte array and then start it
second look at this execute byte array