Search code examples
c#powershellrunspace

C# Powershell in Runspace - How can i get "Format-List" to work?


Im currently writing a Contact Manager for our Exchange Online Tenant in C# using the Powershell-Commands and Runspaces from "System.Management.Automation" and "System.Management.Automation.Runspaces" respectively. Its working fine for adding Contacts to the GAL. But im stuck at editing Contacts.

I need to get the Contact Details with the Powershell Commands. The code i can execute looks like this:

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

However: This gives me just the Contact-Name, of course. I would need to extend that Command. The equivalent native Powershell-Command i need to execute looks like this:

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

When i try to somehow add that "Format-List" to the Command Scheme from above - for example like this:

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

I get an Exception, telling me that "Format-List" is not the Name of a Cmdlet or anything ... I also tried adding it with AddParameter or even AddArgument - none of these work, i always end up with Errors.

Using Google i found threads here on Stackoverflow, where People passed a Script with the "AddScript()"-Command. But when i do something like this:

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

It tells me, that the Syntax is not recognized because that Remote-Powershell is running in No-Language Mode. I have no clue, how to change that Language Mode neither if it is even possible.

Below is the Full Code i use to execute Remote-Powershell-Commands on our Exchange Online Tenant:

        // 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();
        }

I hope you find this excerpt useful for nailing down the Problem. Sorry for the German Comments in there. I need to get track of what i do, you know?! :-)

So ... can you tell me how to pass "Format-List" to that Remote Powershell without using a Script?

Many thanks for your help in advance! Steffen


Solution

  • PSObject returned by ps.Invoke() contains all the properties of object. You can get values of all properties. Just try this

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

    Just enter the name of property you want to get and it will return you the value of that property.