Using PowerShell to change the UPN Suffix

For decades, it’s been a best practice to configure your corporate domain with a non-internet-rotatable .local domain (e.g. example.local instead of example.com). But in the modern everything-is-connected-to-the-internet age, this appears to be falling out of fashion.

When “Preparing to provision users through directory synchronization to Office 365,” you’ll eventually realize that you’ll need to follow “How to prepare a non-routable domain (such as .local domain) for directory synchronization.”

As the KB states, you can use Windows PowerShell to change the UPN suffix for all users. In addition to the cmdlets Microsoft suggests using to change all example.local suffixes to example.com, I added a few PowerShell one liners of my own.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Get users with example Exchange mailboxes whose UPN suffix is example.local
Get-ADUser -Filter 'UserPrincipalName -like "*example.local" -AND Mail -like "*@example*"'
 
# Get users with example Exchange mailboxes whose UPN suffix is example.org
Get-ADUser -Filter 'UserPrincipalName -like "*example.org" -AND Mail -like "*@example*"'
 
# Count how many users with example Exchange mailboxes whose UPN suffix is example.local
Get-ADUser -Filter 'UserPrincipalName -like "*example.local" -AND Mail -like "*@example*"' | Measure-Object
 
# Count how many users with example Exchange mailboxes whose UPN suffix is example.org
Get-ADUser -Filter 'UserPrincipalName -like "*example.org" -AND Mail -like "*@example*"' | Measure-Object
 
# Change 10 random users with example Exchange mailboxes whose UPN suffix is example.local to example.org
Get-ADUser -Filter 'UserPrincipalName -like "*example.local" -AND Mail -like "*@example*"' | Get-Random -Count 10 | foreach { Set-ADUser $_ -UserPrincipalName "$($_.samaccountname)@example.org" }
 
# Change 10 random users with example Exchange mailboxes whose UPN suffix is example.org to example.local
Get-ADUser -Filter 'UserPrincipalName -like "*example.org" -AND Mail -like "*@example*"' | Get-Random -Count 10 | foreach { Set-ADUser $_ -UserPrincipalName "$($_.samaccountname)@example.local" }
 
# Change all users with example Exchange mailboxes whose UPN suffix is example.local to example.org
Get-ADUser -Filter 'UserPrincipalName -like "*example.local" -AND Mail -like "*@example*"' | foreach { Set-ADUser $_ -UserPrincipalName "$($_.samaccountname)@example.org" }
 
# Change all users with example Exchange mailboxes whose UPN suffix is example.org to example.local
Get-ADUser -Filter 'UserPrincipalName -like "*example.org" -AND Mail -like "*@example*"' | foreach { Set-ADUser $_ -UserPrincipalName "$($_.samaccountname)@example.local" }
 
# Change all users whose UPN suffix is example.local to example.org
Get-ADUser -Filter 'UserPrincipalName -like "*example.local"' | foreach { Set-ADUser $_ -UserPrincipalName "$($_.samaccountname)@example.org" }
 
# Change all users whose UPN suffix is example.org to example.local
Get-ADUser -Filter 'UserPrincipalName -like "*example.org"' | foreach { Set-ADUser $_ -UserPrincipalName "$($_.samaccountname)@example.local" }

Since I was a little hesitant how this change might effect our users and environment, I chose to create a scheduled task that ran one an hour to select and change 10 random user accounts. Once comfortable, I simply changed everyone.

Chad Cameron plays Bob Ross

My cousin Chad Cameron recently had the opportunity to channel and play artist Bob Ross in a fun comedy parody that demonstrated the use of Adobe Creative Cloud and the iPad Pro. I understand that it was done with the cooperation of the Bob Ross Foundation.

Nice job Chad. I enjoyed all four videos — along with the mighty mountains and evergreen. I can see that you “just had fun with it.”

Mail Merge Email via PowerShell’s Send-MailMessage

Here’s a script that will help you learn how to use PowerShell to conduct a mail merge via email. While I once used Microsoft Excel, Word, and Outlook to perform this task; PowerShell makes the process better.

