Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <#
- This script will be setup on one or more domain controllers to be run periodically through task scheduler, although the times should be staggered by domain controller.
- Example:
- dc01 runs on the half hour
- dc02 runs on the hour
- Things this script needs to do:
- determine if a new user exists in ou=rippling user temp,ou=user accounts
- if a user or users exist, iterate through them.
- For each user:
- - look at the job title and office to determine if the user should be licensesd for M365 E3 or O365 f3. Add to appropriate AD groups
- - look at the department to assign default groups in AD
- - look at the department to assign default teams in Microsoft 365 (if any)
- - check for available licenses in Microsoft 365
- - move the user from ou=rippling user temp,ou=user accounts to appropriate ou based on country
- - email admins that a new user has been created plus any errors that occured.
- - Synchronize the changes between AD servers and then to the cloud
- Requirements:
- - cert creds to connect to azure ad
- - any ad functions need to specify domain name so it works in testing from azure ad joined computer
- - activedirectory module
- - msgraph module
- #>
- # Import required modules
- Import-Module ActiveDirectory
- # Configuration
- $LogFile = "C:\temp\auto-usersetup-$(Get-Date -Format 'yyyyMMdd').log"
- $SmtpServer = 'mail.contoso.local'
- $SmtpPort = 25
- # Azure AD App Registration Configuration
- # Azure AD App Registration Configuration
- $TenantId = '<YOUR_TENANT_ID>'
- $AppId = '<YOUR_APP_REGISTRATION_CLIENT_ID>'
- $CertThumbprint = '<YOUR_CERT_THUMBPRINT>'
- $Organization = 'contoso.com'
- # Initialize log collection for email
- $script:LogMessages = @()
- $script:CertWarning = $null
- $script:UsersProcessed = 0
- $script:UsersSuccessfullyProcessed = 0 # Only count users who completed all critical steps
- $script:UserPasswords = @() # Store user/password pairs for summary email (only successfully processed users)
- $script:LmsWarnings = @() # Track LMS API failures for email report
- # Set variables
- $domain = "contoso.local"
- $ripplingTempOU = "ou=rippling user temp,ou=user accounts,ou=root-users,dc=contoso,dc=local"
- try {
- $passwordWriteServer = (Get-ADDomain -Server $domain -ErrorAction Stop).PDCEmulator
- }
- catch {
- $passwordWriteServer = $domain
- }
- # European country codes
- $EuropeanCountries = @('GB','DE','IT','FR','ES','NL','BE','AT','SE','DK','NO','FI','PL','CZ','IE','PT','CH','GR','HU','RO','BG','HR','SI','SK','LT','LV','EE','LU','MT','CY','IS','LI','MC','AD','SM','VA','ME','MK','AL','BA','RS','XK','UA','BY','MD')
- # Define destination OUs based on country
- $DestinationOUs = @{
- 'US' = 'OU=US-CAN,OU=user accounts,OU=root-users,DC=contoso,DC=local'
- 'CA' = 'OU=US-CAN,OU=user accounts,OU=root-users,DC=contoso,DC=local'
- 'MX' = 'OU=US-CAN,OU=user accounts,OU=root-users,DC=contoso,DC=local'
- 'AU' = 'OU=Australia,OU=user accounts,OU=root-users,DC=contoso,DC=local'
- 'IN' = 'OU=India,OU=user accounts,OU=root-users,DC=contoso,DC=local'
- }
- # Add European countries to UK-GB OU
- foreach ($euCountry in $EuropeanCountries) {
- $DestinationOUs[$euCountry] = 'OU=UK-GB,OU=user accounts,OU=root-users,DC=contoso,DC=local'
- }
- function New-TemporaryPassword {
- param(
- [int]$Length = 16
- )
- if ($Length -lt 3) {
- $Length = 3
- }
- $upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ'.ToCharArray()
- $lower = 'abcdefghijkmnopqrstuvwxyz'.ToCharArray()
- $digit = '23456789'.ToCharArray()
- # First character is uppercase. Remaining characters are lowercase/numbers.
- $firstChar = ($upper | Get-Random)
- $tailChars = @(
- ($lower | Get-Random)
- ($digit | Get-Random)
- )
- $tailPool = $lower + $digit
- for ($i = $tailChars.Count + 1; $i -lt $Length; $i++) {
- $tailChars += ($tailPool | Get-Random)
- }
- $shuffledTail = $tailChars | Sort-Object { Get-Random }
- $passwordChars = @($firstChar) + $shuffledTail
- return ($passwordChars -join '')
- }
- # Define domain controllers for AD replication (currently unused - replication runs locally only)
- # $DomainControllers = @('dc01.contoso.local', 'dc02.contoso.local', 'dc03.contoso.local')
- #set default error action to stop
- $ErrorActionPreference = "Stop"
- # Logging function
- function Write-Log {
- param(
- [string]$Message,
- [string]$Level = 'INFO'
- )
- $Timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
- $LogEntry = "[$Timestamp] [$Level] $Message"
- # Write to console
- Write-Host $Message
- # Write to log file
- Add-Content -Path $LogFile -Value $LogEntry
- # Add to email collection
- $script:LogMessages += $LogEntry
- }
- # Helper function to add user to AD group, suppressing "already a member" error
- function Add-ADGroupMemberSafe {
- [CmdletBinding()]
- param(
- [Parameter(ValueFromPipeline=$true)]
- $InputObject,
- [Parameter(Mandatory=$false)]
- $Identity,
- [Parameter(Mandatory=$true)]
- $Members,
- [Parameter(Mandatory=$true)]
- [string]$Server
- )
- process {
- $targetIdentity = if ($PSBoundParameters.ContainsKey('Identity')) {
- $Identity
- }
- else {
- $InputObject
- }
- if ($null -eq $targetIdentity -or ($targetIdentity -is [string] -and [string]::IsNullOrWhiteSpace($targetIdentity))) {
- throw "Add-ADGroupMemberSafe requires -Identity or pipeline input."
- }
- try {
- Add-ADGroupMember -Identity $targetIdentity -Members $Members -Server $Server -ErrorAction Stop
- }
- catch {
- # Check if error is "already a member" - if so, silently continue
- if ($_.Exception.Message -like "*already a member of the group*" -or
- $_.Exception.Message -like "One or more added object references already exist for the following modified properties: 'members'") {
- # User is already a member, this is fine - do nothing
- }
- else {
- # This is a different error, re-throw it
- throw
- }
- }
- }
- }
- # Helper function to add user to Exchange Online distribution group, suppressing "already a member" error
- function Add-DistributionGroupMemberSafe {
- param(
- [Parameter(Mandatory=$true)]
- [string]$Identity,
- [Parameter(Mandatory=$true)]
- [string]$Member
- )
- try {
- Add-DistributionGroupMember -Identity $Identity -Member $Member -ErrorAction Stop
- }
- catch {
- if ($_.Exception.Message -like "*already a member*") {
- # User is already a member, this is fine - do nothing
- }
- else {
- throw
- }
- }
- }
- # Helper function to add user to Microsoft Graph group, suppressing "already exists" error
- function New-MgGroupMemberSafe {
- param(
- [Parameter(Mandatory=$true)]
- [string]$DirectoryObjectId,
- [Parameter(Mandatory=$true)]
- [string]$GroupId
- )
- try {
- New-MgGroupMember -DirectoryObjectId $DirectoryObjectId -GroupId $GroupId -ErrorAction Stop
- }
- catch {
- # Check if error is "already exists"
- if ($_.Exception.Message -like "*One or more added object references already exist*") {
- # User is already a member, this is fine - do nothing
- }
- else {
- # This is a different error, re-throw it
- throw
- }
- }
- }
- # Function to check certificate expiration
- function Test-CertificateExpiration {
- param(
- [string]$Thumbprint,
- [int]$WarningDays = 30
- )
- try {
- $cert = Get-ChildItem -Path Cert:\LocalMachine\My\$Thumbprint -ErrorAction Stop
- $daysUntilExpiration = ($cert.NotAfter - (Get-Date)).Days
- Write-Log "Certificate expires on: $($cert.NotAfter.ToString('yyyy-MM-dd'))"
- Write-Log "Days until expiration: $daysUntilExpiration"
- if ($daysUntilExpiration -le 0) {
- $script:CertWarning = "CRITICAL: Certificate has EXPIRED on $($cert.NotAfter.ToString('yyyy-MM-dd'))!"
- Write-Log $script:CertWarning -Level 'ERROR'
- }
- elseif ($daysUntilExpiration -le $WarningDays) {
- $script:CertWarning = "WARNING: Certificate will expire in $daysUntilExpiration days on $($cert.NotAfter.ToString('yyyy-MM-dd'))!"
- Write-Log $script:CertWarning -Level 'WARNING'
- }
- return $cert
- }
- catch {
- Write-Log "ERROR: Could not find certificate with thumbprint $Thumbprint" -Level 'ERROR'
- throw
- }
- }
- Write-Log "=== Auto User Setup Script Started ==="
- # Get users from rippling temp OU
- Write-Log "Searching for new users in Rippling temp OU..."
- $users = Get-ADUser -Filter * -SearchBase $ripplingTempOU -Server $domain -properties *
- Write-Log "Found $($users.Count) users to process"
- if ($users.Count -eq 0) {
- Write-Log "No users to process. Exiting."
- exit 0
- }
- # Check certificate expiration before connecting
- Write-Log "Checking certificate expiration..."
- $cert = Test-CertificateExpiration -Thumbprint $CertThumbprint -WarningDays 30
- # Connect to Microsoft Graph
- Write-Log "Connecting to Microsoft Graph..."
- import-module microsoft.graph.users
- import-module microsoft.graph.groups
- import-module microsoft.graph.teams
- Import-Module Microsoft.Graph.Authentication
- Import-Module ExchangeOnlineManagement
- try {
- Connect-MgGraph -ClientId $AppId -TenantId $TenantId -CertificateThumbprint $CertThumbprint -NoWelcome
- Write-Log "Successfully connected to Microsoft Graph"
- }
- catch {
- Write-Log "ERROR connecting to Microsoft Graph: $_" -Level 'ERROR'
- throw
- }
- Write-Log "Connecting to Exchange Online..."
- $exchangeOnlineConnected = $false
- try {
- Connect-ExchangeOnline -CertificateThumbprint $CertThumbprint -AppId $AppId -Organization $Organization -ShowBanner:$false -ErrorAction Stop
- $exchangeOnlineConnected = $true
- Write-Log "Successfully connected to Exchange Online"
- }
- catch {
- Write-Log "WARNING: Failed to connect to Exchange Online: $($_.Exception.Message). Distribution group assignments will be skipped." -Level 'WARNING'
- }
- #region LMS API
- # LMS API Configuration
- $lmsUrl = "https://lms.example.com/rest/v.01"
- $lmsKey = "<YOUR_LMS_CLIENT_ID>"
- $lmsAuth = "<YOUR_LMS_CLIENT_SECRET>"
- try {
- $lmsTokenHeader = @{"Content-Type" = "application/x-www-form-urlencoded"}
- $lmsTokenBody = [ordered]@{
- client_id = $lmsKey
- client_secret = $lmsAuth
- grant_type = "client_credentials"
- }
- $lmsToken = Invoke-RestMethod -Uri "$lmsUrl/token" -Method Post -Headers $lmsTokenHeader -Body $lmsTokenBody
- Write-Log "Successfully connected to LMS API"
- }
- catch {
- Write-Log "WARNING: Failed to connect to LMS API: $($_.Exception.Message). LMS account creation will be skipped." -Level 'WARNING'
- $lmsToken = $null
- }
- #endregion
- #region Licensing
- # get the available licenses
- $licenses = Get-MgSubscribedSku
- #important sku part numbers to group mapping:
- #AAD_PREMIUM = o365Lic_aadp1
- #SPE_E3 = o365Lic_E3_Installed Office
- #atp_enterprise=defender
- #desklesspack = o365Lic_F3_No Installed Office
- #currently used sku part numbers
- $o365Lic_E3count=($licenses|Where-Object {$_.SkuPartNumber -eq "spe_e3"}).ConsumedUnits
- $o365Lic_F3count=($licenses|Where-Object {$_.SkuPartNumber -eq "desklesspack"}).ConsumedUnits
- $defendercount=($licenses|Where-Object {$_.SkuPartNumber -eq "atp_enterprise"}).ConsumedUnits
- $aadp1count=($licenses|Where-Object {$_.SkuPartNumber -eq "aad_premium"}).ConsumedUnits
- #licenses owned
- $aadp1Owned=($licenses|Where-Object {$_.SkuPartNumber -eq "aad_premium"}).PrepaidUnits.enabled
- $o365Lic_E3Owned=($licenses|Where-Object {$_.SkuPartNumber -eq "spe_e3"}).PrepaidUnits.enabled
- $o365Lic_F3Owned=($licenses|Where-Object {$_.SkuPartNumber -eq "desklesspack"}).PrepaidUnits.enabled
- $defenderOwned=($licenses|Where-Object {$_.SkuPartNumber -eq "atp_enterprise"}).PrepaidUnits.enabled
- #write these variables to log so we can email them in the report
- Write-Log "AAD P1 Owned: $aadp1Owned and Used: $aadp1count"
- Write-log "O365 E3 Owned: $o365Lic_E3Owned and Used: $o365Lic_E3count"
- Write-log "O365 F3 Owned: $o365Lic_F3Owned and Used: $o365Lic_F3count"
- Write-log "Defender Owned: $defenderOwned and Used: $defendercount"
- #initalize variables for how many of each we are adding
- $e3consumed=0
- $f3consumed=0
- $defenderconsumed=0
- $aadp1consumed=0
- #endregion
- #initialize other variables
- #Process each user
- foreach ($user in $users) {
- # setup a list variable to hold what licenses this user requires
- $requiredLicenses= [System.Collections.ArrayList]::new()
- # Track that we're processing a user
- $script:UsersProcessed++
- Write-Log "`n========================================"
- Write-Log "Processing user: $($user.Name) ($($user.UserPrincipalName))"
- Write-Log "Validating user information..."
- # Check for missing required fields
- $deptValue = if ($null -eq $user.Department -or $user.Department -eq '') { "MISSING" } else { $user.Department }
- $officeValue = if ($null -eq $user.Office -or $user.Office -eq '') { "MISSING" } else { $user.Office }
- $countryValue = if ($null -eq $user.Country -or $user.Country -eq '') { "MISSING" } else { $user.Country }
- # Validate email format (should be [email protected])
- $emailValid = $true
- $emailError = ""
- if ($user.UserPrincipalName -notmatch '^[a-z]+\.[a-z-0-9]+@contoso\.com$') {
- $emailValid = $false
- $emailError = "Email format is incorrect. Expected: [email protected], Got: $($user.UserPrincipalName)"
- Write-Log "ERROR: $emailError" -Level 'ERROR'
- }
- # Check if any validation failed
- if($null -eq $user.Department -or $null -eq $user.Office -or $null -eq $user.Country -or
- $user.Department -eq '' -or $user.Office -eq '' -or $user.Country -eq '' -or
- -not $emailValid) {
- if ($null -ne $user.Department -and $null -ne $user.Office -and $null -ne $user.Country -and
- $user.Department -ne '' -and $user.Office -ne '' -and $user.Country -ne '') {
- # Only email issue
- Write-Log "ERROR: User email format is invalid" -Level 'ERROR'
- }
- else {
- # Field missing issue (or both)
- Write-Log "ERROR: User information is incomplete - Department: $deptValue, Office: $officeValue, Country: $countryValue" -Level 'ERROR'
- }
- # Email HR about incomplete user setup
- try {
- $emailSection = if (-not $emailValid) { "`n`nEmail Format Issue:`n$emailError" } else { "" }
- $fieldSection = ""
- if ($deptValue -eq "MISSING" -or $officeValue -eq "MISSING" -or $countryValue -eq "MISSING") {
- $fieldSection = @"
- Missing/Invalid Fields:
- - Department: $deptValue
- - Office: $officeValue
- - Country: $countryValue
- "@
- }
- $body = @"
- User $($user.Name) ($($user.UserPrincipalName)) is not setup properly in Rippling.$fieldSection$emailSection
- Please update the user's information in Rippling and they will be processed on the next run.
- "@
- Send-MailMessage -To "[email protected]" -From "[email protected]" -Subject "User Setup Error: $($user.Name)" -Body $body -SmtpServer "mail.contoso.local" -Port 25
- Write-Log "Error notification email sent to IT"
- }
- catch {
- Write-Log "WARNING: Failed to send error notification email: $_" -Level 'WARNING'
- }
- Write-Log "Skipping user due to validation errors. Moving to next user..."
- continue
- }
- Write-Log "✓ User information validated - Department: $($user.Department), Office: $($user.Office), Country: $($user.Country)"
- Write-Log "✓ Email format validated: $($user.UserPrincipalName)"
- Write-Log "Checking Azure AD user existence..."
- # Check if user exists in Azure AD (may need to wait for sync)
- $userInfo = $null
- $maxRetries = 3
- $retryDelay = 10
- $azureAdUserFound = $false
- for ($retry = 1; $retry -le $maxRetries; $retry++) {
- try {
- $userInfo = Get-MgUser -UserId $user.userPrincipalName -ErrorAction Stop
- if ($userInfo) {
- $azureAdUserFound = $true
- Write-Log "Azure AD user found (attempt $retry): $($userInfo.Id)"
- break
- }
- }
- catch {
- if ($_.Exception.Message -like "*Request_ResourceNotFound*" -or $_.Exception.Message -like "*does not exist*") {
- if ($retry -lt $maxRetries) {
- Write-Log "Azure AD user not found yet, waiting ${retryDelay}s before retry ($retry/$maxRetries)..."
- Start-Sleep -Seconds $retryDelay
- }
- else {
- Write-Log "ERROR: User does not exist in Azure AD after $maxRetries attempts. User was likely just created and needs time to sync from on-prem AD." -Level 'ERROR'
- }
- }
- else {
- Write-Log "ERROR: Unexpected error querying Graph API: $_" -Level 'ERROR'
- break
- }
- }
- }
- # Skip user entirely if not found in Azure AD - they need to sync first
- if (-not $azureAdUserFound) {
- Write-Log "ERROR: Cannot process user '$($user.Name)' - not found in Azure AD. Skipping all setup. User will be processed on next run after AD sync completes." -Level 'ERROR'
- continue
- }
- # Generate random password for user
- Write-Log "Generating secure password for user..."
- $password = New-TemporaryPassword -Length 16
- # Reset user password in AD
- try {
- $securePassword = ConvertTo-SecureString -String $password -AsPlainText -Force
- Set-ADAccountPassword -Identity $user.SamAccountName -NewPassword $securePassword -Reset -Server $passwordWriteServer
- Set-ADUser -Identity $user.SamAccountName -ChangePasswordAtLogon $false -Server $passwordWriteServer
- Write-Log "Password reset successfully"
- $passwordAuditUser = Get-ADUser -Identity $user.SamAccountName -Server $passwordWriteServer -Properties pwdLastSet, whenChanged
- $pwdLastSetText = if ($passwordAuditUser.pwdLastSet -and [int64]$passwordAuditUser.pwdLastSet -gt 0) {
- [datetime]::FromFileTimeUtc([int64]$passwordAuditUser.pwdLastSet).ToString('yyyy-MM-dd HH:mm:ss') + ' UTC'
- }
- else {
- 'not set'
- }
- $whenChangedText = if ($passwordAuditUser.whenChanged) {
- $passwordAuditUser.whenChanged.ToUniversalTime().ToString('yyyy-MM-dd HH:mm:ss') + ' UTC'
- }
- else {
- 'unknown'
- }
- Write-Log "Password write verification on $passwordWriteServer - pwdLastSet: $pwdLastSetText, whenChanged: $whenChangedText"
- }
- catch {
- Write-Log "ERROR resetting password: $_" -Level 'ERROR'
- continue
- }
- try {
- switch ($true) {
- ($user.Office -eq "field") {
- if ($user.country -eq 'US' -or $user.country -eq 'CA') {
- #add user to f3 license group
- get-adgroup -filter 'name -like "o365Lic_F3_No Installed Office"'|Add-ADGroupMemberSafe -Members $user -server $domain
- $requiredLicenses.Add("o365Lic_F3_No Installed Office") | Out-Null
- }
- else {
- #add user to e3 license group
- get-adgroup -filter 'name -like "o365Lic_E3_Installed Office"'|Add-ADGroupMemberSafe -Identity "CN=o365Lic_E3_Installed Office,OU=o365_Licensing_Groups,OU=SecurityGroups,OU=root-users,DC=contoso,DC=local" -Members $user -server $domain
- $requiredLicenses.Add("o365Lic_E3_Installed Office") | Out-Null
- }
- }
- #check to see if the user is in dept 067 - RSU-Transducer but does not have manager in their title
- ($user.Department -eq "dept 067 - Assembly" -and $user.Title -notlike "*manager*") {
- get-adgroup -filter 'name -like "o365Lic_F3_No Installed Office"'|Add-ADGroupMemberSafe -Identity "o365Lic_F3_No Installed Office" -Members $user -server $domain
- $requiredLicenses.Add("o365Lic_F3_No Installed Office") | Out-Null
- get-adgroup -filter 'name -like "o365Lic_aadp1"'|Add-ADGroupMemberSafe -Identity "o365Lic_aadp1" -Members $user -server $domain
- $requiredLicenses.Add("o365Lic_aadp1") | Out-Null
- }
- default {
- #add user to e3 license group
- get-adgroup -filter 'name -like "o365Lic_E3_Installed Office"'|Add-ADGroupMemberSafe -Identity "CN=o365Lic_E3_Installed Office,OU=o365_Licensing_Groups,OU=SecurityGroups,OU=root-users,DC=contoso,DC=local" -Members $user -server $domain
- $requiredLicenses.Add("o365Lic_E3_Installed Office") | Out-Null
- }
- }
- }
- catch{
- Write-Log "ERROR determining base office licenses for user: $($user.Name). Error: $($_.Exception.Message)" -Level 'ERROR'
- continue
- }
- Write-Log "User requires the following licenses: $($requiredLicenses -join ', ')"
- #increment license counters
- foreach ($license in $requiredLicenses) {
- switch ($license) {
- "o365Lic_E3_Installed Office" {
- $e3consumed++
- $defenderconsumed++
- }
- "o365Lic_F3_No Installed Office" {
- $f3consumed++
- $defenderconsumed++
- }
- "o365Lic_aadp1" {
- $aadp1consumed++
- }
- }
- }
- # Check if licensing this user will put us over the limit
- if ($e3consumed -gt $o365Lic_E3Owned) {
- Write-Log "WARNING: E3 license limit exceeded for user: $($user.Name)" -Level 'WARNING'
- }
- if ($f3consumed -gt $o365Lic_F3Owned) {
- Write-Log "WARNING: F3 license limit exceeded for user: $($user.Name)" -Level 'WARNING'
- }
- if ($defenderconsumed -gt $defenderOwned) {
- Write-Log "WARNING: Defender license limit exceeded for user: $($user.Name)" -Level 'WARNING'
- }
- if ($aadp1consumed -gt $aadp1Owned) {
- Write-Log "WARNING: AAD P1 license limit exceeded for user: $($user.Name)" -Level 'WARNING'
- }
- # Setup groups for the user
- Write-Log "Setting up groups and teams for user..."
- $groupsAdded=[System.Collections.ArrayList]::new()
- #default group assignment
- #groups that all users get added to:
- try{
- Add-ADGroupMemberSafe -Identity "SEC_LmsTraining" -Members $user -Server $domain
- $groupsAdded.Add("SEC_LmsTraining") | Out-Null
- # Only add to cloud groups if user exists in Azure AD
- if ($azureAdUserFound -and $userInfo) {
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId "<GROUP_OBJECT_ID>" #Object ID of the group "SEC_viewer_production_read_only"
- $groupsAdded.Add("SEC_viewer_production_read_only") | Out-Null
- }
- else {
- Write-Log "Skipping cloud group assignment - user not yet synced to Azure AD"
- }
- }
- catch{
- Write-Log "ERROR adding user to default groups: $($user.Name). Error: $($_.Exception.Message)" -Level 'ERROR'
- # Continue processing - don't fail the whole user for group errors
- }
- try{
- #if user is a non field, US, CA or MX user
- if ($user.Office -ne "field" -and ($user.country -eq "US" -or $user.country -eq "CA" -or $user.country -eq "MX")) {
- Add-ADGroupMemberSafe -Identity "map_shelton_w" -Members $user -Server $domain
- $groupsAdded.Add("map_shelton_w") | Out-Null
- }
- }
- catch{
- Write-Log "ERROR adding user to map_shelton_w group: $($user.Name). Error: $($_.Exception.Message)" -Level 'ERROR'
- # Non-critical error, continue processing
- }
- try{
- if ($user.Office -eq 'field' -and ($user.country -eq 'US' -or $user.country -eq 'CA' -or $user.country -eq 'MX')) {
- if ($exchangeOnlineConnected) {
- }
- else {
- Write-Log "Skipping [email protected] assignment - Exchange Online is not connected"
- }
- }
- }
- catch{
- Write-Log "ERROR adding user to [email protected]: $($user.Name). Error: $($_.Exception.Message)" -Level 'ERROR'
- # Non-critical error, continue processing
- }
- try {
- switch ($true) {
- {$user.Department -eq "096 - Analysis" -and $user.country -eq "US"} {
- # Add groups for Non-Stop Analysis US
- Add-ADGroupMemberSafe -Identity "Map_hvnz_qualarchive" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "map_m_cardata" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "MAP_Intune_US Analysis" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "fol_global_analysis" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "vpn" -Members $user -Server $domain
- $groupsAdded.Add("vpn") | Out-Null
- $groupsAdded.Add("Map_hvnz_qualarchive") | Out-Null
- $groupsAdded.Add("map_m_cardata") | Out-Null
- $groupsAdded.Add("MAP_Intune_US Analysis") | Out-Null
- $groupsAdded.Add("fol_global_analysis") | Out-Null
- # Teams operations require Azure AD user
- if ($azureAdUserFound -and $userInfo) {
- #US Analysis (Team)
- #find us analysis team
- $usAnalysisTeam = get-mgteam -filter "displayName eq 'US Analysis'"
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId $usAnalysisTeam.Id
- $groupsAdded.Add("US Analysis (Team)") | Out-Null
- # Add to Analysis-Software-IT US channel in Contoso Public team
- $publicTeam = Get-MgTeam -Filter "displayName eq 'Contoso Public'"
- $channel = Get-MgTeamChannel -TeamId $publicTeam.Id | Where-Object { $_.DisplayName -eq 'Analysis-Software-IT US' }
- # Build conversation member payload
- $payload = @{
- "@odata.type" = "#microsoft.graph.aadUserConversationMember"
- "roles" = @() # empty for normal member; use @("owner") only if intended
- } | ConvertTo-Json -Depth 5
- # POST member to the channel
- $uri = "https://graph.microsoft.com/v1.0/teams/$($publicTeam.Id)/channels/$($channel.Id)/members"
- $response = Invoke-MgGraphRequest -Method POST -Uri $uri -Body $payload -ContentType "application/json"
- if ($null -ne $response['displayname'])
- {write-log "$($response['displayname']) has been added to channel Analysis-Software-IT US"}
- else{write-log "$($user.name) was not added to Analysis-Software-IT US correctly"}
- $groupsAdded.Add("Analysis-Software-IT US (Channel in public team)") | Out-Null
- }
- else {
- Write-Log "Skipping Teams assignments for Non-Stop Analysis - user not yet in Azure AD"
- }
- }
- {$user.Department -eq "062 - Production" -and $user.country -eq "US"} {
- # Add groups for Production US
- Add-ADGroupMemberSafe -Identity "fol_production" -Members $user -Server $domain
- $groupsAdded.Add("fol_production") | Out-Null
- }
- {$user.Department -eq "087 - Engineering R&D" -and $user.country -eq "US"} {
- # Add groups for Engineering R&D US
- Add-ADGroupMemberSafe -Identity "map_h_engineer" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "fol_engineering" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "vpn" -Members $user -Server $domain
- $groupsAdded.Add("vpn") | Out-Null
- $groupsAdded.Add("map_h_engineer") | Out-Null
- $groupsAdded.Add("fol_engineering") | Out-Null
- # Teams operations require Azure AD user
- if ($azureAdUserFound -and $userInfo) {
- # Add to Engineering Team
- $engineeringTeam = Get-MgTeam -Filter "displayName eq 'Engineering'"
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId $engineeringTeam.Id
- $groupsAdded.Add("Engineering (Team)") | Out-Null
- }
- else {
- Write-Log "Skipping Teams assignments for Engineering R&D - user not yet in Azure AD"
- }
- }
- {$user.Department -eq "088 - Engineering Embedded" -and $user.country -eq "US"} {
- # Add groups for Engineering Embedded US
- Add-ADGroupMemberSafe -Identity "fol_engineering_Embedded" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "map_h_engineer" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "vpn" -Members $user -Server $domain
- $groupsAdded.Add("vpn") | Out-Null
- $groupsAdded.Add("fol_engineering_Embedded") | Out-Null
- $groupsAdded.Add("map_h_engineer") | Out-Null
- # Teams operations require Azure AD user
- if ($azureAdUserFound -and $userInfo) {
- # Add to Engineering Team
- $engineeringTeam = Get-MgTeam -Filter "displayName eq 'Engineering'"
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId $engineeringTeam.Id
- # Add to Embedded Systems Team
- $embeddedTeam = Get-MgTeam -Filter "displayName eq 'Embedded Systems'"
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId $embeddedTeam.Id
- $groupsAdded.Add("Engineering (Team)") | Out-Null
- $groupsAdded.Add("Embedded Systems (Team)") | Out-Null
- }
- else {
- Write-Log "Skipping Teams assignments for Engineering Embedded - user not yet in Azure AD"
- }
- }
- {$user.Department -eq "042 - Accounting" -and $user.country -eq "US"} {
- # Add groups for Accounting US
- Add-ADGroupMemberSafe -Identity "fol_accounting" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "fol_finance" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "vpn" -Members $user -Server $domain
- $groupsAdded.Add("vpn") | Out-Null
- $groupsAdded.Add("fol_accounting") | Out-Null
- $groupsAdded.Add("fol_finance") | Out-Null
- # Teams operations require Azure AD user
- if ($azureAdUserFound -and $userInfo) {
- # Add to Finance Team
- $financeTeam = Get-MgTeam -Filter "displayName eq 'Finance'"
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId $financeTeam.Id
- $groupsAdded.Add("Finance (Team)") | Out-Null
- }
- else {
- Write-Log "Skipping Teams assignments for Accounting - user not yet in Azure AD"
- }
- }
- {$user.Department -eq "052 - Procurement" -and $user.country -eq "US"} {
- # Add groups for Procurement US
- Add-ADGroupMemberSafe -Identity "fol_logistics" -Members $user -Server $domain
- $groupsAdded.Add("fol_logistics") | Out-Null
- # Teams operations require Azure AD user
- if ($azureAdUserFound -and $userInfo) {
- # Add to Procurement Team
- $procurementTeam = Get-MgTeam -Filter "displayName eq 'Procurement'"
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId $procurementTeam.Id
- $groupsAdded.Add("Procurement (Team)") | Out-Null
- }
- else {
- Write-Log "Skipping Teams assignments for Procurement - user not yet in Azure AD"
- }
- }
- {$user.Department -eq "049 - Quality" -and $user.country -eq "US"} {
- # Add groups for Quality US
- Add-ADGroupMemberSafe -Identity "fol_quality" -Members $user -Server $domain
- Add-ADGroupMemberSafe -Identity "vpn" -Members $user -Server $domain
- $groupsAdded.Add("vpn") | Out-Null
- $groupsAdded.Add("fol_quality") | Out-Null
- # Teams operations require Azure AD user
- if ($azureAdUserFound -and $userInfo) {
- # Add to Service Integrity Team
- $serviceIntegrityTeam = Get-MgTeam -Filter "displayName eq 'Service Integrity'"
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId $serviceIntegrityTeam.Id
- $groupsAdded.Add("service integrity (team)") | Out-Null
- }
- else {
- Write-Log "Skipping Teams assignments for Quality - user not yet in Azure AD"
- }
- }
- {$user.Department -eq "045 - Training" -and $user.country -eq "US"} {
- # Add groups for Training US
- Add-ADGroupMemberSafe -Identity "fol_training" -Members $user -Server $domain
- $groupsAdded.Add("fol_training") | Out-Null
- # Teams operations require Azure AD user
- if ($azureAdUserFound -and $userInfo) {
- # Add to Training Team
- $trainingTeam = Get-MgTeam -Filter "displayName eq 'Training'"
- New-MgGroupMemberSafe -DirectoryObjectId $userInfo.id -GroupId $trainingTeam.Id
- $groupsAdded.Add("Training (Team)") | Out-Null
- }
- else {
- Write-Log "Skipping Teams assignments for Training - user not yet in Azure AD"
- }
- }
- default {
- # No additional groups for other departments/countries
- }
- }
- }
- catch {
- $additionalGroupError = "Failed to assign additional department/country groups for user '$($user.Name)' ($($user.UserPrincipalName)). Department='$($user.Department)', Country='$($user.Country)'. Error: $($_.Exception.Message)"
- Write-Log $additionalGroupError -Level 'ERROR'
- continue
- #throw $additionalGroupError
- }
- Write-Log "Groups and teams added: $($groupsAdded -join ', ')"
- # Move user to appropriate OU based on country
- Write-Log "Moving user to appropriate OU based on country: $($user.Country)..."
- try {
- $destinationOU = $DestinationOUs[$user.Country]
- if ($null -eq $destinationOU) {
- $destinationOU = 'OU=Global,OU=user accounts,OU=root-users,DC=contoso,DC=local'
- Write-Log "Country '$($user.Country)' not in predefined OUs. Using Global OU." -Level 'INFO'
- }
- # Move the user
- Move-ADObject -Identity $user.DistinguishedName -TargetPath $destinationOU -Server $domain
- # Verify the move
- $movedUser = Get-ADUser -Identity $user.SamAccountName -Server $domain
- if ($movedUser.DistinguishedName -like "*$destinationOU") {
- Write-Log "✓ User successfully moved to: $destinationOU" -Level 'SUCCESS'
- }
- else {
- Write-Log "WARNING: User move verification failed. Current location: $($movedUser.DistinguishedName)" -Level 'WARNING'
- }
- }
- catch {
- Write-Log "ERROR moving user to destination OU: $($_.Exception.Message)" -Level 'ERROR'
- continue
- }
- #region LMS User Creation
- if ($null -ne $lmsToken) {
- Write-Log "Creating LMS account for user..."
- try {
- $lmsHeader = @{
- Authorization = $lmsToken.access_token
- "Content-Type" = "application/x-www-form-urlencoded"
- }
- $lmsUserBody = @{
- userid = $user.UserPrincipalName
- firstname = $user.GivenName
- lastname = $user.Surname
- email1 = $user.UserPrincipalName
- status = "A"
- }
- $lmsJson = ConvertTo-Json @($lmsUserBody)
- $null = Invoke-RestMethod -Uri "$lmsUrl/user" -Method Post -Headers $lmsHeader -Body "users=$lmsJson"
- Write-Log "✓ LMS account created/updated for $($user.UserPrincipalName)"
- }
- catch {
- Write-Log "WARNING: Failed to create LMS account for $($user.UserPrincipalName): $($_.Exception.Message)" -Level 'WARNING'
- $script:LmsWarnings += [PSCustomObject]@{
- UserPrincipalName = $user.UserPrincipalName
- DisplayName = $user.Name
- Issue = "Failed to create LMS account: $($_.Exception.Message)"
- }
- }
- }
- else {
- Write-Log "Skipping LMS account creation - API not connected"
- }
- #endregion
- Write-Log "User $($user.Name) processing completed successfully"
- # Track successful completion and store password for summary
- $script:UsersSuccessfullyProcessed++
- # Store password for summary email (only for successfully processed users)
- $script:UserPasswords += [PSCustomObject]@{
- DisplayName = $(if ([string]::IsNullOrWhiteSpace($user.DisplayName)) { $user.Name } else { $user.DisplayName })
- UserPrincipalName = $user.UserPrincipalName
- Title = $user.Title
- Department = $user.Department
- EmployeeNumber = $user.Employeenumber
- Password = $password
- }
- if ($user.Department -eq '046 - Field Training') {
- Write-Log "Skipping manager notification for Field Training user; details will be included in trainee setup summary email."
- }
- else {
- # Send notification email to manager
- Write-Log "Preparing manager notification email..."
- try {
- # Get manager information
- $manager = $null
- if ($user.Manager) {
- $manager = Get-ADUser -Identity $user.Manager -Properties EmailAddress -Server $domain -ErrorAction SilentlyContinue
- }
- $recipientEmails = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::OrdinalIgnoreCase)
- $isCanadaSV = ($user.Country -eq 'CA' -and $user.Department -eq '063 - SV')
- if ($isCanadaSV) {
- Write-Log "Special routing active for CA + 063 - SV user."
- $directorRecipients = Get-ADUser -Filter "Department -eq '077 - Canada' -and Title -like '*Director, Customer Operations*'" -Properties EmailAddress -Server $domain -ErrorAction SilentlyContinue
- foreach ($directorRecipient in $directorRecipients) {
- if (-not [string]::IsNullOrWhiteSpace($directorRecipient.EmailAddress)) {
- [void]$recipientEmails.Add($directorRecipient.EmailAddress)
- }
- }
- if ($manager -and $manager.EmailAddress) {
- [void]$recipientEmails.Add($manager.EmailAddress)
- Write-Log "Manager included in recipient list: $($manager.EmailAddress)"
- }
- }
- elseif ($manager -and $manager.EmailAddress) {
- [void]$recipientEmails.Add($manager.EmailAddress)
- Write-Log "Manager found: $($manager.Name) ($($manager.EmailAddress))"
- }
- if ($recipientEmails.Count -gt 0) {
- $recipientList = @($recipientEmails)
- $greetingName = if ($manager -and $manager.GivenName) { $manager.GivenName } else { 'Team' }
- # Build email body with user setup details
- $managerEmailBody = @"
- Hello $greetingName,
- This is an automated notification that a new user account has been created and configured.
- New User Details:
- - Name: $($user.Name)
- - Email: $($user.UserPrincipalName)
- - Department: $($user.Department)
- - Office: $($user.Office)
- - Country: $($user.Country)
- Temporary Password: $password
- (User will be required to change this password at first login)
- Account Setup Completed:
- - Office 365 licenses assigned
- - Active Directory groups configured
- - Microsoft Teams access granted
- - Email account activated
- The user should be able to log in and access their email and other resources shortly.
- IMPORTANT: Please provide the temporary password to the user securely. They will be required to change it upon first login.
- If you have any questions or need assistance, please contact IT Support.
- ---
- This is an automated message from the IT User Provisioning System.
- "@
- $managerEmailParams = @{
- To = $recipientList
- From = '[email protected]'
- Subject = "New User Account Created: $($user.Name)"
- Body = $managerEmailBody
- SmtpServer = $SmtpServer
- Port = $SmtpPort
- }
- <#
- if user has Country=CA and department is '063 - SV' then instead of only emailing
- the user's manager, email all users with the title of Director, Customer Operations in department 077 - Canada
- #>
- # COMMENTED OUT FOR TESTING - Uncomment when ready to send to managers
- Send-MailMessage @managerEmailParams
- Write-Log "Manager notification email prepared"
- Write-Log " Sent to: $($recipientList -join ', ')"
- }
- else {
- if ($isCanadaSV) {
- Write-Log "WARNING: No valid Director, Customer Operations recipients found in department 077 - Canada, and no manager email available. Notification not sent." -Level 'WARNING'
- }
- else {
- Write-Log "WARNING: Manager not found or manager has no email address. Notification not sent." -Level 'WARNING'
- }
- }
- }
- catch {
- Write-Log "ERROR preparing manager notification: $_" -Level 'ERROR'
- }
- }
- $operationsCoordinationEmail = switch ($true) {
- default { $null }
- }
- if ($operationsCoordinationEmail) {
- Write-Log "Preparing operations coordination notification email..."
- try {
- $operationsCoordinationBody = @"
- Hello Operations Coordination,
- This is an automated notification that a new field user account has been created and configured.
- New User Details:
- - Name: $($user.Name)
- - Email: $($user.UserPrincipalName)
- - Department: $($user.Department)
- - Office: $($user.Office)
- - Country: $($user.Country)
- Temporary Password: $password
- (User will be required to change this password at first login)
- Account Setup Completed:
- - Office 365 licenses assigned
- - Active Directory groups configured
- - Microsoft Teams access granted
- - Email account activated
- The user should be able to log in and access their email and other resources shortly.
- IMPORTANT: Please provide the temporary password to the user securely. They will be required to change it upon first login.
- If you have any questions or need assistance, please contact IT Support.
- ---
- This is an automated message from the IT User Provisioning System.
- "@
- $operationsCoordinationParams = @{
- To = $operationsCoordinationEmail
- From = '[email protected]'
- Subject = "New Field User Account Created: $($user.Name)"
- Body = $operationsCoordinationBody
- SmtpServer = $SmtpServer
- Port = $SmtpPort
- }
- Send-MailMessage @operationsCoordinationParams
- Write-Log "Operations coordination notification sent to: $operationsCoordinationEmail"
- }
- catch {
- Write-Log "ERROR sending operations coordination notification: $_" -Level 'ERROR'
- }
- }
- }
- Write-Log "`n========================================"
- Write-Log "Script completed. Attempted: $script:UsersProcessed | Successfully processed: $script:UsersSuccessfullyProcessed users."
- # Send CO Training summary email only if there are successfully processed CO Training users
- $coTrainingProcessed = $script:UserPasswords | Where-Object { $_.Department -eq '046 - CO Training' }
- if ($coTrainingProcessed.Count -gt 0) {
- Write-Log "Sending CO Training summary email..."
- $coTrainingPasswordList = $coTrainingProcessed | ForEach-Object {
- "- $($_.displayName) ($($_.UserPrincipalName)): $($_.Password)"
- }
- $coTrainingSummaryBody = @"
- New CO Training users created in this run:
- $($coTrainingPasswordList -join "`n")
- NOTE: Users must change password at first login.
- "@
- $coTrainingSummaryEmailParams = @{
- To = '[email protected]'
- From = '[email protected]'
- Subject = "CO Training New User Setup Summary: $($coTrainingProcessed.Count) users"
- Body = $coTrainingSummaryBody
- SmtpServer = $SmtpServer
- Port = $SmtpPort
- }
- try {
- Send-MailMessage @coTrainingSummaryEmailParams
- Write-Log "CO Training summary email sent successfully"
- }
- catch {
- Write-Log "ERROR sending CO Training summary email: $_" -Level 'ERROR'
- }
- }
- # Synchronize AD changes - local DC only (running on $env:COMPUTERNAME)
- if ($script:UsersSuccessfullyProcessed -gt 0) {
- Write-Log "`nSynchronizing Active Directory changes..."
- # Only trigger replication on the local DC - remote DCs require different credentials
- $localDC = $env:COMPUTERNAME + '.contoso.local'
- try {
- Write-Log "Triggering AD replication on local DC ($localDC)..."
- repadmin /syncall /AdeP | Out-Null
- Write-Log " $localDC - Replication triggered successfully"
- }
- catch {
- Write-Log " WARNING: Failed to trigger replication on ${localDC}: $($_.Exception.Message)" -Level 'WARNING'
- }
- # Note: Azure AD Connect sync runs on dc01.contoso.local. Changes will sync on the next scheduled cycle (typically 30 minutes)
- Write-Log "`nAzure AD Connect sync runs on dc01 and will process changes on the next scheduled cycle (typically 30 minutes)"
- Write-Log "Synchronization process completed"
- }
- # Send email notification only if users were successfully processed
- if ($script:UsersSuccessfullyProcessed -gt 0) {
- Write-Log "Sending email notification..."
- # Build certificate warning section
- $certWarningSectionHtml = ""
- if ($null -ne $script:CertWarning) {
- $certWarningText = [System.Net.WebUtility]::HtmlEncode($script:CertWarning)
- $certWarningSectionHtml = @"
- <div style='border: 1px solid #cc0000; background-color: #fff3f3; padding: 10px; margin: 12px 0;'>
- <strong>CERTIFICATE WARNING</strong><br />
- $certWarningText<br />
- <strong>ACTION REQUIRED</strong>
- </div>
- "@
- }
- # Build password list section
- $passwordSectionHtml = ""
- if ($script:UserPasswords.Count -gt 0) {
- $passwordTableRows = $script:UserPasswords | ForEach-Object {
- $displayName = [System.Net.WebUtility]::HtmlEncode($(if ([string]::IsNullOrWhiteSpace($_.DisplayName)) { '' } else { $_.DisplayName }))
- $upn = [System.Net.WebUtility]::HtmlEncode($(if ([string]::IsNullOrWhiteSpace($_.UserPrincipalName)) { '' } else { $_.UserPrincipalName }))
- $title = [System.Net.WebUtility]::HtmlEncode($(if ([string]::IsNullOrWhiteSpace($_.Title)) { '' } else { $_.Title }))
- $department = [System.Net.WebUtility]::HtmlEncode($(if ([string]::IsNullOrWhiteSpace($_.Department)) { '' } else { $_.Department }))
- $tempPassword = [System.Net.WebUtility]::HtmlEncode($(if ([string]::IsNullOrWhiteSpace($_.Password)) { '' } else { $_.Password }))
- $employeeNumber = [System.Net.WebUtility]::HtmlEncode($(if ([string]::IsNullOrWhiteSpace($_.EmployeeNumber)) { '' } else { $_.EmployeeNumber }))
- "<tr><td style='border: 1px solid #cccccc; padding: 6px;'>$displayName</td><td style='border: 1px solid #cccccc; padding: 6px;'>$upn</td><td style='border: 1px solid #cccccc; padding: 6px;'>$title</td><td style='border: 1px solid #cccccc; padding: 6px;'>$department</td><td style='border: 1px solid #cccccc; padding: 6px;'>$employeeNumber</td><td style='border: 1px solid #cccccc; padding: 6px;'>$tempPassword</td></tr>"
- }
- $passwordSectionHtml = @"
- <h3>User Passwords (Temporary)</h3>
- <table style='border-collapse: collapse; width: 100%;'>
- <thead>
- <tr>
- <th style='border: 1px solid #cccccc; padding: 6px; text-align: left;'>Display Name</th>
- <th style='border: 1px solid #cccccc; padding: 6px; text-align: left;'>UPN</th>
- <th style='border: 1px solid #cccccc; padding: 6px; text-align: left;'>Title</th>
- <th style='border: 1px solid #cccccc; padding: 6px; text-align: left;'>Department</th>
- <th style='border: 1px solid #cccccc; padding: 6px; text-align: left;'>Employee Number</th>
- <th style='border: 1px solid #cccccc; padding: 6px; text-align: left;'>Password</th>
- </tr>
- </thead>
- <tbody>
- $($passwordTableRows -join "`n")
- </tbody>
- </table>
- <p><strong>NOTE:</strong> Users must change password at first login.<br />Passwords have been sent to managers or trainee setup mailbox as applicable.</p>
- "@
- }
- # Build LMS warnings section
- $lmsSectionHtml = ""
- if ($script:LmsWarnings.Count -gt 0) {
- $lmsRows = $script:LmsWarnings | ForEach-Object {
- $escapedName = [System.Net.WebUtility]::HtmlEncode($_.DisplayName)
- $escapedUPN = [System.Net.WebUtility]::HtmlEncode($_.UserPrincipalName)
- $escapedIssue = [System.Net.WebUtility]::HtmlEncode($_.Issue)
- "<tr><td style='border:1px solid #cc0000; padding:6px;'>$escapedName</td><td style='border:1px solid #cc0000; padding:6px;'>$escapedUPN</td><td style='border:1px solid #cc0000; padding:6px;'>$escapedIssue</td></tr>"
- }
- $lmsSectionHtml = @"
- <div style='border:2px solid red; padding:12px; background-color:#fff3f3; margin:12px 0;'>
- <strong style='color:red; font-size:12pt;'>LMS Account Failures</strong>
- <p style='margin:6px 0; color:red;'>The following users could not be created in LMS and require manual attention:</p>
- <table style='border-collapse:collapse; width:100%;'>
- <thead>
- <tr style='background-color:#ffcccc;'>
- <th style='border:1px solid #cc0000; padding:6px; text-align:left;'>Display Name</th>
- <th style='border:1px solid #cc0000; padding:6px; text-align:left;'>UPN</th>
- <th style='border:1px solid #cc0000; padding:6px; text-align:left;'>Error</th>
- </tr>
- </thead>
- <tbody>
- $($lmsRows -join "`n")
- </tbody>
- </table>
- </div>
- "@
- }
- $logContentHtml = [System.Net.WebUtility]::HtmlEncode(($script:LogMessages -join "`r`n"))
- $EmailBody = @"
- <html>
- <body style='font-family: Segoe UI, Arial, sans-serif; font-size: 13px;'>
- <h2>Automated User Setup Script Report</h2>
- <p>
- <strong>Run Date:</strong> $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')<br />
- <strong>Server:</strong> $([System.Net.WebUtility]::HtmlEncode($env:COMPUTERNAME))<br />
- <strong>Users Attempted:</strong> $script:UsersProcessed<br />
- <strong>Users Successfully Processed:</strong> $script:UsersSuccessfullyProcessed
- </p>
- $certWarningSectionHtml
- $lmsSectionHtml
- $passwordSectionHtml
- <h3>Detailed Log</h3>
- <pre style='background-color: #f7f7f7; border: 1px solid #cccccc; padding: 10px; white-space: pre-wrap;'>$logContentHtml</pre>
- </body>
- </html>
- "@
- $EmailParams = @{
- To = $EmailTo
- From = $EmailFrom
- Subject = "Auto-User Setup: $script:UsersSuccessfullyProcessed/$script:UsersProcessed users successfully processed on $env:COMPUTERNAME"
- Body = $EmailBody
- BodyAsHtml = $true
- SmtpServer = $SmtpServer
- Port = $SmtpPort
- }
- try {
- Send-MailMessage @EmailParams
- Write-Log "Email notification sent successfully"
- }
- catch {
- Write-Log "ERROR sending email notification: $_" -Level 'ERROR'
- }
- }
Advertisement
Add Comment
Please, Sign In to add comment