If you manage Microsoft 365, you have probably written the same PowerShell commands dozens of times. Connect to Exchange Online. Run Get-Mailbox. Adjust a setting. Disconnect. Next week, same thing — you open your notes, find the script, copy it into a window, and run it again.
There is a better way. It is called a PowerShell module, and once you build one, your commands are available instantly in any PowerShell window — no searching for old scripts, no copy-pasting, no version confusion. You type the name of your function and it works.
This article shows you exactly how to build one from scratch, step by step. By the end, you will have a working module with a real function that sets mailbox quotas in Exchange Online — and you will understand why every IT admin should build one.
What You Need Before Starting
PowerShell 5.1 — already installed on every modern Windows machine. Open the Start menu and search "Windows PowerShell".
ExchangeOnlineManagement module — install it once by running: Install-Module ExchangeOnlineManagement -Scope CurrentUser
M365 admin credentials — you will need an account with Exchange admin rights to test the mailbox quota function.
The Problem With Raw Scripts
Before building something better, it helps to understand exactly what goes wrong with the way most admins work today. Here is a typical Monday morning scenario.
You need to update the mailbox quota for a user. You search your desktop for the script you wrote six months ago. You find three files named mailbox-quota.ps1, mailbox-quota-v2.ps1, and mailbox-quota-FINAL.ps1. You open the newest one, but it references a variable that no longer exists. You fix it, run it, it works. A colleague asks you to share it. You email them the file. They modify their copy. Now there are two different versions doing slightly different things — and neither of you knows which is correct.
Get-Command -Module MyModule lists everythingWhat Is a PowerShell Module?
A PowerShell module is a folder with your functions inside it, placed in a location that PowerShell knows to look. That is genuinely all it is at its core.
When PowerShell starts, it scans a set of known folder paths. If it finds a module folder there, it makes all the functions inside that module available automatically — no dot-sourcing, no copying scripts, no file paths. You just type the function name.
Think of it like installing an app on your phone. Once it is installed, you tap the icon and it works — you do not think about where the app files are stored. A module works the same way. Install it once, use it forever.
Where Does a Module Live?
PowerShell looks for modules in a specific list of folders. You can see this list at any time by running:
$env:PSModulePath -split ';'
That command shows every folder PowerShell checks. There are three locations you need to know about:
| Location | Path | Who Can Use It |
|---|---|---|
|
Current User
Recommended
|
C:\Users\YourName\Documents\WindowsPowerShell\Modules\ | Only you, on this machine. No admin rights needed to install here. |
|
All Users
System-wide
|
C:\Program Files\WindowsPowerShell\Modules\ | Everyone on this machine. Requires admin rights to install here. |
|
Built-in
Read Only
|
C:\Windows\System32\WindowsPowerShell\v1.0\Modules\ | Windows built-in modules. Never put your module here. |
Where to Put Your Module
Use the Current User path for personal use — no admin rights required and it will not affect other users on the machine. Use the All Users path when you want to share the module with all admins on a shared jump server.
Why Build a Module? The Advantages
Get-Command -Module MyModule to see every function in your module. Tab completion works too.-WhatIf preview mode so you see what will change before it happens.Get-Help Set-MyMailboxQuota works just like native PowerShell help.Building Your First Module — Step by Step
You are going to build a real module called MyModule with one practical function: Set-MyMailboxQuota. It connects to Exchange Online, finds the mailbox you specify, shows you a preview of the changes, and only applies them when you confirm.
Follow each step in order. This takes about 10 minutes.
# Create the module folder in the current user module path $modulePath = "$HOME\Documents\WindowsPowerShell\Modules\MyModule" New-Item -Path $modulePath -ItemType Directory -Force # Confirm it was created Write-Host "Module folder created at: $modulePath" -ForegroundColor Green
.psm1) is where your functions live. The filename must match the folder name. Run this to create it:$moduleFile = "$HOME\Documents\WindowsPowerShell\Modules\MyModule\MyModule.psm1" New-Item -Path $moduleFile -ItemType File -Force Write-Host "Module file created: $moduleFile" -ForegroundColor Green
Your module folder now looks like this:
MyModule/ └── MyModule.psm1 # Your functions go in here
MyModule.psm1 in Notepad or VS Code and paste the complete function below. Read through the comments — every section is explained.function Set-MyMailboxQuota { <# .SYNOPSIS Sets the mailbox storage quota for an Exchange Online mailbox. .DESCRIPTION Connects to Exchange Online if not already connected, then sets the Warning, ProhibitSend, and ProhibitSendReceive quotas based on the GB value you provide. Always shows a preview before applying. .PARAMETER Identity The mailbox to update. Accepts email address or display name. .PARAMETER QuotaGB The ProhibitSend quota in GB. Warning is set 1 GB below this, and hard limit (ProhibitSendReceive) 1 GB above. .EXAMPLE Set-MyMailboxQuota -Identity "john.doe@company.com" -QuotaGB 50 .EXAMPLE Set-MyMailboxQuota -Identity "john.doe@company.com" -QuotaGB 50 -WhatIf #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'Medium')] param( [Parameter(Mandatory = $true)] [string]$Identity, [Parameter(Mandatory = $true)] [int]$QuotaGB ) process { # ── Step 1: Connect to Exchange Online if not already connected ──────── $activeSession = Get-ConnectionInformation -ErrorAction SilentlyContinue | Where-Object { $_.State -eq 'Connected' } if (-not $activeSession) { Write-Host "Connecting to Exchange Online..." -ForegroundColor Cyan Connect-ExchangeOnline -ShowBanner:$false -ErrorAction Stop } # ── Step 2: Confirm the mailbox exists before doing anything ────────── $mailbox = Get-Mailbox -Identity $Identity -ErrorAction Stop # ── Step 3: Calculate all three quota thresholds from the one value ─── $warningGB = $QuotaGB - 1 # Warn the user 1 GB before the limit $prohibitSendGB = $QuotaGB # Stop sending at the limit $prohibitReceiveGB = $QuotaGB + 1 # Hard stop 1 GB above # ── Step 4: Always show a clear preview before making any changes ───── Write-Host "" Write-Host " Mailbox : $($mailbox.PrimarySmtpAddress)" -ForegroundColor Cyan Write-Host " Warning Quota : $warningGB GB" -ForegroundColor Yellow Write-Host " Prohibit Send : $prohibitSendGB GB" -ForegroundColor Yellow Write-Host " Hard Stop : $prohibitReceiveGB GB" -ForegroundColor Yellow Write-Host "" # ── Step 5: Apply changes — only if -WhatIf was NOT specified ───────── if ($PSCmdlet.ShouldProcess($mailbox.PrimarySmtpAddress, "Set mailbox quota to $QuotaGB GB")) { Set-Mailbox -Identity $Identity ` -IssueWarningQuota "$warningGB GB" ` -ProhibitSendQuota "$prohibitSendGB GB" ` -ProhibitSendReceiveQuota "$prohibitReceiveGB GB" ` -UseDatabaseQuotaDefaults $false ` -ErrorAction Stop Write-Host " Done. Quota updated successfully." -ForegroundColor Green } } }
# Load the module Import-Module MyModule # Confirm your function is available Get-Command -Module MyModule
You should see your function listed:
CommandType Name Version Source ----------- ---- ------- ------ Function Set-MyMailboxQuota 0.0 MyModule
-WhatIf to see exactly what will change before applying anything. This is the safest way to run any function for the first time:# Preview only — nothing is changed Set-MyMailboxQuota -Identity "john.doe@company.com" -QuotaGB 50 -WhatIf
Mailbox : john.doe@company.com Warning Quota : 49 GB Prohibit Send : 50 GB Hard Stop : 51 GB What if: Performing the operation "Set mailbox quota to 50 GB" on target "john.doe@company.com".
-WhatIf:# Apply the change for real Set-MyMailboxQuota -Identity "john.doe@company.com" -QuotaGB 50
Mailbox : john.doe@company.com Warning Quota : 49 GB Prohibit Send : 50 GB Hard Stop : 51 GB Done. Quota updated successfully.
Get Help on Any Function
Because the function has a comment-based help block, PowerShell's built-in help system works automatically. Run Get-Help Set-MyMailboxQuota to see the description, parameters, and examples — the same way you would look up any native PowerShell cmdlet.
What You Just Built
Take a moment to appreciate what this module gives you that a raw script does not. Your function checks for an existing connection before connecting — so running it twice does not open two EXO sessions. It validates the mailbox exists before touching anything. It calculates all three quota thresholds from one input value so you cannot accidentally set them in the wrong order. It shows you a preview before making any change. And it is available in any PowerShell window by name, right now, without finding a file.
That is the difference between a script and a module function. The function handles the details so you do not have to think about them every time.
What to Build Next
Your module has one function today. Next time you write a PowerShell command you will use again, add it as a function in MyModule.psm1. Over time your module becomes a personal toolkit — everything you need, available instantly.
Some ideas for your next functions:
- Get-MyMailboxSize — report the current size and quota of any mailbox
- Set-MyAutoReply — configure out-of-office replies with HTML body support
- Get-MyLicenseStatus — check which M365 licences a user has assigned
- Disable-MyAccount — offboarding function that disables the AD account, revokes sessions, and sets an auto-reply in one call
Taking It Further
Once you are comfortable with the basics, the natural next step is structuring your module for a hybrid Exchange environment — where you need to manage both on-premises Exchange and Exchange Online from the same session. That introduces a dual session guard pattern, forest-wide AD identity resolution, and a folder structure that scales across dozens of functions. Those patterns are covered in the advanced follow-up to this article.
Key Takeaways
- A PowerShell module is a folder with your functions inside it, placed where PowerShell knows to look. Nothing more complicated than that.
- The Current User module path —
~\Documents\WindowsPowerShell\Modules\— requires no admin rights and works immediately. - Once installed, your functions are available in any PowerShell window by name. No file paths, no dot-sourcing, no searching.
- Always include
SupportsShouldProcesson any function that changes something, so-WhatIfworks and admins can preview before applying. - A module grows with you. Start with one function. Add another when you need it. Your toolkit builds itself over time.
Get-Command -Module MyModulelists everything in your module.Get-Help FunctionNameshows the documentation. These work automatically with no extra setup.