在C#中连接到Microsoft Exchange PowerShell

10
我正在尝试从C# .NET WinForms应用程序连接到远程PowerShell。我的目标是创建自己的Microsoft PowerShell ISE版本。因此,我需要一种在远程机器上从我的应用程序执行PowerShell脚本的方法。我已经创建了几种方法,并在我的应用程序中对本地计算机进行了测试。如果我不使用WSManConnectionInfo,而是使用using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace()),我可以像真正的powershell一样在本地执行脚本(小脚本,使用变量,使用ftfl输出数据,做许多其他我通常使用powershell的事情)。但是当我添加WSManConnectionInfo并将其指向我的Exchange服务器而不是使用本地连接时,问题就开始了。它似乎能够执行基本操作,例如“get-mailbox”,但只要我尝试管道操作,使用一些脚本功能,例如$variables,它就会报错说不支持。
同样,当不在本地使用时,我必须禁用powershell.AddCommand("out-string");

类型为'System.Management.Automation.RemoteException'的未处理异常出现在System.Management.Automation.dll中。

附加信息:未能识别“Out-String”作为命令、函数、脚本文件或可操作程序的名称。请检查名称的拼写,或者如果包含路径,请验证该路径是否正确,然后重试。

如果我不强制远程连接而只是在本地执行,则不会出现完全相同的错误。似乎SchemaUri使其非常严格,仅执行基本命令。我看到其他人使用非常直接的信息,例如:
powershell.AddCommand("Get-Users");
powershell.AddParameter("ResultSize", count);

但是这种方法需要定义大量可能的选项,而且我不想去定义参数和其他东西。我只想像在PowerShell窗口中一样加载“脚本”并执行它。这是我现在使用的一个例子。

    public static WSManConnectionInfo PowerShellConnectionInformation(string serverUrl, PSCredential psCredentials)
    {
        var connectionInfo = new WSManConnectionInfo(new Uri(serverUrl), "http://schemas.microsoft.com/powershell/Microsoft.Exchange", psCredentials);
        //var connectionInfo = new WSManConnectionInfo(new Uri(serverUrl), "http://schemas.microsoft.com/powershell", psCredentials);
        connectionInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
        connectionInfo.SkipCACheck = true;
        connectionInfo.SkipCNCheck = true;
        connectionInfo.SkipRevocationCheck = true;
        connectionInfo.MaximumConnectionRedirectionCount = 5;
        connectionInfo.OperationTimeout = 150000;
        return connectionInfo;
    }
    public static PSCredential SecurePassword(string login, string password)
    {
        SecureString ssLoginPassword = new SecureString();
        foreach (char x in password) { ssLoginPassword.AppendChar(x); }
        return new PSCredential(login, ssLoginPassword);
    }
    public static string RunScriptPs(WSManConnectionInfo connectionInfo, string scriptText)
    {
        StringBuilder stringBuilder = new StringBuilder();
        // Create a remote runspace using the connection information.
        //using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace())
        using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace(connectionInfo))
        {
            // Establish the connection by calling the Open() method to open the runspace. 
            // The OpenTimeout value set previously will be applied while establishing 
            // the connection. Establishing a remote connection involves sending and 
            // receiving some data, so the OperationTimeout will also play a role in this process.
            remoteRunspace.Open();
            // Create a PowerShell object to run commands in the remote runspace.
            using (PowerShell powershell = PowerShell.Create())
            {
                powershell.Runspace = remoteRunspace;
                powershell.AddScript(scriptText);
                //powershell.AddCommand("out-string");
                powershell.Commands.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
                Collection<PSObject> results = powershell.Invoke();
            
                foreach (PSObject result in results) {
                        stringBuilder.AppendLine(result.ToString());
                }

            }
            // Close the connection. Call the Close() method to close the remote 
            // runspace. The Dispose() method (called by using primitive) will call 
            // the Close() method if it is not already called.
            remoteRunspace.Close();
        }

        // convert the script result into a single string
        return stringBuilder.ToString();
    }

有什么建议是为什么会这样发生以及如何解决它的方法?我看到了很多像这个的博客,但定义每一个简单的命令对我来说没有意义。我还看到了一种选项,即创建本地连接,然后在其中执行远程连接,但这得是最后的选择,因为它依赖于多种其他因素。

你的 serverUrl 是什么样子? - Aziz Kabyshev
https://mail1.domain.local/PowerShell 通常是与 PowerShell 相关的编程内容。 - MadBoy
3个回答

9
请参考https://blogs.msdn.microsoft.com/akashb/2010/03/25/how-to-migrating-exchange-2007-powershell-managed-code-to-work-with-exchange-2010/,了解如何将Exchange 2007 PowerShell管理代码迁移到Exchange 2010。在Exchange 2010中,通过PowerShell提供的管理体验已从本地完全转移到远程。在此情况下,只有Exchange cmdlet可用,大多数PowerShell cmdlet将无法运行。这确实意味着您将无法在远程运行空间中运行Where-Object和.PS1脚本等cmdlet。但这不是限制,我们可以轻松地创建新会话并导入它来解决此问题。因此,您需要做类似于以下的操作。
PSCredential creds = new PSCredential(userName, securePassword);
System.Uri uri = new Uri("http://Exchange-Server/powershell?serializationLevel=Full");

Runspace runspace = RunspaceFactory.CreateRunspace();

