{"id":2795,"date":"2013-09-30T00:01:00","date_gmt":"2013-09-30T00:01:00","guid":{"rendered":"https:\/\/blogs.technet.microsoft.com\/heyscriptingguy\/2013\/09\/30\/domain-migration-why-we-need-the-script\/"},"modified":"2013-09-30T00:01:00","modified_gmt":"2013-09-30T00:01:00","slug":"domain-migration-why-we-need-the-script","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/scripting\/domain-migration-why-we-need-the-script\/","title":{"rendered":"Domain Migration: Why We Need the Script"},"content":{"rendered":"<p><strong>Summary<\/strong>: An anonymous blogger shares a domain migration script.<\/p>\n<p>Microsoft Scripting Guy, Ed Wilson, is here. Today&rsquo;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&hellip;<\/p>\n<p>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 &ldquo;lift and shift&rdquo; 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.<\/p>\n<p>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, <strong>sIDHistory<\/strong>) synchronization. Microsoft Forefront Identity Manager performs this synchronization. A unique User account attribute contains a value that associates the legacy and target accounts. The <strong>primaryTelexNumber<\/strong> user attribute contains the unique value that associated the legacy and target accounts.<\/p>\n<p>Populating <strong>primaryTelexNumber<\/strong> 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 <strong>GridView<\/strong> 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.<\/p>\n<p>A secondary requirement is an &ldquo;all-purpose&rdquo; 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.<\/p>\n<h2>Now to the script<\/h2>\n<p>The script begins with the normal housekeeping as follows. This includes importing the Active Directory module, assigning variables, and removing any <strong>PSDrives<\/strong>. The intent of the <strong>Get-MyModule<\/strong> function is to load the Active Directory module once and not load it on subsequent runs during the session.<\/p>\n<p>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.<\/p>\n<p>The intent of the <strong>$OutFile<\/strong> and <strong>$FileDT<\/strong> 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 <strong>$DateTime<\/strong> variable is used in the output file, and it indicates when the attribute was updated.<\/p>\n<p><strong>Function Get-MyModule<\/strong><\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">Param([string]$name)<\/p>\n<p style=\"padding-left: 30px\">if(-not(Get-Module -name $name))<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">if(Get-Module -ListAvailable |<\/p>\n<p style=\"padding-left: 30px\">Where-Object { $_.name -eq $name })<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">Import-Module -Name $name<\/p>\n<p style=\"padding-left: 30px\">} #end if module available then import<\/p>\n<p style=\"padding-left: 30px\">else { $false } #module not available<\/p>\n<p style=\"padding-left: 30px\">} #end if not module<\/p>\n<p style=\"padding-left: 30px\">else { $true } #module already loaded<\/p>\n<p style=\"padding-left: 30px\">} #end function get-MyModule<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;Get-Mymodule -Name &#8220;ActiveDirectory&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">$UserAttribute = &#8220;primaryTelexNumber&#8221;<\/p>\n<p style=\"padding-left: 30px\">$InFilePath&nbsp; &nbsp;= &#8220;C:\\Users\\adm-a99426\\&#8221;<\/p>\n<p style=\"padding-left: 30px\">$InFileCSV&nbsp; &nbsp;= &#8220;IDMGuidUsers.csv&#8221;<\/p>\n<p style=\"padding-left: 30px\">$OutFilePath &nbsp;= &#8220;C:\\Users\\adm- a99426&#8221;<\/p>\n<p style=\"padding-left: 30px\">$OutFile&nbsp;&nbsp; &nbsp;= &#8220;UserAccountMods_$FileDT.txt&#8221;<\/p>\n<p style=\"padding-left: 30px\">$FileDT&nbsp;&nbsp; &nbsp;= get-date -Format MM_dd_y_HHmmsstt<\/p>\n<p style=\"padding-left: 30px\">$DateTime&nbsp; = get-date -Format MM\/dd\/y-HH:mm:sstt<\/p>\n<p style=\"padding-left: 30px\">$MyArray&nbsp; &nbsp;= @()<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">Remove-Psdrive File -Force -ErrorAction SilentlyContinue<\/p>\n<p>Next, the <strong>New-PSDrive<\/strong> cmdlet is invoked to create a mapping to the file system, and specifically to the output file that is associated with the <strong>$OutFilePath<\/strong> variable. This is where the results of the script will be written to.<\/p>\n<p style=\"padding-left: 30px\">New-PSDrive -PSProvider FileSystem -Name File -Root $OutFilePath<\/p>\n<p>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 <strong>FileSystem<\/strong> and <strong>ActiveDirectory<\/strong> providers need to be accessed at the right time when working with objects within those providers.<\/p>\n<p>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 <strong>$UserAttribute<\/strong> variable, which is the <strong>primaryTelexNumber<\/strong> attribute.<\/p>\n<p>Each user account in the input file will have the value of their <strong>primaryTelexNumber<\/strong> changed to what is specified in the file in the IDMGuid field. For example, the <strong>primaryTelexNumber<\/strong> for user account A62100 will change to 001510230, the <strong>primaryTelexNumber<\/strong> for user account A62101 will change to 001510231, and so on.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6406.hsg-9-30-13-1.jpg\"><img decoding=\"async\" title=\"Image of file\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6406.hsg-9-30-13-1.jpg\" alt=\"Image of file\" \/><\/a><\/p>\n<p>The <strong>Import-CSV<\/strong> 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 <strong>User<\/strong> property refers to the distinguished name for each user, and the <strong>IDMGuid<\/strong> property refers to the value of the <strong>primaryTelexNumber<\/strong>. A <strong>Foreach<\/strong> loop will process each object in the <strong>$Accounts<\/strong> variable. This is illustrated here:<\/p>\n<p style=\"padding-left: 30px\">$Accounts = Import-Csv $InFilePath$InFileCSV<\/p>\n<p style=\"padding-left: 30px\">Foreach ($User in $Accounts) {<\/p>\n<p>To change the value of the attribute, we have to first map to the Active Directory provider, which is accomplished by using the <strong>Set-Location<\/strong> cmdlet.<\/p>\n<ul>\n<li>The <strong>$BMod=Get-Item<\/strong> statement is designed to save the before value of the <strong>$UserAttribute<\/strong>, or <strong>primaryTelexNumber<\/strong> for the specific user.<\/li>\n<li>The <strong>Set-ItemProperty<\/strong> makes the revision to the <strong>$UserAttribute<\/strong>, or <strong>primaryTelexNumber<\/strong> with the value contained in the <strong>IDMGuid<\/strong> property for the specific user.<\/li>\n<li>The <strong>$AMod=Get-Item<\/strong> statement is designed to save the after value of the <strong>$UserAttribute<\/strong>, or <strong>primaryTelexNumber<\/strong> for the specific user.<\/li>\n<\/ul>\n<p>This is illustrated here:<\/p>\n<p><strong>Set-Location AD:<\/strong><\/p>\n<p style=\"padding-left: 30px\">$BMod = Get-Item -Path $User.User -Properties $UserAttribute<\/p>\n<p style=\"padding-left: 30px\">Set-ItemProperty -Path $User.User -Name $UserAttribute -Value $User.IDMGuid<\/p>\n<p style=\"padding-left: 30px\">$AMod = Get-Item -Path $User.User -Properties $UserAttribute<\/p>\n<p>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 <strong>$OutFile<\/strong> variable and piped to the <strong>Out-Gridview<\/strong> cmdlet, so the results can be seen immediately upon completion of the script. Note the mapping to the <strong>FileSystem<\/strong> provider to accomplish this. This is illustrated here:<\/p>\n<p><strong>Set-Location File:<\/strong><\/p>\n<p style=\"padding-left: 30px\">$obj1 = New-Object PSObject -property @{UserAccount=$BMod.Name;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;AttrBeforeMod=$BMod.$UserAttribute;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;AttrAfterMod=$AMod.$UserAttribute;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;RunDate=$DateTime}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<\/p>\n<p style=\"padding-left: 30px\">$MyArray += $obj1<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">$MyArray | Out-File $OutFile -width 100<\/p>\n<p style=\"padding-left: 30px\">$MyArray | Out-Gridview<\/p>\n<h2>What the script produces<\/h2>\n<p>With the IDMGuidUsers.csv input file listed as input, the script produces the Out-Gridview and output file shown in the following two images.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3276.hsg-9-30-13-2.jpg\"><img decoding=\"async\" title=\"Image of file\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3276.hsg-9-30-13-2.jpg\" alt=\"Image of file\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/2084.hsg-9-30-13-3.jpg\"><img decoding=\"async\" title=\"Image of file\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/2084.hsg-9-30-13-3.jpg\" alt=\"Image of file\" \/><\/a><\/p>\n<p>On a subsequent run, the following IDMGuidUsers2nd.csv input file produces the Out-Gridview and output file shown in the following images.<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6763.hsg-9-30-13-4.jpg\"><img decoding=\"async\" title=\"Image of file\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6763.hsg-9-30-13-4.jpg\" alt=\"Image of file\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3276.hsg-9-30-13-5.jpg\"><img decoding=\"async\" title=\"Image of file\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/3276.hsg-9-30-13-5.jpg\" alt=\"Image of file\" \/><\/a><\/p>\n<p>&nbsp;<\/p>\n<p><a href=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6607.hsg-9-30-13-6.jpg\"><img decoding=\"async\" title=\"Image of file\" src=\"https:\/\/devblogs.microsoft.com\/wp-content\/uploads\/sites\/29\/2019\/02\/6607.hsg-9-30-13-6.jpg\" alt=\"Image of file\" \/><\/a><\/p>\n<h2>The complete script<\/h2>\n<p><strong>Function Get-MyModule<\/strong><\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">Param([string]$name)<\/p>\n<p style=\"padding-left: 30px\">if(-not(Get-Module -name $name))<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">if(Get-Module -ListAvailable |<\/p>\n<p style=\"padding-left: 30px\">Where-Object { $_.name -eq $name })<\/p>\n<p style=\"padding-left: 30px\">{<\/p>\n<p style=\"padding-left: 30px\">Import-Module -Name $name<\/p>\n<p style=\"padding-left: 30px\">} #end if module available then import<\/p>\n<p style=\"padding-left: 30px\">else { $false } #module not available<\/p>\n<p style=\"padding-left: 30px\">} #end if not module<\/p>\n<p style=\"padding-left: 30px\">else { $true } #module already loaded<\/p>\n<p style=\"padding-left: 30px\">} #end function get-MyModule<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;Get-Mymodule -Name &#8220;ActiveDirectory&#8221;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">$UserAttribute = &#8220;primaryTelexNumber&#8221;<\/p>\n<p style=\"padding-left: 30px\">$InFilePath&nbsp; &nbsp;= &#8220;C:\\Users\\adm-a99426\\&#8221;<\/p>\n<p style=\"padding-left: 30px\">$InFileCSV&nbsp; &nbsp;= &#8220;IDMGuidUsers.csv&#8221;<\/p>\n<p style=\"padding-left: 30px\">$OutFilePath &nbsp;= &#8220;C:\\Users\\adm-a99426&#8221;<\/p>\n<p style=\"padding-left: 30px\">$OutFile&nbsp;&nbsp; &nbsp;= &#8220;UserAccountMods_$FileDT.txt&#8221;<\/p>\n<p style=\"padding-left: 30px\">$FileDT&nbsp;&nbsp;&nbsp; = get-date -Format MM_dd_y_HHmmsstt<\/p>\n<p style=\"padding-left: 30px\">$DateTime&nbsp; = get-date -Format MM\/dd\/y-HH:mm:sstt<\/p>\n<p style=\"padding-left: 30px\">$myarray&nbsp; &nbsp;= @()<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">Remove-Psdrive File -Force -ErrorAction SilentlyContinue<\/p>\n<p style=\"padding-left: 30px\">#<\/p>\n<p style=\"padding-left: 30px\">New-PSDrive -PSProvider FileSystem &nbsp;-Name File -Root $OutFilePath<\/p>\n<p style=\"padding-left: 30px\">#<\/p>\n<p style=\"padding-left: 30px\">$Accounts = Import-Csv $InFilePath$InFileCSV<\/p>\n<p style=\"padding-left: 30px\">Foreach ($User in $Accounts) {<\/p>\n<p style=\"padding-left: 30px\">#<\/p>\n<p style=\"padding-left: 30px\">Set-Location AD:<\/p>\n<p style=\"padding-left: 30px\">$BMod = Get-Item -Path $User.User -Properties $UserAttribute<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">Set-ItemProperty -Path $User.User -Name $UserAttribute -Value $User.IDMGuid<\/p>\n<p style=\"padding-left: 30px\">&nbsp;<\/p>\n<p style=\"padding-left: 30px\">$AMod = Get-Item -Path $User.User -Properties $UserAttribute<\/p>\n<p style=\"padding-left: 30px\">#<\/p>\n<p style=\"padding-left: 30px\">Set-Location File:<\/p>\n<p style=\"padding-left: 30px\">$obj1 = New-Object PSObject -property @{UserAccount=$BMod.Name;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AttrBeforeMod=$BMod.$UserAttribute;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AttrAfterMod=$AMod.$UserAttribute;<\/p>\n<p style=\"padding-left: 30px\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RunDate=$DateTime}&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;<\/p>\n<p style=\"padding-left: 30px\">$myarray += $obj1<\/p>\n<p style=\"padding-left: 30px\">}<\/p>\n<p style=\"padding-left: 30px\">$myarray | out-file $OutFile -width 100<\/p>\n<p style=\"padding-left: 30px\">$myarray | out-gridview<\/p>\n<p>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&rsquo;t be difficult to do when I get to it.<\/p>\n<p>~anonymous<\/p>\n<p>Thank you for sharing your time and knowledge.<\/p>\n<p>I invite you to follow me on <a href=\"http:\/\/bit.ly\/scriptingguystwitter\" target=\"_blank\">Twitter<\/a> and <a href=\"http:\/\/bit.ly\/scriptingguysfacebook\" target=\"_blank\">Facebook<\/a>. If you have any questions, send email to me at <a href=\"mailto:scripter@microsoft.com\" target=\"_blank\">scripter@microsoft.com<\/a>, or post your questions on the <a href=\"http:\/\/bit.ly\/scriptingforum\" target=\"_blank\">Official Scripting Guys Forum<\/a>. See you tomorrow. Until then, peace.<\/p>\n<p><strong>Ed Wilson, Microsoft Scripting Guy<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Summary: An anonymous blogger shares a domain migration script. Microsoft Scripting Guy, Ed Wilson, is here. Today&rsquo;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&hellip; We are a large international [&hellip;]<\/p>\n","protected":false},"author":596,"featured_media":87096,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[7,457,191,56,3,20,45],"class_list":["post-2795","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-scripting","tag-active-directory","tag-anonymous","tag-community","tag-guest-blogger","tag-scripting-guy","tag-user-accounts","tag-windows-powershell"],"acf":[],"blog_post_summary":"<p>Summary: An anonymous blogger shares a domain migration script. Microsoft Scripting Guy, Ed Wilson, is here. Today&rsquo;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&hellip; We are a large international [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/2795","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/users\/596"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/comments?post=2795"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/posts\/2795\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media\/87096"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/media?parent=2795"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/categories?post=2795"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/scripting\/wp-json\/wp\/v2\/tags?post=2795"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}