All scripts
Automation 555
Automate New Employee Setup With Microsoft Graph
Creates a new Entra ID user for each row in a CSV, assigns a license, sets their manager, adds them to the right groups, and logs a temporary password for each new hire. Built to run once per hiring batch instead of clicking through the admin center one field at a time.
New-M365UserOnboarding.ps1
<#
.SYNOPSIS
Creates and provisions a new Microsoft 365 user from a CSV file using Microsoft Graph.
.DESCRIPTION
Reads a CSV of new hires, creates each user in Entra ID, assigns a license,
sets their manager, adds them to the groups listed in the CSV, and writes a
summary log with each temporary password. Meant to run once per hiring batch,
not per user.
.AUTHOR
Shehryar Hassan
.EXAMPLE
.\New-M365UserOnboarding.ps1 -CsvPath ".\new-hires.csv" -UsageLocation "US"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[string]$CsvPath,
[Parameter(Mandatory = $true)]
[string]$UsageLocation,
[Parameter(Mandatory = $false)]
[string]$LogPath = ".\onboarding-log.csv"
)
if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Users)) {
Write-Error "Microsoft.Graph.Users module is not installed. Run: Install-Module Microsoft.Graph -Scope CurrentUser"
return
}
Connect-MgGraph -Scopes "User.ReadWrite.All", "Group.ReadWrite.All", "Directory.ReadWrite.All"
if (-not (Test-Path $CsvPath)) {
Write-Error "CSV file not found at $CsvPath"
return
}
$newHires = Import-Csv -Path $CsvPath
$results = @()
foreach ($hire in $newHires) {
Write-Host "Provisioning $($hire.DisplayName) ($($hire.UserPrincipalName))..." -ForegroundColor Cyan
$tempPassword = -join ((65..90) + (97..122) + (48..57) | Get-Random -Count 14 | ForEach-Object { [char]$_ })
$passwordProfile = @{
Password = $tempPassword
ForceChangePasswordNextSignIn = $true
}
$userParams = @{
AccountEnabled = $true
DisplayName = $hire.DisplayName
UserPrincipalName = $hire.UserPrincipalName
MailNickname = $hire.UserPrincipalName.Split("@")[0]
JobTitle = $hire.JobTitle
Department = $hire.Department
UsageLocation = $UsageLocation
PasswordProfile = $passwordProfile
}
$status = "Success"
$errorMessage = ""
try {
$newUser = New-MgUser @userParams
if ($hire.LicenseSkuId) {
$licenseParams = @{
AddLicenses = @(@{ SkuId = $hire.LicenseSkuId })
RemoveLicenses = @()
}
Set-MgUserLicense -UserId $newUser.Id -BodyParameter $licenseParams
}
if ($hire.ManagerUpn) {
$manager = Get-MgUser -UserId $hire.ManagerUpn
$managerRef = @{
"@odata.id" = "https://graph.microsoft.com/v1.0/users/$($manager.Id)"
}
Set-MgUserManagerByRef -UserId $newUser.Id -BodyParameter $managerRef
}
if ($hire.GroupIds) {
$groupIds = $hire.GroupIds -split ";"
foreach ($groupId in $groupIds) {
New-MgGroupMember -GroupId $groupId.Trim() -DirectoryObjectId $newUser.Id
}
}
}
catch {
$status = "Failed"
$errorMessage = $_.Exception.Message
Write-Warning "Failed to provision $($hire.UserPrincipalName): $errorMessage"
}
$results += [PSCustomObject]@{
DisplayName = $hire.DisplayName
UserPrincipalName = $hire.UserPrincipalName
TempPassword = $tempPassword
Status = $status
Error = $errorMessage
}
}
$results | Export-Csv -Path $LogPath -NoTypeInformation
Write-Host "Done. Log written to $LogPath. Temporary passwords are in that file, treat it as sensitive and delete it once passwords are handed off." -ForegroundColor Yellow
Disconnect-MgGraph
Read it before you run it, and test in a safe tenant first.