1
votes

I have a series of fully functional powershell scripts that leverage remoting that I want to be able to call from a C# WinForm. Here's the code I have so far

    private void button1_Click(object sender, EventArgs e)
    {
        _runspace = RunspaceFactory.CreateRunspace();
        _runspace.Open();
        _ps = PowerShell.Create();

        _ps.Runspace = _runspace;

        var output = new PSDataCollection<PSObject>();
        output.DataAdded += DataAdded;

        _ps.AddScript(@"C:\projects\Acme\trunk\PowerShell\deploy-qa-p5.ps1");
        _invokeResult = _ps.BeginInvoke<PSObject, PSObject>(null, output);

    }

When I run this code, I see that any powershell commands in my script that are meant to execute against a remote session are actually being executed on my local PC.

For example, this bit of code when executed directly from Powershell.exe, uninstalls an application from a remote server. When run from the C# code above, this same code uninstalls said application on my local machine:

Invoke-Command -Session $remoteSession -scriptblock $uninstallScript -ArgumentList $applicationGuid

Again, the exact same PS script referenced in my C# code, when executed directly from powershell.exe, works as expected against the remote server.

Via googling, I found how one can create a remote runspace from C#. However, that would require me to refactor my PS scripts substantially to pull out any remoting code, which would be moved to C#. Since I still need to be able to run my PS scripts in standalone mode (i.e. directly from powershell.exe), this is not a viable solution.

Can anyone suggest a solution to this problem that doesn't require re-jiggering my PS scripts?

1
Check if you have you $remoteSession when running from C#. I'm using Invoke-Command from C#, but use -ComputerName instead of -Session and it's working just fine for me - Andrey Marchuk

1 Answers

-1
votes

When you create the Runspace object, you need to pass it a WSManConnectionInfo object to create a remote runspacen. This MSDN article has details on how to do this. Unlike the example in this topic, make sure you use a WSManConnectionInfo constructor that takes parameters that allow you to specify the remoting endpoint e.g.

_runspace = RunspaceFactory.CreateRunspace("http://ComputerName:5985/wsman");

Sorry, I misread the question. Calling a script that happens to perform remoting should work just as well as when calling from a PowerShell console. The following slightly modified version works just fine for me on my home workgroup:

private void button1_Click(object sender, EventArgs e)
{
    _runspace = RunspaceFactory.CreateRunspace();
    _runspace.Open();
    _ps = PowerShell.Create();

    _ps.Runspace = _runspace;

    var output = new PSDataCollection<PSObject>();
    //output.DataAdded += DataAdded;

    var netCreds = new System.Net.NetworkCredential("Keith", Settings.Default.Password);
    var creds = new PSCredential(netCreds.UserName, netCreds.SecurePassword);

    _ps.AddScript("param($creds) Invoke-Command -cn Kids-PC -Scriptblock {hostname} -Credential $creds").AddArgument(creds);
    _invokeResult = _ps.BeginInvoke<PSObject, PSObject>(null, output);
    _invokeResult.AsyncWaitHandle.WaitOne();
    _ps.EndInvoke(_invokeResult);
    textBox1.Text = output[0].ToString();
}

BTW not sure why you added DataAdded to the output collection for the script? I commented that out. I also have to pass along credentials since I'm on a workgroup and not a domain.

In order to debug this, when it fails take a look at the _ps.Streams.Error object in the debugger. Scroll to the bottom and open Results View.