My script will even create an example CSV file that contains three columns: Email, First Name, Last Name. When you create your own file, it’s important to have the column headers, for PowerShell will read them and let you target their data.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# This script will perform a mail merge for email from a CSV file
# The CSV file must contain a column of email addresses, other columns with other data are optional
# Created by Jason Pearce, 2016 February
 
# ####################
# BEGIN Example CSV File (optional)
# ####################
 
# CSV Example: A CSV file that could serve as an example (maybe replace with [email protected] to test)
$CSVExample = @"
Email,FirstName,LastName
[email protected],Aaron,Anders
[email protected],Betty,Blue
[email protected],Charlie,Cook
"@
 
# CSV File: Optionally create a CSV file to play with
$CSVExample = Out-File -FilePath "C:\temp\example-csv-file.csv"
 
# ####################
# END Example CSV file (optional)
# ####################
 
# ####################
# BEGIN Variables 
# ####################
 
# From: Name and email of sender
$EmailFrom = "Your Name <[email protected]>"
 
# Reporting: Report on Success and Failure (optional)
$EmailDeliveryNotificationOption = "onSuccess, onFailure"
 
# Server: Your Exchange or SMTP server
$EmailSMTPserver = "smtp.example.com"
 
# Users: Tab-delimited list with columns named Name, Email, SamAccountName
$SourcePath = "C:\temp\example-csv-file.csv"
 
# Import: Import the comma-delimited list of users (if tab-delimited, add '-Delimiter "`t"')
$Users = Import-Csv -Path $SourcePath
 
# ####################
# END Variables
# ####################
 
# Begin Loop: Do the following with each row of the file you imported, referencing columns by their header
foreach ($User in $Users) {
 
# To: User's email address
$EmailTo = $User.Email
 
# Subject: Email subject (may merge variables)
$EmailSubject = "A personalized example for " + $User.FirstName + " " + $User.LastName + "."
 
# Body: Email body, with HTML formatting
$EmailBody = "<!DOCTYPE html PUBLIC ""-//W3C//DTD XHTML 1.0 Transitional//EN"" ""https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"">"
$EmailBody += "<html xmlns=""https://www.w3.org/1999/xhtml""><head>"
$EmailBody += "<meta http-equiv=""Content-Type"" content=""text/html; charset=UTF-8"" />"
$EmailBody += "<meta name=""viewport"" content=""width=device-width, initial-scale=1.0""/>"
$EmailBody += "<title>" + $EmailSubject + "</title>"
$EmailBody += "</head><body bgcolor=""#FFFFFF"" style=""font-family: sans-serif; color: #000000"">"
$EmailBody += "<p>Dear " + $User.FirstName + ":</p>"
$EmailBody += "<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>"
$EmailBody += "<p><ul><li>Your First Name: <strong>" + $User.FirstName + "</strong></li>"
$EmailBody += "<li>Your Last Name: <strong>" + $User.LastName + "</strong></li>"
$EmailBody += "<li>Your Email Address: <strong>" + $User.Email + "</strong></li></ul></p>"
$EmailBody += "<p>Phasellus nec sapien sit amet mi maximus venenatis.</p>"
$EmailBody += "<p>Sincerely,</p>"
$EmailBody += "<p>Your Name</p>"
$EmailBody += "</body></html>"
 
# Merge: Conduct the email merge, sending emails (remove -WhatIf)
Send-MailMessage -To $EmailTo -From $EmailFrom -Subject $EmailSubject -Body $EmailBody -BodyAsHTML -SmtpServer $EmailSmtpServer -DeliveryNotificationOption $EmailDeliveryNotificationOption
 
}
# End Loop and Script

Exchange Get-MailboxStatistics with user Alias

Exchange has a nice PowerShell cmdlet called Get-MailboxStatistics, which by default, will output DisplayName, ItemCount, StorageLimitStatus, and LastLogonTime — like so:

1
2
3
4
5
[PS] C:\temp>Get-Mailbox -Identity user@domain.org | Get-MailboxStatistics | Format-Table -AutoSize
 
DisplayName   ItemCount StorageLimitStatus       LastLogonTime
-----------   --------- ------------------       -------------
Last, First 29665             NoChecking 2/4/2016 9:57:33 PM

Well, if you want to easily compare results to Users in Active Directory, having the SamAccountName or Alias would be nice. But the Get-MailboxStatistics cmdlet only outputs the DisplayName, not the SamAccountName or Alias.

Thanks to guidance from Robbie Roberts and Gavin Woodall, I came up with the following PowerShell script that will output the user’s Alias/SamAccountName, DisplayName, ItemCount (in MB), and LastLogonTime in a pipe-delimited format.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Get a list of all Exchange users
$Users = Get-Mailbox -ResultSize unlimited
 
# Do the following for each user
foreach ($User in $Users)
{
 
	# Get the user's alias and display name
	$UserAlias = $User.Alias
	$UserDisplayName = $User.DisplayName
 
	# Get the user's mailbox statistics on ItemCount
	$UserItemCount = Get-MailboxStatistics $UserDisplayName | Select-Object ItemCount
	$UserItemCount = $UserItemCount.ItemCount
 
	# Get the user's mailbox statistics on TotalItemSize, format to just MB
	$UserTotalItemSize = Get-MailboxStatistics $UserDisplayName | Select-Object TotalItemSize
	$UserTotalItemSize = $UserTotalItemSize.TotalItemSize.Value.toMB()
 
	# Get the user's mailbox statistics on LastLogonTime
	$UserLastLogonTime = Get-MailboxStatistics $UserDisplayName | Select-Object LastLogonTime
	$UserLastLogonTime = $UserLastLogonTime.LastLogonTime
 
	# Combine results into a pipe-delimited string
	$OutputString = $UserAlias + "|" + $UserDisplayName + "|" + $UserItemCount + "|" + $UserTotalItemSize + "|" + $UserLastLogonTime
 
	# Display the results on the screen to show progress
	Write-Host $OutputString
 
	# Write the results to a .csv file
	$OutputString | Out-File C:\temp\LastLogonWithAlias.csv -Append
 
}

The result looks something like this:

adamsa|Adams, Aaron|675|87|02/04/2016 15:05:50
bealb|Beal, Britt|440|56|02/02/2016 11:53:21
chandlerc|Chandler, Chris|1867|128|08/19/2014 17:17:02
duked|Duke, David|518|40|01/22/2016 22:22:41

I can now use Microsoft Excel to Covert Text to Columns using the pipe as a delimiter.

Using PowerShell to disable and move user and computer accounts

Here are two PowerShell scripts that I wrote and use to disable old Active Directory user or computer accounts.

Typically I use the Microsoft Assessment and Planning Toolkit to have it identify “Days Since Last Activity” for both Active Directory Users and Devices. I then copy the list of Users or Devices I wish to target, save them to a .txt file, and use these scripts to disable the object and move it to an OU for safe keeping.

On thing I like about these scripts is that I export via Export-Csv a .csv file the before and after settings of the Active Directory objects I’m modifying. This gives me the ability to undo my changes if needed.

Disable and Move AD User Accounts from a File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# This script will disable and move Active Directory User Accounts
# A list of usernames must be provided to the script as a plain text file
# An AD OU must also be created to be the target when your want the user accounts moved to
# Created by Jason Pearce, 2016 February
 
# ####################
# BEGIN Variables
# ####################
 
# Path to a .txt file containing a list of usernames you wish to disable
$FileListOfUsers = "\\server\Scripts\ActiveDirectory\list-of-users-to-disable-from-2015.txt"
 
# Active Directory OU (unique name) that users will move to
$MoveToOU = "LastActiveUsers2015"
 
# Logs Path: The path to write logs
$LogPath = "\\server\Scripts\ActiveDirectory\Logs"
 
# ####################
# END Variables
# ####################
 
# Import Module: Import the Powershell Active Directory module
Import-Module ActiveDirectory
 
# TimeStamp: Create a timestamp for use as part of a directory or file name
$TimeStampBefore = Get-Date -Format s | foreach {$_ -replace ":", "-"}
 
# Log Folder: Create a log folder IF it does not already exist
IF ( -Not (Test-Path -Path $LogPath)) {New-Item -Path $LogPath -ItemType Directory}
 
