Search code examples
c#powershellexchange-online

exchange online Get-DistributionGroupMember command not getting Initials


I am doing this PowerShell command Get-DistributionGroupMember but it is not returning the user's middle name. Is there something I am missing? Any other way to do this?

public List<EmailGroup> GetDistributionGroupMember(string distributionAddress, string displayName)
{
    ClearPowerShell();
    List<EmailGroup> EmailGroups = new List<EmailGroup>();

    powerShell.AddCommand("get-distributiongroupmember")
              .AddParameter("Identity", distributionAddress)
              .AddParameters(new Dictionary<string, object>()
              {
                    { "ResultSize", "Unlimited" }
              })
              .AddCommand("Select-Object")
              .AddArgument(new string[] { "PrimarySMTPAddress", "DisplayName", "FirstName", "Initials", "LastName" });

    Collection<PSObject> results = powerShell.Invoke();

    foreach (var result in results)
    {
        var propertyName = result.Properties.Where(w => w.Name == "DisplayName").Select(s => s.Value).FirstOrDefault();
        var propertyEmail = result.Properties.Where(w => w.Name == "PrimarySmtpAddress").Select(s => s.Value).FirstOrDefault();
        var FirstName = result.Properties.Where(w => w.Name == "FirstName").Select(s => s.Value).FirstOrDefault();
        var Initials = result.Properties.Where(w => w.Name == "Initials").Select(s => s.Value).FirstOrDefault();
        var LastName = result.Properties.Where(w => w.Name == "LastName").Select(s => s.Value).FirstOrDefault();

        EmailGroup emailGroup = new EmailGroup();
        emailGroup.Email = (string)propertyEmail;
        string fullName = string.Format("{0},{1},{2}", LastName, FirstName, Initials);

        emailGroup.FullName = fullName;
        emailGroup.GroupName = displayName;
        EmailGroups.Add(emailGroup);
    }


    if (powerShell.HadErrors)
    {
        var exception = powerShell.Streams.Error.ElementAt(0).Exception;
    }

    return EmailGroups;
}

Updated

I had to add a Get-contact ps call in order to get the Initials its very slow.

public List<EmailGroup> GetDistributionGroupMember(string distributionAddress, string displayName)
{
    ClearPowerShell();
    List<EmailGroup> EmailGroups = new List<EmailGroup>();
    Console.WriteLine(distributionAddress);
    powerShell.AddCommand("get-distributiongroupmember")
              .AddParameter("Identity", distributionAddress)
              .AddParameters(new Dictionary<string, object>()
              {
                    { "ResultSize", "Unlimited" }
              });



    Collection<PSObject> results = powerShell.Invoke();

    foreach (PSObject result in results)
    {
        ClearPowerShell();
        string propertyEmail = result.Properties.Where(w => w.Name == "PrimarySmtpAddress").Select(s => s.Value).FirstOrDefault().ToString();
        Console.WriteLine(propertyEmail);
        powerShell.AddCommand("Get-contact")
                  .AddParameter("Identity", propertyEmail)
                  .AddCommand("Select-Object")
                  .AddArgument(new string[] { "FirstName", "Initials", "LastName" });

        Collection<PSObject> contact = powerShell.Invoke();

        if (contact.Count != 0)
        {
            var FirstName = contact[0].Properties.Where(w => w.Name == "FirstName").Select(s => s.Value).FirstOrDefault();
            var Initials = contact[0].Properties["Initials"].Value;
            var LastName = contact[0].Properties.Where(w => w.Name == "LastName").Select(s => s.Value).FirstOrDefault();

            EmailGroup emailGroup = new EmailGroup();
            emailGroup.Email = propertyEmail;

            string fullName = string.Format("{0},{1},{2}", LastName, FirstName, Initials);

            emailGroup.FullName = fullName;
            emailGroup.GroupName = displayName;
            EmailGroups.Add(emailGroup);
        }
    }


    if (powerShell.HadErrors)
    {
        var exception = powerShell.Streams.Error.ElementAt(0).Exception;
    }

    return EmailGroups;
}

Solution

  • The issue seem to be a typo, you're projecting Initials with Select-Object yet are filtering for Initial on your Where. However, according to the docs, Get-DistributionGroupMember outputs a ReducedRecipient object, and said object doesn't seem to have such property.

    In this case, you could also benefit from Invoke<T>() as long as you reference include the assembly in your project, in which case you could do:

    Collection<ReducedRecipient> results;
    using (PowerShell powerShell = PowerShell.Create())
    {
        results = powerShell
            .AddCommand("Get-DistributionGroupMember")
            .AddParameters(new Dictionary<string, object>()
            {
                { "Identity", distributionAddress },
                { "ResultSize", "Unlimited" }
            })
            .Invoke<ReducedRecipient>();
    }
    
    foreach (ReducedRecipient result in results)
    {
        // do work
    }
    

    To help you find the assembly to add it to your project, if it's a Binary module, you can do:

    (Get-Command Get-DistributionGroupMember).Module.ImplementingAssembly.Location
    

    And, according to the .NET doc of ReducedRecipient, the assembly should be called: Microsoft.Exchange.Data.Directory.dll.

    If you can't include the assembly and have to stick to Collection<PSObject>, you should note that .Properties implements it's own indexer so this is perfectly valid:

    foreach (PSObject result in results)
    {
        object? displayName = result.Properties["DisplayName"].Value;
        // and so on
    }
    

    Regarding the new update, having to use Get-Contact and the code being too slow, you might be able to improve performance by letting PowerShell do all the work instead of having to do multiple PowerShell invocations calling Get-Contact for each group member.

    This is how it'd look:

    public EmailGroup[] GetDistributionGroupMember(
        string distributionAddress,
        string displayName)
    {
        ClearPowerShell();
        Console.WriteLine(distributionAddress);
    
        Collection<Hashtable> results = powerShell.AddScript(@"
            param($identity)
    
            $members = Get-DistributionGroupmember -Identity $identity -ResultSize Unlimited
            foreach ($member in $members) {
                if (-not $member.PrimarySmtpAddress) {
                    continue
                }
    
                $contact = Get-contact $member.PrimarySmtpAddress -ErrorAction SilentlyContinue
                if (-not $contact) {
                    continue
                }
    
                @{
                    PrimarySmtpAddress = $member.PrimarySmtpAddress
                    FirstName          = $contact.FirstName
                    Initials           = $contact.Initials
                    LastName           = $contact.LastName
                }
            }")
            .AddParameter("identity", distributionAddress)
            .Invoke<Hashtable>();
    
        List<EmailGroup> EmailGroups = new List<EmailGroup>(results.Count);
        foreach (Hashtable result in results)
        {
            EmailGroup emailGroup = new EmailGroup()
            {
                GroupName = displayName,
                Email = result["PrimarySmtpAddress"].ToString(),
                FullName = $"{result["LastName"]},{result["FirstName"]},{result["Initials"]}"
            };
    
            EmailGroups.Add(emailGroup);
        }
    
        return EmailGroups.ToArray();
    }
    

    Last consideration

    Assuming you are referencing the PowerShell SDK 7.0 or above in your project, you could change the foreach loop for a ForEach-Object -Parallel loop, allowing you to process the $members array in parallel, however by doing so, the complexity of the PowerShell script will increase, for example, you may need to handle throttling responses from the Exchange API.