3

I am currently building a Windows-Service that will execute PowerShell Commands wenn some request comes in. There will be lots of requests comming in so I adopted the code from: Using Powershell RunspacePool multithreaded to remote server from C#

I am not using the WSManConnectionInfo but just do New-PSSession. See my Code at the end of this question.

This appears to work great for all regular Commands but one which uses Exhange Cmdlets and does an Import-PSSession before. I don't want to import the Cmdlets every time the script runs, but I am not getting the import right from C#. After invocation the PowerShell ErrorStream also notes that the term "Get-Mailbox" is unknown. I do not understand why the import does not work and what I need to do for it.

I had working for once but only with a single runspace. After moving to the RunspacePool I could not get it to work again.

Any help would be highly appreciated.

THE CODE:

static void Main(string[] args)
{ 
    string connectionUri = "https://.../Powershell";
    string userName = @"...\...";
    string password = "...";

    System.Uri uri = new Uri(connectionUri);
    System.Security.SecureString securePassword = String2SecureString(password);

    System.Management.Automation.PSCredential creds = new System.Management.Automation.PSCredential(userName, securePassword);

    //InitialSessionState iss = InitialSessionState.CreateDefault();
    //iss.ImportPSModule(new[] { "Get-Mailbox", "Get-MailboxStatistics" });

    RunspacePool runspacePool = RunspaceFactory.CreateRunspacePool();
    runspacePool.ThreadOptions = PSThreadOptions.UseNewThread;

    PowerShell powershell = PowerShell.Create();
    PSCommand command = new PSCommand();
    command.AddCommand(string.Format("New-PSSession"));
    command.AddParameter("ConfigurationName", "Microsoft.Exchange");
    command.AddParameter("ConnectionUri", uri);
    command.AddParameter("Credential", creds);
    command.AddParameter("Authentication", "Basic");
    command.AddParameter("AllowRedirection");

    // IS THIS NEEDED?
    PSSessionOption sessionOption = new PSSessionOption();
    sessionOption.SkipCACheck = true;
    sessionOption.SkipCNCheck = true;
    sessionOption.SkipRevocationCheck = true;
    command.AddParameter("SessionOption", sessionOption);

    powershell.Commands = command;

    runspacePool.Open();
    powershell.RunspacePool = runspacePool;
    Collection<PSSession> result = powershell.Invoke<PSSession>();

    foreach (ErrorRecord current in powershell.Streams.Error)
    {
        Console.WriteLine("Exception: " + current.Exception.ToString());
        Console.WriteLine("Inner Exception: " + current.Exception.InnerException);
    }

    // Returns the session
    if (result.Count != 1)
        throw new Exception("Unexpected number of Remote Runspace connections returned.");

    // THATS THE PART NOT WORKING
    // First import the cmdlets in the current runspace (using Import-PSSession)
    powershell = PowerShell.Create();
    command = new PSCommand();
    command.AddScript("Import-PSSession $Session -CommandName Get-Mailbox, Get-MailboxStatistics -AllowClobber -WarningAction SilentlyContinue -ErrorAction Stop -DisableNameChecking | Out-Null");
    command.AddParameter("Session", result[0]);

    // This is also strange... without the RunspaceInvoke I always get a SecurityException... 
    RunspaceInvoke scriptInvoker = new RunspaceInvoke();
    scriptInvoker.Invoke("Set-ExecutionPolicy -Scope Process Unrestricted");

    var tasks = new List<Task>();

    for (var i = 0; i < 3; i++)
    {
        var taskID = i;
        var ps = PowerShell.Create();
        ps.RunspacePool = runspacePool;
        PSCommand cmd = new PSCommand();
        cmd.AddCommand(@".\PSScript1.ps1");
        //cmd.AddScript("Get-Mailbox -ResultSize 5");
        cmd.AddParameter("Session", result[0]);
        ps.Commands = cmd;
        var task = Task<PSDataCollection<PSObject>>.Factory.FromAsync(
                                             ps.BeginInvoke(), r => ps.EndInvoke(r));
        System.Diagnostics.Debug.WriteLine(
                          string.Format("Task {0} created", task.Id));
        task.ContinueWith(t => System.Diagnostics.Debug.WriteLine(
                          string.Format("Task {0} completed", t.Id)),
                          TaskContinuationOptions.OnlyOnRanToCompletion);
        task.ContinueWith(t => System.Diagnostics.Debug.WriteLine(
                          string.Format("Task {0} faulted ({1} {2})", t.Id,
                          t.Exception.InnerExceptions.Count,
                          t.Exception.InnerException.Message)),
                          TaskContinuationOptions.OnlyOnFaulted);
        tasks.Add(task);
    }

    Task.WaitAll(tasks.ToArray());               
}

private static SecureString String2SecureString(string password)
{
    SecureString remotePassword = new SecureString();
    for (int i = 0; i < password.Length; i++)
        remotePassword.AppendChar(password[i]);

    return remotePassword;
}

The Script in short form:

Param($Session)

Import-PSSession $Session -CommandName Get-Mailbox, Get-MailboxStatistics -ErrorAction SilentlyContinue | Out-Null

Get-Mailbox -ResultSize 5 | SElectObject Name, Alias, ...

The Script works like that, but when I try to comment out the Import-PSSession part I get the unknown term Get-Mailbox error.

Thanks in advance and best regards Gope

Community
  • 1
  • 1
Gope
  • 1,672
  • 1
  • 13
  • 19

2 Answers2

2

You need to uncomment the ISS stuff. Do something like this:

$iss = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault()
$iss.ImportPSModule($module)

Replace $module with the name of the Exchange module, or just populate the variable with the module name.

Then when you create the RunspacePool do something like this:

$runspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool($minRunspaces, $maxRunspaces, $iss, $Host)

This should make the module available for all runspaces created from the runspace pool.

ojk
  • 2,502
  • 15
  • 17
  • Hello and thank you. I will try that in a few minutes and get back to you! :) – Gope Aug 14 '14 at 08:29
  • I have no Idea what the Exchange module's name is. I tried using the Cmdlet Names, but that did not work. Do you know what value to put there? – Gope Aug 14 '14 at 08:34
  • I'm not an Excel guy myself, so I just assumed that it was packed as a module. If you have it installed you could do a Get-Module -ListAvailable to see if you can see it there. – ojk Aug 14 '14 at 08:40
  • If I'm not misstaken, it seems it might be a snapin instead of a module. In that case, you need to load it like this instead: $iss.ImportPSSnapIn($snapName,[ref]'') – ojk Aug 14 '14 at 08:42
  • You still need to figure out the name of it though :) – ojk Aug 14 '14 at 08:43
  • Ok, I will try to find it out. Thank you so far for clarifying that the initialsessionstate can provide commands to the runspaces from the pool! +1 for that! :) – Gope Aug 14 '14 at 08:48
0

I found a way to make it work it appears. I changed the AddScript to AddCommand and used AddParameter.

// First import the cmdlets in the current runspace (using Import-PSSession)
powershell = PowerShell.Create();
powerShell.RunspacePool = runspacePool;
command = new PSCommand();
command.AddScript("Import-PSSession");
command.AddParameter("Session", result[0]);
command.AddParameter("CommandName", new[]{"Get-Mailbox", "Get-MailboxStatistics"});
command.AddParameter("ErrorAction ", "SilentlyContinue ");


powerShell.Invoke();

That did the trick...

Gope
  • 1,672
  • 1
  • 13
  • 19