[英]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 调用返回的对象。
出于安全原因,您的远程运行空间在语言模式和允许您执行的特定 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
类型的动态对象,则适用于:
如果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.