PowerShell powershell = PowerShell.Create();
PSCommand command = new PSCommand();
command.AddCommand("New-PSSession");
command.AddParameter("ConfigurationName", "Microsoft.Exchange");
command.AddParameter("ConnectionUri", uri);
command.AddParameter("Credential", creds);
command.AddParameter("Authentication", "Default");
powershell.Commands = command;
runspace.Open(); powershell.Runspace = runspace;
Collection<PSSession> result = powershell.Invoke<PSSession>();

powershell = PowerShell.Create();
command = new PSCommand();
command.AddCommand("Set-Variable");
command.AddParameter("Name", "ra");
command.AddParameter("Value", result[0]);
powershell.Commands = command;
powershell.Runspace = runspace;
powershell.Invoke();

powershell = PowerShell.Create();
command = new PSCommand();
command.AddScript("Import-PSSession -Session $ra");
powershell.Commands = command;
powershell.Runspace = runspace;
powershell.Invoke();

# now you can use remote PS like it's local one

看起来很合法,明天试一下 :-) 谢谢。希望这就是我遇到的问题。 - MadBoy
我也希望这能解决问题。祝你好运。请告诉我。 - Aziz Kabyshev
@MadBoy 你好,最近怎么样?也许我应该把这段代码复制到 Stack Overflow 上供未来的调查者使用,你觉得呢? - Aziz Kabyshev
抱歉,我在工作中很忙,所以无法测试这个。但是,是的,这很有道理。博客往往会逐渐消失。我确信答案对我的问题有意义,所以我必须使用提出的解决方法。 - MadBoy

3

从Exchange Server 2010开始,您需要使用远程PowerShell会话,而不是直接添加Exchange PowerShell snapin(因为Exchange 2010使用RBAC而不是ACL)。因此,您需要创建新的PowerShell会话(使用New-PSSession),然后导入它(使用Import-PSSession)。

您可以使用以下代码远程执行PowerShell命令:

void ExecutePowerShellUsingRemotimg()
{
    RunspaceConfiguration runspaceConfig = RunspaceConfiguration.Create();
    PSSnapInException snapInException = null;

    Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfig);
    runspace.Open();

    Pipeline pipeline = runspace.CreatePipeline();


    string serverFqdn = "FQDN of you server";
    pipeline.Commands.AddScript(string.Format("$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://{0}/PowerShell/ -Authentication Kerberos", serverFqdn));
    pipeline.Commands.AddScript("Import-PSSession $Session");
    pipeline.Commands.AddScript("your PowerShell script text");
    pipeline.Commands.Add("Out-String");
    Collection<PSObject> results = pipeline.Invoke();
    runspace.Close();

    StringBuilder sb = new StringBuilder();

    if (pipeline.Error != null && pipeline.Error.Count > 0)
    {
        // Read errors
        succeeded = false;
        Collection<object> errors = pipeline.Error.ReadToEnd();
        foreach (object error in errors)
            sb.Append(error.ToString());
    }
    else
    {
        // Read output
        foreach (PSObject obj in results)
            sb.Append(obj.ToString());
    }

    runspace.Dispose();
    pipeline.Dispose();
}

谢谢。我已经将赏金分配给第一个正确的答案。你的代码是合法的,非常好,并且会非常有帮助。 - MadBoy

0

看起来你遇到了双跳身份验证问题,我在尝试这个时也遇到了同样的问题。

在折腾了一番后,我最终安装了Exchange Powershell插件,并使用它们远程连接到Exchange服务器。

粗略示例:

    RunspaceConfiguration runspaceConfiguration = RunspaceConfiguration.Create();
    PSSnapInInfo info = runspaceConfiguration.AddPSSnapIn("Microsoft.Exchange.Management.PowerShell.Admin", out snapInException);                                    

    Runspace runspace = RunspaceFactory.CreateRunspace(runspaceConfiguration);
    runspace.Open();

    using (PowerShell powershell = PowerShell.Create())
    {
        powershell.Runspace = runspace;

        string script = string.Format(@"Get-Mailbox -Server {0} -Identity {1}", serverName, identity);
        powershell.AddScript(script);

        powershell.Invoke();

       // Do something with the output

    }

微软不鼓励在PowerShell中使用Exchange Addin(所以我认为对于C#也是如此)。这也使得我的应用程序需要在本地安装Exchange工具,这并不理想。 - MadBoy
这不是最理想的解决方案,但却是少数可行的方案之一。我几年前做过一次,它是有效的,但如果我今天要做的话,我会考虑构建一个小的REST服务或类似的东西,在Exchange主机上运行,再次强调,这不是最理想的方法,而且有其局限性,但值得考虑。注意:我假设你已经调查过使用EWS并将其排除在外,否则请看一下。 - ServerMonkey
我不想在Exchange服务器上安装任何东西。我想像平常一样使用ISE进行连接。我认为EWS没有我需要做的所有事情。我想要标准的PowerShell功能。 - MadBoy
我无法为C#方面做出贡献,但是有另一种方法可以获取Exchange cmdlets而不使用snap-in。您可以使用ADSI轮询查找CAS服务器,并执行$session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://fqdngoeshere/powershell Import-PSSession $session | Out-Null以导入运行管理工具的服务器上可用的所有cmdlet。这就是我如何在任何地方处理邮箱而无需携带2GB的MT角色文件,而且此方法直接从MT角色文件中提取。 - Chris Kuperstein

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接