Skip to main content

· 2 min read
Hannes Palmquist

PSDev Module

Recently i published a new module PSDev with the purpose to group together a set of utility functions. Most of the functions are stand-alone and can be used independently of the module. The module is published to PSGallery for ease of access.

Install-Module PSDev -Scope CurrentUser

PSDev Module Docs

Function: Get-Office365IPURL

The first addition to the module is the Get-Office365IPURL and Test-Office365IPURL functions. These functions simplify the process of getting a list of firewall rules for a given set of services and troubleshoot communication issues.

Get-Office365IPURL is used to get a detailed list of the rules. The Office 365 website combines, protocol, port and type of openings. This function will expand these rules based on these attributes. See the example below.

Get-Office365IPURL -Services Exchange -Types IP4,URL | Where-Object {$_.Required -eq $true}

Group Service Type Protocol Port Endpoint Required
----- ------- ---- -------- ---- -------- --------
Exchange_TCP_25_IP Exchange IP4 TCP 25 True
Exchange_TCP_25_IP Exchange IP4 TCP 25 True
Exchange_TCP_25_IP Exchange IP4 TCP 25 True
Exchange_TCP_25_IP Exchange IP4 TCP 25 True
Exchange_TCP_443_IP Exchange IP4 TCP 443 True
Exchange_TCP_443_IP Exchange IP4 TCP 443 True
Exchange_TCP_25_URL Exchange URL TCP 25 * True

Get-Office365IPURL Docs

Function: Test-Office365IPURL

Test-Office365IPURL is used to test weather a IP address is included within the ipranges provided by Microsoft. This could be useful of the firewall logs shows that a connection to an IP adress is blocked and there is a need to verify if that IP belongs to the ranges provided by Microsoft. See the example below.

Test-Office365IPURL -IP | Where-Object {$_.ismember -eq $true} | Format-Table

RuleID ServiceArea TCPPort UDPPort Required Range Subject IsMember
------ ----------- ------- ------- -------- ----- ------- --------
46 Common 80,443 True True
64 Common 443 True True
65 Common 80,443 True True

Test-Office365IPURL Docs

· 2 min read
Hannes Palmquist

This script has been integrated in the module PSDev and is no longer provided as a stand-alone script.

At the time of writing this post there is no native automated way to cleanup/remove github artifacts produced within GitHub Action Workflows. If you for instance have an automated build process you will quite fast hit the free storage quota in GitHub and you'll have to start paying for additional storage. There are a few available options to manage this. You could manuallly remove each workflow run that could contain artifacts. This could be timeconsuming work. You could also configure the retension time of workflows in each repo. However both of these will remove the logs of each workflow run as well. A better way if you don't need the artifacts after the workflow run is to add a cleanup job to the workflow. I've been using geekyeggo/delete-artifact@v2. This action till remove the artifacts that i specify in the workflow file automatically within a cleanup job at the end of the workflow. This works great for future workflow runs once you configure the cleanup job. But what about if you have hit the storage quota and need to cleanup all existing artifacts? Then you would have to do it manually or call the GitHub Rest API and remove all artifacts. This is what the below script does, it will enumerate all artifacts for a specific repo or all repos for a user account and remove all artifacts.

Remove-GitHubArtifact -GitHubSecret "ABC" -GitHubOrg "user"

Repo Artifacts_Found Artifacts_Removed Artifacts_SizeMB
---- --------------- ----------------- ----------------
PSDaikin 5 5 43
PSDataSet 2 2 2
PSMaintenanceManager 2 2 21
PSPortainer 34 34 321
PSQueue 0 0 0
PSScriptInfo 0 0 0
PSSort 0 0 0

Or for a specific repo:

Remove-GitHubArtifact -GitHubSecret "ABC" -GitHubOrg "user" -Repo "PSMaintenanceManager"

Repo Artifacts_Found Artifacts_Removed Artifacts_SizeMB
---- --------------- ----------------- ----------------
PSMaintenanceManager 2 2 21

