Sidebar Menu

For a user migration from Windows 7 to Windows 10 it was needed to perform the following tasks automatically:

  • Move the user to a different OU
  • Add the user to a new VDI group
  • Add the users to new application groups
  • Remove the user from the previous application groups
  • Clear the profile path, if needed.

Fist, more information about the customer. The customer has Windows 7 on a non-persistent VDI environment. For the migration to Windows 10, also on a non-persistent VDI environment it was decided to recreate all the packages and add them to a new application group. The new application groups are in a different OU. There are two different OU's to differ the migrated and non-migrated users. There are 2 different VDI groups: one for Windows 7 and one for Windows 10. The packages where AppV 5.0 and shortcuts to applications on a share. 

I created a script to perform the user migration automatically. 

The activities in the correct order:

  1. Make an overview of all the user accounts that need to be migrated in a specific OU. This is done via the command
    ."\Migrate users (v30).ps1" -CreateFileWithUseridsInCSVFormat -srcOU "OU=Old OU,OU=OU1,DC=testdomain,DC=local,DC=lan"​

    There are columns predefined. You can always add your own columns, no problem. The columns you have created are ignored during the migration. 
    For example: during the migration at the customer site, I created an additional column to specify the migration date. So I had migration files for each migration date.

  2. Make an overview of all the application groups in a specific OU. This is done via the command:
    ."\Migrate users (v30).ps1" -CreateMappingsfileInCSVFormat -srcOU "OU=Old Groups OU,OU=OU1,DC=testdomain,DC=local,DC=lan"​

    You can open the mappings file with notepad. Just specify the new application group for each old group and you are done.

  3. You can test the migration:
    ."\Migrate users (v30).ps1 -PerformUserMigration" -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv​

    By default, the migration is run in a test mode. To do an actual migration you have to add the -ProductuionRun switch.

  4. Run the actual migration, create a roll back file and perform a direct- and indirect group membership inventory after the migration:
    ."\Migrate users (v30).ps1" -PerformUserMigration -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv -ProductionRun -RunDirectAndIndirectGroupInventory -CreateRollbackFile​
  5. Move all the users from one AD group to another AD group:
    ."\Migrate users (v30).ps1" -CopyADUserGroup -OldADUserGroup "Old AD Group" -NewADUserGroup New_Group -DeleteUserFromOldGroup​

     

  6. Check for empty groups and add the text '** EMPTY GROUP **' in the description field in case the group has no members. If the groups were empty and have members the text '** EMPTY GROUP **' is removed. The OU "OU=Applications OU,OU=OU1,DC=testdomain,DC=local,DC=lan" is used.
    ."\Migrate users (v30).ps1" -ScanForEmptyGroups -srcOU "OU=Applications OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -Production​
  7. Check for empty groups and delete these groups. The OU "OU=Applications OU,OU=OU1,DC=testdomain,DC=local,DC=lan" is used. Do this as a test.
    ."\Migrate users (v30).ps1" -ScanForEmptyGroups -srcOU "OU=Applications OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -DeleteEmptyGroups​

All the options can be found with:

Get-Help "Migrate users (v30).ps1"

 

The script Migrate users (v30).ps1

<#
.SYNOPSIS
    Helps to migrate users from - for example - Windows 7 to Windows 10.
 
.DESCRIPTION
    This script helps to migrate users from Windows 7 to Windows 10. It offers the following options:
    * Run an inventory from all the users in an OU and al its sub-OU's.
    * Move the users from one OU to another OU.
    * Add the users to a new group, based on a group mapping file.
    * Run the script is a test modus, so nothing is changed.
    * Shows the users' direct- and indirect group membership.
    * Copy content from the old profile to the new profile.
    * Deletes the Ivanti RES Settings profile. 
	* Shows the direct- and indirect members of an AD group.
	* Copy the groups from one user to another user (so-called copy of colleague).
	* Copy the users from one group to another group.
 
.EXAMPLE
     Perform the actions as described in the CSV Input File 'Userids-to-migrate.csv' in test mode:
     ."\Migrate users (v30).ps1" -PerformUserMigration -FileWithUseridsInCSVFormat Userids-to-migrate.csv

.EXAMPLE
     Perform the actions as described in the CSV Input File 'Userids-to-migrate.csv' and add the groups as per mappingsfile 'MappingOldGroupToNewGroup.csv' in test mode:
     ."\Migrate users (v30).ps1 -PerformUserMigration" -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv
 
.EXAMPLE
     Perform the actions as described in the CSV Input File 'Userids-to-migrate.csv' and add the groups as per mappingsfile 'MappingOldGroupToNewGroup.csv' in production:
     ."\Migrate users (v30).ps1" -PerformUserMigration -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv -ProductionRun

.EXAMPLE
     Perform the actions as described in the CSV Input File 'Userids-to-migrate.csv', add the groups as per mappingsfile 'MappingOldGroupToNewGroup.csv' and remove the Old RES Settings in production:
     ."\Migrate users (v30).ps1" -PerformUserMigration -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv -ProductionRun -DeleteRESSettingsFolder

.EXAMPLE
     Perform the actions as described in the CSV Input File 'Userids-to-migrate.csv' and add the groups as per mappings file 'MappingOldGroupToNewGroup.csv' in production.
     Also run both the direct- and indirect group membership inventory:
     ."\Migrate users (v30).ps1" -PerformUserMigration -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv -ProductionRun -RunDirectAndIndirectGroupInventory

.EXAMPLE
     Perform the actions as described in the CSV Input File 'Userids-to-migrate.csv' and add the groups as per mappingsfile 'MappingOldGroupToNewGroup.csv' in production. Also coppy the profile 
     Also run both the direct- and indirect group membership inventory:
     ."\Migrate users (v30).ps1" -PerformUserMigration -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv -ProductionRun -RunDirectAndIndirectGroupInventory -ProfilePathFrom "\\server\share\w 7" -ProfilePathTo \\server\share\w10

.EXAMPLE
     Perform the actions as described in the CSV Input File 'Userids-to-migrate.csv', add the groups as per mappingsfile 'MappingOldGroupToNewGroup.csv' and remove the old group in production:
     ."\Migrate users (v30).ps1" -PerformUserMigration -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv -ProductionRun -RemoveOldADGroups

.EXAMPLE
     Perform the actions as described in the CSV Input File 'Userids-to-migrate.csv', add the groups as per mappingsfile 'MappingOldGroupToNewGroup.csv', remove the old group in production and run the inventory for direct group membership:
     ."\Migrate users (v30).ps1" -PerformUserMigration -FileWithUseridsInCSVFormat Userids-to-migrate.csv -MapingsFileForAutomaticMigration MappingOldGroupToNewGroup.csv -ProductionRun -RemoveOldADGroups -RunDirectGroupInventory

.EXAMPLE
     Create a csv file with all the userids to migrate from the OU testdomain.local.lan\OU1\Old OU
     ."\Migrate users (v30).ps1" -CreateFileWithUseridsInCSVFormat -srcOU "OU=Old OU,OU=OU1,DC=testdomain,DC=local,DC=lan"

.EXAMPLE
     Create a csv file with all the userids to migrate from the OU testdomain.local.lan\OU1\Old OU, including all child OUs.
     ."\Migrate users (v30).ps1" -CreateFileWithUseridsInCSVFormat -srcOU "OU=Old OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -IncludeChildOUs

.EXAMPLE
     Create a csv file with all the userids to migrate from the OU testdomain.local.lan\OU1\Old OU. The new Citrix VDI group is 'Citrix10VDI'
     ."\Migrate users (v30).ps1" -CreateFileWithUseridsInCSVFormat -srcOU "OU=Old OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -Windows10VDIGroup Citrix10VDI

.EXAMPLE
     Create a csv file with all the userids to migrate from the OU testdomain.local.lan\OU1\Old OU. The new Citrix VDI group is 'Citrix10VDI', including all child OU's.
     ."\Migrate users (v30).ps1" -CreateFileWithUseridsInCSVFormat -srcOU "OU=Old OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -Windows10VDIGroup Citrix10VDI -IncludeChildOUs

.EXAMPLE
     Create a csv file with all the userids to migrate from the OU testdomain.local.lan\OU1\Ould OU. The new OU is testdomain.local.lan\OU1\OU2\Users. The new Citrix VDI group is 'Citrix10VDI'
     ."\Migrate users (v30).ps1" -CreateFileWithUseridsInCSVFormat -srcOU "OU=Old OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -dstOU "OU=Users,OU=OU2,OU=OU1,DC=testdomain,DC=local,DC=lan" -Windows10VDIGroup Citrix10VDI

.EXAMPLE
     Migrate the IE favorites and the desktop folder from the old profile to the new profile in test mode:
     ."\Migrate users (v30).ps1" -FileWithUseridsInCSVFormat Userids-to-migrate.csv -copyProfile -ProfilePathFrom "\\server\share\w 7" -ProfilePathTo \\server\share\w10

.EXAMPLE
     Migrate the IE favorites and the desktop folder from the old profile to the new profile in production:
     ."\Migrate users (v30).ps1" -FileWithUseridsInCSVFormat Userids-to-migrate.csv -copyProfile -ProfilePathFrom "\\server\share\w 7" -ProfilePathTo \\server\share\w10 -ProductionRun

.EXAMPLE
     Createa a mappingsfile based on all the groups in the OU OU=Old Groups OU,OU=OU1,DC=testdomain,DC=local,DC=lan
     ."\Migrate users (v30).ps1" -CreateMappingsfileInCSVFormat -srcOU "OU=Old Groups OU,OU=OU1,DC=testdomain,DC=local,DC=lan"

.EXAMPLE
    Write the direct members of a group to a CSV file
    ."\Migrate users (v30).ps1" -GroupInventory -GroupNameInventory appl_group -RunDirectGroupInventory

.EXAMPLE
    Write the direct and indirect members of multiple groups to a CSV file
    ."\Migrate users (v30).ps1" -GroupInventory -RunDirectAndIndirectGroupInventory -GroupNameInventory appl_group,"group with spaces",appl_group_2

.EXAMPLE
    Copy all the users and groups fron one group to another group in test mode
    ."\Migrate users (v30).ps1" -CopyADUserGroup -OldADUserGroup "Old AD Group" -NewADUserGroup New_Group

.EXAMPLE
    Move all the users and groups fron one group to another group in test mode
    ."\Migrate users (v30).ps1" -CopyADUserGroup -OldADUserGroup "Old AD Group" -NewADUserGroup New_Group -DeleteUserFromOldGroup

.EXAMPLE
    Move all the users and groups fron one group to another group in production
    ."\Migrate users (v30).ps1" -CopyADUserGroup -OldADUserGroup "Old AD Group" -NewADUserGroup New_Group -DeleteUserFromOldGroup -ProductionRun

.EXAMPLE
    Create a CSV input file with all the computers in the given OU, including child OUs.
    ."\Migrate users (v30).ps1" -CreateFileWithComputersInCSVFormat -srcOU "OU=Old Computers OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -dstOU "OU=New Computers OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -IncludeChildOUs

.EXAMPLE
    Move all the computers to the new OU in test mode.
    ."\Migrate users (v30).ps1" -PerformComputerMigration -FileWithComputersInCSVFormat "computers to migrate.csv"

.EXAMPLE
    Move all the computers to the new OU in production.
    ."\Migrate users (v30).ps1" -PerformComputerMigration -FileWithComputersInCSVFormat "computers to migrate.csv" -ProductionRun

.EXAMPLE
    Find all the direct and indirect groups te user 'Userid' belongs to.
    ."\Migrate users (v30).ps1" -UserInventory -RunDirectAndIndirectGroupInventory -srcUser Userid

.EXAMPLE
    Find all the direct groups te user 'Userid' belongs to.
    ."\Migrate users (v30).ps1" -UserInventory -RunDirectGroupInventory -srcUser Userid

.EXAMPLE
    Find all the users in the given OU, and the direct and indirect groups the user belongs to.
    ."\Migrate users (v30).ps1" -UserInventory -RunDirectGroupInventory -RunDirectAndIndirectGroupInventory -srcOU "OU=Old Computers OU,OU=OU1,DC=testdomain,DC=local,DC=lan"

.EXAMPLE
    Find all the users in the given OU, including the child OUs and the direct and indirect groups the user belongs to.
    ."\Migrate users (v30).ps1" -UserInventory -RunDirectGroupInventory -RunDirectAndIndirectGroupInventory -srcOU "OU=Old Computers OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -IncludeChildOUs

.EXAMPLE
    Copy the groups that user belongs to from user sourceuser to user destinationuser as a test run. See it as a 'Copy of colleague'.
    The destinationuser has the same groups as the sourceuser. 
    Create a rollback file.
    ."\Migrate users (v30).ps1" -CopyADUser -srcUser sourceuser -dstUser destinationuser -CreateRollbackFile

.EXAMPLE
    Copy the groups that user belongs to from user sourceuser to user destinationuser as a production run. See it as a 'Copy of colleague'.
    The destinationuser has the same groups as the sourceuser. 
    Create a rollback file.
    ."\Migrate users (v30).ps1" -CopyADUser -srcUser sourceuser -dstUser destinationuser -CreateRollbackFile -ProductionRun

.EXAMPLE
    Check for empty groups and add the text '** EMPTY GROUP **' in the description field in case the group has no members. If the groups were empty and have members the text '** EMPTY GROUP **' 
    is removed. The OU "OU=Applications OU,OU=OU1,DC=testdomain,DC=local,DC=lan" is used.
    ."\Migrate users (v30).ps1" -ScanForEmptyGroups -srcOU "OU=Applications OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -Production

.EXAMPLE
    Check for empty groups and delete these groups. The OU "OU=Applications OU,OU=OU1,DC=testdomain,DC=local,DC=lan" is used. Do this as a test.
    ."\Migrate users (v30).ps1" -ScanForEmptyGroups -srcOU "OU=Applications OU,OU=OU1,DC=testdomain,DC=local,DC=lan" -DeleteEmptyGroups

