Let PowerShell do the heavy lifting when creating new users in a hybrid environment
Since setting up our organisations Microsoft 365 tenant in a hybrid configuration creating new users has been an annoyingly complicated process. The reason for this is that a user needs to have a remote mailbox setting on the on-prem mail server otherwise it causes email delivery errors between cloud mailboxes and on-prem mailboxes. I ended up spending time creating new users in a multitude of different ways such as creating the user in Active Directory with an on-prem mailbox, syncing the user to 365 and then migrating their mailbox. The recommended way by a lot of people is to create the user in the on-prem Exchange Admin Centre as an ‘Office 365 mailbox’ with the appropriate AD organisational unit and then going into Active Directory to assign groups and finally sync to 365. This was a cumbersome method but what annoyed me most was having to use our dated Exchange 2016 Admin Centre to create users rather than either Active Directory or the 365 Admin Centre! I know you can also create users using Powershell so I decided to try making a script to tie all of this together neatly. It’s a work in progress but does most of what we need at the moment and has simplified the new user creation process down to a quick running of a script.
The first thing to add was a line connecting to the mail server so run the script in Exchange Management Shell. Just replace <SERVERNAME> with the name of your mail server:
$Session = New-PSSession -ConfigurationName Microsoft.Exchange -ConnectionUri http://<SERVERNAME>/PowerShell/ -Authentication Kerberos
Import-PSSession $Session
The next lines ask for a first name, last name, job title and a temporary password and stores each result in variables:
$firstName = Read-Host "Enter First Name"
$lastName = Read-Host "Enter Last Name"
$jobTitle = Read-Host "Enter Job Title"
$password = Read-Host "Enter Temprary Password" -AsSecureString
I noticed when testing that our IT engineer would often enter names and titles without capitalising the first letters so I added a few lines to transform the variables into TitleCase (capitalising the first letters):
$firstName = (Get-Culture).TextInfo.ToTitleCase($firstName)
$lastName = (Get-Culture).TextInfo.ToTitleCase($lastName)
$jobTitle = (Get-Culture).TextInfo.ToTitleCase($jobTitle)
I then added a line to create a variable for $fullName by combining the first and last name variables:
$fullName = $firstName + '.' + $lastName
Next I added options to select the department the new user would be working in and store this in a variable $answer:
Write-host "DEPARTMENT:"
Write-Host "1. Caterers"
Write-Host "2. Dean"
Write-Host "3. Directorate"
Write-Host "4. ESS"
Write-Host "5. Estates and Facilities"
Write-Host "6. Events"
Write-Host "7. Finance"
Write-Host "8. Hotel"
Write-Host "9. HR"
Write-Host "10. IT"
Write-Host "11. Marketing"
Write-Host "12. Philanthropy and Alumni"
Write-Host "13. Reception"
Write-Host "14. Registry"
Write-Host "15. The Burn"
$answer = Read-Host "Enter Number for department"
The following lines check the answer from above (using elseif statements on the number entered) and then assign appropriate values to the variables $ou (for the organisational unit and Active Directory), $sg (for the security group membership) and $dept (for the department name). The else statement at the end checks whether an incorrect number has been entered and if so aborts the script. I’m sure there is a better way to do this but this serverd my purposes and I haven’t had a chance to try anything different yet! The Active Directory organisational unit is stored in the $ou variable using it’s distinguished name which can be found by looking at the path in its properties in AD (for example “OU=Caterers,OU=Staff,OU=User Accounts,DC=DOMAIN,DC=co,DC=uk”):
if ($answer -eq '1') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Catering"
$dept = "Caterers"
} elseif ($answer -eq '2') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Dean"
$dept = "Team Dean"
} elseif ($answer -eq '3') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Directorate"
$dept = "Directorate"
} elseif ($answer -eq '4') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Subcontractors"
$dept = "ESS"
} elseif ($answer -eq '5') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Estates"
$dept = "Estates and Facilities"
} elseif ($answer -eq '6') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Events"
$dept = "Events"
} elseif ($answer -eq '7') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Finance"
$dept = "Finance"
} elseif ($answer -eq '8') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Hotel"
$dept = "Hotel"
} elseif ($answer -eq '9') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_HR"
$dept = "HR"
} elseif ($answer -eq '10') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_IT"
$dept = "IT"
} elseif ($answer -eq '11') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Marketing"
$dept = "Marketing"
} elseif ($answer -eq '12') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Philanthropy"
$dept = "Philanthropy and Alumni"
} elseif ($answer -eq '13') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Reception"
$dept = "Reception"
} elseif ($answer -eq '14') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_Registry"
$dept = "Registry"
} elseif ($answer -eq '15') {
$ou = "<OU_DISTINGUISHED_NAME>"
$sg = "DriveAccess_The-Burn"
$dept = "The Burn"
} else {
Write-host "Incorrect selection. You need to select a number from 1-15 above"
Remove-PSSession $Session
Read-Host -Prompt "Enter to Exit"
break
}
The next lines create a user in Active Directory with a remote mailbox and assign the name information, organisational unit, UPN and password based on the variables created above. It also sets the password to require a change on first logon:
New-RemoteMailbox -Name "$firstName $lastName" -FirstName $firstName -LastName $lastName -OnPremisesOrganizationalUnit $ou -UserPrincipalName "$firstName.$lastName@goodenough.ac.uk" -Password $password -ResetPasswordOnNextLogon:$true
In practice I found occasionally the script would fail after this point which I think was due to the length of time it might take the server to create the user or sync AD before moving on to adding groups. So I added a pause for 5 seconds before continuing just as a failsafe which seemed to fix the issue:
Start-Sleep -Seconds 5
Next I added a section asking for more details on the new user. For example whether they needed a 365 license, a Topdesk or Starrez account, whether they were a member of various distribution groups and whether they needed access to shared drives or VPN. The answers in the form of y/n and add to variables. The $driveaccess variable also dynamically adds in the department name for clarity using the $dept variable created earlier:
Write-host "ASSIGN GROUPS:"
$365 = Read-Host "Microsoft 365 License (y/n)"
$topdesk = Read-Host "Topdesk License (y/n)"
$starrez = Read-Host "StarRez License (y/n)"
$allstaff = Read-Host "Email Distribution List - ALLSTAFF (y/n)"
$goodenoughstaff = Read-Host "Email Distribution List - GOODENOUGH STAFF (y/n)"
$shareddriveaccess = Read-Host "Drive Access - AllStaff Shared (y/n)"
$driveaccess = Read-Host "Drive Access -"$dept" Drive (y/n)"
$vpn = Read-Host "VPN Access (y/n)"
The last section then adds the appropriate security groups and distribution memberships based on the answers above:
if ($allstaff -eq 'y') { Add-DistributionGroupMember -Identity "ALLSTAFF" -Member "$fullName" }
if ($goodenoughstaff -eq 'y') { Add-DistributionGroupMember -Identity "Goodenough Staff" -Member "$fullName" }
if ($365 -eq 'y') { Add-ADGroupMember -Identity "365_License_A3" -Members "$fullName" }
if ($topdesk -eq 'y') { Add-ADGroupMember -Identity "Topdesk_Operators" -Members "$fullName" }
if ($starrez -eq 'y') { Add-ADGroupMember -Identity "StarRez" -Members "$fullName" }
if ($shareddriveaccess -eq 'y') {Add-ADGroupMember -Identity "DriveAccess_AllStaff" -Members "$fullName"}
if ($driveaccess -eq 'y') {Add-ADGroupMember -Identity "$sg" -Members "$fullName"}
if ($vpn -eq 'y') {Add-ADGroupMember -Identity "VPN_Users" -Members "$fullName"}
The I added a line to edit the AD user to add in the job title and department as this cannot be added by the New-RemoteMailbox command at setup:
Set-ADUser "$fullName" -Department "$dept" -Title "$jobTitle"
Next is a line to close the remote session to the mail server:
Remove-PSSession $Session
And a line which invokes a command on our Azure Active Directory sync server (ours is called ADSYNC) to force a sync rather than having to wait for the scheduled sync:
Invoke-Command -ComputerName ADSYNC -ScriptBlock {Start-ADSyncSyncCycle -PolicyType Delta}
And finally a last command to quit the open Powershell/Terminal window:
Read-Host -Prompt "Press Enter to exit"
The reason for this final command is that if you right-click on the script and choose ‘Run with Powershell’ then it will run through and automatically quit when it finishes without giving you the chance to see the feedback from the final command. I like to be able to see the results of the sync command so I know it was successful and then am free to “Press Enter to exit” which closes the script window.

