• 一个Exchange后台管理程序(WEB)其二 .NET调用PowerShell


    引言

    EMS(Exchange Management Shell)是管理Exchange的常用手段之一,可以把他看作是加载了Exchange管理模块的PowerShell。所以一般有两种方式启动Exchange的命令行管理。第一是启动EMS,第二是启动PowerShell然后加载Exchange管理模块。要启动Exchange的管理模块,有两种方式,第一是在目录找到PowrShell Modules(如图左)。另一种方式是使用PSSession来加载(如图右)。

    所调用的指令为:

    View Code
    PS C:\Windows\system32> $session = New-PSSession -Authentication Kerberos -Credential lsow\exadmin -ConfigurationName mi
    crosoft.exchange -ConnectionUri http://exchange2010.lsow.ow/powershell
    PS C:\Windows\system32> Import-PSSession $session

    接下来是使用EMS和两种模块加载模式执行一条获取邮箱的操作,证明他们的执行结果是一致的。如图,他们都返回同一个邮箱账户,是一致的。

    要使.NET调用PowerShell组建能够管理Exchange必须在调用的时候加载管理模块,否则和Exchange相关的指令就不被支持。虽然并不是很明白这两种加载模块方式的具体区别,但是由于手动加载Exchange管理模块有两种方式,.NET(或者直接说C#)管理Exchange就有2种方式。第一种方式是将代码编译成COM+组建,注册到COM+应用程序中,以供客户机代码调用。这种方式来自其他网友的指导,稍后我会给出链接;第二种方式不需要注册COM+组建,更大程度得益于“远程管理”,第二种的调用方式五花八门,详情可以参考Exchange小组的技术博客,稍后我会给出链接。哦对了,如果你希望重现截图中的调用,记得不要使用X86版本的PowerShell。

    .NET管理Exchange

    首先是.NET调用PowerShell

    总的来说就是引用一个程序集,调用里面的对象模型。说到引用程序集就会有版本问题,楼主我测试过程中(主要是第二种方式调用),引用了win8的PowerShell程序集,去管理Exchange,结果悲剧了。win8上的是1.0版本的,服务器的是3.0版的。最要命的是,调用不会出错,但是取不到数据,这才是真正让人抓狂的。详情点击这里:http://social.msdn.microsoft.com/Forums/zh-CN/sharepointwebpartzhchs/thread/2315f4dd-9fcc-4291-955f-4e0d1edc100e。由于单纯调用这部分内容网上很多这里就不列举了。http://blogs.technet.com/b/exchange/archive/2009/11/02/3408653.aspx,这是Exchange团队的技术博客里的一篇文章。里面列举了执行远程指令的多种方式,比如,一种是直下载,另一种是指定一个PSSession,所以通过.NET调用也有多种形式。

    第一种方式,COM+应用程序

    这种方式是我最初在网上寻求解决方案的时候找到的。有两个版本(我所知的),第二个版本是在第一个版本之上详细补充的。这里我必须引用原文。

    版本1:http://www.cnblogs.com/xiaogelove/archive/2011/02/17/1956617.html

    版本2:http://www.cnblogs.com/gongguo/archive/2012/03/12/2392049.html

    不得不说,第二个版本已经非常详细...详细到“启动VS-新建项目”,所以我没有信心写的更详细,这里就略过。只贴出代码实现,另外,以上两个版本在权限设置里的说明不够,导致部署会出现问题,我会在这里补充说明几点。我对以上两个版本的代码进行了修改,测试可用。

    View Code
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.EnterpriseServices;
    using System.Linq;
    using System.Management.Automation;
    using System.Management.Automation.Runspaces;
    using System.Security;
    using System.Text;
    
    namespace OLC.EMps
    {
        
        public class ExShellRunner : ServicedComponent
        {
            #region 私有成员
            private Runspace runspace;
            private PSSnapInException exception;
            private bool hasRead;
            #endregion
    
            #region 私有方法
            private void initial()
            {//初始化
                runspace = CreateRunspace2010(out exception);
            }
    
            private static Runspace CreateRunspace(string exchangeVertion,
                                                        out PSSnapInException exception)
            {
                //返回运行环境
                RunspaceConfiguration config = RunspaceConfiguration.Create();
    
                config.AddPSSnapIn(exchangeVertion, out exception);
                Runspace runspace = RunspaceFactory.CreateRunspace(config);
                return runspace;
            }
    
            private static Runspace CreateRunspace2010(out PSSnapInException exception)
            {
                //针对Exchange2010的默认版本
                return CreateRunspace("Microsoft.Exchange.Management.PowerShell.E2010",
                                                                        out exception);
            }
    
            //PSObject无法序列化,所以标记为公开方法无意义
            private Collection<PSObject> RunSingleCommand2010(string name, params object[] args)
            {//执行一条指令
                int argCount = args.Count();//一般性验证
                if (argCount % 2 != 0)
                    throw new Exception("命令不完整,请核对。");
                int pair = argCount / 2;
    
                initial();
    
                try
                {
                    runspace.Open();
    
                    Pipeline line = runspace.CreatePipeline();
                    Command command = new Command(name);
                    for (int i = 0; i < pair; i++)
                    {
                        if (string.IsNullOrEmpty(args[2 * i + 1].ToString()))
                            //空串忽略参数 
                            command.Parameters.Add(args[2 * i].ToString());
                        else
                            command.Parameters.Add(args[2 * i].ToString(), args[2 * i + 1]);
                    }
    
                    line.Commands.Add(command);
                    var result = line.Invoke();
    
                    runspace.Close();
                    return result;
                }
                catch (Exception ex)
                {
                    string argStr = args
                        .Select(i => i.ToString())
                        .Aggregate((c, n) => c + "," + n);
                    throw new Exception(argStr + "," + ex.Message);
                }
            }
    
            #endregion
    
            public PSSnapInException RunExceptionReadOnce
            {
                //异常应该被及时获取
                get
                {
                    if (hasRead)
                    {
                        hasRead = false;
                        exception = null;
                        return null;
                    }
                    else
                    {
                        hasRead = true;
                        return exception;
                    }
                }
            }
    
            public bool IsExistMailBox(string identity)
            {//根据id判断邮箱是否存在
                Collection<PSObject> result = RunSingleCommand2010("Get-Mailbox", "Identity", identity);
                return result != null && result.Count != 0;
            }
    
            public string GetMailboxSize(string identity)
            {//查询邮箱容量
                Collection<PSObject> result = RunSingleCommand2010("Get-Mailbox", "Identity", identity);
                return result.First().Members["ProhibitSendQuota"].Value.ToString();
            }
    
            public void SetMaiboxSize(string identity, string warningSize, string disableSendSize,
                                                                                    string disableSize)
            {//设置邮箱容量,可以使用0.5GB这样的值
                RunSingleCommand2010("Set-Mailbox", "Identity",identity,"IssueWarningQuota", warningSize,
                             "ProhibitSendQuota",disableSendSize,"ProhibitSendReceiveQuota", disableSize);
            }
    
            public int GetMaiboxCountByOU(string ouPath)
            {//根据指定的OU获取邮箱用户数目
                var result = RunSingleCommand2010("Get-Mailbox", "OrganizationalUnit", ouPath ,"ResultSize","unlimited");
                return result.Count;
            }
    
            public void RemoveMailbox(string identity)
            {//根据指定的id移除邮箱
                RunSingleCommand2010("Remove-Mailbox", "Identity", identity, "Confirm", false);
            }
    
            public bool NewMailbox(string name, string userprincipalName, string password, string displayName
                                               , string organizationUnit, string database, string domainName)
            {//添加邮箱帐户
                string upn = userprincipalName + domainName;
    
                bool isExist = this.IsExistMailBox(upn);
                if (isExist)
                    throw new Exception("已存在的邮箱。");
    
                SecureString ss = new SecureString();
                foreach (var i in password) ss.AppendChar(i);
                var result = RunSingleCommand2010("New-Mailbox", "Name", name, "UserPrincipalName"
                    , upn, "Password", ss, "DisplayName", displayName, "OrganizationalUnit"
                    , organizationUnit, "DataBase", database);
                return result != null && result.Count != 0;
            }
    
            public bool IsAllExchangeDatabaseMounted(out string returnMessage)
            {//空串表示忽略参数
                bool anyDismouted = false;
    
                try
                {
                    var databases = this.RunSingleCommand2010("Get-Mailboxdatabase","Status","");
                    StringBuilder errorMessage = null;
                    errorMessage = new StringBuilder(
                           "一些邮件服务器的数据库工作不正常,名称分别为:");
                    foreach (var i in databases)
                    {
                        var obj = i.Properties["Mounted"];
                        var mountedStr = obj.Value.ToString();
                        bool mounted = bool.Parse(mountedStr.ToString());
                        if (mounted == false)
                            errorMessage.AppendFormat("{0},", i.Members["Name"]);
                        anyDismouted = anyDismouted || !mounted;
                    }
    
                    string message = anyDismouted ? errorMessage.ToString()
                                            : "所有邮件服务器数据工作正常!";
                    returnMessage = message;
                    return !anyDismouted;
                }
                catch (Exception ex)
                {//抛出异常
                    throw ex;
                }
            }
        }
    }

    以下是部署时候碰到的几个问题:
    1.按照以上引用的博文的顺序操作,并将程序集编译为64位,忽略VS的警告。

    2.在本地IIS上进行测试,不使用IISExpress(VS以管理员权限运行)。

    3.COM+应用程序设置中有个“标识”页,给他提供能够管理Exchange的用户。否则查询操作可以进行,但是增删就有问题了。

    4.COM+应用程序有个用户[角色-Creator-用户],向里面添加IIS用户(IIS_IUSERS)。

    目前我使用这种方式完成,这里是运行截图。

    第二种方式,远程调用

    没什么特别的,这里直接贴上代码。总共有三个类型...呃,本来是想弄好一点,后来越弄越没信心。

    主要的类型。

    View Code
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Management.Automation;
    using System.Management.Automation.Runspaces;
    using System.Text;
    
    namespace OLC.PowerShellInvoke
    {
        public class PowerShellInvoker
        {
            protected Runspace runspace;
    
            public PowerShellInvoker()
            {//调用本地powershell命令
                runspace = RunspaceFactory.CreateRunspace();
            }
    
            private Collection<PSObject> runSingleCommand(string name, object[] args)
            {
                //执行一条指令,为了正确使用上下文,这里不执行初始化
                int argCount = args.Count();//一般性验证
                if (argCount % 2 != 0)
                    throw new Exception("命令不完整,请核对。");
                int pair = argCount / 2;
    
                //initial();
    
                try
                {
                    runspace.Open();
    
                    Pipeline line = runspace.CreatePipeline();
                    Command command = new Command(name);
                    for (int i = 0; i < pair; i++)
                    {
                        if (string.IsNullOrEmpty(args[2 * i + 1].ToString()))
                        //空串忽略参数 
                        {
                            CommandParameter cp = new CommandParameter(args[2 * i].ToString());
                            command.Parameters.Add(cp);
                        }
                        else
                            command.Parameters.Add(args[2 * i].ToString(), args[2 * i + 1]);
                    }
    
                    line.Commands.Add(command);
                    var result = line.Invoke();
    
                    runspace.Close();
                    return result;
                }
                catch (Exception ex)
                {
                    //关闭运行空间
                    runspace.Close();
    
                    string argStr = args
                        .Select(i => i.ToString())
                        .Aggregate((c, n) => c + "," + n);
                    throw new Exception(argStr + "," + ex.Message);
                }
            }
    
            public Collection<PSObject> RunSingleCommand(string name, params object[] args)
            {
                return runSingleCommand(name, args);
            }
    
            public Collection<PSObject> RunSingleCommandWithArrayArgs(string name, object[] args)
            {//提供传递数组作为参数的版本
                return runSingleCommand(name, args);
            }
        }
    }

    供远程调用的类型,其实就构造函数有区别...

    View Code
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Management.Automation;
    using System.Management.Automation.Runspaces;
    using System.Security;
    using System.Text;
    
    namespace OLC.PowerShellInvoke
    {
        public class PowerShellRemoteInvoker:PowerShellInvoker
        {
            public PowerShellRemoteInvoker(string uri, string username, string password)
            {
                this.uri = uri;
                this.username = username;
                this.password = password;
    
                initial();
            }
    
            protected string uri;
            protected string username;
            protected string password;
    
            protected void initial()
            {
                SecureString ss = new SecureString();
                foreach (var c in password) ss.AppendChar(c);
                //在powershell中,也只能通过.net的方式实例化这个类型
                PSCredential credentail = new PSCredential(username, ss);
    
                WSManConnectionInfo connection = new WSManConnectionInfo(new Uri(uri),//"",
                    "http://schemas.microsoft.com/powershell/Microsoft.Exchange", 
                    credentail);
    
                runspace = RunspaceFactory.CreateRunspace(connection);            
            }
        }
    }

    最后一个是用来处理SecurityString的,没用上。

    View Code
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Management.Automation;
    using System.Text;
    
    namespace OLC.PowerShellInvoke
    {
        public class CredentialCreator
        {
            public static PSCredential Create(string username, string password)
            {
                System.Security.SecureString ss = new System.Security.SecureString();
                foreach (var c in password) ss.AppendChar(c);
                return new PSCredential(username, ss);
            }
        }
    }

    注意,凭据不能用一般的WebCredentials。
    写了个脚本测试了下,获取某个邮箱的配额(在AD的一个远程机子上运行的。)

    View Code
    #I @"E:\EBackUp\MoniRoot\Sort\Exchange\Code\ExhangeMailboxDatabaseDetectionService\OLC.PowerShell\bin\Debug"
    #r "OLC.PowerShellInvoke.dll"
    
    #r "System.Management.Automation.dll"
    
    open OLC.PowerShellInvoke;
    
    let rinvoker = new PowerShellRemoteInvoker("http://exsvr.search.ow/powershell","search\exadmin","*****");
    printf "%s" (rinvoker.RunSingleCommand("Get-Mailbox","Identity","czq@search.ow").Item(0).Members.Item("ProhibitSendQuota").Value.ToString())
    
    System.Console.ReadKey()

    结果是这样的。

    嗯,我用F#有两个原因,第一,好玩...;第二,脚本片段易于保存和重现。有空会测试IIS调用的情况,并补充到本文中。

    对比

    简要对比下:

    1.COM+:要求部署在Exchange服务器上(因为有PowerShell Modules),一般选用CAS服务器。另外COM+应用程序在没有使用的情况下会自动关闭,一有请求又开启,所以第一次调用会显得有点慢。

    2.远程调用。主要是要基于Kerberos的身份验证,使用SSL加密模式也可以,但是就要配置证书,所以目前我使用的都是Kerberos模式的身份验证。可以在同一个AD上部署,或者在建立了信任关系的其他AD中部署。相比第一种来说灵活(差别不大),也干净一点。但是我尚未测试使用WEB调用的情况,所以有没有其他问题尚未明确。

    结语

     我的两篇“小系列”就到这里结束了。如果各位朋友对本文的主题感兴趣,欢迎跟帖讨论!任何问题可以留言,初到博客园,我要做辛勤的园丁。

  • 相关阅读:
    闭包(closure)与协程共用时要注意的事情
    mysql---视图
    职责链模式
    JavaScript DOM(一)
    9.7 迭代
    [BLE--Link Layer]设备蓝牙地址
    Loopback測试软件AX1用户手冊 V3.1
    操作系统
    OpenCV特征点检測------Surf(特征点篇)
    linux 命令 xxd
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/2868028.html
Copyright © 2020-2023  润新知