.NOTES
    Author:  Willem-Jan Vroom
    Website: https://www.vroom.cc/
    Twitter: @TheStingPilot

 v0.1:
   * Initial version.

 v0.2:
   * The logfilename has been changed.

 v0.3:
   * The logfile mentions 'RUNNING IN TEST MODE' in case the switch -ProductionRun is not used.

 v1.0:
   * Introduction automatic migration.
   * Introduction Indirect Group Inventory
   * Automatic creation of the File with the userids to migrate in CSV format.
   * Cleanup old Citrix groups
   * The results log file has a different lay-out
   * Parametersetnames
   * Improved help.
   * Copy desktop and favorites from the old profile to the new profile.
   * Clear the homedir path in Active Directory.

 v1.1:
   * Checks the input files for consistency before doing actions.
   * Renames the Ivanti Settings Folder.
   * Creates a basic mappings file, based on all groups in an OU and its child OUs.
   * The swith FileForAutomaticMigration has been renamed to MapingsFileForAutomaticMigration.
   * Group inventory

 v2.0
   * Option to copy or move users and groups from one group to another group.
   * Move computer objects from one OU to another OU.

 v2.1:
   * Read all the users from an OU and put all the groups the users belong to in a CSV file.
   * Moves only the user to another OU if the user is not already in that OU.
   * Clears the profile path, homedir and homepath only if not empty.

 v2.2:
   * Copy profile copies the old desktop folder to the folder 'Oude Desktop Windows 7' in the users' profile
   * Gives an error if the file cannot be copied, like FullName is too long (> 260 characters).
   * Option to copy user. 
   
 v2.3:
   * Introduction UserInventory.
   * OUGroupInventory has been replaced by UserInventory.
   * RunIndirectGroupInventory has been replaced by RunDirectAndIndirectGroupInventory
   * ConvertCSVFile has been added.
   * copyProfile also possible during the PerformUserMigration.
   * Solved a bug when making a CSV File with all the userids to migrate the source OU contains one member.
   * Verbose output in case -verbose has been used.
   * Introduction ExactMatch switch for both the Group- and UserInventory
   * The group- and userinventory shows the canonical name for both the user and the group.
   * ScanForEmptyGroups has been added. Delete empty groups if needed.
   * Solved a bug that throws up an error message in case the userid to migrate appears to be empty.

 v3.0:
   * New major version with updated format.
#>

[CmdLetBinding()]

param
 (
  [CmdletBinding(DefaultParameterSetName = "PerformUserMigration")]

  # Perform a user migration.
  [Parameter(Mandatory=$True,  ParameterSetName="PerformUserMigration")]
  [Switch]  $PerformUserMigration,

  # CSV Filename that contains all the userids that should be migrated. Default = the script name, with the csv extension.
  # The filename with the users to be modified.
  #
  # This file has the following layout:
  #
  # "Userid","NewOU","VDIGroup","GroupsToAdd","GroupsToRemove"
  # "userid1","OU=Users,OU=OU2,OU=OU1,DC=testdomain,DC=local,DC=lan","Citrix VDI Windows 10","Appl_Group1,Appl_Group2,Appl_Group3","old_appl_group1,old_appl_group2,old_appl_group3,old_appl_group4"
  # "userid2","OU=Users,OU=OU2,OU=OU1,DC=testdomain,DC=local,DC=lan","Citrix VDI Windows 10","",""
  # "userid3","OU=Users,OU=OU2,OU=OU1,DC=testdomain,DC=local,DC=lan","","",""
  # "userid4","","","","old_appl_group1,old_appl_group2"
  #
  # You can add more columns, but these columns are ignored. 
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Parameter(Mandatory=$False, ParameterSetName="CopyProfile")]
  [Parameter(Mandatory=$False, ParameterSetName="CloseOpenFiles")]
  [String]  $FileWithUseridsInCSVFormat = "",

  # CSV filename that contains the old and new group name for a fully automated migration. If not specified, then there is no automatic migration. Defualt is empty.
  #
  # This file has the following layout:
  #
  # "OldGroup","NewGroup"
  # "gg_appl_old1","appl_new1"
  # "gg_appl_old2","appl_new2"
  # "gg_appl_old1","appl_new3"
  # "gg_appl_old4","appl_new4"
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [String]  $MappingsFileForAutomaticMigration = "",

  # The name the logfile starts with. So the logfiles are grouped together.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Parameter(Mandatory=$False, ParameterSetName="CopyProfile")]
  [Parameter(Mandatory=$False, ParameterSetName="ConvertCSVFile")]
  [Parameter(Mandatory=$False, ParameterSetName="CreateFileWithUseridsInCSVFormat")]
  [Parameter(Mandatory=$False, ParameterSetName="CreateFileWithComputersInCSVFormat")]
  [Parameter(Mandatory=$False, ParameterSetName="CreateMappingsFileInCSVFormat")]
  [Parameter(Mandatory=$False, ParameterSetName="ScanForEmptyGroups")]
  [String]  $LogFilePrefix = "ZZZ-Logfile_",

  # Use this switch to update Active Directory. If not specified, the script is run in test mode.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Parameter(Mandatory=$False, ParameterSetName="CopyProfile")]
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUserGroup")]
  [Parameter(Mandatory=$False, ParameterSetName="PerformComputerMigration")]
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUser")]
  [Parameter(Mandatory=$False, ParameterSetName="ScanForEmptyGroups")]
  [Switch]  $ProductionRun,

  # Use this switch to create a rollback file. That makes it possible to perform a rollback in case of unpredicted behavior.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUserGroup")]
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUser")]
  [Switch]  $CreateRollBackFile,

  # Use this switch to delete Ivanti's RES Settings folder.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Switch]  $DeleteRESSettingsFolder,

  # Use this switch to remove all unneeded groups.
  # If this switch is used then the following groups will be removed from the users' account:
  # * All gg_appl groups
  # * WM-Users
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Switch]  $FullCleanUp,

  # Use this switch to remove the old AD groups in case of an automated migration.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Switch]  $RemoveOldADGroups,

  # Use this switch to clear the profile path.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Switch]  $ClearProfilePath,

  # Use this switch to clear the drive mapping and home folder.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Switch]  $ClearHomeFolder,

  # Use this switch to remove all the old Citrix groups from the users' account.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Switch]  $RemoveOldCitrixGroups,

  # Use this switch to display all the direct group membership in the result file.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Parameter(Mandatory=$False, ParameterSetName="UserInventory")]
  [Parameter(Mandatory=$False, ParameterSetName="GroupInventory")]
  [Switch]  $RunDirectGroupInventory,

  # Use this switch to display all the indirect group membership in the result file.
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [Parameter(Mandatory=$False, ParameterSetName="UserInventory")]
  [Parameter(Mandatory=$False, ParameterSetName="GroupInventory")]
  [Switch]  $RunDirectAndIndirectGroupInventory,

  # Use this switch to create a mappingsfile, based on all groups in an OU and its child OUs.
  [Parameter(Mandatory=$False, ParameterSetName="CreateMappingsFileInCSVFormat")]
  [Switch]  $CreateMappingsfileInCSVFormat,

  # Use this switch to create a file with all the userids to migrate based on an OU.
  [Parameter(Mandatory=$False, ParameterSetName="CreateFileWithUseridsInCSVFormat")]
  [Switch]  $CreateFileWithUseridsInCSVFormat,

  # Use this switch to create a file with all the computers to migrate based on an OU.
  [Parameter(Mandatory=$False, ParameterSetName="CreateFileWithComputersInCSVFormat")]
  [Switch]  $CreateFileWithComputersInCSVFormat,

  # The OU (distinguised name) where all the items are found that needs to be migrated. If the OU contains spaces, then add quotes around the OU name.
  [Parameter(Mandatory=$True,  ParameterSetName="CreateFileWithUseridsInCSVFormat")]
  [Parameter(Mandatory=$True,  ParameterSetName="CreateFileWithComputersInCSVFormat")]
  [Parameter(Mandatory=$True,  ParameterSetName="CreateMappingsFileInCSVFormat")]
  [Parameter(Mandatory=$False, ParameterSetName="UserInventory")]
  [Parameter(Mandatory=$False, ParameterSetName="GroupInventory")]
  [Parameter(Mandatory=$False, ParameterSetName="ScanForEmptyGroups")]
  [String]  $srcOU,

  # The destination OU (distinguised name). If this one can is empty then the users or computers are not moved to another OU. If the OU contains spaces, then add quotes around the OU name.
  [Parameter(Mandatory=$False, ParameterSetName="CreateFileWithUseridsInCSVFormat")]
  [Parameter(Mandatory=$True,  ParameterSetName="CreateFileWithComputersInCSVFormat")]
  [String]  $dstOU,

  # The new Windows 10 VDI group. If the VDI group contains spaces, then add quotes around it.
  [Parameter(Mandatory=$False, ParameterSetName="CreateFileWithUseridsInCSVFormat")]
  [String]  $Windows10VDIGroup="",

  # Include child OU's when creating the CSV file with the userids or the CSV file with the groups.
  [Parameter(Mandatory=$False, ParameterSetName="CreateFileWithUseridsInCSVFormat")]
  [Parameter(Mandatory=$False, ParameterSetName="CreateMappingsFileInCSVFormat")]
  [Parameter(Mandatory=$False, ParameterSetName="CreateFileWithComputersInCSVFormat")]
  [Parameter(Mandatory=$False, ParameterSetName="UserInventory")]
  [Parameter(Mandatory=$False, ParameterSetName="GroupInventory")]
  [Parameter(Mandatory=$False, ParameterSetName="ScanForEmptyGroups")]
  [Switch]  $IncludeChildOUs,

  # Perform a computer migration.
  [Parameter(Mandatory=$True,  ParameterSetName="PerformComputerMigration")]
  [Switch]  $PerformComputerMigration,

  # Convert a CSV file so that the migration tool can read it.
  [Parameter(Mandatory=$True,  ParameterSetName="ConvertCSVFile")]
  [Switch]  $ConvertCSVFile,

  # Give the filename.
  [Parameter(Mandatory=$True,  ParameterSetName="ConvertCSVFile")]
  [String]  $CSVFile,

  # Give the filename.
  [Parameter(Mandatory=$False, ParameterSetName="ConvertCSVFile")]
  [String]  $Delimeter=";",

  # Perform a computer migration.
  [Parameter(Mandatory=$True,  ParameterSetName="PerformComputerMigration")]
  [String]  $FileWithComputersInCSVFormat,

  # Use this switch if you want to copy desktop and favorites from the users' old profile to the users' new profile.
  [Parameter(Mandatory=$False, ParameterSetName="CopyProfile")]
  [Switch]  $copyProfile,

  # The old profile path (exluding userid). If the profile path contains spaces, then add quotes around it.
  [Parameter(Mandatory=$True,  ParameterSetName="CopyProfile")]
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [String]  $ProfilePathFrom="",

  # The new profile path (exluding userid). If the profile path contains spaces, then add quotes around it.
  [Parameter(Mandatory=$True,  ParameterSetName="CopyProfile")]
  [Parameter(Mandatory=$False, ParameterSetName="PerformUserMigration")]
  [String]  $ProfilePathTo="",

  # Run a group membership inventory
  [Parameter(Mandatory=$True,  ParameterSetName="GroupInventory")]
  [Switch]  $GroupInventory,

  # Run a group membership inventory
  [Parameter(Mandatory=$True,  ParameterSetName="UserInventory")]
  [Switch]  $UserInventory,

  # Specify the group names. If the name contains spaces, then add quotes around it. Use a comma as delimeter.
  [Parameter(Mandatory=$False, ParameterSetName="GroupInventory")]
  [String[]] $GroupNameInventory,

  # Close open files
  [Parameter(Mandatory=$False, ParameterSetName="CloseOpenFiles")]
  [Switch]  $CloseOpenFiles,

  # Use this switch if you wnant to copy or move users from one group to another group.
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUserGroup")]
  [Switch]  $CopyADUserGroup,

  # Specify group name the users are in now.
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUserGroup")]
  [String]  $OldADUserGroup,

  # Specify the nwe group name.
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUserGroup")]
  [String]  $NewADUserGroup,

  # Use this switch if you wnant to delete the user from the group they are in now.
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUserGroup")]
  [Switch]  $DeleteUserFromOldGroup,

  # Use this switch to copy all the groups fron one user to another user (also known as Copy of Colleague)
  [Parameter(Mandatory=$False, ParameterSetName="CopyADUser")]
  [Switch]  $CopyADUser,

  # Specify the source user
  [Parameter(Mandatory=$True,  ParameterSetName="CopyADUser")]
  [Parameter(Mandatory=$False, ParameterSetName="UserInventory")]
  [String]  $srcUser,

  # Specify the destination user
  [Parameter(Mandatory=$True,  ParameterSetName="CopyADUser")]
  [String]  $dstUser,

  # Perform an exact match.
  [Parameter(Mandatory=$False, ParameterSetName="UserInventory")]
  [Parameter(Mandatory=$False, ParameterSetName="GroupInventory")]
  [Switch]  $ExactMatch,

  # Find empty groups in the given OU and adds ** EMPTY GROUP ** in the description field.
  [Parameter(Mandatory=$True,  ParameterSetName="ScanForEmptyGroups")]
  [Switch]  $ScanForEmptyGroups,

  # Delete empty groups
  [Parameter(Mandatory=$False, ParameterSetName="ScanForEmptyGroups")]
  [Switch]  $DeleteEmptyGroups
)

# =============================================================================================================================================
# Function block
# =============================================================================================================================================

Function Write-EntryToResultsFile
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       03-August-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Write-EntryToResultsFile
=============================================================================================================================================
.SYNOPSIS

This function adds the success or failure information to the array that contains the log
information.

#>

Param
 (
  [String]  $strUserid,
  [String]  $strGroupName = "",
  [String]  $Result       = "",
  [String]  $Action       = "",
  [String]  $Message      = ""
 )

 $Record                = [ordered] @{"Timestamp"="";"Username" = "";"Groupname" = "";"Result"= "";"Action"= "";"Message"= ""}
 $Record."Timestamp"    = (Get-Date -UFormat "%a %e %b %Y %X").ToString()
 $Record."Username"     = $strUserid
 $Record."Groupname"    = $strGroupName
 $Record."Result"       = $Result
 $Record."Action"       = $Action
 $Record."Message"      = $Message
 $objRecord             = New-Object PSObject -Property $Record
 $Global:arrTable      += $objRecord

 Write-Verbose ">>  Write-EntryToResultsFile" 
 Write-Verbose "--> Entry written to the logfile:"
 Write-Verbose "     Username      = $strUserid"
 Write-Verbose "     Groupname     = $strGroupName"
 Write-Verbose "     Result        = $Result"
 Write-Verbose "     Action        = $Action"
 Write-Verbose "     Message       = $Message"
 Write-Verbose "#################################################################"

}

Function Export-ResultsLogFileToCSV
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Export-ResultsLogFileToCSV
=============================================================================================================================================
.SYNOPSIS

This function writes the logfile content to a CSV file.

#>

If($Global:arrTable.Count -gt 0)
 {
  $Global:arrTable | Export-Csv $strCSVLogFileSucces -NoTypeInformation
  Write-Verbose ">> Export-ResultsLogFileToCSV: The file '$strCSVLogFileSucces' has been written." 
 } 
  Else
 {
  Write-Error "Something went wrong while writing the logfile '$strCSVLogFileSucces'. Maybe nothing to report..." -Category CloseError 
 } 

}