The script will remove all artifacts for the specified repo or all repos on the account. This is destructive and can not be reversed.

· One min read
Hannes Palmquist

Some time ago I wanted to automate a process where I needed check status of a few docker containers managed with Portainer and noticed that there was no powershell module available on the gallery for portainer. A couple of minutes later I had discovered the Portainer Rest API and though that it could be a fun project to provide powershell users with the ability to manage their Portainer and Docker instances with powershell.

So here it is, an early pre-release/work in progress powershell module for Portainer, PSPortainer

If you want to contribute to the project it is publically available on GitHub here

To get started, visit the docs for the module at or start exploring directly by installing the module from PSGallery

Install-Module PSPortainer -Scope CurrentUser

· 5 min read
Hannes Palmquist

Download CreateEXOUnattendedAzureApp.ps1

Microsoft has recently released (GA) version 3.0.0 of the ExchangeOnlineManagement powershell module. One of the new features is certificate based authentication which will allow unattended scripts to authenticate with Exchange Online without an interactive login prompt. Up until now you had to use basic authentication to connect to Exchange Online unattended.

Microsoft has also published documentation with all steps required to set this up. To simplify this process I wrote the script CreateEXOUnattendedAzureApp.ps1 which walks you through the process.


Connect to Exchange Online using a certificate

You can connect to Exchange Online with a certificate in three ways.

  • Passing a certificate object to Connect-ExchangeOnline
  • Providing the thumbprint of the certificate. In this case the certificate must be stored in Windows Certificate Store under CurrentUser\My
  • And last by providing the path to the pfx-file and the password

By Certificate object

$Cert = Get-Item cert:\CurrentUser\My\'<thumbprint>'
Connect-ExchangeOnline -Certificate $Cert -AppId '<appid>'

By Certificate thumbprint

Connect-ExchangeOnline -CertificateThumbprint '<thumbprint>' -AppId '<appid>'

By Certificate file

Connect-ExchangeOnline -CertificateFilePath '<path to file.pfx' -CertificatePassword (ConvertTo-SecureString -String '<password>' -AsPlainText -Force) -AppId '<appid>'

Considerations for unattended scripts

In the case where you run a script as a service account with Task Scheduler for instance I would recommend to use the thumbprint method and make sure the certificate is stored in the Windows Certificate Store (This script imports the certificate to the store automatically). In this case you would not need to care about handling passwords for the certificate pfx file. Access to the certificate is based on the fact that the process running the script is authenicated as the service account which is the only account that can access the certificates in the users CurrentUser\My store. Make sure that you import the certificate to the Certificate Store of the service account. This can be acheived by running mmc.exe "as a different user" and adding the certificate snapin.

In case the script runs on linux the thumbprint method is not an option, instead use the certificate by object or file methods.



The script is available on github where you are welcome to contribute or report issues.

Copyright © 2022 Hannes Palmquist

Permission is hereby granted, free of charge, to any person obtaining a copy of this
software and associated documentation files (the "Software"), to deal in the Software
without restriction, including without limitation the rights to use, copy, modify,
merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the following

The above copyright notice and this permission notice shall be included in all copies
or substantial portions of the Software.


"VERSION": "",
"GUID": "7fef8b41-e91d-4cb0-b656-1201c3820eb8",
"FILENAME": "CreateEXOUnattendedAzureApp.ps1",
"AUTHOR": "Hannes Palmquist",
"CREATEDDATE": "2022-10-04",
"COPYRIGHT": "© 2022, Hannes Palmquist, All Rights Reserved"

