Guides / windows-server
Three assumptions an Exchange script never states
A script that has run for years can depend on conditions nobody wrote down. You only find them by running it in an environment rebuilt from scratch.
Re-running your production scripts in a lab is the fastest way to discover what they took for granted. In production those conditions have been true for years, so nobody ever wrote them down: they are part of the environment, not part of the code. In an environment rebuilt from scratch they are not true, and each one announces itself with an error message that blames something else.
One: the session you are actually in
Enable-Mailbox was rejecting a parameter that command has always had.
A parameter cannot be found that matches parameter name 'PrimarySmtpAddress'
And Get-Mailbox, in the same console, was returning GUIDs instead of names.
Neither makes sense in an Exchange Management Shell. Both make perfect sense in a console where the ExchangeOnlineManagement module has been loaded, because it exports cmdlets with the same names as the local ones and shadows them. The commands were not failing: they were working beautifully, in the wrong place. They were going to the cloud.
The check that settles it in one second:
Get-PSSession | Format-Table Name, ComputerName, ConfigurationName, State -AutoSize
And here you understand a choice that looked merely pedantic in the production scripts: they import the local cmdlets with a prefix.
Import-PSSession $session -Prefix 2019
From then on the local command is Enable-2019Mailbox, which collides with nothing and cannot be shadowed. It looks like an affectation until you see why it is there, and it is the only defence that does not depend on whoever is running the script.
Two: the object’s name
The delegation script assigned FullAccess correctly, then stopped on Send As.
ufficio.acquisti wasn't found
The same identifier, in the same script, worked one line above and failed one line below. The reason is that the two commands do not resolve it the same way.
| Command | Resolves by |
|---|---|
Add-MailboxPermission | samAccountName |
Add-ADPermission | object name (CN) |
The object was called Ufficio Acquisti, with a space and capitals; the samAccountName was ufficio.acquisti. In production the two always match, because whoever creates the accounts follows a convention. That convention is written nowhere, verified by no script, and holds up half of every permission assignment.
The fix is to align the CN with the samAccountName. The interesting part is elsewhere: the failure is partial. Half the permissions get assigned, half do not, and a script that does not stop at the first error leaves an environment in an in-between state that looks fine.
Three: the prerequisites nobody checks
One script stopped with a message that helped nobody.
[ERROR] General error
Disconnect-ExchangeOnline : is not recognized
The ExchangeOnlineManagement module was not installed on the machine running it. The fix is obvious once you read the second line, but the first line is the one the user reads first, and it says nothing.
None of the scripts check their own prerequisites. A colleague running them from a new workstation would meet the same message, with no indication of what to do. Three lines at the top prevent the phone call:
if (-not (Get-Module -ListAvailable ExchangeOnlineManagement)) {
throw "ExchangeOnlineManagement is missing. Install it with: Install-Module ExchangeOnlineManagement"
}
The other assumptions, briefly
All found the same way, by walking into them:
Objects must already be synchronised to the cloud before delegation is assigned, because the script looks for them in Exchange Online too. Running it right after creation fails, and the failure has nothing to do with permissions.
Groups must be Universal Security groups, not distribution groups. The type check exists, but inside a function, not in the documentation.
A name prefix is structural. The script prepends a letter to the name read from the source file. Anyone preparing that file without knowing produces objects that will never be found.
One attribute is compared literally. The value of employeeType is compared against "Interno" with no normalisation: a different capital letter routes down the wrong branch, silently.
Windows PowerShell 5.1, not 7. The prefix and the remote cmdlets only behave reliably there.
The scripts do not create accounts. They assume the upstream step has already happened, and they do not say so.
The three lessons
A rebuilt environment is a test of your assumptions. It is not there to verify that the scripts work — you already know that. It is there to reveal how much of them does not live in the code.
A partial failure is worse than a total one. The script that assigns half the permissions and carries on leaves behind a state nobody will check, because nothing stopped.
The first line the user reads should be the useful one. General error followed by the real cause is the wrong order. People stop at the first line.
Lab built with the support of Ilie.