Function Get-CanonicalName
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       3-June-2019
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Get-CanonicalName
=============================================================================================================================================
.SYNOPSIS

This function gets the canonical name of a user, group or OU.

#>

Param
 (
  [String]  $Name
 )

$strCanonicalName  = ""
$strObjectCategory = ""
   
If($strObjectCategory.length -eq 0)
 {
  $strObjectCategory = (Get-ADObject -Filter {sAMAccountName -eq $Name}    -Properties ObjectClass).ObjectClass
 }

If($strObjectCategory.length -eq 0)
 {
  $strObjectCategory = (Get-ADObject -Filter {Name -eq $Name}              -Properties ObjectClass).ObjectClass
 }

If($strObjectCategory.length -eq 0)
 {
  $strObjectCategory = (Get-ADObject -Filter {distinguishedName -eq $Name} -Properties ObjectClass).ObjectClass
 }
      
Write-Verbose ">> Get-CanonicalName: ObjectCategory for '$Name' is '$strObjectCategory'."
        
If($strObjectCategory -eq "group")
 {
  $strCanonicalName = (Get-ADGroup -Filter {distinguishedName -eq $Name} -Properties CanonicalName).CanonicalName
  If($strCanonicalName.Length -eq 0)
   {
    $strCanonicalName = (Get-ADGroup -Filter {Name -eq $Name} -Properties CanonicalName).CanonicalName
   }
 }
  ElseIf($strObjectCategory -eq "user")
 {
  If($strCanonicalName.Length -eq 0)
   {
    $strCanonicalName = (Get-ADUser -Filter {sAMAccountName -eq $Name} -Properties CanonicalName).CanonicalName
   }

  If($strCanonicalName.Length -eq 0)
   {
    $strCanonicalName = (Get-ADUser -Filter {Name -eq $Name}           -Properties CanonicalName).CanonicalName
   }
 }
   ElseIf($strObjectCategory -eq "computer")
 {
  If($strCanonicalName.Length -eq 0)
   {
    $strCanonicalName = (Get-ADComputer -Identity $Name                -Properties CanonicalName).CanonicalName
   }

  If($strCanonicalName.Length -eq 0)
   {
    $strCanonicalName = (Get-ADComputer -Filter {Name -eq $Name}       -Properties CanonicalName).CanonicalName
   }
 }
  Else
 {
  Try
   {
    $strCanonicalName = (Get-ADOrganizationalUnit -Identity $Name -Properties CanonicalName).CanonicalName
   }
    Catch
   {
    $strCanonicalName = "Object does not exists: $Name."
    Write-Verbose $strCanonicalName
    Return $strCanonicalName
   }
 }

 Write-Verbose ">> Get-CanonicalName: $strObjectCategory '$Name' has as canonical name '$strCanonicalName'."
 Return $strCanonicalName

}

Function Export-RollBackFileToCSV
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       29-November-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Export-RollBackFileToCSV
=============================================================================================================================================
.SYNOPSIS

This function writes the rollbackfile content to a CSV file.

#>

If($Global:arrTableWithRollBackRecords.Count -gt 0)
 {
  $Global:arrTableWithRollBackRecords | Export-Csv $strCSVRollBackFile -NoTypeInformation
 } 
  Else
 {
  Write-Error "Something went wrong while writing the rollbackfile $strCSVRollBackFile. Maybe nothing to report..." -Category CloseError
 } 

}

Function Remove-ProfilePathFromUserProfileInAD
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Remove-ProfilePathFromUserProfileInAD
=============================================================================================================================================
.SYNOPSIS

This function clears the ProfilePath from AD.

#>

Param
 (
  [String]  $strUserid
 )

$strProfilePath = (Get-ADUser -Identity $strUserid -Properties profilePath).profilePath
Write-EntryToResultsFile -strUserid $strUserid -Message "Profilepath: $strProfilePath" -Action "Inventory" -Result "Success"

Try
 {
  If($strProfilePath.Length -ge 1)
   {
    $strUserDN = (Get-ADUser -Identity $strUserid).distinguishedName
    Set-ADUser -Identity $strUserDN -Clear profilePath -WhatIf:(-not($ProductionRun)) -Confirm:$false
    Write-EntryToResultsFile -strUserid $strUserid -Action "Clear profile path" -Result "Success"
   }
    Else
   {
    Write-EntryToResultsFile -strUserid $strUserid -Action "Clear profile path" -Result "Information" -Message "Profile path has already been cleared."
   }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid $strUserid -Message $_.Exception.Message -Action "Clear profile path" -Result "Error"
  Continue
 }

}

Function Remove-HomeFolderPathFromUserProfileInAD
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Remove-HomeFolderPathFromUserProfileInAD
=============================================================================================================================================
.SYNOPSIS

This function clears the Home Folder Path from AD.

#>

Param
 (
  [String]  $strUserid
 )

$strHomeDrive     = (Get-ADUser -Identity $strUserid -Properties homeDrive).homeDrive
$strHomeDirectory = (Get-ADUser -Identity $strUserid -Properties homeDirectory).homeDirectory
Write-EntryToResultsFile -strUserid $strUserid -Message "Homedrive:     $strHomeDrive" -Action "Inventory" -Result "Success"
Write-EntryToResultsFile -strUserid $strUserid -Message "Homedirectory: $strHomeDirectory" -Action "Inventory" -Result "Success"

Try
 {
  If($strHomeDirectory.Length -ge 1)
   {
    $strUserDN = (Get-ADUser -Identity $strUserid).distinguishedName
    Set-ADUser -Identity $strUserDN -Clear homeDirectory -WhatIf:(-not($ProductionRun)) -Confirm:$false
    Write-EntryToResultsFile -strUserid $strUserid -Action "Clear homedirectory path" -Result "Success"
   }
    Else
   {
    Write-EntryToResultsFile -strUserid $strUserid -Action "Clear homedirectory path" -Result "Information" -Message "The homedirectory path has already been cleared."
   }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid $strUserid -Message $_.Exception.Message -Action "Clear homedirectory path" -Result "Error"
  Continue
 }

Try
 {
  If($strHomeDrive.Length -ge 1)
   {
    $strUserDN = (Get-ADUser -Identity $strUserid).distinguishedName
    Set-ADUser -Identity $strUserDN -Clear homeDrive -WhatIf:(-not($ProductionRun)) -Confirm:$false
    Write-EntryToResultsFile -strUserid $strUserid -Action "Clear homedrive" -Result "Success"
   }
    Else
   {
    Write-EntryToResultsFile -strUserid $strUserid -Action "Clear homedrive" -Result "Information" -Message "The homedrive has already been cleared."
   }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid $strUserid -Message $_.Exception.Message -Action "Clear homedrive" -Result "Error"
  Continue
 }

}

Function Move-ADUserToOtherOU
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Move-ADUserToOtherOU
=============================================================================================================================================
.SYNOPSIS

This function moves the user to another OU.

#>

Param
 (
  [String]  $strUserid,
  [String]  $strDestinationOU
 )

Try
 {
  If($strDestinationOU.Length -gt 0)
   {
    $strUserDN = (Get-ADUser -Identity $strUserid).distinguishedName
    $strCurrentOU = "OU="+ ($strUserDN -split "=",3)[-1]
    If($strDestinationOU -ne $strCurrentOU)
     {
      Move-ADObject -Identity $strUserDN -TargetPath $strDestinationOU -WhatIf:(-not($ProductionRun)) -Confirm:$false
      Write-EntryToResultsFile -strUserid $strUserid -Action "Move AD User to OU $strDestinationOU." -Result "Success"
     }
      Else
     {
      Write-EntryToResultsFile -strUserid $strUserid -Action "Move AD User to OU $strDestinationOU." -Result "Information" -Message "The user is already in the OU $strDestinationOU." 
     }
   }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid $strUserid -Message $_.Exception.Message -Action "Move AD User to OU $strDestinationOU." -Result "Error"
  Continue
 }

}

Function Move-ADComputerToOtherOU
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       28-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Move-ADComputerToOtherOU
=============================================================================================================================================
.SYNOPSIS

This function moves the user to another OU.

#>

Param
 (
  [String]  $strComputerName,
  [String]  $strDestinationOU
 )

Try
 {
  If($strDestinationOU.Length -gt 0)
   {
    $strComputerDN = (Get-ADComputer -Identity $strComputerName).distinguishedName
    Move-ADObject -Identity $strComputerDN -TargetPath $strDestinationOU -WhatIf:(-not($ProductionRun)) -Confirm:$false
    Write-EntryToResultsFile -strUserid "Computer: $strComputerName" -Action "Move AD computer to OU $(Get-CanonicalName -Name strDestinationOU)." -Result "Success"
   }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid "Computer: $strComputerName" -Message $_.Exception.Message -Action "Move AD computer to OU $strDestinationOU." -Result "Error"
  Continue
 }

}

Function Add-ADMemberToGroup
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Add-ADMemberToGroup
=============================================================================================================================================
.SYNOPSIS

This function adds a user to an AD group.

#>

Param
 (
  [String]  $sAMAccountName,
  [String]  $ADGroupName
 )

$strPrimaryGroup = ""

Try
 {
  If($ADGroupName.Length -gt 0)
   {
    $objectclass = (Get-ADObject -Filter {SamAccountName -eq $sAMAccountName}).ObjectClass

    If($objectClass -eq "User")
     {
      $strDN           = (Get-ADUser -Identity $sAMAccountName).distinguishedName
      $strPrimaryGroup = (Get-ADuser $sAMAccountName -Properties PrimaryGroup).PrimaryGroup
     }
      Else
     {
      $strDN = (Get-ADGroup -Identity $sAMAccountName).distinguishedName
     }

    If($strPrimaryGroup -ne $DNADGroupName)
     {
      Add-ADPrincipalGroupMembership -Identity $strDN -MemberOf $ADGroupName -WhatIf:(-not($ProductionRun)) -Confirm:$false
      Write-EntryToResultsFile -strUserid $sAMAccountName -Action "Add AD group $ADGroupName to $objectClass $sAMAccountName." -Result "Success"
      If($Global:strForRollBackGroupsToRemove.Length -eq 0)
       {
        $Global:strForRollBackGroupsToRemove = $ADGroupName
       }
        Else
       {
        $Global:strForRollBackGroupsToRemove = $Global:strForRollBackGroupsToRemove + "," + $ADGroupName
       }
     }
      Else
     {
      Write-EntryToResultsFile -strUserid $sAMAccountName -Action "Add AD group $ADGroupName to $objectClass $sAMAccountName." -Result "Information" -Message "Group $ADGroupName is the primary group name."
     }
   }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid $sAMAccountName -Message $_.Exception.Message -Action "Add AD group $ADGroupName to $objectClass $sAMAccountName." -Result "Error"
  Continue
 }

}

Function Remove-ADMemberFromGroup
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Remove-ADMemberFromGroup
=============================================================================================================================================
.SYNOPSIS

This function removes a user from an AD group.

#>

Param
 (
  [String]  $sAMAccountName,
  [String]  $ADGroupName
 )

$strPrimaryGroup = "" 

Try
 {
  If($ADGroupName.Length -gt 0)
   {
    $objectclass = (Get-ADObject -Filter {SamAccountName -eq $sAMAccountName}).ObjectClass

    If($objectClass -eq "User")
     {
      $strDN           = (Get-ADUser -Identity $sAMAccountName).distinguishedName
      $strPrimaryGroup = (Get-ADuser $sAMAccountName -Properties PrimaryGroup).PrimaryGroup
     }
      Else
     {
      $strDN = (Get-ADGroup -Identity $sAMAccountName).distinguishedName
     }
    $DNADGroupName = (Get-ADGroup -Identity $ADGroupName).distinguishedName

    If($strPrimaryGroup -ne $DNADGroupName)
     {
      Remove-ADPrincipalGroupMembership -Identity $strDN -MemberOf $ADGroupName -WhatIf:(-not($ProductionRun)) -Confirm:$false
      Write-EntryToResultsFile -strUserid $sAMAccountName -Action "Remove AD group $ADGroupName from $objectClass $sAMAccountName." -Result "Success"
      If($Global:strForRollBackGroupsToAdd.Length -eq 0)
       {
        $Global:strForRollBackGroupsToAdd = $ADGroupName
       }
        Else
       {
        $Global:strForRollBackGroupsToAdd = $Global:strForRollBackGroupsToAdd + "," + $ADGroupName
       }
     }
      Else
     {
      Write-EntryToResultsFile -strUserid $sAMAccountName -Action "Remove AD group $ADGroupName from $objectClass $sAMAccountName." -Result "Information" -Message "Group $ADGroupName is the primary group name."
     }
   }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid $sAMAccountName -Message $_.Exception.Message -Action "Remove AD group $ADGroupName from $objectClass $sAMAccountName." -Result "Error"
  Continue
 }

}

Function Remove-UserFromMultipleGroups
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       12-October-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Remove-UserFromMultipleGroups
=============================================================================================================================================
.SYNOPSIS

Delete multiple groups from the users' account. The group names are in an array.

#>

Param
 (
  [String]  $arrGroupsToSearchFor,
  [String]  $strUserid
 )

$arrGroups = @(Get-ADUser $strUserid -Properties MemberOf).MemberOf
ForEach($objGroup in $arrGroups)
 {
  $strGroup=(Get-ADGroup $objGroup).Name
  ForEach($objGroupMustContain in $arrGroupsToSearchFor)
   {
    $strGroupMustContain = $objGroupMustContain.ToString().ToLower()

    If($strGroup.ToLower().IndexOf($strGroupMustContain) -eq 0)
     {
      Remove-ADMemberFromGroup -sAMAccountName $strUserid -ADGroupName $strGroup
     }  
   }
 }

}

Function Migrate-ADUserToNewGroup
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       11-October-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Migrate-ADUserToNewGroup
=============================================================================================================================================
.SYNOPSIS

Perform the translating from the old group name to the new group name.

#>

Param
 (
  [String]  $strUserid,
  [String]  $strADGroupName
 )
    
$bolOldGroupNameHasBeenFound = $False
   
