Summary: An anonymous blogger shares a domain migration script.
Microsoft Scripting Guy, Ed Wilson, is here. Today’s guest blogger will remain anonymous due to privacy considerations at his company. This does not mean that his knowledge needs to remain undisclosed. Without further ado, here is a really nice guest blog…
We are a large international organization with many legacy domains. A major business objective this year is to move users, workstations, servers, and applications from these legacy domains into one domain and then collapse the legacy domains. The approach to migrating users, applications, etc. is not your typical “lift and shift” from the legacy domains into the target domain. Rather, the aim is to coexist in the legacy and target domains while the users and applications are migrated to the target domain. Coexistence allows for the continual use of production applications while they are being moved and remediated in the target domain.
The migration approach entails that each legacy user will have a user account provisioned in the target domain. Each legacy domain user account will be matched with its associated target domain account for purposes of password and data (that is, sIDHistory) synchronization. Microsoft Forefront Identity Manager performs this synchronization. A unique User account attribute contains a value that associates the legacy and target accounts. The primaryTelexNumber user attribute contains the unique value that associated the legacy and target accounts.
Populating primaryTelexNumber correctly and maintaining a historical record of the changes are key requirements in the process. I developed a Windows PowerShell script that takes a CSV input file of the legacy users by Distinguished Name with the associated IDMGuid, take a before shot of the attribute, update the attribute, take an after shot of the attribute, then write the before and after shots of the attribute to a file and invoke a GridView list after all the changes are complete. This approach leaves us with an audit trail of what was done, which helps in a variety of situations.
A secondary requirement is an “all-purpose” script to change the value of any Active Directory user attribute without having to consider the use of the Active Directory cmdlets or ADSI. I wanted one mechanism to use to change any attribute, complete with an audit trail.
Now to the script
The script begins with the normal housekeeping as follows. This includes importing the Active Directory module, assigning variables, and removing any PSDrives. The intent of the Get-MyModule function is to load the Active Directory module once and not load it on subsequent runs during the session.
The variable assignment section requires the specification of the attribute to be modified, the name and path of the input CSV file, the name and path of the output text file, and the array name where the output objects are stored before they are written to the text file.
The intent of the $OutFile and $FileDT variables is to dynamically construct an output text file at run time so each output file is unique and contains the date and time when it was run. This is very useful in that the results of a specific run are contained in a file named with the date and time when it was run. The $DateTime variable is used in the output file, and it indicates when the attribute was updated.
Function Get-MyModule
{
Param([string]$name)
if(-not(Get-Module -name $name))
{
if(Get-Module -ListAvailable |
Where-Object { $_.name -eq $name })
{
Import-Module -Name $name
} #end if module available then import
else { $false } #module not available
} #end if not module
else { $true } #module already loaded
} #end function get-MyModule
Get-Mymodule -Name “ActiveDirectory”
$UserAttribute = “primaryTelexNumber”
$InFilePath = “C:\Users\adm-a99426\”
$InFileCSV = “IDMGuidUsers.csv”
$OutFilePath = “C:\Users\adm- a99426”
$OutFile = “UserAccountMods_$FileDT.txt”
$FileDT = get-date -Format MM_dd_y_HHmmsstt
$DateTime = get-date -Format MM/dd/y-HH:mm:sstt
$MyArray = @()
Remove-Psdrive File -Force -ErrorAction SilentlyContinue
Next, the New-PSDrive cmdlet is invoked to create a mapping to the file system, and specifically to the output file that is associated with the $OutFilePath variable. This is where the results of the script will be written to.
New-PSDrive -PSProvider FileSystem -Name File -Root $OutFilePath
This is required because to work with the file system and files, you have to work within the file system provider; whereas to work with Active Directory and user accounts, you have to work within the Active Directory provider. Because we have the requirement to update Active Directory user account attributes and save the updates to a file, both the FileSystem and ActiveDirectory providers need to be accessed at the right time when working with objects within those providers.
The following CSV input file provides is a list of user accounts in Distinguished Name format with the new value of the attribute specified in the IDMGuid field. The value of the IDMGuid field will be used to update the attribute as specified in the $UserAttribute variable, which is the primaryTelexNumber attribute.
Each user account in the input file will have the value of their primaryTelexNumber changed to what is specified in the file in the IDMGuid field. For example, the primaryTelexNumber for user account A62100 will change to 001510230, the primaryTelexNumber for user account A62101 will change to 001510231, and so on.
The Import-CSV cmdlet is used to read the IDMGuidUsers.csv file of user accounts with the new value of the attribute, thereby creating an object for each line (or user) in the file. The User property refers to the distinguished name for each user, and the IDMGuid property refers to the value of the primaryTelexNumber. A Foreach loop will process each object in the $Accounts variable. This is illustrated here:
$Accounts = Import-Csv $InFilePath$InFileCSV
Foreach ($User in $Accounts) {
To change the value of the attribute, we have to first map to the Active Directory provider, which is accomplished by using the Set-Location cmdlet.
- The $BMod=Get-Item statement is designed to save the before value of the $UserAttribute, or primaryTelexNumber for the specific user.
- The Set-ItemProperty makes the revision to the $UserAttribute, or primaryTelexNumber with the value contained in the IDMGuid property for the specific user.
- The $AMod=Get-Item statement is designed to save the after value of the $UserAttribute, or primaryTelexNumber for the specific user.
This is illustrated here:
Set-Location AD:
$BMod = Get-Item -Path $User.User -Properties $UserAttribute
Set-ItemProperty -Path $User.User -Name $UserAttribute -Value $User.IDMGuid
$AMod = Get-Item -Path $User.User -Properties $UserAttribute
The reason for this method is to construct an output object for each user with the relevant fields and save the objects in an array until we are finished processing all user accounts, or attribute updates. After we are finished processing all user accounts, the output objects will be written from the array to a file specified by the $OutFile variable and piped to the Out-Gridview cmdlet, so the results can be seen immediately upon completion of the script. Note the mapping to the FileSystem provider to accomplish this. This is illustrated here:
Set-Location File:
$obj1 = New-Object PSObject -property @{UserAccount=$BMod.Name;
AttrBeforeMod=$BMod.$UserAttribute;
AttrAfterMod=$AMod.$UserAttribute;
RunDate=$DateTime}
$MyArray += $obj1
}
$MyArray | Out-File $OutFile -width 100
$MyArray | Out-Gridview
What the script produces
With the IDMGuidUsers.csv input file listed as input, the script produces the Out-Gridview and output file shown in the following two images.
On a subsequent run, the following IDMGuidUsers2nd.csv input file produces the Out-Gridview and output file shown in the following images.
The complete script
Function Get-MyModule
{
Param([string]$name)
if(-not(Get-Module -name $name))
{
if(Get-Module -ListAvailable |
Where-Object { $_.name -eq $name })
{
Import-Module -Name $name
} #end if module available then import
else { $false } #module not available
} #end if not module
else { $true } #module already loaded
} #end function get-MyModule
Get-Mymodule -Name “ActiveDirectory”
$UserAttribute = “primaryTelexNumber”
$InFilePath = “C:\Users\adm-a99426\”
$InFileCSV = “IDMGuidUsers.csv”
$OutFilePath = “C:\Users\adm-a99426”
$OutFile = “UserAccountMods_$FileDT.txt”
$FileDT = get-date -Format MM_dd_y_HHmmsstt
$DateTime = get-date -Format MM/dd/y-HH:mm:sstt
$myarray = @()
Remove-Psdrive File -Force -ErrorAction SilentlyContinue
#
New-PSDrive -PSProvider FileSystem -Name File -Root $OutFilePath
#
$Accounts = Import-Csv $InFilePath$InFileCSV
Foreach ($User in $Accounts) {
#
Set-Location AD:
$BMod = Get-Item -Path $User.User -Properties $UserAttribute
Set-ItemProperty -Path $User.User -Name $UserAttribute -Value $User.IDMGuid
$AMod = Get-Item -Path $User.User -Properties $UserAttribute
#
Set-Location File:
$obj1 = New-Object PSObject -property @{UserAccount=$BMod.Name;
AttrBeforeMod=$BMod.$UserAttribute;
AttrAfterMod=$AMod.$UserAttribute;
RunDate=$DateTime}
$myarray += $obj1
}
$myarray | out-file $OutFile -width 100
$myarray | out-gridview
This script has worked well, and it has allowed us to change a number of different user account attributes with any size input file. The next step with this script is to turn it into a function, which shouldn’t be difficult to do when I get to it.
~anonymous
Thank you for sharing your time and knowledge.
I invite you to follow me on Twitter and Facebook. If you have any questions, send email to me at scripter@microsoft.com, or post your questions on the Official Scripting Guys Forum. See you tomorrow. Until then, peace.
Ed Wilson, Microsoft Scripting Guy
0 comments