RJ.
RJ.

Reputation: 3181

What is the command to access Exchange Management Tools from C# code in Exchange 2010

In Exchange 2007 this line of code is used to load the Exchange Poweshell commands snapin:

PSSnapInInfo info = rsConfig.AddPSSnapIn(
  "Microsoft.Exchange.Management.PowerShell.Admin",
  out snapInException);

However, this does not exist in Exchange 2010 and I am pulling my hair out trying to find out how to access the Exchange Powershell commands from C# code. Microsoft.Exchange.Management.PowerShell.Admin does not exist anywhere on the Exchange Server and I can find nothing on Google that talks about an equivalent line of code.

How do I access Exchange Management Tools from C# code in Exchange 2010?

Below is my complete code for reference, it all works until I add the line of code:

                //Creating and Opening a Runspace
            RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
            PSSnapInException snapInException = null;
            PSSnapInInfo info = rsConfig.AddPSSnapIn(
               "Microsoft.Exchange.Management.PowerShell.Admin",
               out snapInException);
            Runspace myRunSpace = RunspaceFactory.CreateRunspace(rsConfig);
            myRunSpace.Open();

            //How Do I Run a Cmdlet?
            //create a new instance of the Pipeline class 
            Pipeline pipeLine = myRunSpace.CreatePipeline();

            //create an instance of the Command class
            // by using the name of the cmdlet that you want to run
            Command myCommand = new Command(txtCommand.Text);

            //add the command to the Commands collection of the pipeline
            pipeLine.Commands.Add(myCommand);

            Collection<PSObject> commandResults = pipeLine.Invoke();

            // iterate through the commandResults collection
            // and get the name of each cmdlet
            txtResult.Text = "start ....";
            foreach (PSObject cmdlet in commandResults)
            {
                string cmdletName = cmdlet.Properties["Name"].Value.ToString();
                System.Diagnostics.Debug.Print(cmdletName);
                txtResult.Text += "cmdletName: " + cmdletName;
            }
            txtResult.Text += ".... end";

Upvotes: 1

Views: 4092

Answers (3)

NNP
NNP

Reputation: 3451

This is what I am doing:

$sessionOptionsTimeout=180000

$sessionOptionsTimeout=180000

$so = New-PSSessionOption -OperationTimeout $sessionOptionsTimeout -IdleTimeout $sessionOptionsTimeout -OpenTimeout $sessionOptionsTimeout

$connectionUri="http://$fqdn/powershell?serializationLevel=Full;ExchClientVer=14.3.91.1"


$s = New-PSSession -ConnectionURI "$connectionUri" -ConfigurationName Microsoft.Exchange -SessionOption $so

$s | Enter-PSSession

PS>get-mailboxserver

EncryptionRequired        AutoDatabaseMountDial        DatabaseCopyAutoActivationPo
                                                       licy
------------------        ---------------------        ----------------------------
e                         GoodAvailability             Unrestricted
e                         GoodAvailability             Unrestricted