ForEach($objGroupNameForAutomaticMigration in $arrGroupNamesForAutomaticMigration)
 {
  Try
   {
    $strOldGroup = $objGroupNameForAutomaticMigration.OldGroup
    $strNewGroup = $objGroupNameForAutomaticMigration.NewGroup
   }
    Catch
   {
    Write-Error "There is something wrong with the CSV filename $MappingsFileForAutomaticMigration while processing $strUserid." -Category OpenError
    Exit 1 
   }
     
  If($strADGroupName -eq $strOldGroup)
   {
    $bolOldGroupNameHasBeenFound = $true
    If($strNewGroup.Length -gt 0)
     {
      Write-Verbose ">> Migrate-ADUserToNewGroup: Add user $((Get-ADUser $strUserid).Name) to the AD group $strOldGroup."
      Add-ADMemberToGroup -sAMAccountName $strUserid -ADGroupName $strNewGroup
      If($RemoveOldADGroups)
       {
        Write-Verbose ">> Migrate-ADUserToNewGroup: Removing user $((Get-ADUser $strUserid).Name) from the AD group $strOldGroup."
        Remove-ADMemberFromGroup -sAMAccountName $strUserid -ADGroupName $strOldGroup
       }    
     }
      Else
     {
      Write-EntryToResultsFile -strUserid $strUserid -Action "Migrate users" -Message "No new groupname specified for $strADGroupName in $MappingsFileForAutomaticMigration." -Result "Error"
     }
   }
  }
  
If(-not($bolOldGroupNameHasBeenFound))
 {
  Write-EntryToResultsFile -strUserid $strUserid -Action "Migrate users" -Message "The group $strADGroupName has not been found in $MappingsFileForAutomaticMigration." -Result "Error"
 }

}

Function Find-InArry
{
<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       17-October-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Find-InArry
=============================================================================================================================================
.SYNOPSIS

This function checks two arrays for content. I had to write my own function as the build-in functions like
Compare-Object or $arrOnlyIndirectMembers | Where {$arrDirectAndIndirectMembers -notcontains $arrDirectMembers}
did not did their work properly. 

The switch 'NotContains' can be $True or $False. If not specified on the command line, then it is $False.

#>

Param
 (
  [String[]] $SearchIn,
  [String[]] $LookFor,
  [Switch]   $NotContains
 )

$Contains = -not $NotContains

$tmpArray = @()
ForEach($objSearchIn in $SearchIn)
 {
  $bolFound = $false
  ForEach($objLookFor in $LookFor)
   {
    If($objSearchIn -eq $objLookFor)
     {
      $bolFound = $True
      If($bolFound -and $Contains)
      {
       $tmpArray+=$objLookFor
      }
     } 
   }

  If(-not($bolFound) -and -not $Contains)
   {
    $tmpArray+=$objSearchIn
   }
 }
 Return $tmpArray

}

Function Show-IndirectGroupsInResultsFile
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       18-October-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Show-IndirectGroupsInResultsFile
=============================================================================================================================================
.SYNOPSIS

This function shows only the indirect group membership in the results file.

#>

Param
 (
  [String]  $strUserid
 )
     
Try
 {   
  $dn                          = (Get-ADUser $strUserid).DistinguishedName
  $arrOnlyDirectMembers        = @()
  $arrDirectAndIndirectMembers = @()
  $arrOnlyIndirectMembers      = @()

  $arrDirectAndIndirectMembers = Get-ADGroup  -LDAPFilter ("(member:1.2.840.113556.1.4.1941:={0})" -f $dn)   | Select-Object sAMAccountName | Sort-Object sAMAccountName
  $arrOnlyDirectMembers        = Get-ADPrincipalGroupMembership $dn                                          | Select-Object sAMAccountName | Sort-Object sAMAccountName
  $arrOnlyIndirectMembers      = Find-InArry -SearchIn $arrDirectAndIndirectMembers -NotContains -LookFor $arrOnlyDirectMembers

  $arrOnlyIndirectMembers | ForEach($_) {
   $tmpValue = $_
   $tmpValue = $tmpValue -Replace("@{sAMAccountName=","")
   $tmpValue = $tmpValue -Replace("}","")
   Write-EntryToResultsFile -strUserid $strUserid -strGroupName $tmpValue -Result "Information" -Action "User '$(Get-CanonicalName -Name $strUserid)' indirect member of the group '$(Get-CanonicalName -Name $tmpValue)'" -Message ""
  }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid $strUserid -Result "Eror" -Action "Indirect group member" -Message $_.Exception.Message
 }

}

Function Show-DirectGroupsInResultsFile
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       15-November-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Show-DirectGroupsInResultsFile
=============================================================================================================================================
.SYNOPSIS

This function shows only the direct group membership in the results file.

#>

Param
 (
  [String]  $strUserid
 )

Try
 {
  $arrOnlyDirectMembers        = @()  
  $arrOnlyDirectMembers        = Get-ADPrincipalGroupMembership ((Get-ADUser $strUserid).DistinguishedName)  | Select-Object sAMAccountName | Sort-Object sAMAccountName
  $arrOnlyDirectMembers | ForEach($_) {
    $tmpValue = $_.sAMAccountName    
    Write-EntryToResultsFile -strUserid $strUserid -strGroupName $tmpValue -Result "Information" -Action "User '$(Get-CanonicalName -Name $strUserid)' direct member of the group '$(Get-CanonicalName -Name $tmpValue)'" -Message ""
  }
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid $strUserid -Result "Error" -Action "Direct group member" -Message $_.Exception.Message
 }

}

Function Create-FileWithUseridsInCSVFormat
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       18-October-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Create-FileWithUseridsInCSVFormat
=============================================================================================================================================
.SYNOPSIS

This function creates the file with all the userids to migrate, based on OU.

#>

Param
 (
  [String]  $srcOU ="",
  [String]  $dstOU= "",
  [String]  $Windows10VDIGroup= "",
  [Switch]  $IncChildOU
 )

$strBaseFileName = "UseridsToMigrate (" + (Get-Date).ToString('G') + ").csv"
$strBaseFileName = $strBaseFileName -replace ":","-"
$strBaseFileName = $strBaseFileName -replace "/","-"
$strFileName     = $strCurrentPath + "\" + $strBaseFileName
      
If($strFileName.Length -gt 260)
 {
  $valLength       = ($strCurrentPath.Length)-4
  $strBaseFileName = ($strBaseFileName.Substring(0,260-$valLength)) + ".csv"
  $strFileName     = $strCurrentPath + "\" + $strBaseFileName
 }

$arrUserids          = @()
$arrTableWithUserids = @()
$strMessage          = "Discovering users in $srcOU."
$strActivity         = "Create CSV File with users in the OU '$(Get-CanonicalName -Name $srcOU)'."

$strSearchScope = "OneLevel"
If($IncludeChildOUs)
 {
  $strSearchScope = "SubTree"
  $strMessage  += " (Including child OU's)" 
  $strActivity += " (Including child OU's)"
 }

Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU users" -Message $strMessage

Try
 {
  $arrUserids          = @(Get-ADUser -Filter {Enabled -eq "True"} -SearchScope $strSearchScope -SearchBase $srcOU)
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Error" -Action "Discover OU users" -Message "There is an error: $($_.Exception.Message). Check if '$srcOU' exists."
  Exit 1
 }

If($arrUserids.Count -eq 0)
 {
  Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU users" -Message "The OU $srcOU does not contain any users."
  Return
 }

$valCounter          = 1
   
$Users = ForEach($objUserid in $arrUserids)
 {
  $strUserDetails = Get-ADUser $objUserid -Properties Name,sAMAccountName,department,cn, DistinguishedName, mail, description
  Write-Verbose ">> Create-FileWithUseridsInCSVFormat -> Processing: $(Get-CanonicalName -Name $strUserDetails.sAMAccountName)"  
  Write-Progress -Activity $strActivity -Status "Processing user $(Get-CanonicalName -Name $strUserDetails.sAMAccountName)." -PercentComplete ($valCounter / $arrUserids.Count * 100)
  $UserRecord                   = [ordered] @{"Userid" = "";"Full name"= "";"Mail"="";"Department"= "";"Description"="";"CurrentOU"= "";"NewOU"="";"VDIGroup"= "";"GroupsToAdd"= "";"GroupsToRemove"= ""}
  $UserRecord."Userid"          = $strUserDetails.sAMAccountName
  $UserRecord."Full name"       = $strUserDetails.cn
  $UserRecord."Mail"            = $strUserDetails.mail
  $UserRecord."Department"      = $strUserDetails.department
  $UserRecord."Description"     = $strUserDetails.description
  $UserRecord."CurrentOU"       = "OU="+ ($strUserDetails.DistinguishedName -split "=",3)[-1]
  $UserRecord."NewOU"           = $dstOU
  $UserRecord."VDIGroup"        = $Windows10VDIGroup
  $UserRecord."GroupsToAdd"     = ""
  $UserRecord."GroupsToRemove"  = ""
  $objRecordWithUserids         = New-Object PSObject -Property $UserRecord
  $arrTableWithUserids         += $objRecordWithUserids
  $valCounter++
 }

If($arrTableWithUserids.Count -gt 0)
 {
  $arrTableWithUserids | Select-Object * | Sort-Object NewOU, Userid | Export-Csv $strFileName -NoTypeInformation
  $strMessage = $strMessage -replace("Discovering","Finished with discovering")
  Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU users" -Message "Finished discovering users in $srcOU."
 }
  Else
 {
  Write-Error "Something went wrong while writing the file $strFileName. Maybe nothing to report..." -Category CloseError
 } 

}

Function Create-MappingsFileInCSVFormat
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       7-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Create-MappingsFileInCSVFormat
=============================================================================================================================================
.SYNOPSIS

This function craetes the mappingsfile, based on an OU.

#>

Param
 (
  [String]  $srcOU ="",
  [Switch]  $IncChildOU
 )

$strBaseFileName = "MappingsfileOldToNewGroups (" + (Get-Date).ToString('G') + ").csv"
$strBaseFileName = $strBaseFileName -replace ":","-"
$strBaseFileName = $strBaseFileName -replace "/","-"
$strFileName     = $strCurrentPath + "\" + $strBaseFileName

If($strFileName.Length -gt 260)
 {
  $valLength       = ($strCurrentPath.Length)-4
  $strBaseFileName = ($strBaseFileName.Substring(0,260-$valLength)) + ".csv"
  $strFileName     = $strCurrentPath + "\" + $strBaseFileName
 }

$arrTableWithGroups = @()
$arrGroups          = @()
$strMessage         = "Discovering groups in $srcOU."

$strSearchScope = "OneLevel"
If($IncludeChildOUs)
 {
  $strSearchScope = "SubTree"
  $strMessage += " (Including child OU's)"
 }

Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU groups." -Message $strMessage
Try
 {
  $arrGroups          = @(Get-ADGroup -Filter {GroupCategory -eq 'security'} -SearchBase $srcOU -SearchScope $strSearchScope -Properties Name)
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Error" -Action "Discover OU groups." -Message "There is an error: $($_.Exception.Message). Check if '$srcOU' exists."
  Exit 1
 }

If($arrGroups.Count -eq 0)
 {
  Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU groups." -Message "The OU $srcOU does not contain any groups."
  Return
 }

$valCounter         = 1

ForEach($objGroup in $arrGroups)
 {
  $GroupRecord = [ordered] @{"OldGroup"="";"NewGroup"=""}
  $GroupRecord."OldGroup"     = $objGroup.Name
  $GroupRecord."NewGroup"     = ""
  $objRecordWithGroup         = New-Object PSObject -Property $GroupRecord
  $arrTableWithGroups        += $objRecordWithGroup
  $valCounter++
 }
   
If($arrTableWithGroups.Count -ge 0)
 {
  $arrTableWithGroups | Export-Csv $strFileName -NoTypeInformation
  $strMessage = $strMessage -replace("Discovering","Finished with discovering")
  Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU groups." -Message "Finished discovering users in $srcOU."
 }
  Else
 {
  Write-Error "Something went wrong while writing the file $strFileName. Maybe nothing to report..."
 } 

}

Function Create-FileWithComputersInCSVFormat
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       28-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Create-FileWithComputersInCSVFormat
=============================================================================================================================================
.SYNOPSIS

This function creates the file with all the computers to move to another OU.

#>

Param
 (
  [String]  $srcOU ="",
  [String]  $dstOU= "",
  [Switch]  $IncChildOU
 )

$strBaseFileName = "Computers (" + (Get-Date).ToString('G') + ").csv"
$strBaseFileName = $strBaseFileName -replace ":","-"
$strBaseFileName = $strBaseFileName -replace "/","-"
$strFileName     = $strCurrentPath + "\" + $strBaseFileName
      
If($strFileName.Length -gt 260)
 {
  $valLength       = ($strCurrentPath.Length)-4
  $strBaseFileName = ($strBaseFileName.Substring(0,260-$valLength)) + ".csv"
  $strFileName     = $strCurrentPath + "\" + $strBaseFileName
 }

$arrTableWithComputers = @()
$arrComputers          = @()
$strMessage            = "Discovering computers in $srcOU."
$strActivity           = "Create CSV File with computers in the OU '$(Get-CanonicalName -Name $srcOU)'."

$strSearchScope = "OneLevel"
If($IncludeChildOUs)
 {
  $strSearchScope = "SubTree"
  $strMessage    += " (Including child OU's)"
  $strActivity   += " (Including child OU's)"
 }
     
Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU computers." -Message $strMessage

Try
 {
  $arrComputers          = @(Get-ADComputer -Filter {Enabled -eq "True"} -SearchScope $strSearchScope -SearchBase $srcOU)
 }
  Catch
 {
  Write-Error "There is an error: $($_.Exception.Message). Check if '$srcOU' exists." -Category ObjectNotFound
  Exit 1
 }

If($arrComputers.Count -eq 0)
 {
  Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU computers" -Message "The OU $srcOU does not contain any computers."
  Return
 }

$valCounter          = 1

$Users = ForEach($objComputer in $arrComputers)
 {
  $strComputerDetails = Get-ADComputer $objComputer -Properties cn,DistinguishedName
  Write-Progress -Activity $strActivity -Status "Processing user $(Get-CanonicalName -Name $strComputerDetails.cn)." -PercentComplete ($valCounter / $arrComputers.Count * 100)
  $ComputerRecord                   = [ordered] @{"Computername" = "";"CurrentOU"= "";"NewOU"=""}
  $ComputerRecord."Computername"    = $strComputerDetails.cn
  $ComputerRecord."CurrentOU"       = "OU="+ ($strComputerDetails.DistinguishedName -split "=",3)[-1]
  $ComputerRecord."NewOU"           = $dstOU
  $objRecordWithComputer            = New-Object PSObject -Property $ComputerRecord
  $arrTableWithComputers           += $objRecordWithComputer
  $valCounter++
 }

