【问题标题】:Connect to Microsoft Exchange PowerShell within C#在 C# 中连接到 Microsoft Exchange PowerShell
【发布时间】:2016-07-14 05:03:03
【问题描述】:

我正在尝试从 C# .NET WinForms 应用程序连接到远程 powershell。我的目标是创建我自己的 Microsoft PowerShell ISE 版本。所以我需要一种在远程机器上从我的应用程序执行 PowerShell 脚本的方法。我已经创建了几种方法,并从我的应用程序在本地机器上对其进行了测试。如果我不使用 WSManConnectionInfo 并使用 using (Runspace remoteRunspace = RunspaceFactory.CreateRunspace()) 我可以在本地执行脚本,就好像它是真正的 powershell(小脚本,用法变量,使用 ftfl 输出数据,做很多其他我通常用 powershell 做的事情。当我添加 WSManConnectionInfo 和将它指向我的 Exchange Server 而不是使用本地连接。它似乎能够执行诸如“get-mailbox”之类的基本内容,但是一旦我尝试通过管道传输内容,使用一些脚本功能(如 $variables)就会中断说它不受支持。

同样,我必须禁用 powershell.AddCommand("out-string"); 在本地不使用它时。

System.Management.Automation.dll 中出现“System.Management.Automation.RemoteException”类型的未处理异常。

附加信息:“Out-String”一词未被识别为 cmdlet、函数、脚本文件或可运行程序的名称。检查名称的拼写,或者如果包含路径,请验证路径是否正确并重试。

如果我不强制远程连接而只是在本地进行,则不会出现同样的错误。 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();
    }

关于为什么会发生这种情况以及如何解决如何让它以相同方式运行的任何建议?我看过很多像this 这样的博客,但是定义每个简单的命令对我来说没有意义。我还看到了创建本地连接然后执行remote connection within that 的选项,但这必须是最后的手段,因为它依赖于多个其他因素。

【问题讨论】:

标签: c# .net powershell exchange-server


【解决方案1】:

您似乎遇到了双跳身份验证问题,我在尝试此操作时遇到了同样的问题。

在搞砸之后,我最终在本地安装了 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 工具,这并不理想。
  • 它并不理想,但却是少数可行的解决方案之一。几年前我这样做了,但如果我今天这样做,我会考虑构建一个可以在 Exchange 主机上运行的小型 REST 服务或类似服务,这同样不理想,并且有其局限性,但值得考虑。注意:我假设您考虑过使用 EWS 并排除了这一点,否则请看一下。
  • 我不想在 Exchange 服务器上安装任何东西。我想像往常一样使用 ISE 进行连接。我认为 EWS 没有我需要做的所有事情。我想要标准的 powershell 功能。
  • 我不能为 C# 方面的事情做出贡献,但是还有另一种方法可以在不使用管理单元的情况下获取 Exchange cmdlet。您可以使用 ADSI 轮询以查找 CAS 服务器并执行 $session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri fqdngoeshere/powershell Import-PSSession $session | Out-Null 导入所有可用于运行管理工具的服务器的 cmdlet。这就是我在任何地方处理邮箱的方式,无需拖着大约 2GB 的 MT 角色文件,而且这种方法直接取自 MT 角色文件。
【解决方案2】:

查看https://blogs.msdn.microsoft.com/akashb/2010/03/25/how-to-migrating-exchange-2007-powershell-managed-code-to-work-with-exchange-2010/:

Exchange 2010 通过 PowerShell 提供的管理经验 已从本地移动到远程。 [...] 只有交换 cmdlet 才能在这种远程处理方案中工作,您将无法运行大多数 powershell cmdlet。 [...] 是的,这确实意味着您将无法在远程运行空间中运行诸如 Where-Object 和 .PS1 脚本之类的 cmdlet

这是一个限制吗?我不这么认为。我们可以很容易地通过创建一个新的 Session 并导入它来解决它


所以你需要这样做

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 你好,进展如何?也许我应该将这里的代码复制到 SO 以供将来的调查人员使用,您怎么看?
  • 对不起,我工作很忙,所以无法测试这个。但是,是的。这说得通。博客往往会消亡。我确实相信答案对我的问题有意义,因此我必须使用建议的解决方法。
【解决方案3】:

从 Exchange Server 2010 开始,您需要使用远程 PowerShell 会话而不是直接添加 Exchange PowerShell 管理单元(由于在 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();
}

【讨论】:

  • 谢谢。我已将赏金分配给第一个正确答案。你的代码是合法的,非常好,会很有帮助。
猜你喜欢
  • 2021-09-14
  • 1970-01-01
  • 2015-10-03
  • 2016-09-27
  • 2020-10-06
  • 1970-01-01
  • 2018-11-13
  • 1970-01-01
  • 2014-10-26
相关资源
最近更新 更多