Forum Discussion
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()