If($arrTableWithComputers.Count -gt 0)
 {
  $arrTableWithComputers | Export-Csv $strFileName -NoTypeInformation
  $strMessage = $strMessage -replace("Discovering","Finished with discovering")
  Write-EntryToResultsFile -strUserid "" -strGroupName "" -Result "Information" -Action "Discover OU computers." -Message "Finished discovering users in $srcOU."
 }
  Else
 {
  Write-Error "Something went wrong while writing the file $strFileName. Maybe nothing to report..." -Category CloseError
 } 

}

Function Add-TrailingBackSlash
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Add-TrailingBackSlash
=============================================================================================================================================
.SYNOPSIS

This function adds a trailing backslash to a string
#>

Param
 (
  [String]  $AddBackslashTo ="" 
 )

If($AddBackslashTo.Length -gt 0)
 { 
  If(($AddBackslashTo.SubString($AddBackslashTo.Length-1,1)) -ne "\") 
  {
   $AddBackslashTo = $AddBackslashTo + "\"
  }
 }
  Else
 {
  $AddBackslashTo = "\"
 }
 Return $AddBackslashTo

}

Function Copy-UserProfileFiles
{
<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Copy-UserProfileFiles
Source:           https://techblog.dorogin.com/powershell-how-to-recursively-copy-a-folder-structure-excluding-some-child-folders-and-files-a1de7e70f1b
=============================================================================================================================================
.SYNOPSIS

This function really copies the files.
#> 

Param
 (
  [String]  $OldFilesDirectory,
  [String]  $NewFilesDirectory
 )

Write-Verbose ">> Copy-UserProfileFiles: Copying userdata from $OldFilesDirectory to $NewFilesDirectory." 
$arrExclude      = @("desktop.ini","$RECYCLE.BIN","Google Chrome.lnk","thumbs.db")
$arrExcludeMatch = @(".pst")
New-Item -Path $NewFilesDirectory -ItemType Directory -Force -WhatIf:(-not($ProductionRun)) | Out-Null
Try
 {
  $AllDetectedErrors = ""
  Get-ChildItem -Path $OldFilesDirectory -Recurse -Exclude $arrExclude -ErrorAction SilentlyContinue -ErrorVariable AllDetectedErrors|
  where { $arrExcludeMatch -eq $null -or $_.FullName.Replace($OldFilesDirectory, "") -notmatch $arrExcludeMatch } | 
  Copy-Item -Destination {
   If($_.PSIsContainer) 
    {
     Join-Path $NewFilesDirectory $_.Parent.FullName.Substring($OldFilesDirectory.length)
    } 
     Else 
    {
     Join-Path $NewFilesDirectory $_.FullName.Substring($OldFilesDirectory.length)
    }
   } -Force -Exclude $arrExclude -WhatIf:(-not($ProductionRun)) -Confirm:$false -Erroraction SilentlyContinue
   If($AllDetectedErrors.Length -gt 0)
    {
     ForEach($objError in $AllDetectedErrors)
      {
       Write-EntryToResultsFile -strUserid $Userid -Result "Error" -Action "Copy from $OldFilesDirectory to $NewFilesDirectory." -Message "File '$($objError.TargetObject)' cannot be copied. Error: '$($objError.Exception.Message)'." 
      }
     }
      Else
     {
      Write-EntryToResultsFile -strUserid $Userid -Result "Success" -Action "Copy from $OldFilesDirectory to $NewFilesDirectory."
     }
 }
  Catch
   {
    Write-EntryToResultsFile -strUserid $Userid -Result "Error" -Action "Copy from $OldFilesDirectory to $NewFilesDirectory." -Message $_.Exception.Message
    Continue 
   }

}

Function Copy-UserProfile
{
<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Copy-UserProfile
=============================================================================================================================================
.SYNOPSIS

This function copies the favorites and desktop from the old profile to the new profile

#>

Param
 (
  [String]  $Userid="",
  [String]  $OldProfilePath="",
  [String]  $NewProfilePath=""
 )

# Perform some checking
$bolErrorsFound = $False

$OldProfilePath = (Add-TrailingBackSlash -AddBackslashTo $OldProfilePath) + $Userid 
$NewProfilePath = (Add-TrailingBackSlash -AddBackslashTo $NewProfilePath) + $Userid

If(-not(Test-Path($OldProfilePath)))
 {
  $bolErrorsFound = $True
  Write-EntryToResultsFile -strUserid $Userid -Result "Error" -Action "Check directory" -Message "The directory $OldProfilePath does not exists."
 }

If(-not(Test-Path($NewProfilePath)))
 {
  $bolErrorsFound = $True
  Write-EntryToResultsFile -strUserid $Userid -Result "Error" -Action "Check directory" -Message "The directory $NewProfilePath does not exists."
 }

If(Test-Path($NewProfilePath + "\Oude desktop Windows 7"))
 {
  $bolErrorsFound = $True
  Write-EntryToResultsFile -strUserid $Userid -Result "Information" -Action "Check directory" -Message "The directory $($NewProfilePath + "\Oude desktop Windows 7") exists. That means that the profile has already been copied."
 }

If($bolErrorsFound)
 {
  Return
 }

# Copy the Favorites and desktop

Copy-UserProfileFiles -OldFilesDirectory ($OldProfilePath +"\Favorites") -NewFilesDirectory ($NewProfilePath +"\Favorites")
Copy-UserProfileFiles -OldFilesDirectory ($OldProfilePath +"\Desktop")   -NewFilesDirectory ($NewProfilePath +"\Oude desktop Windows 7")

}

Function Add-RollBackRecord
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       06-September-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Add-RollBackRecord
=============================================================================================================================================
.SYNOPSIS

This function adds an item to the rollback file

#>

Param
 (
  [String]  $Userid,
  [String]  $NewOU,
  [String]  $VDIGroup,
  [String]  $GroupsToAdd,
  [String]  $GroupsToRemove
 )
  
$RollBackRecord                       = [ordered] @{"Userid" = "";"NewOU"="";"VDIGroup"= "";"GroupsToAdd"= "";"GroupsToRemove"= ""}
$RollBackRecord."Userid"              = $Userid
$RollBackRecord."NewOU"               = $NewOU
$RollBackRecord."VDIGroup"            = $VDIGroup
$RollBackRecord."GroupsToAdd"         = $GroupsToAdd
$RollBackRecord."GroupsToRemove"      = $GroupsToRemove
$objRollBackRecord                    = New-Object PSObject -Property $RollBackRecord
$Global:arrTableWithRollBackRecords  += $objRollBackRecord

}

Function Run-ConsistencyCheckOnInputCSVFile
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       03-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Run-ConsistencyCheckOnInputCSVFile
=============================================================================================================================================
.SYNOPSIS

This function checks for errors in the InputCSVFile. Errors are written to the Log file in CSV format.

#>

Param
 (
  [String]  $InputCSVFile
 )

If(Test-Path $InputCSVFile)
 {
  $arrUserids = @(Import-Csv $InputCSVFile)
 }
  Else
 {
  Write-Error "The import file $InputCSVFile does not exists."
  Exit 1
 }
  
$bolErrorsFound = $False

ForEach($objUser in $arrUserids)
 {
  $strUserid         = $objUser.Userid
  Try
   {
    $strNewOU          = $objUser.NewOU
    $strVDIGroup       = $objUser.VDIGroup
    $arrGroupsToAdd    = $objUser.GroupsToAdd.Split(",")
    $arrGroupsToRemove = $objUser.GroupsToRemove.Split(",")
   }
    Catch
   {
    $bolErrorsFound = $True
    Write-EntryToResultsFile -strUserid $strUserid -Result "Error" -Message "There is something wrong with the CSV filename $InputCSVFile while processing $strUserid."
   }
  }

If($bolErrorsFound)
 {
  Export-ResultsLogFileToCSV
  Exit 1
 }

}

Function Run-ConsistencyCheckOnMappingsCSVFile
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       03-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Run-ConsistencyCheckOnMappingsCSVFile
=============================================================================================================================================
.SYNOPSIS

This function checks for errors in the InputMappingsCSVFile. Errors are written to the Log file in CSV format.

#>

Param
 (
  [String]  $InputMappingCSVFile
 )

If(Test-Path $InputMappingCSVFile)
 {
  $arrGroups = @(Import-Csv $InputMappingCSVFile)
 }
  Else
 {
  Write-Error "The import file $InputMappingCSVFile does not exists."
  Exit 1
 }
  
$bolErrorsFound = $False

ForEach($objGroup in $arrGroups)
 {
  Try
   {
    $strOldGroup       = $objGroup.OldGroup
    $strNewGroup       = $objGroup.NewGroup
    If($strOldGroup.Length -eq 0)
     {
      $bolErrorsFound = $True
      Write-EntryToResultsFile -strUserid "" -Result "Error" -Message "There is something wrong with the CSV Mappingsfile $InputMappingCSVFile."
     }
   }
    Catch
   {
    $bolErrorsFound = $True
    Write-EntryToResultsFile -strUserid "" -Result "Error" -Message "There is something wrong with the CSV Mappingsfile $InputMappingCSVFile."
   }
 }
 
 If($bolErrorsFound)
  {
   Export-ResultsLogFileToCSV
   Exit 1
  }

 }

Function Run-ConsistencyCheckOnComputerInputCSVFile
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       03-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Run-ConsistencyCheckOnComputerInputCSVFile
=============================================================================================================================================
.SYNOPSIS

This function checks for errors in the InputCSVFile with the computer information. Errors are written to the Log file in CSV format.

#>

Param
 (
  [String]  $InputCSVFileWithComputers
 )

If(Test-Path $InputCSVFileWithComputers)
 {
  $arrComputers = @(Import-Csv $InputCSVFileWithComputers)
 }
  Else
 {
  Write-Error "The import file $InputCSVFileWithComputers does not exists."
  Exit 1
 }
  
$bolErrorsFound = $False

ForEach($objComputer in $arrComputers)
 {
  $strComputerName    = $objComputer.Computername
  Try
   {
    $strNewOU          = $objUser.NewOU
   }
    Catch
   {
    $bolErrorsFound = $True
    Write-EntryToResultsFile -strUserid "Computer: $strComputerName" -Result "Error" -Message "There is something wrong with the CSV filename $InputCSVFileWithComputers while processing $strComputerName."
   }
 }
 
If($bolErrorsFound)
 {
  Export-ResultsLogFileToCSV
  Exit 1
 }

}

Function Delete-RESSettingsFolder
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       04-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Delete-RESSettingsFolder
=============================================================================================================================================
.SYNOPSIS

This function deletes the RES Settings folder (in other words: rename the folder)

#>

Param
 (
  [String]  $RESettingsFolderPath,
  [String]  $Userid
 )

$strFulllRESSettingsFolderPath = (Add-TrailingBackSlash -AddBackslashTo $RESettingsFolderPath) + $Userid + "\RES Settings"

If(Test-Path($strFulllRESSettingsFolderPath))
 {
  $strNewRESSettingsFolderPath = (Add-TrailingBackSlash -AddBackslashTo $RESettingsFolderPath) + $Userid + "\RES Settings Backup (" + (Get-Date).ToString('G') + ")"
  $strNewRESSettingsFolderPath = $strNewRESSettingsFolderPath -replace ":","-"
  $strNewRESSettingsFolderPath = $strNewRESSettingsFolderPath -replace "/","-"
  Try
   {
    Rename-Item $strFulllRESSettingsFolderPath -NewName $strNewRESSettingsFolderPath -Force -Confirm:$False -WhatIf:(-not($ProductionRun)) | Out-Null
    Write-EntryToResultsFile -strUserid $Userid -Result "Information" -Action "Renaming $strFulllRESSettingsFolderPath to $strNewRESSettingsFolderPath."
   }
    Catch
   {
    Write-EntryToResultsFile -strUserid $Userid -Result "Error" -Action "Renaming $strFulllRESSettingsFolderPath to $strNewRESSettingsFolderPath." -Message $_.Exception.Message
    Continue
   }
 }

}

function Get-DirectAndIndirectMembersOfAnADGroup 
{ 

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       09-May-2018
Created by:       Willem-Jan Vroom based on the script Get-DirectAndIndirectMembersOfAnADGroup as found on TechNet Gallery:
                  https://gallery.technet.microsoft.com/scriptcenter/Get-nested-group-15f725f2
Organization:     
Functionname:     Get-DirectAndIndirectMembersOfAnADGroup
=============================================================================================================================================

.SYNOPSIS
Author: Piotr Lewandowski
Version: 1.01 (04.08.2015) - added displayname to the output, changed name to samaccountname in case of user objects.

.DESCRIPTION
Get nested group membership from a given group or a number of groups.
 
#>

Param 
 ( 
  [Parameter(Mandatory=$true)][String]  $GroupName, 
  [int]$Nesting  = -1, 
  [int]$circular = $null 
 ) 
     
$arrNestedMembers = $null 
$strADGroupname   = $null     
$Nesting         ++   

Try
 {
  $strADGroupname   = get-adgroup $groupname -properties memberof,members 
 }
  Catch
 {
  Write-EntryToResultsFile -strUserid "" -strGroupName $groupname -Message $_.Exception.Message -Action "Group inventory $groupname" -Result "Error"
  Continue
 }

$strMemberOf      = $strADGroupname | select -expand memberof 
 
If($strADGroupname) 
 {  
  If($circular) 
   { 
    $arrNestedMembers = Get-ADGroupMember -Identity $GroupName -recursive 
    $circular = $null 
   } 
    Else 
   { 
    $arrNestedMembers = Get-ADGroupMember -Identity $GroupName | sort objectclass -Descending
    If(-not($arrNestedMembers))
     {
      $unknown = $strADGroupname | select -expand members
      If($unknown)
       {
        $arrNestedMembers=@()
        ForEach($member in $unknown)
         {
          $arrNestedMembers += get-adobject $member
         }
       }
     }
   } 
 
  ForEach($objNestedMember in $arrNestedMembers) 
   {            
    $strType        = $objNestedMember.objectclass
    $strUserid      = $objNestedMember.name
    $strDisplayName = ""
    $strParentGroup = $strADGroupname.name
    $strDN          = $objNestedMember.distinguishedname
    $strComment     = ""
    $strResult      = "Succes"
    $strMessage     = ""
                
    If($objNestedMember.objectclass -eq "user") 
     { 
      $nestedADMember = get-aduser $objNestedMember -properties enabled,displayname
      $strUserid      = $nestedADmember.sAMAccountName
      $strDisplayName = $nestedADmember.displayname
      $strMessage    += "Level: $Nesting"
      If($nestedADMember.Enabled)
       {
        Write-EntryToResultsFile -strUserid $strUserid -strGroupName $strParentGroup -Result $strResult -Action "$strType '$(Get-CanonicalName -Name $strUserid)' member of group '$(Get-CanonicalName -Name $strParentGroup)'" -Message $strMessage
       }
     } 
      ElseIf($objNestedMember.objectclass -eq "group") 
     {  
      If($strMemberOf -contains $objNestedMember.distinguishedname) 
       { 
        $strMessage +="Circular membership" 
        $circular = 1 
       } 
        Else 
       { 
        Write-EntryToResultsFile -strUserid $strUserid -strGroupName $strParentGroup -Result $strResult -Action "$strType '$strUserid' member of group '$strParentGroup'" -Message $strMessage
       } 
      Get-DirectAndIndirectMembersOfAnADGroup -GroupName $objNestedMember.samaccountname -nesting $valNesting -circular $circular 
     } 
      Else 
     { 
      If($objNestedMember)
       {
        Write-Verbose ">> Get-DirectAndIndirectMembersOfAnADGroup: Nested Member"
        Write-EntryToResultsFile -strUserid $strUserid -strGroupName $strParentGroup -Result $strResult -Action "$strType $strDisplayName member of group '$strParentGroup'" -Message $strMessage  
       }
     } 
   } 
 } 

} 