Now, converting above to .net (c#) should be easy...

Essentially an exerpt from: "C:\Program Files\Microsoft\Exchange Server\V14\Bin\ConnectFunctions.ps1"

Please refer to the following function:

function _NewExchangeRunspace([String]$fqdn, 
                [System.Management.Automation.PSCredential] 
$credential=$null, 

                [bool]$UseWIA=$true, 

                [bool]$SuppressError=$false,

                $ClientApplication=$null,

                $AllowRedirection=$false)

{
    $hostFQDN = _GetHostFqdn

    if (($fqdn -ne $null) -and ($hostFQDN -ne $null) -and ($hostFQDN.ToLower() -eq $fqdn.ToLower()))

    {
        $ServicesRunning = _CheckServicesStarted

        if ($ServicesRunning -eq $false)
        {
            return
        }
    }

    Write-Verbose ($ConnectFunctions_LocalizedStrings.res_0005 -f $fqdn)

    $so = New-PSSessionOption -OperationTimeout $sessionOptionsTimeout -IdleTimeout $sessionOptionsTimeout -OpenTimeout $sessionOptionsTimeout;
    $setupRegistryEntry = get-itemproperty HKLM:\SOFTWARE\Microsoft\ExchangeServer\v14\Setup -erroraction:silentlycontinue
    if ( $setupRegistryEntry -ne $null)
    {
        $clientVersion = "{0}.{1}.{2}.{3}" -f $setupRegistryEntry.MsiProductMajor, $setupRegistryEntry.MsiProductMinor, $setupRegistryEntry.MsiBuildMajor, $setupRegistryEntry.MsiBuildMinor
        $connectionUri = "http://$fqdn/powershell?serializationLevel=Full;ExchClientVer=$clientVersion"
    }
    else
    {
        $connectionUri = "http://$fqdn/powershell?serializationLevel=Full"
    }

    if ($ClientApplication -ne $null)
    {
        $connectionUri = $connectionUri + ";clientApplication=$ClientApplication"
    }

    write-host -fore Yellow ("connectionUri: " + $connectionUri)

    $contents = 'New-PSSession -ConnectionURI "$connectionUri" -ConfigurationName Microsoft.Exchange -SessionOption $so'

    if (-not $UseWIA)
    {
        $contents = $contents + ' -Authentication Kerberos -Credential $credential'
    }
    if ($SuppressError)
    {
        $contents = $contents + ' -erroraction silentlycontinue'
    }
    if ($AllowRedirection)
    {
        $contents = $contents + ' -AllowRedirection'
    }

    write-host -fore Yellow ("contents: " + $contents)
    write-host -fore Yellow ("join n contents: " + [string]::join("`n", $contents))


    [ScriptBlock] $command = $executioncontext.InvokeCommand.NewScriptBlock([string]::join("`n", $contents))
    $session=invoke-command -Scriptblock $command

  if (!$?)
    {
      # ERROR_ACCESS_DENIED = 5
      # ERROR_LOGON_FAILURE = 1326
      if (!(5 -eq $error[0].exception.errorcode) -and
          !(1326 -eq $error[0].exception.errorcode))
      {
            #Write-Verbose ($ConnectFunctions_LocalizedStrings.res_0006 -f $fqdn)
            return
      }
      else
      {
        # no retries if we get 5 (access denied) or 1326 (logon failure)
        #$REVIEW$ connectedFqdn is not set. Is it okay?
        break connectScope
      }
    }
  $session
}

Upvotes: 0

RJ.
RJ.

Reputation: 3181

After a lot of trial and error, I finally figured this out. The problem with the code above is it works great when run against Exchange 2007 but things have changed in Exchange 2010. Instead of the snapin called "Microsoft.Exchange.Management.PowerShell.Admin", use this snapin, "Microsoft.Exchange.Management.PowerShell.E2010".

The complete code to run a Powershell command from C# looks like this. Hope this helps someone else trying to do this.

You will need references to System.Management.Automation.Runspaces, System.Collections.ObjectModel and System.Management.Automation also.

I found that the reference to System.Management.Automation had to be manually added to the csproj file itself in the ItemGroup section using notepad like this:

  <Reference Include="System.Management.Automation" />

code below:

private class z_test
{
    //set up
    private RunspaceConfiguration rsConfig = RunspaceConfiguration.Create();
    private PSSnapInException snapInException = null;
    private Runspace runSpace;

    private void RunPowerShell()
    {
        //create the runspace
        runSpace = RunspaceFactory.CreateRunspace(rsConfig);
        runSpace.Open();
        rsConfig.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.E2010", out snapInException);

        //set up the pipeline to run the powershell command 
        Pipeline pipeLine = runSpace.CreatePipeline();

        //create the script to run
        String sScript = "get-mailbox -identity 'rj'";

        //invoke the command
        pipeLine.Commands.AddScript(sScript);

        Collection<PSObject> commandResults = pipeLine.Invoke();

        //loop through the results of the command and load the SamAccountName into the list
        foreach (PSObject results in commandResults)
        {
            Console.WriteLine(results.Properties["SamAccountName"].Value.ToString());
        }

        pipeLine.Dispose();

        runSpace.Close();
    }
}

Upvotes: 1

x0n
x0n

Reputation: 52450

I don't know for sure, but Exchange 2010 powershell might be implemented as a powershell 2.0 module, which is loaded in a different manner. To find out, go to a system with the exchange management shell on it, and fire it up. Next, run:

ps> get-module

This will list the loaded v2 modules. I would expect the exchange one to appear if you have started the dedicated exchange management shell. If you loaded the regular powershell console, try:

ps> get-module -list

This will list all modules available to load. If you spot the right one, then you'll need to build your code against the v2 system.management.automation dll. For reasons beyond the scope of this reply, v2 powershell's assembly has the same strong name as v1's, so you cannot easily have both versions of powershell on the same machine. Build this from a machine with v2 powershell installed:

InitialSessionState initial = InitialSessionState.CreateDefault();
initialSession.ImportPSModule(new[] { *modulePathOrModuleName* });
Runspace runspace = RunspaceFactory.CreateRunspace(initial);
runspace.Open();
RunspaceInvoke invoker = new RunspaceInvoke(runspace);
Collection<PSObject> results = invoker.Invoke(*myScript*);

Hope this helps,

-Oisin

Upvotes: 1

Related Questions