设为首页 收藏本站
查看: 1720|回复: 0

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

[复制链接]

尚未签到

发表于 2015-9-10 13:12:58 | 显示全部楼层 |阅读模式
引言
  EMS(Exchange Management Shell)是管理Exchange的常用手段之一,可以把他看作是加载了Exchange管理模块的PowerShell。所以一般有两种方式启动Exchange的命令行管理。第一是启动EMS,第二是启动PowerShell然后加载Exchange管理模块。要启动Exchange的管理模块,有两种方式,第一是在目录找到PowrShell Modules(如图左)。另一种方式是使用PSSession来加载(如图右)。
DSC0000.jpg DSC0001.jpg
  所调用的指令为:


DSC0002.gif DSC0003.gif 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和两种模块加载模式执行一条获取邮箱的操作,证明他们的执行结果是一致的。如图,他们都返回同一个邮箱账户,是一致的。
DSC0004.png
  要使.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)。
  目前我使用这种方式完成,这里是运行截图。
DSC0005.png

第二种方式,远程调用
  没什么特别的,这里直接贴上代码。总共有三个类型...呃,本来是想弄好一点,后来越弄越没信心。
  主要的类型。


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()
  结果是这样的。
DSC0006.png
  嗯,我用F#有两个原因,第一,好玩...;第二,脚本片段易于保存和重现。有空会测试IIS调用的情况,并补充到本文中。

对比
  简要对比下:
  1.COM+:要求部署在Exchange服务器上(因为有PowerShell Modules),一般选用CAS服务器。另外COM+应用程序在没有使用的情况下会自动关闭,一有请求又开启,所以第一次调用会显得有点慢。
  2.远程调用。主要是要基于Kerberos的身份验证,使用SSL加密模式也可以,但是就要配置证书,所以目前我使用的都是Kerberos模式的身份验证。可以在同一个AD上部署,或者在建立了信任关系的其他AD中部署。相比第一种来说灵活(差别不大),也干净一点。但是我尚未测试使用WEB调用的情况,所以有没有其他问题尚未明确。

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

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-111980-1-1.html 上篇帖子: SHELL脚本学习指南_12248662_机械工业出版社_2009.04_ArnoldRobbins,NelsonH.F.Beebe著_Pg494.pdf 下篇帖子: PowerShell管理Exchange
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表