param (
[Parameter()][string]$AppName = 'ExchangeScriptAccess',

$ErrorActionPreference = 'Stop'

$SavedVerbosePreference = $VerbosePreference
$VerbosePreference = 'SilentlyContinue'
Import-Module Microsoft.Graph.Applications, Microsoft.Graph.Authentication, Microsoft.Graph.DeviceManagement.Enrolment -Verbose:$false
$VerbosePreference = $SavedVerbosePreference

function Get-RandomPassword
param (
[int]$length = 25
$Chars = [char[]]([char]33..[char]95) + ([char[]]([char]97..[char]126)) + 0..9
$Scramble = $Chars | Sort-Object { Get-Random }
return ($Scramble[0..$length] -join '')

$ResultObjectHash = [ordered]@{
Organization = $Organization
OutputDirectory = $OutputDirectory
AppName = $AppName
Certificate = $null
CertificatePassword = Get-RandomPassword
CertificatePFXPath = (Join-Path -Path $OutputDirectory -ChildPath "$Organization.pfx")
CertificateCERPath = (Join-Path -Path $OutputDirectory -ChildPath "$Organization.cer")

$null = Connect-Graph -Scopes 'Application.ReadWrite.All', 'RoleManagement.ReadWrite.Directory'

$ResultObjectHash.Certificate = New-SelfSignedCertificate -DnsName $ResultObjectHash.Organization -CertStoreLocation 'cert:\CurrentUser\My' -NotAfter (Get-Date).AddYears(1) -KeySpec KeyExchange
Write-Verbose "Created self-signed certificate with thumbprint: $($ResultObjectHash.Certificate.Thumbprint)"

$null = $ResultObjectHash.Certificate | Export-PfxCertificate -FilePath $ResultObjectHash.CertificatePFXPath -Password (ConvertTo-SecureString -String $ResultObjectHash.CertificatePassword -AsPlainText -Force)
Write-Verbose "Exported certificate with private key (pfx) to: $($ResultObjectHash.CertificatePFXPath)"

$null = $ResultObjectHash.Certificate | Export-Certificate -FilePath $ResultObjectHash.CertificateCERPath
Write-Verbose "Exported certificate with public key (cer) to: $($ResultObjectHash.CertificateCERPath)"

$Web = [Microsoft.Graph.PowerShell.Models.MicrosoftGraphWebApplication]::New()
$Web.RedirectUris = 'https://localhost'
Write-Verbose 'Initialized MicrosoftGraphWebApplication'

$ResourceAccess = [Microsoft.Graph.PowerShell.Models.MicrosoftGraphResourceAccess]::New()
$ResourceAccess.Id = 'dc50a0fb-09a3-484d-be87-e023b12c6440'
$ResourceAccess.Type = 'Role'
Write-Verbose 'Initialized MicrosoftGraphResourceAccess'

$RequiredResourceAccess = [Microsoft.Graph.PowerShell.Models.MicrosoftGraphRequiredResourceAccess]::New()
$RequiredResourceAccess.ResourceAccess = $ResourceAccess
$RequiredResourceAccess.ResourceAppId = '00000002-0000-0ff1-ce00-000000000000'
Write-Verbose 'Initialized MicrosoftGraphRequiredResourceAccess'

$KeyCred = [Microsoft.Graph.PowerShell.Models.MicrosoftGraphKeyCredential]::New()
$keycred.DisplayName = $ResultObjectHash.Organization
$KeyCred.Key = $ResultObjectHash.Certificate.RawData
$KeyCred.KeyId = $ResultObjectHash.Certificate.SerialNumber
$KeyCred.StartDateTime = $ResultObjectHash.Certificate.NotBefore
$KeyCred.EndDateTime = $ResultObjectHash.Certificate.NotAfter
$KeyCred.Usage = 'Verify'
$KeyCred.Type = 'AsymmetricX509Cert'
Write-Verbose 'Initialized MicrosoftGraphKeyCredential'

$ResultObjectHash.AzureAppRegistration = New-MgApplication `
-DisplayName $ResultObjectHash.AppName `
-Description 'Used by automations that need access to exchange online' `
-SignInAudience AzureADMyOrg `
-Web $Web `
-RequiredResourceAccess $RequiredResourceAccess `
-KeyCredentials $KeyCred
Write-Verbose "Created Azure App: $($ResultObjectHash.AppName)"

$ResultObjectHash.ServicePrincipal = New-MgServicePrincipal -AppId $ResultObjectHash.AzureAppRegistration.AppId

Write-Verbose 'Waiting 60 seconds for Azure app to be provisioned...'
Start-Sleep -Seconds 60
Start-Process "$($ResultObjectHash.Organization)/adminconsent?client_id=$($ResultObjectHash.AzureAppRegistration.Appid)"
$null = Read-Host ' Press enter once consent has been given'

$null = New-MgRoleManagementDirectoryRoleAssignment -PrincipalId $ -RoleDefinitionId '29232cdf-9323-42fd-ade2-1d097af3e4de' -DirectoryScopeId /
Write-Verbose 'Added role assignment'

if ($PassThru)
Write-Output ([pscustomobject]$ResultObjectHash)
Write-Host ''
Write-Host ' Use the following command to connect Exchange Online:'
Write-Host ''
Write-Host -ForegroundColor Cyan "Connect-ExchangeOnline -CertificateThumbprint `"$($ResultObjectHash.Certificate.Thumbprint)`" -AppId `"$($ResultObjectHash.AzureAppRegistration.AppId)`" -Organization `"$($ResultObjectHash.Organization)`""
Write-Host ''
Write-Host ' NOTE: Restart powershell before connecting to Exchange Online' -ForegroundColor Yellow
Write-Host ' NOTE: It could take some time before the added roles are effective. If you get an error regarding missing permissions, please wait a minute and try again.' -ForegroundColor Yellow
Write-Host ''

Remove-Variable ResultObjectHash -ErrorAction SilentlyContinue

· One min read
Hannes Palmquist

Recently I noticed a strange issue within Windows Terminal where I was unable to write the "$" sign using Ctrl+Alt+4. (Swedish keyboard layout has Ctrl+Alt+4 as the key combination to write a $ sign). However a workaround was to use Altgr+4 instead which worked fine. This issue was isolated to Windows Terminal as there were no issue writing $ in any other application, ie, notepad, vscode etc.

Luckely the issue for me was simple, with some update to Windows Terminal the development team hi-jacked the shortcut Ctrl+Alt+4 for changing to tab number 3. I removed the shortcut keybinding from the settings page and vola, Ctrl+Alt+4 started working as a $-dollar sign again.

Settings -> Actions -> Look for a shortcut for the key combination that is not working.

The same issue would arise for all keyboard layouts using Ctrl+Alt+X for a character.

In the case of Scandinavian keyboard layouts the following characters is probably affected.

  • Ctrl+Alt+2 -> @
  • Ctrl+Alt+3 -> £
  • Ctrl+Alt+4 -> $
  • Ctrl+Alt+7 -> {
  • Ctrl+Alt+8 -> [
  • Ctrl+Alt+9 -> ]

I though I'd share if anyone else stumbles upon this.

· One min read
Hannes Palmquist

Recently I developed a tool that utilizes the .NET class system.collections.queue. The function runs continously and produces workitems that should be processed in sequence and in the same order they were added. One simple way to acheive this is to use the collection queue. This class provides a simple way to add items to the queue and retreive the oldest item when needed.

For fun I wanted to create a wrapper for the queue class so that it can be used in a powershell style syntax. Additionally i missed functionality to see the rate and velocity that items were added and removed to the queue.

Queue objects created with this module contains metrics for count of added and removed items, rate of added and removed items and velocity.

PowerShell Gallery

· 2 min read
Hannes Palmquist

There are a lot of forum post of people having a hard time mocking some cmdlets, either it is remote cmdlets imported by a session that is not available during testing, cmdlets that are not present on the development machine or simply some built in cmdlets that are hard to mock like Import-PSSession, Invoke-Command etc.

Mocking a cmdlet that are unavailable on the development machine is quite straight forward by just declaring a dummy function before the mock.

function Get-Foo {
$Mailboxes = Get-Mailbox

Describe -Name "Foo" -Fixture {
BeforeAll {
function Get-Mailbox {}
Mock Get-Mailbox -MockWith {
# return fake mailbox object
It -Name "Bar" -Test {
{ Get-Foo } | should -not -throw

The same method can be applied when mocking a built in cmdlet that will fail parameter validation which happends even though the cmdlets is mocked

function Get-Foo {
$Session = New-PSSession
Import-PSSession -Session $Session

Describe -Name "Foo" -Fixture {
BeforeAll {
Mock New-PSSession -MockWith {}
Mock Import-PSSession -MockWith {}
It -Name "Bar" -Test {
{ Get-Foo } | should -not -throw
[-] Foo.Bar 51ms (49ms|1ms)
Expected no exception to be thrown, but an exception "Cannot validate argument on parameter 'Session'. The argument is null. Provide a valid value for the argument, and then try running the command again." was thrown from C:\Users\hanpalmq\OneDrive - Crayon Group\Desktop\temp.ps1:3 char:31
+ Import-PSSession -Session $Session
+ ~~~~~~~~.
at { Get-Foo } | should -not -throw, C:\Users\hanpalmq\OneDrive - Crayon Group\Desktop\temp.ps1:12
at <ScriptBlock>, C:\Users\hanpalmq\OneDrive - Crayon Group\Desktop\temp.ps1:12
Tests completed in 214ms
Tests Passed: 0, Failed: 1, Skipped: 0 NotRun: 0

The error states that Import-PSSession requires valid PSSession object to be passed to the session parameter. Again we need to define a dummy function that don't expects a PSSession object to be passed.

function Get-Foo {
$Session = New-PSSession
Import-PSSession -Session $Session

Describe -Name "Foo" -Fixture {
BeforeAll {
function Import-PSSession {}
Mock New-PSSession -MockWith {}
Mock Import-PSSession -MockWith {}
It -Name "Bar" -Test {
{ Get-Foo } | should -not -throw

· 2 min read
Hannes Palmquist

Eventually you will stumble upon badly formatted date/time strings that you need to parse. Instead of doing a lot of Split, Trim, Substring and what not you can leverage the DateTime class methods Parse/TryParse/ParseExact. To PS-ify the use of this method I wrote this powershell function that can take any date/time string and convert it to either a DateTime object or a new string format.

function Convert-DateStringToDateTimeObject {
Parses a datetimestring with a defined pattern to a datetime object
Defines the string to parse
Defines the pattern that datestring is formatted in
Optional. If this parameter is omitted a standard datetime object is
returned. It is however possible to define an output pattern where
the datetime object is converted back to a string but with the output
pattern instead.
Defines to culture to use for conversion. Default is console default ($PSCulture)
$InputString = '2018_06_11_11_05_03'
Convert-DateStringToDateTimeObject -DateString $InputString -PatternIn
'yyyy_MM_dd_HH_mm_ss' -PatternOut 'yyyy-MM-dd HH:mm:ss'
Convert the string date time representation '2018_06_11_11_05_03' to a
valid datetime object and formats that datetime object to a new string format.
AUTHOR Hannes Palmquist
COPYRIGHT © 2019, Hannes Palmquist, All Rights Reserved
[string]$PatternOut = '',
[string]$Culture = $PSCulture

$DateTimeFormat = [cultureinfo]::GetCultureInfo($Culture).DateTimeFormat

$DateTimeObject = [DateTime]::ParseExact($DateString, $PatternIn, $DateTimeFormat)

if ($PatternOut -eq '') {
Write-Output $DateTimeObject
} else {
Write-Output $DateTimeObject.ToString($PatternOut)

So if we have a date/time string like “2018_06_11_11_05_03” we can convert that date time to a date time object by writing:

Convert-DateTimeStringToDateTimeObject -InputString "2018_06_11_11_05_03" -PatternIn "yyyy_MM_dd_HH_mm_ss"
11 juni 2018 11:05:03

You can also use the parameter “PatternOut” to set a specific format to return the DateTime object as.

Note that “\” (backslash) and “:” (semi-colon) needs to be escaped.

· 3 min read
Hannes Palmquist

In some parts of the world it is more common to work with weeks as measurement of time. Unfortunately there are not easy accessible ways to work with weeks in powershell or .NET. There is some support of retrieving a week number with the culture datatype however if you have a week number and want to resolve dates relatated to that week number you have to resolve that manually. Here is one example of how to do it.

First we define a supporting function that simply retreives the week number for a given date from the Gregorian calendar.

function Get-CalendarWeek {
Gets the current week number based on a specific culture and it's week number descision rules.
Defines the date at which to return the week number for. Defaults to the current date.
.PARAMETER CultureInfo
Defines the culture that should be used to calculate the week number. Defaults to se-SV.
Get the week number for the current date.
Get-CalendarWeek -Date 2018-01-25 -CultureInfo en-US
Get the week number for the date 2018-01-25 according to the week number calculation rules of the en-US culture.
Author: Hannes Palmquist
COPYRIGHT: © 2019, Hannes Palmquist, All Rights Reserved
[datetime]$Date = (Get-Date),
[string]$CultureInfo = $PSCulture

# Get specific culture object
$Culture = [cultureinfo]::GetCultureInfo($CultureInfo)

# retrieve calendar week
write-output $Culture.Calendar.GetWeekOfYear($Date, $Culture.DateTimeFormat.CalendarWeekRule, $Culture.DateTimeFormat.FirstDayOfWeek)

When we have that function we can define the function that can resolve the week.

function Get-WeekInfo {
Gets info about a specific week
Defines the week number to query
Defines which year to query
Get-WeekInfo -Week 5 -Year 1988
Gets the first date of the fifth week of 1988
Author: Hannes Palmquist
Copyright: (c) 2019, Hannes Palmquist, All Rights Reserved

[CmdletBinding()] # Enabled advanced function support
[Parameter(Mandatory)][ValidateRange(1, 53)][int]$Week,
[Parameter(Mandatory)][ValidateRange(1600, 2100)][int]$Year

$WeekHash = [ordered]@{
Week = $Week
Year = $Year

$ReferenceDate = Get-Date -Year $Year -Month 02 -Date 05
$ReferenceWeek = Get-CalendarWeek -Date $ReferenceDate
$WeeksDiff = $Week - $ReferenceWeek
$DateInWeek = $ReferenceDate.AddDays($WeeksDiff * 7)
$WeekHash.FirstDateOfWeek = $DateInWeek.AddDays(1 - [int]$DateInWeek.DayOfWeek)
$WeekHash.LastDateOfWeek = $WeekHash.FirstDateOfWeek.AddDays(7).AddMilliseconds(-1)
$WeekHash.StartsInMonth = ([cultureinfo]::GetCultureInfo($PSCulture)).DateTimeFormat.MonthNames[($WeekHash.FirstDateOfWeek).Month-1]
$WeekHash.EndsInMonth = ([cultureinfo]::GetCultureInfo($PSCulture)).DateTimeFormat.MonthNames[($WeekHash.LastDateOfWeek).Month-1]

Write-Output ([pscustomobject]$WeekHash)

This will allow us to perform queries like:

Get-WeekInfo -Week 30 -Year 2018

Week : 30
Year : 2018
FirstDateOfWeek : 2018-07-23 00:00:00
LastDateOfWeek : 2018-07-29 23:59:59
StartsInMonth : July
EndsInMonth : July

· 4 min read
Hannes Palmquist

Below are all current recipient types. Please comment below if you miss an entry in any of the tables.


ACL able Mailbox UserACLableMailboxUser1073741824
Security Distribution GroupSecurityDistributionGroup1043741833
Equipment MailboxEquipmentMailbox8
Conference Room MailboxConferenceRoomMailbox7
Remote Mail UserRemoteMailUser6
Private Distribution ListPrivateDistributionList5
Dynamic Distribution GroupDynamicDistributionGroup3
Public FolderPublicFolder2
Distribution GroupDistrbutionGroup1
Mailbox UserMailboxUser0
Synced Universal Security Group as Universal Security GroupSyncedUSGasUSG-1073739511
ACL able Synced Universal Secuirty Group as ContactACLableSyncedUSGasContact-1073739514
ACL able Synced Remote Mail UserACLableSyncedRemoteMailUser-1073740282
ACL able Synced Mailbox UserACLableSyncedMailboxUser-1073741818
Synced Universal Security Group as ContactSyncedUSGasContact-2147481338
Synced Universal Security Group as Universal Distribution GroupSyncedUSGasUDG-2147481343
Synced Equipment MailboxSyncedEquipmentMailbox-2147481594
Synced Conference Room MailboxSyncedConferenceRoomMailbox-2147481850
Synced Remote Mail UserSyncedRemoteMailUser-2147482106
Synced Dynamic Distribution GroupSyncedDynamicDistributionGroup-2147482874
Synced Public FolderSyncedPublicFolder-2147483130
Synced Universal Distribution Group as ContactSyncedUDGasContact-2147483386
Synced Universal Distribution Group as Universal Distribution GroupSyncedUDGasUDG-2147483391
Synced Mailbox UserSyncedMailboxUser-2147483642


Team MailboxTeamMailbox137438953472
Remote Shared MailboxRemoteSharedMailbox34359738368
Remote Equipment MailboxRemoteEquipmentMailbox17179869184
Remote Equipment Mailbox (IncorrectValue)RemoteEquipmentMailbox17173869184
Remote Room MailboxRemoteRoomMailbox8589934592
Remote User Mailbox�����RemoteUserMailbox2147483648
Role GroupRoleGroup1073741824
Discovery MailboxDiscoveryMailbox536870912
Room ListRoomList268435456
Linked UserLinkedUser33554432
Mailbox PlanMailboxPlan16777216
Arbitration MailboxArbitrationMailbox8388608
Microsoft ExchangeMicrosoftExchange4194304
Disabled UserDisabledUser2097152
Non-Universal GroupNonUniversalGroup1048576
Universal Security GroupUniversalSecurityGroup524288
Universal Distribution GroupUniversalDistributionGroup262144
Cross-Forest Mail ContactMailForestContact32768
System MailboxSystemMailbox16384
System Attendant MailboxSystemAttendantMailbox8192
Public FolderPublic Folder4096
Dynamic Distribution GroupDynamicDistributionGroup2048
Mail-Enabled Universal Security GroupMailUniversalSecurityGroup1024
Mail-Enabled Non-Universal Distribution GroupMailNonUniversalGroup512
Mail-Enabled Universal Distribution GroupMailUniversalDistributionGroup256
Mail UserMailUser128
Mail ContactMailContact64
Equipment MailboxEquipmentMailbox32
Room MailboxRoomMailbox16
Legacy MailboxLegacyMailbox8
Shared MailboxSharedMailbox4
Linked MailboxLinkedMailbox2
User MailboxUserMailbox1


Migrated, SharedMailbox100
Migrated Equipment Mailbox68
Provisioned Equipment Mailbox65
Migrated Room Mailbox36
Provisioned Room Mailbox33
DeprovisionArchive, Migrated User Mailbox20
Migrated User Mailbox, ProvisionedArchive (Migrated MBX & Cloud Archive)6
Migrated User Mailbox4
Provisioned User Mailbox, Provisioned User Archive (Cloud MBX & Cloud Archive)3
ProvisionedArchive (Cloud Archive)2
Provisioned User Mailbox (Cloud MBX)1