在 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 个月的证书。