# Load Users: Load list of users into a variable
$ListOfUsers = Get-Content $FileListOfUsers
 
# Document Before: Document user settings before making changes
$CsvBeforePath = $LogPath+'\DisableUsers-'+$MoveToOU+'-'+$TimeStampBefore+'-before.csv'
$ListOfUsers | Get-ADUser | Export-Csv -Path $CsvBeforePath
 
# Disable Users: Disable these Active Directory user accounts (remove -WhatIf)
$ListOfUsers | Get-ADUser | Disable-ADAccount -WhatIf
 
# Pause: Pause 30 seconds for Active Directory to replicate changes
Start-Sleep -s 30
 
# Move Users: Move these Active Directory user accounts (remove -WhatIf)
$ListOfUsers | Get-ADUser | Move-ADObject -TargetPath (Get-ADOrganizationalUnit -Filter 'Name -eq $MoveToOU') -WhatIf
 
# Pause: Pause 30 seconds for Active Directory to replicate changes
Start-Sleep -s 30
 
# TimeStamp: Create a timestamp for use as part of a directory or file name
$TimeStampAfter = Get-Date -Format s | foreach {$_ -replace ":", "-"}
 
# Document users after making changes
$CsvAfterPath = $LogPath+'\DisableUsers-'+$MoveToOU+'-'+$TimeStampAfter+'-after.csv'
$ListOfUsers | Get-ADUser | Export-Csv -Path $CsvAfterPath

Disable and Move AD Computer Accounts from a File

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# This script will disable and move Active Directory Computer Accounts
# A list of computernames must be provided to the script as a plain text file
# An AD OU must also be created to be the target when your want the computer accounts moved to
# Created by Jason Pearce, 2016 February
 
# ####################
# BEGIN Variables
# ####################
 
# Path to a .txt file containing a list of usernames you wish to disable
$FileListOfComputers = "\\server\Scripts\ActiveDirectory\list-of-computers-to-disable-from-2015.txt"
 
# Active Directory OU (unique name) that users will move to
$MoveToOU = "LastActiveComputers2015"
 
# Logs Path: The path to write logs
$LogPath = "\\server\Scripts\ActiveDirectory\Logs"
 
# ####################
# END Variables
# ####################
 
# Import Module: Import the Powershell Active Directory module
Import-Module ActiveDirectory
 
# TimeStamp: Create a timestamp for use as part of a directory or file name
$TimeStampBefore = Get-Date -Format s | foreach {$_ -replace ":", "-"}
 
# Log Folder: Create a log folder IF it does not already exist
IF ( -Not (Test-Path -Path $LogPath)) {New-Item -Path $LogPath -ItemType Directory}
 
# Load Computers: Load list of computers into a variable
$ListOfComputers = Get-Content $FileListOfComputers
 
# Document Before: Document computers settings before making changes
$CsvBeforePath = $LogPath+'\DisableComputers-'+$MoveToOU+'-'+$TimeStampBefore+'-before.csv'
$ListOfComputers | Get-ADComputer | Export-Csv -Path $CsvBeforePath
 
# Disable Computers: Disable these Active Directory computer accounts (remove -WhatIf)
$ListOfComputers | Get-ADComputer | Disable-ADAccount -WhatIf
 
# Pause: Pause 30 seconds for Active Directory to replicate changes
Start-Sleep -s 30
 
# Move Users: Move these Active Directory computer accounts (remove -WhatIf)
$ListOfComputers | Get-ADComputer | Move-ADObject -TargetPath (Get-ADOrganizationalUnit -Filter 'Name -eq $MoveToOU') -WhatIf
 
# Pause: Pause 30 seconds for Active Directory to replicate changes
Start-Sleep -s 30
 
# TimeStamp: Create a timestamp for use as part of a directory or file name
$TimeStampAfter = Get-Date -Format s | foreach {$_ -replace ":", "-"}
 
# Document computers after making changes
$CsvAfterPath = $LogPath+'\DisableComputers-'+$MoveToOU+'-'+$TimeStampAfter+'-after.csv'
$ListOfComputers | Get-ADComputer | Export-Csv -Path $CsvAfterPath