I have a PS-Module completely written in C#. It contains about 20 Cmdlets that are already in production. Some of these "share code". Take this example:
I have a Cmdlet called InvokeCommitCommand
that produces a "changeset". This Cmdlet also publishes metadata of this changeset. I would now like to create a new Cmdlet called PublishCommitCommand
that can be called independantly to execute the "publishing" of an already existing changeset. I would therefore like to refactor InvokeCommitCommand
to make use of the new Cmdlet PublishCommitCommand
and avoid code duplication.
More generally speaking ... I am trying to invoke a cmdlet CommandB
from cmdlet CommandA
. They are defined as follows
public CommandA : PSCmdlet
{
...
}
public CommandB : PSCmdlet
{
...
}
I have a few options here. But none of them work.
1. Option
Invoke CommandB
by creating an instance of it. That would've been my first guess. Like so:
var cmd = new CommandB();
cmd.Invoke();
Unfortunately that does not work. I get the exception:
Cmdlets derived from PSCmdlet cannot be invoked directly ...
So ... next option.
2. Option
Create an instance of PowerShell and run the command. Like so:
var ps = PowerShell.Create();
ps.AddCommand("CommandB");
ps.Invoke();
Unfortunately that doesn't work either. This causes a new PowerShell instance to be created and therefore I loose all stream redirections I may have attached to the current PowerShell instance I am running in.
I know I can reuse the runspace. But using the same runspace does NOT save me from losing my redirections. If CommandB
would call Write-Verbose "Huzzah!"
, I would not see that 'Huzzah!' anywhere.
In short: I need to run the CommandB
in the same PS instance as CommandA
3. Option
Use a ScriptBlock. Like so:
var sb = ScriptBlock.Create("CommandB");
sb.Invoke();
That's pretty nice. But the problem here is, that I have no means to pass any complex class arguments to the script block. If CommandB
has a parameter of type ... let's say PSCredential
, I have no easy way to pass that parameter to the script. If I had a PowerShell
object, I could easily do
PowerShell ps
ps.AddCommand("CommandB");
ps.AddArgument("Credential", someCredentialObject);
ps.AddArgument("TargetUri", new Uri("www.google.de"));
But I can not that with a ScriptBlock
. True, I could use InvokeWithContext
which allows me to pass variables to the scriptblock, but I would need to "wrap" each complex argument in a variable first... rather cumbersome.
Conclusion
Any ideas? The best thing would be if I somehow could - from inside CommandA
get access to the current instance of PowerShell
I am running in. I could then leverage option 2 without the issue of creating a new instance. But I do not know if that is even possible...