User Migration Script in PowerShell Part II
Well those of you who were riveted by my last post on User Migration Script in PowerShell Part I then seriously you have bigger problems than me writing these things 
So for you die hard fans of scripting lets recap on what my challengers were:
A summary of the environment is listed below
- Going from Windows 2003 File Server to Windows 2008 File server
- Introducing several different quota groups
- The DFS name space has allowance for different locations (e.g. HO for Head Office, CC for Call Center etc.)
- Folder redirection currently being driven by Group Policy preferences and determined by what group you’re a member
So the things I needed to achieve were these:
- Update AD so that the persons home folder now points to the new DFS name share (based on their location and the quota group they belong to)
- Remove the old folder redirection group and assign the user to the new folder redirection group.
- Create new home folder and assign the correct permissions for the folder
- Copy the data across.
Now just to mix it up a bit we had a requirement to move the old home folder to a new temporary folder as a back up for a week or two called Archive.
Excellent. Why not? Sounds like a challenge. I like challengers. Add to this the need to move lot’s of users from a spreadsheet. OK then, heres the code.
#User Migration Script v1.1 Angelo Papiccio 07/05/2012 *
#This script can be used for migrating users between file servers *
#or from one quota group to another. *
#Note: Requires Quest Active Roles Powershell management Addins *
#******************************************************************
#Reset values to Null
$UserCheck = $null
$UserCheckLoop = $null
$GroupName = $null
$Location = $null
#Define User Variables.
#Imports from a CSV found in "Enter Path here"
#The first section checks imports the users from a spreadsheet and checks if they exist in AD.
#If not an error is written to a log file.
$migdata = '\\server.com.au\files\Public\ICT Infrastructure\MOE\Migration.csv'
$Logfile = '\\server.com.au\files\Public\ICT Infrastructure\MOE\error.log'
$email = Import-Csv -Path $migdata
Clear-Host
foreach ($strEmail in $email)
{
$email=$strEmail.email
$Location = $strEmail.location
$QuotaGroup = $strEmail.group
$UserCheck = $null
$UserCheckLoop = $null
$homeDirectory = $null
Do
{
$ErrorActionPreference = "SilentlyContinue"
$SAM = (Get-Mailbox -Identity $email).SaMAccountName
$UserCheck = (Get-ADUser -Identity $SAM).SaMAccountName
If ($UserCheck -eq $null)
{
Write-Host "$email is not a valid email, error being written to log"
Add-Content $Logfile "$email is not a valid email address"
$UserCheckLoop = "true"
}
Elseif ($UserCheck -ne $SAM)
{
Write-Host "$email is not a valid email, error being written to log"
Add-Content $Logfile "$email is not a valid email address"
$UserCheckLoop = "true"
}
else
{
$homeDirectory = Get-ADUser -Identity $SAM -Properties homeDirectory | select -ExpandProperty homeDirectory
$BaseHomeDir = "\\dfs.com.au\Files\Private"
$NewHomeDir = "$BaseHomeDir\$Location\Group$QuotaGroup\$SAM"
$QuotaGroupName = "Role-USR-Files.Private.$Location.Group$QuotaGroup-Redirection"
#Update Active Directory with new Home Folder Destination
Set-ADUser -Identity $SAM -HomeDirectory $NewHomeDir
Write-Host $SAM "new folder location is" $NewHomeDir
#Create the New Folder
New-Item $NewHomeDir -type directory
#Set the security permissions on the folder to allow user Modify access to all items (including sub folders and files)
$colRights = [System.Security.AccessControl.FileSystemRights]"Modify"
$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit
$PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None
$objType =[System.Security.AccessControl.AccessControlType]::Allow
$objACE = New-Object System.Security.AccessControl.FileSystemAccessRule ($SAM, $colRights, $InheritanceFlag, $PropagationFlag, $objType)
$objACL = Get-Acl $NewHomeDir
$objACL.AddAccessRule($objACE)
Set-Acl $NewHomeDir $objACL
#Copy over the data from the old Home drive to the new home drive
Write-Host "Copying data from $homeDirectory to $NewHomeDir.............."
robocopy $homeDirectory $NewHomeDir /COPY:DAT /E /R:0 /b /np /tee /log+:"\\dfs.com.au\files\public\ICT Infrastructure\MOE\FileCopy.log"
Write-Host "File copy complete. Please check that file copy was full successful before deleting the old file"
#Get the server name of the home folder and create a new path for the archiving of the Private folder
#This array requires the splitting of string through an array and identifying the correct section as the server name
#When the new folder string is created for the archive it will MOVE the folder to the Archive section
$split = $homeDirectory.Split("\")
$OldSeverName = $split[2]
$ArchivePrivate = "\\" + $OldSeverName + "\Private\Archive\$SAM"
Move-Item $homeDirectory $ArchivePrivate
#Delete old home folder - Currently commented out in case something interupts the copy then we need a way to check.
#Write-Host "Removing folder $homeDirectory................"
#Remove-Item $homeDirectory -Recurse -Force
#Write-Host "$homeDirectory has been deleted"
#Check for users current redirection group and remove them from it. If a user is a member of multiple redirection groups then an error will occur and the script will stop
#If this is the case then the user will not get a proper drive mapping for their home drive anyway and will need some manual intervention
$GetGroupName = Get-QADGroup -ContainsMember $SAM | where {($_.Name -ilike "User Folder*") -or ($_.Name -ilike "Role-USR-Files.Private*")}
write-host $GetGroupName
if (!$GetGroupName) {Write-Host "No Redirect group found for user. Continuing to add new group mem"}
else {Remove-QADGroupMember -Identity $GetGroupName -Member $SAM}
#Add user to new Home folder redirect group.
Add-ADGroupMember -Identity $QuotaGroupName -Members $SAM
#Summary Report
Write-Host "The Account $SAM Active Directory profile has been adjusted to map H:\ to $NewHomeDir. $SAM has been removed from $GetGroupName and been added to the $QuotaGroupName group for My Documents folder redirection"
Write-Host "Please make sure that you delete the old home folder from $homeDirectory and migrate any Public Folders they may have"
$UserCheckLoop = "true"
}
}
Until ($UserCheckLoop -eq "true")
}
Cool right?
But what does it all mean? I hear you say
Well like the previous post the first part simply sets some variables and assigns then some values.
$UserCheck = $null
$UserCheckLoop = $null
$GroupName = $null
$Location = $null
#Define User Variables.
#Imports from a CSV found in "Enter Path here"
#The first section checks imports the users from a spreadsheet and checks if they exist in AD.
#If not an error is written to a log file.
$migdata = '\\server.com.au\files\Public\ICT Infrastructure\MOE\Migration.csv'
$Logfile = '\\server.com.au\files\Public\ICT Infrastructure\MOE\error.log'
$email = Import-Csv -Path $migdata
Clear-Host
The line $email=Import-Csv -Path $migdata creates an array and places the data located within the csv file into a variable called $email.
The next section begins a foreach loop so that it reads data for each row in the CSV file and then assigns it to variable.
{
$email=$strEmail.email
$Location = $strEmail.location
$QuotaGroup = $strEmail.group
$UserCheck = $null
$UserCheckLoop = $null
$homeDirectory = $null
Now we start getting into some pretty cool stuff. What I forgot to mention earlier in the post was that my spreadsheet did not have SamAccount info or email addresses it only had first names and last names. So to get around this I merged the first name and last name to get the email address of the user e.g. angelo.papiccio@abc.com.au.
However I need the SamAccount info and a Get-ADUser won’t give back an email address. So here’s how I got around that one.
$UserCheck = (Get-ADUser -Identity $SAM).SaMAccountName
So the $SAM variable is doing a Get-Mailbox using the email address we had in the spreadsheet. From this we can query and extract a SamAccountName. As always I like to check that the users email is a valid one so I use a variable called $UserCheck which then does a Get-ADUser using the value from $SAM to extract what we hope is the same value. To make sure we use a simple If Statement to make sure it is not a Null value returned.
{
Write-Host "$email is not a valid email, error being written to log"
Add-Content $Logfile "$email is not a valid email address"
$UserCheckLoop = "true"
}
Elseif ($UserCheck -ne $SAM)
{
Write-Host "$email is not a valid email, error being written to log"
Add-Content $Logfile "$email is not a valid email address"
$UserCheckLoop = "true"
}
else
{
$homeDirectory = Get-ADUser -Identity $SAM -Properties homeDirectory | select -ExpandProperty homeDirectory
$BaseHomeDir = "<a href="file://\\dfs.com.au\Files\Private">\\dfs.com.au\Files\Private</a>"
$NewHomeDir = "$BaseHomeDir\$Location\Group$QuotaGroup\$SAM"
$QuotaGroupName = "Role-USR-Files.Private.$Location.Group$QuotaGroup-Redirection"
The next part of the script is the same as Part I of this series.
Set-ADUser -Identity $SAM -HomeDirectory $NewHomeDir
Write-Host $SAM "new folder location is" $NewHomeDir
#Create the New Folder New-Item $NewHomeDir -type directory
#Set the security permissions on the folder to allow user Modify access to all items (including sub folders and files)
$colRights = [System.Security.AccessControl.FileSystemRights]"Modify"
$InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor System.Security.AccessControl.InheritanceFlags]::ObjectInherit $PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None
$objType =[System.Security.AccessControl.AccessControlType]::Allow $objACE = New-Object System.Security.AccessControl.FileSystemAccessRule ($SAM, $colRights, $InheritanceFlag, $PropagationFlag, $objType)
$objACL = Get-Acl $NewHomeDir $objACL.AddAccessRule($objACE) Set-Acl $NewHomeDir $objACL
#Copy over the data from the old Home drive to the new home drive
Write-Host "Copying data from $homeDirectory to $NewHomeDir.............."
robocopy $homeDirectory $NewHomeDir /COPY:DAT /E /R:0 /b /np /tee /log+:<a href="file://\\dfs.com.au\files\public\ICT Infrastructure\MOE\FileCopy.log">\\dfs.com.au\files\public\ICT Infrastructure\MOE\FileCopy.log</a>
Write-Host "File copy complete. Please check that file copy was full successful before deleting the old file"
However this next bit was a bit tricky. I needed the server name of the home directory from which I just copied data from. $homedirectory would have that int the form of \\servername\private\angelo but how to get just the server name? Why not use some string manipulation to split up the text. In fact the split command is excatly what you use. Check it out.
#This array requires the splitting of string through an array and identifying the correct section as the server name
#When the new folder string is created for the archive it will MOVE the folder to the Archive section
$split = $homeDirectory.Split("\")
$OldSeverName = $split[2]
$ArchivePrivate = "\\" + $OldSeverName + "\Private\Archive\$SAM"
Move-Item $homeDirectory $ArchivePrivate
By splitting the text in $homedirectory on the value “\” we can work out that position 3 therefore counting from 0 we get the split value=2 (e.g. “\”=0, “\”=1, “Servername”=2)
Based on that we can then use the Move-item option to move the folder to the archive location.
The rest of the script pretty much works the same as my previous one, except for the fact that it goes through all the entries in the csv file.
So there we have it. May not be the prettiest code but it does the job nicely.
User Migration Script in PowerShell Part I
I had a job recently that required us to move users from existing file servers to a new Enterprise File Server that employed DFS at work.
A summary of the environment is listed below
- Going from Windows 2003 File Server to Windows 2008 File server
- Introducing several different quota groups
- The DFS name space has allowance for different locations (e.g. HO for Head Office, CC for Call Centre etc.)
- Folder redirections currently being driven by Group Policy preferences and determined by what group you’re a member
So the things I needed to achieve were these:
- Update AD so that the persons home folder now points to the new DFS name share (based on their location and the quota group they belong to)
- Remove the old folder redirection group and assign the user to the new folder redirection group.
- Create new home folder and assign the correct permissions for the folder
- Copy the data across.
Not that hard, but when I did it manually it took about 6 minutes for a user with a 200MB Home folder (including all the AD fixes). That’s fine for a single instance but we are doing this as part of a Desktop migration to Windows 7 so we are talking about possibly 1400 people.
PowerShell to the rescue!.
Here is the code I wrote to achieve the above
|
#****************************************************************** #User Migration Script v1.0 Angelo Papiccio 24/04/2012 * #This script can be used for migrating users between file servers * #or from one quota group to another. * #Note: Requires Quest Active Roles Powershell management Addins * #******************************************************************#Define User Variables. #It contains several loops to make sure that the input is valid. #The first section checks to see whether the username entered actually exists within AD $UserCheck = $null $UserCheckLoop = $null $GroupName = $nullDo {$SAM = Read-Host "Please enter the users login name (e.g. papicc0a):" $UserCheck = (Get-ADUser -Identity $SAM).saMAccountName If ($UserCheck -eq $SAM){$UserCheckLoop ="true"} else {$UserCheckLoop ="false"} } Until ($UserCheckLoop -eq "true")$homeDirectory = Get-ADUser -Identity $SAM -Properties homeDirectory | select -ExpandProperty homeDirectory $Location = Read-Host "Please enter the Location the Home Drive will reside (e.g. HO, JCC):" $QuotaGroup = Read-Host "Which Quota Group will " $SAM "belong to? (e.g. A,B,C,D)" $BaseHomeDir = "\\servername.com.au\Files\Private" $NewHomeDir = "$BaseHomeDir\$Location\Group$QuotaGroup\$SAM" $QuotaGroupName = "Role-USR-Files.Private.$Location.Group$QuotaGroup-Redirection"#Update Active Directory with new Home Folder Destination Set-ADUser -Identity $SAM -HomeDirectory $NewHomeDir Write-Host $SAM "new folder location is" $NewHomeDir#Create the New Folder New-Item $NewHomeDir -type directory#Set the security permissions on the folder to allow user Modify access to all items (including sub folders and files) $colRights = [System.Security.AccessControl.FileSystemRights]"Modify" $InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit $PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None $objType =[System.Security.AccessControl.AccessControlType]::Allow $objACE = New-Object System.Security.AccessControl.FileSystemAccessRule ($SAM, $colRights, $InheritanceFlag, $PropagationFlag, $objType) $objACL = Get-Acl $NewHomeDir $objACL.AddAccessRule($objACE) Set-Acl $NewHomeDir $objACL#Copy over the data from the old Home drive to the new home drive Write-Host "Copying data from $homeDirectory to $NewHomeDir.............." Copy-Item $homeDirectory\* $NewHomeDir -Recurse Write-Host "File copy complete. Please check that file copy was full successful before deleting the old file"#Delete old home folder - Currently commented out in case something interupts the copy then we need a way to check. #Write-Host "Removing folder $homeDirectory................" #Remove-Item $homeDirectory -Recurse -Force #Write-Host "$homeDirectory has been deleted"#Check for users current redirection group and remove them from it. If a user is a member of multiple redirection groups then an error will occur and the script will stop #If this is the case then the user will not get a proper drive mapping for their home drive anyway and will need some manual intervention $GetGroupName = Get-QADGroup -ContainsMember $SAM | where {($_.Name -ilike "User Folder*") -or ($_.Name -ilike "Role-USR-Files.Private*")} write-host $GetGroupName if (!$GetGroupName) {Write-Host "No Redirect group found for user. Continuing to add new group mem"} else {Remove-QADGroupMember -Identity $GetGroupName -Member $SAM}#Add user to new Home folder redirect group. Add-ADGroupMember -Identity $QuotaGroupName -Members $SAM#Summary Report Write-Host "$SAM Active Directory profile has been adjusted to map H:\ to $NewHomeDir. $SAM has been removed from $GetGroupName and been added to the $QuotaGroupName group for My Documents folder redirection" Write-Host "Please make sure that you delete the old home folder from $homeDirectory and migrate any Public Folders they may have" |
What does the script do? Well I’m glad you asked!
Lets break it down:
|
#Define User Variables. #It contains several loops to make sure that the input is valid. #The first section checks to see whether the username entered actually exists within AD $UserCheck = $null $UserCheckLoop = $null $GroupName = $nullDo {$SAM = Read-Host "Please enter the users login name (e.g. papicc0a):" $UserCheck = (Get-ADUser -Identity $SAM).saMAccountName If ($UserCheck -eq $SAM){$UserCheckLoop ="true"} else {$UserCheckLoop ="false"} } Until ($UserCheckLoop -eq "true") |
This section is pretty straight forward. We define several variables that will get called upon later in the script and start them with Null values.
We then go into a Do Until loop. In here the value $SAM is read from the host (i.e. user enters in a valid SAM account or pre/windows 2000 login name). It then sets the variable name of $UserCheck equal the saMAccountName AD attribute of $SAM by using a Get-ADUser command.
We then parse this through an IF Statement to make sure it is a valid user id. If the saMAccountName = the user input then set the Loop check (i.e. $UserCheckLoop variable) to true otherwise set it to false.
The Do loop will continue until $UserCheckLoop value is set to true.
I always find it useful to put some sanity checks in when dealing with user inputs.
The next section is also straight forward as all it does is set some AD attributes
|
$homeDirectory = Get-ADUser -Identity $SAM -Properties homeDirectory | select -ExpandProperty homeDirectory $Location = Read-Host "Please enter the Location the Home Drive will reside (e.g. HO, JCC):" $QuotaGroup = Read-Host "Which Quota Group will " $SAM "belong to? (e.g. A,B,C,D)" $BaseHomeDir = "\\servername.com.au\Files\Private" $NewHomeDir = "$BaseHomeDir\$Location\Group$QuotaGroup\$SAM" $QuotaGroupName = "Role-USR-Files.Private.$Location.Group$QuotaGroup-Redirection" |
Here we assign some values to the variables. $homeDirectory uses the Get-AdUser commandlet to extract the HomeDirectory attribute from AD.
The Location (which we require for setting up the new home folder string but may be optional for others) is a value entered in by the user. The same with the $QuotaGroup variable (again we use this for building the string value latter on)
The $BaseHomeDir is a variable string I have assigned in the code. It is the part of the string that will remain the same regardless of the location or the quota group being assigned.
The next two vairables $NewHomeDir and $QuotaGroupName are string values that have been put together based the predefined naming methods we have at where I work and the variables entered in by the user.
|
</span> <span style="font-size: 8px;"> #Update Active Directory with new Home Folder Destination</span> <span style="font-size: 8px;"> Set-ADUser -Identity $SAM -HomeDirectory $NewHomeDir</span> <span style="font-size: 8px;"> Write-Host $SAM "new folder location is" $NewHomeDir#Create the New Folder</span> <span style="font-size: 8px;"> New-Item $NewHomeDir -type directory</span> <span style="font-size: 8px;"> |
Alright so now we doing some updates in AD so that the persons Home Directory is assigned and a new folder created in the new location which is what the above code does. A Set-ADUser and New-Item commandlet is used. Nothing tricky so far right?
Well what does the next bit of code do? I’m so glad you asked.
|
#Set the security permissions on the folder to allow user Modify access to all items (including sub folders and files) $colRights = [System.Security.AccessControl.FileSystemRights]"Modify" $InheritanceFlag = [System.Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [System.Security.AccessControl.InheritanceFlags]::ObjectInherit $PropagationFlag = [System.Security.AccessControl.PropagationFlags]::None $objType =[System.Security.AccessControl.AccessControlType]::Allow $objACE = New-Object System.Security.AccessControl.FileSystemAccessRule ($SAM, $colRights, $InheritanceFlag, $PropagationFlag, $objType) $objACL = Get-Acl $NewHomeDir $objACL.AddAccessRule($objACE) Set-Acl $NewHomeDir $objACL |
In this piece of code we start to dive right into .net framework itself.
The first couple of lines set the level of permissions (in this case modify because we don’t want our users to go in and change permissions) as well as setting inheritance.
This is quite a nice piece of code for permission handling in PowerShell. It does require you delve a bit more into .Net than I would otherwise like but hey, you gotta do what you gotta do. If you want more information on .net procedures for file shares try http://technet.microsoft.com/en-us/library/ff730951.aspx it has a lot of good info.
|
#Copy over the data from the old Home drive to the new home drive Write-Host "Copying data from $homeDirectory to $NewHomeDir.............." Copy-Item $homeDirectory\* $NewHomeDir -Recurse Write-Host "File copy complete. Please check that file copy was full successful before deleting the old file"#Delete old home folder - Currently commented out in case something interupts the copy then we need a way to check. #Write-Host "Removing folder $homeDirectory................" #Remove-Item $homeDirectory -Recurse -Force #Write-Host "$homeDirectory has been deleted"#Check for users current redirection group and remove them from it. If a user is a member of multiple redirection groups then an error will occur and the script will stop #If this is the case then the user will not get a proper drive mapping for their home drive anyway and will need some manual intervention $GetGroupName = Get-QADGroup -ContainsMember $SAM | where {($_.Name -ilike "User Folder*") -or ($_.Name -ilike "Role-USR-Files.Private*")} write-host $GetGroupName if (!$GetGroupName) {Write-Host "No Redirect group found for user. Continuing to add new group mem"} else {Remove-QADGroupMember -Identity $GetGroupName -Member $SAM}#Add user to new Home folder redirect group. Add-ADGroupMember -Identity $QuotaGroupName -Members $SAM#Summary Report Write-Host "$SAM Active Directory profile has been adjusted to map H:\ to $NewHomeDir. $SAM has been removed from $GetGroupName and been added to the $QuotaGroupName group for My Documents folder redirection" Write-Host "Please make sure that you delete the old home folder from $homeDirectory and migrate any Public Folders they may have" |
Right now that we have the folder created, the permission set and AD all updated to look in the right spot we need to copy the data across. Now one thing I did neglect to mention was that I installed the Quest Active Server Roles extension for AD Powershell. This gives far more control in PowerShell than the native commandlets. You will notice I use the Get-QADGroup command which then enables me to use the –contain member switch. So what does this do? Well it searchs through all groups in AD for ones that have <username> as a member. So this little line here
looks for groups where $SAM is a member but we pipe it through where statement to target specific groups. I can now target a specific group and get that group name and assign it to $GetGroupName. I love powershell have I told you that yet?
I shove in an IF statement for logic control and then get down to the business of removing old groups and adding new ones.
The last thing I do is write to the screen some summary information to advise the user the script has completed and what it has done.
Cool huh?
Stay tuned for part two when I show you how I used this migration script by importing the data from a csv file.