<#
Function Get-IndirectMembersOfAnADGroup
{


.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       04-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Get-IndirectMembersOfAnADGroup
=============================================================================================================================================
.SYNOPSIS

This function gets all the indirect group membmers of an AD group.



param
   (
   [String[]] $GroupNames
   )

   $strActivity         = "Running an indirect group inventory."

 ForEach($objGroupName in $GroupNames)
 {
  Write-Progress -Activity $strActivity -Status "Processing group $($objGroupName) ($valCounter of $($GroupNames.Count))" -PercentComplete ($valCounter / $GroupNames.Count * 100)
  $objGroupName += "*"  
 }
}

#>

Function Get-DirectMembersOfAnADGroup
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       04-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Get-DirectMembersOfAnADGroup
=============================================================================================================================================
.SYNOPSIS

This function gets all the direct group membmers of an AD group.

#>

Param
 (
  [String[]] $GroupNames,
  [String]   $SpecifyObjectClass,
  [Switch]   $ExactMatchRequired,
  [Switch]   $Silent
 )

$arrUserOrGroupItems = @()
$strActivity         = "Running a direct group inventory."

If(-not($Silent))
  {
   $valCounter          = 1
  }
 
 ForEach($objGroupName in $GroupNames)
 {
  If(-not($Silent))
   {
    Write-Progress -Activity $strActivity -Status "Processing group $(Get-CanonicalName -Name $objGroupName) ($valCounter of $($GroupNames.Count))" -PercentComplete ($valCounter / $GroupNames.Count * 100)
   }

  If(-not($ExactMatchRequired))
   {
    $objGroupName += "*"
   }

  If($SpecifyObjectClass -eq "User")
   {
    $arrGroups =  Get-ADGroup -Filter {Name -like $objGroupName}
    ForEach($objGroup in $arrGroups)
    {
     $arrUserOrGroupItems =  Get-ADGroupMember -Identity $objGroup.Name  | Where {$_.ObjectClass -eq 'User'}  | Get-ADUser  -Property Name, sAMAccountName, ObjectClass | Select ObjectClass, Name, sAMAccountName | Sort-Object -Property sAMAccountName
     ForEach($objUserOrGroupItem in $arrUserOrGroupItems)
      {
       Write-EntryToResultsFile -strUserid $objUserOrGroupItem.sAMAccountName -Result "Success" -Action "User '$(Get-CanonicalName -Name $objUserOrGroupItem.sAMAccountName)' member of group '$($objGroup.Name)'." -Message "$($SpecifyObjectClass)" -strGroupName $($objGroup.Name)
      }
    }
   }
    Else
   {
    $arrGroups           = Get-ADGroup -Filter {Name -like $objGroupName}
    ForEach($objGroup in $arrGroups)
     {
      $arrUserOrGroupItems = Get-ADGroupMember -Identity $objGroup.Name | Where {$_.ObjectClass -eq 'Group'} | Get-ADGroup -Property Name, sAMAccountName, ObjectClass | Select ObjectClass, Name | Sort-Object -Property Name
      ForEach($objUserOrGroupItem in $arrUserOrGroupItems)
       {
        Write-EntryToResultsFile -strUserid "" -Result "Success" -Action "Group '$($objUserOrGroupItem.Name)' member of group '$($objGroup.Name)'." -Message "$($SpecifyObjectClass)" -strGroupName $($objGroup.Name)
       }
     }
   }
  
  If(-not($Silent))
   {
    $valCounter ++
   }
 }

}

Function Close-OpenFiles
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       20-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Close-OpenFile
=============================================================================================================================================
.SYNOPSIS

This function closes open files.

#>

Param
 (
  [String]  $Username,
  [String]  $FileToSearchFor
 )

$arrOpenFiles = Get-SmbopenFile | Where-Object {($_.ShareRelativePath -match $FileToSearchFor) -and ($_.Path -match $Username)}

ForEach($objOpenFile in $arrOpenFiles)
 {
  Try
   {
    Close-SmbOpenFile -FileId $objOpenFile.FileID -WhatIf:(-not($ProductionRun)) -Confirm:$false
    Write-EntryToResultsFile -strUserid $Username -Action "Close open file." -Result "Success"
   }
    Catch
   {
    Write-EntryToResultsFile -strUserid $Username -Message $_.Exception.Message -Action "Close open file." -Result "Error"
    Continue
   }
 }

}

Function Get-ExactGroupMembers
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       04-December-2018
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Get-ExactGroupMembers
=============================================================================================================================================
.SYNOPSIS

This function returns all the group membmers, no wildcard is used.

#>

Param
 (
  [String]  $GroupName
 )
 
$arrUserOrGroupItems = @()
   
$strGroupName        =  Get-ADGroup -Filter {Name -eq $GroupName} 
$arrUserOrGroupItems =  Get-ADGroupMember -Identity $strGroupName | Where {$_.ObjectClass -eq 'User'}  | Get-ADUser  -Property DistinguishedName, Name, ObjectClass
$arrUserOrGroupItems += Get-ADGroupMember -Identity $strGroupName | Where {$_.ObjectClass -eq 'Group'} | Get-ADGroup -Property DistinguishedName, Name, ObjectClass

Return $arrUserOrGroupItems

}

Function Copy-GroupsToAnotherUser
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       12-April-2019
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Copy-GroupsToAnotherUser
=============================================================================================================================================
.SYNOPSIS

This function copy all the groups the user belongs to another user.
See it as a 'Copy of Colleague'. 

#>

Param
 (
  [String]  $sourceUser,
  [String]  $destUser
 )

# First: remove all the groups the destination user belongs to

$arrGroups = @()
$arrGroups = (Get-ADPrincipalGroupMembership $destUser).Name

$arrGroups | Sort-Object | Out-Null

ForEach($objGroupName in $arrGroups)
 {
  Remove-ADMemberFromGroup -sAMAccountName $destUser -ADGroupName $objGroupName
 }

# Now copy all the groups from the srcUser to the dstUser

$arrGroups = @()
$arrGroups = (Get-ADPrincipalGroupMembership $sourceUser).Name

$arrGroups | Sort-Object | Out-Null

ForEach($objGroupName in $arrGroups)
 {
  Add-ADMemberToGroup -sAMAccountName $destUser -ADGroupName $objGroupName
 }

}

Function Check-ForEmptyGroups
{

<#
.NOTES
=============================================================================================================================================
Created with:     Windows PowerShell ISE
Created on:       4-June-2019
Created by:       Willem-Jan Vroom
Organization:     
Functionname:     Check-ForEmptyGroup
=============================================================================================================================================
.SYNOPSIS

This function checks if the given group is an empty one.
In case yes the description is altered: ** EMPTY GROUP ** is added.
#>

Param
 (
  [String]  $GroupName
 )

$strTextToAdd       = "** EMPTY GROUP **"
$GroupCanonicalName = Get-CanonicalName -Name $GroupName

if ((Get-ADGroupMember -Identity $GroupName).Count -eq 0)
 {
  If($DeleteEmptyGroups)
   {
    Write-Verbose ">> Check-ForEmptyGroups: the groupname $GroupCanonicalName is deleted."
    Try
     {
      Remove-ADGroup -Identity $GroupName -WhatIf:(-not($ProductionRun)) -Confirm:$false
      $GroupName = (Get-ADGroup -Identity $GroupName -Properties Name).Name
      Write-EntryToResultsFile -strUserid "" -strGroupName $GroupName -Message "" -Action "Deleted the group '$GroupCanonicalName'" -Result "Succes"
     }
      Catch
     {
      Write-EntryToResultsFile -strUserid "" -strGroupName $GroupName -Message $_.Exception.Message -Action "Deleted the group '$GroupCanonicalName'" -Result "Error"
     }
   }
  Else
   {
    $description = (Get-ADGroup -Identity $GroupName -Properties Description).Description
    Write-Verbose ">> Check-ForEmptyGroup: the group '$GroupCanonicalName' has the description '$description'."
    If($description.Length -gt 0)
     {
      If(-not($description.Contains($strTextToAdd)))
       {
        $description = $strTextToAdd + " " + $description
        Try
         {
          Set-ADGroup -Identity $GroupName -Description $description -WhatIf:(-not($ProductionRun)) -Confirm:$false
          $GroupName = (Get-ADGroup -Identity $GroupName -Properties Name).Name
          Write-EntryToResultsFile -strUserid "" -strGroupName $GroupName -Message "" -Action "Updating description '$description' for group '$GroupCanonicalName'" -Result "Succes"
          Write-Verbose ">> Check-ForEmptyGroup: the group '$GroupName' has the new description '$description'."
         }
          Catch
         {
          Write-EntryToResultsFile -strUserid "" -strGroupName $GroupName -Message $_.Exception.Message -Action "Updating description '$description' for group '$GroupCanonicalName'" -Result "Error"
          Write-Verbose ">> Check-ForEmptyGroup: something went wrong while updating the group '$GroupName' with the new description '$description'."
         }
       }
     }
   }
 }
  Else
 {
  $description = (Get-ADGroup -Identity $GroupName -Properties Description).Description 
  Write-Verbose ">> Check-ForEmptyGroup: the group '$GroupName' has the description '$description'." 
  If(-not($description -eq $NULL))
   {
   If($description.Contains($strTextToAdd))
    {
     $strTextToAdd += " "
     $description   = $description.Replace($strTextToAdd,"")
     Try
      {
       Set-ADGroup -Identity $GroupName -Description $description -WhatIf:(-not($ProductionRun)) -Confirm:$false
       $GroupName = (Get-ADGroup -Identity $GroupName -Properties Name).Name
       Write-EntryToResultsFile -strUserid "" -strGroupName $GroupName -Message "" -Action "Updating description to '$description'" -Result "Succes"
       Write-Verbose ">> Check-ForEmptyGroup: the group '$GroupName' has the new description '$description'."
      }
       Catch
      {
       Write-EntryToResultsFile -strUserid "" -strGroupName $GroupName -Message $_.Exception.Message -Action "Updating description to '$description'" -Result "Error"
       Write-Verbose ">> Check-ForEmptyGroup: something went wrong while updating the group '$GroupName' with the new description '$description'."
      }
    }
  }
 }

}

# =============================================================================================================================================
# End function block
# =============================================================================================================================================

# =============================================================================================================================================
# Declares the variables.
# Modify $strPrefixOldGroupName for your own enfironment.
# The array $arrCitrixOU contains the OUs where the old Citrix VDI groups are located. Users are removed from the old Citrix groups
# containing $strCitrixMatch and not containing $strCitrixNotMatch.
#
# To summerize:
#   Users will be removed from the group 'Citrix VDI Windows 7' but not from 'Citrix VDI Windows 7 CAD' if found in one of the groups that can
#   be found in the OU (or OUs) that is specified in $arrCitrixOU.
# =============================================================================================================================================

  $valCounter                          = 1
  $Global:arrTable                     = @()
  $Global:arrTableWithRollBackRecords  = @()
  $arrCitrixOU                         = @("OU=Old OU,OU=OU1,DC=testdomain,DC=local,DC=lan")
  $arrCitrixGroupsToRemoveFromUser     = @("CitrixTestUsers","CitrixUATUsers")
  $strCitrixMatch                      = "Citrix VDI *"
  $strCitrixNotMatch                   = "CAD|PVS"
  $strCurrentPath                      = Split-Path -parent $MyInvocation.MyCommand.Definition
  $strCurrentFile                      = $MyInvocation.MyCommand.Name
  $strPrefixOldGroupName               = "gg_appl_"
  $strPrefixNewGroupName               = "Appl_"
  $arrGroupMustContainForFullCleanUp   = @($strPrefixOldGroupName,"WM-Users")
  $RESPath                             = "\\IvantiServer\data\users"
  
# =============================================================================================================================================
# check the length of the current directory. Stop the script if more that 248 characters. Otherwise the results cannot be written to the given
# folder.
# =============================================================================================================================================

  If($strCurrentPath.Length -gt 248)
   {
    Write-Error "The current directory $strCurrentPath has a length of more than 248 characters. The script will end with exit code 999 now."
    Exit 999
   }

# =============================================================================================================================================
# Find all the arguments and put them in the log file
# Source: https://ss64.com/ps/psboundparameters.html
# =============================================================================================================================================

  Write-EntryToResultsFile -strUserid "" -Result "Information" -Action "Used script" -Message $strCurrentPath + "\" + $strCurrentFile

  ForEach($boundparam in $PSBoundParameters.GetEnumerator()) 
   {
    Write-EntryToResultsFile -strUserid "" -Result "Information" -Action "Key: $($boundparam.Key)" -Message "Value: $($boundparam.Value)"
   }

