Help with user creation script for M365

I work as a MSP and I have created a script to mainstream the creation of new user accounts in Microsoft 365 for our customers.

The customers fill in a web form to give us all the necessary information regarding username, group memberships, shared mailbox access and so forth.

The script is working great for everything except the group membership part.

The thing is that Microsoft doesn’t allow you to use a single command to add members to different kinds of groups. There are 4 different kinds of groups (Micorsoft 365 group, Distribution list, Security Group and Email Enabled Security Group) and each of them have its own command in Powershell to add members to.

In my script I list all groups in Microsoft 365 for a specific customer and the Administrator can copy/paste them to a parameter in the script:

Get-AzureADGroup | Select Displayname,Description | Sort-Object -Property DisplayName | Format-Table
$groups = read-host “Copy the groups to add the user to. Separate multiple groups with comma (,)”
$groupdata = $groups.split(“,”).trim(" ")

This gives me a list of all groups the script should add for the new user account.
I then get each line ine my $groupsdata parameter and use that to add membership

The problem is that I now need to differentiate the type of each group when I run the script to know what command to use for each row.
I have managed to do that with the following commands:

#Microsoft 365 group
$m365group = Get-AzureADGroup | Where-Object {($.DisplayName -EQ ‘$groupdata’) -and $.MailEnabled -eq $true -and ($_.SecurityEnabled -eq $false)}

#Email Enabled Security Group
$emailsecuritygroup = Get-AzureADGroup | Where-Object {($.DisplayName -EQ ‘$groupdata’) -and ($.MailEnabled -eq $true) -and ($_.SecurityEnabled -eq $true)}

#Regular Security Group
$securitygroup = Get-AzureADGroup | Where-Object {($.DisplayName -EQ ‘$groupdata’) -and $.MailEnabled -eq $false}

And now to the problem.
I don’t know how to separate each line in my $groupdata parameter the the commands above. If I only have 1 group in my $groupdata it works fine, but I have multiple groups I get nothing back.
I then need to use those values which turn up true to a “Add Membership” command depending on which type of group it is.

Here is the full script that has anything to do with groups as it looks right now:

Get-AzureADGroup | Select Displayname,Description | Sort-Object -Property DisplayName | Format-Table
$groups = read-host “Copy the groups to add the user to. Separate multiple groups with comma (,)”
$groupdata = $groups.split(“,”).trim(" ")

