繁体   English   中英

运行空间中的 C# Powershell - 如何让“格式列表”工作?

[英]C# Powershell in Runspace - How can i get “Format-List” to work?

我目前正在为 C# 中的 Exchange Online 租户编写联系人管理器,分别使用“System.Management.Automation”和“System.Management.Automation.Runspaces”中的 Powershell 命令和运行空间。 将联系人添加到 GAL 时效果很好。 但我坚持编辑联系人。

我需要使用 Powershell 命令获取联系方式。 我可以执行的代码如下所示:

var command = new PSCommand();
command.AddCommand("Get-Contact");
command.AddParameter("Identity", "someContact");

但是:这当然只给了我联系人姓名。 我需要扩展该命令。 我需要执行的等效本机 Powershell-Command 如下所示:

Get-Contact -Identity "someContact" | Format-List

当我尝试从上面以某种方式将该“格式列表”添加到命令方案中时 - 例如像这样:

var command = new PSCommand();
command.AddCommand("Get-Contact");
command.AddParameter("Identity", "someContact");
command.AddCommand("Format-List");

我收到一个异常,告诉我“格式列表”不是 Cmdlet 的名称或任何东西......我还尝试使用 AddParameter 甚至 AddArgument 添加它 - 这些都不起作用,我总是以错误结束。

使用谷歌,我在 Stackoverflow 上找到了线程,人们通过“AddScript()”命令传递了一个脚本。 但是当我做这样的事情时:

AddScript("Get-Contact -Identity 'someContact' | Format-List");

它告诉我,由于 Remote-Powershell 以无语言模式运行,因此无法识别语法。 我不知道,如果可能的话,如何更改该语言模式。

下面是我用来在我们的 Exchange Online 租户上执行 Remote-Powershell-Commands 的完整代码:

        // sPass Variable in SecureString umwandeln (Passwort muss ein SecureString
        // sein, sonst wird es von WSManConnectionInfo nicht akzeptiert!)
        SecureString ssPass = new NetworkCredential("", sPass).SecurePassword;

        // Exchange Online Credentials vorbereiten
        PSCredential credential = new PSCredential(sUserAndMail, ssPass);

        // Connection zu Exchange Online mit der URL vorbereiten und Authentication Mode auf Basic setzen
        WSManConnectionInfo wsEOConnInfo = new WSManConnectionInfo(new Uri(sURI), sSchema, credential);
        wsEOConnInfo.AuthenticationMechanism = AuthenticationMechanism.Basic;
        wsEOConnInfo.IdleTimeout = 60000;

        // Runspace erstellen, in dem die Powershell-Befehle ausgeführt werden
        using (Runspace runspace = RunspaceFactory.CreateRunspace(wsEOConnInfo))
        {
            // Connection herstellen
            runspace.Open();

            // Prüfen, ob Connection existiert. Wenn ja, Commands ausführen
            if (runspace.RunspaceStateInfo.State == RunspaceState.Opened)
            {
                PowerShell ps = PowerShell.Create();

                // Zunächst die ExecutionPolicy auf RemoteSigned für den aktuellen Benutzer setzen. 
                // Andernfalls stehen die MailContact-Befehle der Remote-Powershell nicht zur Verfügung.
                ps.AddCommand("Set-ExecutionPolicy").AddParameter("ExecutionPolicy", "RemoteSigned").AddParameter("Scope", "CurrentUser");
                ps.Invoke();

                // Nun den Befehl auf Basis des Use-Cases zusammenstellen
                switch (SearchCaseValue)
                {
                    case 1:
                    {
                        // Hier den Befehl zum Suchen des Kontakts auf Basis des Namens
                        var command = new PSCommand();
                        command.AddCommand("Get-Contact");
                        command.AddParameter("Identity", "*" + tb_SearchTerm.Text + "*");

                        // Kommando zusammensetzen und die Ausführung in diesem Runspace festlegen
                        ps.Commands = command;
                        ps.Runspace = runspace;

                        // Kommando ausführen
                        try
                        {
                            // Den Output des Invokes einer Collection zum Zugriff auf die Inhalte zuweisen
                            Collection<PSObject> psOutput = ps.Invoke();

                            // Einen neuen StringBuilder instantiieren
                            var sb = new System.Text.StringBuilder();

                            // Wenn der Inhalt der Collection nicht leer ist, dann jede Zeile des Powershell-Ouputs
                            // aus der Collection in neue Zeilen des StringBuilders schreiben
                            foreach (PSObject outputItem in psOutput)
                            {
                                lb_SearchResults.Items.Add(outputItem.ToString());
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.Message.ToString());
                        }

                        // Hintergrundfarbe der Listbox ändern, da nun Ergebnisse darin angezeigt werden
                        // und den Button zum Reset der Ergebnisse aktivieren
                        lb_SearchResults.BackColor = Color.White;
                        panel_SearchInProgress.Visible = false;
                        bt_ResetSearchResults.Enabled = true;

                        // Runspace Schließen
                        runspace.Close();
                        break;
                    }
                    case 2:
                    {
                        // Hier den Befehl zum Suchen des Kontakts auf Basis der eMail-Adresse erstellen
                        var command = new PSCommand();
                        command.AddCommand("Get-Contact");
                        command.AddParameter("Filter", "((WindowsEmailAddress -like '*" + tb_SearchTerm.Text + "*'))");

                        // Kommando zusammensetzen und die Ausführung in diesem Runspace festlegen
                        ps.Commands = command;
                        ps.Runspace = runspace;

                        // Kommando ausführen
                        try
                        {
                            // Den Output des Invokes einer Collection zum Zugriff auf die Inhalte zuweisen
                            Collection<PSObject> psOutput = ps.Invoke();

                            // Einen neuen StringBuilder instantiieren
                            var sb = new System.Text.StringBuilder();

                            // Wenn der Inhalt der Collection nicht leer ist, dann jede Zeile des Powershell-Ouputs
                            // aus der Collection in neue Zeilen des StringBuilders schreiben
                            foreach (PSObject outputItem in psOutput)
                            {
                                lb_SearchResults.Items.Add(outputItem.ToString());
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.Message.ToString());
                        }

                        // Hintergrundfarbe der Listbox ändern, da nun Ergebnisse darin angezeigt werden
                        // und den Button zum Reset der Ergebnisse aktivieren
                        lb_SearchResults.BackColor = Color.White;
                        panel_SearchInProgress.Visible = false;
                        bt_ResetSearchResults.Enabled = true;

                        // Runspace Schließen
                        runspace.Close();
                        break;
                    }
                }    
            }
            // Runspace schließen, falls nicht bereits geschehen. Wichtig, da in Exchange Online
            // nur maximal 3 Runspaces (Connections) gleichzeitig offen sein dürfen!
            runspace.Dispose();
        }

我希望您发现此摘录有助于确定问题。 对不起那里的德国评论。 我需要跟踪我的工作,你知道吗?:-)

那么......你能告诉我如何在不使用脚本的情况下将“格式列表”传递给远程 Powershell 吗?

非常感谢您提前提供的帮助! 史蒂芬

笔记:

  • 以下部分显示如何本地运行空间中进一步处理从远程PowerShell 运行空间获得的对象,在这种情况下,出于安全原因,这是必要的。

    • 在这里,必须在本地应用Format-List命令,但请注意,通常不需要调用Format-* cmdlet 来处理从 PowerShell SDK 调用返回的对象 - 请参阅下一点; 如果要创建对象的显示字符串表示,则只需要Format-* ,正如您在 PowerShell 控制台(终端)中看到的那样。
  • 底部讨论如何处理从 PowerShell SDK 调用返回对象

    • 事实证明,直接使用 output 对象作为数据是 Steffen 真正想要的。