# =============================================================================================================================================
# Check if the parameter CreateFileWithUseridsInCSVFormat, CreateFileWithComputersInCSVFormat, CreateMappingsfileInCSVFormat, GroupInventory,
# UserInventory, CopyADUser, CopyADUserGroup, ConvertCSVFile or ScanForEmptypGroups is used. 
# In that case, do what you have to do and stop.
# =============================================================================================================================================

  If($CreateFileWithUseridsInCSVFormat)
   {
    $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
    $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
    $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"
    
    $strCSVLogFileSucces = $strCurrentPath + "\" + $LogFilePrefix + "UseridsToMigrate" +$strLastPartOfFileName

    If($IncludeChildOUs)
     {
      Write-Verbose ">> MAIN: Processing OU $srcOU, including the child OU's." 
      Create-FileWithUseridsInCSVFormat -srcOU $srcOU -dstOU $dstOU -Windows10VDIGroup $Windows10VDIGroup -IncChildOU $IncludeChildOUs
     }
      Else
     {
      Write-Verbose ">> MAIN: Processing OU $srcOU." 
      Create-FileWithUseridsInCSVFormat -srcOU $srcOU -dstOU $dstOU -Windows10VDIGroup $Windows10VDIGroup
     }
    Export-ResultsLogFileToCSV
    Exit 0
   }

  If($CreateMappingsfileInCSVFormat)
   {
    $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
    $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
    $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"
    
    $strCSVLogFileSucces = $strCurrentPath + "\" + $LogFilePrefix + "MappingsFileOldToNewGroups" +$strLastPartOfFileName

    If($IncludeChildOUs)
     {
      Write-Verbose ">> MAIN: Processing OU $srcOU, including the child OU's." 
      Create-MappingsFileInCSVFormat -srcOU $srcOU -IncChildOU $IncludeChildOUs
     }
      Else
     {
      Write-Verbose ">> MAIN: Processing OU $srcOU." 
      Create-MappingsFileInCSVFormat -srcOU $srcOU 
     }
    Export-ResultsLogFileToCSV
    Exit 0
   }

  If($CreateFileWithComputersInCSVFormat)
   {
    $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
    $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
    $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"
    
    $strCSVLogFileSucces = $strCurrentPath + "\" + $LogFilePrefix + "ComputersToMigrate" +$strLastPartOfFileName

    If($IncludeChildOUs)
     {
      Write-Verbose ">> MAIN: Processing OU $srcOU, including the child OU's." 
      Create-FileWithComputersInCSVFormat -srcOU $srcOU -dstOU $dstOU -IncChildOU $IncludeChildOUs 
     }
      Else
     {
      Write-Verbose ">> MAIN: Processing OU $srcOU." 
      Create-FileWithComputersInCSVFormat -srcOU $srcOU -dstOU $dstOU
     }
    Export-ResultsLogFileToCSV
    Exit 0
   }

  If($GroupInventory)
   {
    $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
    $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
    $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"
    
    $strCSVLogFileSucces = $strCurrentPath + "\" + "UserAndGroupDiscovery" +$strLastPartOfFileName

    If($GroupNameInventory.Count -gt 0)
     {
      If($RunDirectGroupInventory)
       {
        If($ExactMatch)
         {
          Get-DirectMembersOfAnADGroup -GroupNames $GroupNameInventory -SpecifyObjectClass "User"  -ExactMatchRequired
          Get-DirectMembersOfAnADGroup -GroupNames $GroupNameInventory -SpecifyObjectClass "Group" -ExactMatchRequired
         }
          Else
         {
          Get-DirectMembersOfAnADGroup -GroupNames $GroupNameInventory -SpecifyObjectClass "User" 
          Get-DirectMembersOfAnADGroup -GroupNames $GroupNameInventory -SpecifyObjectClass "Group"
         }
       }
        Else
       { 
        $strActivity = "Exporting direct and indirect group members"
        $valCounter  = 1
        ForEach($objGroupName in $GroupNameInventory)
         {
          Write-Progress -Activity $strActivity -Status "Processing group $objGroupName ($valCounter of $($GroupNameInventory.Count))" -PercentComplete ($valCounter / $GroupNameInventory.Count * 100)
          If($ExactMatch)
           {
            Get-DirectAndIndirectMembersOfAnADGroup -GroupName $objGroupName
           }
            Else
           {
            $objGroupName +="*"
            $arrGroupsT = @(Get-ADGroup -Filter {Name -like $objGroupName} -Properties Name)
            $arrGroupsT | ForEach($_) {Get-DirectAndIndirectMembersOfAnADGroup -GroupName $_}
           }
          $valCounter++
         }
       }
      Export-ResultsLogFileToCSV
     }
      Else
     {    
      $strActivity = "Exporting direct and indirect group members"
      $valCounter  = 1
      If($RunDirectGroupInventory)
       {
        $strActivity = "Exporting direct group members"
       }
      $strSearchScope = "OneLevel"
      If($IncludeChildOUs)
       {
        $strSearchScope = "SubTree"
       }
    
      Try
       {
        $arrGroups = (Get-ADGroup -Filter {GroupCategory -eq 'security'} -SearchBase $srcOU -SearchScope $strSearchScope -Properties Name)
       }
        Catch
       {
        Write-Error "There is an error: $($_.Exception.Message). Check if '$srcOU' exists." -Category ObjectNotFound
        Exit 1
       }

      ForEach($objGroup in $arrGroups)
       {
        Write-Progress -Activity $strActivity -Status "Processing group $($objGroup.Name) ($valCounter of $($arrGroups.Count))" -PercentComplete ($valCounter / $arrGroups.Count * 100)
        If($RunDirectGroupInventory)
         {
          Get-DirectMembersOfAnADGroup -GroupNames $($objGroup.Name) -SpecifyObjectClass "User"  -ExactMatch -Silent
          Get-DirectMembersOfAnADGroup -GroupNames $($objGroup.Name) -SpecifyObjectClass "Group" -ExactMatch -Silent
         }
          Else
         {
          Get-DirectAndIndirectMembersOfAnADGroup -GroupName $objGroup
         }
        $valCounter++
       }
      Export-ResultsLogFileToCSV
    }
   Exit 0
  }
  
  If($UserInventory)
  {
   Clear-Host
   $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
   $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
   $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"
  
   $arrUsers            = @()
   $valCounter          = 1

   If($srcUser.Length -gt 0)
   {
    If(-not($ExactMatch))
     {
      $srcUser            += "*"
     }
    $arrUsers            = @(Get-ADUser -Filter {samaccountname -like $srcUser -and Enabled -eq $True} -Properties SamAccountName,cn | Where {$_.ObjectClass -eq 'User'} | Sort-Object -Property SamAccountName)
    $strActivity         = "User Inventory based on userid"
    $strCSVLogFileSucces = $strCurrentPath + "\" + "UserDiscovery" +$strLastPartOfFileName
   }    
   
   If($srcOU.Length -gt 0)
   {
    $strSearchScope = "OneLevel"
    If($IncludeChildOUs)
    {
     $strSearchScope = "SubTree"
    }
    
    Try
     {
      $arrUsers            = @((Get-ADUser -Filter {Enabled -eq "True"} -SearchScope $strSearchScope -SearchBase $srcOU -Properties cn,sAMAccountName) | Sort-Object -Property cn)
     }
      Catch
     {
      Write-Error "There is an error: $($_.Exception.Message). Check if '$srcOU' exists." -Category ObjectNotFound
      Exit 1
     }
    $strActivity         = "User Inventory based on OU"
    $strCSVLogFileSucces = $strCurrentPath + "\" + "UsersAndGroupsFromAnOU" +$strLastPartOfFileName
   }
      
   ForEach($objUser in $arrUsers)
    {
     Write-Progress -Activity $strActivity -Status "Processing user $(Get-CanonicalName -Name $objUser.SamAccountName) ($valCounter of $($arrUsers.Count))" -PercentComplete ($valCounter / $arrUsers.Count * 100)

     If($RunDirectGroupInventory)
      {
       Show-DirectGroupsInResultsFile -strUserid $objUser.SamAccountName
      }

     If($RunDirectAndIndirectGroupInventory)
      {
       Show-DirectGroupsInResultsFile   -strUserid $objUser.SamAccountName
       Show-IndirectGroupsInResultsFile -strUserid $objUser.SamAccountName
      }
     $valCounter ++
    }
   Export-ResultsLogFileToCSV
   Exit 0
  }

  If($CopyADUserGroup)
   {
    $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ")"
    $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
    $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"

    $strActivity           = "Modifying users in Active Directory."

    If(-not($ProductionRun))
     {
      $strLastPartOfFileName = $strLastPartOfFileName + "(RUNNING IN TEST MODE).csv"
      $strActivity           = $strActivity           + " (RUNNING IN TEST MODE)"
     }
      Else 
     {
      $strLastPartOfFileName = $strLastPartOfFileName + ".csv"
     }
    
    $strCSVLogFileSucces = $strCurrentPath + "\" + "UserAndGroupMigration"     + $strLastPartOfFileName
    $strCSVRollBackFile  = $strCurrentPath + "\" + "RBF_UserAndGroupMigration" + $strLastPartOfFileName

    $ArrUsersAndGroups = Get-ExactGroupMembers -GroupName $OldADUserGroup
   
    $valCounter = 1

    ForEach($objUsersAndGroup in $ArrUsersAndGroups)
     {
      If($objUsersAndGroup.ObjectClass -eq "User")
       { 
        $strUserCanonicalName = Get-ADUser -Identity $objUsersAndGroup.SamAccountName -Properties cn
       }
        Else
       {
        $strUserCanonicalName = Get-ADGroup -Identity $objUsersAndGroup.SamAccountName -Properties cn
       }

      Write-Progress -Activity $strActivity -Status "Processing user $(Get-CanonicalName -Name $strUserCanonicalName.cn) ($valCounter of $($ArrUsersAndGroups.Count))" -PercentComplete ($valCounter / $ArrUsersAndGroups.Count * 100)

      If($objUsersAndGroup.SamAccountName -ne $NewADUserGroup)
       {   
        If($CreateRollBackFile)
         {
          $Global:strForRollBackGroupsToAdd    = ""
          $Global:strForRollBackGroupsToRemove = ""
         }

        Add-ADMemberToGroup -sAMAccountName $objUsersAndGroup.SamAccountName -ADGroupName $NewADUserGroup
        If($DeleteUserFromOldGroup)
         {
          Remove-ADMemberFromGroup -sAMAccountName $objUsersAndGroup.SamAccountName -ADGroupName $OldADUserGroup
         }

        If($CreateRollBackFile)
         {
          Add-RollBackRecord -Userid $objUsersAndGroup.SamAccountName -NewOU "" -VDIGroup "" -GroupsToAdd $Global:strForRollBackGroupsToAdd -GroupsToRemove $Global:strForRollBackGroupsToRemove
         }
       }
        Else
       {
        Write-EntryToResultsFile -strUserid $objUsersAndGroup.SamAccountName -Result "Error" -Action "Adding $($objUsersAndGroup.ObjectClass) $($objUsersAndGroup.SamAccountName) to $($objUsersAndGroup.ObjectClass) $NewADUserGroup." -Message "This is not possible: source and destination group cannot be the same."
       }

      $valCounter++
    }

    Export-ResultsLogFileToCSV
    If($CreateRollBackFile)
     {
      Export-RollBackFileToCSV
     }
    Exit 0
  }

  If($CopyADUser)
   {
    $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ")"
    $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
    $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"

    $strActivity           = "Copy the groups from one user to another user."

    If(-not($ProductionRun))
     {
      $strLastPartOfFileName = $strLastPartOfFileName + "(RUNNING IN TEST MODE).csv"
      $strActivity           = $strActivity           + " (RUNNING IN TEST MODE)"
     }
      Else 
     {
      $strLastPartOfFileName = $strLastPartOfFileName + ".csv"
     }

    If($CreateRollBackFile)
     {
      $Global:strForRollBackGroupsToAdd    = ""
      $Global:strForRollBackGroupsToRemove = ""
     }

   $strCSVLogFileSucces = $strCurrentPath + "\" + "CoC_" + $srcUser + "_to_" + $dstUser + $strLastPartOfFileName
   $strCSVRollBackFile  = $strCurrentPath + "\" + "RBF_" + "CoC_" + $srcUser + "_to_" + $dstUser + $strLastPartOfFileName

   Copy-GroupsToAnotherUser -sourceUser $srcUser -destUser $dstUser

   If($CreateRollBackFile)
    {
     Add-RollBackRecord -Userid $dstUser -NewOU "" -VDIGroup "" -GroupsToAdd $Global:strForRollBackGroupsToAdd -GroupsToRemove $Global:strForRollBackGroupsToRemove
    }

   Export-ResultsLogFileToCSV

   If($CreateRollBackFile)
    {
     Export-RollBackFileToCSV
    }

   Exit 0

  }

  If($ConvertCSVFile)
   {
    $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
    $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
    $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"
    
    $strCurrentPath      = Split-Path $CSVFile 
    $strCSVLogFileSucces = $strCurrentPath + "\" + $LogFilePrefix + "ConvertCSVFile" +$strLastPartOfFileName

    $strCSVExportFile    = $CSVFile -replace ".csv"," (Converted).csv"

    Try
     {
      Import-Csv $CSVFile -Delimiter $Delimeter | Export-Csv $strCSVExportFile -NoTypeInformation
      Write-EntryToResultsFile -strUserid "" -Action "Convert CSV file '$CSVFile' to '$strCSVExportFile'" -Result "Success"
      Export-ResultsLogFileToCSV
     }
      Catch
     {
      Write-EntryToResultsFile -strUserid "" -Action "Convert CSV file '$CSVFile' to '$strCSVExportFile'" -Result "Error" -Message $_.Exception.Message
      Continue
      Export-ResultsLogFileToCSV
     }

   Exit 0

  }
  
  If($ScanForEmptyGroups)
   {
    Clear-Host
    $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
    $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
    $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"
    $strActivity           = "Finding active groups in the OU $(Get-CanonicalName -Name $srcOU)."
    
    $strCSVLogFileSucces = $strCurrentPath + "\" + $LogFilePrefix + "UpdateDescriptionForEmptyGroups" +$strLastPartOfFileName

    $strSearchScope = "OneLevel"
    If($IncludeChildOUs)
     {
      $strSearchScope = "SubTree"
      $strActivity += " (Including child OUs)"
     }

  If(-not($ProductionRun))
   {
    $strActivity += " (RUNNING IN TEST MODE)"
   }

  Try
   {
    $arrGroups = (Get-ADGroup -Filter {GroupCategory -eq 'security'} -SearchBase $srcOU -SearchScope $strSearchScope -Properties Name)
   }
    Catch
   {
    Write-Error "There is an error: $($_.Exception.Message). Check if '$srcOU' exists." -Category ObjectNotFound
    Exit 1
   }
 
  $valCounter  = 1
  
  ForEach($objGroupName in $arrGroups)
   {
    Write-Progress -Activity $strActivity -Status "Processing group $(Get-CanonicalName -Name $objGroupName) ($valCounter of $($arrGroups.Count))" -PercentComplete ($valCounter / $arrGroups.Count * 100)
    Check-ForEmptyGroups -GroupName $objGroupName
    $valCounter  ++
   }
   
   Export-ResultsLogFileToCSV
   Exit 0
 }
     
# =============================================================================================================================================
# Define the CSV Import File. 
# =============================================================================================================================================