$groupuser = Get-MsolUser -TenantId $cid -UserPrincipalName $UserPrincipalName
$m365group = Get-AzureADGroup | Where-Object {($.DisplayName -EQ $groupdata) -and $.MailEnabled -eq $true -and ($_.SecurityEnabled -eq $false)}
ForEach ($line in $m365group) {
$groupdataobject = Get-AzureADGroup -Filter “Displayname eq ‘$line’”
Add-AzureADGroupMember -ObjectID $groupdataobject.ObjectID -RefObjectId $groupuser.ObjectID

$groupuser = Get-MsolUser -TenantId $cid -UserPrincipalName $UserPrincipalName
$emailsecuritygroup = Get-AzureADGroup | Where-Object {($.DisplayName -EQ ‘$line’) -and ($.MailEnabled -eq $true) -and ($_.SecurityEnabled -eq $true)}
ForEach ($line in $emailsecuritygroup) {
$groupdataobject = Get-AzureADGroup -Filter “Displayname eq ‘$line’”
Add-DistributionGroupMember -Identity $groupdataobject.DisplayName -Member $groupuser.UserPrincipalName -BypassSecurityGroupManagerCheck

$groupuser = Get-MsolUser -TenantId $cid -UserPrincipalName $UserPrincipalName
$securitygroup = Get-AzureADGroup | Where-Object {($.DisplayName -EQ $groupdata) -and $.MailEnabled -eq $false}
ForEach ($line in $securitygroup) {
$groupdataobject = Get-MsolGroup -TenantId $cid | Where {$_.Displayname -eq $line}
Add-MsolGroupMember -TenantId $cid -GroupObjectId $groupdataobject.ObjectID -GroupMemberObjectId $groupuser.ObjectID

Welcome to the forum. :wave:t4:

I’m not completely sure if I got everything from your issue but I think copy and paste is - even for an experienced admin - not the best way to provide a list of groups. It’s inconvenient and error prone.

How about using …

… with the parameter -OutputMode set to “Multiple”? This way you get a proper array for further use.

Thank you.
I agree it might not be the ideal output.

So, the real scenario is that we get a ticket created from our customer regarding a new user account.
It might look something like this.
Full name: Anna Scott
Licenses: Office 365 Business Premium
Groups: Everyone, Marketing and Sales
Shared mailboxes: Info and order mailboxes

In my script I have then created Write-Host asking the administrator to fill in all this information above.
When it comes to mailboxes and groups it’s important that it’s spelled exactly like it is in Office 365, otherwise the script will fail on adding the user to that group. That’s why I list all available groups to give the admin a possibility to copy paste the names.

With this GridVIew I don’t see an easy way to get the specific groups I want in this perticular scenario into the script.

How do you collect all available groups to be able to list them? When you pipe these groups to a GridView and let the admin choose you’re done.

You may try it to see what I mean … for example

$GroupList = @'
"Sales","Sales departement"
"HR","Human ReSources"
"IT","Information Technologies"
"BO","Back Office"
'@ |

$Choice = $GroupList | Out-GridView -OutputMode Multiple

In the GridView you hold your key down and mark 2 of the groups. Then you click on the button.

Now you have the chosen groups in the variable


… what looks like this:

GroupName Description
--------- -----------
BO        Back Office
HR        Human ReSources

… when you output it to the console.

Thank you for this feedback!
This solved my problem.

I didn’t see the “OK” button at first in Out-GridView.

This has really improved the overall feel of my script.

Do you know if there’s a nice way to replace my “Read-Host” prompts I use to fill all other information?
With the look of Gridview I’d like to find something similar but where you can fill your own information.
More like a web form or table to fill.

I assume you’re talking about a graphical interface when you say “nice”, right? :wink: I’m not a fan of graphical interfaces for PowerShell scripts as they usually do not ad any benefit except of increasing the acceptance level of the user. If I don’t have to think about something like this because the user - in your case the admin - has no another choice I wouldn’t even think about it. But this is my opinion and everyone is allowed to have his or her own opinion.

I would rather maybe think about providing a web interface the “end users” use to input all necessary information. This web interface could create a kind of config file they can send by mail. This could be read automatically by your script. This way you would eliminate the boring and error prone task of manually transfering the information from a mail body to your script.

If this web interface allready handles the input validation and plausibilty check you could use the created config file in an automated process leter on. :wink:

Regardless of all that I think there’s nothing wrong with Read-Host. The actual problem is the input validation. If you want to add advanced input validation you may use functions and use their “built in” input validation for your purpose.

Here you can read more


We already have the users fill in the information in a web form, but you can’t trust the users to write every exactly correct. There will always be spelling mistakes and other nonsense that will break the config file.
I’m sure you can make that work on a big firm where this form is used very often, but as a MSP with 20+ small customers we’d never get our money back on the time to create it.

I don’t really have anything against Read-Host itself, but I find myself doing small mistakes when filling the information in Read-Host and have to redo it all again.

How I do it is like this, and I’m sure there’s a 100 better ways to solve this. I’m really just a powershell noob.

Write-Host -BackgroundColor Black -ForegroundColor Green "Please fill in all user parameters"
$FirstName = Read-Host -Prompt 'Fill in the users first name'
$LastName = Read-Host -Prompt 'Fill in the users last name'
$DisplayName = "$Firstname $Lastname"
$UserPrincipalName = Read-Host -Prompt 'Fill in the users email'
$VaxelNr = Read-Host -prompt 'Fill in the Office telephone number'
$FastNr = Read-Host -Prompt 'Fill in the users phone number'
$MobilNr = Read-Host -Prompt 'Fill in the users mobile number'
$Adress = Read-Host -Prompt 'Fill in the street adress'
$PostNr = Read-Host -Prompt 'Fill in the postal code'
$PostOrt = Read-Host -Prompt 'Fill in the City'
$Title = Read-Host -Prompt 'Fill in the users title'
$Password = Read-Host -Prompt 'Password'

All that information above is information I can’t really know beforehand. I can’t have that in the script becuase it’s always more or less unique.

After that I also have some questions where I now use your Out-Gridview suggestion to get information about mailboxes, groups etc. Information that’s already there and easy to reuse.

When all information is filled I prompt the script with that information and ask the admin to verify everything before it continues to do its magic. And if you find any error in the information you have to start the script all over again which is annoying.

Function Show-Input {
Write-Host "Name:                     $DisplayName"
Write-Host "First Name:             $FirstName"
Write-Host "Last Name:              $LastName"
Write-Host "Email:                      $UserPrincipalName"
Write-Host "Office Phone:          $VaxelNr"
Write-Host "User Phone:            $FastNr"
Write-Host "Mobile Phone:         $MobilNr"
Write-Host "Address:                  $Adress"
Write-Host "Postal number:        $PostNr"
Write-Host "City:                         $PostOrt"
Write-Host "Country:                   $Land"
Write-Host "Title:                         $Title"
Write-Host "License:                   $licenser"
Write-Host "Groups:                    $groups"
Write-Host "Shared mailboxes:   $sharedmailboxes"
Write-Host "Password:                $Password"
Write-Host -BackgroundColor Black -ForegroundColor Green "Verifiy the settings"

  switch (read-host "Continue? Yes/No")
   'y'     {Write-Host 'Creating User..'}
   'yes'   {Write-Host 'Creating User..'}
   'n'     {exit}
   'no'    {exit}
   default {
            Write-Host "Invalid entry. Enter only 'Y' or 'N'" -ForegroundColor Yellow

My thought was that if I have more like a form to fill in a table, you can review all your fields and just change your errors before continuing.

OK, but when an email from a user is the only source of information you have where do you know something is spelled wrong? I’d expect you would need to trust the information you get. And for settings derived from other settings you can use algorithms to fill these in.

For example:

$UserPrincipalName = Read-Host -Prompt 'Fill in the users email'

… do you really have to type in this info manually? Shouldn’t be this set automatically from the name and the company or organization the user belongs to? … similar to the $DisplayName:wink:

But you do not need a graphical interface for that. You can output the information to the console and ask for confirmation. If the response is “No” you can show the input prompts again. You just have to wrap it in a loop. :wink: