927 lines
21 KiB
PowerShell
927 lines
21 KiB
PowerShell
#requires -Version 7.2
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Creates a cloud-only Microsoft 365 employee account.
|
|
|
|
.DESCRIPTION
|
|
Creates a Microsoft 365 employee account, assigns a Business Premium
|
|
license, and adds the employee to designated security and distribution
|
|
groups.
|
|
|
|
Required PowerShell modules:
|
|
- Microsoft.Graph.Authentication
|
|
- ExchangeOnlineManagement
|
|
|
|
Missing modules are installed automatically for the current user.
|
|
|
|
The script validates that Microsoft Graph and Exchange Online are
|
|
connected to the same tenant before creating the employee.
|
|
|
|
Do not run with a transcript: the temporary password is displayed
|
|
on screen.
|
|
|
|
.PARAMETER TenantId
|
|
Optional Microsoft Entra tenant ID. Recommended when administering
|
|
multiple Microsoft 365 tenants.
|
|
|
|
.PARAMETER UsageLocation
|
|
Two-letter country code used for Microsoft 365 license assignment.
|
|
|
|
.PARAMETER PurchaseLicenseScript
|
|
Optional billing-provider adapter (.ps1).
|
|
|
|
Called ONCE when license capacity is exhausted, with:
|
|
-TenantId <guid> -SkuPartNumber SPB -AdditionalSeats 1
|
|
|
|
The adapter must purchase one additional Business Premium seat,
|
|
throw on failure, and never print secrets.
|
|
|
|
Graph cannot purchase subscription seats.
|
|
|
|
Without an adapter, the operator is prompted to purchase a seat
|
|
through the Microsoft 365 billing portal.
|
|
|
|
.EXAMPLE
|
|
./New-M365Employee.ps1
|
|
|
|
.EXAMPLE
|
|
./New-M365Employee.ps1 -TenantId 'your-tenant-id' -UsageLocation US
|
|
#>
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$TenantId,
|
|
|
|
[ValidatePattern('^[A-Za-z]{2}$')]
|
|
[string]$UsageLocation,
|
|
|
|
[string]$PurchaseLicenseScript
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
|
|
# ============================================================
|
|
# FUNCTIONS
|
|
# ============================================================
|
|
|
|
function Read-Required {
|
|
param(
|
|
[string]$Prompt
|
|
)
|
|
|
|
do {
|
|
$value = (Read-Host $Prompt).Trim()
|
|
}
|
|
while ([string]::IsNullOrWhiteSpace($value))
|
|
|
|
return $value
|
|
}
|
|
|
|
|
|
function Install-RequiredModule {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$ModuleName
|
|
)
|
|
|
|
Write-Host "Checking module: $ModuleName" -ForegroundColor Cyan
|
|
|
|
$installed = Get-Module -ListAvailable -Name $ModuleName
|
|
|
|
if (-not $installed) {
|
|
|
|
Write-Host "Installing missing module: $ModuleName" `
|
|
-ForegroundColor Yellow
|
|
|
|
Install-Module `
|
|
-Name $ModuleName `
|
|
-Scope CurrentUser `
|
|
-Repository PSGallery `
|
|
-Force `
|
|
-AllowClobber `
|
|
-ErrorAction Stop
|
|
}
|
|
|
|
Import-Module $ModuleName -ErrorAction Stop
|
|
|
|
Write-Host "Module ready: $ModuleName" -ForegroundColor Green
|
|
}
|
|
|
|
|
|
function Get-GraphCollection {
|
|
param(
|
|
[string]$Uri
|
|
)
|
|
|
|
do {
|
|
|
|
$page = Invoke-MgGraphRequest `
|
|
-Method GET `
|
|
-Uri $Uri `
|
|
-OutputType Hashtable
|
|
|
|
foreach ($item in $page['value']) {
|
|
$item
|
|
}
|
|
|
|
$Uri = $page['@odata.nextLink']
|
|
|
|
}
|
|
while ($Uri)
|
|
}
|
|
|
|
|
|
function Get-PremiumSku {
|
|
|
|
$matches = @(
|
|
Get-GraphCollection `
|
|
'https://graph.microsoft.com/v1.0/subscribedSkus' |
|
|
Where-Object {
|
|
$_['skuPartNumber'] -eq 'SPB'
|
|
}
|
|
)
|
|
|
|
if ($matches.Count -ne 1) {
|
|
|
|
throw @"
|
|
Expected exactly one Microsoft 365 Business Premium (SPB) SKU.
|
|
|
|
Check the tenant subscriptions.
|
|
|
|
Subscription variants require a reviewed SKU change.
|
|
"@
|
|
}
|
|
|
|
return $matches[0]
|
|
}
|
|
|
|
|
|
function Test-Capacity {
|
|
param(
|
|
$Sku
|
|
)
|
|
|
|
return (
|
|
$Sku['capabilityStatus'] -eq 'Enabled' -and
|
|
(
|
|
[int]$Sku['prepaidUnits']['enabled'] -
|
|
[int]$Sku['consumedUnits']
|
|
) -gt 0
|
|
)
|
|
}
|
|
|
|
|
|
function New-TemporaryPassword {
|
|
|
|
$sets = @(
|
|
'ABCDEFGHJKLMNPQRSTUVWXYZ'
|
|
'abcdefghijkmnopqrstuvwxyz'
|
|
'23456789'
|
|
'!@#$*-_+=?'
|
|
)
|
|
|
|
$all = $sets -join ''
|
|
|
|
$chars = [System.Collections.Generic.List[char]]::new()
|
|
|
|
foreach ($set in $sets) {
|
|
|
|
$index = [System.Security.Cryptography.RandomNumberGenerator]::GetInt32(
|
|
$set.Length
|
|
)
|
|
|
|
$chars.Add($set[$index])
|
|
}
|
|
|
|
while ($chars.Count -lt 24) {
|
|
|
|
$index = [System.Security.Cryptography.RandomNumberGenerator]::GetInt32(
|
|
$all.Length
|
|
)
|
|
|
|
$chars.Add($all[$index])
|
|
}
|
|
|
|
for ($i = $chars.Count - 1; $i -gt 0; $i--) {
|
|
|
|
$j = [System.Security.Cryptography.RandomNumberGenerator]::GetInt32(
|
|
$i + 1
|
|
)
|
|
|
|
$temp = $chars[$i]
|
|
$chars[$i] = $chars[$j]
|
|
$chars[$j] = $temp
|
|
}
|
|
|
|
return -join $chars
|
|
}
|
|
|
|
|
|
function Test-ExchangeTenant {
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$GraphTenantId
|
|
)
|
|
|
|
Write-Host ""
|
|
Write-Host "Verifying Exchange Online tenant..." `
|
|
-ForegroundColor Cyan
|
|
|
|
$connections = @(
|
|
Get-ConnectionInformation -ErrorAction Stop |
|
|
Where-Object {
|
|
$_.State -eq 'Connected'
|
|
}
|
|
)
|
|
|
|
if ($connections.Count -ne 1) {
|
|
|
|
throw @"
|
|
Exchange Online tenant validation failed.
|
|
|
|
Expected exactly one active Exchange Online connection.
|
|
Found: $($connections.Count)
|
|
|
|
Disconnect unused Exchange Online sessions and retry.
|
|
"@
|
|
}
|
|
|
|
$connection = $connections[0]
|
|
|
|
$exchangeTenantId = [string]$connection.TenantID
|
|
|
|
if ([string]::IsNullOrWhiteSpace($exchangeTenantId)) {
|
|
|
|
throw @"
|
|
Unable to determine the Exchange Online tenant ID.
|
|
|
|
The active Exchange Online connection did not return TenantID.
|
|
|
|
Provisioning has been stopped.
|
|
"@
|
|
}
|
|
|
|
$graphGuid = [guid]::Empty
|
|
$exchangeGuid = [guid]::Empty
|
|
|
|
if (-not [guid]::TryParse($GraphTenantId, [ref]$graphGuid)) {
|
|
throw "Microsoft Graph returned an invalid tenant ID."
|
|
}
|
|
|
|
if (-not [guid]::TryParse($exchangeTenantId, [ref]$exchangeGuid)) {
|
|
throw "Exchange Online returned an invalid tenant ID."
|
|
}
|
|
|
|
Write-Host "Microsoft Graph tenant: $GraphTenantId"
|
|
Write-Host "Exchange Online tenant: $exchangeTenantId"
|
|
|
|
if ($exchangeGuid -ne $graphGuid) {
|
|
|
|
throw @"
|
|
TENANT MISMATCH DETECTED
|
|
|
|
Microsoft Graph Tenant:
|
|
$GraphTenantId
|
|
|
|
Exchange Online Tenant:
|
|
$exchangeTenantId
|
|
|
|
The connections reference different Microsoft 365 tenants.
|
|
|
|
No employee account has been created.
|
|
"@
|
|
}
|
|
|
|
Write-Host "Tenant verification successful." `
|
|
-ForegroundColor Green
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
# MODULE DEPENDENCIES
|
|
# ============================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "Microsoft 365 Employee Provisioning" `
|
|
-ForegroundColor Cyan
|
|
|
|
Write-Host "-----------------------------------"
|
|
|
|
Install-RequiredModule -ModuleName 'Microsoft.Graph.Authentication'
|
|
|
|
Install-RequiredModule -ModuleName 'ExchangeOnlineManagement'
|
|
|
|
|
|
# ============================================================
|
|
# OPTIONAL BILLING ADAPTER
|
|
# ============================================================
|
|
|
|
if ($PurchaseLicenseScript) {
|
|
|
|
$PurchaseLicenseScript = (
|
|
Resolve-Path -LiteralPath $PurchaseLicenseScript
|
|
).Path
|
|
|
|
if (
|
|
[IO.Path]::GetExtension($PurchaseLicenseScript) -ne '.ps1'
|
|
) {
|
|
throw 'Purchase adapter must be a .ps1 file.'
|
|
}
|
|
}
|
|
|
|
|
|
# ============================================================
|
|
# EMPLOYEE INFORMATION
|
|
# ============================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "Enter employee information" -ForegroundColor Cyan
|
|
|
|
$firstName = Read-Required 'First name'
|
|
|
|
$lastName = Read-Required 'Last name'
|
|
|
|
|
|
do {
|
|
|
|
$email = Read-Required `
|
|
'Email address (also used as the sign-in name)'
|
|
|
|
$validEmail = $email -match `
|
|
'^[A-Za-z0-9][A-Za-z0-9._%+!~''-]*@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'
|
|
|
|
if (-not $validEmail) {
|
|
Write-Warning 'Enter a valid email address with an ASCII sign-in name.'
|
|
}
|
|
|
|
}
|
|
while (-not $validEmail)
|
|
|
|
|
|
do {
|
|
|
|
$device = (
|
|
Read-Required `
|
|
'Will the employee use a company-owned device? (yes/no)'
|
|
).ToLowerInvariant()
|
|
|
|
}
|
|
while ($device -notin @('y','yes','n','no'))
|
|
|
|
|
|
# ============================================================
|
|
# DEPARTMENT SELECTION
|
|
# ============================================================
|
|
|
|
$departments = @(
|
|
'Professional Services'
|
|
'Placement'
|
|
'Admin / BD'
|
|
)
|
|
|
|
Write-Host ""
|
|
Write-Host "Select department:" -ForegroundColor Cyan
|
|
|
|
for ($i = 0; $i -lt $departments.Count; $i++) {
|
|
|
|
Write-Host "$($i + 1). $($departments[$i])"
|
|
}
|
|
|
|
do {
|
|
|
|
$choice = Read-Required 'Select department (1-3)'
|
|
|
|
}
|
|
while ($choice -notin @('1','2','3'))
|
|
|
|
$department = $departments[[int]$choice - 1]
|
|
|
|
|
|
# ============================================================
|
|
# LICENSE USAGE LOCATION
|
|
# ============================================================
|
|
|
|
if (-not $UsageLocation) {
|
|
|
|
do {
|
|
|
|
$UsageLocation = Read-Required `
|
|
'Two-letter license usage country code (for example US)'
|
|
|
|
}
|
|
while ($UsageLocation -notmatch '^[A-Za-z]{2}$')
|
|
}
|
|
|
|
$UsageLocation = $UsageLocation.ToUpperInvariant()
|
|
|
|
|
|
# ============================================================
|
|
# CONNECT TO MICROSOFT GRAPH
|
|
# ============================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "Connecting to Microsoft Graph..." `
|
|
-ForegroundColor Cyan
|
|
|
|
$connect = @{
|
|
|
|
Scopes = @(
|
|
'User.ReadWrite.All'
|
|
'GroupMember.ReadWrite.All'
|
|
'Group.Read.All'
|
|
'LicenseAssignment.ReadWrite.All'
|
|
'Domain.Read.All'
|
|
'Organization.Read.All'
|
|
)
|
|
|
|
ContextScope = 'Process'
|
|
|
|
NoWelcome = $true
|
|
}
|
|
|
|
if ($TenantId) {
|
|
$connect.TenantId = $TenantId
|
|
}
|
|
|
|
Connect-MgGraph @connect
|
|
|
|
|
|
# ============================================================
|
|
# PROVISIONING STATE
|
|
# ============================================================
|
|
|
|
$createdUser = $null
|
|
$password = $null
|
|
|
|
$exchangeConnected = $false
|
|
$exchangeConnectedByScript = $false
|
|
|
|
$completed = [System.Collections.Generic.List[string]]::new()
|
|
|
|
|
|
try {
|
|
|
|
# ========================================================
|
|
# VERIFY GRAPH TENANT
|
|
# ========================================================
|
|
|
|
$actualTenant = (Get-MgContext).TenantId
|
|
|
|
Write-Host ""
|
|
Write-Host "Connected to tenant: $actualTenant" `
|
|
-ForegroundColor Green
|
|
|
|
if (
|
|
$TenantId -and
|
|
([guid]$TenantId -ne [guid]$actualTenant)
|
|
) {
|
|
throw 'Connected Microsoft Graph tenant does not match the requested tenant.'
|
|
}
|
|
|
|
|
|
# ========================================================
|
|
# VERIFY EMAIL DOMAIN
|
|
# ========================================================
|
|
|
|
$domain = $email.Split('@')[1]
|
|
|
|
$verified = @(
|
|
Get-GraphCollection `
|
|
'https://graph.microsoft.com/v1.0/domains' |
|
|
Where-Object {
|
|
$_['id'] -eq $domain -and
|
|
$_['isVerified']
|
|
}
|
|
)
|
|
|
|
if ($verified.Count -ne 1) {
|
|
|
|
throw "The email domain '$domain' is not verified in this tenant."
|
|
}
|
|
|
|
|
|
# ========================================================
|
|
# CHECK FOR EXISTING USER
|
|
# ========================================================
|
|
|
|
$filter = [Uri]::EscapeDataString(
|
|
"userPrincipalName eq '$($email.Replace("'", "''"))'"
|
|
)
|
|
|
|
$existing = @(
|
|
Get-GraphCollection `
|
|
"https://graph.microsoft.com/v1.0/users?`$filter=$filter&`$select=id"
|
|
)
|
|
|
|
if ($existing.Count) {
|
|
|
|
throw 'This sign-in name already exists. No existing account was changed.'
|
|
}
|
|
|
|
|
|
# ========================================================
|
|
# RESOLVE REQUIRED GROUPS
|
|
# ========================================================
|
|
|
|
$names = @(
|
|
'Active Employees'
|
|
'All Employees'
|
|
'IT-Communication'
|
|
)
|
|
|
|
if ($device -in @('y','yes')) {
|
|
|
|
$names += 'IT-Communication (Company Devices)'
|
|
}
|
|
|
|
$groups = @(
|
|
foreach ($name in $names) {
|
|
|
|
$filter = [Uri]::EscapeDataString(
|
|
"displayName eq '$($name.Replace("'", "''"))'"
|
|
)
|
|
|
|
$found = @(
|
|
Get-GraphCollection `
|
|
"https://graph.microsoft.com/v1.0/groups?`$filter=$filter&`$select=id,displayName,groupTypes,mailEnabled,mail,onPremisesSyncEnabled,isAssignableToRole"
|
|
)
|
|
|
|
if ($found.Count -ne 1) {
|
|
|
|
throw "Group '$name' is missing or ambiguous. Resolve this before creating the account."
|
|
}
|
|
|
|
$g = $found[0]
|
|
|
|
if (
|
|
$g['onPremisesSyncEnabled'] -or
|
|
$g['isAssignableToRole'] -or
|
|
$g['groupTypes'] -contains 'DynamicMembership'
|
|
) {
|
|
|
|
throw "Group '$name' is synchronized, role-assignable, or dynamic; this script cannot directly manage its membership."
|
|
}
|
|
|
|
$g
|
|
}
|
|
)
|
|
|
|
|
|
# ========================================================
|
|
# IDENTIFY EXCHANGE-MANAGED GROUPS
|
|
# ========================================================
|
|
|
|
$exchangeGroups = @(
|
|
$groups |
|
|
Where-Object {
|
|
$_['mailEnabled'] -and
|
|
$_['groupTypes'] -notcontains 'Unified'
|
|
}
|
|
)
|
|
|
|
|
|
# ========================================================
|
|
# EXCHANGE ONLINE CONNECTION AND TENANT VALIDATION
|
|
# ========================================================
|
|
|
|
if ($exchangeGroups.Count) {
|
|
|
|
Write-Host ""
|
|
Write-Host "Exchange-managed groups detected." `
|
|
-ForegroundColor Cyan
|
|
|
|
$existingConnections = @(
|
|
Get-ConnectionInformation -ErrorAction SilentlyContinue |
|
|
Where-Object {
|
|
$_.State -eq 'Connected'
|
|
}
|
|
)
|
|
|
|
if ($existingConnections.Count -eq 0) {
|
|
|
|
Write-Host "Connecting to Exchange Online..." `
|
|
-ForegroundColor Cyan
|
|
|
|
Connect-ExchangeOnline -ShowBanner:$false `
|
|
-ErrorAction Stop
|
|
|
|
$exchangeConnectedByScript = $true
|
|
}
|
|
|
|
$exchangeConnected = $true
|
|
|
|
# Verify Exchange Online uses the same tenant as Graph.
|
|
Test-ExchangeTenant -GraphTenantId $actualTenant
|
|
|
|
# Verify Exchange groups exist before creating employee.
|
|
foreach ($g in $exchangeGroups) {
|
|
|
|
$null = Get-DistributionGroup `
|
|
-Identity $g['mail'] `
|
|
-ErrorAction Stop
|
|
}
|
|
}
|
|
|
|
|
|
# ========================================================
|
|
# CHECK BUSINESS PREMIUM LICENSE CAPACITY
|
|
# ========================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "Checking Microsoft 365 Business Premium licenses..." `
|
|
-ForegroundColor Cyan
|
|
|
|
$sku = Get-PremiumSku
|
|
|
|
if (-not (Test-Capacity $sku)) {
|
|
|
|
if ($PurchaseLicenseScript) {
|
|
|
|
Write-Host "Requesting one additional Business Premium seat..."
|
|
|
|
& $PurchaseLicenseScript `
|
|
-TenantId $actualTenant `
|
|
-SkuPartNumber 'SPB' `
|
|
-AdditionalSeats 1 |
|
|
Out-Null
|
|
|
|
# Do not automatically retry the purchase.
|
|
|
|
}
|
|
else {
|
|
|
|
Write-Warning 'No available Business Premium license.'
|
|
|
|
Write-Host 'Purchase an additional seat at:'
|
|
Write-Host 'https://admin.microsoft.com'
|
|
Write-Host 'Billing > Your products'
|
|
|
|
$null = Read-Host `
|
|
'Press Enter after purchasing the license, or Ctrl+C to cancel'
|
|
}
|
|
|
|
$deadline = [DateTime]::UtcNow.AddMinutes(10)
|
|
|
|
do {
|
|
|
|
$sku = Get-PremiumSku
|
|
|
|
if (Test-Capacity $sku) {
|
|
break
|
|
}
|
|
|
|
Write-Host 'Waiting for license capacity...'
|
|
|
|
Start-Sleep -Seconds 15
|
|
|
|
}
|
|
while ([DateTime]::UtcNow -lt $deadline)
|
|
|
|
if (-not (Test-Capacity $sku)) {
|
|
|
|
throw 'No available Business Premium license after 10 minutes. No account was created.'
|
|
}
|
|
}
|
|
|
|
Write-Host "Business Premium license available." `
|
|
-ForegroundColor Green
|
|
|
|
|
|
# ========================================================
|
|
# CREATE EMPLOYEE ACCOUNT
|
|
# ========================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "Creating Microsoft 365 employee..." `
|
|
-ForegroundColor Cyan
|
|
|
|
$password = New-TemporaryPassword
|
|
|
|
$body = @{
|
|
|
|
accountEnabled = $true
|
|
|
|
displayName = "$firstName $lastName"
|
|
|
|
givenName = $firstName
|
|
|
|
surname = $lastName
|
|
|
|
userPrincipalName = $email
|
|
|
|
mailNickname = $email.Split('@')[0]
|
|
|
|
department = $department
|
|
|
|
usageLocation = $UsageLocation
|
|
|
|
passwordProfile = @{
|
|
|
|
password = $password
|
|
|
|
forceChangePasswordNextSignIn = $true
|
|
}
|
|
}
|
|
|
|
$createdUser = Invoke-MgGraphRequest `
|
|
-Method POST `
|
|
-Uri 'https://graph.microsoft.com/v1.0/users' `
|
|
-Body ($body | ConvertTo-Json -Depth 5) `
|
|
-ContentType 'application/json' `
|
|
-OutputType Hashtable
|
|
|
|
$id = $createdUser['id']
|
|
|
|
$completed.Add('Employee account created')
|
|
|
|
|
|
# ========================================================
|
|
# DISPLAY INITIAL ACCOUNT INFORMATION
|
|
# ========================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "Account created: $email" `
|
|
-ForegroundColor Green
|
|
|
|
Write-Host "Temporary password: $password" `
|
|
-ForegroundColor Yellow
|
|
|
|
Write-Host 'Password change is required at first sign-in.'
|
|
Write-Host 'Store and share the password securely.'
|
|
|
|
|
|
# ========================================================
|
|
# ASSIGN BUSINESS PREMIUM LICENSE
|
|
# ========================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "Assigning Business Premium license..." `
|
|
-ForegroundColor Cyan
|
|
|
|
$licenseBody = @{
|
|
|
|
addLicenses = @(
|
|
@{
|
|
skuId = $sku['skuId']
|
|
}
|
|
)
|
|
|
|
removeLicenses = @()
|
|
}
|
|
|
|
$null = Invoke-MgGraphRequest `
|
|
-Method POST `
|
|
-Uri "https://graph.microsoft.com/v1.0/users/$id/assignLicense" `
|
|
-Body ($licenseBody | ConvertTo-Json -Depth 5) `
|
|
-ContentType 'application/json'
|
|
|
|
$completed.Add('Business Premium license assigned')
|
|
|
|
Write-Host "Business Premium license assigned." `
|
|
-ForegroundColor Green
|
|
|
|
|
|
# ========================================================
|
|
# ADD EMPLOYEE TO GROUPS
|
|
# ========================================================
|
|
|
|
foreach ($g in $groups) {
|
|
|
|
Write-Host ""
|
|
Write-Host "Adding employee to: $($g['displayName'])" `
|
|
-ForegroundColor Cyan
|
|
|
|
if (
|
|
$g['mailEnabled'] -and
|
|
$g['groupTypes'] -notcontains 'Unified'
|
|
) {
|
|
|
|
# Exchange recipient provisioning can take time.
|
|
|
|
$recipient = $null
|
|
|
|
for ($attempt = 0; $attempt -lt 40; $attempt++) {
|
|
|
|
$recipient = Get-Recipient `
|
|
-Identity $email `
|
|
-ErrorAction SilentlyContinue
|
|
|
|
if ($recipient) {
|
|
break
|
|
}
|
|
|
|
Start-Sleep -Seconds 15
|
|
}
|
|
|
|
if (-not $recipient) {
|
|
|
|
throw 'Exchange recipient was not ready after 10 minutes. Complete distribution group membership manually.'
|
|
}
|
|
|
|
Add-DistributionGroupMember `
|
|
-Identity $g['mail'] `
|
|
-Member $email `
|
|
-BypassSecurityGroupManagerCheck `
|
|
-ErrorAction Stop
|
|
|
|
}
|
|
else {
|
|
|
|
$memberBody = @{
|
|
|
|
'@odata.id' = "https://graph.microsoft.com/v1.0/directoryObjects/$id"
|
|
}
|
|
|
|
$null = Invoke-MgGraphRequest `
|
|
-Method POST `
|
|
-Uri "https://graph.microsoft.com/v1.0/groups/$($g['id'])/members/`$ref" `
|
|
-Body ($memberBody | ConvertTo-Json) `
|
|
-ContentType 'application/json'
|
|
}
|
|
|
|
$completed.Add("Joined $($g['displayName'])")
|
|
|
|
Write-Host "Group membership completed." `
|
|
-ForegroundColor Green
|
|
}
|
|
|
|
|
|
# ========================================================
|
|
# PROVISIONING SUMMARY
|
|
# ========================================================
|
|
|
|
Write-Host ""
|
|
Write-Host "============================================" `
|
|
-ForegroundColor Green
|
|
|
|
Write-Host "EMPLOYEE PROVISIONING COMPLETE" `
|
|
-ForegroundColor Green
|
|
|
|
Write-Host "============================================" `
|
|
-ForegroundColor Green
|
|
|
|
Write-Host "Employee: $firstName $lastName"
|
|
Write-Host "Email: $email"
|
|
Write-Host "Department: $department"
|
|
Write-Host "Tenant: $actualTenant"
|
|
|
|
Write-Host ""
|
|
Write-Host "Completed tasks:"
|
|
|
|
$completed | ForEach-Object {
|
|
|
|
Write-Host " - $_" -ForegroundColor Green
|
|
}
|
|
|
|
}
|
|
catch {
|
|
|
|
Write-Host ""
|
|
Write-Host "PROVISIONING ERROR" -ForegroundColor Red
|
|
|
|
if ($createdUser) {
|
|
|
|
Write-Warning @"
|
|
PARTIAL COMPLETION
|
|
|
|
The employee account exists.
|
|
|
|
Email: $email
|
|
User ID: $($createdUser['id'])
|
|
|
|
Do not rerun the script to recreate this employee.
|
|
|
|
Resolve the remaining provisioning steps on the
|
|
existing account.
|
|
"@
|
|
|
|
$completed | ForEach-Object {
|
|
|
|
Write-Host "Completed: $_" `
|
|
-ForegroundColor Yellow
|
|
}
|
|
}
|
|
|
|
throw
|
|
|
|
}
|
|
finally {
|
|
|
|
# Clear the local password variable.
|
|
$password = $null
|
|
|
|
# Only disconnect an Exchange session created by this script.
|
|
if ($exchangeConnectedByScript) {
|
|
|
|
Disconnect-ExchangeOnline `
|
|
-Confirm:$false `
|
|
-ErrorAction SilentlyContinue
|
|
}
|
|
|
|
Disconnect-MgGraph `
|
|
-ErrorAction SilentlyContinue |
|
|
Out-Null
|
|
} |