If($PerformUserMigration -or $copyProfile)
 {
  If($FileWithUseridsInCSVFormat.Length -eq 0)
   {
    $strCSVFileName = $strCurrentFile -Replace ".ps1",".csv"
   }
    Else
   {
    If($FileWithUseridsInCSVFormat.ToLower().IndexOf(".csv") -eq -1)
     {
      $FileWithUseridsInCSVFormat = $FileWithUseridsInCSVFormat + ".csv"
     }
    $strCSVFileName = $FileWithUseridsInCSVFormat
   }
 }

 If($PerformComputerMigration)
  {
   If($FileWithComputersInCSVFormat.ToLower().IndexOf(".csv") -eq -1)
    {
     $FileWithComputersInCSVFormat = $FileWithComputersInCSVFormat + ".csv"
    }
   $strCSVFileName = $FileWithComputersInCSVFormat
  }

# =============================================================================================================================================
# Check if the string $strCSVFileName is a path. In that case, nothing has to be done.
# In case it is not a path, then the current location should be added.
# =============================================================================================================================================

  If(-not(Split-Path($strCSVFileName)))
   { 
    $strCSVFileName = $strCurrentPath + "\" + $strCSVFileName
   }

# =============================================================================================================================================
# In case of an automated migration check if the string $MappingsFileForAutomaticMigration is a path. In that case, nothing has to be done.
# In case it is not a path, then the current location should be added.
# If the file exists then read all the content, otherwise quit.
# =============================================================================================================================================

  If($MappingsFileForAutomaticMigration.Length -gt 0)
   {
    If(-not(Split-Path($MappingsFileForAutomaticMigration)))
     {
      $MappingsFileForAutomaticMigration = $strCurrentPath + "\" + $MappingsFileForAutomaticMigration
     }
   
    If(Test-Path($MappingsFileForAutomaticMigration))
     {
      $arrGroupNamesForAutomaticMigration = @(Import-Csv $MappingsFileForAutomaticMigration)
      If($arrGroupNamesForAutomaticMigration.Count -eq 0)
       {
        Write-Error "The file $MappingsFileForAutomaticMigration seems to be empty..."
        Exit 1
       }
     }
      Else
     {
      Write-Error "The file $MappingsFileForAutomaticMigration does not exists. Thus quitting."
      Exit 1
     }
   }  

# =============================================================================================================================================
# Define the log file. This log file contains all the results.
# =============================================================================================================================================

  $strLastPartOfFileName = " (" + (Get-Date).ToString('G') + ").csv"
  $strLastPartOfFileName = $strLastPartOfFileName -replace ":","-"
  $strLastPartOfFileName = $strLastPartOfFileName -replace "/","-"
  
  If(-not($ProductionRun))
   {
    $strLastPartOfFileName = " (RUNNING IN TEST MODE)" + $strLastPartOfFileName
   }

  $strCSVLogFileSucces   = $strCSVFileName -Replace ".csv", $strLastPartOfFileName

  $strPathName           = (Split-Path $strCSVLogFileSucces) + "\"
  $strFileName           = $strCSVLogFileSucces.Substring($strPathName.Length,($strCSVLogFileSucces.Length - $strPathName.Length)) 
  
  $strCSVLogFileSucces   = $strPathName + $LogFilePrefix + $strFileName
  If($copyProfile)
   {
    $strCSVLogFileSucces   = $strPathName + $LogFilePrefix + "CopyProfile"+ $strFileName
   }
  
  $strCSVRollBackFile    = $strPathName + "RBF_" + $strFileName
  $valFileLengthLogFile  = $strCSVLogFileSucces.Length

  If($valFileLengthLogFile -gt 260)
   {
    Write-Error "The file name $strCSVLogFileSucces is too long. The maximum file length is 260 characters. This one is $valFileLengthLogFile long.`n No log file is generated, and therefore, this application will quit."
    Exit 2
   }

# =============================================================================================================================================
# Perform the consistency check on the input files
# =============================================================================================================================================

  If($PerformUserMigration)
   {
    Run-ConsistencyCheckOnInputCSVFile -InputCSVFile $strCSVFileName
   }
    Else
   {
    Run-ConsistencyCheckOnComputerInputCSVFile -InputCSVFileWithComputers $strCSVFileName
   }
   
   If($MappingsFileForAutomaticMigration.Length -gt 0)
    {
     Run-ConsistencyCheckOnMappingsCSVFile -InputMappingCSVFile $MappingsFileForAutomaticMigration
    }
 
# If the ProfilePathFrom and ProfilePathTo are filled in, then copy the profile.

  If($ProfilePathFrom.Length -gt 0 -and $ProfilePathTo.Length -gt 0)
   {
    $copyProfile = $True
   }
  
# =============================================================================================================================================
# Read the CSV file.
# Find all the arguments and put them in the log file
# Source: https://ss64.com/ps/psboundparameters.html
# =============================================================================================================================================

  If($PerformUserMigration -or $copyProfile)
   {    
    $arrUserids   = @(Import-Csv $strCSVFileName)
    If($PerformUserMigration -and -not $copyProfile)
     {
      $strActivity = "Modifying users in Active Directory."
     }
      ElseIf($copyProfile -and -not $PerformUserMigration)
     {
      $strActivity = "Copying the users profile."
     }
      Else
     {
      $strActivity = "Modifying users in Active Directory, including copying the users profile."
     }
   }
  ElseIf($PerformComputerMigration)
   {
    $arrComputers = @(Import-Csv $strCSVFileName)
    $strActivity = "Moving computers in Active Directory."
   }

  If(-not($ProductionRun))
   {
    $strActivity += " (RUNNING IN TEST MODE)"
   }

# =============================================================================================================================================
# Process the users or computers in the CSV file.
# =============================================================================================================================================

  Clear-Host

  If ($PerformUserMigration -or $copyProfile)
   {
    ForEach($objUser in $arrUserids)
     {
      $strUserid = $objUser.Userid
      If($strUserid.Length -gt 0)
       {
        $ObjectClass = (Get-ADObject -Filter {SamAccountName -eq $strUserid}).ObjectClass
        If(($ObjectClass -eq "user") -or ($ObjectClass.Length -eq 0))
         {
          Try
           {
            $strUserCanonicalName = Get-ADUser -Identity $strUserid -Properties cn
           }
            Catch
           {
            Write-EntryToResultsFile -strUserid $strUserid -Message $_.Exception.Message -Action "Find user in Active Directory" -Result "Error"
            $valCounter++
            Continue
           }
         }
          Else
         {
          $strUserCanonicalName = Get-ADGroup -Identity $strUserid -Properties cn
         }
        Write-Progress -Activity $strActivity -Status "Processing user $(Get-CanonicalName -Name $strUserCanonicalName.cn) ($valCounter of $($arrUserids.Count))" -PercentComplete ($valCounter / $arrUserids.Count * 100)

        If($CreateRollBackFile)
         {
          $Global:strForRollBackGroupsToAdd    = ""
          $Global:strForRollBackGroupsToRemove = ""
          If($ObjectClass -eq "user")
           {
            $strUserDetails                      = Get-ADUser $strUserid -Properties cn, DistinguishedName
           }
            Else
           {
            $strUserDetails                      = Get-ADGroup $strUserid -Properties cn, DistinguishedName
           }
          $strCurrentUserOU                    = "OU=" + ($strUserDetails.DistinguishedName -split "=",3)[-1]
         }
 
        $strNewOU          = $objUser.NewOU
        $strVDIGroup       = $objUser.VDIGroup
        $arrGroupsToAdd    = $objUser.GroupsToAdd.Split(",")
        $arrGroupsToRemove = $objUser.GroupsToRemove.Split(",")
  
        # Close open files
        If($CloseOpenFiles)
         {
          # Close-OpenFiles -Username $strUserid -FileToSearchFor "docengine.ini"
          # Close-OpenFiles -Username $strUserid -FileToSearchFor "taskinfo.dat"
         } 

        If(-not $CloseOpenFiles -and $PerformUserMigration)
         {
          # Delete the RES Settings folder
          If($DeleteRESSettingsFolder)
           {
            Delete-RESSettingsFolder -RESettingsFolderPath $RESPath -Userid $strUserid
           }
     
          # Clear the profile path
          If($ClearProfilePath)
           {
            Remove-ProfilePathFromUserProfileInAD -strUserid $strUserid
           }
   
          # Clear the homedrive and home directory
          If($ClearHomeFolder)
           {
            Remove-HomeFolderPathFromUserProfileInAD -strUserid $strUserid
           }

          # Move the user to another OU:
          Move-ADUserToOtherOU -strUserid $strUserid -strDestinationOU $strNewOU

          # Remove the user from various groups:
          ForEach($objGroupToRemove in $arrGroupsToRemove)
           {
            Remove-ADMemberFromGroup -sAMAccountName $strUserid -ADGroupName $objGroupToRemove
           }
   
          # Add the user to the new VDI group:
          Add-ADMemberToGroup -sAMAccountName $strUserid -ADGroupName $strVDIGroup
   
          # Add the user to various groups:
          ForEach($objGroupToAdd in $arrGroupsToAdd)
           {
            Add-ADMemberToGroup -sAMAccountName $strUserid -ADGroupName $objGroupToAdd
           }    
         }  

        # Automatic migration

        If($MappingsFileForAutomaticMigration.Length -gt 0 -and -not $CloseOpenFiles)
         {
          $arrGroups = @(Get-ADUser $strUserid -Properties MemberOf).MemberOf
          ForEach($objGroup in $arrGroups)
           {
            $strGroupName = (Get-ADGroup $objGroup).Name
            If($strGroupName.ToLower().IndexOf($strPrefixOldGroupName) -eq 0)
             {
              Migrate-ADUserToNewGroup -strUserid $strUserid -strADGroupName $strGroupName
             }
           }
         }  

        # Full Clean Up
        If($FullCleanUp -and -not $CloseOpenFiles)
         {
          Remove-UserFromMultipleGroups -arrGroupsToSearchFor $arrGroupMustContainForFullCleanUp -strUserid $strUserid
         }

        # Remove old Citrix groups
        If($RemoveOldCitrixGroups -and -not $CloseOpenFiles)
         {
          ForEach($objCitrixOU in $arrCitrixOU)
           {
            $arrWithCitrixGroups = get-adgroup -filter "*" -SearchBase $objCitrixOU | Where-Object {$_.Name -Match $strCitrixMatch -and $_.Name -notmatch $strCitrixNotMatch}
            ForEach($objWithCitrixGroups in $arrWithCitrixGroups)
             {    
              Remove-UserFromMultipleGroups -arrGroupsToSearchFor $objWithCitrixGroups.Name    -strUserid $strUserid
             }
           }

          Remove-UserFromMultipleGroups -arrGroupsToSearchFor $arrCitrixGroupsToRemoveFromUser -strUserid $strUserid
         }

        # Perform the Direct Group Inventory 
        If($RunDirectGroupInventory -and -not $ProductionRun -and -not $CloseOpenFiles)
         {
          Show-DirectGroupsInResultsFile -strUserid $strUserid
         }

        # Perform the Indirect Group Inventory 
        If($RunDirectAndIndirectGroupInventory -and -not $ProductionRun)
         {
          Show-DirectGroupsInResultsFile   -strUserid $strUserid
          Show-IndirectGroupsInResultsFile -strUserid $strUserid
         }

        If($CreateRollBackFile -and -not $CloseOpenFiles)
         {
          Add-RollBackRecord -Userid $strUserid -NewOU $strCurrentUserOU -VDIGroup "" -GroupsToAdd $Global:strForRollBackGroupsToAdd -GroupsToRemove $Global:strForRollBackGroupsToRemove
         }

        # Copy Profile
        If($copyProfile)
         {
          Copy-UserProfile -Userid $strUserid -OldProfilePath $ProfilePathFrom -NewProfilePath $ProfilePathTo
         }
       }
      $valCounter++
     }
   }

  If($PerformComputerMigration)
   {
    $valCounter = 1
    ForEach($objComputer in $arrComputers)
     {
      Write-Progress -Activity $strActivity -Status "Processing computer $($objComputer.ComputerName) ($valCounter of $($arrComputers.Count))" -PercentComplete ($valCounter / $arrComputers.Count * 100)
      Move-ADComputerToOtherOU -strComputerName $($objComputer.ComputerName) -strDestinationOU $($objComputer.NewOU)
      $valCounter++
     }
   } 
  
  If($ProductionRun -and ($RunDirectGroupInventory -or $RunDirectAndIndirectGroupInventory))
   {
    $valCounter = 1
    $maxCounter = 30
    For($valCounter = 1; $valCounter -le $maxCounter;$valCounter++)
     {
      Write-Progress -Activity "Processing changes in Active Directory" -Status "Waiting $valCounter of $maxCounter seconds."  -PercentComplete ($valCounter / $maxCounter * 100)
      Sleep 1
     }
 
   # =============================================================================================================================================
   # Inventory the indirect user groups the user belongs to.
   # This can only be done after updating Active Directory, otherwise incorrect results are shown.
   # =============================================================================================================================================

     $strActivity                       = "Inventory the indirect and / or direct groups the user belongs to."

     If(-not($ProductionRun))
      {
       $strActivity = $strActivity + " (RUNNING IN TEST MODE)"
      }

     $valCounter = 1
     ForEach($objUser in $arrUserids)
      {
       $strUserid = $objUser.Userid
       Try
        {
         $strUserCanonicalName = Get-ADUser -Identity $strUserid -Properties cn
        }
         Catch
        {
         Write-EntryToResultsFile -strUserid $strUserid -Message $_.Exception.Message -Action "Find user in Active Directory" -Result "Error"
         $valCounter++
         Continue
        }
       Write-Progress -Activity $strActivity -Status "Processing user $(Get-CanonicalName -Name $strUserCanonicalName.cn) ($valCounter of $($arrUserids.Count))" -PercentComplete ($valCounter / $arrUserids.Count * 100)

       If($RunDirectGroupInventory)
        {
         Show-DirectGroupsInResultsFile -strUserid $strUserid
        }

       If($RunDirectAndIndirectGroupInventory) 
        {
         Show-DirectGroupsInResultsFile   -strUserid $strUserid
         Show-IndirectGroupsInResultsFile -strUserid $strUserid
        }

       $valCounter++
      }
  }

# =============================================================================================================================================
# Write the results to the csv file.
# =============================================================================================================================================
  
  Export-ResultsLogFileToCSV
   
# =============================================================================================================================================
# Write the Rollback file to a csv file.
# =============================================================================================================================================

  If($CreateRollBackFile)
  {
   Export-RollBackFileToCSV
  }

The script including an overview of all the switches can be found in the attachment.

 

 

 

Attachments:
Download this file (Migrate users (v30).7z)The script and all the switches.[The script and all the switches.]113 kB