马蒂亚斯 R。 Jessen在评论中提供了关键指针:

  • 出于安全原因,您的远程运行空间在语言模式允许您执行的特定 cmdlet方面都受到限制。

    • NoLanguage模式阻止使用任何类型的PowerShell 代码,这排除了使用.AddScript()方法。
    • 看起来,不允许使用Format-List cmdlet。
  • 如果您确实需要将Format-List应用于远程运行空间的 output,则需要使用第二个PowerShell实例来本地执行Format-List ,您将远程运行空间的 Z78E62211F6393D11CEDZF68 传递给该实例

    • 正如其他人所暗示的那样,仅当您想要 output 对象的仅用于显示的字符串表示时才需要Format-List ,如 PowerShell 控制台(终端)中所示。

      • 否则,处理数据,只需直接使用返回的对象及其属性,如底部所示。
    • 此外, Format-List本身并不是 output字符串,而是包含格式化指令对象 要将后者转换为它们编码的格式化字符串表示,请将它们传递给Out-String cmdlet。

一个简化的例子:

PowerShell psRemote = PowerShell.Create();
// Set up the remote runspace ...

PowerShell psLocal = PowerShell.Create();
// NO setup required for a local runspace.

using (psRemote)
using (psLocal) 
{

  // Get output from the remote runspace.
  var remoteOutput = psRemote.AddCommand("Get-Date").Invoke();

  // Pass the output to the local runspace for display formatting.
  foreach (var o in psLocal
                     .AddCommand("Format-List")
                     .AddCommand("Out-String")
                     .Invoke(remoteOutput)) 
  {
    // Print each object's display representation (a single, multi-line string)
    // To get the representation *line by line*, insert `.AddParameter("Stream")`
    // before the .Invoke()
    Console.WriteLine(o);
  }

}

直接使用来自 PowerShell SDK 调用的 output 对象:

Muhammad Arsalan Altaf 的回答显示了一种使用PSObject类型的反射成员(例如.Members.Properties )处理从非泛型.Invoke()方法调用返回的PSObject实例集合的方法。

但是,由于PSObject实现了IDynamicMetaObjectProvider接口,您可以通过dynamic变量使用DLR ,这大大简化了事情

代替:

foreach (PSObject outputItem in ps.Invoke())
{                                
     string name = outputItem.Properties["Name"].Value;
     // ...
}

由于键入了枚举变量dynamic ,您可以简单地执行以下操作:

foreach (dynamic outputItem in ps.Invoke())
{                                
     string name = outputItem.Name; // use direct property access via the DLR
     // ...
}

