简体   繁体   English

在 C# 中调用 Exchange PowerShell

[英]Calling Exchange PowerShell in C#

I have an application that allows users who would not otherwise have access to Exchange to perform some delegate functions.我有一个应用程序,它允许原本无法访问 Exchange 的用户执行某些委托功能。 This all works fine for users who are on standard domain-joined machines.这一切都适用于标准加入域的计算机上的用户。 However we are getting an increasing number of devices built by MEM (Intune) that, while managed according to policies, aren't domain-members.然而,我们正在获得越来越多的由 MEM (Intune) 构建的设备,这些设备虽然根据策略进行管理,但不是域成员。 This causes an issue when I attempt to create an Exchange PowerShell session in code because of the Kerberos authentication used.由于使用了 Kerberos 身份验证,当我尝试在代码中创建 Exchange PowerShell 会话时,这会导致问题。 This is an example of some code from the application:这是应用程序中一些代码的示例:

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;
    }
}

I have thought about using certificate-based authentication instead, but I don't know if this will work with Exchange PowerShell, or how to alter my code to use that instead of Kerberos.我曾考虑使用基于证书的身份验证,但我不知道这是否适用于 Exchange PowerShell,或者如何更改我的代码以使用它而不是 Kerberos。 I know I could change the WinRM clients/servers to allow unencrypted traffic and use Basic authentication, but I would rather not do this unless there is no other choice.我知道我可以更改 WinRM 客户端/服务器以允许未加密的流量并使用基本身份验证,但我宁愿不这样做,除非没有其他选择。

I'd be grateful for any advice on how I could get this to work for the non-domain-joined devices, even if it turns out that Basic is the only option.我将不胜感激有关如何使其适用于未加入域的设备的任何建议,即使事实证明 Basic 是唯一的选择。

If its relevant to anything I'm asking, we are using Exchange 2016 on-premises.如果它与我要问的任何事情相关,我们正在本地使用 Exchange 2016。

OK after a lot of trial and error I have a solution that is working.好的,经过大量试验和错误,我有一个可行的解决方案。 Sharing the info here in case its useful for anyone else, though this does seem to be a bit of a niche requirement.在这里分享信息以防它对其他人有用,尽管这似乎是一个小众需求。

First, you need to make sure that WinRM on the Exchange server is configured for HTTPS and the Certificate authentication is enabled on the service.首先,您需要确保 Exchange 服务器上的 WinRM 配置为 HTTPS,并且在服务上启用了证书身份验证。 Running the quick config will enable the HTTPS listener, and the option to enable certificate authentication can be enabled with the command below:运行快速配置将启用 HTTPS 侦听器,并且可以使用以下命令启用启用证书身份验证的选项:

Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true

You will need a certificate issued for the purposes of computer identification issued to the FQDN of the Exchange server for this.为此,您将需要为计算机标识颁发的证书,该证书颁发给 Exchange 服务器的 FQDN。 Your mileage may vary, but I found that I had to use a cert from our Enterprise CA as self-signed certificates didn't work for some reason.您的里程可能会有所不同,但我发现我必须使用我们企业 CA 的证书,因为自签名证书由于某种原因不起作用。

Next, create a new local user account on the Exchange server and add it to the Remote Management users group.接下来,在 Exchange 服务器上创建一个新的本地用户帐户并将其添加到远程管理用户组。

Next, we need a certificate issued to a domain user.接下来,我们需要颁发给域用户的证书。 Again, for me, self-signed ones didn't work and I had to use one from the Enterprise CA.同样,对我来说,自签名的不起作用,我不得不使用企业 CA 中的一个。 The full PFX (including private key) will need to be installed on all client machines that want to make use of the solution.需要在想要使用该解决方案的所有客户端计算机上安装完整的 PFX(包括私钥)。 Once you have a certificate, export just the public part of it as a CER file and install it on the Exchange server in the Trusted People container of the machine store.获得证书后,只需将其公共部分导出为 CER 文件,然后将其安装在机器存储的 Trusted People 容器中的 Exchange 服务器上。

After this, we need to bind the new local user to the certificate that has been imported which can be done with the following PowerShell command:在此之后,我们需要将新的本地用户绑定到已导入的证书,这可以使用以下 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)

For the Issuer thumbprint, I used the thumbprint of the certificate next up in the chain of the one I imported.对于颁发者指纹,我使用了我导入的证书链中下一个证书的指纹。

After this, its on to code:在此之后,它的代码:

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;
    }
}

In short, what I'm doing above is creating a new PowerShell session as the local user on the Exchange server via certificate auth.简而言之,我在上面所做的是通过证书身份验证在 Exchange 服务器上以本地用户身份创建一个新的 PowerShell 会话。 Adding the credentials of a domain user with permissions in Exchange into the session as a variable, then using them to create an Exchange PowerShell session that I can invoke commands into.将在 Exchange 中具有权限的域用户的凭据作为变量添加到会话中,然后使用它们创建可以调用命令的 Exchange PowerShell 会话。

I hope this helps anyone with a similar requirement.我希望这可以帮助任何有类似要求的人。 Its a bit more fiddly than simply opting for Basic, but it keeps things more secure at the cost of a bit more overhead on certificates, especially if they're only 12 month ones as ours are.它比简单地选择 Basic 稍微复杂一些,但它以更多的证书开销为代价使事情更安全,特别是如果它们像我们一样只有 12 个月。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM