diff --git a/Start-ExchangeRecipientAdminCenter.ps1 b/Start-ExchangeRecipientAdminCenter.ps1 index 289d387..3dca292 100644 --- a/Start-ExchangeRecipientAdminCenter.ps1 +++ b/Start-ExchangeRecipientAdminCenter.ps1 @@ -52,6 +52,7 @@ try { "$(Get-Date -Format s) Powershell webserver started." $WEBLOG = "$(Get-Date -Format s) Powershell webserver started.`n" while ($LISTENER.IsListening) { + try { # analyze incoming request $CONTEXT = $LISTENER.GetContext() $REQUEST = $CONTEXT.Request @@ -139,7 +140,182 @@ try { break } - "GET /distributiongroups" { + "GET /editremotemailbox" { + # View / edit a single Remote Mailbox's attributes + + $Table = @{} + if ($REQUEST.Url.Query) { + foreach ($Item in [URI]::UnescapeDataString(($REQUEST.Url.Query.Replace("?", ""))).Split("&")) { + $Table.Add($Item.Split("=")[0], $Item.Split("=")[1]) + } + } + + $Identity = $Table['id'] + $MailboxDisabled = $false + + # Apply a requested change, if any, before re-reading the mailbox + if ($Table.ContainsKey('action')) { + try { + switch ($Table['action']) { + "addalias" { + $NewAlias = "$($Table['alias_local'])@$($Table['alias_accepteddomain'])" + Set-RemoteMailbox -Identity $Identity -EmailAddresses @{Add = $NewAlias } + $HTML_RESULT = $HTML_SUCCESS.Replace("{result}", "Added alias $NewAlias") + break + } + "removealias" { + Set-RemoteMailbox -Identity $Identity -EmailAddresses @{Remove = $Table['alias'] } + $HTML_RESULT = $HTML_SUCCESS.Replace("{result}", "Removed alias $($Table['alias'])") + break + } + "togglehidden" { + $NewHidden = $Table['hidden'] -eq 'true' + Set-RemoteMailbox -Identity $Identity -HiddenFromAddressListsEnabled $NewHidden + $HTML_RESULT = $HTML_SUCCESS.Replace("{result}", "Set 'Hidden from address lists' to $NewHidden") + break + } + "setrra" { + Set-RemoteMailbox -Identity $Identity -RemoteRoutingAddress $Table['newrra'] + $HTML_RESULT = $HTML_SUCCESS.Replace("{result}", "Remote routing address updated to $($Table['newrra'])") + break + } + "disable" { + # Require the confirmation text to match the mailbox's actual + # PrimarySmtpAddress - checked server-side too, not just via the + # UI's type-to-confirm JS, since that's trivially bypassed. + $MailboxToDisable = Get-RemoteMailbox -Identity $Identity -ErrorAction SilentlyContinue + if (-not $MailboxToDisable) { + $HTML_RESULT = $HTML_WARN.Replace("{result}", "Remote mailbox '$($Identity)' not found") + } + elseif ($Table['confirmAddress'] -ne $MailboxToDisable.PrimarySmtpAddress) { + $HTML_RESULT = $HTML_WARN.Replace("{result}", "Confirmation text didn't match $($MailboxToDisable.PrimarySmtpAddress) - no changes were made") + } + else { + Disable-RemoteMailbox -Identity $Identity -Confirm:$false + $HTML_RESULT = $HTML_SUCCESS.Replace("{result}", "Remote Mailbox for $($MailboxToDisable.PrimarySmtpAddress) has been disabled") + $MailboxDisabled = $true + } + break + } + } + } + catch { + $HTML_RESULT = $HTML_WARN.Replace("{result}", $Error -join "
") + } + } + + # A disabled mailbox no longer exists to render an edit page for - + # send the user back to the Remote Mailboxes list instead. + if ($MailboxDisabled) { + $HTMLROWS_USERS = "" + foreach ($Item in (Get-User -Filter "RecipientType -eq 'User' -and RecipientTypeDetails -ne 'DisabledUser'" | Where { $_.UserPrincipalName })) { + $HTMLROWS_USERS += "`n" + } + + $HTMLROWS_AD = "" + foreach ($Item in (Get-AcceptedDomain)) { + if ($Item.Default) { + $HTMLROWS_AD += "`n" + } + else { + $HTMLROWS_AD += "`n" + } + } + + $HTMLROWS_RRA = "" + foreach ($Item in (Get-AcceptedDomain)) { + if ($Item.DomainName -like "*.mail.onmicrosoft.com") { + $HTMLROWS_RRA += "`n" + } + else { + $HTMLROWS_RRA += "`n" + } + } + + $HTMLROWS_MBX = "" + foreach ($Item in (Get-RemoteMailbox | Select DisplayName, PrimarySMTPAddress, RecipientTypeDetails, WhenChanged)) { + $HTMLROWS_MBX += " + + + $($Item.DisplayName) + $($Item.PrimarySMTPAddress) + $($Item.RecipientTypeDetails) + $($Item.WhenChanged) + "; + } + + $HTMLRESPONSE = (Get-Content -Path "$($BASEDIR)\remotemailboxes.html").Replace("", $HTMLROWS_MBX).Replace("", $HTMLROWS_AD).Replace("", $HTMLROWS_USERS).Replace("", $HTMLROWS_RRA).Replace("", $HTML_RESULT) + break + } + + # Look up the mailbox to display + $Mailbox = Get-RemoteMailbox -Identity $Identity -ErrorAction SilentlyContinue + + if (-not $Mailbox) { + $RESPONSE.StatusCode = 404 + $HTMLRESPONSE = "Remote mailbox '$($Identity)' not found" + break + } + + # Prepare accepted domain list, used for the "add alias" domain picker + $HTMLROWS_AD = "" + foreach ($Item in (Get-AcceptedDomain)) { + $HTMLROWS_AD += "`n" + } + + # Prepare proxy address (EmailAddresses) rows, each with a remove button + $HTMLROWS_PROXY = "" + foreach ($Addr in $Mailbox.EmailAddresses) { + $AddrString = $Addr.ToString() + $IsPrimary = $AddrString.StartsWith("SMTP:") + if ($IsPrimary) { + $Badge = "Primary" + $RemoveBtn = "" + } + else { + $Badge = "Alias" + $EncodedId = [URI]::EscapeDataString($Identity) + $EncodedAddr = [URI]::EscapeDataString($AddrString) + $RemoveBtn = "Remove" + } + $HTMLROWS_PROXY += " + + $($AddrString) + $($Badge) + $($RemoveBtn) + "; + } + + if ($Mailbox.HiddenFromAddressListsEnabled) { + $HiddenBadgeClass = "text-bg-warning" + $HiddenStatusText = "Hidden" + $HiddenToggleValue = "false" + $HiddenToggleLabel = "Unhide from GAL" + } + else { + $HiddenBadgeClass = "text-bg-success" + $HiddenStatusText = "Visible" + $HiddenToggleValue = "true" + $HiddenToggleLabel = "Hide from GAL" + } + + # Create response and replace template placeholders + $HTMLRESPONSE = Get-Content -Path "$($BASEDIR)\editremotemailbox.html" + $HTMLRESPONSE = $HTMLRESPONSE.Replace("{display_name}", $Mailbox.Name) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("{primarysmtpaddress}", $Mailbox.PrimarySmtpAddress) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("{remoteroutingaddress}", $Mailbox.RemoteRoutingAddress) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("{id}", [URI]::EscapeDataString($Identity)) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("{hidden_badge_class}", $HiddenBadgeClass) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("{hidden_status_text}", $HiddenStatusText) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("{hidden_toggle_value}", $HiddenToggleValue) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("{hidden_toggle_label}", $HiddenToggleLabel) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("", $HTMLROWS_PROXY) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("", $HTMLROWS_AD) + $HTMLRESPONSE = $HTMLRESPONSE.Replace("", $HTML_RESULT) + break + } + + "GET /distributiongroups" { # Distribution Groups Section # Prepare Distibution Group lists split into tabs @@ -334,6 +510,15 @@ try { "$(Get-Date -Format s) Stopping powershell webserver..." break; } + } + catch { + # A single request failing (e.g. client closed the connection before the + # response finished writing - "network name is no longer available") should + # not take down the whole webserver. Log it and keep listening. + "$(Get-Date -Format s) Request error: $($_.Exception.Message)" + $WEBLOG += "$(Get-Date -Format s) Request error: $($_.Exception.Message)`n" + try { $RESPONSE.Close() } catch { } + } } } finally { diff --git a/web/editremotemailbox.html b/web/editremotemailbox.html new file mode 100644 index 0000000..78aa838 --- /dev/null +++ b/web/editremotemailbox.html @@ -0,0 +1,261 @@ + + + + + + + + Exchange Recipient Admin Center - Remote Mailbox + + + + + + + + + + + +
+
+ + +
+
+

{display_name}

+ Back to Remote Mailboxes +
+ + + +
+
Properties
+
+
+
Name
+
{display_name}
+ +
Primary SMTP Address
+
{primarysmtpaddress}
+ +
Hidden from Address Lists
+
+ {hidden_status_text} +
+ + + + +
+
+
+
+
+ +
+
Remote Routing Address
+
+

The cloud mailbox this Remote Mailbox is + routed to (RemoteRoutingAddress).

+
+ + +
+ +
+
+ +
+
+
+
+ +
+
+ Email Addresses (Proxy Addresses) + +
+
+ + + + + + + + + + + +
AddressType
+
+
+ + + +
+
Danger Zone
+
+

Disabling this Remote Mailbox removes its Exchange attributes + from Active Directory. It will no longer sync as a Remote + Mailbox. The Exchange Online mailbox itself is not deleted by + this action.

+
+ + +
+ + +
+ +
+
+
+ +
+
+
+ + +