Forum Discussion

marksnyder2135's avatar
marksnyder2135
Copper Contributor
Jul 25, 2025

Get DGs for a specific person from EXO

Having trouble with my script working right. Tried several scenarios. Looking for a GUI version. Attached are the two PS1's. I can connect to EXO but the GUI script won't produce anything ot just locks up

 

Connect EXO:

Connect-ExchangeOnLine

This is straight forward and works but I cannot get it to work with the script below in an all in one script

-------------------

<# 
    Get-UserDGs-GUI.ps1
    Author: You + ChatGPT
    Purpose: GUI to retrieve a user's Distribution Groups (member/owner) from Exchange Online.
#>

#region Preconditions
if ($Host.Runspace.ApartmentState -ne 'STA') {
    Write-Warning "Re-running in STA mode..."
    Start-Process powershell.exe "-NoLogo -NoProfile -ExecutionPolicy Bypass -STA -File `"$PSCommandPath`"" -Verb RunAs
    exit
}

Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Drawing
[System.Windows.Forms.Application]::EnableVisualStyles()
#endregion

#region Helper Functions
function Ensure-ExchangeModule {
    if (-not (Get-Module ExchangeOnlineManagement -ListAvailable)) {
        Write-Host "Installing ExchangeOnlineManagement..."
        Install-Module ExchangeOnlineManagement -Scope CurrentUser -Force -ErrorAction Stop
    }
    Import-Module ExchangeOnlineManagement -ErrorAction Stop
}

function Test-EXOConnection {
    try {
        # Fast no-op cmdlet to see if session works; adjust if needed
        Get-OrganizationConfig -ErrorAction Stop | Out-Null
        return $true
    } catch { return $false }
}

function Connect-EXO {
    param(
        [string]$AdminUpn
    )
    if (Test-EXOConnection) { return $true }

    $connectParams = @{}
    if ($AdminUpn) { $connectParams.UserPrincipalName = $AdminUpn }
    try {
        Connect-ExchangeOnline @connectParams -ShowProgress $false -ErrorAction Stop | Out-Null
        return $true
    } catch {
        [System.Windows.Forms.MessageBox]::Show("Connection failed:`r`n$($_.Exception.Message)","Error",
            [System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null
        return $false
    }
}

function Disconnect-EXO-Safe {
    try { Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue | Out-Null } catch {}
}

function Get-UserDGData {
    param(
        [string]$TargetUpn,
        [bool]$IncludeOwner,
        [bool]$IncludeDynamic
    )

    $resultTable = New-Object System.Data.DataTable "Groups"
    "DisplayName","PrimarySmtpAddress","ManagedBy","RecipientTypeDetails","MembershipType","IsDynamic" |
        ForEach-Object { [void]$resultTable.Columns.Add($_) }

    try {
        # DistinguishedName for membership filter
        $dn = (Get-User $TargetUpn -ErrorAction Stop).DistinguishedName

        # Member DGs
        $memberDgs = Get-DistributionGroup -ResultSize Unlimited -Filter "Members -eq '$dn'" -ErrorAction SilentlyContinue
        foreach ($dg in $memberDgs) {
            $managedBy = ($dg.ManagedBy | ForEach-Object { $_.Name }) -join '; '
            $row = $resultTable.NewRow()
            $row.DisplayName          = $dg.DisplayName
            $row.PrimarySmtpAddress   = $dg.PrimarySmtpAddress
            $row.ManagedBy            = $managedBy
            $row.RecipientTypeDetails = $dg.RecipientTypeDetails
            $row.MembershipType       = "Member"
            $row.IsDynamic            = if ($dg.RecipientTypeDetails -match 'Dynamic') {'Yes'} else {'No'}
            $resultTable.Rows.Add($row)
        }

        # Owner DGs
        if ($IncludeOwner) {
            $ownerDgs = Get-DistributionGroup -ResultSize Unlimited |
                        Where-Object { ($_.ManagedBy -contains $dn) }
            foreach ($dg in $ownerDgs) {
                $managedBy = ($dg.ManagedBy | ForEach-Object { $_.Name }) -join '; '
                $row = $resultTable.NewRow()
                $row.DisplayName          = $dg.DisplayName
                $row.PrimarySmtpAddress   = $dg.PrimarySmtpAddress
                $row.ManagedBy            = $managedBy
                $row.RecipientTypeDetails = $dg.RecipientTypeDetails
                $row.MembershipType       = "Owner"
                $row.IsDynamic            = if ($dg.RecipientTypeDetails -match 'Dynamic') {'Yes'} else {'No'}
                $resultTable.Rows.Add($row)
            }
        }

        # Dynamic DG hit test (optional)
        if ($IncludeDynamic) {
            $dynamicHits = foreach ($ddg in Get-DynamicDistributionGroup -ResultSize Unlimited) {
                $filter = $ddg.RecipientFilter
                $ou     = $ddg.RecipientContainer
                $match  = Get-Recipient -ResultSize Unlimited -RecipientPreviewFilter $filter -OrganizationalUnit $ou |
                          Where-Object { $_.PrimarySmtpAddress -ieq $TargetUpn }
                if ($match) { $ddg }
            }
            foreach ($dg in $dynamicHits) {
                # Avoid duplicates if already in table
                if (-not $resultTable.Select("PrimarySmtpAddress = '$($dg.PrimarySmtpAddress)' AND MembershipType='Member'").Count) {
                    $row = $resultTable.NewRow()
                    $row.DisplayName          = $dg.DisplayName
                    $row.PrimarySmtpAddress   = $dg.PrimarySmtpAddress
                    $row.ManagedBy            = ($dg.ManagedBy | ForEach-Object { $_.Name }) -join '; '
                    $row.RecipientTypeDetails = $dg.RecipientTypeDetails
                    $row.MembershipType       = "Member (Dynamic Match)"
                    $row.IsDynamic            = "Yes"
                    $resultTable.Rows.Add($row)
                }
            }
        }

        return $resultTable
    }
    catch {
        throw $_
    }
}
#endregion

#region GUI Build
# Colors
$colorBg        = [System.Drawing.Color]::FromArgb(35,45,60)
$colorPanel     = [System.Drawing.Color]::FromArgb(50,60,80)
$colorAccent    = [System.Drawing.Color]::FromArgb(106,176,222)
$colorText      = [System.Drawing.Color]::White
$fontMain       = New-Object System.Drawing.Font("Segoe UI",10)
$fontSmall      = New-Object System.Drawing.Font("Segoe UI",7)

$form                       = New-Object System.Windows.Forms.Form
$form.Text                  = "Exchange Online - Distribution Groups Lookup"
$form.StartPosition         = "CenterScreen"
$form.Size                  = New-Object System.Drawing.Size(1000,650)
$form.BackColor             = $colorBg
$form.Font                  = $fontMain

# Top panel
$panelTop                   = New-Object System.Windows.Forms.Panel
$panelTop.Dock              = 'Top'
$panelTop.Height            = 120
$panelTop.BackColor         = $colorPanel
$form.Controls.Add($panelTop)

# Labels / Inputs
$lblAdminUpn = New-Object System.Windows.Forms.Label
$lblAdminUpn.Text = "Admin UPN (for Connect):"
$lblAdminUpn.ForeColor = $colorText
$lblAdminUpn.Location = "20,15"
$lblAdminUpn.AutoSize = $true
$panelTop.Controls.Add($lblAdminUpn)

$txtAdminUpn = New-Object System.Windows.Forms.TextBox
$txtAdminUpn.Location = "220,12"
$txtAdminUpn.Width = 250
$panelTop.Controls.Add($txtAdminUpn)

$btnConnect = New-Object System.Windows.Forms.Button
$btnConnect.Text = "Connect"
$btnConnect.Location = "490,10"
$btnConnect.Width = 100
$btnConnect.BackColor = $colorAccent
$btnConnect.FlatStyle = 'Flat'
$btnConnect.ForeColor = [System.Drawing.Color]::Black
$panelTop.Controls.Add($btnConnect)

$lblTargetUpn = New-Object System.Windows.Forms.Label
$lblTargetUpn.Text = "Target User UPN:"
$lblTargetUpn.ForeColor = $colorText
$lblTargetUpn.Location = "20,50"
$lblTargetUpn.AutoSize = $true
$panelTop.Controls.Add($lblTargetUpn)

$txtTargetUpn = New-Object System.Windows.Forms.TextBox
$txtTargetUpn.Location = "220,47"
$txtTargetUpn.Width = 250
$panelTop.Controls.Add($txtTargetUpn)

$chkOwner = New-Object System.Windows.Forms.CheckBox
$chkOwner.Text = "Include groups where user is OWNER"
$chkOwner.ForeColor = $colorText
$chkOwner.Location = "490,48"
$chkOwner.Width = 260
$panelTop.Controls.Add($chkOwner)

$chkDynamic = New-Object System.Windows.Forms.CheckBox
$chkDynamic.Text = "Check Dynamic DG membership (slow)"
$chkDynamic.ForeColor = $colorText
$chkDynamic.Location = "490,70"
$chkDynamic.Width = 260
$panelTop.Controls.Add($chkDynamic)

$btnGet = New-Object System.Windows.Forms.Button
$btnGet.Text = "Get Groups"
$btnGet.Location = "770,44"
$btnGet.Width = 160
$btnGet.Height = 40
$btnGet.BackColor = $colorAccent
$btnGet.FlatStyle = 'Flat'
$btnGet.ForeColor = [System.Drawing.Color]::Black
$panelTop.Controls.Add($btnGet)

# Grid
$grid = New-Object System.Windows.Forms.DataGridView
$grid.Dock = 'Fill'
$grid.ReadOnly = $true
$grid.AutoSizeColumnsMode = 'Fill'
$grid.BackgroundColor = $colorBg
$grid.ForeColor = [System.Drawing.Color]::Black
$grid.EnableHeadersVisualStyles = $false
$grid.ColumnHeadersDefaultCellStyle.BackColor = $colorAccent
$grid.ColumnHeadersDefaultCellStyle.ForeColor = [System.Drawing.Color]::Black
$grid.RowHeadersVisible = $false
$form.Controls.Add($grid)

# Bottom bar
$panelBottom = New-Object System.Windows.Forms.Panel
$panelBottom.Dock = 'Bottom'
$panelBottom.Height = 70
$panelBottom.BackColor = $colorPanel
$form.Controls.Add($panelBottom)

$btnExport = New-Object System.Windows.Forms.Button
$btnExport.Text = "Export CSV"
$btnExport.Location = "20,15"
$btnExport.Width = 110
$btnExport.BackColor = $colorAccent
$btnExport.FlatStyle = 'Flat'
$btnExport.ForeColor = [System.Drawing.Color]::Black
$panelBottom.Controls.Add($btnExport)

$btnCopy = New-Object System.Windows.Forms.Button
$btnCopy.Text = "Copy to Clipboard"
$btnCopy.Location = "140,15"
$btnCopy.Width = 140
$btnCopy.BackColor = $colorAccent
$btnCopy.FlatStyle = 'Flat'
$btnCopy.ForeColor = [System.Drawing.Color]::Black
$panelBottom.Controls.Add($btnCopy)

$btnClear = New-Object System.Windows.Forms.Button
$btnClear.Text = "Clear"
$btnClear.Location = "290,15"
$btnClear.Width = 90
$btnClear.BackColor = $colorAccent
$btnClear.FlatStyle = 'Flat'
$btnClear.ForeColor = [System.Drawing.Color]::Black
$panelBottom.Controls.Add($btnClear)

$btnDisconnect = New-Object System.Windows.Forms.Button
$btnDisconnect.Text = "Disconnect"
$btnDisconnect.Location = "390,15"
$btnDisconnect.Width = 110
$btnDisconnect.BackColor = $colorAccent
$btnDisconnect.FlatStyle = 'Flat'
$btnDisconnect.ForeColor = [System.Drawing.Color]::Black
$panelBottom.Controls.Add($btnDisconnect)

$chkAutoDisc = New-Object System.Windows.Forms.CheckBox
$chkAutoDisc.Text = "Auto-disconnect on close"
$chkAutoDisc.ForeColor = $colorText
$chkAutoDisc.Location = "520,20"
$chkAutoDisc.Width = 180
$panelBottom.Controls.Add($chkAutoDisc)

$statusLabel = New-Object System.Windows.Forms.Label
$statusLabel.Text = "Ready."
$statusLabel.ForeColor = $colorText
$statusLabel.AutoSize = $true
$statusLabel.Location = "720,22"
$panelBottom.Controls.Add($statusLabel)

# Footer
$lblFooter = New-Object System.Windows.Forms.Label
$lblFooter.Text = "Interactive Form Created By: Mark Snyder - All Rights Reserved!"
$lblFooter.ForeColor = $colorText
$lblFooter.Font = $fontSmall
$lblFooter.AutoSize = $true
$lblFooter.Location = New-Object System.Drawing.Point(20, $panelBottom.Top - 20)
$form.Controls.Add($lblFooter)
#endregion

#region UI Logic
$currentTable = $null

function Set-Status {
    param([string]$msg)
    $statusLabel.Text = $msg
    [System.Windows.Forms.Application]::DoEvents()
}

$btnConnect.Add_Click({
    Set-Status "Connecting..."
    Ensure-ExchangeModule
    if (Connect-EXO -AdminUpn $txtAdminUpn.Text) {
        Set-Status "Connected."
    } else {
        Set-Status "Not connected."
    }
})

$btnGet.Add_Click({
    if (-not $txtTargetUpn.Text.Trim()) {
        [System.Windows.Forms.MessageBox]::Show("Please enter the Target User UPN.","Missing Info",
            [System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Warning) | Out-Null
        return
    }

    Set-Status "Working..."
    $btnGet.Enabled = $false
    $btnGet.Text = "Working..."
    [System.Windows.Forms.Application]::DoEvents()

    Ensure-ExchangeModule
    if (-not (Test-EXOConnection)) {
        if (-not (Connect-EXO -AdminUpn $txtAdminUpn.Text)) {
            Set-Status "Connection failed."
            $btnGet.Enabled = $true
            $btnGet.Text = "Get Groups"
            return
        }
    }

    try {
        $table = Get-UserDGData -TargetUpn $txtTargetUpn.Text.Trim() -IncludeOwner $chkOwner.Checked -IncludeDynamic $chkDynamic.Checked
        $currentTable = $table
        $grid.DataSource = $currentTable
        Set-Status ("Retrieved {0} group(s)." -f $currentTable.Rows.Count)
    }
    catch {
        [System.Windows.Forms.MessageBox]::Show("Error retrieving data:`r`n$($_.Exception.Message)","Error",
            [System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null
        Set-Status "Error."
    }
    finally {
        $btnGet.Enabled = $true
        $btnGet.Text = "Get Groups"
    }
})

$btnExport.Add_Click({
    if (-not $currentTable -or $currentTable.Rows.Count -eq 0) {
        [System.Windows.Forms.MessageBox]::Show("Nothing to export.","Info",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information) | Out-Null
        return
    }
    $sfd = New-Object System.Windows.Forms.SaveFileDialog
    $sfd.Filter = "CSV (*.csv)|*.csv"
    $sfd.FileName = "UserDGs.csv"
    if ($sfd.ShowDialog() -eq [System.Windows.Forms.DialogResult]::OK) {
        try {
            $currentTable | Export-Csv -NoTypeInformation -Path $sfd.FileName -Encoding UTF8
            Set-Status "Saved to $($sfd.FileName)"
        } catch {
            [System.Windows.Forms.MessageBox]::Show("Export failed: $($_.Exception.Message)","Error",
                [System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Error) | Out-Null
        }
    }
})

$btnCopy.Add_Click({
    if (-not $currentTable -or $currentTable.Rows.Count -eq 0) {
        [System.Windows.Forms.MessageBox]::Show("Nothing to copy.","Info",[System.Windows.Forms.MessageBoxButtons]::OK,[System.Windows.Forms.MessageBoxIcon]::Information) | Out-Null
        return
    }
    $string = $currentTable | ConvertTo-Csv -NoTypeInformation | Out-String
    [System.Windows.Forms.Clipboard]::SetText($string)
    # Small toast-ish popup
    $popup = New-Object System.Windows.Forms.Form
    $popup.FormBorderStyle = 'None'
    $popup.StartPosition = 'Manual'
    $popup.BackColor = $colorAccent
    $popup.Size = New-Object System.Drawing.Size(200,60)
    $popup.TopMost = $true
    $popup.ShowInTaskbar = $false
    $popup.Location = New-Object System.Drawing.Point(($form.Location.X + $form.Width - 220), ($form.Location.Y + 40))

    $lbl = New-Object System.Windows.Forms.Label
    $lbl.Text = "Copied to clipboard!"
    $lbl.AutoSize = $false
    $lbl.TextAlign = 'MiddleCenter'
    $lbl.Dock = 'Fill'
    $lbl.Font = New-Object System.Drawing.Font("Segoe UI",10,[System.Drawing.FontStyle]::Bold)
    $popup.Controls.Add($lbl)

    $popup.Show()
    $timer = New-Object System.Windows.Forms.Timer
    $timer.Interval = 1200
    $timer.Add_Tick({ $timer.Stop(); $popup.Close(); $popup.Dispose() })
    $timer.Start()
})

$btnClear.Add_Click({
    $grid.DataSource = $null
    $currentTable = $null
    Set-Status "Cleared."
})

$btnDisconnect.Add_Click({
    Disconnect-EXO-Safe
    Set-Status "Disconnected."
})

$form.Add_FormClosing({
    if ($chkAutoDisc.Checked) {
        Disconnect-EXO-Safe
    } else {
        $res = [System.Windows.Forms.MessageBox]::Show("Disconnect from Exchange Online now?","Disconnect?",
            [System.Windows.Forms.MessageBoxButtons]::YesNoCancel,[System.Windows.Forms.MessageBoxIcon]::Question)
        if ($res -eq [System.Windows.Forms.DialogResult]::Cancel) {
            $_.Cancel = $true
        } elseif ($res -eq [System.Windows.Forms.DialogResult]::Yes) {
            Disconnect-EXO-Safe
        }
    }
})
#endregion

[void]$form.ShowDialog()

 

No RepliesBe the first to reply

Resources