在 C# 中调用 Exchange PowerShell
Calling Exchange PowerShell in C#
我有一个应用程序允许原本无法访问 Exchange 的用户执行某些委托功能。对于使用标准加入域的计算机的用户来说,这一切都很好。然而,我们越来越多的设备由 MEM (Intune) 构建,虽然根据策略进行管理,但它们不是域成员。由于使用了 Kerberos 身份验证,当我尝试在代码中创建 Exchange PowerShell 会话时,这会导致出现问题。这是应用程序中的一些代码示例:
internal static bool UpdateMailbox(string identity, out string reply)
{
reply = string.Empty;
try
{
string server = DatabaseManager.GetConfigOption("ExchangePowerShellURI"); // http://exc16-01.domain.com/PowerShell
string uname = DatabaseManager.GetConfigOption("ADUser"); // user@domain.com
SecureString password = GetPassword(); // makes a SecureString for the user password
PSCredential creds = new PSCredential(uname, password);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(server), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", creds);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
using (Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo))
{
rs.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = rs;
// do stuff with PowerShell
return true;
}
}
}
catch (Exception ex)
{
reply = ex.Message;
return false;
}
}
我考虑过改用基于证书的身份验证,但我不知道这是否适用于 Exchange PowerShell,或者如何更改我的代码以使用它而不是 Kerberos。我知道我可以更改 WinRM clients/servers 以允许未加密的流量并使用基本身份验证,但我宁愿不这样做,除非别无选择。
如果我能就如何使它适用于未加入域的设备提供任何建议,我将不胜感激,即使事实证明 Basic 是唯一的选择。
如果它与我要问的任何问题相关,那么我们正在本地使用 Exchange 2016。
好的,经过大量试验和错误后,我有一个有效的解决方案。在这里分享信息以防它对其他人有用,尽管这似乎是一个小众要求。
首先,您需要确保 Exchange 服务器上的 WinRM 已配置为 HTTPS,并且已在该服务上启用证书身份验证。 运行 快速配置将启用 HTTPS 侦听器,并且可以使用以下命令启用启用证书身份验证的选项:
Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true
为此,您将需要为向 Exchange 服务器的 FQDN 颁发计算机标识而颁发的证书。您的情况可能有所不同,但我发现我必须使用我们企业 CA 的证书,因为 self-signed 证书由于某种原因无效。
接下来,在 Exchange 服务器上创建一个新的本地用户帐户并将其添加到远程管理用户组。
接下来,我们需要颁发给域用户的证书。同样,对我来说,self-signed 个没有用,我不得不使用来自企业 CA 的一个。需要在所有想要使用该解决方案的客户端计算机上安装完整的 PFX(包括私钥)。获得证书后,仅将其 public 部分导出为 CER 文件,并将其安装在机器商店的 Trusted People 容器中的 Exchange 服务器上。
在此之后,我们需要将新的本地用户绑定到已导入的证书,这可以使用以下 PowerShell 命令完成:
New-Item -Path WSMan:\localhost\ClientCertificate -Subject <upn of the user it was issued to> -URI * -Issuer <thumbprint of the CA cert> -Credential (Get-Credential)
对于颁发者指纹,我使用了导入证书链中下一个证书的指纹。
在此之后,进入代码:
internal static bool UpdateMailbox(string identity, out string reply)
{
reply = string.Empty;
try
{
string psURI = DatabaseManager.GetConfigOption("ExchangePowerShellURI"); // http://exc16-01.domain.com/PowerShell
string server = new Uri(psURI).Host;
string uname = DatabaseManager.GetConfigOption("ADUser"); // user@domain.com
SecureString password = GetPassword(); // makes a SecureString for the user password
PSCredential creds = new PSCredential(uname, password); // PowerShell credential object for later use
string thumbPrint = DatabaseManager.GetConfigOption("CertificateThumbprint"); // thumbprint of the cert to use
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri($"https://{server}"), "", creds);
connectionInfo.CertificateThumbprint = thumbPrint;
connectionInfo.Port = 5986;
connectionInfo.MaximumConnectionRedirectionCount = 5;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;
using (Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo))
{
rs.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = rs;
// Add PowerShell credential object into our session as a variable to use later
PSCommand addCreds = new PSCommand();
addCreds.AddCommand("Set-Variable");
addCreds.AddParameter("Name", "cred");
addCreds.AddParameter("Value", creds);
ps.Commands = addCreds;
ps.Invoke();
// Create a new session of the Exchange Management Shell in our session called $ra. Kerberos is OK here as the Exchange server is pointing to itself
PSCommand addSnapin = new PSCommand();
string newSession = $"$ra = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri {psURI} -Authentication Kerberos -Credential $cred";
addSnapin.AddScript(newSession);
ps.Commands = addSnapin;
ps.Invoke();
// Invoke a command in our Exchange session from earlier
PSCommand setMbx = new PSCommand();
string strSetMbx = $"{{ Set-Mailbox -Identity {identity} -GrantSendOnBehalfTo admin@doman.com }}";
setMbx.AddScript($"Invoke-Command -ScriptBlock {strSetMbx} -Session $ra");
ps.Commands = setMbx;
var result = ps.Invoke();
// Do any further processing
return true;
}
}
}
catch (Exception ex)
{
reply = ex.Message;
return false;
}
}
简而言之,我在上面所做的是通过证书身份验证以 Exchange 服务器上的本地用户身份创建一个新的 PowerShell 会话。将在 Exchange 中具有权限的域用户的凭据作为变量添加到会话中,然后使用它们创建我可以在其中调用命令的 Exchange PowerShell 会话。
我希望这对有类似需求的人有所帮助。它比简单地选择 Basic 更繁琐,但它以增加证书开销为代价保持更安全,特别是如果它们像我们一样只有 12 个月的证书。
我有一个应用程序允许原本无法访问 Exchange 的用户执行某些委托功能。对于使用标准加入域的计算机的用户来说,这一切都很好。然而,我们越来越多的设备由 MEM (Intune) 构建,虽然根据策略进行管理,但它们不是域成员。由于使用了 Kerberos 身份验证,当我尝试在代码中创建 Exchange PowerShell 会话时,这会导致出现问题。这是应用程序中的一些代码示例:
internal static bool UpdateMailbox(string identity, out string reply)
{
reply = string.Empty;
try
{
string server = DatabaseManager.GetConfigOption("ExchangePowerShellURI"); // http://exc16-01.domain.com/PowerShell
string uname = DatabaseManager.GetConfigOption("ADUser"); // user@domain.com
SecureString password = GetPassword(); // makes a SecureString for the user password
PSCredential creds = new PSCredential(uname, password);
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri(server), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", creds);
connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Kerberos;
using (Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo))
{
rs.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = rs;
// do stuff with PowerShell
return true;
}
}
}
catch (Exception ex)
{
reply = ex.Message;
return false;
}
}
我考虑过改用基于证书的身份验证,但我不知道这是否适用于 Exchange PowerShell,或者如何更改我的代码以使用它而不是 Kerberos。我知道我可以更改 WinRM clients/servers 以允许未加密的流量并使用基本身份验证,但我宁愿不这样做,除非别无选择。
如果我能就如何使它适用于未加入域的设备提供任何建议,我将不胜感激,即使事实证明 Basic 是唯一的选择。
如果它与我要问的任何问题相关,那么我们正在本地使用 Exchange 2016。
好的,经过大量试验和错误后,我有一个有效的解决方案。在这里分享信息以防它对其他人有用,尽管这似乎是一个小众要求。
首先,您需要确保 Exchange 服务器上的 WinRM 已配置为 HTTPS,并且已在该服务上启用证书身份验证。 运行 快速配置将启用 HTTPS 侦听器,并且可以使用以下命令启用启用证书身份验证的选项:
Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true
为此,您将需要为向 Exchange 服务器的 FQDN 颁发计算机标识而颁发的证书。您的情况可能有所不同,但我发现我必须使用我们企业 CA 的证书,因为 self-signed 证书由于某种原因无效。
接下来,在 Exchange 服务器上创建一个新的本地用户帐户并将其添加到远程管理用户组。
接下来,我们需要颁发给域用户的证书。同样,对我来说,self-signed 个没有用,我不得不使用来自企业 CA 的一个。需要在所有想要使用该解决方案的客户端计算机上安装完整的 PFX(包括私钥)。获得证书后,仅将其 public 部分导出为 CER 文件,并将其安装在机器商店的 Trusted People 容器中的 Exchange 服务器上。
在此之后,我们需要将新的本地用户绑定到已导入的证书,这可以使用以下 PowerShell 命令完成:
New-Item -Path WSMan:\localhost\ClientCertificate -Subject <upn of the user it was issued to> -URI * -Issuer <thumbprint of the CA cert> -Credential (Get-Credential)
对于颁发者指纹,我使用了导入证书链中下一个证书的指纹。
在此之后,进入代码:
internal static bool UpdateMailbox(string identity, out string reply)
{
reply = string.Empty;
try
{
string psURI = DatabaseManager.GetConfigOption("ExchangePowerShellURI"); // http://exc16-01.domain.com/PowerShell
string server = new Uri(psURI).Host;
string uname = DatabaseManager.GetConfigOption("ADUser"); // user@domain.com
SecureString password = GetPassword(); // makes a SecureString for the user password
PSCredential creds = new PSCredential(uname, password); // PowerShell credential object for later use
string thumbPrint = DatabaseManager.GetConfigOption("CertificateThumbprint"); // thumbprint of the cert to use
WSManConnectionInfo connectionInfo = new WSManConnectionInfo(new Uri($"https://{server}"), "", creds);
connectionInfo.CertificateThumbprint = thumbPrint;
connectionInfo.Port = 5986;
connectionInfo.MaximumConnectionRedirectionCount = 5;
connectionInfo.SkipCACheck = true;
connectionInfo.SkipCNCheck = true;
using (Runspace rs = RunspaceFactory.CreateRunspace(connectionInfo))
{
rs.Open();
using (PowerShell ps = PowerShell.Create())
{
ps.Runspace = rs;
// Add PowerShell credential object into our session as a variable to use later
PSCommand addCreds = new PSCommand();
addCreds.AddCommand("Set-Variable");
addCreds.AddParameter("Name", "cred");
addCreds.AddParameter("Value", creds);
ps.Commands = addCreds;
ps.Invoke();
// Create a new session of the Exchange Management Shell in our session called $ra. Kerberos is OK here as the Exchange server is pointing to itself
PSCommand addSnapin = new PSCommand();
string newSession = $"$ra = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri {psURI} -Authentication Kerberos -Credential $cred";
addSnapin.AddScript(newSession);
ps.Commands = addSnapin;
ps.Invoke();
// Invoke a command in our Exchange session from earlier
PSCommand setMbx = new PSCommand();
string strSetMbx = $"{{ Set-Mailbox -Identity {identity} -GrantSendOnBehalfTo admin@doman.com }}";
setMbx.AddScript($"Invoke-Command -ScriptBlock {strSetMbx} -Session $ra");
ps.Commands = setMbx;
var result = ps.Invoke();
// Do any further processing
return true;
}
}
}
catch (Exception ex)
{
reply = ex.Message;
return false;
}
}
简而言之,我在上面所做的是通过证书身份验证以 Exchange 服务器上的本地用户身份创建一个新的 PowerShell 会话。将在 Exchange 中具有权限的域用户的凭据作为变量添加到会话中,然后使用它们创建我可以在其中调用命令的 Exchange PowerShell 会话。
我希望这对有类似需求的人有所帮助。它比简单地选择 Basic 更繁琐,但它以增加证书开销为代价保持更安全,特别是如果它们像我们一样只有 12 个月的证书。