In our scenario we had to keep creating new hires in the legacy domain so that we could get sidHistory, until we can say that we’re done and all things have been migrated.
I did it all in one script, but it’s a little big. I like it cause I did some new (to me) stuff like menus and consolidation. It’s what I wanted to do with the rest of the migration scripts, but just didn’t quite work right. If you’re migrating 100 people at once, you want to verify that everything in step 1 has worked correctly before going on to step 2.
Below is the script. I’ll try to explain as we go, but most of it is just re-doing of things we’ve done before.
This script also assumes that you still have your daily sync scripts going from source to target domain and that you’re waiting at least a day between new hire creation in legacy and migration to target. If not, you’ll need to run the prepare-mailboxmove script manually to create the MEU
$warningpreference='silentlycontinue'
## Function to look in the source directory for all CSV files. Sort by most recent date and return the last 10.
## This way we can manipulate that file and use it for the rest of the script
function SourceFileMenu()
{
$sourceFiles = Get-ChildItem $sourcefolder\*.csv | Sort LastWriteTime -Descending | select name, lastwritetime -first 10
Write-Host ("=" * 80)
Write-Host "Available migration source files"
Write-Host ("-" * 80)
[int]$optionPrefix = 1
# Create menu list
foreach ($option in $sourceFiles)
{
if ($displayProperty -eq $null)
{
Write-Host ("{0,3}: {1,-40} {2}" -f $optionPrefix,$option.Name,$option.lastWriteTime)
}
else
{
Write-Host ("{0,3}: {1}" -f $optionPrefix,$option.$displayProperty)
}
$optionPrefix++
}
$maxOptions = $optionPrefix - 1
Write-Host ("-" * 80)
$response = 0
while($response -lt 1 -or $response -gt $sourcefiles.count)
{
[int]$response = Read-Host "Select a source file [1 - $maxOptions]"
}
$val = $null
if ($response -gt 0 -and $response -le $sourceFiles.Count)
{
$val = $sourceFiles[$response-1]
}
$pattern = [regex] "(.*)\.csv"
##save our selection into a variable and return it to the caller
$sourcename = $pattern.matches($val.Name) | foreach {$_.groups[1].value}
return $sourcename
}
##Pause function. Mainly so that the script will still work if you're using the ISE
Function Pause ($Message = "Press any key to continue . . . ") {
If ($psISE) {
# The "ReadKey" functionality is not supported in Windows PowerShell ISE.
$Shell = New-Object -ComObject "WScript.Shell"
$Button = $Shell.Popup("Click OK to continue.", 0, "Script Paused", 0)
Return
}
##ISE related stuff
Write-Host -NoNewline $Message
$Ignore =
16, # Shift (left or right)
17, # Ctrl (left or right)
18, # Alt (left or right)
20, # Caps lock
91, # Windows key (left)
92, # Windows key (right)
93, # Menu key
144, # Num lock
145, # Scroll lock
166, # Back
167, # Forward
168, # Refresh
169, # Stop
170, # Search
171, # Favorites
172, # Start/Home
173, # Mute
174, # Volume Down
175, # Volume Up
176, # Next Track
177, # Previous Track
178, # Stop Media
179, # Play
180, # Mail
181, # Select Media
182, # Application 1
183 # Application 2
While ($KeyInfo.VirtualKeyCode -Eq $Null -Or $Ignore -Contains $KeyInfo.VirtualKeyCode) {
$KeyInfo = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
}
Write-Host
}
##Function for moving the objects to the correct OU
function CheckSite ($Site){
## Base OU where you have your users. This assumes you separate everything by region.
$BaseUserOU="domain.com/Users OU/"
switch ($Site){
"Site1 {
$OU="/CO"
$UserOU=$BaseUserOU + $OU
}
"Site2" {
$OU="/NC"
$UserOU=$BaseUserOU + $OU
}
"Site3 {
$ou="/MA"
$UserOU=$BaseUserOU + $OU
}
}
## Return the OU to the caller
return $userOU
}
## MainMenu builder
function mainMenu() {
Clear-Host;
#… Present the Menu Options
Write-Host “`n`New Hire Migration Process `n” -ForegroundColor Magenta
Write-Host “`t`tThese steps are in the order they need to be run.” -Fore Cyan
Write-Host “`t`tPlease run them sequentially and ensure each step finishes successfully” -Fore Cyan
Write-Host “`t`tbefore continuing on.`n” -Fore Cyan
Write-Host “`t`t`t1. Generate Include Files” -Fore Cyan
Write-Host “`t`t`t2. Enter Passwords” -Fore Cyan
write-host "`t`t`t3. Check for MEU" -fore cyan
Write-Host “`t`t`t4. ADMT” -Fore Cyan
Write-Host “`t`t`t5. Mailbox Move” -Fore Cyan
Write-Host "`t`t`t6. Set migrated attribute" -ForegroundColor Cyan
write-host "`t`t`t7. Set UPN and Displayname" -ForegroundColor Cyan
Write-Host "`t`t`t8. Disable Lync in source" -ForegroundColor Cyan
Write-Host "`t`t`t9. Enable Lync in target" -ForegroundColor Cyan
Write-Host "`t`t`t10. Set SIP in source -ForegroundColor Cyan
Write-Host "`t`t`t11. Move User to correct OU" -ForegroundColor Cyan
write-host "`t`t`t12. Clean up Files" -fore cyan
Write-Host “`t`t`tQ. for Quit`n” -Fore Cyan
}
##Variables. Add our snapin, decare paths, etc.
Add-PSSnapin Quest.ActiveRoles.ADManagement -erroraction SilentlyContinue
$Path="D:\newhire\includes"
$currfolder = Split-Path -parent $MyInvocation.MyCommand.Definition
$sourceFolder = $currfolder + "/Includes"
##Present the menu as a do/while, thus ensuring we only exit when we want
do {
## Call menu and store the keystroke into a var
mainMenu;
write-host "You last chose number " $input
$input = Read-Host "Enter a number for an option"
##Perform necessary option based on input
switch ($input) {
"1" { ## GEnerate include files for various scripts. renames the include file chosen to userincludes.csv and prepares the admt include
$IncludeFile=SourceFileMenu
$IFile=$includeFile + ".csv"
copy-item $sourcefolder\$IFile -destination $sourcefolder"\UserIncludes.csv"
$output=@()
import-csv $path\Userincludes.csv| ForEach-Object {
$obj = New-Object PSObject | Select-Object SourceName, TargetRDN, TargetUPN
$obj.SourceName = $_.sourceName
$obj.TargetRDN = $_.TargetRDN
$obj.TargetUPN = $_.TargetUPN
$output += $obj
}
write-host "...Generating Files..." -foregroundcolor red
$output|export-csv $path\"ADMTUserInclude.csv" -notypeinformation
Pause
}
"2" { ## Loads the parameter input. Prompts for credentials
write-host "Input target Creds" -backgroundcolor red -foregroundcolor white
$LocalCredentials=get-credential
write-host "Input source creds" -backgroundcolor red -foregroundcolor white
$RemoteCredentials=get-credential
$ImportFile="D:\newhire\includes\UserIncludes.csv"
$SourceDc="sourcedc1.sourcedomain.com"
$TargetDC="targetdc1.targetdomain.com"
$TargetDeliveryDomain="targetdomain.com"
$ExchURI="http://targetex1.targetdomain.com/powershell"
$LyncURI="https://registrarpool.targetdomain.com/ocspowershell"
$LyncFEURI="https://targetfe.targetdomain.com/ocspowershell"
$LyncFESourceURI="https://sourcefe.sourcedomain.com/ocspowershell"
$LyncURISource="https://sourcelyncdir.sourcedomain.com/ocspowershell"
$SourceDomain="sourcedomain.com"
$TargetDomain="targetdomain.com"
$RegistrarPool="registrarpool.targetdomain.com"
$TargetOU="Target/OU"
$import=import-csv $ImportFile
##Create Lync sessions for source and target
$LyncSessionSource=New-PSSession -connectionuri $LyncURISource -credential $RemoteCredentials
$LyncSession=New-PSSession -connectionuri $LyncURI -credential $LocalCredentials
write-host "Variables Loaded"
Pause
}
"3" { ##Check for existence of MEU. We can't do a migration if meu doesn't exist
foreach ($item in $Import){
write-host "Checking if user is enabled for UM in source " $item.smtp -foregroundcolor yellow
##check user attributes to see if they're enabled for UM. They shouldn't be, but just in case
$CheckUM=get-qaduser -service $SourceDC -identity $item.sourcename -includedProperties msExchUMRecipientDialPlanLink
$CheckDial=$checkUM.msExchUMRecipientDialPlanLink
if ($CheckDial){
write-host $item.smtp "is enabled for Unified Messaging. Please go disable and come back and rerun script" -fore yellow
$input="Q"
}
}
##Create Sessions
## Exchange sessions throw an error if it takes too long to get back to them, so we have to create and delete them as we go
$ExchSession=New-PSSession -ConfigurationName Microsoft.Exchange -connectionuri $ExchURI -credential $LocalCredentials
import-pssession $ExchSession|out-null
## Check if there's an MEU in target domain. If not, exit
foreach ($item in $import){
$aUser=get-mailuser $item.smtp
if (!$aUser){write-host "No MEU exists for " $item.smtp "please exit and run the Prepare script manually" -fore yellow
$input="Q"
}
if ($aUser){
##We've had some weird issues where the email address policy isn't applying immediately, so just in case let's go ahead and turn it off and back on for the users.
## We wait 30 seconds because otherwise the script goes too fast.
write-host "MEU Exists for " $item.smtp
write-host "Disabling EaP"
start-sleep -s 30
set-mailuser -identity $item.smtp -emailAddressPolicyenabled $false
write-host "enabling EAP"
start-sleep -s 30
set-mailuser -identity $item.smtp -emailaddresspolicyenabled $true
}
}
remove-pssession $Exchsession
pause
}
"4" { ## ADMT
## Can't be done via script because ADMT won't migrate the sid unless we have it installed on a DC or launch the GUI. Since that's the whole point we launch the GUI to do the ADMT
& C:\Windows\ADMT\migrator.msc
Pause
}
"5" { ## Mailbox Move
##Create Sessions
$ExchSession=New-PSSession -ConfigurationName Microsoft.Exchange -connectionuri $ExchURI -credential $LocalCredentials
import-pssession $ExchSession|out-null
foreach ($User in $import){
New-MoveRequest -Identity $user.smtp -domaincontroller $TargetDC -RemoteLegacy -RemoteGlobalCatalog $SourceDC -RemoteCredential $RemoteCredentials -TargetDeliveryDomain $TargetDeliveryDomain -baditemlimit 50 -warningaction silentlycontinue
}
## Rather than go check manually in exchange for move status, let's just do it all here. Wait 30 seconds between tries.
## Even with no data in the mailbox it still takes about 5 minutes to do the process
foreach ($User in $import){
do {
start-sleep -s 30
$a=get-moverequeststatistics -identity $user.smtp
clear-host
write-host "Getting move request statistics for " $user.smtp -fore yellow
write-host "`t`t`t " $a.status -fore cyan
}
until ($a.status -eq "Completed")
}
remove-pssession $ExchSession
Pause
}
"6" { ## Set attribute for reporting
foreach ($item in $Import){
write-host "Attribute being set for " $item.sourcename -foregroundcolor yellow
set-qaduser -service $SourceDC -identity $item.sourcename -objectAttributes @{"extensionattribute4"="MigratedToCorp"}
}
Pause
}
"7" { ## Set UPN and displayname, enable AS
$ExchSession=New-PSSession -ConfigurationName Microsoft.Exchange -connectionuri $ExchURI -credential $LocalCredentials
import-pssession $ExchSession|out-null
foreach ($item in $Import){
$UPN=$item.newupn+"@csgicorp.com"
$AS=$item.ActiveSync.ToUpper()
write-host "Setting UPN and AS for " $item.displayname -foregroundcolor yellow
set-user -identity $item.displayname -displayname $item.displayname -userprincipalname $UPN
if ($AS -eq "YES"){set-casmailbox -identity $item.smtp -activesyncenabled $true}
}
remove-pssession $ExchSession
Pause
}
"8" { ## Disable Lync in source
import-pssession $LyncSessionSource
foreach ($item in $import){
write-host "Disabling Lync for " $item.olddisplayname -foregroundcolor yellow
disable-csuser -identity $item.olddisplayname
}
remove-pssession $LyncSessionSource
## We add a delay otherwise it goes too fast
start-sleep 60
Pause
}
"9" { ## enable lync in target
import-pssession $LyncSession
foreach ($item in $import){
enable-csuser -identity $item.displayname -registrarpool $registrarpool -sipaddresstype EmailAddress
write-host "Enabling Lync for: " $item.displayname -foregroundcolor yellow
}
remove-pssession $LyncSession
pause
}
"10" { ##Set sip attribute in source
ForEach ($item in $import){
write-host "Adding source SIP for " $item.sourcename -foregroundcolor yellow
$NewSIP="sip:" + $item.smtp
set-qaduser -service $SourceDC -identity $item.sourcename -objectAttributes @{"msRTCSIP-PrimaryUserAddress"=$newsip}
}
pause
}
"11" { ##Move to OU
$date=get-date -format "yyyyMMdd"
foreach ($item in $import){
$return=CheckSite $item.sitecode
get-qaduser -service $targetDC -identity $item.displayname|move-qadobject -service $targetDC -identity {$_} -newparentcontainer $return
}
pause
}
"12" { ##Cleanup created files and move them to a completed dir
$date=get-date -format "yyyyMMdd"
move-item "$sourcefolder\ADMTUserInclude.csv" "$sourcefolder\Completed" -force
move-item "$sourcefolder\UserIncludes.csv" "$sourcefolder\Completed" -force
$ANewName=$date+"ADMTUserInclude.csv"
$UNewName=$date+"UserIncludes.csv"
if (!($(test-path "$sourcefolder\completed\$anewname"))){
rename-item "$sourcefolder\Completed\ADMTUserInclude.csv" $ANewName -force
rename-item "$sourcefolder\Completed\UserIncludes.csv" $UNewName -force
}
else {
remove-item "$sourcefolder\completed\$anewname"
remove-item "$sourcefolder\completed\$Unewname"
rename-item "$sourceFolder\Completed\ADMTUserInclude.csv" $ANewName -force
rename-item "$sourcefolder\Completed\UserIncludes.csv" $UNewName -force
}
pause
}
"Q" { ## quit and clean up our sessions if we missed any
get-pssession|remove-pssession
}
default {
Clear-Host;
Write-Host "Invalid input. Please enter a valid option. Press any key to continue.";
Pause
}
}
} until ($input.ToUpper() -eq "Q");
get-pssession|remove-pssession
Clear-Host;