functions/Import-T2TAttributes.ps1
Function Import-T2TAttributes { <# .SYNOPSIS The script will create on the target AD On-Prem the MEU objects getting all attribute values from the CSV generated by Export-T2TAttributes command. .DESCRIPTION The script will create on the target AD On-Prem the MEU objects getting all attribute values from the CSV generated by Export-T2TAttributes command. .PARAMETER UPNSuffix Mandatory parameter used to inform which is the UPN domain for the MEU object e.g: contoso.com. .PARAMETER Password Optional parameter if you want to choose a password for all new MEU objects .PARAMETER ResetPassword Optional parameter if you want to require users to reset password in the first sign-in .PARAMETER OU Optional parameter if you want to create MEU objects in a specific OU. Valid values are name, Canonical name, Distinguished name (DN) or GUID. If not defined, the user object will be created on Users container. .PARAMETER OUContacts Optional parameter if you want to create Mail Contacts objects in a specific OU. Valid values are name, Canonical name, Distinguished name (DN) or GUID. If not defined, the user object will be created on Users container. .PARAMETER UserListToImport Optional parameter used to inform which path will be used import the CSV. If no path is chosen, the script will search for UserListToImport.csv file on desktop path. .PARAMETER ContactListToImport Optional parameter used to inform which path will be used import the CSV. If no path is chosen, the script will search for ContactListToImport.csv file on desktop path. .PARAMETER LocalMachineIsNotExchange Optional parameter used to inform that you are running the script from a non-Exchange Server machine. This parameter will require the -ExchangeHostname. .PARAMETER ExchangeHostname Mandatory parameter if the switch -LocalMachineIsNotExchange was used. Used to inform the Exchange Server FQDN that the script will connect. .PARAMETER PreferredDC Preferred domain controller to connect with. Consider using this parameter to avoid replication issues in environments with too many domain controllers. .EXAMPLE PS C:\> Import-T2TAttributes -UPNSuffix "fabrikam.com" -ResetPassword The function will import all users from the file "C:\temp\UserListToImport.csv", create the new MailUsers with the new UPNSuffix of "fabrikam.com", and enable the check mark to "Reset the password on next logon". .EXAMPLE PS C:\> Import-T2TAttributes -UPNSuffix "fabrikam.com" -LocalMachineIsNotExchange -ExchangeHostname "ExServer2" The function will connect to the onprem Exchange Server "ExServer2" and import all users from the file "C:\temp\UserListToImport.csv", create the new MailUsers with the new UPNSuffix of "fabrikam.com", and enable the check mark to "Reset the password on next logon". .NOTES Title: Import-T2TAttributes.ps1 Version: 2.1.4 Date: 2021.01.03 Author: Denis Vilaca Signorelli (denis.signorelli@microsoft.com) Contributors: Agustin Gallegos (agustin.gallegos@microsoft.com) REQUIREMENT 1.To make things easier, run this script from Exchange On-Premises machine powershell, the script will automatically import the Exchange On-Prem module. If you don't want to run the script from an Exchange machine, use the switch -LocalMachineIsNotExchange and enter the Exchange Server hostname. 2.The script encourage you to stop the Azure AD Sync cycle before the execution. The script can disable the sync for you as long as you provide the AzureADConnect hostname. Otherwiser, you can disable by your self manually and then re-run the script. ######################################################################### # This sample script is provided AS IS without warranty of any kind and # # not supported under any Microsoft standard support program or service # ######################################################################### #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseSingularNouns", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidGlobalVars", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingPlainTextForPassword", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingUsernameAndPasswordParams", "")] [CmdletBinding(DefaultParameterSetName="Default")] Param( [Parameter(Mandatory=$true, HelpMessage="Enter UPN suffix of your domain E.g. contoso.com")] [string]$UPNSuffix, [Parameter(Mandatory=$false, HelpMessage="Enter the password for the new MEU objects. If no password is chosen, the script will define '?r4mdon-_p@ss0rd!' as password")] [string]$Password, [Parameter(Mandatory=$false, HelpMessage=" SwitchParameter Require password change on first user access")] [switch]$ResetPassword, [Parameter(Mandatory=$false, HelpMessage="Enter the organization unit that MEU objects will be created. The input is accepted as Name, Canonical name, Distinguished name (DN) or GUID")] [string]$OU, [Parameter(Mandatory=$false, HelpMessage="Enter the organization unit that mail objects will be created. The input is accepted as Name, Canonical name, Distinguished name (DN) or GUID")] [string]$OUContacts, [Parameter(Mandatory=$false, HelpMessage="Enter a custom import path for the csv. if no value is defined the script will search on Desktop path for the UserListToImport.csv")] [string]$UserListToImport, [Parameter(Mandatory=$false, HelpMessage="Enter a custom import path for the csv. if no value is defined the script will search on Desktop path for the ContactListToImport.csv")] [string]$ContactListToImport, [Parameter(ParameterSetName="RemoteExchange",Mandatory=$false, HelpMessage="SwitchParameter to indicate that the machine running the function is not an Exchange Server")] [switch]$LocalMachineIsNotExchange, [Parameter(ParameterSetName="RemoteExchange",Mandatory=$true, HelpMessage="Enter the remote exchange hostname")] [string]$ExchangeHostname, [Parameter(Mandatory=$false, HelpMessage="Enter the preferred domain controller FQDN to connect with")] [string]$PreferredDC ) Set-PSFConfig -FullName PSFramework.Logging.FileSystem.ModernLog -Value $True Write-PSFMessage -Level Output -Message "Starting script. All logs are being saved in: $((Get-PSFConfig PSFramework.Logging.FileSystem.LogPath).Value)" # region global variables $Global:UserListToImport | Out-Null $Global:ContactListToImport | Out-Null $Global:ImportUserList | Out-Null $Global:ImportContactList | Out-Null $Global:OUContacts | Out-Null # region requirements check $UserListToImportCheck = Get-CSVStatus -User if ( $UserListToImportCheck -eq 0 ) {Break} $ContactListToImportCheck = Get-CSVStatus -Contact $AADCStopped = Get-Requirement -Requirements AADConnect if ( $AADCStopped -eq 0 ) {Break} # region local variables [int]$counter = 0 [string]$UPNSuffix = "@$UPNSuffix" $pw = New-Object "System.Security.SecureString"; # check headers to decide if we need to import -IncludeGeneral properties $CheckGeneral = $ImportUserList[0].psobject.Properties | Where-Object {$_.Name -eq "description"} <#if ($CheckGeneral) { [bool]$IncludeGeneral = $True } else { [bool]$IncludeGeneral = $False }#> # check headers to decide if we need to import -IncludeAddress properties $CheckAddress = $ImportUserList[0].psobject.Properties | Where-Object {$_.Name -eq "streetAddress"} <#if ($CheckAddress) { [bool]$IncludeAddress = $True } else { [bool]$IncludeAddress = $False }#> # check headers to decide if we need to import -IncludePhones properties $CheckPhones = $ImportUserList[0].psobject.Properties | Where-Object {$_.Name -eq "telephoneNumber"} <#if ($CheckPhones) { [bool]$IncludePhones = $True } else { [bool]$IncludePhones = $False }#> # check header to decide if we need to import -IncludeAddress properties $CheckOrganization = $ImportUserList[0].psobject.Properties | Where-Object {$_.Name -eq "title"} <#if ($CheckOrganization) { [bool]$IncludeOrganization = $True } else { [bool]$IncludeOrganization = $False }#> # check header to decide if we need to import -IncludeManager properties $IncludeManager = $ImportUserList[0].psobject.Properties | Where-Object {$_.Name -eq "Manager"} <#if ($CheckOrganization) { [bool]$IncludeManager = $True } else { [bool]$IncludeManager = $False }#> if ($Password) { $pwstr = $Password } else { $pwstr = "?r4mdon-_p@ss0rd!" } if ($ResetPassword.IsPresent) { [bool]$resetpwrd = $True } else { [bool]$resetpwrd = $False } # region connections if ($LocalMachineIsNotExchange) { $Global:LocalMachineIsNotExchange = $LocalMachineIsNotExchange $ServicesToConnect = Assert-ServiceConnection -Services AD, ExchangeRemote # Connect to services if ServicesToConnect is not empty if ($ServicesToConnect.Count) { Connect-OnlineServices -Services $ServicesToConnect -ExchangeHostname $ExchangeHostname } } else { $ServicesToConnect = Assert-ServiceConnection -Services ExchangeLocal # Connect to services if ServicesToConnect is not empty if ($ServicesToConnect.Count) { Connect-OnlineServices -Services $ServicesToConnect } } # view entire forest and set the preferred DC. If no preferred # DC was set, use the same that dclocator is already connected if ($PreferredDC) { try { Set-AdServerSettings -ViewEntireForest $true -PreferredServer $PreferredDC -ErrorAction Stop } catch { # if no valid DC is used break and clean up sessions Write-PSFMessage -Level Output -Message "Error: DC was not found. Please run the function again providing a valid Domain Controller FQDN. For example: 'DC01.contoso.com'" Get-PSSession | Remove-PSSession Remove-Variable * -ErrorAction SilentlyContinue Break } } else { $PreferredDC = $env:LogOnServer.Replace("\\","") } # region iterate password For ($i=0; $i -lt $pwstr.Length; $i++) { $pw.AppendChar($pwstr[$i]) } # region iterate MEU $UsersCount = ($ImportUserList | Measure-Object).count ForEach ($user in $ImportUserList) { $counter++ Write-Progress -Activity "Creating MEU objects and importing attributes from CSV" -Status "Working on $($user.DisplayName)" -PercentComplete ($counter * 100 / $UsersCount) $Replace = @{} $tmpUser = $null $UPN = $user.Alias+$UPNSuffix # If OU was passed through param, honor it. # Otherwise create the MEU without OU specification if ($OU) { $tmpUser = New-MailUser -UserPrincipalName $upn -ExternalEmailAddress $user.ExternalEmailAddress ` -FirstName $user.FirstName -LastName $user.LastName -SamAccountName $user.SamAccountName -Alias ` $user.alias -PrimarySmtpAddress $upn -Name $User.Name -DisplayName $user.DisplayName -Password ` $pw -ResetPasswordOnNextLogon $resetpwrd -OrganizationalUnit $OU } else { $tmpUser = New-MailUser -UserPrincipalName $upn -ExternalEmailAddress $user.ExternalEmailAddress -FirstName ` $user.FirstName -LastName $user.LastName -SamAccountName $user.SamAccountName -Alias $user.alias -PrimarySmtpAddress ` $upn -Name $User.Name -DisplayName $user.DisplayName -Password $pw -ResetPasswordOnNextLogon $resetpwrd } # Convert legacyDN to X500 and add all proxyAddresses to array $x500 = "x500:" + $user.legacyExchangeDN $proxy = $user.EmailAddresses.Replace(";",",") $ProxyArray = @() $ProxyArray = $proxy.Split(",") + $x500 # Matching the variable's name to the parameter's name $CustomAttributeParam = @{$user.CustomAttribute = $user.CustomAttributeValue} # Set ExchangeGuid, old LegacyDN as X500 and CustomAttribute $tmpUser | Set-MailUser -ExchangeGuid $user.ExchangeGuid @CustomAttributeParam -EmailAddresses @{Add=$ProxyArray} # Add ELC value to hashtable [void]$Replace.Add("msExchELCMailboxFlags", $user.ELCValue) # Set ArchiveGuid if user has source cloud archive. We don't really care if the # archive will be moved, it's up to the batch to decide, we just import the attribute if ($null -ne $user.ArchiveGuid -and $user.ArchiveGuid -ne '') { $tmpUser | Set-MailUser -ArchiveGuid $user.ArchiveGuid } # region junk hashes. If the user has Junk hash, convert the # HEX string to byte array and add to the $replace hashtable if ($user.SafeSender) { $BytelistSafeSender = New-Object -TypeName System.Collections.Generic.List[System.Byte] $HexStringSafeSender = $user.SafeSender for ($i = 0; $i -lt $HexStringSafeSender.Length; $i += 2) { $HexByteSafeSender = [System.Convert]::ToByte($HexStringsafeSender.Substring($i, 2), 16) $BytelistSafeSender.Add($HexByteSafeSender) } [void]$Replace.Add("msExchSafeSendersHash", $BytelistSafeSender.ToArray()) } if ($user.SafeRecipient) { $BytelistSafeRecipient = New-Object -TypeName System.Collections.Generic.List[System.Byte] $HexStringSafeRecipient = $user.SafeRecipient for ($i = 0; $i -lt $HexStringSafeRecipient.Length; $i += 2) { $HexByteSafeRecipient = [System.Convert]::ToByte($HexStringSafeRecipient.Substring($i, 2), 16) $BytelistSafeRecipient.Add($HexByteSafeRecipient) } [void]$Replace.Add("msExchSafeRecipientsHash", $BytelistSafeRecipient.ToArray()) } if ($user.BlockedSender) { $BytelistBlockedSender = New-Object -TypeName System.Collections.Generic.List[System.Byte] $HexStringBlockedSender = $user.BlockedSender for ($i = 0; $i -lt $HexStringBlockedSender.Length; $i += 2) { $HexByteBlockedSender = [System.Convert]::ToByte($HexStringBlockedSender.Substring($i, 2), 16) $BytelistBlockedSender.Add($HexByteBlockedSender) } [void]$Replace.Add("msExchBlockedSendersHash", $BytelistBlockedSender.ToArray()) } # region import "-Include" values if ($CheckGeneral -or $CheckAddress -or $CheckPhones -or $CheckOrganization) { [void](Import-ADPersonalAttribute) } # region set $replace hashtable to ADUser if ($Replace.Count -gt 0 -and $LocalMachineIsNotExchange.IsPresent -and $Null -eq $LocalAD) { Set-RemoteADUser -Identity $user.SamAccountName -Server $PreferredDC -Replace $Replace } elseif ($Replace.Count -gt 0) { Set-ADUser -Identity $user.SamAccountName -Server $PreferredDC -Replace $Replace } Write-PSFMessage -Level InternalComment -Message "$($user.alias) MailUser successfully created." } # region import Manager value if the CSV contains the manager header if ($IncludeManager) { Import-Manager -ObjType MEU } # region export Mail Contacts if ($ContactListToImportCheck -eq 1) { Move-Contact -Sync Import } Write-PSFMessage -Level Output -Message "The import is completed. Please confirm that all users are correctly created before enable the Azure AD Sync Cycle." Write-PSFMessage -Level Output -Message "You can re-enable Azure AD Connect using the following cmdlet: 'Set-ADSyncScheduler -SyncCycleEnabled 1'" # region clean up variables and sessions Get-PSSession | Remove-PSSession Remove-Variable * -ErrorAction SilentlyContinue } |