通常,最好使用.Invoke()方法的通用形式(例如, ps.Invoke<FileInfo>()以便获得早期绑定的 static 类型。

但是,这并不总是一种选择,即:

  • 如果output 对象是PSObject类型的动态对象,则适用于:

    • [pscustomobject]实例,它们是动态构造的自定义对象,最终由PSObject实现。
    • 通过远程处理返回的对象,如您的情况,原始 .NET 类型标识通常丢失,并且PSObject实例用于模拟原始类型 - 请参阅此答案以了解 PowerShell 用于远程处理的基于 XML 的序列化的概述和类型保真度何时丢失的解释。
  • 如果output 对象不都是相同的类型

    • 但是,您可以稍后使用.BaseObject属性使用as运算符或switch表达式或语句来获取包装在PSObject实例中的强类型对象。

以下示例代码说明了对象处理方法

using System;
using System.IO;
using System.Management.Automation;

namespace demo
{

  class ConsoleApp
  {
    static void Main(string[] args)
    {

      using (var ps = PowerShell.Create())
      {

        // Use `dynamic` to enumerate the *Collection<PSObject>* instance that is 
        // returned from the non-generic .Invoke() call.
        // This is necessary for:
        //   - [pscustomobject] instances
        //   - "rehydrated" object instances received via *remoting* that have
        //     lost their original type identity ([psobject] == [pscustomobject])
        //   - multiple objects that don't all have the same type.
        foreach (dynamic o in ps.AddScript("[pscustomobject] @{ Foo = 42 }").Invoke())
        {
          // Note: Trying to access a nonexistent property quietly returns null.
          Console.WriteLine($"Dynamic: {o.Foo}"); // -> 42
        }

        ps.Commands.Clear();

        // If the return objects *all have the same type* (other than PSObject),
        // use the generic form of the .Invoke() method and specify that time <T>
        // This returns a *Collection<T>* instance, the members of which you
        // access with early binding, as usual.
        foreach (DateTime o in ps.AddCommand("Get-Date").Invoke<DateTime>())
        {
          Console.WriteLine($"Static: {o.Year}"); // -> this year
        }

        ps.Commands.Clear();

        // *Hybrid approach* for *non-PSCustomObjects* of *non-uniform type*
        // Work with Collection<PSObject>, but use `.BaseObject as <T>`
        // to work with statically typed objects.
        // Here, a `switch` expression (C# 8+) is used, but note that with `as`, when `<T>` is a *value type*,
        // `as <T>?` must be used, i.e. a *nullable* type.
        foreach (PSObject o in ps.AddCommand("Get-Date").AddStatement().AddCommand("Get-Item").AddArgument("~").AddStatement().AddCommand("Get-Location").Invoke())
        {
          Console.WriteLine(
            o.BaseObject switch
            {
              DateTime dt => $"DateTime: {dt}",
              DirectoryInfo fi => $"DirectoryInfo: {fi}",
              _ => $"Other ({o.BaseObject.GetType().FullName}): {o.BaseObject}"
            }
          );
        }
      }

    }
  }
}

上面打印如下内容:

Dynamic: 42
Static: 2021
DateTime: 3/10/2021 10:39:36 AM
DirectoryInfo: /Users/jdoe
Other (System.Management.Automation.PathInfo): /Users/jdoe/Desktop

PSObject ps.Invoke()返回的 PSObject 包含 object 的所有属性。 您可以获得所有属性的值。 试试这个

ICollection<PSObject> psOutput = ps.Invoke();
foreach (PSObject outputItem in psOutput)
{                                
     var name = outputItem.Members["Name"].Value.ToString();
     var distinguishedName = outputItem.Members["DistinguishedName"].Value.ToString();
     var displayName = outputItem.Members["DisplayName"].Value.ToString();
     var lName = outputItem.Members["LastName"].Value.ToString();

}

只需输入您要获取的属性的名称,它将返回该属性的值。

帮助我了解为什么需要将 Contact object 传递给Format-Table 这基本上是在破坏 Contact Object 本身。 Format-Table 只是将 PSObject 布局到 PS 主机的一种方式,一旦将 object 传递给此 function 表示 object 将失去其所有属性和方法。

我将向您展示我对 AD 用户 Object 的意思的示例。

没有格式表:

PS C:\> $aduser=Get-ADuser -Filter *|select -First 1

PS C:\> $aduser.GetType()

IsPublic IsSerial Name                                     BaseType                                                         
-------- -------- ----                                     --------                                                         
True     False    ADUser                                   Microsoft.ActiveDirectory.Management.ADAccount                   

PS C:\> $aduser.psobject.Properties.Name
DistinguishedName
Enabled
GivenName
Name
ObjectClass
ObjectGUID
SamAccountName
SID
Surname
UserPrincipalName
PropertyNames
AddedProperties
RemovedProperties
ModifiedProperties
PropertyCount

使用格式表:

PS C:\> $aduser=Get-ADuser -Filter *|select -First 1|format-table

PS C:\> $aduser.GetType()

IsPublic IsSerial Name                                     BaseType                                                         
-------- -------- ----                                     --------                                                         
True     True     Object[]                                 System.Array                                                     

PS C:\> $aduser.psobject.Properties.name
Count
Length
LongLength
Rank
SyncRoot
IsReadOnly
IsFixedSize
IsSynchronized

我希望这是有道理的。

暂无
暂无

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

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